@omicronenergy/oscd-shell 0.0.12 → 0.0.14

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 (36) hide show
  1. package/dist/landing-page/landing-page.js +3 -2
  2. package/dist/landing-page/landing-page.js.map +1 -1
  3. package/dist/landing-page/landing-page.spec.js +4 -2
  4. package/dist/landing-page/landing-page.spec.js.map +1 -1
  5. package/dist/locales/de.d.ts +8 -0
  6. package/dist/locales/de.js +9 -0
  7. package/dist/locales/de.js.map +1 -1
  8. package/dist/menus/files-menu.js +4 -5
  9. package/dist/menus/files-menu.js.map +1 -1
  10. package/dist/menus/plugins-menu.d.ts +6 -3
  11. package/dist/menus/plugins-menu.js +46 -5
  12. package/dist/menus/plugins-menu.js.map +1 -1
  13. package/dist/menus/plugins-menu.spec.js +1 -0
  14. package/dist/menus/plugins-menu.spec.js.map +1 -1
  15. package/dist/oscd-shell-design-tokens.js +134 -62
  16. package/dist/oscd-shell-design-tokens.js.map +1 -1
  17. package/dist/oscd-shell.d.ts +31 -22
  18. package/dist/oscd-shell.js +136 -72
  19. package/dist/oscd-shell.js.map +1 -1
  20. package/dist/side-panel/build-tree-nodes.spec.d.ts +1 -0
  21. package/dist/side-panel/build-tree-nodes.spec.js +50 -0
  22. package/dist/side-panel/build-tree-nodes.spec.js.map +1 -0
  23. package/dist/side-panel/editor-plugins-panel.d.ts +79 -7
  24. package/dist/side-panel/editor-plugins-panel.js +823 -92
  25. package/dist/side-panel/editor-plugins-panel.js.map +1 -1
  26. package/dist/side-panel/editor-plugins-panel.spec.js +469 -24
  27. package/dist/side-panel/editor-plugins-panel.spec.js.map +1 -1
  28. package/dist/utils/plugin-utils.d.ts +29 -5
  29. package/dist/utils/plugin-utils.js +73 -14
  30. package/dist/utils/plugin-utils.js.map +1 -1
  31. package/dist/utils/plugin-utils.spec.js +113 -4
  32. package/dist/utils/plugin-utils.spec.js.map +1 -1
  33. package/dist/utils/testing/plugin-helpers.d.ts +7 -3
  34. package/dist/utils/testing/plugin-helpers.js +21 -9
  35. package/dist/utils/testing/plugin-helpers.js.map +1 -1
  36. package/package.json +36 -37
@@ -5,15 +5,16 @@ import { ScopedElementsMixin } from '@open-wc/scoped-elements/lit-element.js';
5
5
  import { localized, msg } from '@lit/localize';
6
6
  import { html as staticHtml, unsafeStatic } from 'lit/static-html.js';
7
7
  import { OscdFilledIconButton } from '@omicronenergy/oscd-ui/iconbutton/OscdFilledIconButton.js';
8
- import { XMLEditor } from '@omicronenergy/oscd-editor';
9
- import { loadSourcedPlugins } from './utils/plugin-utils.js';
10
- import { getLocale, setLocale, } from './localization.js';
8
+ import { XMLEditor } from '@openscd/oscd-editor';
9
+ import { flattenPluginEntries, loadSourcedPlugins, } from './utils/plugin-utils.js';
10
+ import { getLocale, setLocale } from './localization.js';
11
11
  import { EditorPluginsPanel } from './side-panel/editor-plugins-panel.js';
12
12
  import { PluginsMenu } from './menus/plugins-menu.js';
13
13
  import { LandingPage } from './landing-page/landing-page.js';
14
14
  import { FilesMenu } from './menus/files-menu.js';
15
15
  import { oscdShellDesignTokens } from './oscd-shell-design-tokens.js';
16
16
  import { OscdAppBar } from '@omicronenergy/oscd-ui/app-bar/OscdAppBar.js';
17
+ import { OscdDivider } from '@omicronenergy/oscd-ui/divider/OscdDivider.js';
17
18
  import { OscdIcon } from '@omicronenergy/oscd-ui/icon/OscdIcon.js';
18
19
  let OscdShell = class OscdShell extends ScopedElementsMixin(LitElement) {
19
20
  get locale() {
@@ -48,9 +49,6 @@ let OscdShell = class OscdShell extends ScopedElementsMixin(LitElement) {
48
49
  get canUndo() {
49
50
  return this.xmlEditor.past.length >= 1;
50
51
  }
51
- get editor() {
52
- return this.plugins.editor[this.editorIndex]?.tagName ?? '';
53
- }
54
52
  get doc() {
55
53
  return this.docs[this.docName];
56
54
  }
@@ -59,7 +57,7 @@ let OscdShell = class OscdShell extends ScopedElementsMixin(LitElement) {
59
57
  }
60
58
  set docs(newDocs) {
61
59
  this._docs = newDocs;
62
- this.onDocsChanged();
60
+ this.docVersion += 1;
63
61
  }
64
62
  get editableDocs() {
65
63
  return Object.keys(this.docs).filter(name => this.isEditable(name));
@@ -92,7 +90,6 @@ let OscdShell = class OscdShell extends ScopedElementsMixin(LitElement) {
92
90
  'ssd',
93
91
  ];
94
92
  this._plugins = { menu: [], editor: [], background: [] };
95
- this.editorIndex = 0;
96
93
  /** The name of the [[`doc`]] currently being edited */
97
94
  this.docName = '';
98
95
  /** The set of `XMLDocument`s currently loaded */
@@ -143,6 +140,18 @@ let OscdShell = class OscdShell extends ScopedElementsMixin(LitElement) {
143
140
  this.redo();
144
141
  };
145
142
  this.handleKeyPress = (e) => {
143
+ if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key.toLowerCase() === 'f') {
144
+ const panel = this.shadowRoot?.querySelector('editor-plugins-panel');
145
+ if (panel) {
146
+ if (!panel.expanded) {
147
+ panel.searchMode = true;
148
+ }
149
+ panel.focusSearch(true);
150
+ e.preventDefault();
151
+ e.stopPropagation();
152
+ }
153
+ return;
154
+ }
146
155
  if (!e.ctrlKey) {
147
156
  return;
148
157
  }
@@ -188,9 +197,17 @@ let OscdShell = class OscdShell extends ScopedElementsMixin(LitElement) {
188
197
  this.docVersion += 1;
189
198
  });
190
199
  }
200
+ willUpdate(changedProperties) {
201
+ if (changedProperties.has('docName') || changedProperties.has('plugins')) {
202
+ const firstEditor = flattenPluginEntries(this.plugins.editor)[0];
203
+ if (this.docName && firstEditor && !this.selectedEditor) {
204
+ this.selectedEditor = firstEditor;
205
+ }
206
+ }
207
+ }
191
208
  connectedCallback() {
192
209
  super.connectedCallback();
193
- document.addEventListener('keydown', this.handleKeyPress);
210
+ document.addEventListener('keydown', this.handleKeyPress, true);
194
211
  this.addEventListener('oscd-open', this.handleOpenDoc);
195
212
  this.addEventListener('oscd-rename', this.handleRenameDoc);
196
213
  this.addEventListener('oscd-close', this.handleCloseDoc);
@@ -201,7 +218,7 @@ let OscdShell = class OscdShell extends ScopedElementsMixin(LitElement) {
201
218
  disconnectedCallback() {
202
219
  super.disconnectedCallback();
203
220
  // Remove event listeners
204
- document.removeEventListener('keydown', this.handleKeyPress);
221
+ document.removeEventListener('keydown', this.handleKeyPress, true);
205
222
  this.removeEventListener('oscd-open', this.handleOpenDoc);
206
223
  this.removeEventListener('oscd-rename', this.handleRenameDoc);
207
224
  this.removeEventListener('oscd-edit-v2', this.handleEditV2);
@@ -209,42 +226,39 @@ let OscdShell = class OscdShell extends ScopedElementsMixin(LitElement) {
209
226
  this.removeEventListener('oscd-redo', this.handleRedo);
210
227
  this.removeEventListener('oscd-close', this.handleCloseDoc);
211
228
  }
229
+ handlePluginMenuSelect(customEvent) {
230
+ const plugin = customEvent.detail.plugin;
231
+ if (plugin.tagName) {
232
+ this.shadowRoot.querySelector(plugin.tagName).run?.();
233
+ }
234
+ }
212
235
  isEditable(docName) {
213
236
  return !!this.editable.find(ext => docName.toLowerCase().endsWith(`.${ext}`));
214
237
  }
215
- onDocsChanged() {
216
- this.docVersion += 1;
217
- }
218
- renderPlugin(tagName) {
219
- const tag = unsafeStatic(tagName);
220
- return staticHtml `<${tag}
238
+ renderPlugin(plugin) {
239
+ const tag = unsafeStatic(plugin.tagName);
240
+ return staticHtml `<${tag}
221
241
  .locale="${this.locale}"
222
242
  .docName="${this.docName}"
223
243
  .doc=${this.doc}
224
- .docs=${this.docs}
244
+ .docs=${this.docs}
225
245
  .editCount=${this.docVersion}
226
246
  .docVersion=${this.docVersion}
227
247
  .editor=${this.xmlEditor}>
228
248
  </${tag}>`;
229
249
  }
230
- onMenuPluginSelect(customEvent) {
231
- const plugin = customEvent.detail.plugin;
232
- if (plugin.tagName) {
233
- this.shadowRoot.querySelector(plugin.tagName).run?.();
234
- }
235
- }
236
250
  renderOffScreenPlugins() {
237
251
  return html `
238
252
  <section class="off-screen-plugin-container" aria-hidden="true">
239
253
  <div class="menu-plugins">
240
- ${this.plugins.menu
254
+ ${flattenPluginEntries(this.plugins.menu)
241
255
  .filter(plugin => !plugin.requireDoc || !!this.docName)
242
- .map(plugin => this.renderPlugin(plugin.tagName))}
256
+ .map(plugin => this.renderPlugin(plugin))}
243
257
  </div>
244
258
  <div class="background-plugins">
245
259
  ${this.plugins.background
246
260
  .filter(plugin => !plugin.requireDoc || !!this.docName)
247
- .map(plugin => this.renderPlugin(plugin.tagName))}
261
+ .map(plugin => this.renderPlugin(plugin))}
248
262
  </div>
249
263
  </section>
250
264
  `;
@@ -254,9 +268,9 @@ let OscdShell = class OscdShell extends ScopedElementsMixin(LitElement) {
254
268
  <landing-page
255
269
  heading=${this.landingPageHeading}
256
270
  subHeading=${this.landingPageSubHeading}
257
- .menuPlugins=${this.plugins.menu.filter(plugin => !plugin.requireDoc || !!this.docName)}
271
+ .menuPlugins=${flattenPluginEntries(this.plugins.menu).filter(plugin => !plugin.requireDoc || !!this.docName)}
258
272
  .locale=${this.locale}
259
- @menu-plugin-select=${(event) => this.onMenuPluginSelect(event)}
273
+ @menu-plugin-select=${(event) => this.handlePluginMenuSelect(event)}
260
274
  >
261
275
  </landing-page>
262
276
  `;
@@ -264,11 +278,13 @@ let OscdShell = class OscdShell extends ScopedElementsMixin(LitElement) {
264
278
  render() {
265
279
  const hasCustomLandingPage = !!this._landingPageNodes?.length;
266
280
  if (this.editableDocs.length === 0) {
267
- return html ` <slot
268
- name="landing-page"
269
- @slotchange=${() => this.requestUpdate()}
270
- ></slot>
271
- ${!hasCustomLandingPage ? this.renderDefaultLandingPage() : nothing}
281
+ return html ` <div class="landing-page-container">
282
+ <slot
283
+ name="landing-page"
284
+ @slotchange=${() => this.requestUpdate()}
285
+ ></slot>
286
+ ${!hasCustomLandingPage ? this.renderDefaultLandingPage() : nothing}
287
+ </div>
272
288
  ${this.renderOffScreenPlugins()}`;
273
289
  }
274
290
  return html ` <oscd-app-bar>
@@ -279,21 +295,32 @@ let OscdShell = class OscdShell extends ScopedElementsMixin(LitElement) {
279
295
  .editableDocs=${this.editableDocs}
280
296
  .menuPlugins=${this.plugins.menu}
281
297
  .locale=${this.locale}
282
- @menu-plugin-select=${(event) => this.onMenuPluginSelect(event)}
298
+ @menu-plugin-select=${(event) => this.handlePluginMenuSelect(event)}
283
299
  ></plugins-menu>
284
300
 
285
- <files-menu
286
- slot="alignMiddle"
287
- .selectedDocName=${this.docName}
288
- .editableDocs=${this.editableDocs}
289
- .locale=${this.locale}
290
- @change=${(event) => {
291
- const name = event.detail.name;
292
- this.docName = name;
293
- }}
294
- ></files-menu>
301
+ ${this.selectedEditor
302
+ ? html `<div class="current-editor" slot="alignStart">
303
+ <oscd-divider class="vertical" aria-hidden="true"></oscd-divider>
304
+ <span>${this.selectedEditor.name}</span>
305
+ </div>`
306
+ : nothing}
295
307
 
296
308
  <div slot="alignEnd">
309
+ ${this.docName
310
+ ? html `<files-menu
311
+ .selectedDocName=${this.docName}
312
+ .editableDocs=${this.editableDocs}
313
+ .locale=${this.locale}
314
+ @change=${(event) => {
315
+ const name = event.detail.name;
316
+ this.docName = name;
317
+ }}
318
+ ></files-menu>
319
+ <oscd-divider
320
+ class="vertical"
321
+ aria-hidden="true"
322
+ ></oscd-divider>`
323
+ : nothing}
297
324
  <oscd-filled-icon-button
298
325
  aria-label="${msg('Undo')}"
299
326
  ?disabled=${!this.canUndo}
@@ -323,16 +350,18 @@ let OscdShell = class OscdShell extends ScopedElementsMixin(LitElement) {
323
350
  <section class="editors-side-panel-section">
324
351
  <editor-plugins-panel
325
352
  .editors=${this.plugins.editor}
326
- .editorIndex=${this.editorIndex}
353
+ .selectedEditor=${this.selectedEditor}
327
354
  .locale=${this.locale}
328
355
  @editor-select=${(e) => {
329
- this.editorIndex = e.detail.index;
356
+ this.selectedEditor = e.detail.editor;
330
357
  }}
331
358
  ></editor-plugins-panel>
332
359
  </section>
333
360
 
334
361
  <section class="editor-container">
335
- ${this.editor ? this.renderPlugin(this.editor) : nothing}
362
+ ${this.selectedEditor
363
+ ? this.renderPlugin(this.selectedEditor)
364
+ : nothing}
336
365
  </section>
337
366
 
338
367
  ${this.renderOffScreenPlugins()}
@@ -342,6 +371,7 @@ let OscdShell = class OscdShell extends ScopedElementsMixin(LitElement) {
342
371
  OscdShell.scopedElements = {
343
372
  'oscd-app-bar': OscdAppBar,
344
373
  'oscd-filled-icon-button': OscdFilledIconButton,
374
+ 'oscd-divider': OscdDivider,
345
375
  'oscd-icon': OscdIcon,
346
376
  'files-menu': FilesMenu,
347
377
  'plugins-menu': PluginsMenu,
@@ -359,6 +389,18 @@ OscdShell.styles = [
359
389
  grid-template-areas:
360
390
  'header'
361
391
  'main';
392
+ background-color: var(--shell-background-color);
393
+ }
394
+
395
+ .landing-page-container {
396
+ grid-column: 1 / -1;
397
+ grid-row: 1 / -1;
398
+ height: 100%;
399
+ overflow: auto;
400
+ }
401
+
402
+ .landing-page-container ::slotted(*) {
403
+ height: 100%;
362
404
  }
363
405
 
364
406
  oscd-app-bar {
@@ -367,42 +409,67 @@ OscdShell.styles = [
367
409
  z-index: 10;
368
410
  }
369
411
 
370
- oscd-app-bar * {
371
- --md-filled-icon-button-disabled-container-opacity: var(
372
- --app-bar-action-icon-disabled-container-opacity,
373
- 0
374
- );
412
+ .current-editor {
413
+ display: flex;
414
+ align-items: center;
415
+ gap: 12px;
416
+ margin-right: 12px;
417
+ color: var(--app-bar-current-editor-color);
418
+ font-family: var(--app-bar-current-editor-font-family);
419
+ font-size: var(--app-bar-current-editor-font-size);
420
+ font-style: var(--app-bar-current-editor-font-style);
421
+ line-height: var(--app-bar-current-editor-line-height);
422
+ font-weight: var(--app-bar-current-editor-font-weight);
423
+ white-space: nowrap;
424
+ }
425
+
426
+ oscd-divider.vertical {
427
+ width: 1px;
428
+ height: 32px;
429
+ --md-divider-color: var(--app-bar-separator-color);
430
+ opacity: var(--app-bar-separator-opacity);
431
+ }
432
+
433
+ [slot='alignEnd'] {
434
+ display: flex;
435
+ align-items: center;
436
+ gap: 4px;
437
+ }
438
+
439
+ [slot='alignEnd'] oscd-divider.vertical {
440
+ margin: 0 8px;
441
+ }
442
+
443
+ [slot='alignEnd'] oscd-filled-icon-button {
444
+ /* Local colour scheme: setting the system colour once lets the icon
445
+ colour and every derived state layer follow from one declaration.
446
+ Safe because the token mapping block is declared only on :host, so
447
+ --app-bar-action-icon-color is inherited here, not re-declared. */
448
+ --md-sys-color-on-primary: var(--app-bar-action-icon-color);
449
+ --md-filled-icon-button-icon-size: var(--app-bar-action-icon-size);
375
450
  --md-filled-icon-button-disabled-icon-color: var(
376
- --app-bar-action-icon-disabled-color,
377
- var(--md-sys-color-on-primary)
451
+ --app-bar-action-icon-disabled-color
452
+ );
453
+ --md-filled-icon-button-disabled-container-opacity: var(
454
+ --app-bar-action-icon-disabled-container-opacity
378
455
  );
379
- --md-filled-icon-button-icon-size: var(--app-bar-action-icon-size);
380
- --md-filled-icon-button-icon-color: var(--app-bar-action-icon-color);
381
456
  }
382
457
 
383
458
  main {
384
459
  grid-area: main;
385
460
  display: grid;
386
- grid-template-columns: var(--side-panel-width) 1fr;
461
+ /* The side-panel column follows the panel's own intrinsic width, which
462
+ the panel switches between its expanded (308px) and collapsed rail
463
+ (76px) states. */
464
+ grid-template-columns: auto 1fr;
387
465
  grid-template-areas: 'sidebar editor';
388
466
  overflow: hidden;
389
467
  }
390
468
 
391
- /* Side panel collapsed state */
392
- main.sidebar-collapsed {
393
- grid-template-columns: 0 1fr;
394
- }
395
-
396
469
  section.editors-side-panel-section {
397
470
  grid-area: sidebar;
398
471
  overflow-y: auto;
399
472
  overflow-x: hidden;
400
- transition: transform 0.3s ease-in-out;
401
- }
402
-
403
- /* Hide side panel when collapsed */
404
- main.sidebar-collapsed section.editors-side-panel-section {
405
- transform: translateX(-100%);
406
473
  }
407
474
 
408
475
  section.editor-container {
@@ -454,10 +521,7 @@ __decorate([
454
521
  ], OscdShell.prototype, "canUndo", null);
455
522
  __decorate([
456
523
  state()
457
- ], OscdShell.prototype, "editor", null);
458
- __decorate([
459
- state()
460
- ], OscdShell.prototype, "editorIndex", void 0);
524
+ ], OscdShell.prototype, "selectedEditor", void 0);
461
525
  __decorate([
462
526
  state()
463
527
  /** The `XMLDocument` currently being edited */
@@ -1 +1 @@
1
- {"version":3,"file":"oscd-shell.js","sourceRoot":"","sources":["../src/oscd-shell.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement, nothing } from 'lit';\nimport {\n customElement,\n property,\n query,\n queryAssignedNodes,\n state,\n} from 'lit/decorators.js';\nimport { ScopedElementsMixin } from '@open-wc/scoped-elements/lit-element.js';\nimport { localized, msg } from '@lit/localize';\nimport { html as staticHtml, unsafeStatic } from 'lit/static-html.js';\n\nimport { OscdFilledIconButton } from '@omicronenergy/oscd-ui/iconbutton/OscdFilledIconButton.js';\nimport { XMLEditor } from '@omicronenergy/oscd-editor';\nimport { EditEventV2, OpenEvent } from '@openscd/oscd-api';\n\nimport { loadSourcedPlugins } from './utils/plugin-utils.js';\nimport {\n getLocale,\n LocaleTag,\n setLocale,\n Translations,\n} from './localization.js';\nimport { EditorPluginsPanel } from './side-panel/editor-plugins-panel.js';\nimport { PluginsMenu } from './menus/plugins-menu.js';\nimport { LandingPage } from './landing-page/landing-page.js';\nimport { RenameEvent, CloseEvent } from './foundation/events.js';\nimport { FilesMenu } from './menus/files-menu.js';\nimport { oscdShellDesignTokens } from './oscd-shell-design-tokens.js';\nimport { OscdAppBar } from '@omicronenergy/oscd-ui/app-bar/OscdAppBar.js';\nimport { OscdIcon } from '@omicronenergy/oscd-ui/icon/OscdIcon.js';\n\nexport type PluginEntry = {\n name: string;\n translations?: Translations;\n tagName: string;\n icon: string;\n requireDoc?: boolean;\n};\n\nexport type SourcedPluginEntry = {\n name: string;\n translations?: Translations;\n src: string;\n icon: string;\n requireDoc?: boolean;\n};\nexport type PluginSet<P = PluginEntry> = {\n menu: P[];\n editor: P[];\n background: P[];\n};\n\n@localized()\n@customElement('oscd-shell')\nexport class OscdShell extends ScopedElementsMixin(LitElement) {\n static scopedElements = {\n 'oscd-app-bar': OscdAppBar,\n 'oscd-filled-icon-button': OscdFilledIconButton,\n 'oscd-icon': OscdIcon,\n 'files-menu': FilesMenu,\n 'plugins-menu': PluginsMenu,\n 'editor-plugins-panel': EditorPluginsPanel,\n 'landing-page': LandingPage,\n };\n\n /*\n * Properties\n */\n\n /**\n * Url to the app icon displayed in the app bar\n */\n @property({ type: String })\n appIcon: string = '';\n\n @property({ type: String })\n appTitle: string = 'OpenSCD';\n\n @property({ type: String })\n landingPageHeading: string = 'Welcome to OpenSCD';\n\n @property({ type: String })\n landingPageSubHeading: string =\n 'Open Source IEC-61850-6 SCL Editing Platform';\n\n /** The file endings of editable files */\n @property({ type: Array, reflect: true }) editable = [\n 'cid',\n 'icd',\n 'iid',\n 'scd',\n 'sed',\n 'ssd',\n ];\n\n @property({ type: String, reflect: true })\n get locale() {\n return getLocale() as LocaleTag;\n }\n\n set locale(tag: LocaleTag) {\n try {\n if (tag) {\n setLocale(tag);\n }\n } catch {\n // don't change locale if tag is invalid\n }\n }\n\n _plugins: PluginSet = { menu: [], editor: [], background: [] };\n\n @property({ type: Object })\n get plugins(): PluginSet {\n return this._plugins;\n }\n\n set plugins(\n plugins: Partial<PluginSet<Partial<PluginEntry | SourcedPluginEntry>>>,\n ) {\n this._plugins = Object.entries(plugins).reduce(\n (acc, [pluginType, kind]) => {\n const convertedPlugins = loadSourcedPlugins(kind, this.registry!);\n acc[pluginType as keyof PluginSet] = convertedPlugins;\n return acc;\n },\n { menu: [], editor: [], background: [] } as PluginSet,\n );\n }\n\n /*\n * States\n */\n @state()\n get canRedo(): boolean {\n return this.xmlEditor.future.length >= 1;\n }\n\n @state()\n get canUndo(): boolean {\n return this.xmlEditor.past.length >= 1;\n }\n\n @state()\n get editor() {\n return this.plugins.editor[this.editorIndex]?.tagName ?? '';\n }\n\n @state()\n private editorIndex = 0;\n\n @state()\n /** The `XMLDocument` currently being edited */\n get doc(): XMLDocument {\n return this.docs[this.docName];\n }\n\n /** The name of the [[`doc`]] currently being edited */\n @property({ type: String, reflect: true })\n docName = '';\n\n /** The set of `XMLDocument`s currently loaded */\n private _docs: Record<string, XMLDocument> = {};\n\n @state()\n get docs(): Record<string, XMLDocument> {\n return this._docs;\n }\n\n set docs(newDocs: Record<string, XMLDocument>) {\n this._docs = newDocs;\n this.onDocsChanged();\n }\n\n @state()\n docVersion: number = -1;\n\n @state()\n get editableDocs(): string[] {\n return Object.keys(this.docs).filter(name => this.isEditable(name));\n }\n\n @state()\n get last(): number {\n return this.xmlEditor.past.length - 1;\n }\n\n @state()\n xmlEditor: XMLEditor = new XMLEditor();\n\n /*\n * All Queries\n */\n\n @query('plugins-menu')\n pluginsMenu!: PluginsMenu;\n\n @query('editor-plugins-panel')\n editorPluginsPanel!: EditorPluginsPanel;\n\n @queryAssignedNodes({ slot: 'landing-page' })\n private _landingPageNodes?: NodeListOf<HTMLElement>;\n\n /*\n * Constructor & life cycle methods\n */\n constructor() {\n super();\n // Catch all edits (from commits AND events) and trigger an update\n this.xmlEditor.subscribe(() => {\n this.docVersion += 1;\n });\n }\n\n connectedCallback() {\n super.connectedCallback();\n\n document.addEventListener('keydown', this.handleKeyPress);\n this.addEventListener('oscd-open', this.handleOpenDoc);\n this.addEventListener('oscd-rename', this.handleRenameDoc);\n this.addEventListener('oscd-close', this.handleCloseDoc);\n this.addEventListener('oscd-edit-v2', this.handleEditV2);\n this.addEventListener('oscd-undo', this.handleUndo);\n this.addEventListener('oscd-redo', this.handleRedo);\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n\n // Remove event listeners\n document.removeEventListener('keydown', this.handleKeyPress);\n this.removeEventListener('oscd-open', this.handleOpenDoc);\n this.removeEventListener('oscd-rename', this.handleRenameDoc);\n this.removeEventListener('oscd-edit-v2', this.handleEditV2);\n this.removeEventListener('oscd-undo', this.handleUndo);\n this.removeEventListener('oscd-redo', this.handleRedo);\n this.removeEventListener('oscd-close', this.handleCloseDoc);\n }\n\n /*\n * Event Handlers\n */\n\n handleOpenDoc = ({ detail: { docName, doc } }: OpenEvent) => {\n this.docs = {\n ...this.docs,\n [docName]: doc,\n };\n if (this.isEditable(docName)) {\n this.docName = docName;\n }\n this.requestUpdate();\n };\n\n handleRenameDoc = (customEvent: RenameEvent) => {\n const { oldName, newName } = customEvent.detail;\n if (!this.docs[oldName] || newName === oldName || this.docs[newName]) {\n return;\n }\n const doc = this.docs[oldName];\n delete this.docs[oldName];\n this.docs = {\n ...this.docs,\n [newName]: doc,\n };\n this.docName = newName;\n };\n\n handleEditV2 = (event: EditEventV2) => {\n const { edit, title, squash } = event.detail;\n this.xmlEditor.commit(edit, { title, squash });\n };\n\n handleCloseDoc = (event: CloseEvent) => {\n const docName = event.detail.docName as string;\n delete this.docs[docName];\n if (this.docName === docName) {\n this.docName = this.editableDocs[0] || '';\n }\n };\n\n handleUndo = () => {\n this.undo();\n };\n\n handleRedo = () => {\n this.redo();\n };\n\n private handleKeyPress = (e: KeyboardEvent) => {\n if (!e.ctrlKey) {\n return;\n }\n if (!Object.prototype.hasOwnProperty.call(this.hotkeys, e.key)) {\n return;\n }\n this.hotkeys[e.key]!.call(this);\n e.preventDefault();\n };\n\n handleOpenPluginMenu = () => {\n this.pluginsMenu.open();\n };\n\n /** Undo the last `n` [[Edit]]s committed */\n undo = (n = 1) => {\n if (!this.canUndo || n < 1) {\n return;\n }\n this.xmlEditor.undo();\n if (n > 1) {\n this.undo(n - 1);\n }\n this.requestUpdate();\n };\n\n /** Redo the last `n` [[Edit]]s that have been undone */\n redo = (n = 1) => {\n if (!this.canRedo || n < 1) {\n return;\n }\n this.xmlEditor.redo();\n if (n > 1) {\n this.redo(n - 1);\n }\n this.requestUpdate();\n };\n\n private hotkeys: Partial<Record<string, () => void>> = {\n m: this.handleOpenPluginMenu,\n z: this.undo,\n y: this.redo,\n Z: this.redo,\n };\n\n isEditable(docName: string): boolean {\n return !!this.editable.find(ext =>\n docName.toLowerCase().endsWith(`.${ext}`),\n );\n }\n\n onDocsChanged() {\n this.docVersion += 1;\n }\n\n renderPlugin(tagName: string) {\n const tag = unsafeStatic(tagName);\n return staticHtml`<${tag} \n .locale=\"${this.locale}\"\n .docName=\"${this.docName}\"\n .doc=${this.doc}\n .docs=${this.docs} \n .editCount=${this.docVersion}\n .docVersion=${this.docVersion}\n .editor=${this.xmlEditor}>\n </${tag}>`;\n }\n\n onMenuPluginSelect(customEvent: CustomEvent) {\n const plugin = customEvent.detail.plugin as PluginEntry;\n if (plugin.tagName) {\n this.shadowRoot!.querySelector<\n HTMLElement & { run: () => Promise<void> }\n >(plugin.tagName)!.run?.();\n }\n }\n\n renderOffScreenPlugins() {\n return html`\n <section class=\"off-screen-plugin-container\" aria-hidden=\"true\">\n <div class=\"menu-plugins\">\n ${this.plugins.menu\n .filter(plugin => !plugin.requireDoc || !!this.docName)\n .map(plugin => this.renderPlugin(plugin.tagName))}\n </div>\n <div class=\"background-plugins\">\n ${this.plugins.background\n .filter(plugin => !plugin.requireDoc || !!this.docName)\n .map(plugin => this.renderPlugin(plugin.tagName))}\n </div>\n </section>\n `;\n }\n\n renderDefaultLandingPage() {\n return html`\n <landing-page\n heading=${this.landingPageHeading}\n subHeading=${this.landingPageSubHeading}\n .menuPlugins=${this.plugins.menu.filter(\n plugin => !plugin.requireDoc || !!this.docName,\n )}\n .locale=${this.locale}\n @menu-plugin-select=${(event: CustomEvent) =>\n this.onMenuPluginSelect(event)}\n >\n </landing-page>\n `;\n }\n\n render() {\n const hasCustomLandingPage = !!this._landingPageNodes?.length;\n if (this.editableDocs.length === 0) {\n return html` <slot\n name=\"landing-page\"\n @slotchange=${() => this.requestUpdate()}\n ></slot>\n ${!hasCustomLandingPage ? this.renderDefaultLandingPage() : nothing}\n ${this.renderOffScreenPlugins()}`;\n }\n\n return html` <oscd-app-bar>\n <plugins-menu\n slot=\"alignStart\"\n appTitle=${this.appTitle}\n appIcon=${this.appIcon}\n .editableDocs=${this.editableDocs}\n .menuPlugins=${this.plugins.menu}\n .locale=${this.locale}\n @menu-plugin-select=${(event: CustomEvent) =>\n this.onMenuPluginSelect(event)}\n ></plugins-menu>\n\n <files-menu\n slot=\"alignMiddle\"\n .selectedDocName=${this.docName}\n .editableDocs=${this.editableDocs}\n .locale=${this.locale}\n @change=${(event: CustomEvent) => {\n const name = event.detail.name as string;\n this.docName = name;\n }}\n ></files-menu>\n\n <div slot=\"alignEnd\">\n <oscd-filled-icon-button\n aria-label=\"${msg('Undo')}\"\n ?disabled=${!this.canUndo}\n @click=${async () => {\n this.dispatchEvent(\n new CustomEvent('oscd-undo', {\n bubbles: true,\n composed: true,\n }),\n );\n }}\n ><oscd-icon>undo</oscd-icon></oscd-filled-icon-button\n >\n <oscd-filled-icon-button\n aria-label=\"${msg('Redo')}\"\n ?disabled=${!this.canRedo}\n @click=${async () => {\n this.dispatchEvent(\n new CustomEvent('oscd-redo', {\n bubbles: true,\n composed: true,\n }),\n );\n }}\n ><oscd-icon>redo</oscd-icon></oscd-filled-icon-button\n >\n </div>\n </oscd-app-bar>\n\n <main>\n <section class=\"editors-side-panel-section\">\n <editor-plugins-panel\n .editors=${this.plugins.editor}\n .editorIndex=${this.editorIndex}\n .locale=${this.locale}\n @editor-select=${(e: CustomEvent) => {\n this.editorIndex = e.detail.index;\n }}\n ></editor-plugins-panel>\n </section>\n\n <section class=\"editor-container\">\n ${this.editor ? this.renderPlugin(this.editor) : nothing}\n </section>\n\n ${this.renderOffScreenPlugins()}\n </main>`;\n }\n\n static styles = [\n oscdShellDesignTokens,\n css`\n :host {\n height: 100%;\n display: grid;\n grid-template-rows: min-content 1fr;\n grid-template-columns: 1fr;\n grid-template-areas:\n 'header'\n 'main';\n }\n\n oscd-app-bar {\n grid-area: header;\n box-shadow: var(--md-sys-elevation-level-2);\n z-index: 10;\n }\n\n oscd-app-bar * {\n --md-filled-icon-button-disabled-container-opacity: var(\n --app-bar-action-icon-disabled-container-opacity,\n 0\n );\n --md-filled-icon-button-disabled-icon-color: var(\n --app-bar-action-icon-disabled-color,\n var(--md-sys-color-on-primary)\n );\n --md-filled-icon-button-icon-size: var(--app-bar-action-icon-size);\n --md-filled-icon-button-icon-color: var(--app-bar-action-icon-color);\n }\n\n main {\n grid-area: main;\n display: grid;\n grid-template-columns: var(--side-panel-width) 1fr;\n grid-template-areas: 'sidebar editor';\n overflow: hidden;\n }\n\n /* Side panel collapsed state */\n main.sidebar-collapsed {\n grid-template-columns: 0 1fr;\n }\n\n section.editors-side-panel-section {\n grid-area: sidebar;\n overflow-y: auto;\n overflow-x: hidden;\n transition: transform 0.3s ease-in-out;\n }\n\n /* Hide side panel when collapsed */\n main.sidebar-collapsed section.editors-side-panel-section {\n transform: translateX(-100%);\n }\n\n section.editor-container {\n grid-area: editor;\n background-color: var(--editor-background-color);\n padding: var(--editor-padding);\n overflow: auto;\n position: relative;\n }\n\n .off-screen-plugin-container {\n position: absolute;\n top: 0;\n left: 0;\n width: 0;\n height: 0;\n overflow: hidden;\n margin: 0;\n padding: 0;\n }\n `,\n ];\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'oscd-shell': OscdShell;\n }\n}\n"]}
1
+ {"version":3,"file":"oscd-shell.js","sourceRoot":"","sources":["../src/oscd-shell.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement, nothing } from 'lit';\nimport {\n customElement,\n property,\n query,\n queryAssignedNodes,\n state,\n} from 'lit/decorators.js';\nimport { ScopedElementsMixin } from '@open-wc/scoped-elements/lit-element.js';\nimport { localized, msg } from '@lit/localize';\nimport { html as staticHtml, unsafeStatic } from 'lit/static-html.js';\n\nimport { OscdFilledIconButton } from '@omicronenergy/oscd-ui/iconbutton/OscdFilledIconButton.js';\nimport { XMLEditor } from '@openscd/oscd-editor';\nimport { EditEventV2, OpenEvent } from '@openscd/oscd-api';\n\nimport {\n flattenPluginEntries,\n loadSourcedPlugins,\n} from './utils/plugin-utils.js';\nimport { getLocale, LocaleTag, setLocale } from './localization.js';\nimport { EditorPluginsPanel } from './side-panel/editor-plugins-panel.js';\nimport { PluginsMenu } from './menus/plugins-menu.js';\nimport { LandingPage } from './landing-page/landing-page.js';\nimport { RenameEvent, CloseEvent } from './foundation/events.js';\nimport { FilesMenu } from './menus/files-menu.js';\nimport { oscdShellDesignTokens } from './oscd-shell-design-tokens.js';\nimport { OscdAppBar } from '@omicronenergy/oscd-ui/app-bar/OscdAppBar.js';\nimport { OscdDivider } from '@omicronenergy/oscd-ui/divider/OscdDivider.js';\nimport { OscdIcon } from '@omicronenergy/oscd-ui/icon/OscdIcon.js';\n\nexport interface PluginBase {\n name: string;\n translations?: Record<string, string>;\n icon: string;\n}\n\nexport interface SourcedPluginEntry extends PluginBase {\n src: string;\n requireDoc?: boolean;\n}\n\nexport interface PluginEntry extends PluginBase {\n tagName: string;\n requireDoc?: boolean;\n}\n\n/**\n * The unvalidated plugin shape accepted at the `plugins` setter boundary.\n * Fields are optional and either `tagName` or `src` may be present; the\n * concrete kind is resolved and validated later by `loadSourcedPlugins`.\n */\nexport type InputPluginEntry = Partial<PluginEntry & SourcedPluginEntry>;\n\nexport interface PluginGroup<\n P extends Partial<PluginBase> = PluginEntry,\n> extends PluginBase {\n plugins: P[];\n}\n\nexport interface PluginSet<P extends Partial<PluginBase> = PluginEntry> {\n menu: (P | PluginGroup<P>)[];\n editor: (P | PluginGroup<P>)[];\n background: P[];\n}\n\n@localized()\n@customElement('oscd-shell')\nexport class OscdShell extends ScopedElementsMixin(LitElement) {\n static scopedElements = {\n 'oscd-app-bar': OscdAppBar,\n 'oscd-filled-icon-button': OscdFilledIconButton,\n 'oscd-divider': OscdDivider,\n 'oscd-icon': OscdIcon,\n 'files-menu': FilesMenu,\n 'plugins-menu': PluginsMenu,\n 'editor-plugins-panel': EditorPluginsPanel,\n 'landing-page': LandingPage,\n };\n\n /*\n * Properties\n */\n\n /**\n * Url to the app icon displayed in the app bar\n */\n @property({ type: String })\n appIcon: string = '';\n\n @property({ type: String })\n appTitle: string = 'OpenSCD';\n\n @property({ type: String })\n landingPageHeading: string = 'Welcome to OpenSCD';\n\n @property({ type: String })\n landingPageSubHeading: string =\n 'Open Source IEC-61850-6 SCL Editing Platform';\n\n /** The file endings of editable files */\n @property({ type: Array, reflect: true }) editable = [\n 'cid',\n 'icd',\n 'iid',\n 'scd',\n 'sed',\n 'ssd',\n ];\n\n @property({ type: String, reflect: true })\n get locale() {\n return getLocale() as LocaleTag;\n }\n\n set locale(tag: LocaleTag) {\n try {\n if (tag) {\n setLocale(tag);\n }\n } catch {\n // don't change locale if tag is invalid\n }\n }\n\n _plugins: PluginSet = { menu: [], editor: [], background: [] };\n\n @property({ type: Object })\n get plugins(): PluginSet {\n return this._plugins;\n }\n\n set plugins(plugins: Partial<PluginSet<InputPluginEntry>>) {\n this._plugins = Object.entries(plugins).reduce(\n (acc, [pluginType, kind]) => {\n const convertedPlugins = loadSourcedPlugins(kind, this.registry!);\n acc[pluginType as keyof PluginSet] = convertedPlugins;\n return acc;\n },\n { menu: [], editor: [], background: [] } as PluginSet,\n );\n }\n\n /*\n * States\n */\n @state()\n get canRedo(): boolean {\n return this.xmlEditor.future.length >= 1;\n }\n\n @state()\n get canUndo(): boolean {\n return this.xmlEditor.past.length >= 1;\n }\n\n @state()\n selectedEditor?: PluginEntry;\n\n @state()\n /** The `XMLDocument` currently being edited */\n get doc(): XMLDocument {\n return this.docs[this.docName];\n }\n\n /** The name of the [[`doc`]] currently being edited */\n @property({ type: String, reflect: true })\n docName = '';\n\n /** The set of `XMLDocument`s currently loaded */\n private _docs: Record<string, XMLDocument> = {};\n\n @state()\n get docs(): Record<string, XMLDocument> {\n return this._docs;\n }\n\n set docs(newDocs: Record<string, XMLDocument>) {\n this._docs = newDocs;\n this.docVersion += 1;\n }\n\n @state()\n docVersion: number = -1;\n\n @state()\n get editableDocs(): string[] {\n return Object.keys(this.docs).filter(name => this.isEditable(name));\n }\n\n @state()\n get last(): number {\n return this.xmlEditor.past.length - 1;\n }\n\n @state()\n xmlEditor: XMLEditor = new XMLEditor();\n\n /*\n * All Queries\n */\n\n @query('plugins-menu')\n pluginsMenu!: PluginsMenu;\n\n @query('editor-plugins-panel')\n editorPluginsPanel!: EditorPluginsPanel;\n\n @queryAssignedNodes({ slot: 'landing-page' })\n private _landingPageNodes?: NodeListOf<HTMLElement>;\n\n /*\n * Constructor & life cycle methods\n */\n constructor() {\n super();\n // Catch all edits (from commits AND events) and trigger an update\n this.xmlEditor.subscribe(() => {\n this.docVersion += 1;\n });\n }\n\n willUpdate(changedProperties: Map<PropertyKey, unknown>) {\n if (changedProperties.has('docName') || changedProperties.has('plugins')) {\n const firstEditor = flattenPluginEntries(this.plugins.editor)[0];\n if (this.docName && firstEditor && !this.selectedEditor) {\n this.selectedEditor = firstEditor;\n }\n }\n }\n\n connectedCallback() {\n super.connectedCallback();\n\n document.addEventListener('keydown', this.handleKeyPress, true);\n this.addEventListener('oscd-open', this.handleOpenDoc);\n this.addEventListener('oscd-rename', this.handleRenameDoc);\n this.addEventListener('oscd-close', this.handleCloseDoc);\n this.addEventListener('oscd-edit-v2', this.handleEditV2);\n this.addEventListener('oscd-undo', this.handleUndo);\n this.addEventListener('oscd-redo', this.handleRedo);\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n\n // Remove event listeners\n document.removeEventListener('keydown', this.handleKeyPress, true);\n this.removeEventListener('oscd-open', this.handleOpenDoc);\n this.removeEventListener('oscd-rename', this.handleRenameDoc);\n this.removeEventListener('oscd-edit-v2', this.handleEditV2);\n this.removeEventListener('oscd-undo', this.handleUndo);\n this.removeEventListener('oscd-redo', this.handleRedo);\n this.removeEventListener('oscd-close', this.handleCloseDoc);\n }\n\n /*\n * Event Handlers\n */\n\n handleOpenDoc = ({ detail: { docName, doc } }: OpenEvent) => {\n this.docs = {\n ...this.docs,\n [docName]: doc,\n };\n if (this.isEditable(docName)) {\n this.docName = docName;\n }\n this.requestUpdate();\n };\n\n handleRenameDoc = (customEvent: RenameEvent) => {\n const { oldName, newName } = customEvent.detail;\n if (!this.docs[oldName] || newName === oldName || this.docs[newName]) {\n return;\n }\n const doc = this.docs[oldName];\n delete this.docs[oldName];\n this.docs = {\n ...this.docs,\n [newName]: doc,\n };\n this.docName = newName;\n };\n\n handleEditV2 = (event: EditEventV2) => {\n const { edit, title, squash } = event.detail;\n this.xmlEditor.commit(edit, { title, squash });\n };\n\n handleCloseDoc = (event: CloseEvent) => {\n const docName = event.detail.docName as string;\n delete this.docs[docName];\n if (this.docName === docName) {\n this.docName = this.editableDocs[0] || '';\n }\n };\n\n handleUndo = () => {\n this.undo();\n };\n\n handleRedo = () => {\n this.redo();\n };\n\n private handleKeyPress = (e: KeyboardEvent) => {\n if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key.toLowerCase() === 'f') {\n const panel = this.shadowRoot?.querySelector<EditorPluginsPanel>(\n 'editor-plugins-panel',\n );\n if (panel) {\n if (!panel.expanded) {\n panel.searchMode = true;\n }\n panel.focusSearch(true);\n e.preventDefault();\n e.stopPropagation();\n }\n return;\n }\n if (!e.ctrlKey) {\n return;\n }\n if (!Object.prototype.hasOwnProperty.call(this.hotkeys, e.key)) {\n return;\n }\n this.hotkeys[e.key]!.call(this);\n e.preventDefault();\n };\n\n handleOpenPluginMenu = () => {\n this.pluginsMenu.open();\n };\n\n handlePluginMenuSelect(customEvent: CustomEvent) {\n const plugin = customEvent.detail.plugin as PluginEntry;\n if (plugin.tagName) {\n this.shadowRoot!.querySelector<\n HTMLElement & { run: () => Promise<void> }\n >(plugin.tagName)!.run?.();\n }\n }\n /** Undo the last `n` [[Edit]]s committed */\n undo = (n = 1) => {\n if (!this.canUndo || n < 1) {\n return;\n }\n this.xmlEditor.undo();\n if (n > 1) {\n this.undo(n - 1);\n }\n this.requestUpdate();\n };\n\n /** Redo the last `n` [[Edit]]s that have been undone */\n redo = (n = 1) => {\n if (!this.canRedo || n < 1) {\n return;\n }\n this.xmlEditor.redo();\n if (n > 1) {\n this.redo(n - 1);\n }\n this.requestUpdate();\n };\n\n private hotkeys: Partial<Record<string, () => void>> = {\n m: this.handleOpenPluginMenu,\n z: this.undo,\n y: this.redo,\n Z: this.redo,\n };\n\n isEditable(docName: string): boolean {\n return !!this.editable.find(ext =>\n docName.toLowerCase().endsWith(`.${ext}`),\n );\n }\n\n renderPlugin(plugin: PluginEntry) {\n const tag = unsafeStatic(plugin.tagName);\n return staticHtml`<${tag}\n .locale=\"${this.locale}\"\n .docName=\"${this.docName}\"\n .doc=${this.doc}\n .docs=${this.docs}\n .editCount=${this.docVersion}\n .docVersion=${this.docVersion}\n .editor=${this.xmlEditor}>\n </${tag}>`;\n }\n\n renderOffScreenPlugins() {\n return html`\n <section class=\"off-screen-plugin-container\" aria-hidden=\"true\">\n <div class=\"menu-plugins\">\n ${flattenPluginEntries(this.plugins.menu)\n .filter(plugin => !plugin.requireDoc || !!this.docName)\n .map(plugin => this.renderPlugin(plugin))}\n </div>\n <div class=\"background-plugins\">\n ${this.plugins.background\n .filter(plugin => !plugin.requireDoc || !!this.docName)\n .map(plugin => this.renderPlugin(plugin))}\n </div>\n </section>\n `;\n }\n\n renderDefaultLandingPage() {\n return html`\n <landing-page\n heading=${this.landingPageHeading}\n subHeading=${this.landingPageSubHeading}\n .menuPlugins=${flattenPluginEntries(this.plugins.menu).filter(\n plugin => !plugin.requireDoc || !!this.docName,\n )}\n .locale=${this.locale}\n @menu-plugin-select=${(event: CustomEvent) =>\n this.handlePluginMenuSelect(event)}\n >\n </landing-page>\n `;\n }\n\n render() {\n const hasCustomLandingPage = !!this._landingPageNodes?.length;\n if (this.editableDocs.length === 0) {\n return html` <div class=\"landing-page-container\">\n <slot\n name=\"landing-page\"\n @slotchange=${() => this.requestUpdate()}\n ></slot>\n ${!hasCustomLandingPage ? this.renderDefaultLandingPage() : nothing}\n </div>\n ${this.renderOffScreenPlugins()}`;\n }\n\n return html` <oscd-app-bar>\n <plugins-menu\n slot=\"alignStart\"\n appTitle=${this.appTitle}\n appIcon=${this.appIcon}\n .editableDocs=${this.editableDocs}\n .menuPlugins=${this.plugins.menu}\n .locale=${this.locale}\n @menu-plugin-select=${(event: CustomEvent) =>\n this.handlePluginMenuSelect(event)}\n ></plugins-menu>\n\n ${this.selectedEditor\n ? html`<div class=\"current-editor\" slot=\"alignStart\">\n <oscd-divider class=\"vertical\" aria-hidden=\"true\"></oscd-divider>\n <span>${this.selectedEditor.name}</span>\n </div>`\n : nothing}\n\n <div slot=\"alignEnd\">\n ${this.docName\n ? html`<files-menu\n .selectedDocName=${this.docName}\n .editableDocs=${this.editableDocs}\n .locale=${this.locale}\n @change=${(event: CustomEvent) => {\n const name = event.detail.name as string;\n this.docName = name;\n }}\n ></files-menu>\n <oscd-divider\n class=\"vertical\"\n aria-hidden=\"true\"\n ></oscd-divider>`\n : nothing}\n <oscd-filled-icon-button\n aria-label=\"${msg('Undo')}\"\n ?disabled=${!this.canUndo}\n @click=${async () => {\n this.dispatchEvent(\n new CustomEvent('oscd-undo', {\n bubbles: true,\n composed: true,\n }),\n );\n }}\n ><oscd-icon>undo</oscd-icon></oscd-filled-icon-button\n >\n <oscd-filled-icon-button\n aria-label=\"${msg('Redo')}\"\n ?disabled=${!this.canRedo}\n @click=${async () => {\n this.dispatchEvent(\n new CustomEvent('oscd-redo', {\n bubbles: true,\n composed: true,\n }),\n );\n }}\n ><oscd-icon>redo</oscd-icon></oscd-filled-icon-button\n >\n </div>\n </oscd-app-bar>\n\n <main>\n <section class=\"editors-side-panel-section\">\n <editor-plugins-panel\n .editors=${this.plugins.editor}\n .selectedEditor=${this.selectedEditor}\n .locale=${this.locale}\n @editor-select=${(e: CustomEvent) => {\n this.selectedEditor = e.detail.editor;\n }}\n ></editor-plugins-panel>\n </section>\n\n <section class=\"editor-container\">\n ${this.selectedEditor\n ? this.renderPlugin(this.selectedEditor)\n : nothing}\n </section>\n\n ${this.renderOffScreenPlugins()}\n </main>`;\n }\n\n static styles = [\n oscdShellDesignTokens,\n css`\n :host {\n height: 100%;\n display: grid;\n grid-template-rows: min-content 1fr;\n grid-template-columns: 1fr;\n grid-template-areas:\n 'header'\n 'main';\n background-color: var(--shell-background-color);\n }\n\n .landing-page-container {\n grid-column: 1 / -1;\n grid-row: 1 / -1;\n height: 100%;\n overflow: auto;\n }\n\n .landing-page-container ::slotted(*) {\n height: 100%;\n }\n\n oscd-app-bar {\n grid-area: header;\n box-shadow: var(--md-sys-elevation-level-2);\n z-index: 10;\n }\n\n .current-editor {\n display: flex;\n align-items: center;\n gap: 12px;\n margin-right: 12px;\n color: var(--app-bar-current-editor-color);\n font-family: var(--app-bar-current-editor-font-family);\n font-size: var(--app-bar-current-editor-font-size);\n font-style: var(--app-bar-current-editor-font-style);\n line-height: var(--app-bar-current-editor-line-height);\n font-weight: var(--app-bar-current-editor-font-weight);\n white-space: nowrap;\n }\n\n oscd-divider.vertical {\n width: 1px;\n height: 32px;\n --md-divider-color: var(--app-bar-separator-color);\n opacity: var(--app-bar-separator-opacity);\n }\n\n [slot='alignEnd'] {\n display: flex;\n align-items: center;\n gap: 4px;\n }\n\n [slot='alignEnd'] oscd-divider.vertical {\n margin: 0 8px;\n }\n\n [slot='alignEnd'] oscd-filled-icon-button {\n /* Local colour scheme: setting the system colour once lets the icon\n colour and every derived state layer follow from one declaration.\n Safe because the token mapping block is declared only on :host, so\n --app-bar-action-icon-color is inherited here, not re-declared. */\n --md-sys-color-on-primary: var(--app-bar-action-icon-color);\n --md-filled-icon-button-icon-size: var(--app-bar-action-icon-size);\n --md-filled-icon-button-disabled-icon-color: var(\n --app-bar-action-icon-disabled-color\n );\n --md-filled-icon-button-disabled-container-opacity: var(\n --app-bar-action-icon-disabled-container-opacity\n );\n }\n\n main {\n grid-area: main;\n display: grid;\n /* The side-panel column follows the panel's own intrinsic width, which\n the panel switches between its expanded (308px) and collapsed rail\n (76px) states. */\n grid-template-columns: auto 1fr;\n grid-template-areas: 'sidebar editor';\n overflow: hidden;\n }\n\n section.editors-side-panel-section {\n grid-area: sidebar;\n overflow-y: auto;\n overflow-x: hidden;\n }\n\n section.editor-container {\n grid-area: editor;\n background-color: var(--editor-background-color);\n padding: var(--editor-padding);\n overflow: auto;\n position: relative;\n }\n\n .off-screen-plugin-container {\n position: absolute;\n top: 0;\n left: 0;\n width: 0;\n height: 0;\n overflow: hidden;\n margin: 0;\n padding: 0;\n }\n `,\n ];\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'oscd-shell': OscdShell;\n }\n}\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,50 @@
1
+ import { expect } from '@open-wc/testing';
2
+ import { buildTreeNodes } from './editor-plugins-panel.js';
3
+ const leaf = (name, tagName) => ({
4
+ name,
5
+ tagName,
6
+ icon: 'margin',
7
+ });
8
+ describe('buildTreeNodes', () => {
9
+ it('converts a leaf plugin into a node with id set to its tagName', () => {
10
+ const [node] = buildTreeNodes([
11
+ leaf('Substation Editor', 'oscd-substation'),
12
+ ]);
13
+ expect(node.id).to.equal('oscd-substation');
14
+ expect(node.name).to.equal('Substation Editor');
15
+ expect(node.children).to.deep.equal([]);
16
+ });
17
+ it('converts a group into a node with a unique positional id and built children', () => {
18
+ const group = {
19
+ name: 'Communication',
20
+ icon: 'folder',
21
+ plugins: [leaf('GOOSE Editor', 'oscd-goose')],
22
+ };
23
+ const [node] = buildTreeNodes([group]);
24
+ expect(node.id).to.equal('group:0:Communication');
25
+ expect(node.children).to.have.lengthOf(1);
26
+ expect(node.children?.[0].id).to.equal('oscd-goose');
27
+ });
28
+ it('gives same-named groups distinct ids by position', () => {
29
+ const group = (name) => ({
30
+ name,
31
+ icon: 'folder',
32
+ plugins: [],
33
+ });
34
+ const nodes = buildTreeNodes([group('Files'), group('Files')]);
35
+ expect(nodes.map(n => n.id)).to.deep.equal([
36
+ 'group:0:Files',
37
+ 'group:1:Files',
38
+ ]);
39
+ });
40
+ it('builds leaf nodes from a flat list (as used for pinned plugins)', () => {
41
+ // Regression: pinned plugins are flattened entries; each must gain an id so
42
+ // the pinned tree can render and match them.
43
+ const nodes = buildTreeNodes([
44
+ leaf('GOOSE Editor', 'oscd-goose'),
45
+ leaf('Sampled Values', 'oscd-smv'),
46
+ ]);
47
+ expect(nodes.map(n => n.id)).to.deep.equal(['oscd-goose', 'oscd-smv']);
48
+ });
49
+ });
50
+ //# sourceMappingURL=build-tree-nodes.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-tree-nodes.spec.js","sourceRoot":"","sources":["../../src/side-panel/build-tree-nodes.spec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAG3D,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,OAAe,EAAe,EAAE,CAAC,CAAC;IAC5D,IAAI;IACJ,OAAO;IACP,IAAI,EAAE,QAAQ;CACf,CAAC,CAAC;AAEH,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACvE,MAAM,CAAC,IAAI,CAAC,GAAG,cAAc,CAAC;YAC5B,IAAI,CAAC,mBAAmB,EAAE,iBAAiB,CAAC;SAC7C,CAAC,CAAC;QACH,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;QAC5C,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC;QAChD,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6EAA6E,EAAE,GAAG,EAAE;QACrF,MAAM,KAAK,GAAgB;YACzB,IAAI,EAAE,eAAe;YACrB,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,CAAC,IAAI,CAAC,cAAc,EAAE,YAAY,CAAC,CAAC;SAC9C,CAAC;QACF,MAAM,CAAC,IAAI,CAAC,GAAG,cAAc,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;QACvC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;QAClD,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAC1C,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,KAAK,GAAG,CAAC,IAAY,EAAe,EAAE,CAAC,CAAC;YAC5C,IAAI;YACJ,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,EAAE;SACZ,CAAC,CAAC;QACH,MAAM,KAAK,GAAG,cAAc,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAC/D,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC;YACzC,eAAe;YACf,eAAe;SAChB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;QACzE,4EAA4E;QAC5E,6CAA6C;QAC7C,MAAM,KAAK,GAAG,cAAc,CAAC;YAC3B,IAAI,CAAC,cAAc,EAAE,YAAY,CAAC;YAClC,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC;SACnC,CAAC,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { expect } from '@open-wc/testing';\nimport { buildTreeNodes } from './editor-plugins-panel.js';\nimport { PluginEntry, PluginGroup } from '../oscd-shell.js';\n\nconst leaf = (name: string, tagName: string): PluginEntry => ({\n name,\n tagName,\n icon: 'margin',\n});\n\ndescribe('buildTreeNodes', () => {\n it('converts a leaf plugin into a node with id set to its tagName', () => {\n const [node] = buildTreeNodes([\n leaf('Substation Editor', 'oscd-substation'),\n ]);\n expect(node.id).to.equal('oscd-substation');\n expect(node.name).to.equal('Substation Editor');\n expect(node.children).to.deep.equal([]);\n });\n\n it('converts a group into a node with a unique positional id and built children', () => {\n const group: PluginGroup = {\n name: 'Communication',\n icon: 'folder',\n plugins: [leaf('GOOSE Editor', 'oscd-goose')],\n };\n const [node] = buildTreeNodes([group]);\n expect(node.id).to.equal('group:0:Communication');\n expect(node.children).to.have.lengthOf(1);\n expect(node.children?.[0].id).to.equal('oscd-goose');\n });\n\n it('gives same-named groups distinct ids by position', () => {\n const group = (name: string): PluginGroup => ({\n name,\n icon: 'folder',\n plugins: [],\n });\n const nodes = buildTreeNodes([group('Files'), group('Files')]);\n expect(nodes.map(n => n.id)).to.deep.equal([\n 'group:0:Files',\n 'group:1:Files',\n ]);\n });\n\n it('builds leaf nodes from a flat list (as used for pinned plugins)', () => {\n // Regression: pinned plugins are flattened entries; each must gain an id so\n // the pinned tree can render and match them.\n const nodes = buildTreeNodes([\n leaf('GOOSE Editor', 'oscd-goose'),\n leaf('Sampled Values', 'oscd-smv'),\n ]);\n expect(nodes.map(n => n.id)).to.deep.equal(['oscd-goose', 'oscd-smv']);\n });\n});\n"]}