@kispace-io/core 0.7.0
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.
- package/dist/api/base-classes.d.ts +7 -0
- package/dist/api/base-classes.d.ts.map +1 -0
- package/dist/api/constants.d.ts +2 -0
- package/dist/api/constants.d.ts.map +1 -0
- package/dist/api/index.d.ts +6 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +80 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/services.d.ts +27 -0
- package/dist/api/services.d.ts.map +1 -0
- package/dist/api/types.d.ts +11 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/commands/files.d.ts +2 -0
- package/dist/commands/files.d.ts.map +1 -0
- package/dist/commands/global.d.ts +1 -0
- package/dist/commands/global.d.ts.map +1 -0
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/version-info.d.ts +2 -0
- package/dist/commands/version-info.d.ts.map +1 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/k-app-selector.d.ts +17 -0
- package/dist/components/k-app-selector.d.ts.map +1 -0
- package/dist/components/k-app-switcher.d.ts +13 -0
- package/dist/components/k-app-switcher.d.ts.map +1 -0
- package/dist/components/k-command.d.ts +31 -0
- package/dist/components/k-command.d.ts.map +1 -0
- package/dist/components/k-extensions.d.ts +32 -0
- package/dist/components/k-extensions.d.ts.map +1 -0
- package/dist/components/k-fastviews.d.ts +34 -0
- package/dist/components/k-fastviews.d.ts.map +1 -0
- package/dist/components/k-filebrowser.d.ts +40 -0
- package/dist/components/k-filebrowser.d.ts.map +1 -0
- package/dist/components/k-language-selector.d.ts +12 -0
- package/dist/components/k-language-selector.d.ts.map +1 -0
- package/dist/components/k-log-terminal.d.ts +36 -0
- package/dist/components/k-log-terminal.d.ts.map +1 -0
- package/dist/components/k-part-name.d.ts +12 -0
- package/dist/components/k-part-name.d.ts.map +1 -0
- package/dist/components/k-tasks.d.ts +13 -0
- package/dist/components/k-tasks.d.ts.map +1 -0
- package/dist/components/k-workspace-name.d.ts +14 -0
- package/dist/components/k-workspace-name.d.ts.map +1 -0
- package/dist/contributions/default-ui-contributions.d.ts +2 -0
- package/dist/contributions/default-ui-contributions.d.ts.map +1 -0
- package/dist/contributions/index.d.ts +1 -0
- package/dist/contributions/index.d.ts.map +1 -0
- package/dist/contributions/marketplace-catalog-contributions.d.ts +2 -0
- package/dist/contributions/marketplace-catalog-contributions.d.ts.map +1 -0
- package/dist/core/app-host-config.d.ts +7 -0
- package/dist/core/app-host-config.d.ts.map +1 -0
- package/dist/core/apploader.d.ts +214 -0
- package/dist/core/apploader.d.ts.map +1 -0
- package/dist/core/appstate.d.ts +12 -0
- package/dist/core/appstate.d.ts.map +1 -0
- package/dist/core/commandregistry.d.ts +79 -0
- package/dist/core/commandregistry.d.ts.map +1 -0
- package/dist/core/config.d.ts +15 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/constants.d.ts +21 -0
- package/dist/core/constants.d.ts.map +1 -0
- package/dist/core/contributionregistry.d.ts +49 -0
- package/dist/core/contributionregistry.d.ts.map +1 -0
- package/dist/core/di.d.ts +18 -0
- package/dist/core/di.d.ts.map +1 -0
- package/dist/core/dialogservice.d.ts +33 -0
- package/dist/core/dialogservice.d.ts.map +1 -0
- package/dist/core/editorregistry.d.ts +73 -0
- package/dist/core/editorregistry.d.ts.map +1 -0
- package/dist/core/esmsh-service.d.ts +40 -0
- package/dist/core/esmsh-service.d.ts.map +1 -0
- package/dist/core/events.d.ts +7 -0
- package/dist/core/events.d.ts.map +1 -0
- package/dist/core/events.js +63 -0
- package/dist/core/events.js.map +1 -0
- package/dist/core/extensionregistry.d.ts +98 -0
- package/dist/core/extensionregistry.d.ts.map +1 -0
- package/dist/core/filesys.d.ts +139 -0
- package/dist/core/filesys.d.ts.map +1 -0
- package/dist/core/i18n.d.ts +50 -0
- package/dist/core/i18n.d.ts.map +1 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/k-utils.d.ts +2 -0
- package/dist/core/k-utils.d.ts.map +1 -0
- package/dist/core/keybindings.d.ts +67 -0
- package/dist/core/keybindings.d.ts.map +1 -0
- package/dist/core/logger.d.ts +44 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/marketplaceregistry.d.ts +25 -0
- package/dist/core/marketplaceregistry.d.ts.map +1 -0
- package/dist/core/packageinfoservice.d.ts +16 -0
- package/dist/core/packageinfoservice.d.ts.map +1 -0
- package/dist/core/persistenceservice.d.ts +6 -0
- package/dist/core/persistenceservice.d.ts.map +1 -0
- package/dist/core/settingsservice.d.ts +19 -0
- package/dist/core/settingsservice.d.ts.map +1 -0
- package/dist/core/signals.d.ts +3 -0
- package/dist/core/signals.d.ts.map +1 -0
- package/dist/core/taskservice.d.ts +20 -0
- package/dist/core/taskservice.d.ts.map +1 -0
- package/dist/core/toast.d.ts +4 -0
- package/dist/core/toast.d.ts.map +1 -0
- package/dist/core/tree-utils.d.ts +16 -0
- package/dist/core/tree-utils.d.ts.map +1 -0
- package/dist/dialogs/confirm-dialog.d.ts +14 -0
- package/dist/dialogs/confirm-dialog.d.ts.map +1 -0
- package/dist/dialogs/index.d.ts +5 -0
- package/dist/dialogs/index.d.ts.map +1 -0
- package/dist/dialogs/info-dialog.d.ts +13 -0
- package/dist/dialogs/info-dialog.d.ts.map +1 -0
- package/dist/dialogs/navigable-info-dialog.d.ts +33 -0
- package/dist/dialogs/navigable-info-dialog.d.ts.map +1 -0
- package/dist/dialogs/prompt-dialog.d.ts +21 -0
- package/dist/dialogs/prompt-dialog.d.ts.map +1 -0
- package/dist/externals/lit.d.ts +20 -0
- package/dist/externals/lit.d.ts.map +1 -0
- package/dist/externals/lit.js +15 -0
- package/dist/externals/lit.js.map +1 -0
- package/dist/externals/third-party.d.ts +7 -0
- package/dist/externals/third-party.d.ts.map +1 -0
- package/dist/externals/third-party.js +2 -0
- package/dist/externals/third-party.js.map +1 -0
- package/dist/externals/webawesome.d.ts +1 -0
- package/dist/externals/webawesome.d.ts.map +1 -0
- package/dist/externals/webawesome.js +52 -0
- package/dist/externals/webawesome.js.map +1 -0
- package/dist/i18n/extensions.json.d.ts +42 -0
- package/dist/i18n/fastviews.json.d.ts +13 -0
- package/dist/i18n/filebrowser.json.d.ts +35 -0
- package/dist/i18n/index.d.ts +2 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/logterminal.json.d.ts +45 -0
- package/dist/i18n/partname.json.d.ts +15 -0
- package/dist/i18n/tasks.json.d.ts +15 -0
- package/dist/i18n/workspace.json.d.ts +15 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +80 -0
- package/dist/index.js.map +1 -0
- package/dist/k-icon-BZC7dQV0.js +492 -0
- package/dist/k-icon-BZC7dQV0.js.map +1 -0
- package/dist/k-nocontent-Bh_yToGh.js +48 -0
- package/dist/k-nocontent-Bh_yToGh.js.map +1 -0
- package/dist/k-resizable-grid-Ch3iWZaL.js +3157 -0
- package/dist/k-resizable-grid-Ch3iWZaL.js.map +1 -0
- package/dist/k-standard-layout-CQ1VZoxa.js +5011 -0
- package/dist/k-standard-layout-CQ1VZoxa.js.map +1 -0
- package/dist/layouts/k-standard-layout.d.ts +16 -0
- package/dist/layouts/k-standard-layout.d.ts.map +1 -0
- package/dist/parts/index.d.ts +1 -0
- package/dist/parts/index.d.ts.map +1 -0
- package/dist/parts/index.js +53 -0
- package/dist/parts/index.js.map +1 -0
- package/dist/parts/k-app.d.ts +11 -0
- package/dist/parts/k-app.d.ts.map +1 -0
- package/dist/parts/k-container.d.ts +4 -0
- package/dist/parts/k-container.d.ts.map +1 -0
- package/dist/parts/k-contextmenu.d.ts +38 -0
- package/dist/parts/k-contextmenu.d.ts.map +1 -0
- package/dist/parts/k-dialog-content.d.ts +9 -0
- package/dist/parts/k-dialog-content.d.ts.map +1 -0
- package/dist/parts/k-element.d.ts +36 -0
- package/dist/parts/k-element.d.ts.map +1 -0
- package/dist/parts/k-part.d.ts +96 -0
- package/dist/parts/k-part.d.ts.map +1 -0
- package/dist/parts/k-resizable-grid.d.ts +31 -0
- package/dist/parts/k-resizable-grid.d.ts.map +1 -0
- package/dist/parts/k-tabs.d.ts +74 -0
- package/dist/parts/k-tabs.d.ts.map +1 -0
- package/dist/parts/k-toolbar.d.ts +21 -0
- package/dist/parts/k-toolbar.d.ts.map +1 -0
- package/dist/widgets/index.d.ts +1 -0
- package/dist/widgets/index.d.ts.map +1 -0
- package/dist/widgets/index.js +3 -0
- package/dist/widgets/index.js.map +1 -0
- package/dist/widgets/k-icon.d.ts +10 -0
- package/dist/widgets/k-icon.d.ts.map +1 -0
- package/dist/widgets/k-nocontent.d.ts +13 -0
- package/dist/widgets/k-nocontent.d.ts.map +1 -0
- package/dist/widgets/k-widget.d.ts +25 -0
- package/dist/widgets/k-widget.d.ts.map +1 -0
- package/package.json +81 -0
- package/src/api/base-classes.ts +10 -0
- package/src/api/constants.ts +3 -0
- package/src/api/index.ts +31 -0
- package/src/api/services.ts +52 -0
- package/src/api/types.ts +46 -0
- package/src/commands/files.ts +829 -0
- package/src/commands/global.ts +225 -0
- package/src/commands/index.ts +4 -0
- package/src/commands/version-info.ts +214 -0
- package/src/components/index.ts +10 -0
- package/src/components/k-app-selector.ts +233 -0
- package/src/components/k-app-switcher.ts +126 -0
- package/src/components/k-command.ts +236 -0
- package/src/components/k-extensions.ts +615 -0
- package/src/components/k-fastviews.ts +314 -0
- package/src/components/k-filebrowser.ts +442 -0
- package/src/components/k-language-selector.ts +166 -0
- package/src/components/k-log-terminal.ts +337 -0
- package/src/components/k-part-name.ts +54 -0
- package/src/components/k-tasks.ts +267 -0
- package/src/components/k-workspace-name.ts +56 -0
- package/src/contributions/default-ui-contributions.ts +51 -0
- package/src/contributions/index.ts +3 -0
- package/src/contributions/marketplace-catalog-contributions.ts +6 -0
- package/src/core/app-host-config.ts +23 -0
- package/src/core/apploader.ts +630 -0
- package/src/core/appstate.ts +15 -0
- package/src/core/commandregistry.ts +210 -0
- package/src/core/config.ts +29 -0
- package/src/core/constants.ts +27 -0
- package/src/core/contributionregistry.ts +77 -0
- package/src/core/di.ts +54 -0
- package/src/core/dialogservice.ts +266 -0
- package/src/core/editorregistry.ts +303 -0
- package/src/core/esmsh-service.ts +404 -0
- package/src/core/events.ts +68 -0
- package/src/core/extensionregistry.ts +399 -0
- package/src/core/filesys.ts +618 -0
- package/src/core/i18n.ts +221 -0
- package/src/core/index.ts +51 -0
- package/src/core/k-utils.ts +11 -0
- package/src/core/keybindings.ts +274 -0
- package/src/core/logger.ts +187 -0
- package/src/core/marketplaceregistry.ts +197 -0
- package/src/core/packageinfoservice.ts +56 -0
- package/src/core/persistenceservice.ts +15 -0
- package/src/core/settingsservice.ts +70 -0
- package/src/core/signals.ts +18 -0
- package/src/core/taskservice.ts +72 -0
- package/src/core/toast.ts +11 -0
- package/src/core/tree-utils.ts +24 -0
- package/src/dialogs/confirm-dialog.ts +72 -0
- package/src/dialogs/index.ts +4 -0
- package/src/dialogs/info-dialog.ts +67 -0
- package/src/dialogs/navigable-info-dialog.ts +256 -0
- package/src/dialogs/prompt-dialog.ts +123 -0
- package/src/externals/lit.ts +26 -0
- package/src/externals/third-party.ts +9 -0
- package/src/externals/webawesome.ts +54 -0
- package/src/i18n/extensions.json +39 -0
- package/src/i18n/fastviews.json +10 -0
- package/src/i18n/filebrowser.json +33 -0
- package/src/i18n/index.ts +25 -0
- package/src/i18n/logterminal.json +42 -0
- package/src/i18n/partname.json +12 -0
- package/src/i18n/tasks.json +12 -0
- package/src/i18n/workspace.json +12 -0
- package/src/icons/icons.txt +3 -0
- package/src/icons/js.svg +6 -0
- package/src/icons/jupyter.svg +18 -0
- package/src/icons/python.svg +15 -0
- package/src/index.ts +3 -0
- package/src/layouts/k-standard-layout.ts +174 -0
- package/src/parts/index.ts +6 -0
- package/src/parts/k-app.ts +29 -0
- package/src/parts/k-container.ts +4 -0
- package/src/parts/k-contextmenu.ts +245 -0
- package/src/parts/k-dialog-content.ts +31 -0
- package/src/parts/k-element.ts +100 -0
- package/src/parts/k-part.ts +158 -0
- package/src/parts/k-resizable-grid.ts +366 -0
- package/src/parts/k-tabs.ts +574 -0
- package/src/parts/k-toolbar.ts +158 -0
- package/src/vite-env.d.ts +2 -0
- package/src/widgets/index.ts +2 -0
- package/src/widgets/k-icon.ts +39 -0
- package/src/widgets/k-nocontent.ts +40 -0
- package/src/widgets/k-widget.ts +90 -0
|
@@ -0,0 +1,3157 @@
|
|
|
1
|
+
import { css, html, render, nothing } from "lit";
|
|
2
|
+
import { property, state, customElement } from "lit/decorators.js";
|
|
3
|
+
import { styleMap } from "lit/directives/style-map.js";
|
|
4
|
+
import { k as rootContext, e as activeTasksSignal, h as createLogger, i as defaultLogger, t as toastError, u as uiContext, l as toastInfo, K as KWidget, T as TOPIC_CONTRIBUTEIONS_CHANGED, g as contributionRegistry, p as partDirtySignal, c as activePartSignal } from "./k-icon-BZC7dQV0.js";
|
|
5
|
+
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
|
6
|
+
import { publish, subscribe } from "./core/events.js";
|
|
7
|
+
import { createRef, ref } from "lit/directives/ref.js";
|
|
8
|
+
import { when } from "lit/directives/when.js";
|
|
9
|
+
import { repeat } from "lit/directives/repeat.js";
|
|
10
|
+
import { marked } from "marked";
|
|
11
|
+
import { set, get } from "idb-keyval";
|
|
12
|
+
const TOOLBAR_MAIN = "app-toolbars-main";
|
|
13
|
+
const TOOLBAR_MAIN_RIGHT = "app-toolbars-main-right";
|
|
14
|
+
const TOOLBAR_MAIN_CENTER = "app-toolbars-main-center";
|
|
15
|
+
const TOOLBAR_BOTTOM = "app-toolbars-bottom";
|
|
16
|
+
const TOOLBAR_BOTTOM_CENTER = "app-toolbars-bottom-center";
|
|
17
|
+
const TOOLBAR_BOTTOM_END = "app-toolbars-bottom-end";
|
|
18
|
+
const EDITOR_AREA_MAIN = "editor-area-main";
|
|
19
|
+
const SIDEBAR_MAIN = "sidebar-main";
|
|
20
|
+
const SIDEBAR_MAIN_BOTTOM = "sidebar-main-bottom";
|
|
21
|
+
const SIDEBAR_AUXILIARY = "sidebar-auxiliary";
|
|
22
|
+
const PANEL_BOTTOM = "panel-bottom";
|
|
23
|
+
const COMMAND_SAVE = "command-save";
|
|
24
|
+
const HIDE_DOT_RESOURCE = false;
|
|
25
|
+
var MouseButton = /* @__PURE__ */ ((MouseButton2) => {
|
|
26
|
+
MouseButton2[MouseButton2["LEFT"] = 0] = "LEFT";
|
|
27
|
+
MouseButton2[MouseButton2["MIDDLE"] = 1] = "MIDDLE";
|
|
28
|
+
MouseButton2[MouseButton2["RIGHT"] = 2] = "RIGHT";
|
|
29
|
+
MouseButton2[MouseButton2["BACK"] = 3] = "BACK";
|
|
30
|
+
MouseButton2[MouseButton2["FORWARD"] = 4] = "FORWARD";
|
|
31
|
+
return MouseButton2;
|
|
32
|
+
})(MouseButton || {});
|
|
33
|
+
const constants = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
34
|
+
__proto__: null,
|
|
35
|
+
COMMAND_SAVE,
|
|
36
|
+
EDITOR_AREA_MAIN,
|
|
37
|
+
HIDE_DOT_RESOURCE,
|
|
38
|
+
MouseButton,
|
|
39
|
+
PANEL_BOTTOM,
|
|
40
|
+
SIDEBAR_AUXILIARY,
|
|
41
|
+
SIDEBAR_MAIN,
|
|
42
|
+
SIDEBAR_MAIN_BOTTOM,
|
|
43
|
+
TOOLBAR_BOTTOM,
|
|
44
|
+
TOOLBAR_BOTTOM_CENTER,
|
|
45
|
+
TOOLBAR_BOTTOM_END,
|
|
46
|
+
TOOLBAR_MAIN,
|
|
47
|
+
TOOLBAR_MAIN_CENTER,
|
|
48
|
+
TOOLBAR_MAIN_RIGHT
|
|
49
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
50
|
+
class PersistenceService {
|
|
51
|
+
async persistObject(key, value) {
|
|
52
|
+
return set(key, value);
|
|
53
|
+
}
|
|
54
|
+
async getObject(key) {
|
|
55
|
+
return get(key);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const persistenceService = new PersistenceService();
|
|
59
|
+
rootContext.put("persistenceService", persistenceService);
|
|
60
|
+
const SETTINGS_FILE_PATH = ".geospace/settings.json";
|
|
61
|
+
const DIALOG_SETTINGS_KEY = "dialogSettings";
|
|
62
|
+
const TOPIC_SETTINGS_CHANGED = "events/settings/changed";
|
|
63
|
+
class SettingsService {
|
|
64
|
+
async checkSettings() {
|
|
65
|
+
if (!this.appSettings) {
|
|
66
|
+
this.appSettings = await persistenceService.getObject(SETTINGS_FILE_PATH);
|
|
67
|
+
if (!this.appSettings) {
|
|
68
|
+
this.appSettings = {};
|
|
69
|
+
await persistenceService.persistObject(SETTINGS_FILE_PATH, this.appSettings);
|
|
70
|
+
}
|
|
71
|
+
publish(TOPIC_SETTINGS_CHANGED, this.appSettings);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async get(key) {
|
|
75
|
+
await this.checkSettings();
|
|
76
|
+
return this.appSettings[key];
|
|
77
|
+
}
|
|
78
|
+
async set(key, value) {
|
|
79
|
+
await this.checkSettings();
|
|
80
|
+
this.appSettings[key] = value;
|
|
81
|
+
await persistenceService.persistObject(SETTINGS_FILE_PATH, this.appSettings);
|
|
82
|
+
publish(TOPIC_SETTINGS_CHANGED, this.appSettings);
|
|
83
|
+
}
|
|
84
|
+
async getAll() {
|
|
85
|
+
await this.checkSettings();
|
|
86
|
+
return this.appSettings;
|
|
87
|
+
}
|
|
88
|
+
async setAll(settings) {
|
|
89
|
+
this.appSettings = settings;
|
|
90
|
+
await persistenceService.persistObject(SETTINGS_FILE_PATH, this.appSettings);
|
|
91
|
+
publish(TOPIC_SETTINGS_CHANGED, this.appSettings);
|
|
92
|
+
}
|
|
93
|
+
async getDialogSetting(key) {
|
|
94
|
+
await this.checkSettings();
|
|
95
|
+
const dialogSettings = this.appSettings[DIALOG_SETTINGS_KEY] || {};
|
|
96
|
+
return dialogSettings[key];
|
|
97
|
+
}
|
|
98
|
+
async setDialogSetting(key, value) {
|
|
99
|
+
await this.checkSettings();
|
|
100
|
+
const dialogSettings = this.appSettings[DIALOG_SETTINGS_KEY] || {};
|
|
101
|
+
dialogSettings[key] = value;
|
|
102
|
+
this.appSettings[DIALOG_SETTINGS_KEY] = dialogSettings;
|
|
103
|
+
await persistenceService.persistObject(SETTINGS_FILE_PATH, this.appSettings);
|
|
104
|
+
publish(TOPIC_SETTINGS_CHANGED, this.appSettings);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const appSettings = new SettingsService();
|
|
108
|
+
rootContext.put("appSettings", appSettings);
|
|
109
|
+
class TaskService {
|
|
110
|
+
constructor() {
|
|
111
|
+
this.tasks = [];
|
|
112
|
+
this.updateCounter = 0;
|
|
113
|
+
}
|
|
114
|
+
notifyUpdate() {
|
|
115
|
+
this.updateCounter++;
|
|
116
|
+
activeTasksSignal.set(this.updateCounter);
|
|
117
|
+
}
|
|
118
|
+
run(name, task) {
|
|
119
|
+
const progressMonitor = this.createProgressMonitor(name);
|
|
120
|
+
try {
|
|
121
|
+
this.tasks.push(progressMonitor);
|
|
122
|
+
this.notifyUpdate();
|
|
123
|
+
task(progressMonitor);
|
|
124
|
+
} finally {
|
|
125
|
+
this.tasks.splice(this.tasks.indexOf(progressMonitor), 1);
|
|
126
|
+
this.notifyUpdate();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async runAsync(name, task) {
|
|
130
|
+
const progressMonitor = this.createProgressMonitor(name);
|
|
131
|
+
this.tasks.push(progressMonitor);
|
|
132
|
+
this.notifyUpdate();
|
|
133
|
+
return task(progressMonitor).finally(() => {
|
|
134
|
+
this.tasks.splice(this.tasks.indexOf(progressMonitor), 1);
|
|
135
|
+
this.notifyUpdate();
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
createProgressMonitor(name) {
|
|
139
|
+
const monitor = {
|
|
140
|
+
name,
|
|
141
|
+
message: "",
|
|
142
|
+
currentStep: 0,
|
|
143
|
+
totalSteps: -1,
|
|
144
|
+
// -1 indicates indefinite progress
|
|
145
|
+
progress: -1
|
|
146
|
+
// -1 means use step-based calculation
|
|
147
|
+
};
|
|
148
|
+
return new Proxy(monitor, {
|
|
149
|
+
set: (target, prop, value) => {
|
|
150
|
+
target[prop] = value;
|
|
151
|
+
this.notifyUpdate();
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
getActiveTasks() {
|
|
157
|
+
return this.tasks;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const taskService = new TaskService();
|
|
161
|
+
rootContext.put("taskService", taskService);
|
|
162
|
+
const logger$2 = createLogger("EsmShService");
|
|
163
|
+
const _EsmShService = class _EsmShService {
|
|
164
|
+
isEsmShUrl(url) {
|
|
165
|
+
try {
|
|
166
|
+
const urlObj = new URL(url);
|
|
167
|
+
return urlObj.hostname === "esm.sh" || urlObj.hostname === "raw.esm.sh";
|
|
168
|
+
} catch {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
isSourceIdentifier(source) {
|
|
173
|
+
if (this.isEsmShUrl(source)) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
if (this.isHttpUrl(source)) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
return this.parseSource(source) !== null;
|
|
180
|
+
}
|
|
181
|
+
isHttpUrl(url) {
|
|
182
|
+
try {
|
|
183
|
+
const parsed = new URL(url);
|
|
184
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
185
|
+
} catch {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
parseSource(source) {
|
|
190
|
+
if (!source || typeof source !== "string") {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
source = source.trim();
|
|
194
|
+
if (this.isHttpUrl(source)) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
if (source.startsWith(_EsmShService.GITHUB_PREFIX)) {
|
|
198
|
+
return this.parseGitHubSource(source);
|
|
199
|
+
}
|
|
200
|
+
if (source.startsWith(_EsmShService.JSR_PREFIX)) {
|
|
201
|
+
return this.parseJsrSource(source);
|
|
202
|
+
}
|
|
203
|
+
if (source.startsWith(_EsmShService.PR_PREFIX)) {
|
|
204
|
+
return this.parsePrSource(source);
|
|
205
|
+
}
|
|
206
|
+
return this.parseNpmSource(source);
|
|
207
|
+
}
|
|
208
|
+
parseGitHubSource(source) {
|
|
209
|
+
const withoutPrefix = source.substring(_EsmShService.GITHUB_PREFIX.length);
|
|
210
|
+
const parts = withoutPrefix.split("/");
|
|
211
|
+
if (parts.length < 2) {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
const owner = parts[0];
|
|
215
|
+
const repoWithRef = parts[1];
|
|
216
|
+
let repo;
|
|
217
|
+
let version;
|
|
218
|
+
let path;
|
|
219
|
+
const refMatch = repoWithRef.match(/^(.+?)(@(.+))?$/);
|
|
220
|
+
if (!refMatch) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
repo = refMatch[1];
|
|
224
|
+
version = refMatch[3];
|
|
225
|
+
if (parts.length > 2) {
|
|
226
|
+
path = parts.slice(2).join("/");
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
type: "github",
|
|
230
|
+
owner,
|
|
231
|
+
repo,
|
|
232
|
+
version,
|
|
233
|
+
path
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
parseJsrSource(source) {
|
|
237
|
+
const withoutPrefix = source.substring(_EsmShService.JSR_PREFIX.length);
|
|
238
|
+
if (!withoutPrefix.startsWith("@")) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
const parts = withoutPrefix.split("/");
|
|
242
|
+
if (parts.length < 2) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
const scope = parts[0];
|
|
246
|
+
const packageWithVersion = parts[1];
|
|
247
|
+
let packageName;
|
|
248
|
+
let version;
|
|
249
|
+
let path;
|
|
250
|
+
const versionMatch = packageWithVersion.match(/^(.+?)(@(.+))?$/);
|
|
251
|
+
if (!versionMatch) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
packageName = `${scope}/${versionMatch[1]}`;
|
|
255
|
+
version = versionMatch[3];
|
|
256
|
+
if (parts.length > 2) {
|
|
257
|
+
path = parts.slice(2).join("/");
|
|
258
|
+
}
|
|
259
|
+
return {
|
|
260
|
+
type: "jsr",
|
|
261
|
+
package: packageName,
|
|
262
|
+
version,
|
|
263
|
+
path
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
parsePrSource(source) {
|
|
267
|
+
const withoutPrefix = source.substring(_EsmShService.PR_PREFIX.length);
|
|
268
|
+
const parts = withoutPrefix.split("/");
|
|
269
|
+
if (parts.length < 2) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
const owner = parts[0];
|
|
273
|
+
const repoWithCommit = parts[1];
|
|
274
|
+
let repo;
|
|
275
|
+
let commit;
|
|
276
|
+
const commitMatch = repoWithCommit.match(/^(.+?)@(.+)$/);
|
|
277
|
+
if (commitMatch) {
|
|
278
|
+
repo = commitMatch[1];
|
|
279
|
+
commit = commitMatch[2];
|
|
280
|
+
} else {
|
|
281
|
+
repo = repoWithCommit;
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
type: "pr",
|
|
285
|
+
owner,
|
|
286
|
+
repo,
|
|
287
|
+
commit
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
parseNpmSource(source) {
|
|
291
|
+
const parts = source.split("/");
|
|
292
|
+
const firstPart = parts[0];
|
|
293
|
+
let packageName;
|
|
294
|
+
let version;
|
|
295
|
+
let path;
|
|
296
|
+
const versionMatch = firstPart.match(/^(.+?)(@(.+))?$/);
|
|
297
|
+
if (!versionMatch) {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
packageName = versionMatch[1];
|
|
301
|
+
version = versionMatch[3];
|
|
302
|
+
if (parts.length > 1) {
|
|
303
|
+
path = parts.slice(1).join("/");
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
type: "npm",
|
|
307
|
+
package: packageName,
|
|
308
|
+
version,
|
|
309
|
+
path
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
buildEsmShUrl(source, options) {
|
|
313
|
+
let url = _EsmShService.ESM_SH_BASE;
|
|
314
|
+
switch (source.type) {
|
|
315
|
+
case "npm":
|
|
316
|
+
url += `/${source.package}`;
|
|
317
|
+
if (source.version) {
|
|
318
|
+
url += `@${source.version}`;
|
|
319
|
+
}
|
|
320
|
+
if (source.path) {
|
|
321
|
+
url += `/${source.path}`;
|
|
322
|
+
}
|
|
323
|
+
break;
|
|
324
|
+
case "github":
|
|
325
|
+
url += `/${_EsmShService.GITHUB_PREFIX}${source.owner}/${source.repo}`;
|
|
326
|
+
if (source.version) {
|
|
327
|
+
url += `@${source.version}`;
|
|
328
|
+
}
|
|
329
|
+
if (source.path) {
|
|
330
|
+
url += `/${source.path}`;
|
|
331
|
+
}
|
|
332
|
+
break;
|
|
333
|
+
case "jsr":
|
|
334
|
+
url += `/${_EsmShService.JSR_PREFIX}${source.package}`;
|
|
335
|
+
if (source.version) {
|
|
336
|
+
url += `@${source.version}`;
|
|
337
|
+
}
|
|
338
|
+
if (source.path) {
|
|
339
|
+
url += `/${source.path}`;
|
|
340
|
+
}
|
|
341
|
+
break;
|
|
342
|
+
case "pr":
|
|
343
|
+
url += `/${_EsmShService.PR_PREFIX}${source.owner}/${source.repo}`;
|
|
344
|
+
if (source.commit) {
|
|
345
|
+
url += `@${source.commit}`;
|
|
346
|
+
}
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
const queryParams = [];
|
|
350
|
+
if (options?.deps) {
|
|
351
|
+
const depsString = Object.entries(options.deps).map(([pkg, version]) => `${pkg}@${version}`).join(",");
|
|
352
|
+
queryParams.push(`deps=${encodeURIComponent(depsString)}`);
|
|
353
|
+
}
|
|
354
|
+
if (options?.target) {
|
|
355
|
+
queryParams.push(`target=${encodeURIComponent(options.target)}`);
|
|
356
|
+
}
|
|
357
|
+
if (options?.dev) {
|
|
358
|
+
queryParams.push("dev");
|
|
359
|
+
}
|
|
360
|
+
if (options?.bundle === false) {
|
|
361
|
+
queryParams.push("bundle=false");
|
|
362
|
+
} else if (options?.bundle === true) {
|
|
363
|
+
queryParams.push("bundle");
|
|
364
|
+
}
|
|
365
|
+
if (queryParams.length > 0) {
|
|
366
|
+
url += `?${queryParams.join("&")}`;
|
|
367
|
+
}
|
|
368
|
+
return url;
|
|
369
|
+
}
|
|
370
|
+
normalizeToEsmSh(source, options) {
|
|
371
|
+
if (this.isEsmShUrl(source)) {
|
|
372
|
+
return source;
|
|
373
|
+
}
|
|
374
|
+
if (this.isHttpUrl(source)) {
|
|
375
|
+
return source;
|
|
376
|
+
}
|
|
377
|
+
const parsed = this.parseSource(source);
|
|
378
|
+
if (!parsed) {
|
|
379
|
+
logger$2.warn(`Could not parse source identifier: ${source}`);
|
|
380
|
+
return source;
|
|
381
|
+
}
|
|
382
|
+
return this.buildEsmShUrl(parsed, options);
|
|
383
|
+
}
|
|
384
|
+
extractPackageName(source) {
|
|
385
|
+
const parsed = this.parseSource(source);
|
|
386
|
+
if (!parsed) {
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
switch (parsed.type) {
|
|
390
|
+
case "npm":
|
|
391
|
+
return parsed.package || null;
|
|
392
|
+
case "github":
|
|
393
|
+
return `${parsed.owner}/${parsed.repo}`;
|
|
394
|
+
case "jsr":
|
|
395
|
+
return parsed.package || null;
|
|
396
|
+
case "pr":
|
|
397
|
+
return `${parsed.owner}/${parsed.repo}`;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
isGitHubUrl(url) {
|
|
401
|
+
try {
|
|
402
|
+
const urlObj = new URL(url);
|
|
403
|
+
return urlObj.hostname === "github.com" || urlObj.hostname === "www.github.com";
|
|
404
|
+
} catch {
|
|
405
|
+
return url.startsWith("https://github.com/") || url.startsWith("http://github.com/");
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
convertGitHubUrlToSource(githubUrl) {
|
|
409
|
+
try {
|
|
410
|
+
const urlObj = new URL(githubUrl);
|
|
411
|
+
const pathParts = urlObj.pathname.split("/").filter((p) => p);
|
|
412
|
+
if (pathParts.length < 2) {
|
|
413
|
+
throw new Error("Invalid GitHub URL format");
|
|
414
|
+
}
|
|
415
|
+
const owner = pathParts[0];
|
|
416
|
+
let repo = pathParts[1].replace(/\.git$/, "");
|
|
417
|
+
let ref2;
|
|
418
|
+
let filePath;
|
|
419
|
+
if (pathParts.length > 2) {
|
|
420
|
+
if (pathParts[2] === "blob" || pathParts[2] === "tree") {
|
|
421
|
+
ref2 = pathParts[3] || "main";
|
|
422
|
+
if (pathParts[2] === "blob" && pathParts.length > 4) {
|
|
423
|
+
filePath = pathParts.slice(4).join("/");
|
|
424
|
+
}
|
|
425
|
+
} else if (pathParts[2] === "commit") {
|
|
426
|
+
ref2 = pathParts[3];
|
|
427
|
+
} else {
|
|
428
|
+
filePath = pathParts.slice(2).join("/");
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
let ghUrl = `${_EsmShService.GITHUB_PREFIX}${owner}/${repo}`;
|
|
432
|
+
if (ref2) {
|
|
433
|
+
ghUrl += `@${ref2}`;
|
|
434
|
+
}
|
|
435
|
+
if (filePath) {
|
|
436
|
+
ghUrl += `/${filePath}`;
|
|
437
|
+
}
|
|
438
|
+
return ghUrl;
|
|
439
|
+
} catch {
|
|
440
|
+
const urlMatch = githubUrl.match(/github\.com\/([^\/]+)\/([^\/]+)/);
|
|
441
|
+
if (urlMatch) {
|
|
442
|
+
return `${_EsmShService.GITHUB_PREFIX}${urlMatch[1]}/${urlMatch[2].replace(/\.git$/, "")}`;
|
|
443
|
+
}
|
|
444
|
+
return githubUrl;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
async fetchGitHubPackageJson(source) {
|
|
448
|
+
if (source.type !== "github") {
|
|
449
|
+
throw new Error("Source must be a GitHub source");
|
|
450
|
+
}
|
|
451
|
+
const owner = source.owner;
|
|
452
|
+
const repo = source.repo;
|
|
453
|
+
const ref2 = source.version || "main";
|
|
454
|
+
const packageJsonUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${ref2}/package.json`;
|
|
455
|
+
const response = await fetch(packageJsonUrl);
|
|
456
|
+
if (!response.ok) {
|
|
457
|
+
throw new Error(`Failed to fetch package.json: ${response.statusText}`);
|
|
458
|
+
}
|
|
459
|
+
return await response.json();
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
_EsmShService.ESM_SH_BASE = "https://esm.sh";
|
|
463
|
+
_EsmShService.GITHUB_PREFIX = "gh/";
|
|
464
|
+
_EsmShService.JSR_PREFIX = "jsr/";
|
|
465
|
+
_EsmShService.PR_PREFIX = "pr/";
|
|
466
|
+
let EsmShService = _EsmShService;
|
|
467
|
+
const esmShService = new EsmShService();
|
|
468
|
+
rootContext.put("esmShService", esmShService);
|
|
469
|
+
const TOPIC_EXTENSIONS_CHANGED = "events/extensionsregistry/extensionsConfigChanged";
|
|
470
|
+
const KEY_EXTENSIONS_CONFIG = "extensions";
|
|
471
|
+
const KEY_EXTERNAL_EXTENSIONS = "extensions.external";
|
|
472
|
+
class ExtensionRegistry {
|
|
473
|
+
constructor() {
|
|
474
|
+
this.extensions = {};
|
|
475
|
+
this.loadedExtensions = /* @__PURE__ */ new Set();
|
|
476
|
+
this.loadingPromises = /* @__PURE__ */ new Map();
|
|
477
|
+
subscribe(TOPIC_SETTINGS_CHANGED, () => {
|
|
478
|
+
this.extensionsSettings = void 0;
|
|
479
|
+
this.checkExtensionsConfig().then();
|
|
480
|
+
});
|
|
481
|
+
this.loadPersistedExternalExtensions().then(() => {
|
|
482
|
+
this.checkExtensionsConfig().then(async () => {
|
|
483
|
+
const loadPromises = this.extensionsSettings?.filter((setting) => this.isEnabled(setting.id)).map(
|
|
484
|
+
(setting) => this.load(setting.id).catch((e) => {
|
|
485
|
+
toastError("Extension could not be loaded: " + e.message);
|
|
486
|
+
})
|
|
487
|
+
) || [];
|
|
488
|
+
await Promise.all(loadPromises);
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
async loadPersistedExternalExtensions() {
|
|
493
|
+
try {
|
|
494
|
+
const persisted = await appSettings.get(KEY_EXTERNAL_EXTENSIONS);
|
|
495
|
+
if (persisted && Array.isArray(persisted)) {
|
|
496
|
+
persisted.forEach((ext) => {
|
|
497
|
+
this.extensions[ext.id] = ext;
|
|
498
|
+
});
|
|
499
|
+
defaultLogger.debug(`Loaded ${persisted.length} persisted external extensions`);
|
|
500
|
+
}
|
|
501
|
+
} catch (error) {
|
|
502
|
+
defaultLogger.error(`Failed to load persisted external extensions: ${error}`);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
async savePersistedExternalExtensions() {
|
|
506
|
+
try {
|
|
507
|
+
const externalExtensions = Object.values(this.extensions).filter((ext) => ext.external);
|
|
508
|
+
await appSettings.set(KEY_EXTERNAL_EXTENSIONS, externalExtensions);
|
|
509
|
+
} catch (error) {
|
|
510
|
+
defaultLogger.error(`Failed to save persisted external extensions: ${error}`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
async checkExtensionsConfig() {
|
|
514
|
+
if (!this.extensionsSettings) {
|
|
515
|
+
this.extensionsSettings = await appSettings.get(KEY_EXTENSIONS_CONFIG);
|
|
516
|
+
if (!this.extensionsSettings) {
|
|
517
|
+
await appSettings.set(KEY_EXTENSIONS_CONFIG, []);
|
|
518
|
+
this.extensionsSettings = await appSettings.get(KEY_EXTENSIONS_CONFIG);
|
|
519
|
+
}
|
|
520
|
+
publish(TOPIC_EXTENSIONS_CHANGED, this.extensionsSettings);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
registerExtension(extension) {
|
|
524
|
+
this.extensions[extension.id] = extension;
|
|
525
|
+
if (extension.external) {
|
|
526
|
+
this.savePersistedExternalExtensions().catch((err) => {
|
|
527
|
+
defaultLogger.error(`Failed to persist external extension: ${err}`);
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
publish(TOPIC_EXTENSIONS_CHANGED, this.extensionsSettings);
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Load an extension from a URL and register it.
|
|
534
|
+
* The module at the URL must export a default function that receives uiContext.
|
|
535
|
+
* The extension will register its contributions when loaded.
|
|
536
|
+
*
|
|
537
|
+
* Supports:
|
|
538
|
+
* - Direct URLs (http/https)
|
|
539
|
+
* - esm.sh URLs
|
|
540
|
+
* - Source identifiers (npm packages, GitHub repos, JSR packages, PR packages)
|
|
541
|
+
* Examples: 'react@18', 'gh/user/repo', 'jsr/@std/encoding@1.0.0', 'pr/owner/repo@commit'
|
|
542
|
+
*
|
|
543
|
+
* @param url - URL or source identifier to the extension module
|
|
544
|
+
* @param extensionId - Optional extension ID. If not provided, generates one from the URL.
|
|
545
|
+
* @returns Promise that resolves to the extension ID when the extension is loaded
|
|
546
|
+
*/
|
|
547
|
+
async loadExtensionFromUrl(url, extensionId) {
|
|
548
|
+
defaultLogger.info(`Loading extension from URL: ${url}...`);
|
|
549
|
+
try {
|
|
550
|
+
let finalUrl = url;
|
|
551
|
+
let extensionName = `Extension from ${url}`;
|
|
552
|
+
if (esmShService.isSourceIdentifier(url)) {
|
|
553
|
+
const packageName = esmShService.extractPackageName(url);
|
|
554
|
+
if (packageName) {
|
|
555
|
+
extensionName = `Extension: ${packageName}`;
|
|
556
|
+
}
|
|
557
|
+
finalUrl = esmShService.normalizeToEsmSh(url);
|
|
558
|
+
defaultLogger.debug(`Converted source identifier to esm.sh URL: ${url} -> ${finalUrl}`);
|
|
559
|
+
}
|
|
560
|
+
const id = extensionId || `url:${finalUrl}`;
|
|
561
|
+
if (this.isEnabled(id)) {
|
|
562
|
+
defaultLogger.info(`Extension from URL ${finalUrl} is already enabled`);
|
|
563
|
+
return id;
|
|
564
|
+
}
|
|
565
|
+
if (!this.extensions[id]) {
|
|
566
|
+
const extension = {
|
|
567
|
+
id,
|
|
568
|
+
name: extensionName,
|
|
569
|
+
description: `Extension loaded from: ${url}`,
|
|
570
|
+
url: finalUrl
|
|
571
|
+
};
|
|
572
|
+
this.registerExtension(extension);
|
|
573
|
+
defaultLogger.info(`Registered extension from URL: ${id}`);
|
|
574
|
+
}
|
|
575
|
+
this.enable(id, false);
|
|
576
|
+
defaultLogger.info(`Successfully enabled extension from URL: ${finalUrl}`);
|
|
577
|
+
return id;
|
|
578
|
+
} catch (error) {
|
|
579
|
+
defaultLogger.error(`Failed to load extension from URL ${url}: ${error}`);
|
|
580
|
+
throw error;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
getExtensions() {
|
|
584
|
+
return Object.values(this.extensions);
|
|
585
|
+
}
|
|
586
|
+
isEnabled(extensionId) {
|
|
587
|
+
this.checkExtensionsConfig();
|
|
588
|
+
return !!this.extensionsSettings?.find((setting) => setting.id === extensionId && setting.enabled);
|
|
589
|
+
}
|
|
590
|
+
isLoaded(extensionId) {
|
|
591
|
+
return this.loadedExtensions.has(extensionId);
|
|
592
|
+
}
|
|
593
|
+
enable(extensionId, informUser = false) {
|
|
594
|
+
if (this.isEnabled(extensionId)) {
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
defaultLogger.debug(`Loading extension: ${extensionId}`);
|
|
598
|
+
this.load(extensionId).then(() => {
|
|
599
|
+
this.updateEnablement(extensionId, true, informUser);
|
|
600
|
+
}).catch((_e) => {
|
|
601
|
+
defaultLogger.error(`Could not load extension: ${extensionId}: ${_e}`);
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Loads an extension and all its dependencies.
|
|
606
|
+
*
|
|
607
|
+
* Features:
|
|
608
|
+
* - Automatically loads all dependencies recursively before loading the extension
|
|
609
|
+
* - Ensures each extension is loaded only once (idempotent)
|
|
610
|
+
* - Dependencies are loaded in the order they are declared
|
|
611
|
+
* - If an extension is already being loaded, waits for that load to complete
|
|
612
|
+
* - Detects circular dependencies in the dependency chain
|
|
613
|
+
*
|
|
614
|
+
* @param extensionId - The ID of the extension to load
|
|
615
|
+
* @param loadingChain - Internal parameter to track the dependency chain for circular detection
|
|
616
|
+
* @throws Error if the extension is not found or if a circular dependency is detected
|
|
617
|
+
*
|
|
618
|
+
* @example
|
|
619
|
+
* ```typescript
|
|
620
|
+
* // This will automatically load system.pythonruntime first
|
|
621
|
+
* await extensionRegistry.load('system.notebook')
|
|
622
|
+
* ```
|
|
623
|
+
*/
|
|
624
|
+
async load(extensionId, loadingChain = []) {
|
|
625
|
+
if (this.loadedExtensions.has(extensionId)) {
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
const existingPromise = this.loadingPromises.get(extensionId);
|
|
629
|
+
if (existingPromise) {
|
|
630
|
+
return existingPromise;
|
|
631
|
+
}
|
|
632
|
+
if (loadingChain.includes(extensionId)) {
|
|
633
|
+
const chain = [...loadingChain, extensionId].join(" → ");
|
|
634
|
+
throw new Error(`Circular dependency detected: ${chain}`);
|
|
635
|
+
}
|
|
636
|
+
const extension = this.extensions[extensionId];
|
|
637
|
+
if (!extension) {
|
|
638
|
+
throw new Error("Extension not found: " + extensionId);
|
|
639
|
+
}
|
|
640
|
+
const loadingPromise = (async () => {
|
|
641
|
+
try {
|
|
642
|
+
if (extension.dependencies && extension.dependencies.length > 0) {
|
|
643
|
+
defaultLogger.debug(`Loading dependencies for ${extensionId}: ${extension.dependencies.join(", ")}`);
|
|
644
|
+
const newChain = [...loadingChain, extensionId];
|
|
645
|
+
for (const depId of extension.dependencies) {
|
|
646
|
+
await this.load(depId, newChain);
|
|
647
|
+
if (!this.isEnabled(depId)) {
|
|
648
|
+
await this.updateEnablementAsync(depId, true, false);
|
|
649
|
+
defaultLogger.debug(`Auto-enabled dependency: ${depId}`);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
const module = await taskService.runAsync("Loading extension: " + extension.name, async () => {
|
|
654
|
+
if (extension.loader) {
|
|
655
|
+
return extension.loader();
|
|
656
|
+
} else if (extension.url) {
|
|
657
|
+
let finalUrl = extension.url;
|
|
658
|
+
if (esmShService.isSourceIdentifier(extension.url)) {
|
|
659
|
+
finalUrl = esmShService.normalizeToEsmSh(extension.url);
|
|
660
|
+
defaultLogger.debug(`Normalized extension URL: ${extension.url} -> ${finalUrl}`);
|
|
661
|
+
}
|
|
662
|
+
return import(
|
|
663
|
+
/* @vite-ignore */
|
|
664
|
+
finalUrl
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
this.loadedExtensions.add(extensionId);
|
|
669
|
+
if (module?.default instanceof Function) {
|
|
670
|
+
defaultLogger.debug(`Executing extension function for: ${extensionId}`);
|
|
671
|
+
try {
|
|
672
|
+
module?.default(uiContext.getProxy());
|
|
673
|
+
defaultLogger.debug(`Extension function executed successfully: ${extensionId}`);
|
|
674
|
+
} catch (error) {
|
|
675
|
+
defaultLogger.error(`Error executing extension function for ${extensionId}: ${error}`);
|
|
676
|
+
throw error;
|
|
677
|
+
}
|
|
678
|
+
} else {
|
|
679
|
+
defaultLogger.warn(`Extension ${extensionId} does not export a default function`);
|
|
680
|
+
}
|
|
681
|
+
defaultLogger.debug(`Extension loaded: ${extensionId}`);
|
|
682
|
+
} catch (error) {
|
|
683
|
+
this.loadedExtensions.delete(extensionId);
|
|
684
|
+
throw error;
|
|
685
|
+
} finally {
|
|
686
|
+
this.loadingPromises.delete(extensionId);
|
|
687
|
+
}
|
|
688
|
+
})();
|
|
689
|
+
this.loadingPromises.set(extensionId, loadingPromise);
|
|
690
|
+
return loadingPromise;
|
|
691
|
+
}
|
|
692
|
+
disable(extensionId, informUser = false) {
|
|
693
|
+
if (!this.isEnabled(extensionId)) {
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
this.updateEnablement(extensionId, false, informUser);
|
|
697
|
+
}
|
|
698
|
+
updateEnablement(extensionId, enabled, informUser) {
|
|
699
|
+
this.checkExtensionsConfig().then(() => {
|
|
700
|
+
const extension = this.extensionsSettings?.find((e) => e.id == extensionId);
|
|
701
|
+
if (extension) {
|
|
702
|
+
extension.enabled = enabled;
|
|
703
|
+
} else {
|
|
704
|
+
this.extensionsSettings?.push({ id: extensionId, enabled });
|
|
705
|
+
}
|
|
706
|
+
appSettings.set(KEY_EXTENSIONS_CONFIG, this.extensionsSettings).then(() => {
|
|
707
|
+
if (informUser) {
|
|
708
|
+
const extObj = this.extensions[extensionId];
|
|
709
|
+
if (enabled) {
|
|
710
|
+
toastInfo(extObj.name + " enabled.");
|
|
711
|
+
} else {
|
|
712
|
+
toastInfo(extObj.name + " disabled - Please restart to take effect");
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
publish(TOPIC_EXTENSIONS_CHANGED, this.extensionsSettings);
|
|
716
|
+
});
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
async updateEnablementAsync(extensionId, enabled, informUser) {
|
|
720
|
+
await this.checkExtensionsConfig();
|
|
721
|
+
const extension = this.extensionsSettings?.find((e) => e.id == extensionId);
|
|
722
|
+
if (extension) {
|
|
723
|
+
extension.enabled = enabled;
|
|
724
|
+
} else {
|
|
725
|
+
this.extensionsSettings?.push({ id: extensionId, enabled });
|
|
726
|
+
}
|
|
727
|
+
await appSettings.set(KEY_EXTENSIONS_CONFIG, this.extensionsSettings);
|
|
728
|
+
if (informUser) {
|
|
729
|
+
const extObj = this.extensions[extensionId];
|
|
730
|
+
if (enabled) {
|
|
731
|
+
toastInfo(extObj.name + " enabled.");
|
|
732
|
+
} else {
|
|
733
|
+
toastInfo(extObj.name + " disabled - Please restart to take effect");
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
publish(TOPIC_EXTENSIONS_CHANGED, this.extensionsSettings);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
defaultLogger.debug("ExtensionRegistry initializing...");
|
|
740
|
+
const extensionRegistry = new ExtensionRegistry();
|
|
741
|
+
rootContext.put("extensionRegistry", extensionRegistry);
|
|
742
|
+
defaultLogger.debug("ExtensionRegistry initialized");
|
|
743
|
+
class KElement extends KWidget {
|
|
744
|
+
constructor() {
|
|
745
|
+
super(...arguments);
|
|
746
|
+
this.settingsKey = null;
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Builds a unique DOM tree path for this element.
|
|
750
|
+
* Uses id attribute if available, otherwise builds a path based on tag names and indices.
|
|
751
|
+
* Useful for generating unique settings keys.
|
|
752
|
+
*
|
|
753
|
+
* @returns A string representing the DOM path, or null if path cannot be determined
|
|
754
|
+
*/
|
|
755
|
+
buildDOMTreePath() {
|
|
756
|
+
const pathParts = [];
|
|
757
|
+
let current = this;
|
|
758
|
+
while (current && current !== document.body && current !== document.documentElement) {
|
|
759
|
+
const id = current.getAttribute("id");
|
|
760
|
+
if (id) {
|
|
761
|
+
pathParts.unshift(`#${id}`);
|
|
762
|
+
break;
|
|
763
|
+
}
|
|
764
|
+
const tagName = current.tagName.toLowerCase();
|
|
765
|
+
const parent = current.parentElement;
|
|
766
|
+
if (!parent) {
|
|
767
|
+
break;
|
|
768
|
+
}
|
|
769
|
+
const siblings = Array.from(parent.children).filter(
|
|
770
|
+
(child) => child.tagName.toLowerCase() === tagName
|
|
771
|
+
);
|
|
772
|
+
const index = siblings.indexOf(current);
|
|
773
|
+
if (index >= 0) {
|
|
774
|
+
pathParts.unshift(`${tagName}:${index}`);
|
|
775
|
+
} else {
|
|
776
|
+
pathParts.unshift(tagName);
|
|
777
|
+
}
|
|
778
|
+
current = parent;
|
|
779
|
+
}
|
|
780
|
+
return pathParts.length > 0 ? pathParts.join(" > ") : null;
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Initializes the settings key for this element using the element's tag name.
|
|
784
|
+
* Called automatically on first access via getDialogSetting() or setDialogSetting().
|
|
785
|
+
*/
|
|
786
|
+
initializeSettingsKey() {
|
|
787
|
+
if (!this.settingsKey) {
|
|
788
|
+
const prefix = this.tagName.toLowerCase();
|
|
789
|
+
const id = this.getAttribute("id");
|
|
790
|
+
if (id) {
|
|
791
|
+
this.settingsKey = `${prefix}:${id}`;
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
const path = this.buildDOMTreePath();
|
|
795
|
+
if (path) {
|
|
796
|
+
this.settingsKey = `${prefix}:${path}`;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Gets a dialog setting value for this element.
|
|
802
|
+
* Automatically initializes the settings key on first access if not already set.
|
|
803
|
+
*
|
|
804
|
+
* @returns The persisted setting value, or undefined if not found
|
|
805
|
+
*/
|
|
806
|
+
async getDialogSetting() {
|
|
807
|
+
this.initializeSettingsKey();
|
|
808
|
+
if (!this.settingsKey) {
|
|
809
|
+
return void 0;
|
|
810
|
+
}
|
|
811
|
+
return await appSettings.getDialogSetting(this.settingsKey);
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Saves a dialog setting value for this element.
|
|
815
|
+
* Automatically initializes the settings key on first access if not already set.
|
|
816
|
+
*
|
|
817
|
+
* @param value - The value to persist
|
|
818
|
+
*/
|
|
819
|
+
async setDialogSetting(value) {
|
|
820
|
+
this.initializeSettingsKey();
|
|
821
|
+
if (!this.settingsKey) {
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
await appSettings.setDialogSetting(this.settingsKey, value);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
const _KDialogContent = class _KDialogContent extends KElement {
|
|
828
|
+
dispose() {
|
|
829
|
+
}
|
|
830
|
+
getResult() {
|
|
831
|
+
return void 0;
|
|
832
|
+
}
|
|
833
|
+
renderMessage(message, markdown = false) {
|
|
834
|
+
if (markdown) {
|
|
835
|
+
const htmlContent = marked.parse(message, { async: false });
|
|
836
|
+
return html`<div class="dialog-message" style="white-space: normal;">${unsafeHTML(htmlContent)}</div>`;
|
|
837
|
+
}
|
|
838
|
+
return html`<div class="dialog-message" style="white-space: pre-line;">${message}</div>`;
|
|
839
|
+
}
|
|
840
|
+
};
|
|
841
|
+
_KDialogContent.styles = [
|
|
842
|
+
css`
|
|
843
|
+
.dialog-message {
|
|
844
|
+
margin-bottom: 0.5rem;
|
|
845
|
+
color: var(--wa-color-text-normal);
|
|
846
|
+
}
|
|
847
|
+
`
|
|
848
|
+
];
|
|
849
|
+
let KDialogContent = _KDialogContent;
|
|
850
|
+
const logger$1 = createLogger("DialogService");
|
|
851
|
+
const DIALOG_CONTRIBUTION_TARGET = "dialogs";
|
|
852
|
+
const OK_BUTTON = {
|
|
853
|
+
id: "ok",
|
|
854
|
+
label: "OK",
|
|
855
|
+
variant: "primary"
|
|
856
|
+
};
|
|
857
|
+
const CANCEL_BUTTON = {
|
|
858
|
+
id: "cancel",
|
|
859
|
+
label: "Cancel",
|
|
860
|
+
variant: "default"
|
|
861
|
+
};
|
|
862
|
+
const CLOSE_BUTTON = {
|
|
863
|
+
id: "close",
|
|
864
|
+
label: "Close",
|
|
865
|
+
variant: "default"
|
|
866
|
+
};
|
|
867
|
+
let dialogContainer = null;
|
|
868
|
+
function getDialogContainer() {
|
|
869
|
+
if (!dialogContainer) {
|
|
870
|
+
dialogContainer = document.createElement("div");
|
|
871
|
+
dialogContainer.id = "global-dialog-container";
|
|
872
|
+
document.body.appendChild(dialogContainer);
|
|
873
|
+
}
|
|
874
|
+
return dialogContainer;
|
|
875
|
+
}
|
|
876
|
+
class DialogService {
|
|
877
|
+
constructor() {
|
|
878
|
+
this.contributions = /* @__PURE__ */ new Map();
|
|
879
|
+
this.loadContributions();
|
|
880
|
+
subscribe(TOPIC_CONTRIBUTEIONS_CHANGED, (event) => {
|
|
881
|
+
if (event.target === DIALOG_CONTRIBUTION_TARGET) {
|
|
882
|
+
this.loadContributions();
|
|
883
|
+
}
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
loadContributions() {
|
|
887
|
+
const contributions = contributionRegistry.getContributions(DIALOG_CONTRIBUTION_TARGET);
|
|
888
|
+
this.contributions.clear();
|
|
889
|
+
for (const contribution of contributions) {
|
|
890
|
+
if (!contribution.id) {
|
|
891
|
+
logger$1.warn("Dialog contribution missing id, skipping");
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
if (!contribution.component) {
|
|
895
|
+
logger$1.warn(`Dialog contribution "${contribution.id}" has no component function, skipping`);
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
if (!contribution.onButton) {
|
|
899
|
+
logger$1.warn(`Dialog contribution "${contribution.id}" has no onButton callback, skipping`);
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
this.contributions.set(contribution.id, contribution);
|
|
903
|
+
logger$1.debug(`Loaded dialog contribution: ${contribution.id}`);
|
|
904
|
+
}
|
|
905
|
+
logger$1.info(`Loaded ${this.contributions.size} dialog contributions`);
|
|
906
|
+
}
|
|
907
|
+
async open(dialogId, state2) {
|
|
908
|
+
const contribution = this.contributions.get(dialogId);
|
|
909
|
+
if (!contribution) {
|
|
910
|
+
logger$1.error(`Dialog "${dialogId}" not found`);
|
|
911
|
+
throw new Error(`Dialog "${dialogId}" not found`);
|
|
912
|
+
}
|
|
913
|
+
return new Promise((resolve) => {
|
|
914
|
+
const container = getDialogContainer();
|
|
915
|
+
let isOpen = true;
|
|
916
|
+
let dialogContentElement = null;
|
|
917
|
+
const cleanup = async () => {
|
|
918
|
+
if (!isOpen) return;
|
|
919
|
+
isOpen = false;
|
|
920
|
+
if (dialogContentElement) {
|
|
921
|
+
try {
|
|
922
|
+
await dialogContentElement.dispose();
|
|
923
|
+
} catch (error) {
|
|
924
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
925
|
+
logger$1.error(`Error disposing dialog content for "${dialogId}": ${errorMessage}`);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
try {
|
|
929
|
+
const result = dialogContentElement ? dialogContentElement.getResult() : void 0;
|
|
930
|
+
await contribution.onButton("close", result, stateWithClose);
|
|
931
|
+
} catch (error) {
|
|
932
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
933
|
+
logger$1.error(`Error executing close callback for dialog "${dialogId}": ${errorMessage}`);
|
|
934
|
+
}
|
|
935
|
+
render(html``, container);
|
|
936
|
+
resolve();
|
|
937
|
+
};
|
|
938
|
+
const handleButtonClick = async (buttonId) => {
|
|
939
|
+
try {
|
|
940
|
+
const result = dialogContentElement ? dialogContentElement.getResult() : void 0;
|
|
941
|
+
const shouldClose = await contribution.onButton(buttonId, result, stateWithClose);
|
|
942
|
+
if (shouldClose !== false) {
|
|
943
|
+
cleanup();
|
|
944
|
+
}
|
|
945
|
+
} catch (error) {
|
|
946
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
947
|
+
logger$1.error(`Error executing button callback for dialog "${dialogId}": ${errorMessage}`);
|
|
948
|
+
cleanup();
|
|
949
|
+
}
|
|
950
|
+
};
|
|
951
|
+
const buttons = contribution.buttons && contribution.buttons.length > 0 ? contribution.buttons : [OK_BUTTON];
|
|
952
|
+
if (state2 && typeof state2 === "object") {
|
|
953
|
+
state2.close = cleanup;
|
|
954
|
+
}
|
|
955
|
+
const stateWithClose = { ...state2, close: cleanup };
|
|
956
|
+
const template = html`
|
|
957
|
+
<wa-dialog label="${contribution.label || dialogId}" open @wa-request-close=${cleanup}>
|
|
958
|
+
<style>
|
|
959
|
+
.dialog-service-content {
|
|
960
|
+
display: flex;
|
|
961
|
+
flex-direction: column;
|
|
962
|
+
gap: 1rem;
|
|
963
|
+
padding: 1rem;
|
|
964
|
+
min-width: 400px;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
.dialog-service-footer {
|
|
968
|
+
display: flex;
|
|
969
|
+
gap: 0.5rem;
|
|
970
|
+
justify-content: flex-end;
|
|
971
|
+
margin-top: 1rem;
|
|
972
|
+
padding-top: 1rem;
|
|
973
|
+
border-top: 1px solid var(--wa-color-neutral-20);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
:host-context(.wa-light) .dialog-service-footer {
|
|
977
|
+
border-top-color: var(--wa-color-neutral-80);
|
|
978
|
+
}
|
|
979
|
+
</style>
|
|
980
|
+
|
|
981
|
+
<div class="dialog-service-content"
|
|
982
|
+
@dialog-ok=${() => {
|
|
983
|
+
const okButton = buttons.find((b) => b.id === "ok");
|
|
984
|
+
if (okButton) {
|
|
985
|
+
handleButtonClick(okButton.id);
|
|
986
|
+
}
|
|
987
|
+
}}
|
|
988
|
+
@dialog-cancel=${() => {
|
|
989
|
+
const cancelButton = buttons.find((b) => b.id === "cancel");
|
|
990
|
+
if (cancelButton) {
|
|
991
|
+
handleButtonClick(cancelButton.id);
|
|
992
|
+
} else {
|
|
993
|
+
cleanup();
|
|
994
|
+
}
|
|
995
|
+
}}>
|
|
996
|
+
${contribution.component(state2)}
|
|
997
|
+
|
|
998
|
+
<div class="dialog-service-footer">
|
|
999
|
+
${buttons.map((button) => html`
|
|
1000
|
+
<wa-button
|
|
1001
|
+
variant="${button.variant || "default"}"
|
|
1002
|
+
?disabled=${button.disabled}
|
|
1003
|
+
@click=${() => handleButtonClick(button.id)}
|
|
1004
|
+
>
|
|
1005
|
+
${button.label}
|
|
1006
|
+
</wa-button>
|
|
1007
|
+
`)}
|
|
1008
|
+
</div>
|
|
1009
|
+
</div>
|
|
1010
|
+
</wa-dialog>
|
|
1011
|
+
`;
|
|
1012
|
+
render(template, container);
|
|
1013
|
+
(async () => {
|
|
1014
|
+
const allElements = Array.from(container.querySelectorAll("*"));
|
|
1015
|
+
for (const element of allElements) {
|
|
1016
|
+
if (element instanceof KDialogContent) {
|
|
1017
|
+
await element.updateComplete;
|
|
1018
|
+
dialogContentElement = element;
|
|
1019
|
+
break;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
})();
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
getDialogIds() {
|
|
1026
|
+
return Array.from(this.contributions.keys());
|
|
1027
|
+
}
|
|
1028
|
+
hasDialog(dialogId) {
|
|
1029
|
+
return this.contributions.has(dialogId);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
const dialogService = new DialogService();
|
|
1033
|
+
rootContext.put("dialogService", dialogService);
|
|
1034
|
+
class KContainer extends KElement {
|
|
1035
|
+
}
|
|
1036
|
+
var __defProp$8 = Object.defineProperty;
|
|
1037
|
+
var __decorateClass$9 = (decorators, target, key, kind) => {
|
|
1038
|
+
var result = void 0;
|
|
1039
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
1040
|
+
if (decorator = decorators[i])
|
|
1041
|
+
result = decorator(target, key, result) || result;
|
|
1042
|
+
if (result) __defProp$8(target, key, result);
|
|
1043
|
+
return result;
|
|
1044
|
+
};
|
|
1045
|
+
class KPart extends KContainer {
|
|
1046
|
+
constructor() {
|
|
1047
|
+
super(...arguments);
|
|
1048
|
+
this.dirty = false;
|
|
1049
|
+
this.isEditor = false;
|
|
1050
|
+
}
|
|
1051
|
+
getCommandStack() {
|
|
1052
|
+
return this.commandStack;
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* Override this method to provide toolbar content for this part.
|
|
1056
|
+
* This is a lightweight alternative to registering toolbar contributions
|
|
1057
|
+
* for actions that are scoped to this part instance.
|
|
1058
|
+
*
|
|
1059
|
+
* IMPORTANT: Event handlers MUST use arrow functions to preserve the component's 'this' context.
|
|
1060
|
+
* The toolbar template is rendered in a different component (k-toolbar), so direct method
|
|
1061
|
+
* references lose their binding.
|
|
1062
|
+
*
|
|
1063
|
+
* ✅ Correct:
|
|
1064
|
+
* @click=${() => this.myMethod()}
|
|
1065
|
+
* @click=${(e) => this.handleClick(e)}
|
|
1066
|
+
*
|
|
1067
|
+
* ❌ Wrong (this will be bound to the toolbar, not your component):
|
|
1068
|
+
* @click=${this.myMethod}
|
|
1069
|
+
*
|
|
1070
|
+
* Example:
|
|
1071
|
+
* ```typescript
|
|
1072
|
+
* protected renderToolbar() {
|
|
1073
|
+
* return html`
|
|
1074
|
+
* <wa-button @click=${() => this.save()} title="Save">
|
|
1075
|
+
* <wa-icon name="save"></wa-icon>
|
|
1076
|
+
* </wa-button>
|
|
1077
|
+
* `;
|
|
1078
|
+
* }
|
|
1079
|
+
* ```
|
|
1080
|
+
*
|
|
1081
|
+
* @returns TemplateResult with toolbar items, or nothing if no toolbar needed
|
|
1082
|
+
*/
|
|
1083
|
+
renderToolbar() {
|
|
1084
|
+
return nothing;
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Call this method to update the toolbar when the component's state changes.
|
|
1088
|
+
* This triggers a re-render of the toolbar with the latest content from renderToolbar().
|
|
1089
|
+
*/
|
|
1090
|
+
updateToolbar() {
|
|
1091
|
+
this.dispatchEvent(new CustomEvent("part-toolbar-changed", {
|
|
1092
|
+
bubbles: true,
|
|
1093
|
+
composed: true
|
|
1094
|
+
}));
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Override this method to provide context menu content for this part.
|
|
1098
|
+
* This is a lightweight alternative to registering context menu contributions
|
|
1099
|
+
* for actions that are scoped to this part instance.
|
|
1100
|
+
*
|
|
1101
|
+
* IMPORTANT: Event handlers MUST use arrow functions to preserve the component's 'this' context.
|
|
1102
|
+
* The context menu is rendered in a different component (k-contextmenu), so direct method
|
|
1103
|
+
* references lose their binding.
|
|
1104
|
+
*
|
|
1105
|
+
* ✅ Correct:
|
|
1106
|
+
* @click=${() => this.myMethod()}
|
|
1107
|
+
* @click=${(e) => this.handleClick(e)}
|
|
1108
|
+
*
|
|
1109
|
+
* ❌ Wrong (this will be bound to the context menu, not your component):
|
|
1110
|
+
* @click=${this.myMethod}
|
|
1111
|
+
*
|
|
1112
|
+
* Example:
|
|
1113
|
+
* ```typescript
|
|
1114
|
+
* protected renderContextMenu() {
|
|
1115
|
+
* return html`
|
|
1116
|
+
* <wa-dropdown-item @click=${() => this.open()}>
|
|
1117
|
+
* <wa-icon name="folder-open"></wa-icon>
|
|
1118
|
+
* Open
|
|
1119
|
+
* </wa-dropdown-item>
|
|
1120
|
+
* <wa-divider></wa-divider>
|
|
1121
|
+
* <wa-dropdown-item @click=${() => this.delete()}>
|
|
1122
|
+
* <wa-icon name="trash"></wa-icon>
|
|
1123
|
+
* Delete
|
|
1124
|
+
* </wa-dropdown-item>
|
|
1125
|
+
* `;
|
|
1126
|
+
* }
|
|
1127
|
+
* ```
|
|
1128
|
+
*
|
|
1129
|
+
* @returns TemplateResult with context menu items, or nothing if no context menu needed
|
|
1130
|
+
*/
|
|
1131
|
+
renderContextMenu() {
|
|
1132
|
+
return nothing;
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Call this method to update the context menu when the component's state changes.
|
|
1136
|
+
* This triggers a re-render of the context menu with the latest content from renderContextMenu().
|
|
1137
|
+
*/
|
|
1138
|
+
updateContextMenu() {
|
|
1139
|
+
this.dispatchEvent(new CustomEvent("part-contextmenu-changed", {
|
|
1140
|
+
bubbles: true,
|
|
1141
|
+
composed: true
|
|
1142
|
+
}));
|
|
1143
|
+
}
|
|
1144
|
+
updated(_changedProperties) {
|
|
1145
|
+
super.updated(_changedProperties);
|
|
1146
|
+
if (_changedProperties.has("dirty")) {
|
|
1147
|
+
const dirty = _changedProperties.get("dirty");
|
|
1148
|
+
if (dirty !== void 0) {
|
|
1149
|
+
this.dispatchEvent(new CustomEvent("dirty", { detail: this.dirty, bubbles: true }));
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
doClose() {
|
|
1154
|
+
}
|
|
1155
|
+
disconnectedCallback() {
|
|
1156
|
+
super.disconnectedCallback();
|
|
1157
|
+
}
|
|
1158
|
+
close() {
|
|
1159
|
+
this.doClose();
|
|
1160
|
+
}
|
|
1161
|
+
connectedCallback() {
|
|
1162
|
+
super.connectedCallback();
|
|
1163
|
+
}
|
|
1164
|
+
save() {
|
|
1165
|
+
}
|
|
1166
|
+
isDirty() {
|
|
1167
|
+
return this.dirty;
|
|
1168
|
+
}
|
|
1169
|
+
markDirty(dirty) {
|
|
1170
|
+
this.dirty = dirty;
|
|
1171
|
+
partDirtySignal.set(null);
|
|
1172
|
+
partDirtySignal.set(this);
|
|
1173
|
+
activePartSignal.set(null);
|
|
1174
|
+
activePartSignal.set(this);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
__decorateClass$9([
|
|
1178
|
+
property()
|
|
1179
|
+
], KPart.prototype, "dirty");
|
|
1180
|
+
const logger = createLogger("AppLoader");
|
|
1181
|
+
function getErrorMessage(error) {
|
|
1182
|
+
return error instanceof Error ? error.message : String(error);
|
|
1183
|
+
}
|
|
1184
|
+
function extractLastPathSegment(urlString) {
|
|
1185
|
+
try {
|
|
1186
|
+
const url = new URL(urlString);
|
|
1187
|
+
const pathSegments = url.pathname.split("/").filter(Boolean);
|
|
1188
|
+
return pathSegments.length > 0 ? pathSegments[pathSegments.length - 1] : void 0;
|
|
1189
|
+
} catch {
|
|
1190
|
+
const pathSegments = urlString.split("/").filter(Boolean);
|
|
1191
|
+
return pathSegments.length > 0 ? pathSegments[pathSegments.length - 1] : void 0;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
function extractAppIdFromPath() {
|
|
1195
|
+
const pathname = window.location.pathname;
|
|
1196
|
+
const pathSegments = pathname.split("/").filter(Boolean);
|
|
1197
|
+
if (pathSegments.length === 0) {
|
|
1198
|
+
return void 0;
|
|
1199
|
+
}
|
|
1200
|
+
const firstSegment = pathSegments[0];
|
|
1201
|
+
if (!firstSegment || firstSegment === "index.html" || firstSegment.endsWith(".html")) {
|
|
1202
|
+
return void 0;
|
|
1203
|
+
}
|
|
1204
|
+
return firstSegment;
|
|
1205
|
+
}
|
|
1206
|
+
const _AppLoaderService = class _AppLoaderService {
|
|
1207
|
+
constructor() {
|
|
1208
|
+
this.apps = /* @__PURE__ */ new Map();
|
|
1209
|
+
this.started = false;
|
|
1210
|
+
this.container = document.body;
|
|
1211
|
+
this.systemRequiredExtensions = /* @__PURE__ */ new Set();
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Register an application with the framework.
|
|
1215
|
+
* Optionally starts the apploader automatically after registration.
|
|
1216
|
+
*
|
|
1217
|
+
* @param app - Application definition
|
|
1218
|
+
* @param options - Optional configuration for registration and auto-starting
|
|
1219
|
+
*/
|
|
1220
|
+
registerApp(app, options) {
|
|
1221
|
+
if (this.apps.has(app.id)) {
|
|
1222
|
+
logger.warn(`App '${app.id}' is already registered. Overwriting.`);
|
|
1223
|
+
}
|
|
1224
|
+
this.apps.set(app.id, app);
|
|
1225
|
+
logger.info(`Registered app: ${app.name} (${app.id}) v${app.version}`);
|
|
1226
|
+
if (options?.defaultAppId) {
|
|
1227
|
+
this.defaultAppId = options.defaultAppId;
|
|
1228
|
+
}
|
|
1229
|
+
if (options?.container) {
|
|
1230
|
+
this.container = options.container;
|
|
1231
|
+
}
|
|
1232
|
+
if (options?.autoStart && !this.started) {
|
|
1233
|
+
this.start();
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
registerSystemRequiredExtension(extensionId) {
|
|
1237
|
+
this.systemRequiredExtensions.add(extensionId);
|
|
1238
|
+
}
|
|
1239
|
+
/**
|
|
1240
|
+
* Load an application definition from a URL.
|
|
1241
|
+
* The module at the URL must export an AppDefinition as the default export.
|
|
1242
|
+
*
|
|
1243
|
+
* @param url - URL to the app definition module
|
|
1244
|
+
* @returns Promise that resolves to the loaded AppDefinition
|
|
1245
|
+
*/
|
|
1246
|
+
async loadAppFromUrl(url) {
|
|
1247
|
+
logger.info(`Loading app from URL: ${url}...`);
|
|
1248
|
+
try {
|
|
1249
|
+
const module = await import(
|
|
1250
|
+
/* @vite-ignore */
|
|
1251
|
+
url
|
|
1252
|
+
);
|
|
1253
|
+
if (!module.default) {
|
|
1254
|
+
throw new Error(`Module at ${url} does not have a default export`);
|
|
1255
|
+
}
|
|
1256
|
+
const app = module.default;
|
|
1257
|
+
if (!app.id || !app.name || !app.version) {
|
|
1258
|
+
throw new Error(`Module at ${url} does not export a valid AppDefinition`);
|
|
1259
|
+
}
|
|
1260
|
+
logger.info(`Successfully loaded app definition from URL: ${app.name} (${app.id})`);
|
|
1261
|
+
return app;
|
|
1262
|
+
} catch (error) {
|
|
1263
|
+
logger.error(`Failed to load app from URL ${url}: ${getErrorMessage(error)}`);
|
|
1264
|
+
throw error;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
/**
|
|
1268
|
+
* Start the application loader.
|
|
1269
|
+
* Checks URL parameters for app=URL, loads that extension or app if found.
|
|
1270
|
+
* URL parameter has higher precedence than defaultAppId.
|
|
1271
|
+
* Then loads the default app or first registered app.
|
|
1272
|
+
* This method is idempotent - calling it multiple times only starts once.
|
|
1273
|
+
*/
|
|
1274
|
+
async start() {
|
|
1275
|
+
if (this.started) {
|
|
1276
|
+
logger.debug("AppLoader already started");
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
this.started = true;
|
|
1280
|
+
logger.info("Starting AppLoader...");
|
|
1281
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
1282
|
+
const appUrl = urlParams.get("app");
|
|
1283
|
+
const appIdFromUrl = urlParams.get("appId");
|
|
1284
|
+
const appIdFromPath = extractAppIdFromPath();
|
|
1285
|
+
const appsBeforeExtension = this.apps.size;
|
|
1286
|
+
let appIdFromAppUrl;
|
|
1287
|
+
if (appUrl) {
|
|
1288
|
+
appIdFromAppUrl = extractLastPathSegment(appUrl);
|
|
1289
|
+
if (appIdFromAppUrl) {
|
|
1290
|
+
logger.info(`Extracted app ID from URL path: ${appIdFromAppUrl}`);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
if (appIdFromPath) {
|
|
1294
|
+
logger.info(`Extracted app ID from current page path: ${appIdFromPath}`);
|
|
1295
|
+
}
|
|
1296
|
+
if (appUrl) {
|
|
1297
|
+
try {
|
|
1298
|
+
logger.info(`URL parameter 'app' found: ${appUrl}, attempting to load extension or app`);
|
|
1299
|
+
try {
|
|
1300
|
+
await extensionRegistry.loadExtensionFromUrl(appUrl);
|
|
1301
|
+
logger.info(`Successfully loaded extension from URL: ${appUrl}`);
|
|
1302
|
+
} catch (extensionError) {
|
|
1303
|
+
logger.info(`Failed to load as extension, trying as app definition: ${getErrorMessage(extensionError)}`);
|
|
1304
|
+
try {
|
|
1305
|
+
const app = await this.loadAppFromUrl(appUrl);
|
|
1306
|
+
this.registerApp(app);
|
|
1307
|
+
await this.loadApp(app.id, this.container);
|
|
1308
|
+
logger.info(`Successfully loaded app from URL: ${appUrl}`);
|
|
1309
|
+
return;
|
|
1310
|
+
} catch (appError) {
|
|
1311
|
+
logger.error(`Failed to load from URL as both extension and app: ${getErrorMessage(appError)}`);
|
|
1312
|
+
throw appError;
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
} catch (error) {
|
|
1316
|
+
logger.error(`Failed to load from URL parameter, falling back to default app: ${getErrorMessage(error)}`);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
const appToLoad = await this.selectAppToLoad({
|
|
1320
|
+
appIdFromUrl,
|
|
1321
|
+
appIdFromPath,
|
|
1322
|
+
appIdFromAppUrl,
|
|
1323
|
+
appsBeforeExtension
|
|
1324
|
+
});
|
|
1325
|
+
if (!appToLoad) {
|
|
1326
|
+
throw new Error("No apps registered");
|
|
1327
|
+
}
|
|
1328
|
+
await this.loadApp(appToLoad, this.container);
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Load and initialize an application.
|
|
1332
|
+
*
|
|
1333
|
+
* @param appId - Application identifier (must be already registered)
|
|
1334
|
+
* @param container - Optional DOM element to render into (if provided, auto-renders after loading)
|
|
1335
|
+
* @returns Promise that resolves when app is initialized and rendered
|
|
1336
|
+
*/
|
|
1337
|
+
async loadApp(appId, container) {
|
|
1338
|
+
const app = this.apps.get(appId);
|
|
1339
|
+
if (!app) {
|
|
1340
|
+
throw new Error(`App '${appId}' not found. Make sure it's registered.`);
|
|
1341
|
+
}
|
|
1342
|
+
logger.info(`Loading app: ${app.name}...`);
|
|
1343
|
+
if (this.currentApp) {
|
|
1344
|
+
logger.info(`Disposing current app: ${this.currentApp.name}`);
|
|
1345
|
+
if (this.currentApp.dispose) {
|
|
1346
|
+
await this.currentApp.dispose();
|
|
1347
|
+
}
|
|
1348
|
+
if (this.currentApp.extensions && this.currentApp.extensions.length > 0) {
|
|
1349
|
+
logger.info(`Disabling ${this.currentApp.extensions.length} extensions...`);
|
|
1350
|
+
this.currentApp.extensions.forEach((extId) => {
|
|
1351
|
+
extensionRegistry.disable(extId);
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
if (app.contributions) {
|
|
1356
|
+
logger.info("Registering app contributions...");
|
|
1357
|
+
if (app.contributions.ui) {
|
|
1358
|
+
app.contributions.ui.forEach((contribution) => {
|
|
1359
|
+
const target = contribution.target;
|
|
1360
|
+
if (target) {
|
|
1361
|
+
contributionRegistry.registerContribution(target, contribution);
|
|
1362
|
+
}
|
|
1363
|
+
});
|
|
1364
|
+
logger.info(`Registered ${app.contributions.ui.length} UI contributions`);
|
|
1365
|
+
}
|
|
1366
|
+
if (app.contributions.extensions) {
|
|
1367
|
+
app.contributions.extensions.forEach((extension) => {
|
|
1368
|
+
extensionRegistry.registerExtension(extension);
|
|
1369
|
+
});
|
|
1370
|
+
logger.info(`Registered ${app.contributions.extensions.length} app extensions`);
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
const extensionsSet = new Set(app.extensions || []);
|
|
1374
|
+
this.systemRequiredExtensions.forEach((extId) => extensionsSet.add(extId));
|
|
1375
|
+
app.extensions = Array.from(extensionsSet);
|
|
1376
|
+
if (app.extensions.length > 0) {
|
|
1377
|
+
logger.info(`Enabling ${app.extensions.length} extensions...`);
|
|
1378
|
+
app.extensions.forEach((extId) => {
|
|
1379
|
+
extensionRegistry.enable(extId);
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
if (app.initialize) {
|
|
1383
|
+
logger.info(`Initializing ${app.name}...`);
|
|
1384
|
+
await app.initialize();
|
|
1385
|
+
}
|
|
1386
|
+
this.currentApp = app;
|
|
1387
|
+
logger.info(`App ${app.name} loaded successfully`);
|
|
1388
|
+
this.updateDocumentMetadata(app);
|
|
1389
|
+
if (container) {
|
|
1390
|
+
this.renderApp(container);
|
|
1391
|
+
}
|
|
1392
|
+
window.dispatchEvent(new CustomEvent("app-loaded", { detail: { appId: app.id } }));
|
|
1393
|
+
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Updates document title and favicon from app metadata
|
|
1396
|
+
*/
|
|
1397
|
+
updateDocumentMetadata(app) {
|
|
1398
|
+
document.title = app.name;
|
|
1399
|
+
if (app.metadata?.favicon) {
|
|
1400
|
+
const faviconPath = app.metadata.favicon;
|
|
1401
|
+
let link = document.querySelector("link[rel*='icon']");
|
|
1402
|
+
if (!link) {
|
|
1403
|
+
link = document.createElement("link");
|
|
1404
|
+
link.rel = "icon";
|
|
1405
|
+
document.head.appendChild(link);
|
|
1406
|
+
}
|
|
1407
|
+
link.type = "image/svg+xml";
|
|
1408
|
+
link.href = faviconPath;
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
/**
|
|
1412
|
+
* Render the current application to the DOM.
|
|
1413
|
+
*
|
|
1414
|
+
* @param container - DOM element to render into
|
|
1415
|
+
*/
|
|
1416
|
+
renderApp(container) {
|
|
1417
|
+
if (!this.currentApp) {
|
|
1418
|
+
throw new Error("No app loaded. Call loadApp() first.");
|
|
1419
|
+
}
|
|
1420
|
+
const r = this.currentApp.render;
|
|
1421
|
+
if (typeof r === "string") {
|
|
1422
|
+
const el = document.createElement(r);
|
|
1423
|
+
container.innerHTML = "";
|
|
1424
|
+
container.appendChild(el);
|
|
1425
|
+
} else if (r && typeof r === "object" && "tag" in r) {
|
|
1426
|
+
const el = document.createElement(r.tag);
|
|
1427
|
+
for (const [key, value] of Object.entries(r.attributes ?? {})) {
|
|
1428
|
+
el.setAttribute(key, value);
|
|
1429
|
+
}
|
|
1430
|
+
container.innerHTML = "";
|
|
1431
|
+
container.appendChild(el);
|
|
1432
|
+
} else if (typeof r === "function") {
|
|
1433
|
+
const template = r();
|
|
1434
|
+
render(template, container);
|
|
1435
|
+
} else {
|
|
1436
|
+
render(html`<k-standard-layout></k-standard-layout>`, container);
|
|
1437
|
+
}
|
|
1438
|
+
logger.info(`Rendered ${this.currentApp.name}`);
|
|
1439
|
+
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Get the currently loaded application.
|
|
1442
|
+
*/
|
|
1443
|
+
getCurrentApp() {
|
|
1444
|
+
return this.currentApp;
|
|
1445
|
+
}
|
|
1446
|
+
/**
|
|
1447
|
+
* Get all registered applications.
|
|
1448
|
+
*/
|
|
1449
|
+
getRegisteredApps() {
|
|
1450
|
+
return Array.from(this.apps.values());
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* Get the preferred app ID from settings.
|
|
1454
|
+
*/
|
|
1455
|
+
async getPreferredAppId() {
|
|
1456
|
+
try {
|
|
1457
|
+
return await appSettings.get(_AppLoaderService.PREFERRED_APP_KEY);
|
|
1458
|
+
} catch (error) {
|
|
1459
|
+
logger.debug(`Failed to get preferred app ID from settings: ${getErrorMessage(error)}`);
|
|
1460
|
+
return void 0;
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Set the preferred app ID and persist it to settings.
|
|
1465
|
+
*/
|
|
1466
|
+
async setPreferredAppId(appId) {
|
|
1467
|
+
if (!this.apps.has(appId)) {
|
|
1468
|
+
throw new Error(`App '${appId}' not found. Make sure it's registered.`);
|
|
1469
|
+
}
|
|
1470
|
+
try {
|
|
1471
|
+
await appSettings.set(_AppLoaderService.PREFERRED_APP_KEY, appId);
|
|
1472
|
+
this.defaultAppId = appId;
|
|
1473
|
+
logger.info(`Set preferred app to: ${appId}`);
|
|
1474
|
+
} catch (error) {
|
|
1475
|
+
logger.error(`Failed to persist preferred app ID: ${getErrorMessage(error)}`);
|
|
1476
|
+
throw error;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
/**
|
|
1480
|
+
* Select which app to load based on priority:
|
|
1481
|
+
* 1. appId URL parameter (?appId=...)
|
|
1482
|
+
* 2. App ID from current page URL path (/geospace)
|
|
1483
|
+
* 3. App ID extracted from app URL parameter (?app=...)
|
|
1484
|
+
* 4. App registered by extension
|
|
1485
|
+
* 5. Preferred app ID from settings
|
|
1486
|
+
* 6. Default app ID
|
|
1487
|
+
* 7. First registered app
|
|
1488
|
+
*/
|
|
1489
|
+
async selectAppToLoad(options) {
|
|
1490
|
+
const { appIdFromUrl, appIdFromPath, appIdFromAppUrl, appsBeforeExtension } = options;
|
|
1491
|
+
if (appIdFromUrl) {
|
|
1492
|
+
if (this.apps.has(appIdFromUrl)) {
|
|
1493
|
+
logger.info(`Loading app specified by URL parameter 'appId': ${appIdFromUrl}`);
|
|
1494
|
+
return appIdFromUrl;
|
|
1495
|
+
}
|
|
1496
|
+
logger.warn(`App ID '${appIdFromUrl}' from URL parameter not found`);
|
|
1497
|
+
}
|
|
1498
|
+
if (appIdFromPath) {
|
|
1499
|
+
if (this.apps.has(appIdFromPath)) {
|
|
1500
|
+
logger.info(`Loading app from URL path: ${appIdFromPath}`);
|
|
1501
|
+
return appIdFromPath;
|
|
1502
|
+
}
|
|
1503
|
+
logger.debug(`App ID '${appIdFromPath}' from URL path not found, continuing search`);
|
|
1504
|
+
}
|
|
1505
|
+
if (appIdFromAppUrl) {
|
|
1506
|
+
if (this.apps.has(appIdFromAppUrl)) {
|
|
1507
|
+
logger.info(`Loading app using ID extracted from app URL path: ${appIdFromAppUrl}`);
|
|
1508
|
+
return appIdFromAppUrl;
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
if (this.apps.size > appsBeforeExtension) {
|
|
1512
|
+
const newlyRegisteredApps = Array.from(this.apps.values()).slice(appsBeforeExtension);
|
|
1513
|
+
if (newlyRegisteredApps.length > 0) {
|
|
1514
|
+
const app = newlyRegisteredApps[0];
|
|
1515
|
+
logger.info(`Loading app registered by extension: ${app.name} (${app.id})`);
|
|
1516
|
+
return app.id;
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
const preferredAppId = await this.getPreferredAppId();
|
|
1520
|
+
if (preferredAppId && this.apps.has(preferredAppId)) {
|
|
1521
|
+
logger.info(`Loading preferred app from settings: ${preferredAppId}`);
|
|
1522
|
+
return preferredAppId;
|
|
1523
|
+
}
|
|
1524
|
+
if (this.defaultAppId) {
|
|
1525
|
+
if (this.apps.has(this.defaultAppId)) {
|
|
1526
|
+
return this.defaultAppId;
|
|
1527
|
+
}
|
|
1528
|
+
logger.warn(`Default app '${this.defaultAppId}' not found`);
|
|
1529
|
+
}
|
|
1530
|
+
const registeredApps = this.getRegisteredApps();
|
|
1531
|
+
if (registeredApps.length > 0) {
|
|
1532
|
+
const app = registeredApps[0];
|
|
1533
|
+
logger.info(`Loading first registered app: ${app.name} (${app.id})`);
|
|
1534
|
+
return app.id;
|
|
1535
|
+
}
|
|
1536
|
+
return void 0;
|
|
1537
|
+
}
|
|
1538
|
+
};
|
|
1539
|
+
_AppLoaderService.PREFERRED_APP_KEY = "preferredAppId";
|
|
1540
|
+
let AppLoaderService = _AppLoaderService;
|
|
1541
|
+
const appLoaderService = new AppLoaderService();
|
|
1542
|
+
rootContext.put("appLoaderService", appLoaderService);
|
|
1543
|
+
var __defProp$7 = Object.defineProperty;
|
|
1544
|
+
var __getOwnPropDesc$8 = Object.getOwnPropertyDescriptor;
|
|
1545
|
+
var __decorateClass$8 = (decorators, target, key, kind) => {
|
|
1546
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$8(target, key) : target;
|
|
1547
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
1548
|
+
if (decorator = decorators[i])
|
|
1549
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
1550
|
+
if (kind && result) __defProp$7(target, key, result);
|
|
1551
|
+
return result;
|
|
1552
|
+
};
|
|
1553
|
+
let KPromptDialogContent = class extends KDialogContent {
|
|
1554
|
+
constructor() {
|
|
1555
|
+
super(...arguments);
|
|
1556
|
+
this.message = "";
|
|
1557
|
+
this.defaultValue = "";
|
|
1558
|
+
this.markdown = false;
|
|
1559
|
+
this.inputValue = "";
|
|
1560
|
+
}
|
|
1561
|
+
async firstUpdated(changedProperties) {
|
|
1562
|
+
super.firstUpdated(changedProperties);
|
|
1563
|
+
this.inputValue = this.defaultValue;
|
|
1564
|
+
await this.updateComplete;
|
|
1565
|
+
const input = this.shadowRoot?.querySelector("wa-input");
|
|
1566
|
+
if (input) {
|
|
1567
|
+
const inputEl = input.shadowRoot?.querySelector("input");
|
|
1568
|
+
if (inputEl) {
|
|
1569
|
+
inputEl.focus();
|
|
1570
|
+
inputEl.select();
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
getResult() {
|
|
1575
|
+
return this.inputValue;
|
|
1576
|
+
}
|
|
1577
|
+
handleInput(e) {
|
|
1578
|
+
this.inputValue = e.target.value;
|
|
1579
|
+
}
|
|
1580
|
+
handleKeyDown(e) {
|
|
1581
|
+
if (e.key === "Enter") {
|
|
1582
|
+
e.preventDefault();
|
|
1583
|
+
this.dispatchEvent(new CustomEvent("dialog-ok", { bubbles: true, composed: true }));
|
|
1584
|
+
} else if (e.key === "Escape") {
|
|
1585
|
+
e.preventDefault();
|
|
1586
|
+
this.dispatchEvent(new CustomEvent("dialog-cancel", { bubbles: true, composed: true }));
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
render() {
|
|
1590
|
+
return html`
|
|
1591
|
+
${this.renderMessage(this.message, this.markdown)}
|
|
1592
|
+
<wa-input
|
|
1593
|
+
value="${this.inputValue}"
|
|
1594
|
+
@input=${this.handleInput}
|
|
1595
|
+
@keydown=${this.handleKeyDown}
|
|
1596
|
+
autofocus
|
|
1597
|
+
></wa-input>
|
|
1598
|
+
`;
|
|
1599
|
+
}
|
|
1600
|
+
};
|
|
1601
|
+
KPromptDialogContent.styles = [
|
|
1602
|
+
...KDialogContent.styles,
|
|
1603
|
+
css`
|
|
1604
|
+
wa-input {
|
|
1605
|
+
width: 100%;
|
|
1606
|
+
}
|
|
1607
|
+
`
|
|
1608
|
+
];
|
|
1609
|
+
__decorateClass$8([
|
|
1610
|
+
property({ type: String })
|
|
1611
|
+
], KPromptDialogContent.prototype, "message", 2);
|
|
1612
|
+
__decorateClass$8([
|
|
1613
|
+
property({ type: String, attribute: "default-value" })
|
|
1614
|
+
], KPromptDialogContent.prototype, "defaultValue", 2);
|
|
1615
|
+
__decorateClass$8([
|
|
1616
|
+
property({ type: Boolean })
|
|
1617
|
+
], KPromptDialogContent.prototype, "markdown", 2);
|
|
1618
|
+
__decorateClass$8([
|
|
1619
|
+
state()
|
|
1620
|
+
], KPromptDialogContent.prototype, "inputValue", 2);
|
|
1621
|
+
KPromptDialogContent = __decorateClass$8([
|
|
1622
|
+
customElement("k-prompt-dialog-content")
|
|
1623
|
+
], KPromptDialogContent);
|
|
1624
|
+
contributionRegistry.registerContribution(DIALOG_CONTRIBUTION_TARGET, {
|
|
1625
|
+
id: "prompt",
|
|
1626
|
+
label: "Input",
|
|
1627
|
+
buttons: [OK_BUTTON, CANCEL_BUTTON],
|
|
1628
|
+
component: (state2) => {
|
|
1629
|
+
if (!state2) {
|
|
1630
|
+
return html`<div>Error: No prompt dialog state</div>`;
|
|
1631
|
+
}
|
|
1632
|
+
return html`
|
|
1633
|
+
<k-prompt-dialog-content
|
|
1634
|
+
.message="${state2.message}"
|
|
1635
|
+
.defaultValue="${state2.defaultValue}"
|
|
1636
|
+
.markdown="${state2.markdown}"
|
|
1637
|
+
></k-prompt-dialog-content>
|
|
1638
|
+
`;
|
|
1639
|
+
},
|
|
1640
|
+
onButton: async (id, result, state2) => {
|
|
1641
|
+
if (!state2) {
|
|
1642
|
+
return true;
|
|
1643
|
+
}
|
|
1644
|
+
if (id === "ok") {
|
|
1645
|
+
state2.resolve(result || "");
|
|
1646
|
+
} else {
|
|
1647
|
+
state2.resolve(null);
|
|
1648
|
+
}
|
|
1649
|
+
return true;
|
|
1650
|
+
}
|
|
1651
|
+
});
|
|
1652
|
+
async function promptDialog(message, defaultValue = "", markdown = false) {
|
|
1653
|
+
return new Promise((resolve) => {
|
|
1654
|
+
dialogService.open("prompt", {
|
|
1655
|
+
message,
|
|
1656
|
+
defaultValue,
|
|
1657
|
+
markdown,
|
|
1658
|
+
resolve
|
|
1659
|
+
});
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
var __defProp$6 = Object.defineProperty;
|
|
1663
|
+
var __getOwnPropDesc$7 = Object.getOwnPropertyDescriptor;
|
|
1664
|
+
var __decorateClass$7 = (decorators, target, key, kind) => {
|
|
1665
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$7(target, key) : target;
|
|
1666
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
1667
|
+
if (decorator = decorators[i])
|
|
1668
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
1669
|
+
if (kind && result) __defProp$6(target, key, result);
|
|
1670
|
+
return result;
|
|
1671
|
+
};
|
|
1672
|
+
let KInfoDialogContent = class extends KDialogContent {
|
|
1673
|
+
constructor() {
|
|
1674
|
+
super(...arguments);
|
|
1675
|
+
this.message = "";
|
|
1676
|
+
this.markdown = false;
|
|
1677
|
+
}
|
|
1678
|
+
render() {
|
|
1679
|
+
return html`
|
|
1680
|
+
${this.renderMessage(this.message, this.markdown)}
|
|
1681
|
+
`;
|
|
1682
|
+
}
|
|
1683
|
+
};
|
|
1684
|
+
__decorateClass$7([
|
|
1685
|
+
property({ type: String })
|
|
1686
|
+
], KInfoDialogContent.prototype, "message", 2);
|
|
1687
|
+
__decorateClass$7([
|
|
1688
|
+
property({ type: Boolean })
|
|
1689
|
+
], KInfoDialogContent.prototype, "markdown", 2);
|
|
1690
|
+
KInfoDialogContent = __decorateClass$7([
|
|
1691
|
+
customElement("k-info-dialog-content")
|
|
1692
|
+
], KInfoDialogContent);
|
|
1693
|
+
contributionRegistry.registerContribution(DIALOG_CONTRIBUTION_TARGET, {
|
|
1694
|
+
id: "info",
|
|
1695
|
+
label: "Information",
|
|
1696
|
+
buttons: [OK_BUTTON],
|
|
1697
|
+
component: (state2) => {
|
|
1698
|
+
if (!state2) {
|
|
1699
|
+
return html`<div>Error: No info dialog state</div>`;
|
|
1700
|
+
}
|
|
1701
|
+
return html`
|
|
1702
|
+
<k-info-dialog-content
|
|
1703
|
+
.message="${state2.message}"
|
|
1704
|
+
.markdown="${state2.markdown}"
|
|
1705
|
+
></k-info-dialog-content>
|
|
1706
|
+
`;
|
|
1707
|
+
},
|
|
1708
|
+
onButton: async (id, result, state2) => {
|
|
1709
|
+
if (!state2) {
|
|
1710
|
+
return true;
|
|
1711
|
+
}
|
|
1712
|
+
if (state2.resolve) {
|
|
1713
|
+
state2.resolve();
|
|
1714
|
+
}
|
|
1715
|
+
return true;
|
|
1716
|
+
}
|
|
1717
|
+
});
|
|
1718
|
+
async function infoDialog(title, message, markdown = false) {
|
|
1719
|
+
return new Promise((resolve) => {
|
|
1720
|
+
dialogService.open("info", {
|
|
1721
|
+
title,
|
|
1722
|
+
message,
|
|
1723
|
+
markdown,
|
|
1724
|
+
resolve
|
|
1725
|
+
});
|
|
1726
|
+
});
|
|
1727
|
+
}
|
|
1728
|
+
var __defProp$5 = Object.defineProperty;
|
|
1729
|
+
var __getOwnPropDesc$6 = Object.getOwnPropertyDescriptor;
|
|
1730
|
+
var __decorateClass$6 = (decorators, target, key, kind) => {
|
|
1731
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$6(target, key) : target;
|
|
1732
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
1733
|
+
if (decorator = decorators[i])
|
|
1734
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
1735
|
+
if (kind && result) __defProp$5(target, key, result);
|
|
1736
|
+
return result;
|
|
1737
|
+
};
|
|
1738
|
+
let KConfirmDialogContent = class extends KDialogContent {
|
|
1739
|
+
constructor() {
|
|
1740
|
+
super(...arguments);
|
|
1741
|
+
this.message = "";
|
|
1742
|
+
this.markdown = false;
|
|
1743
|
+
}
|
|
1744
|
+
getResult() {
|
|
1745
|
+
return false;
|
|
1746
|
+
}
|
|
1747
|
+
render() {
|
|
1748
|
+
return html`
|
|
1749
|
+
${this.renderMessage(this.message, this.markdown)}
|
|
1750
|
+
`;
|
|
1751
|
+
}
|
|
1752
|
+
};
|
|
1753
|
+
__decorateClass$6([
|
|
1754
|
+
property({ type: String })
|
|
1755
|
+
], KConfirmDialogContent.prototype, "message", 2);
|
|
1756
|
+
__decorateClass$6([
|
|
1757
|
+
property({ type: Boolean })
|
|
1758
|
+
], KConfirmDialogContent.prototype, "markdown", 2);
|
|
1759
|
+
KConfirmDialogContent = __decorateClass$6([
|
|
1760
|
+
customElement("k-confirm-dialog-content")
|
|
1761
|
+
], KConfirmDialogContent);
|
|
1762
|
+
contributionRegistry.registerContribution(DIALOG_CONTRIBUTION_TARGET, {
|
|
1763
|
+
id: "confirm",
|
|
1764
|
+
label: "Confirm",
|
|
1765
|
+
buttons: [OK_BUTTON, CANCEL_BUTTON],
|
|
1766
|
+
component: (state2) => {
|
|
1767
|
+
if (!state2) {
|
|
1768
|
+
return html`<div>Error: No confirm dialog state</div>`;
|
|
1769
|
+
}
|
|
1770
|
+
return html`
|
|
1771
|
+
<k-confirm-dialog-content
|
|
1772
|
+
.message="${state2.message}"
|
|
1773
|
+
.markdown="${state2.markdown}"
|
|
1774
|
+
></k-confirm-dialog-content>
|
|
1775
|
+
`;
|
|
1776
|
+
},
|
|
1777
|
+
onButton: async (id, result, state2) => {
|
|
1778
|
+
if (!state2) {
|
|
1779
|
+
return true;
|
|
1780
|
+
}
|
|
1781
|
+
if (id === "ok") {
|
|
1782
|
+
state2.resolve(true);
|
|
1783
|
+
} else {
|
|
1784
|
+
state2.resolve(false);
|
|
1785
|
+
}
|
|
1786
|
+
return true;
|
|
1787
|
+
}
|
|
1788
|
+
});
|
|
1789
|
+
async function confirmDialog(message, markdown = false) {
|
|
1790
|
+
return new Promise((resolve) => {
|
|
1791
|
+
dialogService.open("confirm", {
|
|
1792
|
+
message,
|
|
1793
|
+
markdown,
|
|
1794
|
+
resolve
|
|
1795
|
+
});
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1798
|
+
var __defProp$4 = Object.defineProperty;
|
|
1799
|
+
var __getOwnPropDesc$5 = Object.getOwnPropertyDescriptor;
|
|
1800
|
+
var __decorateClass$5 = (decorators, target, key, kind) => {
|
|
1801
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$5(target, key) : target;
|
|
1802
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
1803
|
+
if (decorator = decorators[i])
|
|
1804
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
1805
|
+
if (kind && result) __defProp$4(target, key, result);
|
|
1806
|
+
return result;
|
|
1807
|
+
};
|
|
1808
|
+
let KNavigableInfoDialogContent = class extends KDialogContent {
|
|
1809
|
+
constructor() {
|
|
1810
|
+
super(...arguments);
|
|
1811
|
+
this.title = "";
|
|
1812
|
+
this.message = "";
|
|
1813
|
+
this.markdown = false;
|
|
1814
|
+
this.actions = [];
|
|
1815
|
+
this.currentTitle = "";
|
|
1816
|
+
this.currentMessage = "";
|
|
1817
|
+
this.dialogElement = null;
|
|
1818
|
+
}
|
|
1819
|
+
async firstUpdated(changedProperties) {
|
|
1820
|
+
super.firstUpdated(changedProperties);
|
|
1821
|
+
this.currentTitle = this.title;
|
|
1822
|
+
this.currentMessage = this.message;
|
|
1823
|
+
await this.updateComplete;
|
|
1824
|
+
const dialog = this.closest("wa-dialog");
|
|
1825
|
+
if (dialog) {
|
|
1826
|
+
this.dialogElement = dialog;
|
|
1827
|
+
this.updateDialogLabel();
|
|
1828
|
+
}
|
|
1829
|
+
const contentContainer = this.closest(".dialog-service-content");
|
|
1830
|
+
if (contentContainer) {
|
|
1831
|
+
const footer = contentContainer.parentElement?.querySelector(".dialog-service-footer");
|
|
1832
|
+
if (footer) {
|
|
1833
|
+
footer.style.display = "none";
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
updated(changedProperties) {
|
|
1838
|
+
super.updated(changedProperties);
|
|
1839
|
+
if (changedProperties.has("title")) {
|
|
1840
|
+
this.currentTitle = this.title;
|
|
1841
|
+
this.updateDialogLabel();
|
|
1842
|
+
}
|
|
1843
|
+
if (changedProperties.has("message")) {
|
|
1844
|
+
this.currentMessage = this.message;
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
updateDialogLabel() {
|
|
1848
|
+
if (this.dialogElement) {
|
|
1849
|
+
this.dialogElement.setAttribute("label", this.currentTitle);
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
updateDialog(newTitle, newMessage, newActions) {
|
|
1853
|
+
this.currentTitle = newTitle;
|
|
1854
|
+
this.currentMessage = newMessage;
|
|
1855
|
+
this.actions = [...newActions];
|
|
1856
|
+
this.updateDialogLabel();
|
|
1857
|
+
this.requestUpdate();
|
|
1858
|
+
}
|
|
1859
|
+
handleActionClick(action) {
|
|
1860
|
+
action.callback();
|
|
1861
|
+
}
|
|
1862
|
+
handleClose() {
|
|
1863
|
+
const dialog = this.closest("wa-dialog");
|
|
1864
|
+
if (dialog && this.resolveCallback) {
|
|
1865
|
+
this.resolveCallback();
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
render() {
|
|
1869
|
+
const leftActions = this.actions.filter((a) => a.label !== "Close");
|
|
1870
|
+
const rightActions = this.actions.filter((a) => a.label === "Close");
|
|
1871
|
+
return html`
|
|
1872
|
+
<div class="dialog-content">
|
|
1873
|
+
<wa-scroller class="dialog-scroller">
|
|
1874
|
+
${this.renderMessage(this.currentMessage, this.markdown)}
|
|
1875
|
+
</wa-scroller>
|
|
1876
|
+
|
|
1877
|
+
<div class="dialog-actions">
|
|
1878
|
+
<div class="dialog-actions-left">
|
|
1879
|
+
${leftActions.map((action) => html`
|
|
1880
|
+
<wa-button
|
|
1881
|
+
variant="${action.variant || "default"}"
|
|
1882
|
+
?disabled=${action.disabled}
|
|
1883
|
+
@click=${() => this.handleActionClick(action)}
|
|
1884
|
+
>
|
|
1885
|
+
${action.label}
|
|
1886
|
+
</wa-button>
|
|
1887
|
+
`)}
|
|
1888
|
+
</div>
|
|
1889
|
+
<div class="dialog-actions-right">
|
|
1890
|
+
${rightActions.map((action) => html`
|
|
1891
|
+
<wa-button
|
|
1892
|
+
variant="${action.variant || "primary"}"
|
|
1893
|
+
@click=${() => {
|
|
1894
|
+
this.handleActionClick(action);
|
|
1895
|
+
this.handleClose();
|
|
1896
|
+
}}
|
|
1897
|
+
>
|
|
1898
|
+
${action.label}
|
|
1899
|
+
</wa-button>
|
|
1900
|
+
`)}
|
|
1901
|
+
</div>
|
|
1902
|
+
</div>
|
|
1903
|
+
</div>
|
|
1904
|
+
`;
|
|
1905
|
+
}
|
|
1906
|
+
};
|
|
1907
|
+
KNavigableInfoDialogContent.styles = [
|
|
1908
|
+
...KDialogContent.styles,
|
|
1909
|
+
css`
|
|
1910
|
+
:host {
|
|
1911
|
+
display: block;
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
:host-context(.dialog-service-content) {
|
|
1915
|
+
padding: 0;
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
.dialog-content {
|
|
1919
|
+
display: flex;
|
|
1920
|
+
flex-direction: column;
|
|
1921
|
+
gap: 1rem;
|
|
1922
|
+
min-width: 400px;
|
|
1923
|
+
max-width: 600px;
|
|
1924
|
+
height: 500px;
|
|
1925
|
+
padding: 1rem;
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
.dialog-scroller {
|
|
1929
|
+
flex: 1;
|
|
1930
|
+
overflow-y: auto;
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
.dialog-actions {
|
|
1934
|
+
display: flex;
|
|
1935
|
+
gap: 0.5rem;
|
|
1936
|
+
justify-content: space-between;
|
|
1937
|
+
margin-top: 0.5rem;
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
.dialog-actions-left,
|
|
1941
|
+
.dialog-actions-right {
|
|
1942
|
+
display: flex;
|
|
1943
|
+
gap: 0.5rem;
|
|
1944
|
+
}
|
|
1945
|
+
`
|
|
1946
|
+
];
|
|
1947
|
+
__decorateClass$5([
|
|
1948
|
+
property({ type: String })
|
|
1949
|
+
], KNavigableInfoDialogContent.prototype, "title", 2);
|
|
1950
|
+
__decorateClass$5([
|
|
1951
|
+
property({ type: String })
|
|
1952
|
+
], KNavigableInfoDialogContent.prototype, "message", 2);
|
|
1953
|
+
__decorateClass$5([
|
|
1954
|
+
property({ type: Boolean })
|
|
1955
|
+
], KNavigableInfoDialogContent.prototype, "markdown", 2);
|
|
1956
|
+
__decorateClass$5([
|
|
1957
|
+
state()
|
|
1958
|
+
], KNavigableInfoDialogContent.prototype, "actions", 2);
|
|
1959
|
+
__decorateClass$5([
|
|
1960
|
+
state()
|
|
1961
|
+
], KNavigableInfoDialogContent.prototype, "currentTitle", 2);
|
|
1962
|
+
__decorateClass$5([
|
|
1963
|
+
state()
|
|
1964
|
+
], KNavigableInfoDialogContent.prototype, "currentMessage", 2);
|
|
1965
|
+
KNavigableInfoDialogContent = __decorateClass$5([
|
|
1966
|
+
customElement("k-navigable-info-dialog-content")
|
|
1967
|
+
], KNavigableInfoDialogContent);
|
|
1968
|
+
contributionRegistry.registerContribution(DIALOG_CONTRIBUTION_TARGET, {
|
|
1969
|
+
id: "navigable-info",
|
|
1970
|
+
label: "Information",
|
|
1971
|
+
buttons: [CLOSE_BUTTON],
|
|
1972
|
+
component: (state2) => {
|
|
1973
|
+
if (!state2) {
|
|
1974
|
+
return html`<div>Error: No navigable info dialog state</div>`;
|
|
1975
|
+
}
|
|
1976
|
+
const componentHtml = html`
|
|
1977
|
+
<k-navigable-info-dialog-content
|
|
1978
|
+
.title="${state2.title}"
|
|
1979
|
+
.message="${state2.message}"
|
|
1980
|
+
.markdown="${state2.markdown}"
|
|
1981
|
+
></k-navigable-info-dialog-content>
|
|
1982
|
+
`;
|
|
1983
|
+
(async () => {
|
|
1984
|
+
const element = document.querySelector("k-navigable-info-dialog-content");
|
|
1985
|
+
if (element) {
|
|
1986
|
+
await element.updateComplete;
|
|
1987
|
+
element.actions = state2.actions || [];
|
|
1988
|
+
element.resolveCallback = state2.resolve;
|
|
1989
|
+
if (state2.updateDialogRef) {
|
|
1990
|
+
state2.updateDialogRef.current = (newTitle, newMessage, newActions) => {
|
|
1991
|
+
element.updateDialog(newTitle, newMessage, newActions);
|
|
1992
|
+
};
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
})();
|
|
1996
|
+
return componentHtml;
|
|
1997
|
+
},
|
|
1998
|
+
onButton: async (id, result, state2) => {
|
|
1999
|
+
if (!state2) {
|
|
2000
|
+
return false;
|
|
2001
|
+
}
|
|
2002
|
+
if (id === "close" && state2.resolve) {
|
|
2003
|
+
state2.resolve();
|
|
2004
|
+
return true;
|
|
2005
|
+
}
|
|
2006
|
+
return false;
|
|
2007
|
+
}
|
|
2008
|
+
});
|
|
2009
|
+
async function navigableInfoDialog(title, message, actions, markdown = false) {
|
|
2010
|
+
return new Promise((resolve) => {
|
|
2011
|
+
const updateDialogRef = {};
|
|
2012
|
+
dialogService.open("navigable-info", {
|
|
2013
|
+
title,
|
|
2014
|
+
message,
|
|
2015
|
+
actions,
|
|
2016
|
+
markdown,
|
|
2017
|
+
resolve,
|
|
2018
|
+
updateDialogRef
|
|
2019
|
+
});
|
|
2020
|
+
const updateDialog = (newTitle, newMessage, newActions) => {
|
|
2021
|
+
if (updateDialogRef.current) {
|
|
2022
|
+
updateDialogRef.current(newTitle, newMessage, newActions);
|
|
2023
|
+
}
|
|
2024
|
+
};
|
|
2025
|
+
actions.updateDialog = updateDialog;
|
|
2026
|
+
});
|
|
2027
|
+
}
|
|
2028
|
+
var __getOwnPropDesc$4 = Object.getOwnPropertyDescriptor;
|
|
2029
|
+
var __decorateClass$4 = (decorators, target, key, kind) => {
|
|
2030
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$4(target, key) : target;
|
|
2031
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
2032
|
+
if (decorator = decorators[i])
|
|
2033
|
+
result = decorator(result) || result;
|
|
2034
|
+
return result;
|
|
2035
|
+
};
|
|
2036
|
+
let KApp = class extends KElement {
|
|
2037
|
+
render() {
|
|
2038
|
+
return html`
|
|
2039
|
+
<slot></slot>
|
|
2040
|
+
`;
|
|
2041
|
+
}
|
|
2042
|
+
};
|
|
2043
|
+
KApp.styles = css`
|
|
2044
|
+
:host {
|
|
2045
|
+
display: flex;
|
|
2046
|
+
flex-direction: column;
|
|
2047
|
+
flex: 1;
|
|
2048
|
+
min-height: 0;
|
|
2049
|
+
width: 100%;
|
|
2050
|
+
box-sizing: border-box;
|
|
2051
|
+
}
|
|
2052
|
+
`;
|
|
2053
|
+
KApp = __decorateClass$4([
|
|
2054
|
+
customElement("k-app")
|
|
2055
|
+
], KApp);
|
|
2056
|
+
var __defProp$3 = Object.defineProperty;
|
|
2057
|
+
var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor;
|
|
2058
|
+
var __decorateClass$3 = (decorators, target, key, kind) => {
|
|
2059
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$3(target, key) : target;
|
|
2060
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
2061
|
+
if (decorator = decorators[i])
|
|
2062
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
2063
|
+
if (kind && result) __defProp$3(target, key, result);
|
|
2064
|
+
return result;
|
|
2065
|
+
};
|
|
2066
|
+
let KToolbar = class extends KElement {
|
|
2067
|
+
constructor() {
|
|
2068
|
+
super(...arguments);
|
|
2069
|
+
this.position = "start";
|
|
2070
|
+
this.isEditor = false;
|
|
2071
|
+
this.partToolbarContent = void 0;
|
|
2072
|
+
this.partToolbarRenderer = void 0;
|
|
2073
|
+
this.contributions = [];
|
|
2074
|
+
}
|
|
2075
|
+
doBeforeUI() {
|
|
2076
|
+
const id = this.getAttribute("id");
|
|
2077
|
+
if (id) {
|
|
2078
|
+
this.loadContributions(id);
|
|
2079
|
+
}
|
|
2080
|
+
subscribe(TOPIC_CONTRIBUTEIONS_CHANGED, (event) => {
|
|
2081
|
+
if (!id) return;
|
|
2082
|
+
const shouldReload = this.matchesTarget(id, event.target);
|
|
2083
|
+
if (shouldReload) {
|
|
2084
|
+
this.loadContributions(id);
|
|
2085
|
+
this.requestUpdate();
|
|
2086
|
+
}
|
|
2087
|
+
});
|
|
2088
|
+
}
|
|
2089
|
+
matchesTarget(id, target) {
|
|
2090
|
+
if (target === id) return true;
|
|
2091
|
+
if (!id.includes(":")) return false;
|
|
2092
|
+
const [prefix] = id.split(":");
|
|
2093
|
+
if (target === `${prefix}:*`) return true;
|
|
2094
|
+
const targetParts = target.split(":");
|
|
2095
|
+
if (targetParts.length === 2) {
|
|
2096
|
+
const categoryToken = targetParts[1];
|
|
2097
|
+
if (categoryToken === "system.editors" || categoryToken === ".system.editors") {
|
|
2098
|
+
return this.isEditor && id.startsWith(`${prefix}:`);
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
return false;
|
|
2102
|
+
}
|
|
2103
|
+
loadContributions(id) {
|
|
2104
|
+
const specific = contributionRegistry.getContributions(id);
|
|
2105
|
+
if (!id.includes(":")) {
|
|
2106
|
+
this.contributions = specific;
|
|
2107
|
+
return;
|
|
2108
|
+
}
|
|
2109
|
+
const [prefix] = id.split(":");
|
|
2110
|
+
const wildcardId = `${prefix}:*`;
|
|
2111
|
+
const wildcard = contributionRegistry.getContributions(wildcardId);
|
|
2112
|
+
const categoryMatches = [];
|
|
2113
|
+
if (this.isEditor) {
|
|
2114
|
+
const allCategories = ["system.editors", ".system.editors"];
|
|
2115
|
+
for (const category of allCategories) {
|
|
2116
|
+
const categoryId = `${prefix}:${category}`;
|
|
2117
|
+
const matches = contributionRegistry.getContributions(categoryId);
|
|
2118
|
+
categoryMatches.push(...matches);
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
this.contributions = [...wildcard, ...categoryMatches, ...specific];
|
|
2122
|
+
}
|
|
2123
|
+
contributionCreator(contribution) {
|
|
2124
|
+
if ("command" in contribution) {
|
|
2125
|
+
const commandContribution = contribution;
|
|
2126
|
+
const showLabel = !!commandContribution.showLabel;
|
|
2127
|
+
return html`
|
|
2128
|
+
<wa-button @click=${() => this.executeCommand(commandContribution.command, commandContribution.params || {})}
|
|
2129
|
+
title=${commandContribution.label}
|
|
2130
|
+
?disabled="${commandContribution.disabled?.get()}"
|
|
2131
|
+
appearance="plain" size="small">
|
|
2132
|
+
<wa-icon name=${commandContribution.icon} label="${commandContribution.label}"></wa-icon>
|
|
2133
|
+
${showLabel ? commandContribution.label : ""}
|
|
2134
|
+
</wa-button>
|
|
2135
|
+
`;
|
|
2136
|
+
} else if ("html" in contribution) {
|
|
2137
|
+
const contents = contribution.html;
|
|
2138
|
+
if (contents instanceof Function) {
|
|
2139
|
+
return contents();
|
|
2140
|
+
}
|
|
2141
|
+
return unsafeHTML(contents);
|
|
2142
|
+
} else {
|
|
2143
|
+
return html`<span>unknown contribution type: ${typeof contribution}</span>`;
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
render() {
|
|
2147
|
+
const partContent = this.partToolbarRenderer ? this.partToolbarRenderer() : this.partToolbarContent ? this.partToolbarContent : "";
|
|
2148
|
+
return html`
|
|
2149
|
+
<div class="toolbar-items" style=${styleMap({ "justify-content": this.position })}>
|
|
2150
|
+
<slot name="start">
|
|
2151
|
+
${this.contributions.filter((c) => c.slot === "start").map(this.contributionCreator.bind(this))}
|
|
2152
|
+
</slot>
|
|
2153
|
+
${partContent}
|
|
2154
|
+
${this.contributions.filter((c) => c.slot === void 0).map(this.contributionCreator.bind(this))}
|
|
2155
|
+
<slot>
|
|
2156
|
+
</slot>
|
|
2157
|
+
<slot name="end">
|
|
2158
|
+
${this.contributions.filter((c) => c.slot === "end").map(this.contributionCreator.bind(this))}
|
|
2159
|
+
</slot>
|
|
2160
|
+
</div>
|
|
2161
|
+
`;
|
|
2162
|
+
}
|
|
2163
|
+
};
|
|
2164
|
+
KToolbar.styles = css`
|
|
2165
|
+
:host {
|
|
2166
|
+
display: flex;
|
|
2167
|
+
flex-direction: row;
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
.toolbar-items {
|
|
2171
|
+
display: flex;
|
|
2172
|
+
flex: 1;
|
|
2173
|
+
}
|
|
2174
|
+
`;
|
|
2175
|
+
__decorateClass$3([
|
|
2176
|
+
property()
|
|
2177
|
+
], KToolbar.prototype, "position", 2);
|
|
2178
|
+
__decorateClass$3([
|
|
2179
|
+
property({ type: Boolean, attribute: "is-editor" })
|
|
2180
|
+
], KToolbar.prototype, "isEditor", 2);
|
|
2181
|
+
__decorateClass$3([
|
|
2182
|
+
property({ attribute: false })
|
|
2183
|
+
], KToolbar.prototype, "partToolbarContent", 2);
|
|
2184
|
+
__decorateClass$3([
|
|
2185
|
+
property({ attribute: false })
|
|
2186
|
+
], KToolbar.prototype, "partToolbarRenderer", 2);
|
|
2187
|
+
__decorateClass$3([
|
|
2188
|
+
state()
|
|
2189
|
+
], KToolbar.prototype, "contributions", 2);
|
|
2190
|
+
KToolbar = __decorateClass$3([
|
|
2191
|
+
customElement("k-toolbar")
|
|
2192
|
+
], KToolbar);
|
|
2193
|
+
var __defProp$2 = Object.defineProperty;
|
|
2194
|
+
var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
|
|
2195
|
+
var __decorateClass$2 = (decorators, target, key, kind) => {
|
|
2196
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
|
|
2197
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
2198
|
+
if (decorator = decorators[i])
|
|
2199
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
2200
|
+
if (kind && result) __defProp$2(target, key, result);
|
|
2201
|
+
return result;
|
|
2202
|
+
};
|
|
2203
|
+
let KContextMenu = class extends KElement {
|
|
2204
|
+
constructor() {
|
|
2205
|
+
super(...arguments);
|
|
2206
|
+
this.isEditor = false;
|
|
2207
|
+
this.partContextMenuRenderer = void 0;
|
|
2208
|
+
this.contributions = [];
|
|
2209
|
+
this.isOpen = false;
|
|
2210
|
+
this.position = { x: 0, y: 0 };
|
|
2211
|
+
this.anchorRef = createRef();
|
|
2212
|
+
this.dropdownRef = createRef();
|
|
2213
|
+
}
|
|
2214
|
+
doBeforeUI() {
|
|
2215
|
+
const id = this.getAttribute("id");
|
|
2216
|
+
if (id) {
|
|
2217
|
+
this.loadContributions(id);
|
|
2218
|
+
}
|
|
2219
|
+
subscribe(TOPIC_CONTRIBUTEIONS_CHANGED, (event) => {
|
|
2220
|
+
if (!id) return;
|
|
2221
|
+
const shouldReload = this.matchesTarget(id, event.target);
|
|
2222
|
+
if (shouldReload) {
|
|
2223
|
+
this.loadContributions(id);
|
|
2224
|
+
this.requestUpdate();
|
|
2225
|
+
}
|
|
2226
|
+
});
|
|
2227
|
+
}
|
|
2228
|
+
matchesTarget(id, target) {
|
|
2229
|
+
if (target === id) return true;
|
|
2230
|
+
if (!id.includes(":")) return false;
|
|
2231
|
+
const [prefix] = id.split(":");
|
|
2232
|
+
if (target === `${prefix}:*`) return true;
|
|
2233
|
+
const targetParts = target.split(":");
|
|
2234
|
+
if (targetParts.length === 2) {
|
|
2235
|
+
const categoryToken = targetParts[1];
|
|
2236
|
+
if (categoryToken === "system.editors" || categoryToken === ".system.editors") {
|
|
2237
|
+
return this.isEditor && id.startsWith(`${prefix}:`);
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
return false;
|
|
2241
|
+
}
|
|
2242
|
+
loadContributions(id) {
|
|
2243
|
+
const specific = contributionRegistry.getContributions(id);
|
|
2244
|
+
if (!id.includes(":")) {
|
|
2245
|
+
this.contributions = specific;
|
|
2246
|
+
return;
|
|
2247
|
+
}
|
|
2248
|
+
const [prefix] = id.split(":");
|
|
2249
|
+
const wildcardId = `${prefix}:*`;
|
|
2250
|
+
const wildcard = contributionRegistry.getContributions(wildcardId);
|
|
2251
|
+
const categoryMatches = [];
|
|
2252
|
+
if (this.isEditor) {
|
|
2253
|
+
const allCategories = ["system.editors", ".system.editors"];
|
|
2254
|
+
for (const category of allCategories) {
|
|
2255
|
+
const categoryId = `${prefix}:${category}`;
|
|
2256
|
+
const matches = contributionRegistry.getContributions(categoryId);
|
|
2257
|
+
categoryMatches.push(...matches);
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
this.contributions = [...wildcard, ...categoryMatches, ...specific];
|
|
2261
|
+
}
|
|
2262
|
+
/**
|
|
2263
|
+
* Gets the element at the given point, traversing shadow DOM boundaries recursively.
|
|
2264
|
+
* This is necessary because elementFromPoint() doesn't penetrate shadow roots.
|
|
2265
|
+
*/
|
|
2266
|
+
getElementFromPoint(x, y) {
|
|
2267
|
+
let element = document.elementFromPoint(x, y);
|
|
2268
|
+
if (!element) return null;
|
|
2269
|
+
while (element) {
|
|
2270
|
+
const shadowRoot = element.shadowRoot;
|
|
2271
|
+
if (shadowRoot) {
|
|
2272
|
+
const shadowElement = shadowRoot.elementFromPoint(x, y);
|
|
2273
|
+
if (shadowElement && shadowElement !== element) {
|
|
2274
|
+
element = shadowElement;
|
|
2275
|
+
continue;
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
break;
|
|
2279
|
+
}
|
|
2280
|
+
return element;
|
|
2281
|
+
}
|
|
2282
|
+
/**
|
|
2283
|
+
* Triggers a click on the element under the cursor to update selection before showing context menu.
|
|
2284
|
+
*/
|
|
2285
|
+
triggerClickUnderCursor(mouseEvent) {
|
|
2286
|
+
const elementUnderCursor = this.getElementFromPoint(mouseEvent.clientX, mouseEvent.clientY);
|
|
2287
|
+
if (elementUnderCursor) {
|
|
2288
|
+
const clickEvent = new MouseEvent("click", {
|
|
2289
|
+
bubbles: true,
|
|
2290
|
+
cancelable: true,
|
|
2291
|
+
view: window,
|
|
2292
|
+
clientX: mouseEvent.clientX,
|
|
2293
|
+
clientY: mouseEvent.clientY,
|
|
2294
|
+
screenX: mouseEvent.screenX,
|
|
2295
|
+
screenY: mouseEvent.screenY,
|
|
2296
|
+
button: 0,
|
|
2297
|
+
buttons: 0,
|
|
2298
|
+
detail: 1,
|
|
2299
|
+
which: 1
|
|
2300
|
+
});
|
|
2301
|
+
elementUnderCursor.dispatchEvent(clickEvent);
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
show(position, mouseEvent) {
|
|
2305
|
+
if (mouseEvent) {
|
|
2306
|
+
this.triggerClickUnderCursor(mouseEvent);
|
|
2307
|
+
}
|
|
2308
|
+
this.position = position;
|
|
2309
|
+
this.isOpen = true;
|
|
2310
|
+
}
|
|
2311
|
+
onClose() {
|
|
2312
|
+
this.isOpen = false;
|
|
2313
|
+
}
|
|
2314
|
+
handleCommandClick(commandId, params) {
|
|
2315
|
+
return async () => {
|
|
2316
|
+
this.executeCommand(commandId, params || {});
|
|
2317
|
+
};
|
|
2318
|
+
}
|
|
2319
|
+
renderContribution(contribution) {
|
|
2320
|
+
if ("command" in contribution) {
|
|
2321
|
+
const commandContribution = contribution;
|
|
2322
|
+
return html`
|
|
2323
|
+
<wa-dropdown-item
|
|
2324
|
+
@click=${this.handleCommandClick(commandContribution.command, commandContribution.params)}
|
|
2325
|
+
?disabled="${commandContribution.disabled?.get()}">
|
|
2326
|
+
${commandContribution.icon ? html`<wa-icon slot="icon" name=${commandContribution.icon}></wa-icon>` : ""}
|
|
2327
|
+
${commandContribution.label}
|
|
2328
|
+
</wa-dropdown-item>
|
|
2329
|
+
`;
|
|
2330
|
+
} else if ("html" in contribution) {
|
|
2331
|
+
const contents = contribution.html;
|
|
2332
|
+
if (contents instanceof Function) {
|
|
2333
|
+
return contents();
|
|
2334
|
+
}
|
|
2335
|
+
return unsafeHTML(contents);
|
|
2336
|
+
}
|
|
2337
|
+
return nothing;
|
|
2338
|
+
}
|
|
2339
|
+
render() {
|
|
2340
|
+
if (!this.isOpen) return nothing;
|
|
2341
|
+
const partContent = this.partContextMenuRenderer ? this.partContextMenuRenderer() : nothing;
|
|
2342
|
+
return html`
|
|
2343
|
+
<wa-dropdown
|
|
2344
|
+
${ref(this.dropdownRef)}
|
|
2345
|
+
?open=${this.isOpen}
|
|
2346
|
+
@wa-after-hide=${this.onClose}
|
|
2347
|
+
placement="bottom-start"
|
|
2348
|
+
distance="0">
|
|
2349
|
+
|
|
2350
|
+
<div
|
|
2351
|
+
slot="trigger"
|
|
2352
|
+
${ref(this.anchorRef)}
|
|
2353
|
+
style="position: fixed;
|
|
2354
|
+
left: ${this.position.x}px;
|
|
2355
|
+
top: ${this.position.y}px;
|
|
2356
|
+
width: 1px;
|
|
2357
|
+
height: 1px;
|
|
2358
|
+
pointer-events: none;">
|
|
2359
|
+
</div>
|
|
2360
|
+
|
|
2361
|
+
${partContent}
|
|
2362
|
+
${this.contributions.map((c) => this.renderContribution(c))}
|
|
2363
|
+
</wa-dropdown>
|
|
2364
|
+
`;
|
|
2365
|
+
}
|
|
2366
|
+
};
|
|
2367
|
+
KContextMenu.styles = css`
|
|
2368
|
+
:host {
|
|
2369
|
+
position: fixed;
|
|
2370
|
+
top: 0;
|
|
2371
|
+
left: 0;
|
|
2372
|
+
width: 0;
|
|
2373
|
+
height: 0;
|
|
2374
|
+
pointer-events: none;
|
|
2375
|
+
z-index: 10000;
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
wa-dropdown {
|
|
2379
|
+
pointer-events: auto;
|
|
2380
|
+
min-width: 200px;
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
wa-dropdown::part(panel) {
|
|
2384
|
+
min-width: 200px;
|
|
2385
|
+
}
|
|
2386
|
+
`;
|
|
2387
|
+
__decorateClass$2([
|
|
2388
|
+
property({ type: Boolean, attribute: "is-editor" })
|
|
2389
|
+
], KContextMenu.prototype, "isEditor", 2);
|
|
2390
|
+
__decorateClass$2([
|
|
2391
|
+
property({ attribute: false })
|
|
2392
|
+
], KContextMenu.prototype, "partContextMenuRenderer", 2);
|
|
2393
|
+
__decorateClass$2([
|
|
2394
|
+
state()
|
|
2395
|
+
], KContextMenu.prototype, "contributions", 2);
|
|
2396
|
+
__decorateClass$2([
|
|
2397
|
+
state()
|
|
2398
|
+
], KContextMenu.prototype, "isOpen", 2);
|
|
2399
|
+
__decorateClass$2([
|
|
2400
|
+
state()
|
|
2401
|
+
], KContextMenu.prototype, "position", 2);
|
|
2402
|
+
KContextMenu = __decorateClass$2([
|
|
2403
|
+
customElement("k-contextmenu")
|
|
2404
|
+
], KContextMenu);
|
|
2405
|
+
var __defProp$1 = Object.defineProperty;
|
|
2406
|
+
var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
|
|
2407
|
+
var __decorateClass$1 = (decorators, target, key, kind) => {
|
|
2408
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
|
|
2409
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
2410
|
+
if (decorator = decorators[i])
|
|
2411
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
2412
|
+
if (kind && result) __defProp$1(target, key, result);
|
|
2413
|
+
return result;
|
|
2414
|
+
};
|
|
2415
|
+
let KTabs = class extends KContainer {
|
|
2416
|
+
constructor() {
|
|
2417
|
+
super(...arguments);
|
|
2418
|
+
this.contributions = [];
|
|
2419
|
+
this.tabGroup = createRef();
|
|
2420
|
+
this.containerId = null;
|
|
2421
|
+
this.resizeObservers = /* @__PURE__ */ new WeakMap();
|
|
2422
|
+
}
|
|
2423
|
+
// ============= Lifecycle Methods =============
|
|
2424
|
+
doBeforeUI() {
|
|
2425
|
+
this.containerId = this.getAttribute("id");
|
|
2426
|
+
if (!this.containerId) {
|
|
2427
|
+
throw new Error("k-tabs requires an 'id' attribute to function");
|
|
2428
|
+
}
|
|
2429
|
+
this.loadAndResolveContributions();
|
|
2430
|
+
}
|
|
2431
|
+
doInitUI() {
|
|
2432
|
+
this.updateComplete.then(() => {
|
|
2433
|
+
this.activateNextAvailableTab();
|
|
2434
|
+
if (!this.tabGroup.value) return;
|
|
2435
|
+
this.tabGroup.value.addEventListener("wa-tab-show", (event) => {
|
|
2436
|
+
const tabPanel = this.getTabPanel(event.detail.name);
|
|
2437
|
+
if (tabPanel) {
|
|
2438
|
+
this.updateToolbarFromComponent(tabPanel);
|
|
2439
|
+
requestAnimationFrame(() => {
|
|
2440
|
+
this.updateToolbarHeightVariable(tabPanel);
|
|
2441
|
+
this.setupToolbarResizeObserver(tabPanel);
|
|
2442
|
+
});
|
|
2443
|
+
this.dispatchEvent(new CustomEvent("tab-shown", { detail: tabPanel }));
|
|
2444
|
+
}
|
|
2445
|
+
});
|
|
2446
|
+
this.tabGroup.value.addEventListener("part-toolbar-changed", (event) => {
|
|
2447
|
+
const component = event.target;
|
|
2448
|
+
const tabPanel = component.closest("wa-tab-panel");
|
|
2449
|
+
if (tabPanel) {
|
|
2450
|
+
this.updateToolbarFromComponent(tabPanel);
|
|
2451
|
+
requestAnimationFrame(() => this.updateToolbarHeightVariable(tabPanel));
|
|
2452
|
+
}
|
|
2453
|
+
});
|
|
2454
|
+
this.tabGroup.value.addEventListener("part-contextmenu-changed", (event) => {
|
|
2455
|
+
const component = event.target;
|
|
2456
|
+
const tabPanel = component.closest("wa-tab-panel");
|
|
2457
|
+
if (tabPanel) {
|
|
2458
|
+
this.updateContextMenuFromComponent(tabPanel);
|
|
2459
|
+
}
|
|
2460
|
+
});
|
|
2461
|
+
this.tabGroup.value.addEventListener("click", (event) => {
|
|
2462
|
+
const target = event.target;
|
|
2463
|
+
const tab = target.closest("wa-tab");
|
|
2464
|
+
if (tab) {
|
|
2465
|
+
const panelName = tab.getAttribute("panel");
|
|
2466
|
+
if (panelName) {
|
|
2467
|
+
const tabPanel2 = this.getTabPanel(panelName);
|
|
2468
|
+
if (tabPanel2) {
|
|
2469
|
+
const contentDiv2 = tabPanel2.querySelector(".tab-content");
|
|
2470
|
+
if (contentDiv2 && contentDiv2.firstElementChild) {
|
|
2471
|
+
const part = contentDiv2.firstElementChild;
|
|
2472
|
+
if (part instanceof KPart) {
|
|
2473
|
+
activePartSignal.set(part);
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
return;
|
|
2479
|
+
}
|
|
2480
|
+
const scroller = target.closest("wa-scroller.tab-content");
|
|
2481
|
+
if (!scroller) return;
|
|
2482
|
+
const tabPanel = scroller.closest("wa-tab-panel");
|
|
2483
|
+
if (!tabPanel) return;
|
|
2484
|
+
const contentDiv = tabPanel.querySelector(".tab-content");
|
|
2485
|
+
if (contentDiv && contentDiv.firstElementChild) {
|
|
2486
|
+
const part = contentDiv.firstElementChild;
|
|
2487
|
+
if (part instanceof KPart) {
|
|
2488
|
+
activePartSignal.set(part);
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
});
|
|
2492
|
+
this.tabGroup.value.addEventListener("contextmenu", (event) => {
|
|
2493
|
+
const mouseEvent = event;
|
|
2494
|
+
const scroller = mouseEvent.target.closest("wa-scroller.tab-content");
|
|
2495
|
+
if (!scroller) return;
|
|
2496
|
+
mouseEvent.preventDefault();
|
|
2497
|
+
const tabPanel = scroller.closest("wa-tab-panel");
|
|
2498
|
+
if (!tabPanel) return;
|
|
2499
|
+
requestAnimationFrame(() => {
|
|
2500
|
+
this.updateContextMenuFromComponent(tabPanel);
|
|
2501
|
+
const contextMenu = tabPanel.querySelector("k-contextmenu");
|
|
2502
|
+
if (contextMenu) {
|
|
2503
|
+
contextMenu.show({ x: mouseEvent.clientX, y: mouseEvent.clientY }, mouseEvent);
|
|
2504
|
+
}
|
|
2505
|
+
});
|
|
2506
|
+
});
|
|
2507
|
+
});
|
|
2508
|
+
subscribe(TOPIC_CONTRIBUTEIONS_CHANGED, (event) => {
|
|
2509
|
+
if (!this.containerId || event.target !== this.containerId) return;
|
|
2510
|
+
this.loadAndResolveContributions();
|
|
2511
|
+
this.requestUpdate();
|
|
2512
|
+
this.updateComplete.then(() => {
|
|
2513
|
+
this.activateNextAvailableTab();
|
|
2514
|
+
});
|
|
2515
|
+
});
|
|
2516
|
+
}
|
|
2517
|
+
updated(changedProperties) {
|
|
2518
|
+
super.updated(changedProperties);
|
|
2519
|
+
if (changedProperties.has("contributions")) {
|
|
2520
|
+
const isEditorArea = this.containerId === EDITOR_AREA_MAIN;
|
|
2521
|
+
this.contributions.forEach((contribution) => {
|
|
2522
|
+
const tabPanel = this.getTabPanel(contribution.name);
|
|
2523
|
+
if (!tabPanel) return;
|
|
2524
|
+
const contentDiv = tabPanel.querySelector(".tab-content");
|
|
2525
|
+
if (contentDiv && contentDiv.firstElementChild) {
|
|
2526
|
+
const part = contentDiv.firstElementChild;
|
|
2527
|
+
if (part instanceof KPart) {
|
|
2528
|
+
part.tabContribution = contribution;
|
|
2529
|
+
part.isEditor = isEditorArea;
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
requestAnimationFrame(() => this.updateToolbarHeightVariable(tabPanel));
|
|
2533
|
+
});
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
// ============= Public API Methods =============
|
|
2537
|
+
has(key) {
|
|
2538
|
+
if (!this.tabGroup.value) return false;
|
|
2539
|
+
return !!this.getTabPanel(key);
|
|
2540
|
+
}
|
|
2541
|
+
activate(key) {
|
|
2542
|
+
if (!this.tabGroup.value) return;
|
|
2543
|
+
this.tabGroup.value.setAttribute("active", key);
|
|
2544
|
+
}
|
|
2545
|
+
getActiveEditor() {
|
|
2546
|
+
if (!this.tabGroup.value) return null;
|
|
2547
|
+
return this.tabGroup.value.getAttribute("active");
|
|
2548
|
+
}
|
|
2549
|
+
open(contribution) {
|
|
2550
|
+
const existing = this.contributions.find((c) => c.name === contribution.name);
|
|
2551
|
+
if (existing) {
|
|
2552
|
+
this.activate(contribution.name);
|
|
2553
|
+
return;
|
|
2554
|
+
}
|
|
2555
|
+
this.contributions.push(contribution);
|
|
2556
|
+
this.requestUpdate();
|
|
2557
|
+
this.updateComplete.then(() => {
|
|
2558
|
+
this.activate(contribution.name);
|
|
2559
|
+
const tabPanel = this.getTabPanel(contribution.name);
|
|
2560
|
+
if (tabPanel) {
|
|
2561
|
+
const contentDiv = tabPanel.querySelector(".tab-content");
|
|
2562
|
+
if (contentDiv && contentDiv.firstElementChild) {
|
|
2563
|
+
const part = contentDiv.firstElementChild;
|
|
2564
|
+
if (part instanceof KPart) {
|
|
2565
|
+
part.tabContribution = contribution;
|
|
2566
|
+
part.isEditor = this.containerId === EDITOR_AREA_MAIN;
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
requestAnimationFrame(() => {
|
|
2570
|
+
this.updateToolbarFromComponent(tabPanel);
|
|
2571
|
+
this.updateToolbarHeightVariable(tabPanel);
|
|
2572
|
+
this.setupToolbarResizeObserver(tabPanel);
|
|
2573
|
+
});
|
|
2574
|
+
}
|
|
2575
|
+
});
|
|
2576
|
+
}
|
|
2577
|
+
handleTabAuxClick(event, contribution) {
|
|
2578
|
+
if (event.button === MouseButton.MIDDLE && contribution.closable) {
|
|
2579
|
+
this.closeTab(event, contribution.name);
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
async closeTab(event, tabName) {
|
|
2583
|
+
event.stopPropagation();
|
|
2584
|
+
if (this.isDirty(tabName) && !await confirmDialog("Unsaved changes will be lost: Do you really want to close?")) {
|
|
2585
|
+
return;
|
|
2586
|
+
}
|
|
2587
|
+
const tabPanel = this.getTabPanel(tabName);
|
|
2588
|
+
if (!tabPanel) return;
|
|
2589
|
+
const contribution = this.contributions.find((c) => c.name === tabName);
|
|
2590
|
+
if (!contribution) return;
|
|
2591
|
+
this.cleanupTabInstance(tabPanel);
|
|
2592
|
+
const index = this.contributions.indexOf(contribution);
|
|
2593
|
+
if (index > -1) {
|
|
2594
|
+
this.contributions.splice(index, 1);
|
|
2595
|
+
}
|
|
2596
|
+
this.dispatchEvent(new CustomEvent("tab-closed", { detail: tabPanel }));
|
|
2597
|
+
this.requestUpdate();
|
|
2598
|
+
this.updateComplete.then(() => {
|
|
2599
|
+
this.activateNextAvailableTab();
|
|
2600
|
+
});
|
|
2601
|
+
}
|
|
2602
|
+
markDirty(name, dirty) {
|
|
2603
|
+
const tab = this.getTab(name);
|
|
2604
|
+
tab.classList.toggle("part-dirty", dirty);
|
|
2605
|
+
}
|
|
2606
|
+
isDirty(name) {
|
|
2607
|
+
const tab = this.getTab(name);
|
|
2608
|
+
return tab.classList.contains("part-dirty");
|
|
2609
|
+
}
|
|
2610
|
+
// ============= Private Helper Methods =============
|
|
2611
|
+
/**
|
|
2612
|
+
* Loads tab contributions from the registry.
|
|
2613
|
+
*/
|
|
2614
|
+
loadAndResolveContributions() {
|
|
2615
|
+
this.contributions = contributionRegistry.getContributions(this.containerId);
|
|
2616
|
+
this.requestUpdate();
|
|
2617
|
+
}
|
|
2618
|
+
/**
|
|
2619
|
+
* Cleans up a tab instance when the tab is closed.
|
|
2620
|
+
*
|
|
2621
|
+
* Cleanup Process:
|
|
2622
|
+
* 1. Disconnect ResizeObserver if one exists
|
|
2623
|
+
* 2. Call component's close() method if available (disposes resources)
|
|
2624
|
+
* 3. DOM element is removed by caller (closeTab method)
|
|
2625
|
+
*/
|
|
2626
|
+
cleanupTabInstance(tabPanel) {
|
|
2627
|
+
const observer = this.resizeObservers.get(tabPanel);
|
|
2628
|
+
if (observer) {
|
|
2629
|
+
observer.disconnect();
|
|
2630
|
+
this.resizeObservers.delete(tabPanel);
|
|
2631
|
+
}
|
|
2632
|
+
const contentDiv = tabPanel.querySelector(".tab-content");
|
|
2633
|
+
if (contentDiv && contentDiv.firstElementChild) {
|
|
2634
|
+
const component = contentDiv.firstElementChild;
|
|
2635
|
+
if ("close" in component && typeof component.close === "function") {
|
|
2636
|
+
component.close();
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
activateNextAvailableTab() {
|
|
2641
|
+
if (!this.tabGroup.value) return;
|
|
2642
|
+
const allRemainingTabs = this.tabGroup.value.querySelectorAll("wa-tab");
|
|
2643
|
+
if (allRemainingTabs.length > 0) {
|
|
2644
|
+
const newActive = allRemainingTabs.item(0).getAttribute("panel");
|
|
2645
|
+
if (newActive) {
|
|
2646
|
+
this.tabGroup.value.setAttribute("active", newActive);
|
|
2647
|
+
}
|
|
2648
|
+
} else {
|
|
2649
|
+
this.tabGroup.value.removeAttribute("active");
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
getTabPanel(name) {
|
|
2653
|
+
if (!this.tabGroup.value) return null;
|
|
2654
|
+
return this.tabGroup.value.querySelector(`wa-tab-panel[name='${name}']`);
|
|
2655
|
+
}
|
|
2656
|
+
getTab(name) {
|
|
2657
|
+
if (!this.tabGroup.value) return null;
|
|
2658
|
+
return this.tabGroup.value.querySelector(`wa-tab[panel='${name}']`);
|
|
2659
|
+
}
|
|
2660
|
+
/**
|
|
2661
|
+
* Updates the toolbar for a tab panel by querying the component for its toolbar content.
|
|
2662
|
+
* This allows KPart components to provide their own toolbar items directly.
|
|
2663
|
+
*/
|
|
2664
|
+
updateToolbarFromComponent(tabPanel) {
|
|
2665
|
+
const contentDiv = tabPanel.querySelector(".tab-content");
|
|
2666
|
+
if (!contentDiv || !contentDiv.firstElementChild) return;
|
|
2667
|
+
const component = contentDiv.firstElementChild;
|
|
2668
|
+
if (!(component instanceof KPart)) return;
|
|
2669
|
+
if (!component["renderToolbar"]) return;
|
|
2670
|
+
const toolbar = tabPanel.querySelector("k-toolbar");
|
|
2671
|
+
if (toolbar) {
|
|
2672
|
+
toolbar.partToolbarRenderer = () => component["renderToolbar"]();
|
|
2673
|
+
toolbar.requestUpdate();
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
/**
|
|
2677
|
+
* Updates the context menu for a tab panel by querying the component for its context menu content.
|
|
2678
|
+
* This allows KPart components to provide their own context menu items directly.
|
|
2679
|
+
*/
|
|
2680
|
+
updateContextMenuFromComponent(tabPanel) {
|
|
2681
|
+
const contentDiv = tabPanel.querySelector(".tab-content");
|
|
2682
|
+
if (!contentDiv || !contentDiv.firstElementChild) return;
|
|
2683
|
+
const component = contentDiv.firstElementChild;
|
|
2684
|
+
if (!(component instanceof KPart)) return;
|
|
2685
|
+
if (!component["renderContextMenu"]) return;
|
|
2686
|
+
const contextMenu = tabPanel.querySelector("k-contextmenu");
|
|
2687
|
+
if (contextMenu) {
|
|
2688
|
+
contextMenu.partContextMenuRenderer = () => component["renderContextMenu"]();
|
|
2689
|
+
contextMenu.requestUpdate();
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
/**
|
|
2693
|
+
* Updates the toolbar height CSS variable for calc() positioning.
|
|
2694
|
+
*/
|
|
2695
|
+
updateToolbarHeightVariable(tabPanel) {
|
|
2696
|
+
const toolbar = tabPanel.querySelector(".tab-toolbar");
|
|
2697
|
+
if (!toolbar) return;
|
|
2698
|
+
const toolbarHeight = toolbar.offsetHeight;
|
|
2699
|
+
tabPanel.style.setProperty("--toolbar-height", `${toolbarHeight}px`);
|
|
2700
|
+
}
|
|
2701
|
+
/**
|
|
2702
|
+
* Sets up a ResizeObserver to update toolbar height variable when toolbar size changes.
|
|
2703
|
+
* Reuses existing observer if one already exists for this tab panel.
|
|
2704
|
+
*/
|
|
2705
|
+
setupToolbarResizeObserver(tabPanel) {
|
|
2706
|
+
if (this.resizeObservers.has(tabPanel)) return;
|
|
2707
|
+
const toolbar = tabPanel.querySelector(".tab-toolbar");
|
|
2708
|
+
if (!toolbar) return;
|
|
2709
|
+
const observer = new ResizeObserver(() => {
|
|
2710
|
+
this.updateToolbarHeightVariable(tabPanel);
|
|
2711
|
+
});
|
|
2712
|
+
observer.observe(toolbar);
|
|
2713
|
+
this.resizeObservers.set(tabPanel, observer);
|
|
2714
|
+
}
|
|
2715
|
+
// ============= Render Method =============
|
|
2716
|
+
render() {
|
|
2717
|
+
const currentApp = appLoaderService.getCurrentApp();
|
|
2718
|
+
return html`
|
|
2719
|
+
<wa-tab-group ${ref(this.tabGroup)}>
|
|
2720
|
+
${when(
|
|
2721
|
+
this.contributions.length === 0,
|
|
2722
|
+
() => html`
|
|
2723
|
+
<div class="empty-state">
|
|
2724
|
+
${when(
|
|
2725
|
+
currentApp,
|
|
2726
|
+
() => html`
|
|
2727
|
+
<div class="empty-content">
|
|
2728
|
+
<h2 class="empty-title">${currentApp.name}</h2>
|
|
2729
|
+
${when(
|
|
2730
|
+
currentApp.description,
|
|
2731
|
+
() => html`<p class="empty-description">${currentApp.description}</p>`
|
|
2732
|
+
)}
|
|
2733
|
+
</div>
|
|
2734
|
+
`,
|
|
2735
|
+
() => html`
|
|
2736
|
+
<wa-icon name="folder-open" class="empty-icon"></wa-icon>
|
|
2737
|
+
`
|
|
2738
|
+
)}
|
|
2739
|
+
</div>
|
|
2740
|
+
`,
|
|
2741
|
+
() => repeat(
|
|
2742
|
+
this.contributions,
|
|
2743
|
+
(c) => c.name,
|
|
2744
|
+
(c) => html`
|
|
2745
|
+
<wa-tab panel="${c.name}"
|
|
2746
|
+
@auxclick="${(e) => this.handleTabAuxClick(e, c)}">
|
|
2747
|
+
<k-icon name="${c.icon}"></k-icon>
|
|
2748
|
+
${c.label}
|
|
2749
|
+
${when(c.closable, () => html`
|
|
2750
|
+
<wa-icon name="xmark" label="Close" @click="${(e) => this.closeTab(e, c.name)}"></wa-icon>
|
|
2751
|
+
`)}
|
|
2752
|
+
</wa-tab>
|
|
2753
|
+
<wa-tab-panel name="${c.name}">
|
|
2754
|
+
<k-toolbar id="toolbar:${c.editorId ?? c.name}"
|
|
2755
|
+
class="tab-toolbar"
|
|
2756
|
+
?is-editor="${this.containerId === EDITOR_AREA_MAIN}"></k-toolbar>
|
|
2757
|
+
<wa-scroller class="tab-content" orientation="vertical">
|
|
2758
|
+
${c.component ? c.component(c.name) : nothing}
|
|
2759
|
+
</wa-scroller>
|
|
2760
|
+
<k-contextmenu id="contextmenu:${c.name}"
|
|
2761
|
+
?is-editor="${this.containerId === EDITOR_AREA_MAIN}"></k-contextmenu>
|
|
2762
|
+
</wa-tab-panel>
|
|
2763
|
+
`
|
|
2764
|
+
)
|
|
2765
|
+
)}
|
|
2766
|
+
</wa-tab-group>
|
|
2767
|
+
`;
|
|
2768
|
+
}
|
|
2769
|
+
};
|
|
2770
|
+
KTabs.styles = css`
|
|
2771
|
+
:host {
|
|
2772
|
+
height: 100%;
|
|
2773
|
+
width: 100%;
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
wa-tab-group {
|
|
2777
|
+
height: 100%;
|
|
2778
|
+
width: 100%;
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
wa-tab-group::part(base) {
|
|
2782
|
+
display: grid;
|
|
2783
|
+
grid-template-rows: auto minmax(0, 1fr);
|
|
2784
|
+
height: 100%;
|
|
2785
|
+
width: 100%;
|
|
2786
|
+
}
|
|
2787
|
+
|
|
2788
|
+
wa-tab-panel[active] {
|
|
2789
|
+
display: grid;
|
|
2790
|
+
grid-template-rows: minmax(0, 1fr);
|
|
2791
|
+
height: 100%;
|
|
2792
|
+
width: 100%;
|
|
2793
|
+
overflow: hidden;
|
|
2794
|
+
position: relative;
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2797
|
+
.tab-content {
|
|
2798
|
+
position: absolute;
|
|
2799
|
+
top: calc(var(--toolbar-height, 0px));
|
|
2800
|
+
right: 0;
|
|
2801
|
+
left: 0;
|
|
2802
|
+
height: calc(100% - var(--toolbar-height, 0px));
|
|
2803
|
+
}
|
|
2804
|
+
|
|
2805
|
+
wa-tab::part(base) {
|
|
2806
|
+
padding: 3px 0.5rem;
|
|
2807
|
+
}
|
|
2808
|
+
|
|
2809
|
+
wa-tab-panel {
|
|
2810
|
+
--padding: 0px;
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2813
|
+
.part-dirty::part(base) {
|
|
2814
|
+
font-style: italic;
|
|
2815
|
+
color: var(--wa-color-danger-fill-loud)
|
|
2816
|
+
}
|
|
2817
|
+
|
|
2818
|
+
.empty-state {
|
|
2819
|
+
display: flex;
|
|
2820
|
+
align-items: center;
|
|
2821
|
+
justify-content: center;
|
|
2822
|
+
width: 100%;
|
|
2823
|
+
height: 100%;
|
|
2824
|
+
grid-row: 2;
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
.empty-content {
|
|
2828
|
+
display: flex;
|
|
2829
|
+
flex-direction: column;
|
|
2830
|
+
align-items: center;
|
|
2831
|
+
justify-content: center;
|
|
2832
|
+
text-align: center;
|
|
2833
|
+
padding: 2rem;
|
|
2834
|
+
gap: 0.75rem;
|
|
2835
|
+
opacity: 0.3;
|
|
2836
|
+
}
|
|
2837
|
+
|
|
2838
|
+
.empty-title {
|
|
2839
|
+
margin: 0;
|
|
2840
|
+
font-size: 1.5rem;
|
|
2841
|
+
font-weight: 500;
|
|
2842
|
+
color: var(--wa-color-text-quiet);
|
|
2843
|
+
}
|
|
2844
|
+
|
|
2845
|
+
.empty-description {
|
|
2846
|
+
margin: 0;
|
|
2847
|
+
font-size: 1rem;
|
|
2848
|
+
color: var(--wa-color-text-quiet);
|
|
2849
|
+
max-width: 500px;
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
.empty-icon {
|
|
2853
|
+
font-size: 6rem;
|
|
2854
|
+
opacity: 0.2;
|
|
2855
|
+
color: var(--wa-color-text-quiet);
|
|
2856
|
+
}
|
|
2857
|
+
`;
|
|
2858
|
+
__decorateClass$1([
|
|
2859
|
+
state()
|
|
2860
|
+
], KTabs.prototype, "contributions", 2);
|
|
2861
|
+
KTabs = __decorateClass$1([
|
|
2862
|
+
customElement("k-tabs")
|
|
2863
|
+
], KTabs);
|
|
2864
|
+
var __defProp = Object.defineProperty;
|
|
2865
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
2866
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
2867
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
2868
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
2869
|
+
if (decorator = decorators[i])
|
|
2870
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
2871
|
+
if (kind && result) __defProp(target, key, result);
|
|
2872
|
+
return result;
|
|
2873
|
+
};
|
|
2874
|
+
let KResizableGrid = class extends KElement {
|
|
2875
|
+
constructor() {
|
|
2876
|
+
super(...arguments);
|
|
2877
|
+
this.orientation = "horizontal";
|
|
2878
|
+
this.gridSizes = [];
|
|
2879
|
+
this.gridChildren = [];
|
|
2880
|
+
this.resizing = null;
|
|
2881
|
+
this.resizeOverlay = null;
|
|
2882
|
+
this.childrenLoaded = false;
|
|
2883
|
+
this.childStylesApplied = false;
|
|
2884
|
+
this.settingsLoaded = false;
|
|
2885
|
+
this.handleResize = (e) => {
|
|
2886
|
+
if (!this.resizing) return;
|
|
2887
|
+
const currentPos = this.orientation === "horizontal" ? e.clientX : e.clientY;
|
|
2888
|
+
const delta = currentPos - this.resizing.startPos;
|
|
2889
|
+
const newSizes = [...this.resizing.startSizes];
|
|
2890
|
+
newSizes[this.resizing.handleIndex] += delta;
|
|
2891
|
+
newSizes[this.resizing.handleIndex + 1] -= delta;
|
|
2892
|
+
const containerSize = this.orientation === "horizontal" ? this.offsetWidth : this.offsetHeight;
|
|
2893
|
+
const minSize = containerSize * 0.05;
|
|
2894
|
+
if (newSizes[this.resizing.handleIndex] >= minSize && newSizes[this.resizing.handleIndex + 1] >= minSize) {
|
|
2895
|
+
this.resizing.currentSizes = newSizes;
|
|
2896
|
+
const gridTemplate = newSizes.map((size, index) => {
|
|
2897
|
+
const percent = size / containerSize * 100;
|
|
2898
|
+
const sizeStr = `${percent.toFixed(2)}%`;
|
|
2899
|
+
if (index === newSizes.length - 1) {
|
|
2900
|
+
return sizeStr;
|
|
2901
|
+
}
|
|
2902
|
+
return `${sizeStr} 4px`;
|
|
2903
|
+
}).join(" ");
|
|
2904
|
+
if (this.orientation === "horizontal") {
|
|
2905
|
+
this.style.gridTemplateColumns = gridTemplate;
|
|
2906
|
+
} else {
|
|
2907
|
+
this.style.gridTemplateRows = gridTemplate;
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
};
|
|
2911
|
+
this.stopResize = async () => {
|
|
2912
|
+
if (this.resizing?.currentSizes) {
|
|
2913
|
+
const containerSize = this.orientation === "horizontal" ? this.offsetWidth : this.offsetHeight;
|
|
2914
|
+
this.gridSizes = this.resizing.currentSizes.map((size) => {
|
|
2915
|
+
const percent = size / containerSize * 100;
|
|
2916
|
+
return `${percent.toFixed(2)}%`;
|
|
2917
|
+
});
|
|
2918
|
+
await this.saveSizes();
|
|
2919
|
+
this.requestUpdate();
|
|
2920
|
+
}
|
|
2921
|
+
if (this.resizeOverlay) {
|
|
2922
|
+
document.body.removeChild(this.resizeOverlay);
|
|
2923
|
+
this.resizeOverlay = null;
|
|
2924
|
+
}
|
|
2925
|
+
this.resizing = null;
|
|
2926
|
+
document.removeEventListener("mousemove", this.handleResize);
|
|
2927
|
+
document.removeEventListener("mouseup", this.stopResize);
|
|
2928
|
+
document.body.style.cursor = "";
|
|
2929
|
+
document.body.style.userSelect = "";
|
|
2930
|
+
};
|
|
2931
|
+
}
|
|
2932
|
+
createRenderRoot() {
|
|
2933
|
+
return this;
|
|
2934
|
+
}
|
|
2935
|
+
// ============= Lifecycle Methods =============
|
|
2936
|
+
doBeforeUI() {
|
|
2937
|
+
if (!this.childrenLoaded) {
|
|
2938
|
+
this.mutationObserver = new MutationObserver(() => {
|
|
2939
|
+
if (!this.childrenLoaded) {
|
|
2940
|
+
this.loadChildren();
|
|
2941
|
+
}
|
|
2942
|
+
});
|
|
2943
|
+
this.mutationObserver.observe(this, { childList: true, subtree: false });
|
|
2944
|
+
this.loadChildren();
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2947
|
+
async loadChildren() {
|
|
2948
|
+
const potentialChildren = Array.from(this.children).filter(
|
|
2949
|
+
(child) => child.tagName !== "STYLE" && child.tagName !== "SCRIPT" && !child.classList.contains("resize-handle")
|
|
2950
|
+
);
|
|
2951
|
+
if (potentialChildren.length === 0) {
|
|
2952
|
+
return;
|
|
2953
|
+
}
|
|
2954
|
+
this.childrenLoaded = true;
|
|
2955
|
+
if (this.mutationObserver) {
|
|
2956
|
+
this.mutationObserver.disconnect();
|
|
2957
|
+
this.mutationObserver = void 0;
|
|
2958
|
+
}
|
|
2959
|
+
this.gridChildren = potentialChildren;
|
|
2960
|
+
if (!this.settingsLoaded) {
|
|
2961
|
+
this.settingsLoaded = true;
|
|
2962
|
+
const persisted = await this.getDialogSetting();
|
|
2963
|
+
if (persisted && Array.isArray(persisted.sizes) && persisted.sizes.length === this.gridChildren.length) {
|
|
2964
|
+
this.gridSizes = persisted.sizes;
|
|
2965
|
+
this.requestUpdate();
|
|
2966
|
+
return;
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
if (this.sizes) {
|
|
2970
|
+
this.gridSizes = this.sizes.split(",").map((s) => s.trim());
|
|
2971
|
+
} else {
|
|
2972
|
+
const equalSize = `${100 / this.gridChildren.length}%`;
|
|
2973
|
+
this.gridSizes = this.gridChildren.map(() => equalSize);
|
|
2974
|
+
}
|
|
2975
|
+
this.requestUpdate();
|
|
2976
|
+
}
|
|
2977
|
+
async saveSizes() {
|
|
2978
|
+
if (this.gridSizes.length === 0) {
|
|
2979
|
+
return;
|
|
2980
|
+
}
|
|
2981
|
+
await this.setDialogSetting({
|
|
2982
|
+
sizes: this.gridSizes,
|
|
2983
|
+
orientation: this.orientation
|
|
2984
|
+
});
|
|
2985
|
+
}
|
|
2986
|
+
updated(changedProperties) {
|
|
2987
|
+
super.updated(changedProperties);
|
|
2988
|
+
if (changedProperties.has("gridChildren") && !this.childStylesApplied && this.gridChildren.length > 0) {
|
|
2989
|
+
this.childStylesApplied = true;
|
|
2990
|
+
this.gridChildren.forEach((child, index) => {
|
|
2991
|
+
child.style.overflow = "hidden";
|
|
2992
|
+
child.style.height = "100%";
|
|
2993
|
+
child.style.width = "100%";
|
|
2994
|
+
child.style.gridColumn = this.orientation === "horizontal" ? `${index * 2 + 1}` : "1";
|
|
2995
|
+
child.style.gridRow = this.orientation === "vertical" ? `${index * 2 + 1}` : "1";
|
|
2996
|
+
child.style.display = "flex";
|
|
2997
|
+
child.style.flexDirection = "column";
|
|
2998
|
+
});
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
// ============= Resize Handling Methods =============
|
|
3002
|
+
startResize(e, handleIndex) {
|
|
3003
|
+
e.preventDefault();
|
|
3004
|
+
if (handleIndex >= this.gridChildren.length - 1) return;
|
|
3005
|
+
const startPos = this.orientation === "horizontal" ? e.clientX : e.clientY;
|
|
3006
|
+
const containerSize = this.orientation === "horizontal" ? this.offsetWidth : this.offsetHeight;
|
|
3007
|
+
const startSizes = this.gridSizes.map((size) => {
|
|
3008
|
+
if (size.endsWith("%")) {
|
|
3009
|
+
return parseFloat(size) / 100 * containerSize;
|
|
3010
|
+
} else if (size.endsWith("px")) {
|
|
3011
|
+
return parseFloat(size);
|
|
3012
|
+
} else {
|
|
3013
|
+
return parseFloat(size);
|
|
3014
|
+
}
|
|
3015
|
+
});
|
|
3016
|
+
this.resizing = {
|
|
3017
|
+
handleIndex,
|
|
3018
|
+
startPos,
|
|
3019
|
+
startSizes
|
|
3020
|
+
};
|
|
3021
|
+
this.resizeOverlay = document.createElement("div");
|
|
3022
|
+
this.resizeOverlay.style.position = "fixed";
|
|
3023
|
+
this.resizeOverlay.style.top = "0";
|
|
3024
|
+
this.resizeOverlay.style.left = "0";
|
|
3025
|
+
this.resizeOverlay.style.width = "100%";
|
|
3026
|
+
this.resizeOverlay.style.height = "100%";
|
|
3027
|
+
this.resizeOverlay.style.zIndex = "9999";
|
|
3028
|
+
this.resizeOverlay.style.cursor = this.orientation === "horizontal" ? "col-resize" : "row-resize";
|
|
3029
|
+
document.body.appendChild(this.resizeOverlay);
|
|
3030
|
+
document.addEventListener("mousemove", this.handleResize);
|
|
3031
|
+
document.addEventListener("mouseup", this.stopResize);
|
|
3032
|
+
document.body.style.cursor = this.orientation === "horizontal" ? "col-resize" : "row-resize";
|
|
3033
|
+
document.body.style.userSelect = "none";
|
|
3034
|
+
}
|
|
3035
|
+
// ============= Render Methods =============
|
|
3036
|
+
render() {
|
|
3037
|
+
if (this.gridChildren.length === 0 || this.gridSizes.length === 0) {
|
|
3038
|
+
return nothing;
|
|
3039
|
+
}
|
|
3040
|
+
const gridTemplate = this.gridSizes.flatMap((size, index) => {
|
|
3041
|
+
if (index === this.gridSizes.length - 1) {
|
|
3042
|
+
return [size];
|
|
3043
|
+
}
|
|
3044
|
+
return [size, "4px"];
|
|
3045
|
+
}).join(" ");
|
|
3046
|
+
this.style.display = "grid";
|
|
3047
|
+
if (this.orientation === "horizontal") {
|
|
3048
|
+
this.style.gridTemplateColumns = gridTemplate;
|
|
3049
|
+
this.style.gridTemplateRows = "100%";
|
|
3050
|
+
} else {
|
|
3051
|
+
this.style.gridTemplateColumns = "100%";
|
|
3052
|
+
this.style.gridTemplateRows = gridTemplate;
|
|
3053
|
+
}
|
|
3054
|
+
this.style.overflow = "hidden";
|
|
3055
|
+
return html`
|
|
3056
|
+
<style>
|
|
3057
|
+
.resize-handle {
|
|
3058
|
+
position: relative;
|
|
3059
|
+
z-index: 10;
|
|
3060
|
+
background-color: var(--wa-color-neutral-border-quiet);
|
|
3061
|
+
transition: background-color var(--wa-transition-fast);
|
|
3062
|
+
}
|
|
3063
|
+
|
|
3064
|
+
.resize-handle:hover {
|
|
3065
|
+
background-color: var(--wa-color-brand-fill-normal);
|
|
3066
|
+
}
|
|
3067
|
+
</style>
|
|
3068
|
+
|
|
3069
|
+
${this.gridChildren.map((_, index) => {
|
|
3070
|
+
if (index < this.gridChildren.length - 1) {
|
|
3071
|
+
const gridCol = this.orientation === "horizontal" ? `${index * 2 + 2}` : "1";
|
|
3072
|
+
const gridRow = this.orientation === "vertical" ? `${index * 2 + 2}` : "1";
|
|
3073
|
+
return html`
|
|
3074
|
+
<div
|
|
3075
|
+
class="resize-handle"
|
|
3076
|
+
style="
|
|
3077
|
+
cursor: ${this.orientation === "horizontal" ? "col-resize" : "row-resize"};
|
|
3078
|
+
grid-column: ${gridCol};
|
|
3079
|
+
grid-row: ${gridRow};
|
|
3080
|
+
"
|
|
3081
|
+
@mousedown=${(e) => this.startResize(e, index)}
|
|
3082
|
+
></div>
|
|
3083
|
+
`;
|
|
3084
|
+
}
|
|
3085
|
+
return nothing;
|
|
3086
|
+
})}
|
|
3087
|
+
`;
|
|
3088
|
+
}
|
|
3089
|
+
// ============= Cleanup Methods =============
|
|
3090
|
+
disconnectedCallback() {
|
|
3091
|
+
super.disconnectedCallback();
|
|
3092
|
+
if (this.resizing) {
|
|
3093
|
+
this.stopResize();
|
|
3094
|
+
}
|
|
3095
|
+
if (this.mutationObserver) {
|
|
3096
|
+
this.mutationObserver.disconnect();
|
|
3097
|
+
this.mutationObserver = void 0;
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3100
|
+
connectedCallback() {
|
|
3101
|
+
super.connectedCallback();
|
|
3102
|
+
this.style.height = "100%";
|
|
3103
|
+
this.style.width = "100%";
|
|
3104
|
+
}
|
|
3105
|
+
};
|
|
3106
|
+
__decorateClass([
|
|
3107
|
+
property()
|
|
3108
|
+
], KResizableGrid.prototype, "orientation", 2);
|
|
3109
|
+
__decorateClass([
|
|
3110
|
+
property()
|
|
3111
|
+
], KResizableGrid.prototype, "sizes", 2);
|
|
3112
|
+
__decorateClass([
|
|
3113
|
+
state()
|
|
3114
|
+
], KResizableGrid.prototype, "gridSizes", 2);
|
|
3115
|
+
__decorateClass([
|
|
3116
|
+
state()
|
|
3117
|
+
], KResizableGrid.prototype, "gridChildren", 2);
|
|
3118
|
+
KResizableGrid = __decorateClass([
|
|
3119
|
+
customElement("k-resizable-grid")
|
|
3120
|
+
], KResizableGrid);
|
|
3121
|
+
export {
|
|
3122
|
+
COMMAND_SAVE as C,
|
|
3123
|
+
DIALOG_CONTRIBUTION_TARGET as D,
|
|
3124
|
+
EDITOR_AREA_MAIN as E,
|
|
3125
|
+
HIDE_DOT_RESOURCE as H,
|
|
3126
|
+
KContainer as K,
|
|
3127
|
+
MouseButton as M,
|
|
3128
|
+
PANEL_BOTTOM as P,
|
|
3129
|
+
SIDEBAR_AUXILIARY as S,
|
|
3130
|
+
TOOLBAR_BOTTOM as T,
|
|
3131
|
+
KDialogContent as a,
|
|
3132
|
+
KElement as b,
|
|
3133
|
+
KPart as c,
|
|
3134
|
+
SIDEBAR_MAIN as d,
|
|
3135
|
+
SIDEBAR_MAIN_BOTTOM as e,
|
|
3136
|
+
TOOLBAR_BOTTOM_CENTER as f,
|
|
3137
|
+
TOOLBAR_BOTTOM_END as g,
|
|
3138
|
+
TOOLBAR_MAIN as h,
|
|
3139
|
+
TOOLBAR_MAIN_CENTER as i,
|
|
3140
|
+
TOOLBAR_MAIN_RIGHT as j,
|
|
3141
|
+
TOPIC_SETTINGS_CHANGED as k,
|
|
3142
|
+
appLoaderService as l,
|
|
3143
|
+
appSettings as m,
|
|
3144
|
+
confirmDialog as n,
|
|
3145
|
+
esmShService as o,
|
|
3146
|
+
extensionRegistry as p,
|
|
3147
|
+
infoDialog as q,
|
|
3148
|
+
navigableInfoDialog as r,
|
|
3149
|
+
persistenceService as s,
|
|
3150
|
+
promptDialog as t,
|
|
3151
|
+
taskService as u,
|
|
3152
|
+
TOPIC_EXTENSIONS_CHANGED as v,
|
|
3153
|
+
CLOSE_BUTTON as w,
|
|
3154
|
+
dialogService as x,
|
|
3155
|
+
constants as y
|
|
3156
|
+
};
|
|
3157
|
+
//# sourceMappingURL=k-resizable-grid-Ch3iWZaL.js.map
|