diff --git a/packages/open-scd/src/addons/Settings.ts b/packages/open-scd/src/addons/Settings.ts index 53e6dfdacb..715f036f15 100644 --- a/packages/open-scd/src/addons/Settings.ts +++ b/packages/open-scd/src/addons/Settings.ts @@ -382,7 +382,8 @@ export class OscdSettings extends LitElement { } render(): TemplateResult { - return html` + - ${getTheme(this.settings.theme)}`; } diff --git a/packages/open-scd/src/addons/Waiter.ts b/packages/open-scd/src/addons/Waiter.ts index 2296412bf1..5f7e4c82ed 100644 --- a/packages/open-scd/src/addons/Waiter.ts +++ b/packages/open-scd/src/addons/Waiter.ts @@ -35,7 +35,7 @@ export class OscdWaiter extends LitElement { } render(): TemplateResult { - return html` + return html` `; } diff --git a/packages/open-scd/src/editors/substation/zeroline-pane.ts b/packages/open-scd/src/editors/substation/zeroline-pane.ts index 3b79df1fd9..1a324c02dd 100644 --- a/packages/open-scd/src/editors/substation/zeroline-pane.ts +++ b/packages/open-scd/src/editors/substation/zeroline-pane.ts @@ -27,10 +27,10 @@ import { selectGseControlWizard } from '../../wizards/gsecontrol.js'; import { emptyWizard, wizards } from '../../wizards/wizard-library.js'; import { getAttachedIeds } from './foundation.js'; import { selectSampledValueControlWizard } from '../../wizards/sampledvaluecontrol.js'; -import { Settings } from '../../Setting.js'; import { selectReportControlWizard } from '../../wizards/reportcontrol.js'; import { SCLTag, tags } from '../../foundation.js'; +import { Settings } from '../../addons/Settings.js'; function shouldShowIEDs(): boolean { return localStorage.getItem('showieds') === 'on'; diff --git a/packages/open-scd/src/open-scd.ts b/packages/open-scd/src/open-scd.ts index 0899c42900..238a502748 100644 --- a/packages/open-scd/src/open-scd.ts +++ b/packages/open-scd/src/open-scd.ts @@ -5,26 +5,65 @@ import { LitElement, property, TemplateResult, + query, } from 'lit-element'; import { ListItem } from '@material/mwc-list/mwc-list-item'; -import { newOpenDocEvent, newPendingStateEvent } from './foundation.js'; +import '@material/mwc-icon'; +import '@material/mwc-icon-button'; +import '@material/mwc-linear-progress'; +import '@material/mwc-list'; +import '@material/mwc-list/mwc-list-item'; +import '@material/mwc-tab'; +import '@material/mwc-tab-bar'; +import '@material/mwc-top-app-bar-fixed'; +import '@material/mwc-drawer'; + +import { + newOpenDocEvent, + newPendingStateEvent, + newSettingsUIEvent, +} from './foundation.js'; import { Editing } from './Editing.js'; -import { Hosting } from './Hosting.js'; import { Historing } from './Historing.js'; -import { Plugging } from './Plugging.js'; +import { Plugging, Plugin, pluginIcons } from './Plugging.js'; import { Wizarding } from './Wizarding.js'; import './addons/Settings.js'; import './addons/Waiter.js'; +import { ActionDetail, List } from '@material/mwc-list'; +import { Drawer } from '@material/mwc-drawer'; +import { translate } from 'lit-translate'; + +// HOSTING INTERFACES + +interface MenuItem { + icon: string; + name: string; + hint?: string; + actionItem?: boolean; + action?: (event: CustomEvent) => void; + disabled?: () => boolean; + content?: TemplateResult; + kind: string; +} + +interface Validator { + validate: () => Promise; +} + +interface MenuPlugin { + run: () => Promise; +} + /** The `` custom element is the main entry point of the * Open Substation Configuration Designer. */ @customElement('open-scd') -export class OpenSCD extends Hosting( - Wizarding(Plugging(Editing(Historing(LitElement)))) +export class OpenSCD extends Wizarding( + Plugging(Editing(Historing(LitElement))) ) { private currentSrc = ''; /** The current file's URL. `blob:` URLs are *revoked after parsing*! */ @@ -87,9 +126,39 @@ export class OpenSCD extends Hosting( document.onkeydown = this.handleKeyPress; } + connectedCallback(): void { + super.connectedCallback(); + this.addEventListener('validate', async () => { + this.shouldValidate = true; + await this.validated; + + if (!this.shouldValidate) return; + + this.diagnoses.clear(); + this.shouldValidate = false; + + this.validated = Promise.allSettled( + this.menuUI + .querySelector('mwc-list')! + .items.filter(item => item.className === 'validator') + .map(item => + ((item.nextElementSibling)).validate() + ) + ).then(); + this.dispatchEvent(newPendingStateEvent(this.validated)); + }); + this.addEventListener('close-drawer', async () => { + this.menuUI.open = false; + }); + } + + protected renderMain(): TemplateResult { + return html`${this.renderHosting()}${super.render()}`; + } + render(): TemplateResult { return html` - ${super.render()} + ${this.renderMain()} `; } @@ -183,4 +252,281 @@ export class OpenSCD extends Hosting( display: flex; } `; + + // HOSTING + /** The currently active editor tab. */ + @property({ type: Number }) + activeTab = 0; + @property({ attribute: false }) + validated: Promise = Promise.resolve(); + + private shouldValidate = false; + + @query('#menu') menuUI!: Drawer; + + get menu(): (MenuItem | 'divider')[] { + const topMenu: (MenuItem | 'divider')[] = []; + const middleMenu: (MenuItem | 'divider')[] = []; + const bottomMenu: (MenuItem | 'divider')[] = []; + const validators: (MenuItem | 'divider')[] = []; + + this.topMenu.forEach(plugin => + topMenu.push({ + icon: plugin.icon || pluginIcons['menu'], + name: plugin.name, + action: ae => { + this.dispatchEvent( + newPendingStateEvent( + (( + (( + (ae.target).items[ae.detail.index].nextElementSibling + )) + )).run() + ) + ); + }, + disabled: (): boolean => plugin.requireDoc! && this.doc === null, + content: plugin.content, + kind: 'top', + }) + ); + + this.middleMenu.forEach(plugin => + middleMenu.push({ + icon: plugin.icon || pluginIcons['menu'], + name: plugin.name, + action: ae => { + this.dispatchEvent( + newPendingStateEvent( + (( + (( + (ae.target).items[ae.detail.index].nextElementSibling + )) + )).run() + ) + ); + }, + disabled: (): boolean => plugin.requireDoc! && this.doc === null, + content: plugin.content, + kind: 'middle', + }) + ); + + this.bottomMenu.forEach(plugin => + bottomMenu.push({ + icon: plugin.icon || pluginIcons['menu'], + name: plugin.name, + action: ae => { + this.dispatchEvent( + newPendingStateEvent( + (( + (( + (ae.target).items[ae.detail.index].nextElementSibling + )) + )).run() + ) + ); + }, + disabled: (): boolean => plugin.requireDoc! && this.doc === null, + content: plugin.content, + kind: 'middle', + }) + ); + + this.validators.forEach(plugin => + validators.push({ + icon: plugin.icon || pluginIcons['validator'], + name: plugin.name, + action: ae => { + if (this.diagnoses.get(plugin.src)) + this.diagnoses.get(plugin.src)!.length = 0; + + this.dispatchEvent( + newPendingStateEvent( + (( + (( + (ae.target).items[ae.detail.index].nextElementSibling + )) + )).validate() + ) + ); + }, + disabled: (): boolean => this.doc === null, + content: plugin.content, + kind: 'validator', + }) + ); + + if (middleMenu.length > 0) middleMenu.push('divider'); + if (bottomMenu.length > 0) bottomMenu.push('divider'); + + console.log('menu'); + return [ + 'divider', + ...topMenu, + 'divider', + { + icon: 'undo', + name: 'undo', + actionItem: true, + action: this.undo, + disabled: (): boolean => !this.canUndo, + kind: 'static', + }, + { + icon: 'redo', + name: 'redo', + actionItem: true, + action: this.redo, + disabled: (): boolean => !this.canRedo, + kind: 'static', + }, + ...validators, + { + icon: 'list', + name: 'menu.viewLog', + actionItem: true, + action: (): void => this.logUI.show(), + kind: 'static', + }, + { + icon: 'history', + name: 'menu.viewHistory', + actionItem: true, + action: (): void => this.historyUI.show(), + kind: 'static', + }, + { + icon: 'rule', + name: 'menu.viewDiag', + actionItem: true, + action: (): void => this.diagnosticUI.show(), + kind: 'static', + }, + 'divider', + ...middleMenu, + { + icon: 'settings', + name: 'settings.title', + action: (): void => { + this.dispatchEvent(newSettingsUIEvent(true)); + }, + kind: 'static', + }, + ...bottomMenu, + { + icon: 'extension', + name: 'plugins.heading', + action: (): void => this.pluginUI.show(), + kind: 'static', + }, + ]; + } + + renderMenuItem(me: MenuItem | 'divider'): TemplateResult { + if (me === 'divider') + return html`
  • `; + if (me.actionItem) return html``; + return html` + ${me.icon} + ${translate(me.name)} + ${me.hint + ? html`${me.hint}` + : ''} + + ${me.content ?? ''} + `; + } + + renderActionItem(me: MenuItem | 'divider'): TemplateResult { + if (me !== 'divider' && me.actionItem) + return html``; + else return html``; + } + + renderEditorTab({ name, icon }: Plugin): TemplateResult { + return html` + `; + } + + renderHosting(): TemplateResult { + return html` + ${translate('menu.title')} + ${this.docName + ? html`${this.docName}` + : ''} + ) => { + //FIXME: dirty hack to be fixed in open-scd-core + // if clause not necessary when oscd... components in open-scd not list + if (ae.target instanceof List) + (( + this.menu.filter( + item => item !== 'divider' && !item.actionItem + )[ae.detail.index] + ))?.action?.(ae); + }} + > + ${this.menu.map(this.renderMenuItem)} + + + + (this.menuUI.open = true)} + > +
    ${this.docName}
    + ${this.menu.map(this.renderActionItem)} + ${this.doc + ? html` + (this.activeTab = e.detail.index)} + > + ${this.editors.map(this.renderEditorTab)} + ` + : ``} +
    +
    + + ${this.doc && this.editors[this.activeTab]?.content + ? this.editors[this.activeTab].content + : html`
    + ${(this.menu.filter(mi => mi !== 'divider')).map( + (mi: MenuItem, index) => + mi.kind === 'top' && !mi.disabled?.() + ? html` + +
    ${mi.name}
    +
    + ` + : html`` + )} +
    `}`; + } } diff --git a/packages/open-scd/test/unit/__snapshots__/Setting.test.snap.js b/packages/open-scd/test/unit/__snapshots__/Setting.test.snap.js index 6433cc078a..25a8d6a9f0 100644 --- a/packages/open-scd/test/unit/__snapshots__/Setting.test.snap.js +++ b/packages/open-scd/test/unit/__snapshots__/Setting.test.snap.js @@ -1,370 +1,10 @@ /* @web/test-runner snapshot v1 */ export const snapshots = {}; -snapshots["SettingElement saves chosen .nsdoc file and looks like latest snapshot"] = -` -
    - - - English - - - German (Deutsch) - - - - - - - - - - - - - - -
    - - -
    -

    - Uploaded NSDoc files -

    - - - -
    - - - - IEC 61850-7-2 - - - 2007B3 - - - done - - - delete - - - - - IEC 61850-7-3 - - - close - - - - - IEC 61850-7-4 - - - close - - - - - IEC 61850-8-1 - - - close - - - - - Cancel - - - Reset - - - Save - -
    -`; -/* end snapshot SettingElement saves chosen .nsdoc file and looks like latest snapshot */ - -snapshots["SettingElement deletes a chosen .nsdoc file and looks like latest snapshot"] = -` -
    - - - English - - - German (Deutsch) - - - - - - - - - - - - - - -
    - - -
    -

    - Uploaded NSDoc files -

    - - - -
    - - - - IEC 61850-7-2 - - - close - - - - - IEC 61850-7-3 - - - close - - - - - IEC 61850-7-4 - - - close - - - - - IEC 61850-8-1 - - - close - - - - - Cancel - - - Reset - - - Save - -
    -`; -/* end snapshot SettingElement deletes a chosen .nsdoc file and looks like latest snapshot */ - snapshots["OSCD-Settings saves chosen .nsdoc file and looks like latest snapshot"] = -` + + - - `; /* end snapshot OSCD-Settings saves chosen .nsdoc file and looks like latest snapshot */ snapshots["OSCD-Settings deletes a chosen .nsdoc file and looks like latest snapshot"] = -` + + - - `; /* end snapshot OSCD-Settings deletes a chosen .nsdoc file and looks like latest snapshot */