@omicronenergy/oscd-shell 0.0.5 → 0.0.8

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 (127) hide show
  1. package/dist/foundation/cyrb64.d.ts +6 -0
  2. package/dist/foundation/cyrb64.js +26 -0
  3. package/dist/foundation/cyrb64.js.map +1 -0
  4. package/dist/foundation.d.ts +1 -0
  5. package/dist/foundation.js +2 -0
  6. package/dist/foundation.js.map +1 -0
  7. package/dist/locales/de.d.ts +10 -0
  8. package/dist/locales/de.js +15 -0
  9. package/dist/locales/de.js.map +1 -0
  10. package/dist/locales.d.ts +13 -0
  11. package/dist/locales.js +21 -0
  12. package/dist/locales.js.map +1 -0
  13. package/dist/oscd-shell.d.ts +119 -0
  14. package/dist/oscd-shell.js +685 -0
  15. package/dist/oscd-shell.js.map +1 -0
  16. package/dist/utils/plugin-utils.d.ts +29 -0
  17. package/dist/utils/plugin-utils.js +138 -0
  18. package/dist/utils/plugin-utils.js.map +1 -0
  19. package/dist/utils/plugin-utils.spec.d.ts +1 -0
  20. package/dist/utils/plugin-utils.spec.js +115 -0
  21. package/dist/utils/plugin-utils.spec.js.map +1 -0
  22. package/package.json +40 -35
  23. package/.editorconfig +0 -22
  24. package/.github/actions/setup-node-env/action.yml +0 -49
  25. package/.github/workflows/release-please.yml +0 -84
  26. package/.github/workflows/test.yml +0 -18
  27. package/.github/workflows/update-screenshots.yml +0 -29
  28. package/.husky/commit-msg +0 -1
  29. package/.husky/pre-commit +0 -1
  30. package/.release-please-manifest.json +0 -1
  31. package/CHANGELOG.md +0 -73
  32. package/CONTRIBUTING.md +0 -393
  33. package/commitlint.config.js +0 -1
  34. package/demo/AddPlugins.js +0 -42
  35. package/demo/DemoPluginSrc.js +0 -9
  36. package/demo/embedded.html +0 -17
  37. package/demo/index.html +0 -95
  38. package/foundation/cyrb64.ts +0 -27
  39. package/foundation/edit-event.ts +0 -273
  40. package/foundation/open-event.ts +0 -21
  41. package/foundation.ts +0 -28
  42. package/lit-localize.json +0 -15
  43. package/localization/de.xlf +0 -75
  44. package/oscd-shell.editing.spec.ts +0 -813
  45. package/oscd-shell.plugging.spec.ts +0 -78
  46. package/oscd-shell.spec.ts +0 -431
  47. package/oscd-shell.test.ts +0 -382
  48. package/oscd-shell.ts +0 -906
  49. package/release-please-config.json +0 -21
  50. package/rollup.config.js +0 -51
  51. package/screenshots/Chromium/baseline/app-bar-de.png +0 -0
  52. package/screenshots/Chromium/baseline/app-bar-en.png +0 -0
  53. package/screenshots/Chromium/baseline/document-name-de.png +0 -0
  54. package/screenshots/Chromium/baseline/document-name-en.png +0 -0
  55. package/screenshots/Chromium/baseline/editor-plugins-de.png +0 -0
  56. package/screenshots/Chromium/baseline/editor-plugins-en.png +0 -0
  57. package/screenshots/Chromium/baseline/editor-plugins-selected-de.png +0 -0
  58. package/screenshots/Chromium/baseline/editor-plugins-selected-en.png +0 -0
  59. package/screenshots/Chromium/baseline/editor-plugins-with-doc-de.png +0 -0
  60. package/screenshots/Chromium/baseline/editor-plugins-with-doc-en.png +0 -0
  61. package/screenshots/Chromium/baseline/log-entries-de.png +0 -0
  62. package/screenshots/Chromium/baseline/log-entries-en.png +0 -0
  63. package/screenshots/Chromium/baseline/log-entries-redone-de.png +0 -0
  64. package/screenshots/Chromium/baseline/log-entries-redone-en.png +0 -0
  65. package/screenshots/Chromium/baseline/log-entries-undone-de.png +0 -0
  66. package/screenshots/Chromium/baseline/log-entries-undone-en.png +0 -0
  67. package/screenshots/Chromium/baseline/log-screen-de.png +0 -0
  68. package/screenshots/Chromium/baseline/log-screen-en.png +0 -0
  69. package/screenshots/Chromium/baseline/menu-drawer-de.png +0 -0
  70. package/screenshots/Chromium/baseline/menu-drawer-en.png +0 -0
  71. package/screenshots/Chromium/baseline/menu-plugins-de.png +0 -0
  72. package/screenshots/Chromium/baseline/menu-plugins-en.png +0 -0
  73. package/screenshots/Chromium/baseline/menu-plugins-triggered-de.png +0 -0
  74. package/screenshots/Chromium/baseline/menu-plugins-triggered-en.png +0 -0
  75. package/screenshots/Firefox/baseline/app-bar-de.png +0 -0
  76. package/screenshots/Firefox/baseline/app-bar-en.png +0 -0
  77. package/screenshots/Firefox/baseline/document-name-de.png +0 -0
  78. package/screenshots/Firefox/baseline/document-name-en.png +0 -0
  79. package/screenshots/Firefox/baseline/editor-plugins-de.png +0 -0
  80. package/screenshots/Firefox/baseline/editor-plugins-en.png +0 -0
  81. package/screenshots/Firefox/baseline/editor-plugins-selected-de.png +0 -0
  82. package/screenshots/Firefox/baseline/editor-plugins-selected-en.png +0 -0
  83. package/screenshots/Firefox/baseline/editor-plugins-with-doc-de.png +0 -0
  84. package/screenshots/Firefox/baseline/editor-plugins-with-doc-en.png +0 -0
  85. package/screenshots/Firefox/baseline/log-entries-de.png +0 -0
  86. package/screenshots/Firefox/baseline/log-entries-en.png +0 -0
  87. package/screenshots/Firefox/baseline/log-entries-redone-de.png +0 -0
  88. package/screenshots/Firefox/baseline/log-entries-redone-en.png +0 -0
  89. package/screenshots/Firefox/baseline/log-entries-undone-de.png +0 -0
  90. package/screenshots/Firefox/baseline/log-entries-undone-en.png +0 -0
  91. package/screenshots/Firefox/baseline/log-screen-de.png +0 -0
  92. package/screenshots/Firefox/baseline/log-screen-en.png +0 -0
  93. package/screenshots/Firefox/baseline/menu-drawer-de.png +0 -0
  94. package/screenshots/Firefox/baseline/menu-drawer-en.png +0 -0
  95. package/screenshots/Firefox/baseline/menu-plugins-de.png +0 -0
  96. package/screenshots/Firefox/baseline/menu-plugins-en.png +0 -0
  97. package/screenshots/Firefox/baseline/menu-plugins-triggered-de.png +0 -0
  98. package/screenshots/Firefox/baseline/menu-plugins-triggered-en.png +0 -0
  99. package/screenshots/Webkit/baseline/app-bar-de.png +0 -0
  100. package/screenshots/Webkit/baseline/app-bar-en.png +0 -0
  101. package/screenshots/Webkit/baseline/document-name-de.png +0 -0
  102. package/screenshots/Webkit/baseline/document-name-en.png +0 -0
  103. package/screenshots/Webkit/baseline/editor-plugins-de.png +0 -0
  104. package/screenshots/Webkit/baseline/editor-plugins-en.png +0 -0
  105. package/screenshots/Webkit/baseline/editor-plugins-selected-de.png +0 -0
  106. package/screenshots/Webkit/baseline/editor-plugins-selected-en.png +0 -0
  107. package/screenshots/Webkit/baseline/editor-plugins-with-doc-de.png +0 -0
  108. package/screenshots/Webkit/baseline/editor-plugins-with-doc-en.png +0 -0
  109. package/screenshots/Webkit/baseline/log-entries-de.png +0 -0
  110. package/screenshots/Webkit/baseline/log-entries-en.png +0 -0
  111. package/screenshots/Webkit/baseline/log-entries-redone-de.png +0 -0
  112. package/screenshots/Webkit/baseline/log-entries-redone-en.png +0 -0
  113. package/screenshots/Webkit/baseline/log-entries-undone-de.png +0 -0
  114. package/screenshots/Webkit/baseline/log-entries-undone-en.png +0 -0
  115. package/screenshots/Webkit/baseline/log-screen-de.png +0 -0
  116. package/screenshots/Webkit/baseline/log-screen-en.png +0 -0
  117. package/screenshots/Webkit/baseline/menu-drawer-de.png +0 -0
  118. package/screenshots/Webkit/baseline/menu-drawer-en.png +0 -0
  119. package/screenshots/Webkit/baseline/menu-plugins-de.png +0 -0
  120. package/screenshots/Webkit/baseline/menu-plugins-en.png +0 -0
  121. package/screenshots/Webkit/baseline/menu-plugins-triggered-de.png +0 -0
  122. package/screenshots/Webkit/baseline/menu-plugins-triggered-en.png +0 -0
  123. package/tsconfig.json +0 -23
  124. package/utils/testing.spec.ts +0 -210
  125. package/utils/testing.ts +0 -65
  126. package/web-dev-server.config.js +0 -24
  127. package/web-test-runner.config.js +0 -180
package/oscd-shell.ts DELETED
@@ -1,906 +0,0 @@
1
- import '@webcomponents/scoped-custom-element-registry';
2
-
3
- import { configureLocalization, localized, msg, str } from '@lit/localize';
4
- import { css, html, LitElement, nothing, TemplateResult } from 'lit';
5
- import { customElement, property, query, state } from 'lit/decorators.js';
6
- import { html as staticHtml, unsafeStatic } from 'lit/static-html.js';
7
- import { ScopedElementsMixin } from '@open-wc/scoped-elements/lit-element.js';
8
-
9
- import { OscdAppBar } from '@omicronenergy/oscd-ui/app-bar/oscd-app-bar.js';
10
- import { OscdDialog } from '@omicronenergy/oscd-ui/dialog/oscd-dialog.js';
11
- import { OscdDivider } from '@omicronenergy/oscd-ui/divider/oscd-divider.js';
12
- import { OscdFilledIconButton } from '@omicronenergy/oscd-ui/iconbutton/oscd-filled-icon-button.js';
13
- import { OscdFilledSelect } from '@omicronenergy/oscd-ui/select/oscd-filled-select.js';
14
- import { OscdFilledTextField } from '@omicronenergy/oscd-ui/textfield/oscd-filled-text-field.js';
15
- import { OscdIcon } from '@omicronenergy/oscd-ui/icon/oscd-icon.js';
16
- import { OscdList } from '@omicronenergy/oscd-ui/list/oscd-list.js';
17
- import { OscdListItem } from '@omicronenergy/oscd-ui/list/oscd-list-item.js';
18
- import { OscdMenu } from '@omicronenergy/oscd-ui/menu/oscd-menu.js';
19
- import { OscdMenuItem } from '@omicronenergy/oscd-ui/menu/oscd-menu-item.js';
20
- import { OscdNavigationDrawer } from '@omicronenergy/oscd-ui/navigation-drawer/oscd-navigation-drawer.js';
21
- import { OscdNavigationDrawerHeader } from '@omicronenergy/oscd-ui/navigation-drawer/oscd-navigation-drawer-header.js';
22
- import { OscdSecondaryTab } from '@omicronenergy/oscd-ui/tabs/oscd-secondary-tab.js';
23
- import { OscdSelectOption } from '@omicronenergy/oscd-ui/select/oscd-select-option.js';
24
- import { OscdTabs } from '@omicronenergy/oscd-ui/tabs/oscd-tabs.js';
25
- import { OscdTextButton } from '@omicronenergy/oscd-ui/button/oscd-text-button.js';
26
-
27
- import { allLocales, sourceLocale, targetLocales } from './locales.js';
28
-
29
- import {
30
- cyrb64,
31
- Edit,
32
- EditEvent,
33
- handleEdit,
34
- isComplex,
35
- isInsert,
36
- isRemove,
37
- isUpdate,
38
- OpenEvent,
39
- } from './foundation.js';
40
-
41
- const _customElementsDefine = window.customElements.define;
42
- window.customElements.define = (name, cl, conf) => {
43
- if (!customElements.get(name)) {
44
- _customElementsDefine.call(window.customElements, name, cl, conf);
45
- }
46
- };
47
-
48
- export type LogEntry = { undo: Edit; redo: Edit };
49
-
50
- export type Plugin = {
51
- name: string;
52
- translations?: Record<(typeof targetLocales)[number], string>;
53
- src: string;
54
- icon: string;
55
- requireDoc?: boolean;
56
- active?: boolean;
57
- };
58
- export type PluginSet = { menu: Plugin[]; editor: Plugin[] };
59
-
60
- const pluginTags = new Map<string, string>();
61
-
62
- /** @returns a valid customElement tagName containing the URI hash. */
63
- function pluginTag(uri: string): string {
64
- if (!pluginTags.has(uri)) {
65
- pluginTags.set(uri, `oscd-p${cyrb64(uri)}`);
66
- }
67
- return pluginTags.get(uri)!;
68
- }
69
-
70
- type Control = {
71
- icon: string;
72
- getName: () => string;
73
- isDisabled: () => boolean;
74
- action?: () => void | Promise<void>;
75
- };
76
-
77
- type RenderedPlugin = Control & { tagName: string };
78
-
79
- type LocaleTag = (typeof allLocales)[number];
80
-
81
- const { getLocale, setLocale } = configureLocalization({
82
- sourceLocale,
83
- targetLocales,
84
- loadLocale: locale =>
85
- import(new URL(`./locales/${locale}.js`, import.meta.url).href),
86
- });
87
-
88
- function describe({ undo, redo }: LogEntry) {
89
- let result = msg('Something unexpected happened!');
90
- if (isComplex(redo)) {
91
- result = msg(str`≥ ${redo.length} nodes changed`);
92
- }
93
- if (isInsert(redo)) {
94
- if (isInsert(undo)) {
95
- result = msg(str`${redo.node.nodeName} moved to ${redo.parent.nodeName}`);
96
- } else {
97
- result = msg(
98
- str`${redo.node.nodeName} inserted into ${redo.parent.nodeName}`,
99
- );
100
- }
101
- }
102
- if (isRemove(redo)) {
103
- result = msg(str`${redo.node.nodeName} removed`);
104
- }
105
- if (isUpdate(redo)) {
106
- result = msg(str`${redo.element.tagName} updated`);
107
- }
108
- return result;
109
- }
110
-
111
- function renderActionItem(
112
- control: Control,
113
- slot = 'actionItems',
114
- ): TemplateResult {
115
- return html`<oscd-filled-icon-button
116
- slot="${slot}"
117
- aria-label="${control.getName()}"
118
- ?disabled=${control.isDisabled()}
119
- @click=${control.action}
120
- ><oscd-icon>${control.icon}</oscd-icon></oscd-filled-icon-button
121
- >`;
122
- }
123
-
124
- function renderMenuItem(control: Control): TemplateResult {
125
- return html`
126
- <oscd-list-item
127
- type="button"
128
- .disabled=${control.isDisabled()}
129
- @click=${control.action}
130
- >
131
- <oscd-icon slot="start">${control.icon}</oscd-icon>
132
- <span>${control.getName()}</span>
133
- </oscd-list-item>
134
- `;
135
- }
136
-
137
- @customElement('oscd-shell')
138
- @localized()
139
- export class OpenSCD extends ScopedElementsMixin(LitElement) {
140
- static get scopedElements() {
141
- return {
142
- 'oscd-app-bar': OscdAppBar,
143
- 'oscd-dialog': OscdDialog,
144
- 'oscd-icon': OscdIcon,
145
- 'oscd-filled-text-field': OscdFilledTextField,
146
- 'oscd-filled-select': OscdFilledSelect,
147
- 'oscd-select-option': OscdSelectOption,
148
- 'oscd-filled-icon-button': OscdFilledIconButton,
149
- 'oscd-list': OscdList,
150
- 'oscd-list-item': OscdListItem,
151
- 'oscd-divider': OscdDivider,
152
- 'oscd-menu-item': OscdMenuItem,
153
- 'oscd-navigation-drawer': OscdNavigationDrawer,
154
- 'oscd-navigation-drawer-header': OscdNavigationDrawerHeader,
155
- 'oscd-secondary-tab': OscdSecondaryTab,
156
- 'oscd-tabs': OscdTabs,
157
- 'oscd-text-button': OscdTextButton,
158
- 'oscd-menu': OscdMenu,
159
- };
160
- }
161
-
162
- @state()
163
- /** The `XMLDocument` currently being edited */
164
- get doc(): XMLDocument {
165
- return this.docs[this.docName];
166
- }
167
-
168
- @state()
169
- history: LogEntry[] = [];
170
-
171
- @state()
172
- editCount: number = 0;
173
-
174
- @state()
175
- get last(): number {
176
- return this.editCount - 1;
177
- }
178
-
179
- @state()
180
- get canUndo(): boolean {
181
- return this.last >= 0;
182
- }
183
-
184
- @state()
185
- get canRedo(): boolean {
186
- return this.editCount < this.history.length;
187
- }
188
-
189
- /** The set of `XMLDocument`s currently loaded */
190
- @state()
191
- docs: Record<string, XMLDocument> = {};
192
-
193
- /** The name of the [[`doc`]] currently being edited */
194
- @property({ type: String, reflect: true }) docName = '';
195
-
196
- /** The file endings of editable files */
197
- @property({ type: Array, reflect: true }) editable = [
198
- 'cid',
199
- 'icd',
200
- 'iid',
201
- 'scd',
202
- 'sed',
203
- 'ssd',
204
- ];
205
-
206
- isEditable(docName: string): boolean {
207
- return !!this.editable.find(ext =>
208
- docName.toLowerCase().endsWith(`.${ext}`),
209
- );
210
- }
211
-
212
- @state()
213
- get editableDocs(): string[] {
214
- return Object.keys(this.docs).filter(name => this.isEditable(name));
215
- }
216
-
217
- #plugins: PluginSet = { menu: [], editor: [] };
218
-
219
- @property({ type: Object })
220
- get plugins(): PluginSet {
221
- return this.#plugins;
222
- }
223
-
224
- set plugins(plugins: Partial<PluginSet>) {
225
- Object.entries(plugins).forEach(([pluginType, kind]) =>
226
- kind.forEach((plugin, index) => {
227
- const tagName = pluginTag(plugin.src);
228
- if (this.#loadedPluginTagNames.includes(tagName)) {
229
- return;
230
- }
231
-
232
- if (this.registry!.get(tagName)) {
233
- this.addLoadedPlugin(
234
- tagName,
235
- pluginType as keyof PluginSet,
236
- plugin,
237
- index,
238
- );
239
- this.requestUpdate('loadedPlugins');
240
- return;
241
- }
242
- const url = new URL(plugin.src, window.location.href).toString();
243
- import(url).then(mod => {
244
- this.addLoadedPlugin(
245
- tagName,
246
- pluginType as keyof PluginSet,
247
- plugin,
248
- index,
249
- );
250
- // Because this is async, we need to check (again) if the element is already defined.
251
- if (!this.registry!.get(tagName)) {
252
- this.registry!.define(tagName, mod.default);
253
- }
254
- this.requestUpdate('loadedPlugins');
255
- });
256
- }),
257
- );
258
- this.#plugins = { menu: [], editor: [], ...plugins };
259
- }
260
-
261
- #loadedPlugins: PluginSet = {
262
- menu: new Array(this.plugins.menu.length).fill(null),
263
- editor: new Array(this.plugins.editor.length).fill(null),
264
- };
265
-
266
- #loadedPluginTagNames: string[] = [];
267
-
268
- @state()
269
- get loadedPlugins(): PluginSet {
270
- return this.#loadedPlugins;
271
- }
272
-
273
- addLoadedPlugin(
274
- tagName: string,
275
- kind: keyof PluginSet,
276
- plugin: Plugin,
277
- index: number,
278
- ) {
279
- this.#loadedPlugins[kind][index] = plugin;
280
- this.#loadedPluginTagNames.push(tagName);
281
- }
282
-
283
- handleOpenDoc({ detail: { docName, doc } }: OpenEvent) {
284
- this.docs[docName] = doc;
285
- if (this.isEditable(docName)) {
286
- this.docName = docName;
287
- }
288
- this.requestUpdate();
289
- }
290
-
291
- handleEditEvent(event: EditEvent) {
292
- const edit = event.detail;
293
- this.history.splice(this.editCount);
294
- this.history.push({ undo: handleEdit(edit), redo: edit });
295
- this.editCount += 1;
296
- }
297
-
298
- /** Undo the last `n` [[Edit]]s committed */
299
- undo(n = 1) {
300
- if (!this.canUndo || n < 1) {
301
- return;
302
- }
303
- handleEdit(this.history[this.last!].undo);
304
- this.editCount -= 1;
305
- if (n > 1) {
306
- this.undo(n - 1);
307
- }
308
- }
309
-
310
- /** Redo the last `n` [[Edit]]s that have been undone */
311
- redo(n = 1) {
312
- if (!this.canRedo || n < 1) {
313
- return;
314
- }
315
- handleEdit(this.history[this.editCount].redo);
316
- this.editCount += 1;
317
- if (n > 1) {
318
- this.redo(n - 1);
319
- }
320
- }
321
-
322
- @query('#log')
323
- logUI!: OscdDialog;
324
-
325
- @query('#editFile')
326
- editFileUI!: OscdDialog;
327
-
328
- @query('#menu')
329
- menuUI!: OscdNavigationDrawer;
330
-
331
- @query('#fileName')
332
- fileNameUI!: HTMLInputElement;
333
-
334
- @query('#fileExtension')
335
- fileExtensionUI!: HTMLInputElement;
336
-
337
- @query('#fileMenu')
338
- fileMenuUI!: OscdMenu;
339
-
340
- @query('#fileMenuButton')
341
- fileMenuButtonUI?: OscdFilledIconButton;
342
-
343
- @property({ type: String, reflect: true })
344
- get locale() {
345
- return getLocale() as LocaleTag;
346
- }
347
-
348
- set locale(tag: LocaleTag) {
349
- try {
350
- setLocale(tag);
351
- } catch {
352
- // don't change locale if tag is invalid
353
- }
354
- }
355
-
356
- @state()
357
- private editorIndex = 0;
358
-
359
- @state()
360
- get editor() {
361
- return this.editors[this.editorIndex]?.tagName ?? '';
362
- }
363
-
364
- private controls: Record<
365
- 'undo' | 'redo' | 'log' | 'menu',
366
- Required<Control>
367
- > = {
368
- undo: {
369
- icon: 'undo',
370
- getName: () => msg('Undo'),
371
- action: () => {
372
- this.undo();
373
- this.menuUI.opened = false;
374
- },
375
- isDisabled: () => !this.canUndo,
376
- },
377
- redo: {
378
- icon: 'redo',
379
- getName: () => msg('Redo'),
380
- action: () => {
381
- this.redo();
382
- this.menuUI.opened = false;
383
- },
384
- isDisabled: () => !this.canRedo,
385
- },
386
- log: {
387
- icon: 'history',
388
- getName: () => msg('Editing history'),
389
- action: () => {
390
- if (this.logUI.open) {
391
- this.logUI.close();
392
- } else {
393
- this.logUI.show();
394
- }
395
- // this.menuUI.opened = false;
396
- },
397
- isDisabled: () => false,
398
- },
399
- menu: {
400
- icon: 'menu',
401
- getName: () => msg('Menu'),
402
- action: async () => {
403
- this.menuUI.opened = !this.menuUI.opened;
404
- await this.menuUI.updateComplete;
405
- if (this.menuUI.opened) {
406
- this.menuUI.querySelector<OscdList>('oscd-list')!.focus();
407
- }
408
- },
409
- isDisabled: () => false,
410
- },
411
- };
412
-
413
- #actions = [this.controls.undo, this.controls.redo, this.controls.log];
414
-
415
- @state()
416
- get menu() {
417
- return (<Required<Control>[]>this.loadedPlugins.menu
418
- ?.map((plugin): RenderedPlugin | undefined =>
419
- plugin.active
420
- ? {
421
- icon: plugin.icon,
422
- getName: () =>
423
- plugin.translations?.[
424
- this.locale as (typeof targetLocales)[number]
425
- ] || plugin.name,
426
- isDisabled: () => (plugin.requireDoc && !this.docName) ?? false,
427
- tagName: pluginTag(plugin.src),
428
- action: () => {
429
- this.shadowRoot!.querySelector<
430
- HTMLElement & { run: () => Promise<void> }
431
- >(pluginTag(plugin.src))!.run?.();
432
- this.menuUI.opened = false;
433
- },
434
- }
435
- : undefined,
436
- )
437
- .filter(p => p !== undefined)).concat(this.#actions);
438
- }
439
-
440
- @state()
441
- get editors() {
442
- return <RenderedPlugin[]>this.loadedPlugins.editor
443
- ?.map((plugin): RenderedPlugin | undefined =>
444
- plugin.active
445
- ? {
446
- icon: plugin.icon,
447
- getName: () =>
448
- plugin.translations?.[
449
- this.locale as (typeof targetLocales)[number]
450
- ] || plugin.name,
451
- isDisabled: () => (plugin.requireDoc && !this.docName) ?? false,
452
- tagName: pluginTag(plugin.src),
453
- }
454
- : undefined,
455
- )
456
- .filter(p => p !== undefined);
457
- }
458
-
459
- private hotkeys: Partial<Record<string, () => void>> = {
460
- m: this.controls.menu.action,
461
- z: this.controls.undo.action,
462
- y: this.controls.redo.action,
463
- Z: this.controls.redo.action,
464
- l: this.controls.log.action,
465
- };
466
-
467
- private handleKeyPress(e: KeyboardEvent): void {
468
- if (!e.ctrlKey) {
469
- return;
470
- }
471
- if (!Object.prototype.hasOwnProperty.call(this.hotkeys, e.key)) {
472
- return;
473
- }
474
- this.hotkeys[e.key]!();
475
- e.preventDefault();
476
- }
477
-
478
- constructor() {
479
- super();
480
-
481
- document.addEventListener('keydown', event => this.handleKeyPress(event));
482
-
483
- this.addEventListener('oscd-open', event => this.handleOpenDoc(event));
484
- this.addEventListener('oscd-edit', event => this.handleEditEvent(event));
485
- }
486
-
487
- private renderLogEntry(entry: LogEntry) {
488
- return html` <abbr title="${describe(entry)}">
489
- <oscd-list-item ?activated=${this.history[this.last] === entry}>
490
- <span>${describe(entry)}</span>
491
- <oscd-icon slot="start">history</oscd-icon>
492
- </oscd-list-item></abbr
493
- >`;
494
- }
495
-
496
- private renderHistory(): TemplateResult[] | TemplateResult {
497
- if (this.history.length > 0) {
498
- return this.history.slice().reverse().map(this.renderLogEntry, this);
499
- }
500
- return html`<oscd-list-item>
501
- <span>${msg('Your editing history will be displayed here.')}</span>
502
- <oscd-icon slot="start">info</oscd-icon>
503
- </oscd-list-item>`;
504
- }
505
-
506
- updated(changedProps: Map<string, unknown>) {
507
- super.updated?.(changedProps);
508
-
509
- this.updateComplete.then(() => {
510
- // Ensure the active tab is set after tabs are rendered
511
- const oscdTabs = this.shadowRoot?.querySelector('oscd-tabs');
512
- if (oscdTabs && oscdTabs.activeTabIndex !== this.editorIndex) {
513
- oscdTabs.activeTabIndex = this.editorIndex;
514
- }
515
- });
516
- }
517
-
518
- render() {
519
- return html`<oscd-app-bar slot="appContent">
520
- ${renderActionItem(this.controls.menu, 'actionStart')}
521
- <div slot="title" id="title">
522
- ${this.editableDocs.length > 1
523
- ? html`<oscd-filled-icon-button
524
- id="fileMenuButton"
525
- @click=${() => this.fileMenuUI.show()}
526
- ><oscd-icon>arrow_drop_down</oscd-icon></oscd-filled-icon-button
527
- >`
528
- : nothing}
529
- ${this.docName}
530
- ${this.docName
531
- ? html`<oscd-filled-icon-button
532
- @click=${() => this.editFileUI.show()}
533
- ><oscd-icon>edit</oscd-icon></oscd-filled-icon-button
534
- >`
535
- : nothing}
536
- <oscd-menu
537
- fixed
538
- id="fileMenu"
539
- anchor="fileMenuButton"
540
- corner="BOTTOM_END"
541
- >
542
- ${this.editableDocs.map(
543
- (name, index) =>
544
- html`<oscd-menu-item
545
- @click=${() => {
546
- this.docName = name;
547
- }}
548
- ?disabled=${!this.isEditable(name)}
549
- ?selected=${this.docName === name}
550
- id=${index}
551
- >${name}</oscd-menu-item
552
- >`,
553
- )}
554
- </oscd-menu>
555
- </div>
556
-
557
- <div slot="actionEnd">
558
- ${this.#actions.map(op => renderActionItem(op))}
559
- </div>
560
-
561
- <oscd-tabs
562
- .activeTabIndex=${this.editorIndex}
563
- @change=${(event: Event) => {
564
- const tabs = event.currentTarget as OscdTabs;
565
- this.editorIndex = tabs.activeTabIndex;
566
- }}
567
- >
568
- ${this.editors.map(editor =>
569
- editor.isDisabled()
570
- ? nothing
571
- : html`<oscd-secondary-tab>
572
- <oscd-icon slot="icon">${editor.icon}</oscd-icon>
573
- ${editor.getName()}
574
- </oscd-secondary-tab>`,
575
- )}
576
- </oscd-tabs>
577
- </oscd-app-bar>
578
-
579
- <oscd-navigation-drawer hasheader type="modal" id="menu">
580
- <oscd-navigation-drawer-header>
581
- <span slot="headline">${msg('Menu')}</span>
582
- ${this.docName
583
- ? html`<span slot="supporting-text">${this.docName}</span>`
584
- : ''}
585
- </oscd-navigation-drawer-header>
586
- <oscd-list>
587
- <oscd-divider></oscd-divider>
588
- ${this.menu.map(renderMenuItem)}
589
- </oscd-list>
590
- </oscd-navigation-drawer>
591
-
592
- ${this.editor
593
- ? staticHtml`<${unsafeStatic(this.editor)} docName="${
594
- this.docName || nothing
595
- }" .doc=${this.doc} locale="${this.locale}" .docs=${
596
- this.docs
597
- } .editCount=${this.editCount}></${unsafeStatic(this.editor)}>`
598
- : nothing}
599
-
600
- <oscd-dialog
601
- id="editFile"
602
- @closed=${(event: CustomEvent) => {
603
- const dialog = event.target as OscdDialog;
604
- if (dialog.returnValue === 'remove') {
605
- delete this.docs[this.docName];
606
- this.docName = this.editableDocs[0] || '';
607
- }
608
- }}
609
- >
610
- <div slot="headline">
611
- <oscd-icon>description</oscd-icon>${this.docName}
612
- </div>
613
- <form slot="content" id="edit-file-form" method="dialog">
614
- <oscd-filled-text-field
615
- id="fileName"
616
- label="${msg('Filename')}"
617
- value="${this.docName.replace(/\.[^.]+$/, '')}"
618
- @input=${(event: Event) => {
619
- const input = event.target as HTMLInputElement;
620
- const { value } = input;
621
- const name = `${value}.${this.fileExtensionUI.value}`;
622
- if (name in this.docs && name !== this.docName) {
623
- input.setCustomValidity('File already exists!');
624
- } else {
625
- input.setCustomValidity('');
626
- }
627
- input.reportValidity();
628
- }}
629
- ></oscd-filled-text-field>
630
- <oscd-filled-select
631
- label="${msg('Extension')}"
632
- fixedMenuPosition
633
- id="fileExtension"
634
- @selected=${() => this.fileNameUI.reportValidity()}
635
- >
636
- ${this.editable.map(
637
- ext =>
638
- html`<oscd-select-option
639
- ?selected=${this.docName.endsWith(`.${ext}`)}
640
- value="${ext}"
641
- >${ext}</oscd-select-option
642
- >`,
643
- )}
644
- </oscd-filled-select>
645
- </form>
646
- <div slot="actions">
647
- <oscd-text-button
648
- form="edit-file-form"
649
- class="edit-dialog-remove-button"
650
- value="remove"
651
- >
652
- <oscd-icon slot="icon">delete</oscd-icon>
653
- ${msg('Close file')}
654
- </oscd-text-button>
655
- <oscd-text-button form="edit-file-form" value="close">
656
- ${msg('Cancel')}
657
- </oscd-text-button>
658
- <oscd-text-button
659
- @click=${() => {
660
- const valid = this.fileNameUI.checkValidity();
661
- if (!valid) {
662
- this.fileNameUI.reportValidity();
663
- return;
664
- }
665
- const newDocName = `${this.fileNameUI.value}.${this.fileExtensionUI.value}`;
666
- this.docs[newDocName] = this.doc;
667
- delete this.docs[this.docName];
668
- this.docName = newDocName;
669
- this.editFileUI.close();
670
- }}
671
- trailing-icon
672
- >
673
- <oscd-icon slot="icon">edit</oscd-icon>
674
- ${msg('Rename')}
675
- </oscd-text-button>
676
- </div>
677
- </oscd-dialog>
678
- <oscd-dialog id="log" heading="">
679
- <div slot="headline">
680
- <oscd-icon>history</oscd-icon>${this.controls.log.getName()}
681
- </div>
682
- <form slot="content" id="log-dialog-form" method="dialog">
683
- <oscd-list>${this.renderHistory()}</oscd-list>
684
- </form>
685
- <div slot="actions">
686
- <oscd-text-button
687
- ?disabled=${!this.canUndo}
688
- @click=${this.undo}
689
- value="undo"
690
- >${msg('Undo')}<oscd-icon slot="icon"
691
- >undo</oscd-icon
692
- ></oscd-text-button
693
- >
694
- <oscd-text-button
695
- ?disabled=${!this.canRedo}
696
- @click=${this.redo}
697
- value="redo"
698
- >${msg('Redo')}<oscd-icon slot="icon"
699
- >redo</oscd-icon
700
- ></oscd-text-button
701
- >
702
- <oscd-text-button form="log-dialog-form" value="close"
703
- >${msg('Close')}</oscd-text-button
704
- >
705
- </div>
706
- </oscd-dialog>
707
- <aside>
708
- ${this.loadedPlugins.menu.map(
709
- plugin =>
710
- staticHtml`<${unsafeStatic(pluginTag(plugin.src))} docName="${
711
- this.docName
712
- }" .doc=${this.doc} locale="${this.locale}" .docs=${
713
- this.docs
714
- } .editCount=${this.editCount}></${unsafeStatic(
715
- pluginTag(plugin.src),
716
- )}>`,
717
- )}
718
- </aside>`;
719
- }
720
-
721
- firstUpdated() {
722
- const background = getComputedStyle(this.menuUI).getPropertyValue(
723
- '--oscd-base2',
724
- );
725
- document.body.style.background = background;
726
- }
727
-
728
- static styles = css`
729
- .fileext {
730
- opacity: 0.81;
731
- }
732
-
733
- .filename {
734
- caret-color: var(--oscd-secondary);
735
- }
736
-
737
- .filename:focus {
738
- outline: none;
739
- }
740
-
741
- aside {
742
- position: absolute;
743
- top: 0;
744
- left: 0;
745
- width: 0;
746
- height: 0;
747
- overflow: hidden;
748
- margin: 0;
749
- padding: 0;
750
- }
751
-
752
- abbr {
753
- text-decoration: none;
754
- }
755
-
756
- @media (prefers-color-scheme: light) {
757
- * {
758
- --oscd-primary: var(--oscd-theme-primary, #2aa198);
759
- --oscd-secondary: var(--oscd-theme-secondary, #6c71c4);
760
- --oscd-base03: var(--oscd-theme-base03, #002b36);
761
- --oscd-base02: var(--oscd-theme-base02, #073642);
762
- --oscd-base01: var(--oscd-theme-base01, #586e75);
763
- --oscd-base00: var(--oscd-theme-base00, #657b83);
764
- --oscd-base0: var(--oscd-theme-base0, #839496);
765
- --oscd-base1: var(--oscd-theme-base1, #93a1a1);
766
- --oscd-base2: var(--oscd-theme-base2, #eee8d5);
767
- --oscd-base3: var(--oscd-theme-base3, #fdf6e3);
768
-
769
- --mdc-theme-text-disabled-on-light: rgba(255, 255, 255, 0.38);
770
- }
771
- }
772
-
773
- @media (prefers-color-scheme: dark) {
774
- * {
775
- --oscd-primary: var(--oscd-theme-secondary, #6c71c4);
776
- --oscd-secondary: var(--oscd-theme-primary, #2aa198);
777
- --oscd-base03: var(--oscd-theme-base3, #fdf6e3);
778
- --oscd-base02: var(--oscd-theme-base2, #eee8d5);
779
- --oscd-base01: var(--oscd-theme-base1, #93a1a1);
780
- --oscd-base00: var(--oscd-theme-base0, #839496);
781
- --oscd-base0: var(--oscd-theme-base00, #657b83);
782
- --oscd-base1: var(--oscd-theme-base01, #586e75);
783
- --oscd-base2: var(--oscd-theme-base02, #073642);
784
- --oscd-base3: var(--oscd-theme-base03, #002b36);
785
- }
786
- }
787
-
788
- * {
789
- --oscd-error: var(--oscd-theme-error, #dc322f);
790
-
791
- --oscd-text-font: var(--oscd-theme-text-font, 'Roboto');
792
- --oscd-icon-font: var(--oscd-theme-icon-font, 'Material Icons');
793
- --oscd-text-font-mono: var(--oscd-theme-text-font-mono, 'Roboto Mono');
794
-
795
- --oscd-warning: var(--oscd-theme-warning, #b58900);
796
- --md-sys-color-primary: var(--oscd-primary);
797
- --md-sys-color-on-primary: var(--oscd-base3);
798
-
799
- --md-sys-color-secondary: var(--oscd-secondary);
800
- --md-sys-color-on-secondary: var(--oscd-base3);
801
- --md-sys-color-secondary-container: var(--oscd-base2);
802
-
803
- --md-sys-color-surface: var(--oscd-base3);
804
- --md-sys-color-on-surface: var(--oscd-base00);
805
- --md-sys-color-surface-variant: var(--oscd-base3);
806
- --md-sys-color-on-surface-variant: var(--oscd-base00);
807
- --md-sys-color-surface-bright: var(--oscd-base2);
808
- --md-sys-color-surface-container: var(--oscd-base3);
809
- --md-sys-color-surface-container-high: var(--oscd-base3);
810
- --md-sys-color-surface-container-highest: var(--oscd-base3);
811
-
812
- --md-sys-color-outline-variant: var(--oscd-primary);
813
- --md-sys-color-scrim: #000000;
814
- --md-sys-color-error: var(--oscd-error);
815
- --md-sys-color-on-error: var(--oscd-base3);
816
-
817
- /* --md-menu-item-selected-label-text-color: var(--oscd-base01); */
818
- --md-icon-button-disabled-icon-color: var(--oscd-base3);
819
-
820
- /* MDC Theme Colors
821
- * Needed for supporting any pluggins still using the depricated MWC Components
822
- */
823
- --mdc-theme-primary: var(--oscd-primary);
824
- --mdc-theme-secondary: var(--oscd-secondary);
825
- --mdc-theme-background: var(--oscd-base3);
826
- --mdc-theme-surface: var(--oscd-base3);
827
- --mdc-theme-on-primary: var(--oscd-base2);
828
- --mdc-theme-on-secondary: var(--oscd-base02);
829
- --mdc-theme-on-background: var(--oscd-base00);
830
- --mdc-theme-on-surface: var(--oscd-base00);
831
- --mdc-theme-text-primary-on-background: var(--oscd-base01);
832
- --mdc-theme-text-secondary-on-background: var(--oscd-base00);
833
- --mdc-theme-text-icon-on-background: var(--oscd-base00);
834
- --mdc-theme-error: var(--oscd-error);
835
-
836
- --mdc-button-disabled-ink-color: var(--oscd-base1);
837
-
838
- --mdc-drawer-heading-ink-color: var(--oscd-base00);
839
-
840
- --mdc-dialog-heading-ink-color: var(--oscd-base00);
841
-
842
- --mdc-text-field-fill-color: var(--oscd-base2);
843
- --mdc-text-field-ink-color: var(--oscd-base02);
844
- --mdc-text-field-label-ink-color: var(--oscd-base01);
845
- --mdc-text-field-idle-line-color: var(--oscd-base00);
846
- --mdc-text-field-hover-line-color: var(--oscd-base02);
847
-
848
- --mdc-select-fill-color: var(--oscd-base2);
849
- --mdc-select-ink-color: var(--oscd-base02);
850
- --mdc-select-label-ink-color: var(--oscd-base01);
851
- --mdc-select-idle-line-color: var(--oscd-base00);
852
- --mdc-select-hover-line-color: var(--oscd-base02);
853
- --mdc-select-dropdown-icon-color: var(--oscd-base01);
854
-
855
- --mdc-typography-font-family: var(--oscd-text-font);
856
- --mdc-icon-font: var(--oscd-icon-font);
857
- }
858
-
859
- oscd-navigation-drawer-header {
860
- --md-list-item-supporting-text-color: var(--md-sys-color-on-surface);
861
- }
862
- oscd-app-bar * {
863
- --md-filled-icon-button-disabled-container-opacity: 0;
864
- --md-filled-icon-button-disabled-icon-color: var(
865
- --md-sys-color-on-primary
866
- );
867
- }
868
-
869
- oscd-tabs {
870
- display: flex;
871
- flex-grow: 1;
872
- --md-secondary-tab-container-color: var(--oscd-primary);
873
- }
874
- oscd-secondary-tab {
875
- --md-sys-color-on-surface: var(--md-sys-color-on-primary);
876
- --md-sys-color-on-surface-variant: var(--md-sys-color-on-primary);
877
- --md-secondary-tab-active-indicator-color: var(--oscd-base2);
878
- }
879
-
880
- .edit-dialog-remove-button {
881
- --md-text-button-icon-color: var(--oscd-error);
882
- --md-text-button-label-text-color: var(--oscd-error);
883
-
884
- --md-text-button-focus-label-text-color: var(--oscd-error);
885
- --md-text-button-focus-icon-color: var(--oscd-error);
886
-
887
- --md-text-button-hover-label-text-color: var(--oscd-error);
888
- --md-text-button-hover-state-layer-color: var(--oscd-error);
889
- --md-text-button-hover-icon-color: var(--oscd-error);
890
-
891
- --md-text-button-pressed-label-text-color: var(--oscd-error);
892
- --md-text-button-pressed-state-layer-color: var(--oscd-error);
893
- --md-text-button-pressed-icon-color: var(--oscd-error);
894
- }
895
-
896
- #title {
897
- position: relative;
898
- }
899
- `;
900
- }
901
-
902
- declare global {
903
- interface HTMLElementTagNameMap {
904
- 'oscd-shell': OpenSCD;
905
- }
906
- }