@omicronenergy/oscd-shell 0.0.9 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/foundation/cyrb64.js +0 -3
  2. package/dist/foundation/cyrb64.js.map +1 -1
  3. package/dist/foundation/events.d.ts +17 -0
  4. package/dist/foundation/events.js +2 -0
  5. package/dist/foundation/events.js.map +1 -0
  6. package/dist/foundation.js.map +1 -1
  7. package/dist/landing-page/landing-page.d.ts +26 -0
  8. package/dist/landing-page/landing-page.js +163 -0
  9. package/dist/landing-page/landing-page.js.map +1 -0
  10. package/dist/landing-page/landing-page.spec.d.ts +1 -0
  11. package/dist/landing-page/landing-page.spec.js +87 -0
  12. package/dist/landing-page/landing-page.spec.js.map +1 -0
  13. package/dist/locales/de.d.ts +0 -5
  14. package/dist/locales/de.js +0 -5
  15. package/dist/locales/de.js.map +1 -1
  16. package/dist/locales.js.map +1 -1
  17. package/dist/localization.d.ts +15 -0
  18. package/dist/localization.js +22 -0
  19. package/dist/localization.js.map +1 -0
  20. package/dist/menus/files-menu.d.ts +27 -0
  21. package/dist/menus/files-menu.js +102 -0
  22. package/dist/menus/files-menu.js.map +1 -0
  23. package/dist/menus/files-menu.spec.d.ts +1 -0
  24. package/dist/menus/files-menu.spec.js +33 -0
  25. package/dist/menus/files-menu.spec.js.map +1 -0
  26. package/dist/menus/plugins-menu.d.ts +32 -0
  27. package/dist/menus/plugins-menu.js +128 -0
  28. package/dist/menus/plugins-menu.js.map +1 -0
  29. package/dist/menus/plugins-menu.spec.d.ts +1 -0
  30. package/dist/menus/plugins-menu.spec.js +65 -0
  31. package/dist/menus/plugins-menu.spec.js.map +1 -0
  32. package/dist/oscd-shell.d.ts +70 -84
  33. package/dist/oscd-shell.js +422 -511
  34. package/dist/oscd-shell.js.map +1 -1
  35. package/dist/side-panel/editor-plugins-panel.d.ts +29 -0
  36. package/dist/side-panel/editor-plugins-panel.js +151 -0
  37. package/dist/side-panel/editor-plugins-panel.js.map +1 -0
  38. package/dist/side-panel/editor-plugins-panel.spec.d.ts +1 -0
  39. package/dist/side-panel/editor-plugins-panel.spec.js +87 -0
  40. package/dist/side-panel/editor-plugins-panel.spec.js.map +1 -0
  41. package/dist/utils/plugin-utils.d.ts +1 -1
  42. package/dist/utils/plugin-utils.js +8 -11
  43. package/dist/utils/plugin-utils.js.map +1 -1
  44. package/dist/utils/plugin-utils.spec.js.map +1 -1
  45. package/dist/utils/testing/plugin-helpers.d.ts +24 -0
  46. package/dist/utils/testing/plugin-helpers.js +74 -0
  47. package/dist/utils/testing/plugin-helpers.js.map +1 -0
  48. package/dist/utils/testing/test-doc-helpers.d.ts +7 -0
  49. package/dist/utils/testing/test-doc-helpers.js +28 -0
  50. package/dist/utils/testing/test-doc-helpers.js.map +1 -0
  51. package/dist/utils/testing/test-plugins.d.ts +44 -0
  52. package/dist/utils/testing/test-plugins.js +56 -0
  53. package/dist/utils/testing/test-plugins.js.map +1 -0
  54. package/package.json +62 -125
  55. package/dist/AddPlugins.js +0 -39
  56. package/dist/DemoPluginSrc.js +0 -7
  57. package/dist/embedded.html +0 -17
  58. package/dist/index.html +0 -112
@@ -1,186 +1,86 @@
1
- var _OpenSCD_plugins, _OpenSCD_actions;
2
- import { __classPrivateFieldGet, __classPrivateFieldSet, __decorate } from "tslib";
3
- import '@webcomponents/scoped-custom-element-registry';
4
- import { configureLocalization, localized, msg } from '@lit/localize';
1
+ import { __decorate } from "tslib";
5
2
  import { css, html, LitElement, nothing } from 'lit';
6
- import { customElement, property, query, state } from 'lit/decorators.js';
3
+ import { customElement, property, query, queryAssignedNodes, state, } from 'lit/decorators.js';
4
+ import { ScopedElementsMixin } from '@open-wc/scoped-elements/lit-element.js';
5
+ import { localized, msg } from '@lit/localize';
7
6
  import { html as staticHtml, unsafeStatic } from 'lit/static-html.js';
8
- import '@omicronenergy/oscd-ui/app-bar/oscd-app-bar.js';
9
- import '@omicronenergy/oscd-ui/dialog/oscd-dialog.js';
10
- import '@omicronenergy/oscd-ui/divider/oscd-divider.js';
11
- import '@omicronenergy/oscd-ui/iconbutton/oscd-filled-icon-button.js';
12
- import '@omicronenergy/oscd-ui/select/oscd-filled-select.js';
13
- import '@omicronenergy/oscd-ui/textfield/oscd-filled-text-field.js';
14
- import '@omicronenergy/oscd-ui/icon/oscd-icon.js';
15
- import '@omicronenergy/oscd-ui/list/oscd-list.js';
16
- import '@omicronenergy/oscd-ui/list/oscd-list-item.js';
17
- import '@omicronenergy/oscd-ui/menu/oscd-menu.js';
18
- import '@omicronenergy/oscd-ui/menu/oscd-menu-item.js';
19
- import '@omicronenergy/oscd-ui/navigation-drawer/oscd-navigation-drawer.js';
20
- import '@omicronenergy/oscd-ui/navigation-drawer/oscd-navigation-drawer-header.js';
21
- import '@omicronenergy/oscd-ui/tabs/oscd-secondary-tab.js';
22
- import '@omicronenergy/oscd-ui/select/oscd-select-option.js';
23
- import '@omicronenergy/oscd-ui/tabs/oscd-tabs.js';
24
- import '@omicronenergy/oscd-ui/button/oscd-text-button.js';
7
+ import { OscdFilledIconButton } from '@omicronenergy/oscd-ui/iconbutton/OscdFilledIconButton.js';
25
8
  import { XMLEditor } from '@omicronenergy/oscd-editor';
26
- import { sourceLocale, targetLocales } from './locales.js';
27
9
  import { loadSourcedPlugins } from './utils/plugin-utils.js';
28
- const _customElementsDefine = window.customElements.define;
29
- window.customElements.define = (name, cl, conf) => {
30
- if (!customElements.get(name)) {
31
- _customElementsDefine.call(window.customElements, name, cl, conf);
32
- }
33
- };
34
- const { getLocale, setLocale } = configureLocalization({
35
- sourceLocale,
36
- targetLocales,
37
- loadLocale: locale => import(new URL(`./locales/${locale}.js`, import.meta.url).href),
38
- });
39
- function renderActionItem(control, slot = 'actionItems') {
40
- return html `<oscd-filled-icon-button
41
- slot="${slot}"
42
- aria-label="${control.getName()}"
43
- ?disabled=${control.isDisabled()}
44
- @click=${control.action}
45
- ><oscd-icon>${control.icon}</oscd-icon></oscd-filled-icon-button
46
- >`;
47
- }
48
- function renderMenuItem(control) {
49
- return html `
50
- <oscd-list-item
51
- type="button"
52
- .disabled=${control.isDisabled()}
53
- @click=${control.action}
54
- >
55
- <oscd-icon slot="start">${control.icon}</oscd-icon>
56
- <span>${control.getName()}</span>
57
- </oscd-list-item>
58
- `;
59
- }
60
- let OpenSCD = class OpenSCD extends LitElement {
61
- isEditable(docName) {
62
- return !!this.editable.find(ext => docName.toLowerCase().endsWith(`.${ext}`));
63
- }
64
- get editableDocs() {
65
- return Object.keys(this.docs).filter(name => this.isEditable(name));
66
- }
67
- /** The set of `XMLDocument`s currently loaded */
68
- get docs() {
69
- return this._docs;
70
- }
71
- set docs(newDocs) {
72
- this._docs = newDocs;
73
- this.onDocsChanged();
74
- }
75
- onDocsChanged() {
76
- this.docVersion += 1;
77
- }
78
- get doc() {
79
- return this.docs[this.docName];
80
- }
81
- get last() {
82
- return this.xmlEditor.past.length - 1;
83
- }
84
- get canUndo() {
85
- return this.xmlEditor.past.length >= 0;
10
+ import { getLocale, setLocale, } from './localization.js';
11
+ import { EditorPluginsPanel } from './side-panel/editor-plugins-panel.js';
12
+ import { PluginsMenu } from './menus/plugins-menu.js';
13
+ import { LandingPage } from './landing-page/landing-page.js';
14
+ import { FilesMenu } from './menus/files-menu.js';
15
+ import { OscdAppBar } from '@omicronenergy/oscd-ui/app-bar/OscdAppBar.js';
16
+ import { OscdIcon } from '@omicronenergy/oscd-ui/icon/OscdIcon.js';
17
+ let OscdShell = class OscdShell extends ScopedElementsMixin(LitElement) {
18
+ get locale() {
19
+ return getLocale();
86
20
  }
87
- get canRedo() {
88
- return this.xmlEditor.future.length >= 0;
21
+ set locale(tag) {
22
+ try {
23
+ if (tag) {
24
+ setLocale(tag);
25
+ }
26
+ }
27
+ catch {
28
+ // don't change locale if tag is invalid
29
+ }
89
30
  }
90
31
  get plugins() {
91
- return __classPrivateFieldGet(this, _OpenSCD_plugins, "f");
32
+ return this._plugins;
92
33
  }
93
34
  set plugins(plugins) {
94
- __classPrivateFieldSet(this, _OpenSCD_plugins, Object.entries(plugins).reduce((acc, [pluginType, kind]) => {
95
- const convertedPlugins = loadSourcedPlugins(kind);
35
+ this._plugins = Object.entries(plugins).reduce((acc, [pluginType, kind]) => {
36
+ const convertedPlugins = loadSourcedPlugins(kind, this.registry);
96
37
  acc[pluginType] = convertedPlugins;
97
38
  return acc;
98
- }, { menu: [], editor: [], background: [] }), "f");
39
+ }, { menu: [], editor: [], background: [] });
99
40
  }
100
- handleOpenDoc({ detail: { docName, doc } }) {
101
- this.docs = {
102
- ...this.docs,
103
- [docName]: doc,
104
- };
105
- if (this.isEditable(docName)) {
106
- this.docName = docName;
107
- }
108
- this.requestUpdate();
41
+ /*
42
+ * States
43
+ */
44
+ get canRedo() {
45
+ return this.xmlEditor.future.length >= 1;
109
46
  }
110
- /** Undo the last `n` [[Edit]]s committed */
111
- undo(n = 1) {
112
- if (!this.canUndo || n < 1) {
113
- return;
114
- }
115
- this.xmlEditor.undo();
116
- if (n > 1) {
117
- this.undo(n - 1);
118
- }
119
- this.requestUpdate();
47
+ get canUndo() {
48
+ return this.xmlEditor.past.length >= 1;
120
49
  }
121
- /** Redo the last `n` [[Edit]]s that have been undone */
122
- redo(n = 1) {
123
- if (!this.canRedo || n < 1) {
124
- return;
125
- }
126
- this.xmlEditor.redo();
127
- if (n > 1) {
128
- this.redo(n - 1);
129
- }
130
- this.requestUpdate();
50
+ get editor() {
51
+ return this.plugins.editor[this.editorIndex]?.tagName ?? '';
131
52
  }
132
- get locale() {
133
- return getLocale();
53
+ get doc() {
54
+ return this.docs[this.docName];
134
55
  }
135
- set locale(tag) {
136
- try {
137
- setLocale(tag);
138
- }
139
- catch {
140
- // don't change locale if tag is invalid
141
- }
56
+ get docs() {
57
+ return this._docs;
142
58
  }
143
- get editor() {
144
- return this.editors[this.editorIndex]?.tagName ?? '';
145
- }
146
- get menu() {
147
- return (this.plugins.menu
148
- ?.map(({ name, tagName, requireDoc, icon, translations }) => ({
149
- icon,
150
- getName: () => translations?.[this.locale] ||
151
- name,
152
- isDisabled: () => (requireDoc && !this.docName) ?? false,
153
- tagName,
154
- action: () => {
155
- if (tagName) {
156
- this.shadowRoot.querySelector(tagName).run?.();
157
- this.menuUI.opened = false;
158
- }
159
- },
160
- }))
161
- .filter(p => p !== undefined)).concat(__classPrivateFieldGet(this, _OpenSCD_actions, "f"));
162
- }
163
- get editors() {
164
- return this.plugins.editor?.map(({ name, tagName, requireDoc, icon, translations, }) => ({
165
- icon,
166
- getName: () => translations?.[this.locale] ||
167
- name,
168
- isDisabled: () => (requireDoc && !this.docName) ?? false,
169
- tagName,
170
- }));
171
- }
172
- handleKeyPress(e) {
173
- if (!e.ctrlKey) {
174
- return;
175
- }
176
- if (!Object.prototype.hasOwnProperty.call(this.hotkeys, e.key)) {
177
- return;
178
- }
179
- this.hotkeys[e.key]();
180
- e.preventDefault();
59
+ set docs(newDocs) {
60
+ this._docs = newDocs;
61
+ this.onDocsChanged();
62
+ }
63
+ get editableDocs() {
64
+ return Object.keys(this.docs).filter(name => this.isEditable(name));
181
65
  }
66
+ get last() {
67
+ return this.xmlEditor.past.length - 1;
68
+ }
69
+ /*
70
+ * Constructor & life cycle methods
71
+ */
182
72
  constructor() {
183
73
  super();
74
+ /*
75
+ * Properties
76
+ */
77
+ /**
78
+ * Url to the app icon displayed in the app bar
79
+ */
80
+ this.appIcon = '';
81
+ this.appTitle = 'OpenSCD';
82
+ this.landingPageHeading = 'Welcome to OpenSCD';
83
+ this.landingPageSubHeading = 'Open Source IEC-61850-6 SCL Editing Platform';
184
84
  /** The file endings of editable files */
185
85
  this.editable = [
186
86
  'cid',
@@ -190,72 +90,129 @@ let OpenSCD = class OpenSCD extends LitElement {
190
90
  'sed',
191
91
  'ssd',
192
92
  ];
193
- this._docs = {};
93
+ this._plugins = { menu: [], editor: [], background: [] };
94
+ this.editorIndex = 0;
194
95
  /** The name of the [[`doc`]] currently being edited */
195
96
  this.docName = '';
196
- this.xmlEditor = new XMLEditor();
97
+ /** The set of `XMLDocument`s currently loaded */
98
+ this._docs = {};
197
99
  this.docVersion = -1;
198
- _OpenSCD_plugins.set(this, { menu: [], editor: [], background: [] });
199
- this.editorIndex = 0;
200
- this.controls = {
201
- undo: {
202
- icon: 'undo',
203
- getName: () => msg('Undo'),
204
- action: () => {
205
- this.undo();
206
- this.menuUI.opened = false;
207
- },
208
- isDisabled: () => !this.canUndo,
209
- },
210
- redo: {
211
- icon: 'redo',
212
- getName: () => msg('Redo'),
213
- action: () => {
214
- this.redo();
215
- this.menuUI.opened = false;
216
- },
217
- isDisabled: () => !this.canRedo,
218
- },
219
- menu: {
220
- icon: 'menu',
221
- getName: () => msg('Menu'),
222
- action: async () => {
223
- this.menuUI.opened = !this.menuUI.opened;
224
- await this.menuUI.updateComplete;
225
- if (this.menuUI.opened) {
226
- this.menuUI.querySelector('oscd-list').focus();
227
- }
228
- },
229
- isDisabled: () => false,
230
- },
100
+ this.xmlEditor = new XMLEditor();
101
+ /*
102
+ * Event Handlers
103
+ */
104
+ this.handleOpenDoc = ({ detail: { docName, doc } }) => {
105
+ this.docs = {
106
+ ...this.docs,
107
+ [docName]: doc,
108
+ };
109
+ if (this.isEditable(docName)) {
110
+ this.docName = docName;
111
+ }
112
+ this.requestUpdate();
231
113
  };
232
- _OpenSCD_actions.set(this, [this.controls.undo, this.controls.redo]);
233
- this.hotkeys = {
234
- m: this.controls.menu.action,
235
- z: this.controls.undo.action,
236
- y: this.controls.redo.action,
237
- Z: this.controls.redo.action,
114
+ this.handleRenameDoc = (customEvent) => {
115
+ const { oldName, newName } = customEvent.detail;
116
+ if (!this.docs[oldName] || newName === oldName || this.docs[newName]) {
117
+ return;
118
+ }
119
+ const doc = this.docs[oldName];
120
+ delete this.docs[oldName];
121
+ this.docs = {
122
+ ...this.docs,
123
+ [newName]: doc,
124
+ };
125
+ this.docName = newName;
238
126
  };
239
- document.addEventListener('keydown', event => this.handleKeyPress(event));
240
- this.addEventListener('oscd-open', event => this.handleOpenDoc(event));
241
- this.addEventListener('oscd-edit-v2', (event) => {
127
+ this.handleEditV2 = (event) => {
242
128
  const { edit, title, squash } = event.detail;
243
129
  this.xmlEditor.commit(edit, { title, squash });
244
- });
130
+ };
131
+ this.handleCloseDoc = (event) => {
132
+ const docName = event.detail.docName;
133
+ delete this.docs[docName];
134
+ if (this.docName === docName) {
135
+ this.docName = this.editableDocs[0] || '';
136
+ }
137
+ };
138
+ this.handleUndo = () => {
139
+ this.undo();
140
+ };
141
+ this.handleRedo = () => {
142
+ this.redo();
143
+ };
144
+ this.handleKeyPress = (e) => {
145
+ if (!e.ctrlKey) {
146
+ return;
147
+ }
148
+ if (!Object.prototype.hasOwnProperty.call(this.hotkeys, e.key)) {
149
+ return;
150
+ }
151
+ this.hotkeys[e.key].call(this);
152
+ e.preventDefault();
153
+ };
154
+ this.handleOpenPluginMenu = () => {
155
+ this.pluginsMenu.open();
156
+ };
157
+ /** Undo the last `n` [[Edit]]s committed */
158
+ this.undo = (n = 1) => {
159
+ if (!this.canUndo || n < 1) {
160
+ return;
161
+ }
162
+ this.xmlEditor.undo();
163
+ if (n > 1) {
164
+ this.undo(n - 1);
165
+ }
166
+ this.requestUpdate();
167
+ };
168
+ /** Redo the last `n` [[Edit]]s that have been undone */
169
+ this.redo = (n = 1) => {
170
+ if (!this.canRedo || n < 1) {
171
+ return;
172
+ }
173
+ this.xmlEditor.redo();
174
+ if (n > 1) {
175
+ this.redo(n - 1);
176
+ }
177
+ this.requestUpdate();
178
+ };
179
+ this.hotkeys = {
180
+ m: this.handleOpenPluginMenu,
181
+ z: this.undo,
182
+ y: this.redo,
183
+ Z: this.redo,
184
+ };
245
185
  // Catch all edits (from commits AND events) and trigger an update
246
186
  this.xmlEditor.subscribe(() => {
247
187
  this.docVersion += 1;
248
188
  });
249
189
  }
250
- updated(changedProps) {
251
- super.updated(changedProps);
252
- this.updateComplete.then(() => {
253
- // Ensure the active tab is set after tabs are rendered
254
- const oscdTabs = this.shadowRoot.querySelector('oscd-tabs');
255
- if (oscdTabs && oscdTabs.activeTabIndex !== this.editorIndex) {
256
- oscdTabs.activeTabIndex = this.editorIndex;
257
- }
258
- });
190
+ connectedCallback() {
191
+ super.connectedCallback();
192
+ document.addEventListener('keydown', this.handleKeyPress);
193
+ this.addEventListener('oscd-open', this.handleOpenDoc);
194
+ this.addEventListener('oscd-rename', this.handleRenameDoc);
195
+ this.addEventListener('oscd-close', this.handleCloseDoc);
196
+ this.addEventListener('oscd-edit-v2', this.handleEditV2);
197
+ this.addEventListener('oscd-undo', this.handleUndo);
198
+ this.addEventListener('oscd-redo', this.handleRedo);
199
+ }
200
+ disconnectedCallback() {
201
+ super.disconnectedCallback();
202
+ // Remove event listeners
203
+ document.removeEventListener('keydown', this.handleKeyPress);
204
+ this.removeEventListener('oscd-open', this.handleOpenDoc);
205
+ this.removeEventListener('oscd-rename', this.handleRenameDoc);
206
+ this.removeEventListener('oscd-edit-v2', this.handleEditV2);
207
+ this.removeEventListener('oscd-undo', this.handleUndo);
208
+ this.removeEventListener('oscd-redo', this.handleRedo);
209
+ this.removeEventListener('oscd-close', this.handleCloseDoc);
210
+ }
211
+ isEditable(docName) {
212
+ return !!this.editable.find(ext => docName.toLowerCase().endsWith(`.${ext}`));
213
+ }
214
+ onDocsChanged() {
215
+ this.docVersion += 1;
259
216
  }
260
217
  renderPlugin(tagName) {
261
218
  const tag = unsafeStatic(tagName);
@@ -269,251 +226,155 @@ let OpenSCD = class OpenSCD extends LitElement {
269
226
  .editor=${this.xmlEditor}>
270
227
  </${tag}>`;
271
228
  }
272
- render() {
273
- return html `<oscd-app-bar slot="appContent">
274
- ${renderActionItem(this.controls.menu, 'actionStart')}
275
- <div slot="title" id="title">
276
- ${this.editableDocs.length > 1
277
- ? html `<oscd-filled-icon-button
278
- id="fileMenuButton"
279
- @click=${() => this.fileMenuUI.show()}
280
- ><oscd-icon>arrow_drop_down</oscd-icon></oscd-filled-icon-button
281
- >`
282
- : nothing}
283
- ${this.docName}
284
- ${this.docName
285
- ? html `<oscd-filled-icon-button
286
- @click=${() => this.editFileUI.show()}
287
- ><oscd-icon>edit</oscd-icon></oscd-filled-icon-button
288
- >`
289
- : nothing}
290
- <oscd-menu
291
- fixed
292
- id="fileMenu"
293
- anchor="fileMenuButton"
294
- corner="BOTTOM_END"
295
- >
296
- ${this.editableDocs.map((name, index) => html `<oscd-menu-item
297
- @click=${() => {
298
- this.docName = name;
299
- }}
300
- ?disabled=${!this.isEditable(name)}
301
- ?selected=${this.docName === name}
302
- id=${index}
303
- >${name}</oscd-menu-item
304
- >`)}
305
- </oscd-menu>
229
+ onMenuPluginSelect(customEvent) {
230
+ const plugin = customEvent.detail.plugin;
231
+ if (plugin.tagName) {
232
+ this.shadowRoot.querySelector(plugin.tagName).run?.();
233
+ }
234
+ }
235
+ renderOffScreenPlugins() {
236
+ return html `
237
+ <section class="off-screen-plugin-container" aria-hidden="true">
238
+ <div class="menu-plugins">
239
+ ${this.plugins.menu
240
+ .filter(plugin => !plugin.requireDoc || !!this.docName)
241
+ .map(plugin => this.renderPlugin(plugin.tagName))}
306
242
  </div>
307
-
308
- <div slot="actionEnd">
309
- ${__classPrivateFieldGet(this, _OpenSCD_actions, "f").map(op => renderActionItem(op))}
243
+ <div class="background-plugins">
244
+ ${this.plugins.background
245
+ .filter(plugin => !plugin.requireDoc || !!this.docName)
246
+ .map(plugin => this.renderPlugin(plugin.tagName))}
310
247
  </div>
311
-
312
- <oscd-tabs
313
- .activeTabIndex=${this.editorIndex}
248
+ </section>
249
+ `;
250
+ }
251
+ renderDefaultLandingPage() {
252
+ return html `
253
+ <landing-page
254
+ heading=${this.landingPageHeading}
255
+ subHeading=${this.landingPageSubHeading}
256
+ .menuPlugins=${this.plugins.menu.filter(plugin => !plugin.requireDoc || !!this.docName)}
257
+ .locale=${this.locale}
258
+ @menu-plugin-select=${(event) => this.onMenuPluginSelect(event)}
259
+ >
260
+ </landing-page>
261
+ `;
262
+ }
263
+ render() {
264
+ const hasCustomLandingPage = !!this._landingPageNodes?.length;
265
+ if (this.editableDocs.length === 0) {
266
+ return html ` <slot
267
+ name="landing-page"
268
+ @slotchange=${() => this.requestUpdate()}
269
+ ></slot>
270
+ ${!hasCustomLandingPage ? this.renderDefaultLandingPage() : nothing}
271
+ ${this.renderOffScreenPlugins()}`;
272
+ }
273
+ return html ` <oscd-app-bar>
274
+ <plugins-menu
275
+ slot="alignStart"
276
+ appTitle=${this.appTitle}
277
+ appIcon=${this.appIcon}
278
+ .editableDocs=${this.editableDocs}
279
+ .menuPlugins=${this.plugins.menu}
280
+ .locale=${this.locale}
281
+ @menu-plugin-select=${(event) => this.onMenuPluginSelect(event)}
282
+ ></plugins-menu>
283
+
284
+ <files-menu
285
+ slot="alignMiddle"
286
+ .selectedDocName=${this.docName}
287
+ .editableDocs=${this.editableDocs}
288
+ .locale=${this.locale}
314
289
  @change=${(event) => {
315
- const tabs = event.currentTarget;
316
- this.editorIndex = tabs.activeTabIndex;
290
+ const name = event.detail.name;
291
+ this.docName = name;
317
292
  }}
318
- >
319
- ${this.editors.map(editor => editor.isDisabled()
320
- ? nothing
321
- : html `<oscd-secondary-tab>
322
- <oscd-icon slot="icon">${editor.icon}</oscd-icon>
323
- ${editor.getName()}
324
- </oscd-secondary-tab>`)}
325
- </oscd-tabs>
326
- </oscd-app-bar>
327
-
328
- <oscd-navigation-drawer hasheader type="modal" id="menu">
329
- <oscd-navigation-drawer-header>
330
- <span slot="headline">${msg('Menu')}</span>
331
- ${this.docName
332
- ? html `<span slot="supporting-text">${this.docName}</span>`
333
- : ''}
334
- </oscd-navigation-drawer-header>
335
- <oscd-list>
336
- <oscd-divider></oscd-divider>
337
- ${this.menu.map(renderMenuItem)}
338
- </oscd-list>
339
- </oscd-navigation-drawer>
340
-
341
- ${this.editor ? this.renderPlugin(this.editor) : nothing}
342
-
343
- <oscd-dialog
344
- id="editFile"
345
- @closed=${(event) => {
346
- const dialog = event.target;
347
- if (dialog.returnValue === 'remove') {
348
- delete this.docs[this.docName];
349
- this.docName = this.editableDocs[0] || '';
350
- }
293
+ ></files-menu>
294
+
295
+ <div slot="alignEnd">
296
+ <oscd-filled-icon-button
297
+ aria-label="${msg('Undo')}"
298
+ ?disabled=${!this.canUndo}
299
+ @click=${async () => {
300
+ this.dispatchEvent(new CustomEvent('oscd-undo', {
301
+ bubbles: true,
302
+ composed: true,
303
+ }));
351
304
  }}
352
- >
353
- <div slot="headline">
354
- <oscd-icon>description</oscd-icon>${this.docName}
355
- </div>
356
- <form slot="content" id="edit-file-form" method="dialog">
357
- <oscd-filled-text-field
358
- id="fileName"
359
- label="${msg('Filename')}"
360
- value="${this.docName.replace(/\.[^.]+$/, '')}"
361
- @input=${(event) => {
362
- const input = event.target;
363
- const { value } = input;
364
- const name = `${value}.${this.fileExtensionUI.value}`;
365
- if (name in this.docs && name !== this.docName) {
366
- input.setCustomValidity('File already exists!');
367
- }
368
- else {
369
- input.setCustomValidity('');
370
- }
371
- input.reportValidity();
372
- }}
373
- ></oscd-filled-text-field>
374
- <oscd-filled-select
375
- label="${msg('Extension')}"
376
- fixedMenuPosition
377
- id="fileExtension"
378
- @selected=${() => this.fileNameUI.reportValidity()}
379
- >
380
- ${this.editable.map(ext => html `<oscd-select-option
381
- ?selected=${this.docName.endsWith(`.${ext}`)}
382
- value="${ext}"
383
- >${ext}</oscd-select-option
384
- >`)}
385
- </oscd-filled-select>
386
- </form>
387
- <div slot="actions">
388
- <oscd-text-button
389
- form="edit-file-form"
390
- class="edit-dialog-remove-button"
391
- value="remove"
305
+ ><oscd-icon>undo</oscd-icon></oscd-filled-icon-button
392
306
  >
393
- <oscd-icon slot="icon">delete</oscd-icon>
394
- ${msg('Close file')}
395
- </oscd-text-button>
396
- <oscd-text-button form="edit-file-form" value="close">
397
- ${msg('Cancel')}
398
- </oscd-text-button>
399
- <oscd-text-button
400
- @click=${() => {
401
- const valid = this.fileNameUI.checkValidity();
402
- if (!valid) {
403
- this.fileNameUI.reportValidity();
404
- return;
405
- }
406
- const newDocName = `${this.fileNameUI.value}.${this.fileExtensionUI.value}`;
407
- const doc = this.docs[this.docName];
408
- delete this.docs[this.docName];
409
- this.docs = {
410
- ...this.docs,
411
- [newDocName]: doc,
412
- };
413
- this.docName = newDocName;
414
- this.editFileUI.close();
307
+ <oscd-filled-icon-button
308
+ aria-label="${msg('Redo')}"
309
+ ?disabled=${!this.canRedo}
310
+ @click=${async () => {
311
+ this.dispatchEvent(new CustomEvent('oscd-redo', {
312
+ bubbles: true,
313
+ composed: true,
314
+ }));
415
315
  }}
416
- trailing-icon
316
+ ><oscd-icon>redo</oscd-icon></oscd-filled-icon-button
417
317
  >
418
- <oscd-icon slot="icon">edit</oscd-icon>
419
- ${msg('Rename')}
420
- </oscd-text-button>
421
- </div>
422
- </oscd-dialog>
423
-
424
- <aside>
425
- <div class="menu-plugins">
426
- ${this.plugins.menu.map(plugin => this.renderPlugin(plugin.tagName))}
427
- </div>
428
- <div class="background-plugins">
429
- ${this.plugins.background
430
- .filter(plugin => !plugin.requireDoc || !!this.docName)
431
- .map(plugin => this.renderPlugin(plugin.tagName))}
432
318
  </div>
433
- </aside> `;
434
- }
435
- firstUpdated() {
436
- const background = getComputedStyle(this.menuUI).getPropertyValue('--oscd-base2');
437
- document.body.style.background = background;
438
- }
439
- };
440
- _OpenSCD_plugins = new WeakMap();
441
- _OpenSCD_actions = new WeakMap();
442
- OpenSCD.styles = css `
443
- .fileext {
444
- opacity: 0.81;
445
- }
446
-
447
- .filename {
448
- caret-color: var(--oscd-secondary);
449
- }
450
-
451
- .filename:focus {
452
- outline: none;
453
- }
454
-
455
- aside {
456
- position: absolute;
457
- top: 0;
458
- left: 0;
459
- width: 0;
460
- height: 0;
461
- overflow: hidden;
462
- margin: 0;
463
- padding: 0;
464
- }
465
-
466
- abbr {
467
- text-decoration: none;
468
- }
319
+ </oscd-app-bar>
469
320
 
470
- @media (prefers-color-scheme: light) {
471
- * {
472
- --oscd-primary: var(--oscd-theme-primary, #2aa198);
473
- --oscd-secondary: var(--oscd-theme-secondary, #6c71c4);
474
- --oscd-base03: var(--oscd-theme-base03, #002b36);
475
- --oscd-base02: var(--oscd-theme-base02, #073642);
476
- --oscd-base01: var(--oscd-theme-base01, #586e75);
477
- --oscd-base00: var(--oscd-theme-base00, #657b83);
478
- --oscd-base0: var(--oscd-theme-base0, #839496);
479
- --oscd-base1: var(--oscd-theme-base1, #93a1a1);
480
- --oscd-base2: var(--oscd-theme-base2, #eee8d5);
481
- --oscd-base3: var(--oscd-theme-base3, #fdf6e3);
321
+ <main>
322
+ <section class="editors-side-panel-section">
323
+ <editor-plugins-panel
324
+ .editors=${this.plugins.editor}
325
+ .editorIndex=${this.editorIndex}
326
+ .locale=${this.locale}
327
+ @editor-select=${(e) => {
328
+ this.editorIndex = e.detail.index;
329
+ }}
330
+ ></editor-plugins-panel>
331
+ </section>
482
332
 
483
- --mdc-theme-text-disabled-on-light: rgba(255, 255, 255, 0.38);
484
- }
485
- }
333
+ <section class="editor-container">
334
+ ${this.editor ? this.renderPlugin(this.editor) : nothing}
335
+ </section>
486
336
 
487
- @media (prefers-color-scheme: dark) {
488
- * {
489
- --oscd-primary: var(--oscd-theme-secondary, #6c71c4);
490
- --oscd-secondary: var(--oscd-theme-primary, #2aa198);
491
- --oscd-base03: var(--oscd-theme-base3, #fdf6e3);
492
- --oscd-base02: var(--oscd-theme-base2, #eee8d5);
493
- --oscd-base01: var(--oscd-theme-base1, #93a1a1);
494
- --oscd-base00: var(--oscd-theme-base0, #839496);
495
- --oscd-base0: var(--oscd-theme-base00, #657b83);
496
- --oscd-base1: var(--oscd-theme-base01, #586e75);
497
- --oscd-base2: var(--oscd-theme-base02, #073642);
498
- --oscd-base3: var(--oscd-theme-base03, #002b36);
499
- }
337
+ ${this.renderOffScreenPlugins()}
338
+ </main>`;
500
339
  }
501
-
340
+ };
341
+ OscdShell.scopedElements = {
342
+ 'oscd-app-bar': OscdAppBar,
343
+ 'oscd-filled-icon-button': OscdFilledIconButton,
344
+ 'oscd-icon': OscdIcon,
345
+ 'files-menu': FilesMenu,
346
+ 'plugins-menu': PluginsMenu,
347
+ 'editor-plugins-panel': EditorPluginsPanel,
348
+ 'landing-page': LandingPage,
349
+ };
350
+ OscdShell.styles = css `
351
+ /* Theme variables with default values */
502
352
  * {
353
+ --oscd-primary: var(--oscd-theme-primary, #0b335b);
354
+ --oscd-secondary: var(--oscd-theme-secondary, #2485e5);
355
+ --oscd-base03: var(--oscd-theme-base03, #121417);
356
+ --oscd-base02: var(--oscd-theme-base02, #1a1e23);
357
+ --oscd-base01: var(--oscd-theme-base01, #3d4651);
358
+ --oscd-base00: var(--oscd-theme-base00, #46505d);
359
+ --oscd-base0: var(--oscd-theme-base0, #8b97a7);
360
+ --oscd-base1: var(--oscd-theme-base1, #96a1b0);
361
+ --oscd-base2: var(--oscd-theme-base2, #f3f5f6);
362
+ --oscd-base3: var(--oscd-theme-base3, white);
503
363
  --oscd-error: var(--oscd-theme-error, #dc322f);
504
-
364
+ --oscd-warning: var(--oscd-theme-warning, #b58900);
505
365
  --oscd-text-font: var(--oscd-theme-text-font, 'Roboto');
506
- --oscd-icon-font: var(--oscd-theme-icon-font, 'Material Icons');
507
366
  --oscd-text-font-mono: var(--oscd-theme-text-font-mono, 'Roboto Mono');
367
+ --oscd-icon-font: var(
368
+ --oscd-theme-icon-font,
369
+ 'Material Symbols Outlined'
370
+ );
508
371
 
509
- --oscd-warning: var(--oscd-theme-warning, #b58900);
372
+ /* Fallbacks for Material Design variables */
510
373
  --md-sys-color-primary: var(--oscd-primary);
511
374
  --md-sys-color-on-primary: var(--oscd-base3);
512
-
513
375
  --md-sys-color-secondary: var(--oscd-secondary);
514
376
  --md-sys-color-on-secondary: var(--oscd-base3);
515
377
  --md-sys-color-secondary-container: var(--oscd-base2);
516
-
517
378
  --md-sys-color-surface: var(--oscd-base3);
518
379
  --md-sys-color-on-surface: var(--oscd-base00);
519
380
  --md-sys-color-surface-variant: var(--oscd-base3);
@@ -522,12 +383,11 @@ OpenSCD.styles = css `
522
383
  --md-sys-color-surface-container: var(--oscd-base3);
523
384
  --md-sys-color-surface-container-high: var(--oscd-base3);
524
385
  --md-sys-color-surface-container-highest: var(--oscd-base3);
525
-
526
386
  --md-sys-color-outline-variant: var(--oscd-primary);
527
387
  --md-sys-color-scrim: #000000;
528
388
  --md-sys-color-error: var(--oscd-error);
529
389
  --md-sys-color-on-error: var(--oscd-base3);
530
-
390
+ --md-icon-button-disabled-icon-color: var(--oscd-base3);
531
391
  /* --md-menu-item-selected-label-text-color: var(--oscd-base01); */
532
392
  --md-icon-button-disabled-icon-color: var(--oscd-base3);
533
393
 
@@ -539,40 +399,109 @@ OpenSCD.styles = css `
539
399
  --mdc-theme-background: var(--oscd-base3);
540
400
  --mdc-theme-surface: var(--oscd-base3);
541
401
  --mdc-theme-on-primary: var(--oscd-base2);
542
- --mdc-theme-on-secondary: var(--oscd-base02);
402
+ --mdc-theme-on-secondary: var(--oscd-base3);
543
403
  --mdc-theme-on-background: var(--oscd-base00);
544
404
  --mdc-theme-on-surface: var(--oscd-base00);
545
405
  --mdc-theme-text-primary-on-background: var(--oscd-base01);
546
- --mdc-theme-text-secondary-on-background: var(--oscd-base00);
547
- --mdc-theme-text-icon-on-background: var(--oscd-base00);
406
+ --mdc-theme-text-secondary-on-background: var(--oscd-base3);
407
+ --mdc-theme-text-icon-on-background: var(--oscd-base3);
548
408
  --mdc-theme-error: var(--oscd-error);
549
-
550
409
  --mdc-button-disabled-ink-color: var(--oscd-base1);
551
-
552
410
  --mdc-drawer-heading-ink-color: var(--oscd-base00);
553
-
554
411
  --mdc-dialog-heading-ink-color: var(--oscd-base00);
555
-
556
412
  --mdc-text-field-fill-color: var(--oscd-base2);
557
413
  --mdc-text-field-ink-color: var(--oscd-base02);
558
414
  --mdc-text-field-label-ink-color: var(--oscd-base01);
559
415
  --mdc-text-field-idle-line-color: var(--oscd-base00);
560
416
  --mdc-text-field-hover-line-color: var(--oscd-base02);
561
-
562
417
  --mdc-select-fill-color: var(--oscd-base2);
563
418
  --mdc-select-ink-color: var(--oscd-base02);
564
419
  --mdc-select-label-ink-color: var(--oscd-base01);
565
420
  --mdc-select-idle-line-color: var(--oscd-base00);
566
421
  --mdc-select-hover-line-color: var(--oscd-base02);
567
422
  --mdc-select-dropdown-icon-color: var(--oscd-base01);
568
-
569
423
  --mdc-typography-font-family: var(--oscd-text-font);
570
424
  --mdc-icon-font: var(--oscd-icon-font);
425
+ --mdc-theme-text-disabled-on-light: rgba(255, 255, 255, 0.38);
426
+ }
427
+
428
+ * {
429
+ --app-bar-height: 54px;
430
+ --side-panel-width: 280px;
431
+
432
+ --md-sys-color-primary: var(--oscd-primary);
433
+ --md-sys-color-on-primary: var(--oscd-base3);
434
+
435
+ --md-sys-color-secondary-container: var(--oscd-base2);
436
+
437
+ --md-sys-color-surface: var(--oscd-base3);
438
+ --md-sys-color-on-surface: var(--oscd-base00);
439
+ }
440
+
441
+ :host {
442
+ height: 100%;
443
+ display: grid;
444
+ grid-template-rows: min-content 1fr;
445
+ grid-template-columns: 1fr;
446
+ grid-template-areas:
447
+ 'header'
448
+ 'main';
449
+ }
450
+
451
+ oscd-app-bar {
452
+ grid-area: header;
453
+ box-shadow: var(--md-sys-elevation-level-2);
454
+ z-index: 10;
455
+ }
456
+
457
+ main {
458
+ grid-area: main;
459
+ display: grid;
460
+ grid-template-columns: var(--side-panel-width) 1fr;
461
+ grid-template-areas: 'sidebar editor';
462
+ overflow: hidden;
463
+ }
464
+
465
+ /* Side panel collapsed state */
466
+ main.sidebar-collapsed {
467
+ grid-template-columns: 0 1fr;
468
+ }
469
+
470
+ section.editors-side-panel-section {
471
+ grid-area: sidebar;
472
+ overflow-y: auto;
473
+ overflow-x: hidden;
474
+ transition: transform 0.3s ease-in-out;
475
+ }
476
+
477
+ /* Hide side panel when collapsed */
478
+ main.sidebar-collapsed section.editors-side-panel-section {
479
+ transform: translateX(-100%);
480
+ }
481
+
482
+ section.editor-container {
483
+ grid-area: editor;
484
+ background-color: var(--oscd-base3);
485
+ padding: 8px;
486
+ overflow: auto;
487
+ position: relative;
488
+ }
489
+
490
+ .off-screen-plugin-container {
491
+ position: absolute;
492
+ top: 0;
493
+ left: 0;
494
+ width: 0;
495
+ height: 0;
496
+ overflow: hidden;
497
+ margin: 0;
498
+ padding: 0;
571
499
  }
572
500
 
573
501
  oscd-navigation-drawer-header {
574
502
  --md-list-item-supporting-text-color: var(--md-sys-color-on-surface);
575
503
  }
504
+
576
505
  oscd-app-bar * {
577
506
  --md-filled-icon-button-disabled-container-opacity: 0;
578
507
  --md-filled-icon-button-disabled-icon-color: var(
@@ -580,107 +509,89 @@ OpenSCD.styles = css `
580
509
  );
581
510
  }
582
511
 
583
- oscd-tabs {
584
- display: flex;
585
- flex-grow: 1;
586
- --md-secondary-tab-container-color: var(--oscd-primary);
587
- }
588
- oscd-secondary-tab {
589
- --md-sys-color-on-surface: var(--md-sys-color-on-primary);
590
- --md-sys-color-on-surface-variant: var(--md-sys-color-on-primary);
591
- --md-secondary-tab-active-indicator-color: var(--oscd-base2);
592
- }
593
-
594
512
  .edit-dialog-remove-button {
595
513
  --md-text-button-icon-color: var(--oscd-error);
596
514
  --md-text-button-label-text-color: var(--oscd-error);
597
-
598
515
  --md-text-button-focus-label-text-color: var(--oscd-error);
599
516
  --md-text-button-focus-icon-color: var(--oscd-error);
600
-
601
517
  --md-text-button-hover-label-text-color: var(--oscd-error);
602
518
  --md-text-button-hover-state-layer-color: var(--oscd-error);
603
519
  --md-text-button-hover-icon-color: var(--oscd-error);
604
-
605
520
  --md-text-button-pressed-label-text-color: var(--oscd-error);
606
521
  --md-text-button-pressed-state-layer-color: var(--oscd-error);
607
522
  --md-text-button-pressed-icon-color: var(--oscd-error);
608
523
  }
609
-
610
524
  #title {
611
525
  position: relative;
612
526
  }
613
527
  `;
614
528
  __decorate([
615
- property({ type: Array, reflect: true })
616
- ], OpenSCD.prototype, "editable", void 0);
529
+ property({ type: String })
530
+ ], OscdShell.prototype, "appIcon", void 0);
617
531
  __decorate([
618
- state()
619
- ], OpenSCD.prototype, "editableDocs", null);
532
+ property({ type: String })
533
+ ], OscdShell.prototype, "appTitle", void 0);
620
534
  __decorate([
621
- state()
622
- ], OpenSCD.prototype, "docs", null);
535
+ property({ type: String })
536
+ ], OscdShell.prototype, "landingPageHeading", void 0);
537
+ __decorate([
538
+ property({ type: String })
539
+ ], OscdShell.prototype, "landingPageSubHeading", void 0);
540
+ __decorate([
541
+ property({ type: Array, reflect: true })
542
+ ], OscdShell.prototype, "editable", void 0);
623
543
  __decorate([
624
544
  property({ type: String, reflect: true })
625
- ], OpenSCD.prototype, "docName", void 0);
545
+ ], OscdShell.prototype, "locale", null);
626
546
  __decorate([
627
- state()
628
- /** The `XMLDocument` currently being edited */
629
- ], OpenSCD.prototype, "doc", null);
547
+ property({ type: Object })
548
+ ], OscdShell.prototype, "plugins", null);
630
549
  __decorate([
631
550
  state()
632
- ], OpenSCD.prototype, "xmlEditor", void 0);
551
+ ], OscdShell.prototype, "canRedo", null);
633
552
  __decorate([
634
553
  state()
635
- ], OpenSCD.prototype, "docVersion", void 0);
554
+ ], OscdShell.prototype, "canUndo", null);
636
555
  __decorate([
637
556
  state()
638
- ], OpenSCD.prototype, "last", null);
557
+ ], OscdShell.prototype, "editor", null);
639
558
  __decorate([
640
559
  state()
641
- ], OpenSCD.prototype, "canUndo", null);
560
+ ], OscdShell.prototype, "editorIndex", void 0);
642
561
  __decorate([
643
562
  state()
644
- ], OpenSCD.prototype, "canRedo", null);
645
- __decorate([
646
- property({ type: Object })
647
- ], OpenSCD.prototype, "plugins", null);
648
- __decorate([
649
- query('#editFile')
650
- ], OpenSCD.prototype, "editFileUI", void 0);
651
- __decorate([
652
- query('#menu')
653
- ], OpenSCD.prototype, "menuUI", void 0);
654
- __decorate([
655
- query('#fileName')
656
- ], OpenSCD.prototype, "fileNameUI", void 0);
657
- __decorate([
658
- query('#fileExtension')
659
- ], OpenSCD.prototype, "fileExtensionUI", void 0);
660
- __decorate([
661
- query('#fileMenu')
662
- ], OpenSCD.prototype, "fileMenuUI", void 0);
663
- __decorate([
664
- query('#fileMenuButton')
665
- ], OpenSCD.prototype, "fileMenuButtonUI", void 0);
563
+ /** The `XMLDocument` currently being edited */
564
+ ], OscdShell.prototype, "doc", null);
666
565
  __decorate([
667
566
  property({ type: String, reflect: true })
668
- ], OpenSCD.prototype, "locale", null);
567
+ ], OscdShell.prototype, "docName", void 0);
568
+ __decorate([
569
+ state()
570
+ ], OscdShell.prototype, "docs", null);
669
571
  __decorate([
670
572
  state()
671
- ], OpenSCD.prototype, "editorIndex", void 0);
573
+ ], OscdShell.prototype, "docVersion", void 0);
672
574
  __decorate([
673
575
  state()
674
- ], OpenSCD.prototype, "editor", null);
576
+ ], OscdShell.prototype, "editableDocs", null);
675
577
  __decorate([
676
578
  state()
677
- ], OpenSCD.prototype, "menu", null);
579
+ ], OscdShell.prototype, "last", null);
678
580
  __decorate([
679
581
  state()
680
- ], OpenSCD.prototype, "editors", null);
681
- OpenSCD = __decorate([
682
- customElement('oscd-shell'),
683
- localized()
684
- ], OpenSCD);
685
- export { OpenSCD };
582
+ ], OscdShell.prototype, "xmlEditor", void 0);
583
+ __decorate([
584
+ query('plugins-menu')
585
+ ], OscdShell.prototype, "pluginsMenu", void 0);
586
+ __decorate([
587
+ query('editor-plugins-panel')
588
+ ], OscdShell.prototype, "editorPluginsPanel", void 0);
589
+ __decorate([
590
+ queryAssignedNodes({ slot: 'landing-page' })
591
+ ], OscdShell.prototype, "_landingPageNodes", void 0);
592
+ OscdShell = __decorate([
593
+ localized(),
594
+ customElement('oscd-shell')
595
+ ], OscdShell);
596
+ export { OscdShell };
686
597
  //# sourceMappingURL=oscd-shell.js.map