@eclipse-lyra/core 0.0.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.
Files changed (281) hide show
  1. package/dist/api/base-classes.d.ts +7 -0
  2. package/dist/api/base-classes.d.ts.map +1 -0
  3. package/dist/api/constants.d.ts +2 -0
  4. package/dist/api/constants.d.ts.map +1 -0
  5. package/dist/api/index.d.ts +6 -0
  6. package/dist/api/index.d.ts.map +1 -0
  7. package/dist/api/index.js +84 -0
  8. package/dist/api/index.js.map +1 -0
  9. package/dist/api/services.d.ts +28 -0
  10. package/dist/api/services.d.ts.map +1 -0
  11. package/dist/api/types.d.ts +11 -0
  12. package/dist/api/types.d.ts.map +1 -0
  13. package/dist/commands/editor.d.ts +1 -0
  14. package/dist/commands/editor.d.ts.map +1 -0
  15. package/dist/commands/files.d.ts +2 -0
  16. package/dist/commands/files.d.ts.map +1 -0
  17. package/dist/commands/global.d.ts +1 -0
  18. package/dist/commands/global.d.ts.map +1 -0
  19. package/dist/commands/index.d.ts +1 -0
  20. package/dist/commands/index.d.ts.map +1 -0
  21. package/dist/commands/open-view-as-editor.d.ts +2 -0
  22. package/dist/commands/open-view-as-editor.d.ts.map +1 -0
  23. package/dist/commands/version-info.d.ts +2 -0
  24. package/dist/commands/version-info.d.ts.map +1 -0
  25. package/dist/components/app-selector.d.ts +17 -0
  26. package/dist/components/app-selector.d.ts.map +1 -0
  27. package/dist/components/app-switcher.d.ts +13 -0
  28. package/dist/components/app-switcher.d.ts.map +1 -0
  29. package/dist/components/command.d.ts +31 -0
  30. package/dist/components/command.d.ts.map +1 -0
  31. package/dist/components/extensions.d.ts +32 -0
  32. package/dist/components/extensions.d.ts.map +1 -0
  33. package/dist/components/fastviews.d.ts +34 -0
  34. package/dist/components/fastviews.d.ts.map +1 -0
  35. package/dist/components/filebrowser.d.ts +45 -0
  36. package/dist/components/filebrowser.d.ts.map +1 -0
  37. package/dist/components/index.d.ts +1 -0
  38. package/dist/components/index.d.ts.map +1 -0
  39. package/dist/components/language-selector.d.ts +12 -0
  40. package/dist/components/language-selector.d.ts.map +1 -0
  41. package/dist/components/log-terminal.d.ts +36 -0
  42. package/dist/components/log-terminal.d.ts.map +1 -0
  43. package/dist/components/part-name.d.ts +12 -0
  44. package/dist/components/part-name.d.ts.map +1 -0
  45. package/dist/components/tasks.d.ts +13 -0
  46. package/dist/components/tasks.d.ts.map +1 -0
  47. package/dist/contributions/default-ui-contributions.d.ts +2 -0
  48. package/dist/contributions/default-ui-contributions.d.ts.map +1 -0
  49. package/dist/contributions/index.d.ts +1 -0
  50. package/dist/contributions/index.d.ts.map +1 -0
  51. package/dist/contributions/marketplace-catalog-contributions.d.ts +2 -0
  52. package/dist/contributions/marketplace-catalog-contributions.d.ts.map +1 -0
  53. package/dist/core/app-host-config.d.ts +7 -0
  54. package/dist/core/app-host-config.d.ts.map +1 -0
  55. package/dist/core/apploader.d.ts +214 -0
  56. package/dist/core/apploader.d.ts.map +1 -0
  57. package/dist/core/appstate.d.ts +12 -0
  58. package/dist/core/appstate.d.ts.map +1 -0
  59. package/dist/core/commandregistry.d.ts +79 -0
  60. package/dist/core/commandregistry.d.ts.map +1 -0
  61. package/dist/core/config.d.ts +15 -0
  62. package/dist/core/config.d.ts.map +1 -0
  63. package/dist/core/constants.d.ts +22 -0
  64. package/dist/core/constants.d.ts.map +1 -0
  65. package/dist/core/contributionregistry.d.ts +53 -0
  66. package/dist/core/contributionregistry.d.ts.map +1 -0
  67. package/dist/core/di.d.ts +18 -0
  68. package/dist/core/di.d.ts.map +1 -0
  69. package/dist/core/dialogservice.d.ts +33 -0
  70. package/dist/core/dialogservice.d.ts.map +1 -0
  71. package/dist/core/editorregistry.d.ts +87 -0
  72. package/dist/core/editorregistry.d.ts.map +1 -0
  73. package/dist/core/esmsh-service.d.ts +40 -0
  74. package/dist/core/esmsh-service.d.ts.map +1 -0
  75. package/dist/core/events.d.ts +7 -0
  76. package/dist/core/events.d.ts.map +1 -0
  77. package/dist/core/events.js +63 -0
  78. package/dist/core/events.js.map +1 -0
  79. package/dist/core/extensionregistry.d.ts +98 -0
  80. package/dist/core/extensionregistry.d.ts.map +1 -0
  81. package/dist/core/filesys/common.d.ts +122 -0
  82. package/dist/core/filesys/common.d.ts.map +1 -0
  83. package/dist/core/filesys/fs-access.d.ts +31 -0
  84. package/dist/core/filesys/fs-access.d.ts.map +1 -0
  85. package/dist/core/filesys/index.d.ts +5 -0
  86. package/dist/core/filesys/index.d.ts.map +1 -0
  87. package/dist/core/filesys/indexeddb.d.ts +41 -0
  88. package/dist/core/filesys/indexeddb.d.ts.map +1 -0
  89. package/dist/core/filesys/opfs.d.ts +14 -0
  90. package/dist/core/filesys/opfs.d.ts.map +1 -0
  91. package/dist/core/i18n.d.ts +50 -0
  92. package/dist/core/i18n.d.ts.map +1 -0
  93. package/dist/core/index.d.ts +1 -0
  94. package/dist/core/index.d.ts.map +1 -0
  95. package/dist/core/keybindings.d.ts +67 -0
  96. package/dist/core/keybindings.d.ts.map +1 -0
  97. package/dist/core/logger.d.ts +44 -0
  98. package/dist/core/logger.d.ts.map +1 -0
  99. package/dist/core/marketplaceregistry.d.ts +25 -0
  100. package/dist/core/marketplaceregistry.d.ts.map +1 -0
  101. package/dist/core/packageinfoservice.d.ts +16 -0
  102. package/dist/core/packageinfoservice.d.ts.map +1 -0
  103. package/dist/core/persistenceservice.d.ts +6 -0
  104. package/dist/core/persistenceservice.d.ts.map +1 -0
  105. package/dist/core/settingsservice.d.ts +54 -0
  106. package/dist/core/settingsservice.d.ts.map +1 -0
  107. package/dist/core/signals.d.ts +3 -0
  108. package/dist/core/signals.d.ts.map +1 -0
  109. package/dist/core/taskservice.d.ts +20 -0
  110. package/dist/core/taskservice.d.ts.map +1 -0
  111. package/dist/core/toast.d.ts +4 -0
  112. package/dist/core/toast.d.ts.map +1 -0
  113. package/dist/core/tree-utils.d.ts +16 -0
  114. package/dist/core/tree-utils.d.ts.map +1 -0
  115. package/dist/dialogs/confirm-dialog.d.ts +14 -0
  116. package/dist/dialogs/confirm-dialog.d.ts.map +1 -0
  117. package/dist/dialogs/index.d.ts +5 -0
  118. package/dist/dialogs/index.d.ts.map +1 -0
  119. package/dist/dialogs/info-dialog.d.ts +13 -0
  120. package/dist/dialogs/info-dialog.d.ts.map +1 -0
  121. package/dist/dialogs/navigable-info-dialog.d.ts +33 -0
  122. package/dist/dialogs/navigable-info-dialog.d.ts.map +1 -0
  123. package/dist/dialogs/prompt-dialog.d.ts +21 -0
  124. package/dist/dialogs/prompt-dialog.d.ts.map +1 -0
  125. package/dist/externals/lit.d.ts +21 -0
  126. package/dist/externals/lit.d.ts.map +1 -0
  127. package/dist/externals/lit.js +24 -0
  128. package/dist/externals/lit.js.map +1 -0
  129. package/dist/externals/third-party.d.ts +7 -0
  130. package/dist/externals/third-party.d.ts.map +1 -0
  131. package/dist/externals/third-party.js +7 -0
  132. package/dist/externals/third-party.js.map +1 -0
  133. package/dist/externals/webawesome.d.ts +1 -0
  134. package/dist/externals/webawesome.d.ts.map +1 -0
  135. package/dist/externals/webawesome.js +75 -0
  136. package/dist/externals/webawesome.js.map +1 -0
  137. package/dist/i18n/extensions.json.d.ts +42 -0
  138. package/dist/i18n/fastviews.json.d.ts +13 -0
  139. package/dist/i18n/filebrowser.json.d.ts +29 -0
  140. package/dist/i18n/index.d.ts +2 -0
  141. package/dist/i18n/index.d.ts.map +1 -0
  142. package/dist/i18n/logterminal.json.d.ts +45 -0
  143. package/dist/i18n/partname.json.d.ts +15 -0
  144. package/dist/i18n/tasks.json.d.ts +15 -0
  145. package/dist/i18n/workspace.json.d.ts +15 -0
  146. package/dist/icon-DN6fp0dg.js +487 -0
  147. package/dist/icon-DN6fp0dg.js.map +1 -0
  148. package/dist/index.d.ts +2 -0
  149. package/dist/index.d.ts.map +1 -0
  150. package/dist/index.js +84 -0
  151. package/dist/index.js.map +1 -0
  152. package/dist/layouts/standard-layout.d.ts +16 -0
  153. package/dist/layouts/standard-layout.d.ts.map +1 -0
  154. package/dist/nocontent-BhrN6yxJ.js +48 -0
  155. package/dist/nocontent-BhrN6yxJ.js.map +1 -0
  156. package/dist/parts/container.d.ts +4 -0
  157. package/dist/parts/container.d.ts.map +1 -0
  158. package/dist/parts/contextmenu.d.ts +50 -0
  159. package/dist/parts/contextmenu.d.ts.map +1 -0
  160. package/dist/parts/dialog-content.d.ts +9 -0
  161. package/dist/parts/dialog-content.d.ts.map +1 -0
  162. package/dist/parts/element.d.ts +36 -0
  163. package/dist/parts/element.d.ts.map +1 -0
  164. package/dist/parts/index.d.ts +1 -0
  165. package/dist/parts/index.d.ts.map +1 -0
  166. package/dist/parts/index.js +3 -0
  167. package/dist/parts/index.js.map +1 -0
  168. package/dist/parts/part.d.ts +96 -0
  169. package/dist/parts/part.d.ts.map +1 -0
  170. package/dist/parts/resizable-grid.d.ts +31 -0
  171. package/dist/parts/resizable-grid.d.ts.map +1 -0
  172. package/dist/parts/tabs.d.ts +75 -0
  173. package/dist/parts/tabs.d.ts.map +1 -0
  174. package/dist/parts/toolbar.d.ts +35 -0
  175. package/dist/parts/toolbar.d.ts.map +1 -0
  176. package/dist/resizable-grid-BRH3MyZK.js +3813 -0
  177. package/dist/resizable-grid-BRH3MyZK.js.map +1 -0
  178. package/dist/standard-layout-BSGa06lP.js +4907 -0
  179. package/dist/standard-layout-BSGa06lP.js.map +1 -0
  180. package/dist/widgets/icon.d.ts +10 -0
  181. package/dist/widgets/icon.d.ts.map +1 -0
  182. package/dist/widgets/index.d.ts +1 -0
  183. package/dist/widgets/index.d.ts.map +1 -0
  184. package/dist/widgets/index.js +3 -0
  185. package/dist/widgets/index.js.map +1 -0
  186. package/dist/widgets/nocontent.d.ts +13 -0
  187. package/dist/widgets/nocontent.d.ts.map +1 -0
  188. package/dist/widgets/widget.d.ts +25 -0
  189. package/dist/widgets/widget.d.ts.map +1 -0
  190. package/package.json +81 -0
  191. package/src/api/base-classes.ts +10 -0
  192. package/src/api/constants.ts +3 -0
  193. package/src/api/index.ts +31 -0
  194. package/src/api/services.ts +58 -0
  195. package/src/api/types.ts +46 -0
  196. package/src/commands/editor.ts +1 -0
  197. package/src/commands/files.ts +425 -0
  198. package/src/commands/global.ts +198 -0
  199. package/src/commands/index.ts +6 -0
  200. package/src/commands/open-view-as-editor.ts +28 -0
  201. package/src/commands/version-info.ts +214 -0
  202. package/src/components/app-selector.ts +233 -0
  203. package/src/components/app-switcher.ts +126 -0
  204. package/src/components/command.ts +236 -0
  205. package/src/components/extensions.ts +615 -0
  206. package/src/components/fastviews.ts +314 -0
  207. package/src/components/filebrowser.ts +518 -0
  208. package/src/components/index.ts +9 -0
  209. package/src/components/language-selector.ts +166 -0
  210. package/src/components/log-terminal.ts +337 -0
  211. package/src/components/part-name.ts +54 -0
  212. package/src/components/tasks.ts +275 -0
  213. package/src/contributions/default-ui-contributions.ts +44 -0
  214. package/src/contributions/index.ts +3 -0
  215. package/src/contributions/marketplace-catalog-contributions.ts +6 -0
  216. package/src/core/app-host-config.ts +23 -0
  217. package/src/core/apploader.ts +630 -0
  218. package/src/core/appstate.ts +15 -0
  219. package/src/core/commandregistry.ts +210 -0
  220. package/src/core/config.ts +29 -0
  221. package/src/core/constants.ts +29 -0
  222. package/src/core/contributionregistry.ts +81 -0
  223. package/src/core/di.ts +54 -0
  224. package/src/core/dialogservice.ts +266 -0
  225. package/src/core/editorregistry.ts +347 -0
  226. package/src/core/esmsh-service.ts +404 -0
  227. package/src/core/events.ts +68 -0
  228. package/src/core/extensionregistry.ts +399 -0
  229. package/src/core/filesys/common.ts +474 -0
  230. package/src/core/filesys/fs-access.ts +339 -0
  231. package/src/core/filesys/index.ts +5 -0
  232. package/src/core/filesys/indexeddb.ts +596 -0
  233. package/src/core/filesys/opfs.ts +95 -0
  234. package/src/core/i18n.ts +221 -0
  235. package/src/core/index.ts +51 -0
  236. package/src/core/keybindings.ts +274 -0
  237. package/src/core/logger.ts +187 -0
  238. package/src/core/marketplaceregistry.ts +197 -0
  239. package/src/core/packageinfoservice.ts +56 -0
  240. package/src/core/persistenceservice.ts +46 -0
  241. package/src/core/settingsservice.ts +191 -0
  242. package/src/core/signals.ts +18 -0
  243. package/src/core/taskservice.ts +72 -0
  244. package/src/core/toast.ts +21 -0
  245. package/src/core/tree-utils.ts +24 -0
  246. package/src/dialogs/confirm-dialog.ts +72 -0
  247. package/src/dialogs/index.ts +4 -0
  248. package/src/dialogs/info-dialog.ts +67 -0
  249. package/src/dialogs/navigable-info-dialog.ts +256 -0
  250. package/src/dialogs/prompt-dialog.ts +123 -0
  251. package/src/externals/lit.ts +36 -0
  252. package/src/externals/third-party.ts +10 -0
  253. package/src/externals/webawesome.ts +76 -0
  254. package/src/i18n/extensions.json +39 -0
  255. package/src/i18n/fastviews.json +10 -0
  256. package/src/i18n/filebrowser.json +27 -0
  257. package/src/i18n/index.ts +25 -0
  258. package/src/i18n/logterminal.json +42 -0
  259. package/src/i18n/partname.json +12 -0
  260. package/src/i18n/tasks.json +12 -0
  261. package/src/i18n/workspace.json +12 -0
  262. package/src/icons/icons.txt +3 -0
  263. package/src/icons/js.svg +6 -0
  264. package/src/icons/jupyter.svg +18 -0
  265. package/src/icons/python.svg +15 -0
  266. package/src/index.ts +3 -0
  267. package/src/layouts/standard-layout.ts +174 -0
  268. package/src/parts/container.ts +4 -0
  269. package/src/parts/contextmenu.ts +266 -0
  270. package/src/parts/dialog-content.ts +31 -0
  271. package/src/parts/element.ts +100 -0
  272. package/src/parts/index.ts +5 -0
  273. package/src/parts/part.ts +158 -0
  274. package/src/parts/resizable-grid.ts +366 -0
  275. package/src/parts/tabs.ts +581 -0
  276. package/src/parts/toolbar.ts +260 -0
  277. package/src/vite-env.d.ts +16 -0
  278. package/src/widgets/icon.ts +38 -0
  279. package/src/widgets/index.ts +2 -0
  280. package/src/widgets/nocontent.ts +40 -0
  281. package/src/widgets/widget.ts +92 -0
@@ -0,0 +1,4907 @@
1
+ import { l as rootContext, h as contributionRegistry, b as TOPIC_CONTRIBUTEIONS_CHANGED, w as watchSignal, p as partDirtySignal, d as activePartSignal, c as activeEditorSignal, i as createLogger, e as activeSelectionSignal, f as activeTasksSignal, t as toastError, m as toastInfo, o as registerLogHandler, q as unregisterLogHandler, L as LyraWidget, r as registerAll, u as uiContext, n as toastWarning } from "./icon-DN6fp0dg.js";
2
+ import { html, css, nothing, render } from "lit";
3
+ import { n as appSettings, l as TOPIC_SETTINGS_CHANGED, t as persistenceService, E as EDITOR_AREA_MAIN, c as LyraPart, q as extensionRegistry, H as HIDE_DOT_RESOURCE, o as confirmDialog, g as TOOLBAR_BOTTOM_CENTER, v as taskService, b as LyraElement, w as TOPIC_EXTENSIONS_CHANGED, m as appLoaderService, u as promptDialog, p as esmShService, D as DIALOG_CONTRIBUTION_TARGET, x as CLOSE_BUTTON, y as dialogService, d as SIDEBAR_MAIN, h as TOOLBAR_BOTTOM_END, k as TOOLBAR_MAIN_RIGHT, f as SYSTEM_VIEWS, z as constants, L as LyraContainer, i as TOOLBAR_MAIN, j as TOOLBAR_MAIN_CENTER, T as TOOLBAR_BOTTOM, e as SIDEBAR_MAIN_BOTTOM, P as PANEL_BOTTOM, S as SIDEBAR_AUXILIARY } from "./resizable-grid-BRH3MyZK.js";
4
+ import { signal } from "@lit-labs/signals";
5
+ import { subscribe, publish } from "./core/events.js";
6
+ import "./externals/webawesome.js";
7
+ import { state, customElement, property } from "lit/decorators.js";
8
+ import { when } from "lit/directives/when.js";
9
+ import { createRef, ref } from "lit/directives/ref.js";
10
+ import "./nocontent-BhrN6yxJ.js";
11
+ import { unsafeHTML } from "lit/directives/unsafe-html.js";
12
+ import { marked } from "marked";
13
+ class PackageInfoService {
14
+ constructor() {
15
+ this.packages = [];
16
+ }
17
+ addPackage(packageInfo) {
18
+ this.packages.push(packageInfo);
19
+ }
20
+ hasPackages() {
21
+ return this.packages.length > 0 && this.packages.some(
22
+ (pkg) => pkg.dependencies && Object.keys(pkg.dependencies).length > 0
23
+ );
24
+ }
25
+ renderTree() {
26
+ if (this.packages.length === 0) {
27
+ return html``;
28
+ }
29
+ return html`
30
+ <wa-tree style="--indent-guide-width: 1px;">
31
+ ${this.packages.map((pkg) => {
32
+ const deps = pkg.dependencies || {};
33
+ const depEntries = Object.entries(deps);
34
+ if (depEntries.length === 0) {
35
+ return html``;
36
+ }
37
+ return html`
38
+ <wa-tree-item expanded>
39
+ <span>${pkg.name}</span>
40
+ ${depEntries.map(([name, version]) => html`
41
+ <wa-tree-item>
42
+ <span>${name} <small>${version}</small></span>
43
+ </wa-tree-item>
44
+ `)}
45
+ </wa-tree-item>
46
+ `;
47
+ })}
48
+ </wa-tree>
49
+ `;
50
+ }
51
+ }
52
+ const packageInfoService = new PackageInfoService();
53
+ rootContext.put("packageInfoService", packageInfoService);
54
+ const SYSTEM_LANGUAGE_BUNDLES = "system.language_bundles";
55
+ const SETTINGS_KEY_LANGUAGE = "language";
56
+ function replaceParameters(text, params) {
57
+ if (!params) {
58
+ return text;
59
+ }
60
+ return text.replace(/\{(\w+)\}/g, (match, paramKey) => {
61
+ return params[paramKey] !== void 0 ? params[paramKey] : match;
62
+ });
63
+ }
64
+ class LazyTranslation extends String {
65
+ constructor(i18nService2, namespace2, key, params) {
66
+ super("");
67
+ this.i18nService = i18nService2;
68
+ this.namespace = namespace2;
69
+ this.key = key;
70
+ this.params = params;
71
+ }
72
+ toString() {
73
+ const currentLanguage = this.i18nService.currentLanguageSignal.get();
74
+ if (this.cachedValue !== void 0 && this.cachedLanguage === currentLanguage) {
75
+ return this.cachedValue;
76
+ }
77
+ this.cachedValue = this.i18nService.translate(this.namespace, this.key, this.params);
78
+ this.cachedLanguage = currentLanguage;
79
+ return this.cachedValue;
80
+ }
81
+ valueOf() {
82
+ return this.toString();
83
+ }
84
+ [Symbol.toPrimitive](hint) {
85
+ if (hint === "number") {
86
+ const num = Number(this.toString());
87
+ return isNaN(num) ? 0 : num;
88
+ }
89
+ return this.toString();
90
+ }
91
+ toJSON() {
92
+ return this.toString();
93
+ }
94
+ }
95
+ const _I18nService = class _I18nService {
96
+ constructor() {
97
+ this.translationCache = /* @__PURE__ */ new Map();
98
+ this.currentLanguageSignal = signal(this.getBrowserLanguage());
99
+ this.languageContributionsSignal = signal([]);
100
+ this.initialize();
101
+ }
102
+ getBrowserLanguage() {
103
+ const browserLanguage = navigator.language || navigator.languages?.[0] || _I18nService.DEFAULT_LANGUAGE;
104
+ return browserLanguage.split("-")[0].toLowerCase();
105
+ }
106
+ async initializeLanguage() {
107
+ const settingsLanguage = await appSettings.get(SETTINGS_KEY_LANGUAGE);
108
+ return settingsLanguage || this.getBrowserLanguage();
109
+ }
110
+ async updateLanguageFromSettings() {
111
+ const language = await this.initializeLanguage();
112
+ this.currentLanguageSignal.set(language);
113
+ }
114
+ getPrimaryLanguage(language) {
115
+ return language.split("-")[0].toLowerCase();
116
+ }
117
+ updateLanguageContributions() {
118
+ const contributions = contributionRegistry.getContributions(SYSTEM_LANGUAGE_BUNDLES);
119
+ this.languageContributionsSignal.set(contributions);
120
+ }
121
+ createCacheKey(namespace2, language) {
122
+ return `${namespace2}:${language}`;
123
+ }
124
+ mergeTranslationsForLanguage(contributions, namespace2, language) {
125
+ const cacheKey = this.createCacheKey(namespace2, language);
126
+ const cached = this.translationCache.get(cacheKey);
127
+ if (cached !== void 0) {
128
+ return cached;
129
+ }
130
+ const merged = {};
131
+ for (const contribution of contributions) {
132
+ if (contribution.namespace !== namespace2) {
133
+ continue;
134
+ }
135
+ let translations;
136
+ if (contribution.translations && contribution.language === language) {
137
+ translations = contribution.translations;
138
+ } else if (contribution[language] && typeof contribution[language] === "object") {
139
+ translations = contribution[language];
140
+ }
141
+ if (translations) {
142
+ Object.assign(merged, translations);
143
+ }
144
+ }
145
+ this.translationCache.set(cacheKey, merged);
146
+ return merged;
147
+ }
148
+ invalidateTranslationCache() {
149
+ this.translationCache.clear();
150
+ }
151
+ translate(namespace2, key, params) {
152
+ const currentLanguage = this.currentLanguageSignal.get();
153
+ const primaryLanguage = this.getPrimaryLanguage(currentLanguage);
154
+ const contributions = this.languageContributionsSignal.get();
155
+ let translation;
156
+ const currentLangTranslations = this.mergeTranslationsForLanguage(contributions, namespace2, currentLanguage);
157
+ if (currentLangTranslations[key]) {
158
+ translation = currentLangTranslations[key];
159
+ } else {
160
+ const primaryLangTranslations = currentLanguage !== primaryLanguage ? this.mergeTranslationsForLanguage(contributions, namespace2, primaryLanguage) : currentLangTranslations;
161
+ if (primaryLangTranslations[key]) {
162
+ translation = primaryLangTranslations[key];
163
+ } else if (primaryLanguage !== _I18nService.DEFAULT_LANGUAGE && currentLanguage !== _I18nService.DEFAULT_LANGUAGE) {
164
+ const defaultLangTranslations = this.mergeTranslationsForLanguage(contributions, namespace2, _I18nService.DEFAULT_LANGUAGE);
165
+ if (defaultLangTranslations[key]) {
166
+ translation = defaultLangTranslations[key];
167
+ }
168
+ }
169
+ }
170
+ if (!translation) {
171
+ return key;
172
+ }
173
+ return replaceParameters(translation, params);
174
+ }
175
+ initialize() {
176
+ subscribe(TOPIC_SETTINGS_CHANGED, async (settings) => {
177
+ const language = settings?.[SETTINGS_KEY_LANGUAGE] || this.getBrowserLanguage();
178
+ this.currentLanguageSignal.set(language);
179
+ this.invalidateTranslationCache();
180
+ });
181
+ subscribe(TOPIC_CONTRIBUTEIONS_CHANGED, (event) => {
182
+ if (event.target === SYSTEM_LANGUAGE_BUNDLES) {
183
+ this.invalidateTranslationCache();
184
+ this.updateLanguageContributions();
185
+ }
186
+ });
187
+ this.updateLanguageFromSettings();
188
+ this.updateLanguageContributions();
189
+ }
190
+ i18n(namespace2) {
191
+ return (key, params) => {
192
+ return this.translate(namespace2, key, params);
193
+ };
194
+ }
195
+ i18nLazy(namespace2) {
196
+ return (key, params) => {
197
+ return new LazyTranslation(this, namespace2, key, params);
198
+ };
199
+ }
200
+ };
201
+ _I18nService.DEFAULT_LANGUAGE = "en";
202
+ let I18nService = _I18nService;
203
+ const i18nService = new I18nService();
204
+ rootContext.put("i18nService", i18nService);
205
+ const currentLanguageSignal = i18nService.currentLanguageSignal;
206
+ const languageContributionsSignal = i18nService.languageContributionsSignal;
207
+ const i18n = (namespace2) => i18nService.i18n(namespace2);
208
+ const i18nLazy = (namespace2) => i18nService.i18nLazy(namespace2);
209
+ const TOPIC_WORKSPACE_CHANGED = "events/filesys/workspaceChanged";
210
+ const TOPIC_WORKSPACE_CONNECTED = "events/filesys/workspaceConnected";
211
+ class Resource {
212
+ constructor() {
213
+ this.state = {};
214
+ }
215
+ getWorkspacePath() {
216
+ const paths = [];
217
+ let current = this;
218
+ let root;
219
+ while (current) {
220
+ paths.push(current.getName());
221
+ const parent = current.getParent();
222
+ if (!parent) root = current;
223
+ current = parent;
224
+ }
225
+ paths.reverse();
226
+ const workspace = typeof workspaceService?.getWorkspaceSync === "function" ? workspaceService.getWorkspaceSync() : void 0;
227
+ if (workspace && root && "isDirectChild" in workspace && typeof workspace.isDirectChild === "function" && workspace.isDirectChild(root)) {
228
+ const folderName = workspace.getFolderNameForDirectory(root);
229
+ if (folderName && paths.length > 0) return paths.length > 1 ? folderName + "/" + paths.slice(1).join("/") : folderName;
230
+ }
231
+ paths.shift();
232
+ return paths.join("/");
233
+ }
234
+ getWorkspace() {
235
+ let current = this;
236
+ while (current) {
237
+ const parent = current.getParent();
238
+ if (parent) {
239
+ current = parent;
240
+ } else {
241
+ break;
242
+ }
243
+ }
244
+ return current;
245
+ }
246
+ }
247
+ var FileContentType = /* @__PURE__ */ ((FileContentType2) => {
248
+ FileContentType2[FileContentType2["TEXT"] = 0] = "TEXT";
249
+ FileContentType2[FileContentType2["BINARY"] = 1] = "BINARY";
250
+ return FileContentType2;
251
+ })(FileContentType || {});
252
+ var FileContentEncoding = /* @__PURE__ */ ((FileContentEncoding2) => {
253
+ FileContentEncoding2[FileContentEncoding2["BASE64"] = 0] = "BASE64";
254
+ return FileContentEncoding2;
255
+ })(FileContentEncoding || {});
256
+ class File extends Resource {
257
+ }
258
+ class Directory extends Resource {
259
+ }
260
+ class StringFile extends File {
261
+ constructor(contents, name) {
262
+ super();
263
+ this.contents = contents;
264
+ this.name = name;
265
+ }
266
+ async getContents(_options) {
267
+ return this.contents;
268
+ }
269
+ async saveContents(contents, _options) {
270
+ this.contents = contents;
271
+ }
272
+ async size() {
273
+ return this.contents.length || null;
274
+ }
275
+ async copyTo(_targetPath) {
276
+ throw Error(`Not supported`);
277
+ }
278
+ delete(_name, _recursive) {
279
+ throw Error(`Not supported`);
280
+ }
281
+ async rename(_newName) {
282
+ throw Error(`Not supported`);
283
+ }
284
+ getName() {
285
+ return this.name;
286
+ }
287
+ getParent() {
288
+ return void 0;
289
+ }
290
+ }
291
+ class CompositeDirectory extends Directory {
292
+ constructor(directories, displayName = "/") {
293
+ super();
294
+ this.displayName = displayName;
295
+ this.entriesByName = new Map(directories.map((d) => [d.getName(), d]));
296
+ }
297
+ getFolderNameForDirectory(dir) {
298
+ for (const [name, d] of this.entriesByName) {
299
+ if (d === dir) return name;
300
+ }
301
+ return void 0;
302
+ }
303
+ isDirectChild(dir) {
304
+ return this.getFolderNameForDirectory(dir) !== void 0;
305
+ }
306
+ getName() {
307
+ return this.displayName;
308
+ }
309
+ getParent() {
310
+ return void 0;
311
+ }
312
+ async listChildren(_forceRefresh) {
313
+ return Array.from(this.entriesByName.values());
314
+ }
315
+ async getResource(path, options) {
316
+ if (!path || !path.trim()) {
317
+ return null;
318
+ }
319
+ const idx = path.indexOf("/");
320
+ const folderName = idx >= 0 ? path.slice(0, idx).trim() : path.trim();
321
+ const rest = idx >= 0 ? path.slice(idx + 1).trim() : "";
322
+ const dir = this.entriesByName.get(folderName);
323
+ if (!dir) {
324
+ return null;
325
+ }
326
+ if (!rest) {
327
+ return dir;
328
+ }
329
+ return dir.getResource(rest, options);
330
+ }
331
+ touch() {
332
+ for (const dir of this.entriesByName.values()) {
333
+ dir.touch();
334
+ }
335
+ }
336
+ async delete(_name, _recursive) {
337
+ throw new Error("Delete not supported on workspace root");
338
+ }
339
+ async copyTo(_targetPath) {
340
+ throw new Error("Copy not supported on workspace root");
341
+ }
342
+ async rename(_newName) {
343
+ throw new Error("Rename not supported on workspace root");
344
+ }
345
+ getFolderByName(name) {
346
+ return this.entriesByName.get(name);
347
+ }
348
+ }
349
+ const LEGACY_WORKSPACE_KEY = "workspace_data";
350
+ const _WorkspaceService = class _WorkspaceService {
351
+ constructor() {
352
+ this._currentWorkspace = void 0;
353
+ this.folders = [];
354
+ this.contributions = /* @__PURE__ */ new Map();
355
+ let resolveInit;
356
+ this.initPromise = new Promise((resolve) => {
357
+ resolveInit = resolve;
358
+ });
359
+ this.loadPersistedWorkspace(resolveInit);
360
+ }
361
+ getWorkspaceSync() {
362
+ return this._currentWorkspace;
363
+ }
364
+ registerContribution(contribution) {
365
+ this.contributions.set(contribution.type, contribution);
366
+ console.log(`Workspace contribution registered: ${contribution.name} (${contribution.type})`);
367
+ }
368
+ getContributions() {
369
+ return Array.from(this.contributions.values());
370
+ }
371
+ async loadPersistedWorkspace(resolveInit) {
372
+ try {
373
+ const raw = await persistenceService.getObject(LEGACY_WORKSPACE_KEY);
374
+ if (!raw) {
375
+ this.workspace = Promise.resolve(void 0);
376
+ this._currentWorkspace = void 0;
377
+ }
378
+ if (raw?.folders && Array.isArray(raw.folders) && raw.folders.length > 0) {
379
+ const normalized = raw.folders.map((f) => ({ type: f.type, data: f.data }));
380
+ await this.resolveFolders(normalized);
381
+ const composite = this.buildComposite();
382
+ this.workspace = Promise.resolve(composite);
383
+ this._currentWorkspace = composite ?? void 0;
384
+ if (composite) {
385
+ publish(TOPIC_WORKSPACE_CONNECTED, composite);
386
+ }
387
+ resolveInit();
388
+ return;
389
+ }
390
+ if (raw && raw.type && raw.data !== void 0) {
391
+ const contribution = this.contributions.get(raw.type);
392
+ if (contribution?.restore) {
393
+ try {
394
+ const dir = await contribution.restore(raw.data);
395
+ if (dir) {
396
+ this.folders = [{ type: raw.type, data: raw.data, directory: dir }];
397
+ const comp = this.buildComposite();
398
+ this.workspace = Promise.resolve(comp);
399
+ this._currentWorkspace = comp ?? void 0;
400
+ this.currentType = raw.type;
401
+ await this.persistFolders();
402
+ publish(TOPIC_WORKSPACE_CONNECTED, comp);
403
+ }
404
+ } catch (error) {
405
+ console.error("Failed to restore legacy workspace:", error);
406
+ }
407
+ }
408
+ }
409
+ if (!this.workspace) {
410
+ this.workspace = Promise.resolve(void 0);
411
+ this._currentWorkspace = void 0;
412
+ }
413
+ resolveInit();
414
+ } finally {
415
+ if (this.folders.length === 0) {
416
+ try {
417
+ await this.connectFolder({ indexeddb: true, name: _WorkspaceService.DEFAULT_INDEXEDDB_FOLDER_NAME });
418
+ } catch (e) {
419
+ console.warn("Failed to connect default IndexedDB folder", e);
420
+ }
421
+ }
422
+ }
423
+ }
424
+ async resolveFolders(persisted) {
425
+ this.folders = [];
426
+ for (const folder of persisted) {
427
+ const contribution = this.contributions.get(folder.type);
428
+ if (!contribution?.restore) {
429
+ continue;
430
+ }
431
+ try {
432
+ const dir = await contribution.restore(folder.data);
433
+ if (dir) {
434
+ this.folders.push({ type: folder.type, data: folder.data, directory: dir });
435
+ }
436
+ } catch (error) {
437
+ console.warn(`Failed to restore folder (${folder.type}):`, error);
438
+ }
439
+ }
440
+ }
441
+ buildComposite() {
442
+ if (this.folders.length === 0) {
443
+ return void 0;
444
+ }
445
+ return new CompositeDirectory(this.folders.map((f) => f.directory));
446
+ }
447
+ async persistFolders() {
448
+ const toPersist = this.folders.length > 0 ? { folders: this.folders.map((f) => ({ type: f.type, data: f.data })) } : null;
449
+ await persistenceService.persistObject(LEGACY_WORKSPACE_KEY, toPersist);
450
+ await persistenceService.persistObject("workspace", null);
451
+ }
452
+ async getFolders() {
453
+ await this.initPromise;
454
+ return this.folders.map((f) => ({ name: f.directory.getName(), type: f.type }));
455
+ }
456
+ async getFolderInfoForDirectory(directory) {
457
+ await this.initPromise;
458
+ const folder = this.folders.find((f) => f.directory === directory);
459
+ if (!folder) {
460
+ return void 0;
461
+ }
462
+ const name = folder.data && typeof folder.data === "object" && folder.data.name || folder.directory.getName();
463
+ const contribution = this.contributions.get(folder.type);
464
+ const backendName = contribution?.name ?? folder.type;
465
+ return { name, type: folder.type, backendName };
466
+ }
467
+ /**
468
+ * Update persisted metadata for a workspace root directory.
469
+ * Currently used to keep display names of roots (e.g. IndexedDB) in sync
470
+ * with their in-memory Directory instances.
471
+ */
472
+ async updateFolderName(directory, name) {
473
+ await this.initPromise;
474
+ const folder = this.folders.find((f) => f.directory === directory);
475
+ if (!folder) {
476
+ return;
477
+ }
478
+ if (folder.data && typeof folder.data === "object") {
479
+ folder.data = { ...folder.data, name };
480
+ } else {
481
+ folder.data = { name };
482
+ }
483
+ await this.persistFolders();
484
+ const composite = this.buildComposite();
485
+ this.workspace = Promise.resolve(composite);
486
+ this._currentWorkspace = composite ?? void 0;
487
+ publish(TOPIC_WORKSPACE_CONNECTED, composite);
488
+ }
489
+ async connectFolder(input) {
490
+ await this.initPromise;
491
+ const contribution = Array.from(this.contributions.values()).find((c) => c.canHandle(input));
492
+ if (!contribution) {
493
+ throw new Error("No workspace contribution can handle this input");
494
+ }
495
+ const directory = await contribution.connect(input);
496
+ const data = contribution.persist ? await contribution.persist(directory) : input;
497
+ this.folders.push({ type: contribution.type, data, directory });
498
+ await this.persistFolders();
499
+ this.currentType = this.folders.length === 1 ? contribution.type : void 0;
500
+ const composite = this.buildComposite();
501
+ this.workspace = Promise.resolve(composite);
502
+ this._currentWorkspace = composite;
503
+ publish(TOPIC_WORKSPACE_CONNECTED, composite);
504
+ return composite;
505
+ }
506
+ async disconnectFolder(directory) {
507
+ await this.initPromise;
508
+ const idx = this.folders.findIndex((f) => f.directory === directory);
509
+ if (idx < 0) {
510
+ return;
511
+ }
512
+ this.folders.splice(idx, 1);
513
+ await this.persistFolders();
514
+ if (this.folders.length > 0) {
515
+ this.currentType = this.folders[0].type;
516
+ } else {
517
+ this.currentType = void 0;
518
+ }
519
+ const composite = this.buildComposite();
520
+ this.workspace = Promise.resolve(composite);
521
+ this._currentWorkspace = composite ?? void 0;
522
+ publish(TOPIC_WORKSPACE_CONNECTED, composite);
523
+ }
524
+ async connectWorkspace(input) {
525
+ return this.connectFolder(input);
526
+ }
527
+ async getWorkspace() {
528
+ await this.initPromise;
529
+ if (!this.workspace) {
530
+ throw new Error("No workspace connected.");
531
+ }
532
+ return await this.workspace;
533
+ }
534
+ isConnected() {
535
+ return this.folders.length > 0;
536
+ }
537
+ getWorkspaceType() {
538
+ return this.currentType;
539
+ }
540
+ async disconnectWorkspace() {
541
+ await this.initPromise;
542
+ this.workspace = Promise.resolve(void 0);
543
+ this._currentWorkspace = void 0;
544
+ this.folders = [];
545
+ this.currentType = void 0;
546
+ await this.persistFolders();
547
+ publish(TOPIC_WORKSPACE_CONNECTED, void 0);
548
+ }
549
+ };
550
+ _WorkspaceService.DEFAULT_INDEXEDDB_FOLDER_NAME = "My Folder";
551
+ let WorkspaceService = _WorkspaceService;
552
+ const workspaceService = new WorkspaceService();
553
+ rootContext.put("workspaceService", workspaceService);
554
+ class FileSysFileHandleResource extends File {
555
+ constructor(fileHandle, parent) {
556
+ super();
557
+ this.fileHandle = fileHandle;
558
+ this.parent = parent;
559
+ }
560
+ getName() {
561
+ return this.fileHandle.name;
562
+ }
563
+ getParent() {
564
+ return this.parent;
565
+ }
566
+ async delete() {
567
+ return this.getParent().delete(this.getName());
568
+ }
569
+ async getContents(options) {
570
+ const file = await this.fileHandle.getFile();
571
+ if (!options || options?.contentType == FileContentType.TEXT) {
572
+ return await file.text();
573
+ }
574
+ if (options?.encoding == FileContentEncoding.BASE64 || options?.uri) {
575
+ return URL.createObjectURL(file);
576
+ }
577
+ if (options?.blob) {
578
+ return file;
579
+ }
580
+ return file.stream();
581
+ }
582
+ async size() {
583
+ try {
584
+ const file = await this.fileHandle.getFile();
585
+ return file.size;
586
+ } catch {
587
+ return null;
588
+ }
589
+ }
590
+ async saveContents(contents, _options) {
591
+ const writable = await this.fileHandle.createWritable();
592
+ if (contents && typeof contents.pipeTo === "function") {
593
+ await contents.pipeTo(writable);
594
+ } else {
595
+ const writer = writable.getWriter();
596
+ try {
597
+ await writer.write(contents);
598
+ } finally {
599
+ await writer.close();
600
+ }
601
+ }
602
+ }
603
+ async copyTo(targetPath) {
604
+ const contents = await this.getContents({ blob: true });
605
+ const targetFile = await this.getWorkspace().getResource(targetPath, { create: true });
606
+ await targetFile.saveContents(contents);
607
+ }
608
+ async rename(newName) {
609
+ const parent = this.getParent();
610
+ if (!parent) {
611
+ throw new Error("Cannot rename root resource");
612
+ }
613
+ if (this.getName() === newName) {
614
+ return;
615
+ }
616
+ if (!("move" in this.fileHandle) || typeof this.fileHandle.move !== "function") {
617
+ throw new Error("File rename not supported in this browser. Please use a browser with File System Access API move() support.");
618
+ }
619
+ try {
620
+ await this.fileHandle.move(newName);
621
+ } catch (error) {
622
+ if (error.name === "NotAllowedError" || error.message?.includes("not allowed") || error.message?.includes("user agent")) {
623
+ throw new Error("File rename failed: The operation took too long and user activation expired. Please try again.");
624
+ }
625
+ throw error;
626
+ }
627
+ await parent.listChildren(true);
628
+ publish(TOPIC_WORKSPACE_CHANGED, workspaceService.getWorkspaceSync() ?? this.getWorkspace());
629
+ }
630
+ }
631
+ class FileSysDirHandleResource extends Directory {
632
+ constructor(dirHandle, parent) {
633
+ super();
634
+ this.dirHandle = dirHandle;
635
+ this.parent = parent;
636
+ }
637
+ getHandle() {
638
+ return this.dirHandle;
639
+ }
640
+ getParent() {
641
+ return this.parent;
642
+ }
643
+ getName() {
644
+ return this.dirHandle.name;
645
+ }
646
+ async listChildren(forceRefresh = false) {
647
+ if (forceRefresh || !this.files) {
648
+ if (this.loadingPromise) {
649
+ return this.loadingPromise;
650
+ }
651
+ this.loadingPromise = (async () => {
652
+ try {
653
+ const files = {};
654
+ try {
655
+ for await (const entry of this.dirHandle.values()) {
656
+ const isFile = entry.kind === "file";
657
+ const child = isFile ? new FileSysFileHandleResource(entry, this) : new FileSysDirHandleResource(entry, this);
658
+ files[child.getName()] = child;
659
+ }
660
+ } catch (error) {
661
+ if (error.name === "NotFoundError") {
662
+ this.files = {};
663
+ return [];
664
+ }
665
+ throw error;
666
+ }
667
+ this.files = files;
668
+ return Object.values(this.files);
669
+ } finally {
670
+ this.loadingPromise = void 0;
671
+ }
672
+ })();
673
+ return this.loadingPromise;
674
+ }
675
+ return Object.values(this.files);
676
+ }
677
+ async getResource(path, options) {
678
+ if (!path) {
679
+ throw new Error("No path provided");
680
+ }
681
+ const segments = path.split("/");
682
+ let currentResource = this;
683
+ let workspaceChanged = false;
684
+ try {
685
+ for (let i = 0; i < segments.length; i++) {
686
+ let segment = segments[i];
687
+ if (segment) {
688
+ segment = segment.trim();
689
+ }
690
+ if (!segment) {
691
+ break;
692
+ }
693
+ if (currentResource instanceof FileSysDirHandleResource) {
694
+ await currentResource.listChildren();
695
+ if (!currentResource.files) {
696
+ return null;
697
+ }
698
+ const next = currentResource.files[segment];
699
+ if (!next) {
700
+ if (options?.create) {
701
+ workspaceChanged = true;
702
+ if (i < segments.length - 1) {
703
+ try {
704
+ const newDirHandle = await currentResource.dirHandle.getDirectoryHandle(segment, { create: true });
705
+ const nextResource = new FileSysDirHandleResource(newDirHandle, currentResource);
706
+ currentResource.files[segment] = nextResource;
707
+ currentResource = nextResource;
708
+ if (currentResource instanceof FileSysDirHandleResource) {
709
+ await currentResource.listChildren();
710
+ }
711
+ continue;
712
+ } catch (error) {
713
+ if (error.name === "NotFoundError") {
714
+ throw new Error(`Directory not found or not accessible: ${segments.slice(0, i + 1).join("/")}`);
715
+ }
716
+ throw error;
717
+ }
718
+ } else {
719
+ try {
720
+ const newFileHandle = await currentResource.dirHandle.getFileHandle(segment, { create: true });
721
+ const nextResource = new FileSysFileHandleResource(newFileHandle, currentResource);
722
+ currentResource.files[segment] = nextResource;
723
+ return nextResource;
724
+ } catch (error) {
725
+ if (error.name === "NotFoundError") {
726
+ throw new Error(`File not found or not accessible: ${segments.join("/")}`);
727
+ }
728
+ throw error;
729
+ }
730
+ }
731
+ } else {
732
+ return null;
733
+ }
734
+ } else {
735
+ currentResource = next;
736
+ }
737
+ }
738
+ }
739
+ } finally {
740
+ if (workspaceChanged) {
741
+ publish(TOPIC_WORKSPACE_CHANGED, workspaceService.getWorkspaceSync() ?? this.getWorkspace());
742
+ }
743
+ }
744
+ return currentResource;
745
+ }
746
+ touch() {
747
+ publish(TOPIC_WORKSPACE_CHANGED, workspaceService.getWorkspaceSync() ?? this.getWorkspace());
748
+ }
749
+ async delete(name, recursive = true) {
750
+ if (!name) {
751
+ const parent = this.getParent();
752
+ if (parent instanceof FileSysDirHandleResource) {
753
+ await parent.listChildren();
754
+ if (parent.files) {
755
+ delete parent.files[this.getName()];
756
+ }
757
+ }
758
+ this.files = void 0;
759
+ this.loadingPromise = void 0;
760
+ return parent?.delete(this.getName());
761
+ }
762
+ return this.dirHandle.removeEntry(name, {
763
+ recursive
764
+ }).then(async () => {
765
+ if (this.files) {
766
+ delete this.files[name];
767
+ }
768
+ publish(TOPIC_WORKSPACE_CHANGED, workspaceService.getWorkspaceSync() ?? this.getWorkspace());
769
+ });
770
+ }
771
+ async copyTo(targetPath) {
772
+ for (const resource of await this.listChildren()) {
773
+ const targetResourceName = [targetPath, resource.getName()].join("/");
774
+ await resource.copyTo(targetResourceName);
775
+ }
776
+ }
777
+ async rename(newName) {
778
+ const parent = this.getParent();
779
+ if (!parent) {
780
+ throw new Error("Cannot rename workspace root");
781
+ }
782
+ if (this.getName() === newName) {
783
+ return;
784
+ }
785
+ if (!("move" in this.dirHandle) || typeof this.dirHandle.move !== "function") {
786
+ throw new Error("Directory rename not supported in this browser. Please use a browser with File System Access API move() support.");
787
+ }
788
+ try {
789
+ await this.dirHandle.move(newName);
790
+ } catch (error) {
791
+ if (error.name === "NotAllowedError" || error.message?.includes("not allowed") || error.message?.includes("user agent")) {
792
+ throw new Error("Directory rename failed: The operation took too long and user activation expired. Please try again.");
793
+ }
794
+ throw error;
795
+ }
796
+ await parent.listChildren(true);
797
+ publish(TOPIC_WORKSPACE_CHANGED, workspaceService.getWorkspaceSync() ?? this.getWorkspace());
798
+ }
799
+ }
800
+ workspaceService.registerContribution({
801
+ type: "filesystem",
802
+ name: "fs",
803
+ canHandle(input) {
804
+ return input && "kind" in input && input.kind === "directory";
805
+ },
806
+ async connect(input) {
807
+ return new FileSysDirHandleResource(input);
808
+ },
809
+ async restore(data) {
810
+ if (data && "kind" in data && data.kind === "directory") {
811
+ return new FileSysDirHandleResource(data, void 0);
812
+ }
813
+ return void 0;
814
+ },
815
+ async persist(workspace) {
816
+ if (workspace instanceof FileSysDirHandleResource) {
817
+ return workspace.getHandle();
818
+ }
819
+ return null;
820
+ }
821
+ });
822
+ const fsAccess = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
823
+ __proto__: null,
824
+ FileSysDirHandleResource,
825
+ FileSysFileHandleResource
826
+ }, Symbol.toStringTag, { value: "Module" }));
827
+ const OPFS_DISPLAY_NAME = ".opfs";
828
+ async function getOPFSRoot() {
829
+ if (typeof navigator === "undefined" || !navigator.storage?.getDirectory) {
830
+ throw new Error("OPFS is not available in this environment");
831
+ }
832
+ return await navigator.storage.getDirectory();
833
+ }
834
+ class OPFSRootDirectory extends Directory {
835
+ constructor(inner) {
836
+ super();
837
+ this.inner = inner;
838
+ }
839
+ getName() {
840
+ return OPFS_DISPLAY_NAME;
841
+ }
842
+ getParent() {
843
+ return this.inner.getParent();
844
+ }
845
+ async listChildren(forceRefresh) {
846
+ return this.inner.listChildren(forceRefresh);
847
+ }
848
+ async getResource(path, options) {
849
+ return this.inner.getResource(path, options);
850
+ }
851
+ touch() {
852
+ this.inner.touch();
853
+ }
854
+ async delete(name, recursive) {
855
+ return this.inner.delete(name, recursive);
856
+ }
857
+ async copyTo(targetPath) {
858
+ return this.inner.copyTo(targetPath);
859
+ }
860
+ async rename(newName) {
861
+ return this.inner.rename(newName);
862
+ }
863
+ }
864
+ workspaceService.registerContribution({
865
+ type: "opfs",
866
+ name: "opfs",
867
+ canHandle(input) {
868
+ return input && typeof input === "object" && input.opfs === true;
869
+ },
870
+ async connect(_input) {
871
+ const root = await getOPFSRoot();
872
+ const fsModule = await Promise.resolve().then(() => fsAccess);
873
+ const FileSysDirHandleResource2 = fsModule.FileSysDirHandleResource;
874
+ const inner = new FileSysDirHandleResource2(root);
875
+ return new OPFSRootDirectory(inner);
876
+ },
877
+ async restore(data) {
878
+ if (data && typeof data === "object" && data.opfs === true) {
879
+ const root = await getOPFSRoot();
880
+ const fsModule = await Promise.resolve().then(() => fsAccess);
881
+ const FileSysDirHandleResource2 = fsModule.FileSysDirHandleResource;
882
+ const inner = new FileSysDirHandleResource2(root);
883
+ return new OPFSRootDirectory(inner);
884
+ }
885
+ return void 0;
886
+ },
887
+ async persist(workspace) {
888
+ if (workspace instanceof OPFSRootDirectory) {
889
+ return { opfs: true };
890
+ }
891
+ return null;
892
+ }
893
+ });
894
+ const IDB_WORKSPACE_DB_NAME = "eclipse-lyra-workspace-idb";
895
+ const IDB_WORKSPACE_STORE_NAME = "files";
896
+ let idbWorkspacePromise = null;
897
+ async function getWorkspaceIDB() {
898
+ if (typeof indexedDB === "undefined") {
899
+ throw new Error("IndexedDB is not available in this environment");
900
+ }
901
+ if (!idbWorkspacePromise) {
902
+ idbWorkspacePromise = new Promise((resolve, reject) => {
903
+ const request = indexedDB.open(IDB_WORKSPACE_DB_NAME, 1);
904
+ request.onerror = () => reject(request.error);
905
+ request.onsuccess = () => resolve(request.result);
906
+ request.onupgradeneeded = (e) => {
907
+ const db = e.target.result;
908
+ if (!db.objectStoreNames.contains(IDB_WORKSPACE_STORE_NAME)) {
909
+ db.createObjectStore(IDB_WORKSPACE_STORE_NAME);
910
+ }
911
+ };
912
+ });
913
+ }
914
+ return idbWorkspacePromise;
915
+ }
916
+ async function getNextIndexedDBName() {
917
+ const baseName = "IndexedDB";
918
+ const folders = await workspaceService.getFolders();
919
+ const existingNames = new Set(
920
+ folders.filter((f) => f.type === "indexeddb").map((f) => f.name)
921
+ );
922
+ if (!existingNames.has(baseName)) {
923
+ return baseName;
924
+ }
925
+ let index = 1;
926
+ while (existingNames.has(`${baseName} (${index})`)) {
927
+ index += 1;
928
+ }
929
+ return `${baseName} (${index})`;
930
+ }
931
+ function normalizePath(path) {
932
+ if (!path) return "";
933
+ return path.split("/").filter(Boolean).join("/");
934
+ }
935
+ function joinPath(base, name) {
936
+ const cleanBase = normalizePath(base);
937
+ const cleanName = normalizePath(name);
938
+ if (!cleanBase) return cleanName;
939
+ if (!cleanName) return cleanBase;
940
+ return `${cleanBase}/${cleanName}`;
941
+ }
942
+ function storageKey(rootId, path) {
943
+ const norm = normalizePath(path);
944
+ return norm ? `${rootId}/${norm}` : rootId;
945
+ }
946
+ function storagePrefix(rootId, path) {
947
+ const norm = normalizePath(path);
948
+ return norm ? `${rootId}/${norm}/` : `${rootId}/`;
949
+ }
950
+ async function idbGet(rootId, path) {
951
+ const db = await getWorkspaceIDB();
952
+ const tx = db.transaction(IDB_WORKSPACE_STORE_NAME, "readonly");
953
+ const store = tx.objectStore(IDB_WORKSPACE_STORE_NAME);
954
+ const key = path ? storageKey(rootId, path) : rootId;
955
+ return await new Promise((resolve, reject) => {
956
+ const req = store.get(key);
957
+ req.onsuccess = () => resolve(req.result);
958
+ req.onerror = () => reject(req.error);
959
+ });
960
+ }
961
+ async function idbPut(rootId, path, entry) {
962
+ const db = await getWorkspaceIDB();
963
+ const tx = db.transaction(IDB_WORKSPACE_STORE_NAME, "readwrite");
964
+ const store = tx.objectStore(IDB_WORKSPACE_STORE_NAME);
965
+ const key = path ? storageKey(rootId, path) : rootId;
966
+ await new Promise((resolve, reject) => {
967
+ const req = store.put(entry, key);
968
+ req.onsuccess = () => resolve();
969
+ req.onerror = () => reject(req.error);
970
+ });
971
+ }
972
+ async function idbDelete(rootId, path) {
973
+ const db = await getWorkspaceIDB();
974
+ const tx = db.transaction(IDB_WORKSPACE_STORE_NAME, "readwrite");
975
+ const store = tx.objectStore(IDB_WORKSPACE_STORE_NAME);
976
+ const key = path ? storageKey(rootId, path) : rootId;
977
+ await new Promise((resolve, reject) => {
978
+ const req = store.delete(key);
979
+ req.onsuccess = () => resolve();
980
+ req.onerror = () => reject(req.error);
981
+ });
982
+ }
983
+ async function idbDeleteTree(rootId, rootPath) {
984
+ const db = await getWorkspaceIDB();
985
+ const tx = db.transaction(IDB_WORKSPACE_STORE_NAME, "readwrite");
986
+ const store = tx.objectStore(IDB_WORKSPACE_STORE_NAME);
987
+ const prefix = storageKey(rootId, rootPath);
988
+ const prefixWithSlash = prefix + "/";
989
+ const cursorReq = store.openCursor();
990
+ await new Promise((resolve, reject) => {
991
+ cursorReq.onerror = () => reject(cursorReq.error);
992
+ cursorReq.onsuccess = (ev) => {
993
+ const cursor = ev.target.result;
994
+ if (!cursor) {
995
+ resolve();
996
+ return;
997
+ }
998
+ const key = String(cursor.key);
999
+ if (key === prefix || key.startsWith(prefixWithSlash)) {
1000
+ cursor.delete();
1001
+ }
1002
+ cursor.continue();
1003
+ };
1004
+ });
1005
+ }
1006
+ async function idbRenameTree(rootId, oldPath, newPath) {
1007
+ const db = await getWorkspaceIDB();
1008
+ const tx = db.transaction(IDB_WORKSPACE_STORE_NAME, "readwrite");
1009
+ const store = tx.objectStore(IDB_WORKSPACE_STORE_NAME);
1010
+ const oldPrefix = storageKey(rootId, oldPath);
1011
+ const newPrefix = storageKey(rootId, newPath);
1012
+ const cursorReq = store.openCursor();
1013
+ const operations = [];
1014
+ await new Promise((resolve, reject) => {
1015
+ cursorReq.onerror = () => reject(cursorReq.error);
1016
+ cursorReq.onsuccess = (ev) => {
1017
+ const cursor = ev.target.result;
1018
+ if (!cursor) {
1019
+ resolve();
1020
+ return;
1021
+ }
1022
+ const key = String(cursor.key);
1023
+ if (key === oldPrefix || key.startsWith(oldPrefix + "/")) {
1024
+ const suffix = key.slice(oldPrefix.length);
1025
+ const newKey = newPrefix + suffix;
1026
+ const value = cursor.value;
1027
+ operations.push(() => {
1028
+ cursor.delete();
1029
+ store.put(value, newKey);
1030
+ });
1031
+ }
1032
+ cursor.continue();
1033
+ };
1034
+ });
1035
+ for (const op of operations) {
1036
+ op();
1037
+ }
1038
+ }
1039
+ async function idbListChildrenOfDir(rootId, dirPath) {
1040
+ const db = await getWorkspaceIDB();
1041
+ const tx = db.transaction(IDB_WORKSPACE_STORE_NAME, "readonly");
1042
+ const store = tx.objectStore(IDB_WORKSPACE_STORE_NAME);
1043
+ const prefix = storagePrefix(rootId, dirPath);
1044
+ const cursorReq = store.openCursor();
1045
+ const dirNames = /* @__PURE__ */ new Set();
1046
+ const fileEntries = /* @__PURE__ */ new Map();
1047
+ await new Promise((resolve, reject) => {
1048
+ cursorReq.onerror = () => reject(cursorReq.error);
1049
+ cursorReq.onsuccess = (ev) => {
1050
+ const cursor = ev.target.result;
1051
+ if (!cursor) {
1052
+ resolve();
1053
+ return;
1054
+ }
1055
+ const key = String(cursor.key);
1056
+ const entry = cursor.value;
1057
+ if (!key.startsWith(prefix)) {
1058
+ cursor.continue();
1059
+ return;
1060
+ }
1061
+ const rest = key.slice(prefix.length);
1062
+ if (!rest) {
1063
+ cursor.continue();
1064
+ return;
1065
+ }
1066
+ const idx = rest.indexOf("/");
1067
+ const childName = idx === -1 ? rest : rest.slice(0, idx);
1068
+ if (idx === -1) {
1069
+ if (entry.type === "dir") {
1070
+ dirNames.add(childName);
1071
+ } else {
1072
+ fileEntries.set(childName, entry);
1073
+ }
1074
+ } else {
1075
+ dirNames.add(childName);
1076
+ }
1077
+ cursor.continue();
1078
+ };
1079
+ });
1080
+ const result = [];
1081
+ for (const name of dirNames) {
1082
+ result.push({ name, entry: { type: "dir" }, type: "dir" });
1083
+ }
1084
+ for (const [name, entry] of fileEntries) {
1085
+ if (!dirNames.has(name)) {
1086
+ result.push({ name, entry, type: "file" });
1087
+ }
1088
+ }
1089
+ return result;
1090
+ }
1091
+ function getRootIdFromParent(parent) {
1092
+ return parent instanceof IDBDirectoryResource ? parent.getRootId() : "";
1093
+ }
1094
+ class IDBFileResource extends File {
1095
+ constructor(path, parent) {
1096
+ super();
1097
+ this.path = normalizePath(path);
1098
+ this.parent = parent;
1099
+ }
1100
+ getName() {
1101
+ const parts = this.path.split("/");
1102
+ return parts[parts.length - 1] || "";
1103
+ }
1104
+ getParent() {
1105
+ return this.parent;
1106
+ }
1107
+ getRootId() {
1108
+ return getRootIdFromParent(this.parent);
1109
+ }
1110
+ async delete() {
1111
+ await idbDelete(this.getRootId(), this.path);
1112
+ publish(TOPIC_WORKSPACE_CHANGED, workspaceService.getWorkspaceSync() ?? this.getWorkspace());
1113
+ }
1114
+ async getContents(options) {
1115
+ const entry = await idbGet(this.getRootId(), this.path);
1116
+ let raw = entry?.content;
1117
+ if (typeof raw === "string") {
1118
+ const migratedBlob = new Blob([raw], { type: entry?.mimeType || "text/plain" });
1119
+ raw = migratedBlob;
1120
+ if (entry) {
1121
+ entry.content = migratedBlob;
1122
+ await idbPut(this.getRootId(), this.path, entry);
1123
+ }
1124
+ }
1125
+ if (!options || options.contentType === FileContentType.TEXT) {
1126
+ if (!raw) {
1127
+ return "";
1128
+ }
1129
+ return await raw.text();
1130
+ }
1131
+ let blob;
1132
+ if (raw) {
1133
+ blob = raw;
1134
+ } else {
1135
+ blob = new Blob([], { type: entry?.mimeType });
1136
+ }
1137
+ if (options.blob) {
1138
+ return blob;
1139
+ }
1140
+ if (options.uri) {
1141
+ return URL.createObjectURL(blob);
1142
+ }
1143
+ return blob.stream();
1144
+ }
1145
+ async saveContents(contents, _options) {
1146
+ let blob;
1147
+ let mimeType;
1148
+ if (contents instanceof Blob) {
1149
+ blob = contents;
1150
+ mimeType = contents.type || void 0;
1151
+ } else if (typeof contents === "string") {
1152
+ mimeType = "text/plain";
1153
+ blob = new Blob([contents], { type: mimeType });
1154
+ } else {
1155
+ const text = String(contents ?? "");
1156
+ mimeType = "text/plain";
1157
+ blob = new Blob([text], { type: mimeType });
1158
+ }
1159
+ await idbPut(this.getRootId(), this.path, { type: "file", content: blob, mimeType });
1160
+ publish(TOPIC_WORKSPACE_CHANGED, workspaceService.getWorkspaceSync() ?? this.getWorkspace());
1161
+ }
1162
+ async size() {
1163
+ const entry = await idbGet(this.getRootId(), this.path);
1164
+ const content = entry?.content;
1165
+ if (!content) return null;
1166
+ return content.size;
1167
+ }
1168
+ async copyTo(targetPath) {
1169
+ const contents = await this.getContents({ blob: true });
1170
+ const targetFile = await this.getWorkspace().getResource(targetPath, { create: true });
1171
+ if (!targetFile) {
1172
+ throw new Error(`Failed to create target file: ${targetPath}`);
1173
+ }
1174
+ await targetFile.saveContents(contents);
1175
+ }
1176
+ async rename(newName) {
1177
+ if (this.getName() === newName) {
1178
+ return;
1179
+ }
1180
+ const parentDir = this.getParent();
1181
+ const parentPath = parentDir instanceof IDBDirectoryResource ? parentDir.getPath() : "";
1182
+ const newPath = joinPath(parentPath, newName);
1183
+ const rootId = this.getRootId();
1184
+ const entry = await idbGet(rootId, this.path);
1185
+ if (!entry) {
1186
+ throw new Error("File not found in IndexedDB");
1187
+ }
1188
+ await idbDelete(rootId, this.path);
1189
+ await idbPut(rootId, newPath, entry);
1190
+ publish(TOPIC_WORKSPACE_CHANGED, workspaceService.getWorkspaceSync() ?? this.getWorkspace());
1191
+ }
1192
+ }
1193
+ class IDBDirectoryResource extends Directory {
1194
+ constructor(path, parent) {
1195
+ super();
1196
+ this.path = normalizePath(path);
1197
+ this.parent = parent;
1198
+ }
1199
+ getPath() {
1200
+ return this.path;
1201
+ }
1202
+ getName() {
1203
+ if (!this.path) {
1204
+ return "";
1205
+ }
1206
+ const parts = this.path.split("/");
1207
+ return parts[parts.length - 1];
1208
+ }
1209
+ getParent() {
1210
+ return this.parent;
1211
+ }
1212
+ getRoot() {
1213
+ const p = this.getParent();
1214
+ if (!p) return this;
1215
+ return p.getRoot();
1216
+ }
1217
+ getRootId() {
1218
+ const r = this.getRoot();
1219
+ return r instanceof IDBRootDirectory ? r.getRootId() : "";
1220
+ }
1221
+ async listChildren(_forceRefresh) {
1222
+ const childrenInfo = await idbListChildrenOfDir(this.getRootId(), this.path);
1223
+ const result = [];
1224
+ for (const child of childrenInfo) {
1225
+ const childPath = joinPath(this.path, child.name);
1226
+ if (child.type === "dir") {
1227
+ result.push(new IDBDirectoryResource(childPath, this));
1228
+ } else {
1229
+ result.push(new IDBFileResource(childPath, this));
1230
+ }
1231
+ }
1232
+ return result;
1233
+ }
1234
+ async getResource(path, options) {
1235
+ if (!path) {
1236
+ throw new Error("No path provided");
1237
+ }
1238
+ const segments = path.split("/").filter((s) => s.trim());
1239
+ let currentDir = this;
1240
+ for (let i = 0; i < segments.length; i++) {
1241
+ const segment = segments[i];
1242
+ const isLast = i === segments.length - 1;
1243
+ const currentPath = currentDir.getPath();
1244
+ const candidatePath = joinPath(currentPath, segment);
1245
+ const rootId = this.getRootId();
1246
+ const entry = await idbGet(rootId, candidatePath);
1247
+ if (!entry) {
1248
+ if (!options?.create) {
1249
+ return null;
1250
+ }
1251
+ if (isLast) {
1252
+ await idbPut(rootId, candidatePath, { type: "file", content: new Blob([]) });
1253
+ publish(TOPIC_WORKSPACE_CHANGED, workspaceService.getWorkspaceSync() ?? this.getWorkspace());
1254
+ return new IDBFileResource(candidatePath, currentDir);
1255
+ }
1256
+ await idbPut(rootId, candidatePath, { type: "dir" });
1257
+ currentDir = new IDBDirectoryResource(candidatePath, currentDir);
1258
+ continue;
1259
+ }
1260
+ if (isLast) {
1261
+ if (entry.type === "dir") {
1262
+ return new IDBDirectoryResource(candidatePath, currentDir);
1263
+ }
1264
+ return new IDBFileResource(candidatePath, currentDir);
1265
+ }
1266
+ if (entry.type !== "dir") {
1267
+ return null;
1268
+ }
1269
+ currentDir = new IDBDirectoryResource(candidatePath, currentDir);
1270
+ }
1271
+ return currentDir;
1272
+ }
1273
+ touch() {
1274
+ publish(TOPIC_WORKSPACE_CHANGED, workspaceService.getWorkspaceSync() ?? this.getWorkspace());
1275
+ }
1276
+ async delete(name, _recursive = true) {
1277
+ if (!name) {
1278
+ const parent = this.getParent();
1279
+ if (parent instanceof IDBDirectoryResource) {
1280
+ await parent.delete(this.getName());
1281
+ return;
1282
+ }
1283
+ return;
1284
+ }
1285
+ const targetPath = joinPath(this.path, name);
1286
+ await idbDeleteTree(this.getRootId(), targetPath);
1287
+ publish(TOPIC_WORKSPACE_CHANGED, workspaceService.getWorkspaceSync() ?? this.getWorkspace());
1288
+ }
1289
+ async copyTo(targetPath) {
1290
+ for (const resource of await this.listChildren(false)) {
1291
+ const childTarget = [targetPath, resource.getName()].join("/");
1292
+ await resource.copyTo(childTarget);
1293
+ }
1294
+ }
1295
+ async rename(newName) {
1296
+ if (this.getName() === newName) {
1297
+ return;
1298
+ }
1299
+ const parentDir = this.getParent();
1300
+ if (!(parentDir instanceof IDBDirectoryResource)) {
1301
+ throw new Error("Cannot rename IndexedDB root directory");
1302
+ }
1303
+ const oldPath = this.getPath();
1304
+ const newPath = joinPath(parentDir.getPath(), newName);
1305
+ await idbRenameTree(this.getRootId(), oldPath, newPath);
1306
+ publish(TOPIC_WORKSPACE_CHANGED, workspaceService.getWorkspaceSync() ?? this.getWorkspace());
1307
+ }
1308
+ }
1309
+ class IDBRootDirectory extends IDBDirectoryResource {
1310
+ constructor(displayName, rootId) {
1311
+ super("");
1312
+ this.displayName = displayName || "IndexedDB";
1313
+ this.rootId = rootId;
1314
+ }
1315
+ getRootId() {
1316
+ return this.rootId;
1317
+ }
1318
+ getName() {
1319
+ return this.displayName;
1320
+ }
1321
+ getParent() {
1322
+ return void 0;
1323
+ }
1324
+ async rename(_newName) {
1325
+ const name = String(_newName ?? "").trim();
1326
+ if (!name || name === this.displayName) {
1327
+ return;
1328
+ }
1329
+ this.displayName = name;
1330
+ await workspaceService.updateFolderName(this, name);
1331
+ }
1332
+ }
1333
+ function generateRootId() {
1334
+ return typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : "default-" + Math.random().toString(36).slice(2) + Date.now().toString(36);
1335
+ }
1336
+ workspaceService.registerContribution({
1337
+ type: "indexeddb",
1338
+ name: "idb",
1339
+ canHandle(input) {
1340
+ return input && typeof input === "object" && input.indexeddb === true;
1341
+ },
1342
+ async connect(input) {
1343
+ await getWorkspaceIDB();
1344
+ const explicitName = input.name && String(input.name).trim();
1345
+ const name = explicitName && explicitName.length > 0 ? explicitName : await getNextIndexedDBName();
1346
+ const rootId = generateRootId();
1347
+ return new IDBRootDirectory(name, rootId);
1348
+ },
1349
+ async restore(data) {
1350
+ if (data && typeof data === "object" && data.indexeddb === true && data.rootId) {
1351
+ await getWorkspaceIDB();
1352
+ const name = data.name && String(data.name).trim() || "IndexedDB";
1353
+ return new IDBRootDirectory(name, String(data.rootId));
1354
+ }
1355
+ return void 0;
1356
+ },
1357
+ async persist(workspace) {
1358
+ if (workspace instanceof IDBRootDirectory) {
1359
+ return { indexeddb: true, name: workspace.getName(), rootId: workspace.getRootId() };
1360
+ }
1361
+ return null;
1362
+ }
1363
+ });
1364
+ class EditorRegistry {
1365
+ constructor() {
1366
+ this.editorInputHandlers = [];
1367
+ this.listenersAttached = false;
1368
+ this.cachedIconContributions = null;
1369
+ subscribe(TOPIC_WORKSPACE_CONNECTED, () => {
1370
+ });
1371
+ subscribe(TOPIC_CONTRIBUTEIONS_CHANGED, (event) => {
1372
+ if (event.target === "system.icons") {
1373
+ this.cachedIconContributions = null;
1374
+ }
1375
+ });
1376
+ }
1377
+ getSortedIconContributions() {
1378
+ if (this.cachedIconContributions !== null) {
1379
+ return this.cachedIconContributions;
1380
+ }
1381
+ const contributions = contributionRegistry.getContributions("system.icons");
1382
+ this.cachedIconContributions = [...contributions].sort((a, b) => {
1383
+ const priorityA = a.priority ?? 0;
1384
+ const priorityB = b.priority ?? 0;
1385
+ if (priorityB !== priorityA) {
1386
+ return priorityB - priorityA;
1387
+ }
1388
+ return a.label.localeCompare(b.label);
1389
+ });
1390
+ return this.cachedIconContributions;
1391
+ }
1392
+ setupEventListeners(editorArea) {
1393
+ if (this.listenersAttached) {
1394
+ return;
1395
+ }
1396
+ this.listenersAttached = true;
1397
+ const handler = (event) => {
1398
+ const tabPanel = event.detail;
1399
+ if (tabPanel) {
1400
+ const parts = Array.from(tabPanel.querySelectorAll(`*`)).filter((element) => element instanceof LyraPart);
1401
+ parts.forEach((part) => {
1402
+ activePartSignal.set(part);
1403
+ if (part.isEditor) {
1404
+ activeEditorSignal.set(part);
1405
+ }
1406
+ });
1407
+ }
1408
+ };
1409
+ editorArea.addEventListener("tab-shown", handler);
1410
+ const closed = (event) => {
1411
+ const tabPanel = event.detail;
1412
+ const parts = Array.from(tabPanel.querySelectorAll(`*`)).filter((element) => element instanceof LyraPart);
1413
+ parts.forEach((part) => {
1414
+ if (activePartSignal.get() == part) {
1415
+ activePartSignal.set(null);
1416
+ }
1417
+ if (activeEditorSignal.get() == part) {
1418
+ activeEditorSignal.set(null);
1419
+ }
1420
+ });
1421
+ };
1422
+ editorArea.addEventListener("tab-closed", closed);
1423
+ const dirtyHandler = (targetPart) => {
1424
+ const tabPanel = targetPart.closest("wa-tab-panel");
1425
+ const name = tabPanel.getAttribute("name");
1426
+ editorArea.markDirty(name, targetPart.isDirty());
1427
+ };
1428
+ this.signalCleanup = watchSignal(partDirtySignal, dirtyHandler);
1429
+ }
1430
+ registerEditorInputHandler(editorInputHandler) {
1431
+ this.editorInputHandlers.push({
1432
+ definition: editorInputHandler,
1433
+ initialized: false
1434
+ });
1435
+ this.editorInputHandlers.sort((a, b) => {
1436
+ const rankA = a.definition.ranking ?? 0;
1437
+ const rankB = b.definition.ranking ?? 0;
1438
+ return rankB - rankA;
1439
+ });
1440
+ }
1441
+ async ensureHandlerInitialized(entry) {
1442
+ const handler = entry.definition;
1443
+ if (!handler.lazyInit || entry.initialized) {
1444
+ return;
1445
+ }
1446
+ if (!entry.lazyInitPromise) {
1447
+ entry.lazyInitPromise = Promise.resolve(handler.lazyInit()).then(() => {
1448
+ entry.initialized = true;
1449
+ entry.lazyInitPromise = void 0;
1450
+ }).catch((error) => {
1451
+ entry.lazyInitPromise = void 0;
1452
+ throw error;
1453
+ });
1454
+ }
1455
+ await entry.lazyInitPromise;
1456
+ }
1457
+ getEditorOptionsForInput(input) {
1458
+ const seen = /* @__PURE__ */ new Set();
1459
+ const options = [];
1460
+ for (const entry of this.editorInputHandlers) {
1461
+ const handler = entry.definition;
1462
+ if (!handler.canHandle(input) || seen.has(handler.editorId)) continue;
1463
+ seen.add(handler.editorId);
1464
+ options.push({
1465
+ editorId: handler.editorId,
1466
+ title: handler.label,
1467
+ icon: handler.icon
1468
+ });
1469
+ }
1470
+ return options;
1471
+ }
1472
+ async handleInput(input, preferredEditorId) {
1473
+ if (preferredEditorId !== void 0) {
1474
+ const entry = this.editorInputHandlers.find(
1475
+ (e) => e.definition.editorId === preferredEditorId
1476
+ );
1477
+ if (entry) {
1478
+ await this.ensureHandlerInitialized(entry);
1479
+ const result = await entry.definition.handle(input);
1480
+ if (result) result.editorId = entry.definition.editorId;
1481
+ return result;
1482
+ }
1483
+ return void 0;
1484
+ }
1485
+ for (let i = 0; i < this.editorInputHandlers.length; i++) {
1486
+ const entry = this.editorInputHandlers[i];
1487
+ const editorInputHandler = entry.definition;
1488
+ if (editorInputHandler.canHandle(input)) {
1489
+ await this.ensureHandlerInitialized(entry);
1490
+ const result = await editorInputHandler.handle(input);
1491
+ if (result) result.editorId = editorInputHandler.editorId;
1492
+ return result;
1493
+ }
1494
+ }
1495
+ }
1496
+ getEditorArea() {
1497
+ return document.querySelector(`lyra-tabs#${EDITOR_AREA_MAIN}`);
1498
+ }
1499
+ async loadEditor(editorInput, preferredEditorId) {
1500
+ if (!editorInput) {
1501
+ return;
1502
+ }
1503
+ if (!("widgetFactory" in editorInput)) {
1504
+ editorInput = await this.handleInput(editorInput, preferredEditorId);
1505
+ }
1506
+ if (!editorInput || !("widgetFactory" in editorInput)) {
1507
+ return;
1508
+ }
1509
+ const editorId = editorInput.editorId ?? preferredEditorId;
1510
+ if (editorId) editorInput.editorId = editorId;
1511
+ await this.openTab({
1512
+ name: editorInput.key,
1513
+ editorId,
1514
+ label: editorInput.title,
1515
+ icon: editorInput.icon,
1516
+ closable: true,
1517
+ noOverflow: editorInput.noOverflow,
1518
+ component: editorInput.widgetFactory
1519
+ });
1520
+ }
1521
+ async openTab(tabContribution) {
1522
+ const editorArea = this.getEditorArea();
1523
+ if (!editorArea) {
1524
+ console.error("Editor area not found. The split pane system may not be initialized yet.");
1525
+ return;
1526
+ }
1527
+ this.setupEventListeners(editorArea);
1528
+ if (editorArea.has(tabContribution.name)) {
1529
+ editorArea.activate(tabContribution.name);
1530
+ return;
1531
+ }
1532
+ editorArea.open(tabContribution);
1533
+ }
1534
+ getFileIcon(fileNameOrType) {
1535
+ const extension = fileNameOrType.includes(".") ? fileNameOrType.split(".").pop()?.toLowerCase() || "" : fileNameOrType.toLowerCase();
1536
+ const sortedContributions = this.getSortedIconContributions();
1537
+ if (sortedContributions.length === 0) {
1538
+ return "file";
1539
+ }
1540
+ for (const contribution of sortedContributions) {
1541
+ if (contribution.mappings && contribution.mappings[extension]) {
1542
+ return contribution.mappings[extension];
1543
+ }
1544
+ }
1545
+ return "file";
1546
+ }
1547
+ }
1548
+ const editorRegistry = new EditorRegistry();
1549
+ rootContext.put("editorRegistry", editorRegistry);
1550
+ contributionRegistry.registerContribution("system.icons", {
1551
+ label: "Default File Icons",
1552
+ mappings: {
1553
+ "pdf": "file-pdf",
1554
+ "md": "book",
1555
+ "txt": "file-lines",
1556
+ "ts": "code",
1557
+ "tsx": "code",
1558
+ "js": "code",
1559
+ "jsx": "code",
1560
+ "json": "file-code",
1561
+ "geojson": "file-code",
1562
+ "py": "python",
1563
+ "html": "code",
1564
+ "htm": "code",
1565
+ "css": "code",
1566
+ "scss": "code",
1567
+ "sass": "code",
1568
+ "xml": "file-code",
1569
+ "yaml": "file-code",
1570
+ "yml": "file-code",
1571
+ "sql": "database",
1572
+ "kml": "file-code",
1573
+ "gpx": "file-code",
1574
+ "jpg": "image",
1575
+ "jpeg": "image",
1576
+ "png": "image",
1577
+ "gif": "image",
1578
+ "svg": "image",
1579
+ "webp": "image",
1580
+ "bmp": "image",
1581
+ "ico": "image"
1582
+ },
1583
+ priority: 0
1584
+ });
1585
+ const logger$2 = createLogger("MarketplaceRegistry");
1586
+ const TOPIC_MARKETPLACE_CHANGED = "events/marketplaceregistry/changed";
1587
+ const KEY_CATALOG_URLS = "marketplace.catalogUrls";
1588
+ class MarketplaceRegistry {
1589
+ constructor() {
1590
+ this.catalogUrls = [];
1591
+ this.loadingPromises = /* @__PURE__ */ new Map();
1592
+ this.loadCatalogUrls().then(() => {
1593
+ this.refreshCatalogs().catch((err) => {
1594
+ logger$2.error(`Failed to refresh catalogs on init: ${err.message}`);
1595
+ });
1596
+ });
1597
+ }
1598
+ async loadCatalogUrls() {
1599
+ try {
1600
+ const urls = await appSettings.get(KEY_CATALOG_URLS);
1601
+ this.catalogUrls = Array.isArray(urls) ? urls : [];
1602
+ logger$2.debug(`Loaded ${this.catalogUrls.length} catalog URLs`);
1603
+ } catch (error) {
1604
+ logger$2.error(`Failed to load catalog URLs: ${error}`);
1605
+ this.catalogUrls = [];
1606
+ }
1607
+ }
1608
+ async saveCatalogUrls() {
1609
+ await appSettings.set(KEY_CATALOG_URLS, this.catalogUrls);
1610
+ publish(TOPIC_MARKETPLACE_CHANGED, { type: "catalogs", urls: this.catalogUrls });
1611
+ }
1612
+ async addCatalogUrl(url) {
1613
+ if (!this.isValidUrl(url)) {
1614
+ throw new Error(`Invalid catalog URL: ${url}`);
1615
+ }
1616
+ if (this.catalogUrls.includes(url)) {
1617
+ logger$2.debug(`Catalog URL already exists: ${url}`);
1618
+ return;
1619
+ }
1620
+ this.catalogUrls.push(url);
1621
+ await this.saveCatalogUrls();
1622
+ logger$2.info(`Added catalog URL: ${url}`);
1623
+ try {
1624
+ await this.refreshCatalogs();
1625
+ } catch (error) {
1626
+ logger$2.warn(`Failed to refresh catalogs immediately after adding: ${error}`);
1627
+ }
1628
+ }
1629
+ async removeCatalogUrl(url) {
1630
+ const index = this.catalogUrls.indexOf(url);
1631
+ if (index === -1) {
1632
+ return;
1633
+ }
1634
+ this.catalogUrls.splice(index, 1);
1635
+ await this.saveCatalogUrls();
1636
+ logger$2.info(`Removed catalog URL: ${url}`);
1637
+ }
1638
+ getCatalogUrls() {
1639
+ return [...this.catalogUrls];
1640
+ }
1641
+ isValidUrl(url) {
1642
+ try {
1643
+ const parsed = new URL(url);
1644
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
1645
+ } catch {
1646
+ return false;
1647
+ }
1648
+ }
1649
+ async fetchCatalog(url) {
1650
+ const existingPromise = this.loadingPromises.get(url);
1651
+ if (existingPromise) {
1652
+ return existingPromise;
1653
+ }
1654
+ const fetchPromise = (async () => {
1655
+ try {
1656
+ logger$2.debug(`Fetching catalog from: ${url}`);
1657
+ const response = await fetch(url, {
1658
+ method: "GET",
1659
+ headers: {
1660
+ "Accept": "application/json"
1661
+ }
1662
+ });
1663
+ if (!response.ok) {
1664
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1665
+ }
1666
+ const data = await response.json();
1667
+ if (!data.extensions || !Array.isArray(data.extensions)) {
1668
+ throw new Error("Invalid catalog format: extensions array is required");
1669
+ }
1670
+ const catalog = {
1671
+ name: data.name,
1672
+ description: data.description,
1673
+ extensions: data.extensions || []
1674
+ };
1675
+ const extCount = catalog.extensions?.length || 0;
1676
+ logger$2.debug(`Successfully fetched catalog from ${url}: ${extCount} extensions`);
1677
+ return catalog;
1678
+ } catch (error) {
1679
+ logger$2.error(`Failed to fetch catalog from ${url}: ${error}`);
1680
+ throw error;
1681
+ } finally {
1682
+ this.loadingPromises.delete(url);
1683
+ }
1684
+ })();
1685
+ this.loadingPromises.set(url, fetchPromise);
1686
+ return fetchPromise;
1687
+ }
1688
+ async refreshCatalogs() {
1689
+ logger$2.info(`Refreshing ${this.catalogUrls.length} catalogs...`);
1690
+ const promises = this.catalogUrls.map(
1691
+ (url) => this.fetchCatalog(url).catch((error) => {
1692
+ logger$2.warn(`Failed to refresh catalog ${url}: ${error.message}`);
1693
+ return null;
1694
+ })
1695
+ );
1696
+ const catalogs = await Promise.allSettled(promises);
1697
+ catalogs.forEach((result, index) => {
1698
+ if (result.status === "fulfilled" && result.value) {
1699
+ const catalog = result.value;
1700
+ if (catalog.extensions) {
1701
+ catalog.extensions.forEach((marketplaceExt) => {
1702
+ if (!extensionRegistry.getExtensions().find((e) => e.id === marketplaceExt.id)) {
1703
+ const extension = {
1704
+ ...marketplaceExt,
1705
+ external: true
1706
+ };
1707
+ extensionRegistry.registerExtension(extension);
1708
+ logger$2.debug(`Registered marketplace extension: ${marketplaceExt.id}`);
1709
+ }
1710
+ });
1711
+ }
1712
+ }
1713
+ });
1714
+ publish(TOPIC_MARKETPLACE_CHANGED, { type: "refreshed" });
1715
+ logger$2.info("Catalog refresh completed");
1716
+ }
1717
+ getMarketplaceExtension(extensionId) {
1718
+ const extension = extensionRegistry.getExtensions().find((e) => e.id === extensionId);
1719
+ if (extension && extension.external) {
1720
+ return extension;
1721
+ }
1722
+ return void 0;
1723
+ }
1724
+ isMarketplaceExtension(extensionId) {
1725
+ const extension = extensionRegistry.getExtensions().find((e) => e.id === extensionId);
1726
+ return extension !== void 0 && extension.external === true;
1727
+ }
1728
+ }
1729
+ const marketplaceRegistry = new MarketplaceRegistry();
1730
+ rootContext.put("marketplaceRegistry", marketplaceRegistry);
1731
+ const namespace$6 = "filebrowser";
1732
+ const en$6 = { "CONNECT_WORKSPACE": "Connect Workspace...", "REFRESH_RESOURCE": "Refresh resource", "CREATE_NEW": "Create new...", "OPEN": "Open", "FILE": "File", "FOLDER": "Folder", "FILE_EXISTS_OVERWRITE": 'File "{fileName}" already exists. Do you want to overwrite it?', "SELECT_WORKSPACE": "Select a workspace.", "DROP_FILES_HERE": "📁 Drop files here", "OPEN_WITH": "Open with..." };
1733
+ const de$6 = { "CONNECT_WORKSPACE": "Arbeitsbereich verbinden...", "REFRESH_RESOURCE": "Ressource aktualisieren", "CREATE_NEW": "Neu erstellen...", "OPEN": "Öffnen", "FILE": "Datei", "FOLDER": "Ordner", "FILE_EXISTS_OVERWRITE": 'Die Datei "{fileName}" existiert bereits. Möchten Sie sie überschreiben?', "SELECT_WORKSPACE": "Wählen Sie einen Arbeitsbereich aus.", "DROP_FILES_HERE": "📁 Dateien hier ablegen", "OPEN_WITH": "Öffnen mit..." };
1734
+ const filebrowserBundle = {
1735
+ namespace: namespace$6,
1736
+ en: en$6,
1737
+ de: de$6
1738
+ };
1739
+ const namespace$5 = "extensions";
1740
+ const en$5 = { "FILTER_PLACEHOLDER": "Filter extensions...", "INSTALLED": "Installed", "AVAILABLE": "Available", "NO_MATCHES": 'No extensions match "{filterText}"', "EXTERNAL_EXTENSION": "External Extension", "UNINSTALL": "Uninstall (requires restart)", "REQUIRED_HINT": "This extension is required by the current app and cannot be uninstalled", "INSTALL": "Install", "EXPERIMENTAL": "This is an experimental extension!", "VERSION": "Version:", "AUTHOR": "Author:", "DEPENDENCIES": "Dependencies", "NOT_INSTALLED": "Not Installed", "DEPENDENCIES_HINT": "Dependencies are automatically installed when this extension is enabled.", "DISABLE_TITLE": "Disable this extension", "ENABLE_TITLE": "Enable this extension" };
1741
+ const de$5 = { "FILTER_PLACEHOLDER": "Erweiterungen filtern...", "INSTALLED": "Installiert", "AVAILABLE": "Verfügbar", "NO_MATCHES": 'Keine Erweiterungen entsprechen "{filterText}"', "EXTERNAL_EXTENSION": "Externe Erweiterung", "UNINSTALL": "Deinstallieren (Neustart erforderlich)", "REQUIRED_HINT": "Diese Erweiterung ist für die aktuelle App erforderlich und kann nicht deinstalliert werden", "INSTALL": "Installieren", "EXPERIMENTAL": "Dies ist eine experimentelle Erweiterung!", "VERSION": "Version:", "AUTHOR": "Autor:", "DEPENDENCIES": "Abhängigkeiten", "NOT_INSTALLED": "Nicht installiert", "DEPENDENCIES_HINT": "Abhängigkeiten werden automatisch installiert, wenn diese Erweiterung aktiviert wird.", "DISABLE_TITLE": "Diese Erweiterung deaktivieren", "ENABLE_TITLE": "Diese Erweiterung aktivieren" };
1742
+ const extensionsBundle = {
1743
+ namespace: namespace$5,
1744
+ en: en$5,
1745
+ de: de$5
1746
+ };
1747
+ const namespace$4 = "tasks";
1748
+ const en$4 = { "ACTIVE_TASKS": "Active Tasks", "ACTIVE_TASKS_TITLE": "Active tasks: {taskCount}. Click to view details." };
1749
+ const de$4 = { "ACTIVE_TASKS": "Aktive Aufgaben", "ACTIVE_TASKS_TITLE": "Aktive Aufgaben: {taskCount}. Klicken Sie, um Details anzuzeigen." };
1750
+ const tasksBundle = {
1751
+ namespace: namespace$4,
1752
+ en: en$4,
1753
+ de: de$4
1754
+ };
1755
+ const namespace$3 = "workspace";
1756
+ const en$3 = { "NO_WORKSPACE": "<no workspace>", "LOAD_WORKSPACE": "Load workspace" };
1757
+ const de$3 = { "NO_WORKSPACE": "<kein Arbeitsbereich>", "LOAD_WORKSPACE": "Arbeitsbereich laden" };
1758
+ const workspaceBundle = {
1759
+ namespace: namespace$3,
1760
+ en: en$3,
1761
+ de: de$3
1762
+ };
1763
+ const namespace$2 = "fastviews";
1764
+ const en$2 = { "FAST_VIEWS": "Fast Views" };
1765
+ const de$2 = { "FAST_VIEWS": "Schnellansichten" };
1766
+ const fastviewsBundle = {
1767
+ namespace: namespace$2,
1768
+ en: en$2,
1769
+ de: de$2
1770
+ };
1771
+ const namespace$1 = "logterminal";
1772
+ const en$1 = { "ALL_LOGS": "All logs", "ALL": "All", "INFO_LOGS": "Info logs", "INFO": "Info", "WARNING_LOGS": "Warning logs", "WARNINGS": "Warnings", "ERROR_LOGS": "Error logs", "ERRORS": "Errors", "DEBUG_LOGS": "Debug logs", "DEBUG": "Debug", "AUTO_SCROLL_ENABLED": "Auto-scroll enabled", "AUTO_SCROLL_DISABLED": "Auto-scroll disabled", "AUTO_SCROLL": "Auto-scroll", "MANUAL": "Manual", "CLEAR_LOGS": "Clear logs", "CLEAR": "Clear", "NO_LOG_MESSAGES": "No log messages" };
1773
+ const de$1 = { "ALL_LOGS": "Alle Protokolle", "ALL": "Alle", "INFO_LOGS": "Info-Protokolle", "INFO": "Info", "WARNING_LOGS": "Warnungsprotokolle", "WARNINGS": "Warnungen", "ERROR_LOGS": "Fehlerprotokolle", "ERRORS": "Fehler", "DEBUG_LOGS": "Debug-Protokolle", "DEBUG": "Debug", "AUTO_SCROLL_ENABLED": "Automatisches Scrollen aktiviert", "AUTO_SCROLL_DISABLED": "Automatisches Scrollen deaktiviert", "AUTO_SCROLL": "Automatisches Scrollen", "MANUAL": "Manuell", "CLEAR_LOGS": "Protokolle löschen", "CLEAR": "Löschen", "NO_LOG_MESSAGES": "Keine Protokollnachrichten" };
1774
+ const logterminalBundle = {
1775
+ namespace: namespace$1,
1776
+ en: en$1,
1777
+ de: de$1
1778
+ };
1779
+ const namespace = "partname";
1780
+ const en = { "NO_PART": "<no part>", "ACTIVE_PART": "Active part" };
1781
+ const de = { "NO_PART": "<kein Bereich>", "ACTIVE_PART": "Aktiver Bereich" };
1782
+ const partnameBundle = {
1783
+ namespace,
1784
+ en,
1785
+ de
1786
+ };
1787
+ contributionRegistry.registerContribution(
1788
+ SYSTEM_LANGUAGE_BUNDLES,
1789
+ filebrowserBundle
1790
+ );
1791
+ contributionRegistry.registerContribution(
1792
+ SYSTEM_LANGUAGE_BUNDLES,
1793
+ extensionsBundle
1794
+ );
1795
+ contributionRegistry.registerContribution(
1796
+ SYSTEM_LANGUAGE_BUNDLES,
1797
+ tasksBundle
1798
+ );
1799
+ contributionRegistry.registerContribution(
1800
+ SYSTEM_LANGUAGE_BUNDLES,
1801
+ workspaceBundle
1802
+ );
1803
+ contributionRegistry.registerContribution(
1804
+ SYSTEM_LANGUAGE_BUNDLES,
1805
+ fastviewsBundle
1806
+ );
1807
+ contributionRegistry.registerContribution(
1808
+ SYSTEM_LANGUAGE_BUNDLES,
1809
+ logterminalBundle
1810
+ );
1811
+ contributionRegistry.registerContribution(
1812
+ SYSTEM_LANGUAGE_BUNDLES,
1813
+ partnameBundle
1814
+ );
1815
+ const treeNodeComparator = (c1, c2) => {
1816
+ if (!c1.leaf && c2.leaf) {
1817
+ return -1;
1818
+ }
1819
+ if (c1.leaf && !c2.leaf) {
1820
+ return 1;
1821
+ }
1822
+ return c1.label.localeCompare(c2.label);
1823
+ };
1824
+ var __defProp$5 = Object.defineProperty;
1825
+ var __getOwnPropDesc$8 = Object.getOwnPropertyDescriptor;
1826
+ var __decorateClass$8 = (decorators, target, key, kind) => {
1827
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$8(target, key) : target;
1828
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
1829
+ if (decorator = decorators[i])
1830
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
1831
+ if (kind && result) __defProp$5(target, key, result);
1832
+ return result;
1833
+ };
1834
+ const t$5 = i18n("filebrowser");
1835
+ const WORKSPACE_CHANGED_DEBOUNCE_MS = 250;
1836
+ let LyraFileBrowser = class extends LyraPart {
1837
+ constructor() {
1838
+ super(...arguments);
1839
+ this.fileEditorOptions = [];
1840
+ this.treeRef = createRef();
1841
+ this.loadingNodes = /* @__PURE__ */ new Set();
1842
+ }
1843
+ doBeforeUI() {
1844
+ this.initializeWorkspace();
1845
+ subscribe(TOPIC_CONTRIBUTEIONS_CHANGED, (event) => {
1846
+ if (event.target === "system.icons") {
1847
+ this.requestUpdate();
1848
+ }
1849
+ });
1850
+ this.subscribe(TOPIC_WORKSPACE_CHANGED, (workspaceDir) => this.onWorkspaceChanged(workspaceDir));
1851
+ this.subscribe(TOPIC_WORKSPACE_CONNECTED, (workspaceDir) => this.onWorkspaceConnected(workspaceDir));
1852
+ }
1853
+ disconnectedCallback() {
1854
+ if (this.workspaceChangedDebounceId !== void 0) {
1855
+ clearTimeout(this.workspaceChangedDebounceId);
1856
+ this.workspaceChangedDebounceId = void 0;
1857
+ }
1858
+ this.pendingWorkspaceDir = void 0;
1859
+ super.disconnectedCallback();
1860
+ }
1861
+ firstUpdated(changedProperties) {
1862
+ super.firstUpdated(changedProperties);
1863
+ this.setupDragAndDrop();
1864
+ }
1865
+ updated(changedProperties) {
1866
+ super.updated(changedProperties);
1867
+ if (changedProperties.has("workspaceDir") && this.workspaceDir) {
1868
+ this.setupDragAndDrop();
1869
+ }
1870
+ }
1871
+ async initializeWorkspace() {
1872
+ const workspaceDir = await workspaceService.getWorkspace();
1873
+ await this.loadWorkspace(workspaceDir ?? void 0);
1874
+ }
1875
+ renderToolbar() {
1876
+ return html`
1877
+ <lyra-command icon="folder-open" title="${t$5("CONNECT_WORKSPACE")}" dropdown="filebrowser.connections"></lyra-command>
1878
+ <lyra-command cmd="refresh_resource" icon="repeat" title="${t$5("REFRESH_RESOURCE")}"></lyra-command>
1879
+ <lyra-command cmd="touch" icon="plus" title="${t$5("CREATE_NEW")}" dropdown="filebrowser.create"></lyra-command>
1880
+ `;
1881
+ }
1882
+ renderContextMenu() {
1883
+ const selection = activeSelectionSignal.get();
1884
+ const file = selection instanceof File ? selection : null;
1885
+ const hasOpenWith = file && this.fileEditorOptions.length > 0;
1886
+ return html`
1887
+ <lyra-command cmd="open_editor" icon="folder-open">${t$5("OPEN")}</lyra-command>
1888
+ ${hasOpenWith ? html`
1889
+ <wa-dropdown-item>
1890
+ <lyra-icon name="folder-open" slot="icon"></lyra-icon>
1891
+ ${t$5("OPEN_WITH")}
1892
+ ${this.fileEditorOptions.map((opt) => html`
1893
+ <lyra-command
1894
+ slot="submenu"
1895
+ cmd="open_editor"
1896
+ icon="${opt.icon ?? "file"}"
1897
+ .params=${{ path: file.getWorkspacePath(), editorId: opt.editorId }}>
1898
+ ${opt.title}
1899
+ </lyra-command>
1900
+ `)}
1901
+ </wa-dropdown-item>
1902
+ ` : nothing}
1903
+ <lyra-command cmd="touch" icon="plus" dropdown="filebrowser.create">${t$5("CREATE_NEW")}</lyra-command>
1904
+ `;
1905
+ }
1906
+ onWorkspaceChanged(workspaceDir) {
1907
+ this.pendingWorkspaceDir = workspaceDir;
1908
+ if (this.workspaceChangedDebounceId !== void 0) {
1909
+ clearTimeout(this.workspaceChangedDebounceId);
1910
+ }
1911
+ this.workspaceChangedDebounceId = setTimeout(() => {
1912
+ this.workspaceChangedDebounceId = void 0;
1913
+ const dir = this.pendingWorkspaceDir;
1914
+ this.pendingWorkspaceDir = void 0;
1915
+ if (dir) this.applyWorkspaceChange(dir);
1916
+ else this.loadWorkspace(void 0, true);
1917
+ }, WORKSPACE_CHANGED_DEBOUNCE_MS);
1918
+ }
1919
+ async applyWorkspaceChange(workspaceDir) {
1920
+ activeSelectionSignal.set(void 0);
1921
+ await this.loadWorkspace(workspaceDir, true);
1922
+ await this.syncTreeSelection();
1923
+ }
1924
+ async onWorkspaceConnected(workspaceDir) {
1925
+ await this.loadWorkspace(workspaceDir, true);
1926
+ }
1927
+ async loadWorkspace(workspaceDir, forceRefresh = false) {
1928
+ this.workspaceDir = workspaceDir;
1929
+ if (!workspaceDir) {
1930
+ this.root = void 0;
1931
+ } else {
1932
+ this.root = await this.resourceToTreeNode(workspaceDir, true, forceRefresh);
1933
+ }
1934
+ }
1935
+ async syncTreeSelection() {
1936
+ await this.updateComplete;
1937
+ const waTree = this.treeRef.value?.querySelector("wa-tree");
1938
+ const selectedItems = waTree?.selectedItems || [];
1939
+ if (selectedItems.length > 0) {
1940
+ activeSelectionSignal.set(selectedItems[0].model?.data);
1941
+ }
1942
+ }
1943
+ async resourceToTreeNode(resource, loadChildren = false, forceRefreshChildren = false) {
1944
+ const isFile = resource instanceof File;
1945
+ const node = {
1946
+ data: resource,
1947
+ label: resource.getName(),
1948
+ leaf: isFile,
1949
+ children: []
1950
+ };
1951
+ if (resource instanceof Directory && !resource.getParent()) {
1952
+ try {
1953
+ const info = await workspaceService.getFolderInfoForDirectory(resource);
1954
+ if (info?.backendName) {
1955
+ node.workspaceTag = info.backendName;
1956
+ }
1957
+ } catch (e) {
1958
+ console.warn("Failed to get workspace info for directory", e);
1959
+ }
1960
+ }
1961
+ if (resource instanceof Directory && loadChildren) {
1962
+ for (const childResource of await resource.listChildren(forceRefreshChildren)) {
1963
+ const child = await this.resourceToTreeNode(childResource, true);
1964
+ node.children.push(child);
1965
+ }
1966
+ node.children.sort(treeNodeComparator);
1967
+ }
1968
+ return node;
1969
+ }
1970
+ createTreeItems(node, expanded = false) {
1971
+ if (!node) {
1972
+ return html``;
1973
+ }
1974
+ const isLazy = !node.leaf && node.children.length === 0;
1975
+ const resource = node.data;
1976
+ const isFile = resource instanceof File;
1977
+ const icon = isFile ? editorRegistry.getFileIcon(resource.getName()) : node.icon || "folder-open";
1978
+ const workspaceTag = node.workspaceTag;
1979
+ return html`
1980
+ <wa-tree-item
1981
+ draggable=${isFile}
1982
+ @dragstart=${isFile ? this.nobubble((e) => this.onDragStart(e, resource)) : void 0}
1983
+ @dblclick=${this.nobubble(this.onFileDoubleClicked)}
1984
+ @wa-lazy-load=${this.nobubble((e) => this.onLazyLoad(e, node))}
1985
+ .model=${node}
1986
+ ?expanded=${expanded}
1987
+ ?lazy=${isLazy}>
1988
+ <span class="tree-label">
1989
+ <wa-icon name=${icon} label="${node.leaf ? t$5("FILE") : t$5("FOLDER")}"></wa-icon>
1990
+ <span class="tree-label-text">${node.label}</span>
1991
+ ${!node.leaf && workspaceTag ? html`<wa-badge appearance="outlined" variant="neutral" style="font-size: var(--wa-font-size-xs);">${workspaceTag}</wa-badge>` : null}
1992
+ </span>
1993
+ ${node.children.map((child) => this.createTreeItems(child, false))}
1994
+ </wa-tree-item>`;
1995
+ }
1996
+ onDragStart(e, file) {
1997
+ if (!e.dataTransfer) return;
1998
+ const filePath = file.getWorkspacePath();
1999
+ const fileName = file.getName();
2000
+ e.dataTransfer.effectAllowed = "copy";
2001
+ e.dataTransfer.setData("text/plain", filePath);
2002
+ e.dataTransfer.setData("application/x-workspace-file", filePath);
2003
+ e.dataTransfer.setData("text/uri-list", filePath);
2004
+ if (e.dataTransfer.setDragImage) {
2005
+ const dragImage = document.createElement("div");
2006
+ dragImage.textContent = fileName;
2007
+ dragImage.style.position = "absolute";
2008
+ dragImage.style.top = "-1000px";
2009
+ dragImage.style.padding = "4px 8px";
2010
+ dragImage.style.background = "var(--wa-color-neutral-10)";
2011
+ dragImage.style.border = "1px solid var(--wa-color-neutral-30)";
2012
+ dragImage.style.borderRadius = "4px";
2013
+ document.body.appendChild(dragImage);
2014
+ e.dataTransfer.setDragImage(dragImage, 0, 0);
2015
+ setTimeout(() => document.body.removeChild(dragImage), 0);
2016
+ }
2017
+ }
2018
+ async onLazyLoad(event, node) {
2019
+ const resource = node.data;
2020
+ if (resource instanceof Directory && node.children.length === 0) {
2021
+ await this.loadNodeChildren(node, resource);
2022
+ }
2023
+ }
2024
+ async loadNodeChildren(node, resource) {
2025
+ if (this.loadingNodes.has(node)) {
2026
+ return;
2027
+ }
2028
+ this.loadingNodes.add(node);
2029
+ try {
2030
+ for (const childResource of await resource.listChildren(false)) {
2031
+ if (HIDE_DOT_RESOURCE && childResource.getName().startsWith(".")) ;
2032
+ const child = await this.resourceToTreeNode(childResource, false);
2033
+ node.children.push(child);
2034
+ }
2035
+ node.children.sort(treeNodeComparator);
2036
+ this.requestUpdate();
2037
+ } catch (error) {
2038
+ console.error("Failed to load directory children:", error);
2039
+ } finally {
2040
+ this.loadingNodes.delete(node);
2041
+ }
2042
+ }
2043
+ async onFileDoubleClicked(event) {
2044
+ const node = event.currentTarget.model;
2045
+ const resource = node.data;
2046
+ if (resource instanceof File) {
2047
+ activeSelectionSignal.set(resource);
2048
+ this.executeCommand("open_editor", {});
2049
+ }
2050
+ }
2051
+ onSelectionChanged(event) {
2052
+ const selection = event.detail.selection;
2053
+ if (selection && selection.length > 0) {
2054
+ const node = selection[0].model;
2055
+ const data = node.data;
2056
+ activeSelectionSignal.set(data);
2057
+ if (data instanceof File) {
2058
+ this.fileEditorOptions = editorRegistry.getEditorOptionsForInput(data);
2059
+ this.updateContextMenu();
2060
+ } else {
2061
+ this.fileEditorOptions = [];
2062
+ this.updateContextMenu();
2063
+ }
2064
+ } else {
2065
+ activeSelectionSignal.set(void 0);
2066
+ this.fileEditorOptions = [];
2067
+ this.updateContextMenu();
2068
+ }
2069
+ }
2070
+ setupDragAndDrop() {
2071
+ const treeElement = this.treeRef.value;
2072
+ if (!treeElement) return;
2073
+ const dragOverHandler = (e) => {
2074
+ if (!e.dataTransfer?.types.includes("Files")) return;
2075
+ e.preventDefault();
2076
+ e.dataTransfer.dropEffect = "copy";
2077
+ treeElement.classList.add("drag-over");
2078
+ const target = e.target;
2079
+ const treeItem = target.closest("wa-tree-item");
2080
+ if (treeItem && treeItem !== this.currentDropTarget) {
2081
+ this.currentDropTarget?.classList.remove("drop-target");
2082
+ this.currentDropTarget = treeItem;
2083
+ treeItem.classList.add("drop-target");
2084
+ }
2085
+ };
2086
+ const dragEnterHandler = (e) => {
2087
+ if (!e.dataTransfer?.types.includes("Files")) return;
2088
+ e.preventDefault();
2089
+ treeElement.classList.add("drag-over");
2090
+ };
2091
+ const dragLeaveHandler = (e) => {
2092
+ const rect = treeElement.getBoundingClientRect();
2093
+ const x = e.clientX;
2094
+ const y = e.clientY;
2095
+ if (x <= rect.left || x >= rect.right || y <= rect.top || y >= rect.bottom) {
2096
+ treeElement.classList.remove("drag-over");
2097
+ this.currentDropTarget?.classList.remove("drop-target");
2098
+ this.currentDropTarget = void 0;
2099
+ }
2100
+ };
2101
+ const dropHandler = async (e) => {
2102
+ e.preventDefault();
2103
+ treeElement.classList.remove("drag-over");
2104
+ this.currentDropTarget?.classList.remove("drop-target");
2105
+ this.currentDropTarget = void 0;
2106
+ if (!e.dataTransfer || !this.workspaceDir) return;
2107
+ const files = Array.from(e.dataTransfer.files);
2108
+ if (files.length === 0) return;
2109
+ const targetDir = await this.getDropTarget(e);
2110
+ await this.handleFilesDrop(files, targetDir);
2111
+ };
2112
+ treeElement.removeEventListener("dragover", dragOverHandler);
2113
+ treeElement.removeEventListener("dragenter", dragEnterHandler);
2114
+ treeElement.removeEventListener("dragleave", dragLeaveHandler);
2115
+ treeElement.removeEventListener("drop", dropHandler);
2116
+ treeElement.addEventListener("dragover", dragOverHandler);
2117
+ treeElement.addEventListener("dragenter", dragEnterHandler);
2118
+ treeElement.addEventListener("dragleave", dragLeaveHandler);
2119
+ treeElement.addEventListener("drop", dropHandler);
2120
+ }
2121
+ async getDropTarget(e) {
2122
+ const target = e.target;
2123
+ const treeItem = target.closest("wa-tree-item");
2124
+ if (treeItem) {
2125
+ const node = treeItem.model;
2126
+ const resource = node.data;
2127
+ if (resource instanceof Directory) {
2128
+ return resource;
2129
+ }
2130
+ const parent = resource.getParent();
2131
+ if (parent) {
2132
+ return parent;
2133
+ }
2134
+ }
2135
+ return this.workspaceDir;
2136
+ }
2137
+ async handleFilesDrop(files, targetDir) {
2138
+ const total = files.length;
2139
+ let processed = 0;
2140
+ let failed = 0;
2141
+ let skipped = 0;
2142
+ for (const file of files) {
2143
+ try {
2144
+ const targetPath = this.buildTargetPath(targetDir, file.name);
2145
+ const existingFile = await this.workspaceDir.getResource(targetPath);
2146
+ if (existingFile) {
2147
+ const overwrite = await confirmDialog(t$5("FILE_EXISTS_OVERWRITE", { fileName: file.name }));
2148
+ if (!overwrite) {
2149
+ skipped++;
2150
+ continue;
2151
+ }
2152
+ }
2153
+ const workspaceFile = await this.workspaceDir.getResource(
2154
+ targetPath,
2155
+ { create: true }
2156
+ );
2157
+ await workspaceFile.saveContents(file);
2158
+ processed++;
2159
+ } catch (error) {
2160
+ console.error(`Failed to upload ${file.name}:`, error);
2161
+ failed++;
2162
+ }
2163
+ }
2164
+ console.log(`Uploaded ${processed}/${total} files${skipped > 0 ? `, ${skipped} skipped` : ""}${failed > 0 ? `, ${failed} failed` : ""}`);
2165
+ await this.loadWorkspace(this.workspaceDir);
2166
+ }
2167
+ buildTargetPath(targetDir, fileName) {
2168
+ const dirPath = targetDir.getWorkspacePath();
2169
+ return dirPath ? `${dirPath}/${fileName}` : fileName;
2170
+ }
2171
+ render() {
2172
+ return html`
2173
+ <div class="tree" ${ref(this.treeRef)} style="--drop-files-text: '${t$5("DROP_FILES_HERE")}'">
2174
+ ${when(!this.workspaceDir, () => html`
2175
+ <lyra-no-content message="${t$5("SELECT_WORKSPACE")}"></lyra-no-content>`, () => when(this.root, () => html`
2176
+ <wa-tree @wa-selection-change=${this.nobubble(this.onSelectionChanged)}
2177
+ style="--indent-guide-width: 1px;">
2178
+ ${this.root.children.map((child) => this.createTreeItems(child, true))}
2179
+ </wa-tree>`, () => html``))}
2180
+ </div>
2181
+ `;
2182
+ }
2183
+ };
2184
+ LyraFileBrowser.styles = css`
2185
+ :host {
2186
+ }
2187
+
2188
+ .tree {
2189
+ height: 100%;
2190
+ position: relative;
2191
+ transition: all 0.2s ease;
2192
+ }
2193
+
2194
+ .tree.drag-over {
2195
+ background-color: var(--wa-color-brand-fill-quiet);
2196
+ outline: 2px dashed var(--wa-color-brand-border-normal);
2197
+ outline-offset: -4px;
2198
+ border-radius: var(--wa-border-radius-medium);
2199
+ }
2200
+
2201
+ .tree.drag-over::before {
2202
+ content: var(--drop-files-text);
2203
+ position: absolute;
2204
+ top: 50%;
2205
+ left: 50%;
2206
+ transform: translate(-50%, -50%);
2207
+ background: var(--wa-color-brand-fill-loud);
2208
+ color: var(--wa-color-brand-on-loud);
2209
+ padding: var(--wa-spacing-large);
2210
+ border-radius: var(--wa-border-radius-large);
2211
+ font-weight: bold;
2212
+ pointer-events: none;
2213
+ z-index: 1000;
2214
+ opacity: 0.3;
2215
+ }
2216
+
2217
+ .tree-label {
2218
+ display: inline-flex;
2219
+ align-items: center;
2220
+ gap: 0.4rem;
2221
+ }
2222
+
2223
+ .tree-label-text {
2224
+ white-space: nowrap;
2225
+ }
2226
+
2227
+ wa-tree-item.drop-target {
2228
+ background-color: var(--wa-color-brand-fill-loud);
2229
+ color: var(--wa-color-brand-on-loud);
2230
+ border-radius: var(--wa-border-radius-small);
2231
+ outline: 2px solid var(--wa-color-brand-border-loud);
2232
+ outline-offset: -2px;
2233
+ }
2234
+ `;
2235
+ __decorateClass$8([
2236
+ state()
2237
+ ], LyraFileBrowser.prototype, "root", 2);
2238
+ __decorateClass$8([
2239
+ state()
2240
+ ], LyraFileBrowser.prototype, "fileEditorOptions", 2);
2241
+ LyraFileBrowser = __decorateClass$8([
2242
+ customElement("lyra-filebrowser")
2243
+ ], LyraFileBrowser);
2244
+ var __getOwnPropDesc$7 = Object.getOwnPropertyDescriptor;
2245
+ var __decorateClass$7 = (decorators, target, key, kind) => {
2246
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$7(target, key) : target;
2247
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
2248
+ if (decorator = decorators[i])
2249
+ result = decorator(result) || result;
2250
+ return result;
2251
+ };
2252
+ const t$4 = i18n("tasks");
2253
+ contributionRegistry.registerContribution(TOOLBAR_BOTTOM_CENTER, {
2254
+ html: "<lyra-tasks></lyra-tasks>"
2255
+ });
2256
+ let progressDialogContainer = null;
2257
+ function getProgressDialogContainer() {
2258
+ if (!progressDialogContainer) {
2259
+ progressDialogContainer = document.createElement("div");
2260
+ progressDialogContainer.id = "progress-dialog-container";
2261
+ document.body.appendChild(progressDialogContainer);
2262
+ }
2263
+ return progressDialogContainer;
2264
+ }
2265
+ function getDialogElement() {
2266
+ const container = getProgressDialogContainer();
2267
+ return container.querySelector("wa-dialog");
2268
+ }
2269
+ function showProgressDialog() {
2270
+ updateProgressDialog(true);
2271
+ }
2272
+ function updateProgressDialog(forceOpen = false) {
2273
+ const container = getProgressDialogContainer();
2274
+ const tasks = taskService.getActiveTasks();
2275
+ const currentTaskCount = tasks.length;
2276
+ if (currentTaskCount === 0) {
2277
+ render(html``, container);
2278
+ return;
2279
+ }
2280
+ const existingDialog = getDialogElement();
2281
+ const isOpen = forceOpen || existingDialog?.open === true;
2282
+ if (!isOpen) {
2283
+ return;
2284
+ }
2285
+ const handleClose = () => {
2286
+ const dialog = getDialogElement();
2287
+ if (dialog) {
2288
+ dialog.open = false;
2289
+ }
2290
+ };
2291
+ const handleAfterHide = () => {
2292
+ render(html``, container);
2293
+ };
2294
+ const template = html`
2295
+ <wa-dialog
2296
+ label="${t$4("ACTIVE_TASKS")}"
2297
+ open
2298
+ light-dismiss
2299
+ style="--width: 600px;"
2300
+ @wa-request-close=${handleClose}
2301
+ @wa-after-hide=${handleAfterHide}
2302
+ >
2303
+ <style>
2304
+ .progress-dialog-content {
2305
+ display: flex;
2306
+ flex-direction: column;
2307
+ gap: 1.5rem;
2308
+ }
2309
+
2310
+ .tasitem {
2311
+ display: flex;
2312
+ flex-direction: column;
2313
+ gap: 0.75rem;
2314
+ padding: 1rem;
2315
+ background: var(--wa-color-neutral-10);
2316
+ border-radius: 8px;
2317
+ border: 1px solid var(--wa-color-neutral-20);
2318
+ }
2319
+
2320
+ :host-context(.wa-light) .tasitem {
2321
+ background: var(--wa-color-neutral-95);
2322
+ border: 1px solid var(--wa-color-neutral-85);
2323
+ }
2324
+
2325
+ .tasheader {
2326
+ display: flex;
2327
+ align-items: center;
2328
+ gap: 0.75rem;
2329
+ }
2330
+
2331
+ .tasname {
2332
+ font-weight: 600;
2333
+ font-size: 1rem;
2334
+ color: var(--wa-color-neutral-90);
2335
+ }
2336
+
2337
+ :host-context(.wa-light) .tasname {
2338
+ color: var(--wa-color-neutral-10);
2339
+ }
2340
+
2341
+ .tasmessage {
2342
+ font-size: 0.875rem;
2343
+ color: var(--wa-color-neutral-70);
2344
+ margin-top: 0.25rem;
2345
+ }
2346
+
2347
+ :host-context(.wa-light) .tasmessage {
2348
+ color: var(--wa-color-neutral-30);
2349
+ }
2350
+
2351
+ .tasprogress {
2352
+ margin-top: 0.5rem;
2353
+ }
2354
+
2355
+ wa-progress-bar {
2356
+ --tracheight: 1.5rem;
2357
+ }
2358
+
2359
+ wa-progress-bar::part(label) {
2360
+ text-align: center;
2361
+ width: 100%;
2362
+ font-size: 0.875rem;
2363
+ }
2364
+
2365
+ .no-tasks {
2366
+ text-align: center;
2367
+ padding: 2rem;
2368
+ color: var(--wa-color-neutral-60);
2369
+ }
2370
+
2371
+ :host-context(.wa-light) .no-tasks {
2372
+ color: var(--wa-color-neutral-40);
2373
+ }
2374
+ </style>
2375
+
2376
+ <div class="progress-dialog-content">
2377
+ ${tasks.map((taskProgress) => {
2378
+ const hasProgress = taskProgress.progress >= 0 || taskProgress.totalSteps > 0;
2379
+ const progress = taskProgress.progress >= 0 ? taskProgress.progress : taskProgress.totalSteps > 0 ? Math.round(taskProgress.currentStep / taskProgress.totalSteps * 100) : 0;
2380
+ const showSteps = taskProgress.progress < 0 && taskProgress.totalSteps > 0;
2381
+ return html`
2382
+ <div class="tasitem">
2383
+ <div class="tasheader">
2384
+ <wa-icon name="hourglass" style="color: var(--wa-color-warning-fill-loud);"></wa-icon>
2385
+ <div style="flex: 1;">
2386
+ <div class="tasname">${taskProgress.name}</div>
2387
+ ${taskProgress.message ? html`
2388
+ <div class="tasmessage">${taskProgress.message}</div>
2389
+ ` : ""}
2390
+ </div>
2391
+ </div>
2392
+ <div class="tasprogress">
2393
+ ${hasProgress ? html`
2394
+ <wa-progress-bar value="${progress}">
2395
+ ${showSteps ? `${taskProgress.currentStep}/${taskProgress.totalSteps} - ` : ""}${progress}%
2396
+ </wa-progress-bar>
2397
+ ` : html`
2398
+ <wa-progress-bar indeterminate></wa-progress-bar>
2399
+ `}
2400
+ </div>
2401
+ </div>
2402
+ `;
2403
+ })}
2404
+ </div>
2405
+ </wa-dialog>
2406
+ `;
2407
+ render(template, container);
2408
+ }
2409
+ let LyraTasks = class extends LyraPart {
2410
+ doBeforeUI() {
2411
+ this.watch(activeTasksSignal, () => {
2412
+ updateProgressDialog();
2413
+ this.requestUpdate();
2414
+ });
2415
+ }
2416
+ handleIndicatorClick() {
2417
+ showProgressDialog();
2418
+ }
2419
+ render() {
2420
+ activeTasksSignal.get();
2421
+ const tasks = taskService.getActiveTasks();
2422
+ const taskCount = tasks.length;
2423
+ if (taskCount === 0) {
2424
+ return html``;
2425
+ }
2426
+ return html`
2427
+ <div class="tasindicator" @click=${this.handleIndicatorClick} title="${t$4("ACTIVE_TASKS_TITLE", { taskCount: taskCount.toString() })}">
2428
+ <wa-spinner
2429
+ style="font-size: 1rem; --indicator-color: var(--wa-color-warning-fill-loud);"
2430
+ label="${t$4("ACTIVE_TASKS")}"
2431
+ ></wa-spinner>
2432
+ <span class="tascount">${taskCount}</span>
2433
+ <div class="tasbar-wrap"><wa-progress-bar indeterminate></wa-progress-bar></div>
2434
+ </div>
2435
+ `;
2436
+ }
2437
+ };
2438
+ LyraTasks.styles = css`
2439
+ :host {
2440
+ display: flex;
2441
+ align-items: center;
2442
+ }
2443
+
2444
+ .tasindicator {
2445
+ display: flex;
2446
+ align-items: center;
2447
+ gap: 0.5rem;
2448
+ cursor: pointer;
2449
+ padding: 0.25rem 0.5rem;
2450
+ border-radius: 4px;
2451
+ transition: background-color 0.2s;
2452
+ }
2453
+
2454
+ .tasindicator:hover {
2455
+ background: var(--wa-color-neutral-15);
2456
+ }
2457
+
2458
+ :host-context(.wa-light) .tasindicator:hover {
2459
+ background: var(--wa-color-neutral-85);
2460
+ }
2461
+
2462
+ .tascount {
2463
+ font-size: 0.875rem;
2464
+ color: var(--wa-color-neutral-70);
2465
+ }
2466
+
2467
+ :host-context(.wa-light) .tascount {
2468
+ color: var(--wa-color-neutral-30);
2469
+ }
2470
+
2471
+ .tasbar-wrap {
2472
+ width: 3rem;
2473
+ }
2474
+
2475
+ .tasbar-wrap wa-progress-bar {
2476
+ --tracheight: 4px;
2477
+ }
2478
+ `;
2479
+ LyraTasks = __decorateClass$7([
2480
+ customElement("lyra-tasks")
2481
+ ], LyraTasks);
2482
+ var __getOwnPropDesc$6 = Object.getOwnPropertyDescriptor;
2483
+ var __decorateClass$6 = (decorators, target, key, kind) => {
2484
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$6(target, key) : target;
2485
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
2486
+ if (decorator = decorators[i])
2487
+ result = decorator(result) || result;
2488
+ return result;
2489
+ };
2490
+ const t$3 = i18n("partname");
2491
+ contributionRegistry.registerContribution(TOOLBAR_BOTTOM_CENTER, {
2492
+ html: "<lyra-part-name></lyra-part-name>"
2493
+ });
2494
+ let LyraPartName = class extends LyraElement {
2495
+ doBeforeUI() {
2496
+ this.watch(activePartSignal, () => {
2497
+ this.requestUpdate();
2498
+ });
2499
+ }
2500
+ getPartName() {
2501
+ const activePart = activePartSignal.get();
2502
+ if (!activePart) {
2503
+ return t$3("NO_PART");
2504
+ }
2505
+ return activePart.tabContribution?.label || activePart.getAttribute("id") || t$3("NO_PART");
2506
+ }
2507
+ render() {
2508
+ const activePart = activePartSignal.get();
2509
+ const partIcon = activePart?.tabContribution?.icon || "box";
2510
+ return html`
2511
+ <wa-button
2512
+ appearance="plain"
2513
+ size="small"
2514
+ title="${t$3("ACTIVE_PART")}">
2515
+ <lyra-icon slot="start" name="${partIcon}" label="Part"></lyra-icon>
2516
+ ${this.getPartName()}
2517
+ </wa-button>
2518
+ `;
2519
+ }
2520
+ };
2521
+ LyraPartName = __decorateClass$6([
2522
+ customElement("lyra-part-name")
2523
+ ], LyraPartName);
2524
+ var __defProp$4 = Object.defineProperty;
2525
+ var __getOwnPropDesc$5 = Object.getOwnPropertyDescriptor;
2526
+ var __decorateClass$5 = (decorators, target, key, kind) => {
2527
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$5(target, key) : target;
2528
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
2529
+ if (decorator = decorators[i])
2530
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
2531
+ if (kind && result) __defProp$4(target, key, result);
2532
+ return result;
2533
+ };
2534
+ const logger$1 = createLogger("LyraExtensions");
2535
+ const t$2 = i18n("extensions");
2536
+ let LyraExtensions = class extends LyraPart {
2537
+ constructor() {
2538
+ super(...arguments);
2539
+ this.filterText = "";
2540
+ this.showRegisterDialog = false;
2541
+ this.registerExtensionData = {};
2542
+ }
2543
+ doInitUI() {
2544
+ subscribe(TOPIC_EXTENSIONS_CHANGED, () => {
2545
+ this.requestUpdate();
2546
+ });
2547
+ }
2548
+ onExtensionDblClick() {
2549
+ }
2550
+ enable(extension) {
2551
+ extensionRegistry.enable(extension.id, true);
2552
+ this.requestUpdate();
2553
+ }
2554
+ disable(extension) {
2555
+ extensionRegistry.disable(extension.id, true);
2556
+ this.requestUpdate();
2557
+ }
2558
+ isExtensionRequired(extensionId) {
2559
+ const currentApp = appLoaderService.getCurrentApp();
2560
+ if (!currentApp || !currentApp.extensions) {
2561
+ return false;
2562
+ }
2563
+ return currentApp.extensions.includes(extensionId);
2564
+ }
2565
+ selectionChanged(e) {
2566
+ const selection = e.detail.selection || [];
2567
+ if (selection.length > 0 && selection[0].model) {
2568
+ this.selectedExtension = selection[0].model;
2569
+ } else {
2570
+ this.selectedExtension = void 0;
2571
+ }
2572
+ }
2573
+ getFilteredExtensions() {
2574
+ if (!this.filterText.trim()) {
2575
+ return extensionRegistry.getExtensions();
2576
+ }
2577
+ const filter = this.filterText.toLowerCase();
2578
+ return extensionRegistry.getExtensions().filter((ext) => {
2579
+ return String(ext.name).toLowerCase().includes(filter) || (ext.description ? String(ext.description).toLowerCase().includes(filter) : false) || ext.id.toLowerCase().includes(filter);
2580
+ });
2581
+ }
2582
+ getGroupedExtensions() {
2583
+ const filtered = this.getFilteredExtensions();
2584
+ const enabled = [];
2585
+ const available = [];
2586
+ filtered.forEach((ext) => {
2587
+ if (extensionRegistry.isEnabled(ext.id)) {
2588
+ enabled.push(ext);
2589
+ } else {
2590
+ available.push(ext);
2591
+ }
2592
+ });
2593
+ enabled.sort((a, b) => String(a.name).localeCompare(String(b.name)));
2594
+ available.sort((a, b) => String(a.name).localeCompare(String(b.name)));
2595
+ return { enabled, available };
2596
+ }
2597
+ isExternalExtension(extension) {
2598
+ return extension.external === true;
2599
+ }
2600
+ handleFilterInput(e) {
2601
+ this.filterText = e.target.value;
2602
+ this.requestUpdate();
2603
+ }
2604
+ clearFilter() {
2605
+ this.filterText = "";
2606
+ this.requestUpdate();
2607
+ }
2608
+ async handleRegisterExtension() {
2609
+ try {
2610
+ const url = await promptDialog("Enter extension URL or source identifier:", "", false);
2611
+ if (!url) {
2612
+ return;
2613
+ }
2614
+ await taskService.runAsync("Registering extension", async () => {
2615
+ let finalUrl = url;
2616
+ if (esmShService.isGitHubUrl(url)) {
2617
+ finalUrl = esmShService.convertGitHubUrlToSource(url);
2618
+ }
2619
+ const parsed = esmShService.parseSource(finalUrl);
2620
+ if (parsed?.type === "github") {
2621
+ await this.fetchGitHubMetadata(parsed, finalUrl);
2622
+ } else {
2623
+ this.registerExtensionData = {
2624
+ url: finalUrl,
2625
+ id: "",
2626
+ name: "",
2627
+ description: ""
2628
+ };
2629
+ this.showRegisterDialog = true;
2630
+ this.requestUpdate();
2631
+ }
2632
+ });
2633
+ } catch (error) {
2634
+ toastError(`Failed to register extension: ${error}`);
2635
+ }
2636
+ }
2637
+ async fetchGitHubMetadata(parsed, url) {
2638
+ try {
2639
+ const packageJson = await esmShService.fetchGitHubPackageJson(parsed);
2640
+ const owner = parsed.owner;
2641
+ const repo = parsed.repo;
2642
+ const extensionId = packageJson.name || `ext.${owner}-${repo}`;
2643
+ const extensionName = packageJson.name || `${owner}/${repo}`;
2644
+ const description = packageJson.description || "";
2645
+ const version = packageJson.version || "";
2646
+ const author = packageJson.author || (typeof packageJson.author === "string" ? packageJson.author : packageJson.author?.name) || "";
2647
+ this.registerExtensionData = {
2648
+ id: extensionId,
2649
+ name: extensionName,
2650
+ description,
2651
+ url,
2652
+ version,
2653
+ author,
2654
+ icon: "puzzle-piece",
2655
+ external: true
2656
+ };
2657
+ this.showRegisterDialog = true;
2658
+ this.requestUpdate();
2659
+ } catch (error) {
2660
+ logger$1.warn(`Could not fetch package.json, using defaults: ${error}`);
2661
+ this.registerExtensionData = {
2662
+ url,
2663
+ id: "",
2664
+ name: "",
2665
+ description: ""
2666
+ };
2667
+ this.showRegisterDialog = true;
2668
+ this.requestUpdate();
2669
+ }
2670
+ }
2671
+ async confirmRegisterExtension() {
2672
+ if (!this.registerExtensionData.url) {
2673
+ toastError("URL is required");
2674
+ return;
2675
+ }
2676
+ if (!this.registerExtensionData.id) {
2677
+ toastError("Extension ID is required");
2678
+ return;
2679
+ }
2680
+ if (!this.registerExtensionData.name) {
2681
+ toastError("Extension name is required");
2682
+ return;
2683
+ }
2684
+ try {
2685
+ await taskService.runAsync("Registering extension", async () => {
2686
+ const extension = {
2687
+ id: this.registerExtensionData.id,
2688
+ name: this.registerExtensionData.name,
2689
+ description: this.registerExtensionData.description,
2690
+ url: this.registerExtensionData.url,
2691
+ version: this.registerExtensionData.version,
2692
+ author: this.registerExtensionData.author,
2693
+ icon: this.registerExtensionData.icon || "puzzle-piece",
2694
+ external: true
2695
+ };
2696
+ extensionRegistry.registerExtension(extension);
2697
+ await extensionRegistry.loadExtensionFromUrl(this.registerExtensionData.url, extension.id);
2698
+ toastInfo(`Extension ${extension.name} registered successfully`);
2699
+ this.showRegisterDialog = false;
2700
+ this.registerExtensionData = {};
2701
+ this.requestUpdate();
2702
+ });
2703
+ } catch (error) {
2704
+ toastError(`Failed to register extension: ${error}`);
2705
+ }
2706
+ }
2707
+ cancelRegisterExtension() {
2708
+ this.showRegisterDialog = false;
2709
+ this.registerExtensionData = {};
2710
+ this.requestUpdate();
2711
+ }
2712
+ renderToolbar() {
2713
+ return html`
2714
+ <wa-input
2715
+ placeholder="${t$2("FILTER_PLACEHOLDER")}"
2716
+ .value=${this.filterText}
2717
+ @input=${(e) => this.handleFilterInput(e)}
2718
+ @wa-clear=${() => this.clearFilter()}
2719
+ with-clear
2720
+ size="small"
2721
+ style="width: 300px;">
2722
+ <wa-icon slot="start" name="magnifying-glass" label="Filter"></wa-icon>
2723
+ </wa-input>
2724
+ <wa-button
2725
+ variant="primary"
2726
+ appearance="plain"
2727
+ @click=${() => this.handleRegisterExtension()}
2728
+ title="Register a new extension">
2729
+ <wa-icon name="plus" label="Add"></wa-icon>
2730
+ Register Extension
2731
+ </wa-button>
2732
+ `;
2733
+ }
2734
+ render() {
2735
+ const grouped = this.getGroupedExtensions();
2736
+ const hasAnyExtensions = grouped.enabled.length > 0 || grouped.available.length > 0;
2737
+ return html`
2738
+ ${when(this.showRegisterDialog, () => html`
2739
+ <wa-dialog
2740
+ label="Register Extension"
2741
+ open
2742
+ @wa-request-close=${() => this.cancelRegisterExtension()}
2743
+ style="--wa-dialog-width: 500px;">
2744
+ <div style="display: flex; flex-direction: column; gap: 1rem; padding: 1rem;">
2745
+ <wa-input
2746
+ label="Extension ID *"
2747
+ .value=${this.registerExtensionData.id || ""}
2748
+ @input=${(e) => {
2749
+ this.registerExtensionData.id = e.target.value;
2750
+ this.requestUpdate();
2751
+ }}
2752
+ required
2753
+ hint="Unique identifier for the extension (e.g., 'ext.my-extension')">
2754
+ </wa-input>
2755
+
2756
+ <wa-input
2757
+ label="Name *"
2758
+ .value=${this.registerExtensionData.name || ""}
2759
+ @input=${(e) => {
2760
+ this.registerExtensionData.name = e.target.value;
2761
+ this.requestUpdate();
2762
+ }}
2763
+ required
2764
+ hint="Display name of the extension">
2765
+ </wa-input>
2766
+
2767
+ <wa-input
2768
+ label="Description"
2769
+ .value=${this.registerExtensionData.description || ""}
2770
+ @input=${(e) => {
2771
+ this.registerExtensionData.description = e.target.value;
2772
+ this.requestUpdate();
2773
+ }}
2774
+ hint="Description of what the extension does">
2775
+ </wa-input>
2776
+
2777
+ <wa-input
2778
+ label="URL *"
2779
+ .value=${this.registerExtensionData.url || ""}
2780
+ @input=${(e) => {
2781
+ this.registerExtensionData.url = e.target.value;
2782
+ this.requestUpdate();
2783
+ }}
2784
+ required
2785
+ readonly
2786
+ hint="Extension source URL or identifier">
2787
+ </wa-input>
2788
+
2789
+ <div style="display: flex; gap: 0.5rem;">
2790
+ <wa-input
2791
+ label="Version"
2792
+ .value=${this.registerExtensionData.version || ""}
2793
+ @input=${(e) => {
2794
+ this.registerExtensionData.version = e.target.value;
2795
+ this.requestUpdate();
2796
+ }}
2797
+ style="flex: 1;"
2798
+ hint="Extension version">
2799
+ </wa-input>
2800
+
2801
+ <wa-input
2802
+ label="Author"
2803
+ .value=${this.registerExtensionData.author || ""}
2804
+ @input=${(e) => {
2805
+ this.registerExtensionData.author = e.target.value;
2806
+ this.requestUpdate();
2807
+ }}
2808
+ style="flex: 1;"
2809
+ hint="Extension author">
2810
+ </wa-input>
2811
+ </div>
2812
+
2813
+ <wa-input
2814
+ label="Icon"
2815
+ .value=${this.registerExtensionData.icon || "puzzle-piece"}
2816
+ @input=${(e) => {
2817
+ this.registerExtensionData.icon = e.target.value;
2818
+ this.requestUpdate();
2819
+ }}
2820
+ hint="Icon name (FontAwesome icon)">
2821
+ </wa-input>
2822
+
2823
+ <div style="display: flex; justify-content: flex-end; gap: 0.5rem; margin-top: 1rem;">
2824
+ <wa-button
2825
+ variant="default"
2826
+ @click=${() => this.cancelRegisterExtension()}>
2827
+ Cancel
2828
+ </wa-button>
2829
+ <wa-button
2830
+ variant="primary"
2831
+ @click=${() => this.confirmRegisterExtension()}
2832
+ ?disabled=${!this.registerExtensionData.id || !this.registerExtensionData.name || !this.registerExtensionData.url}>
2833
+ Register
2834
+ </wa-button>
2835
+ </div>
2836
+ </div>
2837
+ </wa-dialog>
2838
+ `)}
2839
+ <wa-split-panel position="30" style="height: 100%">
2840
+ <wa-tree
2841
+ selection="leaf"
2842
+ style="--indent-guide-width: 1px;"
2843
+ slot="start"
2844
+ @wa-selection-change="${this.selectionChanged}">
2845
+ ${hasAnyExtensions ? html`
2846
+ ${grouped.enabled.length > 0 ? html`
2847
+ <wa-tree-item expanded>
2848
+ <span>
2849
+ <wa-icon name="check-circle" style="color: var(--wa-color-success-50);"></wa-icon>
2850
+ ${t$2("INSTALLED")} (${grouped.enabled.length})
2851
+ </span>
2852
+ ${grouped.enabled.map((e) => {
2853
+ const isExternal = this.isExternalExtension(e);
2854
+ return html`
2855
+ <wa-tree-item @dblclick=${this.nobubble(this.onExtensionDblClick)} .model=${e}>
2856
+ <span><lyra-icon name="${e.icon}"></lyra-icon></span> ${e.name}${isExternal ? html` <span style="opacity: 0.6; font-size: 0.9em; margin-left: 0.5rem;">(External)</span>` : ""}
2857
+ </wa-tree-item>
2858
+ `;
2859
+ })}
2860
+ </wa-tree-item>
2861
+ ` : ""}
2862
+ ${grouped.available.length > 0 ? html`
2863
+ <wa-tree-item expanded>
2864
+ <span>
2865
+ <wa-icon name="circle" style="color: var(--wa-color-neutral-50);"></wa-icon>
2866
+ ${t$2("AVAILABLE")} (${grouped.available.length})
2867
+ </span>
2868
+ ${grouped.available.map((e) => {
2869
+ const isExternal = this.isExternalExtension(e);
2870
+ return html`
2871
+ <wa-tree-item @dblclick=${this.nobubble(this.onExtensionDblClick)} .model=${e}>
2872
+ <span><lyra-icon name="${e.icon}"></lyra-icon></span> ${e.name}${isExternal ? html` <span style="opacity: 0.6; font-size: 0.9em; margin-left: 0.5rem;">(External)</span>` : ""}
2873
+ </wa-tree-item>
2874
+ `;
2875
+ })}
2876
+ </wa-tree-item>
2877
+ ` : ""}
2878
+ ` : ""}
2879
+ ${!hasAnyExtensions ? html`
2880
+ <div style="padding: 1em; text-align: center; opacity: 0.7;">
2881
+ ${t$2("NO_MATCHES", { filterText: this.filterText })}
2882
+ </div>
2883
+ ` : ""}
2884
+ </wa-tree>
2885
+ <div slot="end" style="padding: 1em;">
2886
+ ${when(this.selectedExtension, (e) => {
2887
+ const isExternal = this.isExternalExtension(e);
2888
+ const isEnabled = extensionRegistry.isEnabled(e.id);
2889
+ return html`
2890
+ <h1><lyra-icon name="${e.icon}"></lyra-icon> ${e.name}${isExternal ? " (External)" : ""}</h1>
2891
+ ${when(isExternal, () => html`
2892
+ <div class="marketplace-badge">
2893
+ <wa-icon name="store"></wa-icon>
2894
+ <span>${t$2("EXTERNAL_EXTENSION")}</span>
2895
+ </div>
2896
+ `)}
2897
+ <hr>
2898
+ <div>
2899
+ ${when(isEnabled, () => html`
2900
+ <wa-button
2901
+ title="${this.isExtensionRequired(e.id) ? t$2("REQUIRED_HINT") : t$2("DISABLE_TITLE")}"
2902
+ @click="${() => this.disable(e)}"
2903
+ variant="danger"
2904
+ appearance="plain"
2905
+ ?disabled=${this.isExtensionRequired(e.id)}>
2906
+ <wa-icon name="xmark" label="Uninstall"></wa-icon>&nbsp;${t$2("UNINSTALL")}
2907
+ </wa-button>
2908
+ ${when(this.isExtensionRequired(e.id), () => html`
2909
+ <div class="required-hint">
2910
+ <wa-icon name="info-circle" style="color: var(--wa-color-primary-50);"></wa-icon>
2911
+ <span>${t$2("REQUIRED_HINT")}</span>
2912
+ </div>
2913
+ `)}
2914
+ `, () => html`
2915
+ <wa-button
2916
+ title="${t$2("ENABLE_TITLE")}"
2917
+ @click="${() => this.enable(e)}"
2918
+ variant="brand"
2919
+ appearance="plain">
2920
+ <wa-icon name="download" label="Install"></wa-icon>&nbsp;${t$2("INSTALL")}
2921
+ </wa-button>
2922
+ `)}
2923
+ </div>
2924
+
2925
+ ${when(e.experimental, () => html`
2926
+ <div style="margin-top: 1em;">
2927
+ <wa-button size="small" variant="warning" appearance="plain">
2928
+ <wa-icon name="triangle-exclamation" label="Warning"></wa-icon>
2929
+ </wa-button>
2930
+ <small><i>${t$2("EXPERIMENTAL")}</i></small>
2931
+ </div>
2932
+ `)}
2933
+
2934
+ ${when(e.version || e.author, () => html`
2935
+ <div style="margin-top: 1em; display: flex; flex-direction: column; gap: 0.5rem;">
2936
+ ${when(e.version, () => html`
2937
+ <div style="display: flex; align-items: center; gap: 0.5rem;">
2938
+ <wa-icon name="tag" style="font-size: 0.9em; opacity: 0.7;"></wa-icon>
2939
+ <span style="font-size: 0.9em; opacity: 0.8;">${t$2("VERSION")} <strong>${e.version}</strong></span>
2940
+ </div>
2941
+ `)}
2942
+ ${when(e.author, () => html`
2943
+ <div style="display: flex; align-items: center; gap: 0.5rem;">
2944
+ <wa-icon name="user" style="font-size: 0.9em; opacity: 0.7;"></wa-icon>
2945
+ <span style="font-size: 0.9em; opacity: 0.8;">${t$2("AUTHOR")} <strong>${e.author}</strong></span>
2946
+ </div>
2947
+ `)}
2948
+ </div>
2949
+ `)}
2950
+
2951
+ <p style="margin-top: 1em;">${e.description}</p>
2952
+
2953
+ ${when(e.dependencies && e.dependencies.length > 0, () => html`
2954
+ <div style="margin-top: 1.5em;">
2955
+ <h3 style="margin-bottom: 0.5em;">
2956
+ <wa-icon name="puzzle-piece" style="font-size: 0.9em;"></wa-icon>
2957
+ ${t$2("DEPENDENCIES")}
2958
+ </h3>
2959
+ <div class="dependencies-list">
2960
+ ${e.dependencies.map((depId) => {
2961
+ const depExt = extensionRegistry.getExtensions().find((ex) => ex.id === depId);
2962
+ const isEnabled2 = extensionRegistry.isEnabled(depId);
2963
+ return html`
2964
+ <div class="dependency-item">
2965
+ <wa-icon
2966
+ name="${isEnabled2 ? "check-circle" : "circle"}"
2967
+ style="color: ${isEnabled2 ? "var(--wa-color-success-50)" : "var(--wa-color-neutral-50)"};">
2968
+ </wa-icon>
2969
+ <lyra-icon name="${depExt?.icon || "puzzle-piece"}"></lyra-icon>
2970
+ <span>${depExt?.name || depId}</span>
2971
+ ${!isEnabled2 ? html`
2972
+ <span class="dependency-badge">${t$2("NOT_INSTALLED")}</span>
2973
+ ` : ""}
2974
+ </div>
2975
+ `;
2976
+ })}
2977
+ </div>
2978
+ <small style="opacity: 0.7; display: block; margin-top: 0.5em;">
2979
+ <wa-icon name="info-circle" style="font-size: 0.9em;"></wa-icon>
2980
+ ${t$2("DEPENDENCIES_HINT")}
2981
+ </small>
2982
+ </div>
2983
+ `)}
2984
+ `;
2985
+ })}
2986
+ </div>
2987
+ </wa-split-panel>
2988
+ `;
2989
+ }
2990
+ };
2991
+ LyraExtensions.styles = css`
2992
+ :host {
2993
+ }
2994
+
2995
+ wa-tree-item > span {
2996
+ display: flex;
2997
+ align-items: center;
2998
+ gap: 0.5rem;
2999
+ }
3000
+
3001
+ wa-tree-item:has(> wa-tree-item) {
3002
+ font-weight: 500;
3003
+ }
3004
+
3005
+ .dependencies-list {
3006
+ display: flex;
3007
+ flex-direction: column;
3008
+ gap: 0.5rem;
3009
+ }
3010
+
3011
+ .dependency-item {
3012
+ display: flex;
3013
+ align-items: center;
3014
+ gap: 0.5rem;
3015
+ padding: 0.5rem;
3016
+ border-radius: 4px;
3017
+ background: var(--wa-color-surface-variant);
3018
+ }
3019
+
3020
+ .dependency-item wa-icon:first-child {
3021
+ flex-shrink: 0;
3022
+ }
3023
+
3024
+ .dependency-item icon {
3025
+ flex-shrink: 0;
3026
+ }
3027
+
3028
+ .dependency-item span:not(.dependency-badge) {
3029
+ flex: 1;
3030
+ }
3031
+
3032
+ .dependency-badge {
3033
+ font-size: 0.85rem;
3034
+ padding: 0.25rem 0.5rem;
3035
+ border-radius: 4px;
3036
+ background: var(--wa-color-warning-100);
3037
+ color: var(--wa-color-warning-900);
3038
+ }
3039
+
3040
+ .required-hint {
3041
+ display: flex;
3042
+ align-items: center;
3043
+ gap: 0.5rem;
3044
+ margin-top: 0.75rem;
3045
+ padding: 0.5rem;
3046
+ border-radius: 4px;
3047
+ background: var(--wa-color-primary-10);
3048
+ color: var(--wa-color-primary-70);
3049
+ font-size: 0.875rem;
3050
+ }
3051
+
3052
+ .required-hint wa-icon {
3053
+ flex-shrink: 0;
3054
+ }
3055
+
3056
+ .required-hint span {
3057
+ line-height: 1.4;
3058
+ }
3059
+
3060
+ .marketplace-badge {
3061
+ display: inline-flex;
3062
+ align-items: center;
3063
+ gap: 0.5rem;
3064
+ padding: 0.375rem 0.875rem;
3065
+ border-radius: 4px;
3066
+ background: var(--wa-color-primary-10);
3067
+ color: var(--wa-color-primary-70);
3068
+ font-size: 0.875rem;
3069
+ font-weight: 500;
3070
+ margin-top: 0.75rem;
3071
+ margin-bottom: 0.5rem;
3072
+ border: 1px solid var(--wa-color-primary-30);
3073
+ }
3074
+ `;
3075
+ __decorateClass$5([
3076
+ state()
3077
+ ], LyraExtensions.prototype, "selectedExtension", 2);
3078
+ __decorateClass$5([
3079
+ state()
3080
+ ], LyraExtensions.prototype, "filterText", 2);
3081
+ __decorateClass$5([
3082
+ state()
3083
+ ], LyraExtensions.prototype, "showRegisterDialog", 2);
3084
+ __decorateClass$5([
3085
+ state()
3086
+ ], LyraExtensions.prototype, "registerExtensionData", 2);
3087
+ LyraExtensions = __decorateClass$5([
3088
+ customElement("lyra-extensions")
3089
+ ], LyraExtensions);
3090
+ var __defProp$3 = Object.defineProperty;
3091
+ var __getOwnPropDesc$4 = Object.getOwnPropertyDescriptor;
3092
+ var __decorateClass$4 = (decorators, target, key, kind) => {
3093
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$4(target, key) : target;
3094
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
3095
+ if (decorator = decorators[i])
3096
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
3097
+ if (kind && result) __defProp$3(target, key, result);
3098
+ return result;
3099
+ };
3100
+ const t$1 = i18n("logterminal");
3101
+ let LyraLogTerminal = class extends LyraPart {
3102
+ constructor() {
3103
+ super(...arguments);
3104
+ this.messages = [];
3105
+ this.autoScroll = true;
3106
+ this.filter = "all";
3107
+ this.containerRef = createRef();
3108
+ }
3109
+ connectedCallback() {
3110
+ super.connectedCallback();
3111
+ this.loadSettings();
3112
+ registerLogHandler(this.log.bind(this));
3113
+ }
3114
+ disconnectedCallback() {
3115
+ super.disconnectedCallback();
3116
+ unregisterLogHandler();
3117
+ }
3118
+ log(source, message, level = "info") {
3119
+ const logMessage = {
3120
+ timestamp: /* @__PURE__ */ new Date(),
3121
+ level,
3122
+ source,
3123
+ message
3124
+ };
3125
+ this.messages = [...this.messages, logMessage];
3126
+ this.updateToolbar();
3127
+ if (this.autoScroll) {
3128
+ this.updateComplete.then(() => {
3129
+ const container = this.containerRef.value;
3130
+ if (container) {
3131
+ container.scrollTop = container.scrollHeight;
3132
+ }
3133
+ });
3134
+ }
3135
+ }
3136
+ clear() {
3137
+ this.messages = [];
3138
+ this.updateToolbar();
3139
+ }
3140
+ getFilteredMessages() {
3141
+ if (this.filter === "all") {
3142
+ return this.messages;
3143
+ }
3144
+ return this.messages.filter((m) => m.level === this.filter);
3145
+ }
3146
+ formatTimestamp(date) {
3147
+ return date.toLocaleTimeString("en-US", {
3148
+ hour12: false,
3149
+ hour: "2-digit",
3150
+ minute: "2-digit",
3151
+ second: "2-digit"
3152
+ });
3153
+ }
3154
+ getLevelIcon(level) {
3155
+ switch (level) {
3156
+ case "info":
3157
+ return "circle-info";
3158
+ case "warning":
3159
+ return "triangle-exclamation";
3160
+ case "error":
3161
+ return "circle-xmark";
3162
+ case "debug":
3163
+ return "bug";
3164
+ }
3165
+ }
3166
+ getLevelColor(level) {
3167
+ switch (level) {
3168
+ case "info":
3169
+ return "var(--wa-color-primary-text, #0066cc)";
3170
+ case "warning":
3171
+ return "var(--wa-color-warning-text, #ff9800)";
3172
+ case "error":
3173
+ return "var(--wa-color-danger-text, #dc3545)";
3174
+ case "debug":
3175
+ return "var(--wa-color-neutral-text-subtle, #6c757d)";
3176
+ }
3177
+ }
3178
+ renderToolbar() {
3179
+ const infoCount = this.messages.filter((m) => m.level === "info").length;
3180
+ const warningCount = this.messages.filter((m) => m.level === "warning").length;
3181
+ const errorCount = this.messages.filter((m) => m.level === "error").length;
3182
+ const debugCount = this.messages.filter((m) => m.level === "debug").length;
3183
+ return html`
3184
+ <lyra-command
3185
+ icon="list"
3186
+ title="${t$1("ALL_LOGS")}"
3187
+ appearance="${this.filter === "all" ? "filled" : "plain"}"
3188
+ variant="${this.filter === "all" ? "brand" : "neutral"}"
3189
+ .action=${() => {
3190
+ this.filter = "all";
3191
+ this.saveSettings();
3192
+ this.updateToolbar();
3193
+ }}>
3194
+ ${t$1("ALL")} (${this.messages.length})
3195
+ </lyra-command>
3196
+
3197
+ <lyra-command
3198
+ icon="circle-info"
3199
+ title="${t$1("INFO_LOGS")}"
3200
+ appearance="${this.filter === "info" ? "filled" : "plain"}"
3201
+ variant="${this.filter === "info" ? "brand" : "neutral"}"
3202
+ .action=${() => {
3203
+ this.filter = "info";
3204
+ this.saveSettings();
3205
+ this.updateToolbar();
3206
+ }}>
3207
+ ${t$1("INFO")}${infoCount > 0 ? ` (${infoCount})` : ""}
3208
+ </lyra-command>
3209
+
3210
+ <lyra-command
3211
+ icon="triangle-exclamation"
3212
+ title="${t$1("WARNING_LOGS")}"
3213
+ appearance="${this.filter === "warning" ? "filled" : "plain"}"
3214
+ variant="${this.filter === "warning" ? "brand" : "neutral"}"
3215
+ .action=${() => {
3216
+ this.filter = "warning";
3217
+ this.saveSettings();
3218
+ this.updateToolbar();
3219
+ }}>
3220
+ ${t$1("WARNINGS")}${warningCount > 0 ? ` (${warningCount})` : ""}
3221
+ </lyra-command>
3222
+
3223
+ <lyra-command
3224
+ icon="circle-xmark"
3225
+ title="${t$1("ERROR_LOGS")}"
3226
+ appearance="${this.filter === "error" ? "filled" : "plain"}"
3227
+ variant="${this.filter === "error" ? "brand" : "neutral"}"
3228
+ .action=${() => {
3229
+ this.filter = "error";
3230
+ this.saveSettings();
3231
+ this.updateToolbar();
3232
+ }}>
3233
+ ${t$1("ERRORS")}${errorCount > 0 ? ` (${errorCount})` : ""}
3234
+ </lyra-command>
3235
+
3236
+ <lyra-command
3237
+ icon="bug"
3238
+ title="${t$1("DEBUG_LOGS")}"
3239
+ appearance="${this.filter === "debug" ? "filled" : "plain"}"
3240
+ variant="${this.filter === "debug" ? "brand" : "neutral"}"
3241
+ .action=${() => {
3242
+ this.filter = "debug";
3243
+ this.saveSettings();
3244
+ this.updateToolbar();
3245
+ }}>
3246
+ ${t$1("DEBUG")}${debugCount > 0 ? ` (${debugCount})` : ""}
3247
+ </lyra-command>
3248
+
3249
+ <wa-divider orientation="vertical"></wa-divider>
3250
+
3251
+ <lyra-command
3252
+ icon="arrow-down"
3253
+ title="${this.autoScroll ? t$1("AUTO_SCROLL_ENABLED") : t$1("AUTO_SCROLL_DISABLED")}"
3254
+ appearance="${this.autoScroll ? "filled" : "plain"}"
3255
+ variant="${this.autoScroll ? "brand" : "neutral"}"
3256
+ .action=${() => {
3257
+ this.autoScroll = !this.autoScroll;
3258
+ this.saveSettings();
3259
+ this.updateToolbar();
3260
+ }}>
3261
+ ${this.autoScroll ? t$1("AUTO_SCROLL") : t$1("MANUAL")}
3262
+ </lyra-command>
3263
+
3264
+ <lyra-command
3265
+ icon="trash"
3266
+ title="${t$1("CLEAR_LOGS")}"
3267
+ .action=${() => this.clear()}>
3268
+ ${t$1("CLEAR")}
3269
+ </lyra-command>
3270
+ `;
3271
+ }
3272
+ render() {
3273
+ const filteredMessages = this.getFilteredMessages();
3274
+ return html`
3275
+ <div class="log-terminal">
3276
+ <div class="messages" ${ref(this.containerRef)}>
3277
+ ${filteredMessages.length === 0 ? html`<div class="empty-state">${t$1("NO_LOG_MESSAGES")}</div>` : filteredMessages.map((msg) => html`
3278
+ <div class="message" data-level="${msg.level}">
3279
+ <span class="timestamp">${this.formatTimestamp(msg.timestamp)}</span>
3280
+ <wa-icon
3281
+ name="${this.getLevelIcon(msg.level)}"
3282
+ label="${msg.level}"
3283
+ style="color: ${this.getLevelColor(msg.level)}">
3284
+ </wa-icon>
3285
+ <span class="source">[${msg.source}]</span>
3286
+ <span class="text">${msg.message}</span>
3287
+ </div>
3288
+ `)}
3289
+ </div>
3290
+ </div>
3291
+ `;
3292
+ }
3293
+ async loadSettings() {
3294
+ const persisted = await this.getDialogSetting();
3295
+ if (persisted) {
3296
+ if (typeof persisted.filter === "string" && (persisted.filter === "all" || ["info", "warning", "error", "debug"].includes(persisted.filter))) {
3297
+ this.filter = persisted.filter;
3298
+ }
3299
+ if (typeof persisted.autoScroll === "boolean") {
3300
+ this.autoScroll = persisted.autoScroll;
3301
+ }
3302
+ this.updateToolbar();
3303
+ }
3304
+ }
3305
+ async saveSettings() {
3306
+ await this.setDialogSetting({
3307
+ filter: this.filter,
3308
+ autoScroll: this.autoScroll
3309
+ });
3310
+ }
3311
+ };
3312
+ LyraLogTerminal.styles = css`
3313
+ :host {
3314
+ display: flex;
3315
+ flex-direction: column;
3316
+ height: 100%;
3317
+ width: 100%;
3318
+ }
3319
+
3320
+ .log-terminal {
3321
+ display: flex;
3322
+ flex-direction: column;
3323
+ height: 100%;
3324
+ width: 100%;
3325
+ }
3326
+
3327
+ .messages {
3328
+ flex: 1;
3329
+ overflow-y: auto;
3330
+ padding: 0.5rem;
3331
+ font-family: var(--wa-font-mono);
3332
+ font-size: 0.875rem;
3333
+ line-height: 1.5;
3334
+ }
3335
+
3336
+ .message {
3337
+ display: flex;
3338
+ gap: 0.5rem;
3339
+ padding: 0.25rem 0.5rem;
3340
+ align-items: baseline;
3341
+ border-radius: var(--wa-border-radius-small);
3342
+ }
3343
+
3344
+ .message:hover {
3345
+ background: var(--wa-color-neutral-background-hover);
3346
+ }
3347
+
3348
+ .timestamp {
3349
+ color: var(--wa-color-neutral-text-subtle);
3350
+ font-size: 0.75rem;
3351
+ white-space: nowrap;
3352
+ }
3353
+
3354
+ .source {
3355
+ color: var(--wa-color-primary-text);
3356
+ font-weight: 500;
3357
+ white-space: nowrap;
3358
+ }
3359
+
3360
+ .text {
3361
+ color: var(--wa-color-neutral-text);
3362
+ word-break: breaword;
3363
+ }
3364
+
3365
+ .message[data-level="error"] .text {
3366
+ color: var(--wa-color-danger-text);
3367
+ }
3368
+
3369
+ .message[data-level="warning"] .text {
3370
+ color: var(--wa-color-warning-text);
3371
+ }
3372
+
3373
+ .message[data-level="debug"] .text {
3374
+ color: var(--wa-color-neutral-text-subtle);
3375
+ }
3376
+
3377
+ .empty-state {
3378
+ display: flex;
3379
+ align-items: center;
3380
+ justify-content: center;
3381
+ height: 100%;
3382
+ color: var(--wa-color-neutral-text-subtle);
3383
+ font-style: italic;
3384
+ }
3385
+
3386
+ wa-icon {
3387
+ flex-shrink: 0;
3388
+ }
3389
+ `;
3390
+ __decorateClass$4([
3391
+ state()
3392
+ ], LyraLogTerminal.prototype, "messages", 2);
3393
+ __decorateClass$4([
3394
+ state()
3395
+ ], LyraLogTerminal.prototype, "autoScroll", 2);
3396
+ __decorateClass$4([
3397
+ state()
3398
+ ], LyraLogTerminal.prototype, "filter", 2);
3399
+ LyraLogTerminal = __decorateClass$4([
3400
+ customElement("lyra-log-terminal")
3401
+ ], LyraLogTerminal);
3402
+ var __defProp$2 = Object.defineProperty;
3403
+ var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor;
3404
+ var __decorateClass$3 = (decorators, target, key, kind) => {
3405
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$3(target, key) : target;
3406
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
3407
+ if (decorator = decorators[i])
3408
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
3409
+ if (kind && result) __defProp$2(target, key, result);
3410
+ return result;
3411
+ };
3412
+ const t = i18n("fastviews");
3413
+ let LyraFastViews = class extends LyraWidget {
3414
+ constructor() {
3415
+ super(...arguments);
3416
+ this.target = "";
3417
+ this.title = "";
3418
+ this.disabled = false;
3419
+ this.appearance = "plain";
3420
+ this.size = "small";
3421
+ this.placement = "bottom-start";
3422
+ this.tabContributions = [];
3423
+ this.drawerOpen = false;
3424
+ this.drawerSize = "50vw";
3425
+ this.drawerRef = createRef();
3426
+ this.tabsRef = createRef();
3427
+ this.resizeHandleRef = createRef();
3428
+ this.resizing = null;
3429
+ }
3430
+ getDrawerTabsId() {
3431
+ return `fastviews-drawer-tabs-${this.target}`;
3432
+ }
3433
+ handleTabClick(contribution) {
3434
+ if (this.disabled) return;
3435
+ if (this.containerId) {
3436
+ const tabContainer = document.querySelector(`lyra-tabs#${this.containerId}`);
3437
+ if (!tabContainer) {
3438
+ console.error(`fastviews: Tab container with id "${this.containerId}" not found`);
3439
+ return;
3440
+ }
3441
+ tabContainer.open(contribution);
3442
+ } else {
3443
+ this.drawerOpen = true;
3444
+ this.updateComplete.then(() => {
3445
+ const tabsContainer = this.tabsRef.value;
3446
+ if (tabsContainer) {
3447
+ tabsContainer.open(contribution);
3448
+ }
3449
+ });
3450
+ }
3451
+ }
3452
+ handleDrawerHide() {
3453
+ this.drawerOpen = false;
3454
+ }
3455
+ startResize(e) {
3456
+ e.preventDefault();
3457
+ e.stopPropagation();
3458
+ const drawer = this.drawerRef.value;
3459
+ if (!drawer) return;
3460
+ const getDrawerSize = () => {
3461
+ const drawerPanel = drawer.shadowRoot?.querySelector('[part="panel"]');
3462
+ if (drawerPanel && drawerPanel.offsetWidth > 0) {
3463
+ return drawerPanel.offsetWidth;
3464
+ }
3465
+ const computedStyle = window.getComputedStyle(drawer);
3466
+ const sizeValue = computedStyle.getPropertyValue("--size") || this.drawerSize;
3467
+ const match = sizeValue.match(/^(\d+(?:\.\d+)?)(px|vw|vh|%)$/);
3468
+ if (match) {
3469
+ const value = parseFloat(match[1]);
3470
+ const unit = match[2];
3471
+ if (unit === "px") return value;
3472
+ if (unit === "vw") return value / 100 * window.innerWidth;
3473
+ if (unit === "vh") return value / 100 * window.innerHeight;
3474
+ if (unit === "%") return value / 100 * window.innerWidth;
3475
+ }
3476
+ return 0;
3477
+ };
3478
+ let currentSize = getDrawerSize();
3479
+ if (currentSize === 0) {
3480
+ currentSize = window.innerWidth * 0.5;
3481
+ }
3482
+ const handleMouseMove = (moveEvent) => {
3483
+ if (!this.resizing) return;
3484
+ moveEvent.preventDefault();
3485
+ moveEvent.stopPropagation();
3486
+ if (this.resizing.rafId !== null) {
3487
+ cancelAnimationFrame(this.resizing.rafId);
3488
+ }
3489
+ this.resizing.rafId = requestAnimationFrame(() => {
3490
+ if (!this.resizing) return;
3491
+ const delta = this.resizing.startX - moveEvent.clientX;
3492
+ const newSize = Math.round(this.resizing.startSize + delta);
3493
+ const minSize = 200;
3494
+ const maxSize = Math.round(window.innerWidth * 0.9);
3495
+ if (newSize >= minSize && newSize <= maxSize) {
3496
+ this.drawerSize = `${newSize}px`;
3497
+ const drawer2 = this.drawerRef.value;
3498
+ if (drawer2) {
3499
+ drawer2.style.setProperty("--size", this.drawerSize);
3500
+ drawer2.style.setProperty("transition", "none");
3501
+ }
3502
+ }
3503
+ this.resizing.rafId = null;
3504
+ });
3505
+ };
3506
+ const handleMouseUp = () => {
3507
+ if (this.resizing) {
3508
+ if (this.resizing.rafId !== null) {
3509
+ cancelAnimationFrame(this.resizing.rafId);
3510
+ this.resizing.rafId = null;
3511
+ }
3512
+ document.removeEventListener("mousemove", this.resizing.handleMouseMove);
3513
+ document.removeEventListener("mouseup", this.resizing.handleMouseUp);
3514
+ document.body.style.cursor = "";
3515
+ document.body.style.userSelect = "";
3516
+ const drawer2 = this.drawerRef.value;
3517
+ if (drawer2) {
3518
+ drawer2.style.removeProperty("transition");
3519
+ }
3520
+ this.resizing = null;
3521
+ }
3522
+ };
3523
+ this.resizing = {
3524
+ startX: e.clientX,
3525
+ startSize: currentSize,
3526
+ handleMouseMove,
3527
+ handleMouseUp,
3528
+ rafId: null
3529
+ };
3530
+ document.addEventListener("mousemove", handleMouseMove, { passive: false });
3531
+ document.addEventListener("mouseup", handleMouseUp, { passive: false });
3532
+ document.body.style.cursor = "col-resize";
3533
+ document.body.style.userSelect = "none";
3534
+ }
3535
+ doBeforeUI() {
3536
+ if (this.target) {
3537
+ this.loadTabContributions();
3538
+ subscribe(TOPIC_CONTRIBUTEIONS_CHANGED, (event) => {
3539
+ if (this.target && event.target === this.target) {
3540
+ this.loadTabContributions();
3541
+ }
3542
+ });
3543
+ }
3544
+ }
3545
+ loadTabContributions() {
3546
+ if (!this.target) return;
3547
+ const allContributions = contributionRegistry.getContributions(this.target);
3548
+ this.tabContributions = allContributions.filter((c) => "name" in c);
3549
+ this.requestUpdate();
3550
+ }
3551
+ renderTabContribution(contribution) {
3552
+ return html`
3553
+ <wa-dropdown-item
3554
+ @click=${() => this.handleTabClick(contribution)}>
3555
+ <lyra-icon name="${contribution.icon || ""}" label="${contribution.label}" slot="icon"></lyra-icon>
3556
+ ${contribution.label}
3557
+ </wa-dropdown-item>
3558
+ `;
3559
+ }
3560
+ render() {
3561
+ if (!this.target) {
3562
+ return nothing;
3563
+ }
3564
+ if (this.tabContributions.length === 0) {
3565
+ return nothing;
3566
+ }
3567
+ return html`
3568
+ <wa-dropdown placement=${this.placement}>
3569
+ <wa-button
3570
+ slot="trigger"
3571
+ appearance=${this.appearance}
3572
+ size=${this.size}
3573
+ ?disabled=${this.disabled}
3574
+ with-caret
3575
+ title=${this.title}>
3576
+ <lyra-icon slot="start" name="${this.icon}" label="${this.title}"></lyra-icon>
3577
+ <slot></slot>
3578
+ </wa-button>
3579
+
3580
+ ${this.title ? html`
3581
+ <h6 style="padding: var(--wa-space-xs) var(--wa-space-s); margin: 0; color: var(--wa-color-neutral-50); font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;">
3582
+ ${this.title}
3583
+ </h6>
3584
+ ` : nothing}
3585
+
3586
+ ${this.tabContributions.map((c) => this.renderTabContribution(c))}
3587
+ </wa-dropdown>
3588
+
3589
+ ${!this.containerId ? html`
3590
+ <wa-drawer
3591
+ ${ref(this.drawerRef)}
3592
+ label="${this.title || t("FAST_VIEWS")}"
3593
+ placement="end"
3594
+ ?open=${this.drawerOpen}
3595
+ @wa-hide=${this.handleDrawerHide}
3596
+ style="--size: ${this.drawerSize};">
3597
+ <div
3598
+ ${ref(this.resizeHandleRef)}
3599
+ class="resize-handle"
3600
+ @mousedown=${this.startResize}>
3601
+ </div>
3602
+ <lyra-tabs
3603
+ ${ref(this.tabsRef)}
3604
+ id="${this.getDrawerTabsId()}"
3605
+ style="width: 100%; height: 100%; display: flex; flex-direction: column;">
3606
+ </lyra-tabs>
3607
+ </wa-drawer>
3608
+ ` : nothing}
3609
+ `;
3610
+ }
3611
+ };
3612
+ LyraFastViews.styles = css`
3613
+ :host {
3614
+ display: inline-block;
3615
+ }
3616
+
3617
+ wa-drawer {
3618
+ position: relative;
3619
+ }
3620
+
3621
+ wa-drawer::part(panel) {
3622
+ position: relative;
3623
+ }
3624
+
3625
+ .resize-handle {
3626
+ position: absolute;
3627
+ left: 0;
3628
+ top: 0;
3629
+ bottom: 0;
3630
+ width: 4px;
3631
+ cursor: col-resize;
3632
+ z-index: 1000;
3633
+ background: transparent;
3634
+ transition: background-color 0.2s;
3635
+ user-select: none;
3636
+ touch-action: none;
3637
+ }
3638
+
3639
+ .resize-handle:hover {
3640
+ background: var(--wa-color-brand-fill-loud);
3641
+ }
3642
+
3643
+ .resize-handle:active {
3644
+ background: var(--wa-color-brand-fill-loud);
3645
+ }
3646
+ `;
3647
+ __decorateClass$3([
3648
+ property()
3649
+ ], LyraFastViews.prototype, "target", 2);
3650
+ __decorateClass$3([
3651
+ property()
3652
+ ], LyraFastViews.prototype, "title", 2);
3653
+ __decorateClass$3([
3654
+ property()
3655
+ ], LyraFastViews.prototype, "icon", 2);
3656
+ __decorateClass$3([
3657
+ property({ type: Boolean })
3658
+ ], LyraFastViews.prototype, "disabled", 2);
3659
+ __decorateClass$3([
3660
+ property()
3661
+ ], LyraFastViews.prototype, "appearance", 2);
3662
+ __decorateClass$3([
3663
+ property()
3664
+ ], LyraFastViews.prototype, "size", 2);
3665
+ __decorateClass$3([
3666
+ property()
3667
+ ], LyraFastViews.prototype, "placement", 2);
3668
+ __decorateClass$3([
3669
+ property()
3670
+ ], LyraFastViews.prototype, "containerId", 2);
3671
+ __decorateClass$3([
3672
+ state()
3673
+ ], LyraFastViews.prototype, "tabContributions", 2);
3674
+ __decorateClass$3([
3675
+ state()
3676
+ ], LyraFastViews.prototype, "drawerOpen", 2);
3677
+ __decorateClass$3([
3678
+ state()
3679
+ ], LyraFastViews.prototype, "drawerSize", 2);
3680
+ LyraFastViews = __decorateClass$3([
3681
+ customElement("lyra-fastviews")
3682
+ ], LyraFastViews);
3683
+ var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
3684
+ var __decorateClass$2 = (decorators, target, key, kind) => {
3685
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
3686
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
3687
+ if (decorator = decorators[i])
3688
+ result = decorator(result) || result;
3689
+ return result;
3690
+ };
3691
+ function getDialogContainer() {
3692
+ let container = document.getElementById("global-dialog-container");
3693
+ if (!container) {
3694
+ container = document.createElement("div");
3695
+ container.id = "global-dialog-container";
3696
+ document.body.appendChild(container);
3697
+ }
3698
+ return container;
3699
+ }
3700
+ const getLanguageName = (code) => {
3701
+ try {
3702
+ return new Intl.DisplayNames([code], { type: "language" }).of(code) || code.toUpperCase();
3703
+ } catch {
3704
+ return code.toUpperCase();
3705
+ }
3706
+ };
3707
+ const getAvailableLanguages = () => {
3708
+ const contributions = languageContributionsSignal.get();
3709
+ const languages = /* @__PURE__ */ new Set();
3710
+ for (const contribution of contributions) {
3711
+ if (contribution.namespace) {
3712
+ const contributionObj = contribution;
3713
+ for (const key in contributionObj) {
3714
+ if (key !== "namespace" && key !== "label" && key !== "language" && key !== "translations" && typeof contributionObj[key] === "object") {
3715
+ languages.add(key);
3716
+ }
3717
+ }
3718
+ }
3719
+ }
3720
+ return Array.from(languages).sort();
3721
+ };
3722
+ const showLanguageSelectorDialog = async () => {
3723
+ const availableLanguages = getAvailableLanguages();
3724
+ const currentLanguage = currentLanguageSignal.get();
3725
+ return new Promise((resolve) => {
3726
+ const container = getDialogContainer();
3727
+ let isResolved = false;
3728
+ const handleClose = () => {
3729
+ const dialog = container.querySelector("wa-dialog");
3730
+ if (dialog && !isResolved) {
3731
+ dialog.open = false;
3732
+ }
3733
+ };
3734
+ const handleAfterHide = () => {
3735
+ if (isResolved) return;
3736
+ isResolved = true;
3737
+ render(html``, container);
3738
+ resolve();
3739
+ };
3740
+ const selectLanguage = async (language) => {
3741
+ await appSettings.set(SETTINGS_KEY_LANGUAGE, language);
3742
+ handleClose();
3743
+ };
3744
+ const template = html`
3745
+ <wa-dialog
3746
+ label="Select Language"
3747
+ open
3748
+ light-dismiss
3749
+ @wa-request-close=${handleClose}
3750
+ @wa-after-hide=${handleAfterHide}>
3751
+ <style>
3752
+ .language-list {
3753
+ display: flex;
3754
+ flex-direction: column;
3755
+ gap: 0.5rem;
3756
+ padding: 1rem;
3757
+ min-width: 300px;
3758
+ max-height: 400px;
3759
+ overflow-y: auto;
3760
+ }
3761
+
3762
+ .language-item {
3763
+ display: flex;
3764
+ align-items: center;
3765
+ padding: 0.75rem;
3766
+ border-radius: var(--wa-border-radius-small);
3767
+ cursor: pointer;
3768
+ transition: background-color 0.2s;
3769
+ }
3770
+
3771
+ .language-item:hover {
3772
+ background-color: var(--wa-color-neutral-fill-quiet);
3773
+ }
3774
+
3775
+ .language-item.active {
3776
+ background-color: var(--wa-color-brand-fill-quiet);
3777
+ font-weight: 600;
3778
+ }
3779
+
3780
+ .language-code {
3781
+ font-family: monospace;
3782
+ margin-right: 0.75rem;
3783
+ min-width: 3rem;
3784
+ color: var(--wa-color-neutral-600);
3785
+ }
3786
+
3787
+ .language-name {
3788
+ flex: 1;
3789
+ }
3790
+ </style>
3791
+
3792
+ <div class="language-list">
3793
+ ${availableLanguages.map((lang) => html`
3794
+ <div
3795
+ class="language-item ${lang === currentLanguage ? "active" : ""}"
3796
+ @click=${() => selectLanguage(lang)}>
3797
+ <span class="language-code">${lang.toUpperCase()}</span>
3798
+ <span class="language-name">${getLanguageName(lang)}</span>
3799
+ </div>
3800
+ `)}
3801
+ </div>
3802
+ </wa-dialog>
3803
+ `;
3804
+ render(template, container);
3805
+ });
3806
+ };
3807
+ let LyraLanguageSelector = class extends LyraElement {
3808
+ render() {
3809
+ const currentLanguage = currentLanguageSignal.get();
3810
+ const languageName = getLanguageName(currentLanguage);
3811
+ const buttonLabel = `${currentLanguage.toUpperCase()} ${languageName}`;
3812
+ return html`
3813
+ <wa-button
3814
+ appearance="plain"
3815
+ size="small"
3816
+ title="Current language: ${languageName}"
3817
+ @click=${() => showLanguageSelectorDialog()}>
3818
+ ${buttonLabel}
3819
+ </wa-button>
3820
+ `;
3821
+ }
3822
+ };
3823
+ LyraLanguageSelector.styles = css`
3824
+ :host {
3825
+ display: inline-block;
3826
+ }
3827
+ `;
3828
+ LyraLanguageSelector = __decorateClass$2([
3829
+ customElement("lyra-language-selector")
3830
+ ], LyraLanguageSelector);
3831
+ var __defProp$1 = Object.defineProperty;
3832
+ var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
3833
+ var __decorateClass$1 = (decorators, target, key, kind) => {
3834
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
3835
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
3836
+ if (decorator = decorators[i])
3837
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
3838
+ if (kind && result) __defProp$1(target, key, result);
3839
+ return result;
3840
+ };
3841
+ const APP_SWITCHER_DIALOG_ID = "app-switcher";
3842
+ contributionRegistry.registerContribution(DIALOG_CONTRIBUTION_TARGET, {
3843
+ id: APP_SWITCHER_DIALOG_ID,
3844
+ label: "Switch Application",
3845
+ buttons: [CLOSE_BUTTON],
3846
+ component: (state2) => {
3847
+ const apps = state2?.apps || [];
3848
+ const currentAppId = state2?.currentAppId;
3849
+ const selectApp = state2?.selectApp;
3850
+ return html`
3851
+ <wa-scroller orientation="vertical" style="min-width: 300px; max-height: 400px; padding: var(--wa-space-m);">
3852
+ <div style="display: flex; flex-direction: column; gap: var(--wa-space-s);">
3853
+ ${apps.map((app) => html`
3854
+ <wa-card
3855
+ style="cursor: pointer;"
3856
+ variant=${app.id === currentAppId ? "brand" : "neutral"}
3857
+ @click=${() => selectApp(app)}>
3858
+ <div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: var(--wa-space-xs);">
3859
+ <span style="font-weight: 600;">${app.name}</span>
3860
+ ${app.version ? html`<wa-badge variant="neutral">v${app.version}</wa-badge>` : ""}
3861
+ </div>
3862
+ ${app.description ? html`<p style="margin: 0; font-size: 0.875rem; line-height: 1.4;">${app.description}</p>` : ""}
3863
+ <div style="font-size: 0.75rem; color: var(--wa-color-neutral-foreground-quiet); font-family: monospace; margin-top: var(--wa-space-xs);">ID: ${app.id}</div>
3864
+ </wa-card>
3865
+ `)}
3866
+ </div>
3867
+ </wa-scroller>
3868
+ `;
3869
+ },
3870
+ onButton: async () => true
3871
+ });
3872
+ const showAppSwitcherDialog = async () => {
3873
+ const apps = appLoaderService.getRegisteredApps();
3874
+ const currentApp = appLoaderService.getCurrentApp();
3875
+ if (apps.length === 0) {
3876
+ return;
3877
+ }
3878
+ const state2 = {
3879
+ apps,
3880
+ currentAppId: currentApp?.id,
3881
+ selectApp: async (app) => {
3882
+ if (app.id === currentApp?.id) {
3883
+ state2.close?.();
3884
+ return;
3885
+ }
3886
+ try {
3887
+ await appLoaderService.setPreferredAppId(app.id);
3888
+ await appLoaderService.loadApp(app.id, document.body);
3889
+ } catch (error) {
3890
+ console.error("Failed to switch app:", error);
3891
+ } finally {
3892
+ state2.close?.();
3893
+ }
3894
+ },
3895
+ close: void 0
3896
+ };
3897
+ await dialogService.open(APP_SWITCHER_DIALOG_ID, state2);
3898
+ };
3899
+ let LyraAppSwitcher = class extends LyraElement {
3900
+ doBeforeUI() {
3901
+ this.currentApp = appLoaderService.getCurrentApp();
3902
+ const updateCurrentApp = () => {
3903
+ this.currentApp = appLoaderService.getCurrentApp();
3904
+ this.requestUpdate();
3905
+ };
3906
+ window.addEventListener("app-loaded", updateCurrentApp);
3907
+ return () => {
3908
+ window.removeEventListener("app-loaded", updateCurrentApp);
3909
+ };
3910
+ }
3911
+ render() {
3912
+ const apps = appLoaderService.getRegisteredApps();
3913
+ const appName = this.currentApp?.name || "No App";
3914
+ if (apps.length <= 1) {
3915
+ return html``;
3916
+ }
3917
+ return html`
3918
+ <wa-button
3919
+ appearance="plain"
3920
+ size="small"
3921
+ title="Current app: ${appName}. Click to switch applications."
3922
+ @click=${() => showAppSwitcherDialog()}>
3923
+ <wa-icon name="grip" style="margin-right: 0.5rem;"></wa-icon>
3924
+ ${appName}
3925
+ </wa-button>
3926
+ `;
3927
+ }
3928
+ };
3929
+ LyraAppSwitcher.styles = css`
3930
+ :host {
3931
+ display: inline-block;
3932
+ }
3933
+ `;
3934
+ __decorateClass$1([
3935
+ state()
3936
+ ], LyraAppSwitcher.prototype, "currentApp", 2);
3937
+ LyraAppSwitcher = __decorateClass$1([
3938
+ customElement("lyra-app-switcher")
3939
+ ], LyraAppSwitcher);
3940
+ contributionRegistry.registerContribution(SIDEBAR_MAIN, {
3941
+ name: "filebrowser",
3942
+ label: "Workspace",
3943
+ icon: "folder-open",
3944
+ component: (id) => html`<lyra-filebrowser id="${id}"></lyra-filebrowser>`
3945
+ });
3946
+ contributionRegistry.registerContribution("system.fastviews-bottomend", {
3947
+ name: "log-terminal",
3948
+ label: "Log Messages",
3949
+ icon: "list",
3950
+ component: (id) => html`<lyra-log-terminal id="${id}"></lyra-log-terminal>`
3951
+ });
3952
+ contributionRegistry.registerContribution(TOOLBAR_BOTTOM_END, {
3953
+ label: "Info",
3954
+ icon: "circle-info",
3955
+ command: "show_version_info",
3956
+ showLabel: true
3957
+ });
3958
+ contributionRegistry.registerContribution(TOOLBAR_BOTTOM_END, {
3959
+ label: `Fast Views`,
3960
+ html: `<lyra-fastviews target="system.fastviews-bottomend" icon="bolt" title="Fast Views"></lyra-fastviews>`
3961
+ });
3962
+ contributionRegistry.registerContribution(TOOLBAR_BOTTOM_END, {
3963
+ label: "Language",
3964
+ html: () => html`<lyra-language-selector></lyra-language-selector>`
3965
+ });
3966
+ contributionRegistry.registerContribution(TOOLBAR_MAIN_RIGHT, {
3967
+ label: "App Switcher",
3968
+ html: () => html`<lyra-app-switcher></lyra-app-switcher>`
3969
+ });
3970
+ const logger = createLogger("MarketplaceCatalogContributions");
3971
+ logger.debug("Marketplace catalog URLs are set by the app host via applyAppHostConfig()");
3972
+ async function getWorkspaceAndPath(params, requirePath = true) {
3973
+ const workspace = await workspaceService.getWorkspace();
3974
+ if (!workspace) {
3975
+ return null;
3976
+ }
3977
+ const path = params?.path;
3978
+ if (requirePath && !path) {
3979
+ return null;
3980
+ }
3981
+ return { workspace, path: path || "" };
3982
+ }
3983
+ function isEditorContentProvider(editor) {
3984
+ return editor && typeof editor.getContent === "function" && typeof editor.getSelection === "function" && typeof editor.getSnippet === "function" && typeof editor.getLanguage === "function" && typeof editor.getFilePath === "function";
3985
+ }
3986
+ function createNullEditorResponse(includeCursorLine = false) {
3987
+ const base = {
3988
+ filePath: null,
3989
+ language: null
3990
+ };
3991
+ if (includeCursorLine) {
3992
+ return { ...base, snippet: null, cursorLine: null };
3993
+ }
3994
+ return base;
3995
+ }
3996
+ async function getWorkspaceAndFile(params, requirePath = true) {
3997
+ const result = await getWorkspaceAndPath(params, requirePath);
3998
+ if (!result) {
3999
+ return null;
4000
+ }
4001
+ const { workspace, path } = result;
4002
+ if (!path) {
4003
+ return null;
4004
+ }
4005
+ try {
4006
+ const resource = await workspace.getResource(path);
4007
+ if (!resource || !(resource instanceof File)) {
4008
+ return null;
4009
+ }
4010
+ return { workspace, path, file: resource };
4011
+ } catch (err) {
4012
+ return null;
4013
+ }
4014
+ }
4015
+ registerAll({
4016
+ command: {
4017
+ "id": "disconnect_folder",
4018
+ "name": "Disconnect folder",
4019
+ "description": "Disconnects a folder from the workspace"
4020
+ },
4021
+ handler: {
4022
+ execute: async () => {
4023
+ const selection = activeSelectionSignal.get();
4024
+ if (!(selection instanceof Directory && selection.getParent() === void 0)) {
4025
+ toastError("Select a folder root to disconnect.");
4026
+ return;
4027
+ }
4028
+ try {
4029
+ await workspaceService.disconnectFolder(selection);
4030
+ } catch (err) {
4031
+ toastError(err.message);
4032
+ }
4033
+ }
4034
+ },
4035
+ contribution: {
4036
+ target: "contextmenu:filebrowser",
4037
+ label: "Disconnect folder",
4038
+ icon: "folder-minus",
4039
+ disabled: () => {
4040
+ const selection = activeSelectionSignal.get();
4041
+ return !(selection instanceof Directory && selection.getParent() === void 0);
4042
+ }
4043
+ }
4044
+ });
4045
+ registerAll({
4046
+ command: {
4047
+ "id": "load_workspace",
4048
+ "name": "Local Folder",
4049
+ "description": "Connect to a local folder using File System Access API",
4050
+ "parameters": []
4051
+ },
4052
+ handler: {
4053
+ execute: async (_context) => {
4054
+ await window.showDirectoryPicker({
4055
+ mode: "readwrite"
4056
+ }).then((dirHandle) => {
4057
+ return workspaceService.connectWorkspace(dirHandle);
4058
+ }).catch((err) => {
4059
+ toastError(err.message);
4060
+ });
4061
+ }
4062
+ },
4063
+ contribution: {
4064
+ target: "filebrowser.connections",
4065
+ label: "Local Folder",
4066
+ icon: "folder-open"
4067
+ }
4068
+ });
4069
+ registerAll({
4070
+ command: {
4071
+ "id": "connect_opfs",
4072
+ "name": "OPFS",
4073
+ "description": "Connect to Origin Private File System (browser storage)",
4074
+ "parameters": []
4075
+ },
4076
+ handler: {
4077
+ execute: async () => {
4078
+ try {
4079
+ await workspaceService.connectFolder({ opfs: true });
4080
+ } catch (err) {
4081
+ toastError(err.message);
4082
+ }
4083
+ }
4084
+ },
4085
+ contribution: {
4086
+ target: "filebrowser.connections",
4087
+ label: "OPFS (Browser Storage)",
4088
+ icon: "database"
4089
+ }
4090
+ });
4091
+ registerAll({
4092
+ command: {
4093
+ "id": "connect_indexeddb",
4094
+ "name": "IndexedDB",
4095
+ "description": "Connect to IndexedDB-backed workspace (browser storage)",
4096
+ "parameters": [
4097
+ {
4098
+ "name": "name",
4099
+ "description": "Optional display name for this IndexedDB workspace root",
4100
+ "required": false
4101
+ }
4102
+ ]
4103
+ },
4104
+ handler: {
4105
+ execute: async (context) => {
4106
+ const nameParam = context.params?.["name"];
4107
+ try {
4108
+ await workspaceService.connectFolder({ indexeddb: true, name: nameParam });
4109
+ } catch (err) {
4110
+ toastError(err.message);
4111
+ }
4112
+ }
4113
+ },
4114
+ contribution: {
4115
+ target: "filebrowser.connections",
4116
+ label: "IndexedDB (Browser Storage)",
4117
+ icon: "database"
4118
+ }
4119
+ });
4120
+ registerAll({
4121
+ command: {
4122
+ "id": "refresh_resource",
4123
+ "name": "Refresh resource",
4124
+ "description": "Refreshes the connected folder of the selected resource, or the whole workspace if nothing is selected",
4125
+ "parameters": []
4126
+ },
4127
+ handler: {
4128
+ execute: async () => {
4129
+ const selection = activeSelectionSignal.get();
4130
+ if (selection) {
4131
+ selection.getWorkspace().touch();
4132
+ return;
4133
+ }
4134
+ const workspace = await workspaceService.getWorkspace();
4135
+ if (!workspace) {
4136
+ toastError("No workspace selected.");
4137
+ return;
4138
+ }
4139
+ workspace.touch();
4140
+ }
4141
+ }
4142
+ });
4143
+ registerAll({
4144
+ command: {
4145
+ "id": "open_editor",
4146
+ "name": "Open editor",
4147
+ "description": "Opens a file in an editor",
4148
+ "parameters": [
4149
+ {
4150
+ "name": "path",
4151
+ "description": "The path of the file to open within the workspace; if omitted, the active selection is opened",
4152
+ "required": false
4153
+ },
4154
+ {
4155
+ "name": "editorId",
4156
+ "description": "Open with this editor id; if omitted, use default editor",
4157
+ "required": false
4158
+ }
4159
+ ]
4160
+ },
4161
+ handler: {
4162
+ execute: async (context) => {
4163
+ const path = context.params?.["path"];
4164
+ const editorId = context.params?.["editorId"];
4165
+ let file = null;
4166
+ if (path) {
4167
+ const result = await getWorkspaceAndFile({ path });
4168
+ file = result?.file ?? null;
4169
+ } else {
4170
+ const selection = activeSelectionSignal.get();
4171
+ file = selection instanceof File ? selection : null;
4172
+ }
4173
+ if (!file) return;
4174
+ await editorRegistry.loadEditor(file, editorId);
4175
+ }
4176
+ }
4177
+ });
4178
+ registerAll({
4179
+ command: {
4180
+ "id": "get_active_editor_content",
4181
+ "name": "Get active editor content",
4182
+ "description": "Gets the complete contents of the currently active editor. Returns null if no editor is active or if the editor is not a code editor.",
4183
+ "parameters": [],
4184
+ "output": [
4185
+ {
4186
+ "name": "content",
4187
+ "description": "the complete contents of the active editor, or null if no editor is active"
4188
+ },
4189
+ {
4190
+ "name": "filePath",
4191
+ "description": "the workspace path of the file in the active editor, or null if no editor is active"
4192
+ },
4193
+ {
4194
+ "name": "language",
4195
+ "description": "the programming language of the active editor, or null if no editor is active"
4196
+ }
4197
+ ]
4198
+ },
4199
+ handler: {
4200
+ execute: async (context) => {
4201
+ const activeEditor = context.activeEditor;
4202
+ if (!isEditorContentProvider(activeEditor)) {
4203
+ return { ...createNullEditorResponse(), content: null };
4204
+ }
4205
+ try {
4206
+ return {
4207
+ content: activeEditor.getContent(),
4208
+ filePath: activeEditor.getFilePath(),
4209
+ language: activeEditor.getLanguage()
4210
+ };
4211
+ } catch (err) {
4212
+ return { ...createNullEditorResponse(), content: null };
4213
+ }
4214
+ }
4215
+ }
4216
+ });
4217
+ registerAll({
4218
+ command: {
4219
+ "id": "get_active_editor_selection",
4220
+ "name": "Get active editor selection",
4221
+ "description": "Gets the currently selected text in the active editor. Returns null if no editor is active, no selection exists, or if the editor is not a code editor.",
4222
+ "parameters": [],
4223
+ "output": [
4224
+ {
4225
+ "name": "selection",
4226
+ "description": "the selected text in the active editor, or null if no selection exists or no editor is active"
4227
+ },
4228
+ {
4229
+ "name": "filePath",
4230
+ "description": "the workspace path of the file in the active editor, or null if no editor is active"
4231
+ },
4232
+ {
4233
+ "name": "language",
4234
+ "description": "the programming language of the active editor, or null if no editor is active"
4235
+ }
4236
+ ]
4237
+ },
4238
+ handler: {
4239
+ execute: async (context) => {
4240
+ const activeEditor = context.activeEditor;
4241
+ if (!isEditorContentProvider(activeEditor)) {
4242
+ return { ...createNullEditorResponse(), selection: null };
4243
+ }
4244
+ try {
4245
+ return {
4246
+ selection: activeEditor.getSelection(),
4247
+ filePath: activeEditor.getFilePath(),
4248
+ language: activeEditor.getLanguage()
4249
+ };
4250
+ } catch (err) {
4251
+ return { ...createNullEditorResponse(), selection: null };
4252
+ }
4253
+ }
4254
+ }
4255
+ });
4256
+ registerAll({
4257
+ command: {
4258
+ "id": "get_active_editor_snippet",
4259
+ "name": "Get active editor snippet around cursor",
4260
+ "description": "Gets a code snippet from the active editor with n lines before and n lines after the cursor position. Useful for getting context around the cursor without loading the entire file.",
4261
+ "parameters": [
4262
+ {
4263
+ "name": "lines",
4264
+ "description": "number of lines to include before and after the cursor position (default: 5)",
4265
+ "type": "number",
4266
+ "required": false
4267
+ }
4268
+ ],
4269
+ "output": [
4270
+ {
4271
+ "name": "snippet",
4272
+ "description": "the code snippet with n lines before and after the cursor, or null if no editor is active"
4273
+ },
4274
+ {
4275
+ "name": "filePath",
4276
+ "description": "the workspace path of the file in the active editor, or null if no editor is active"
4277
+ },
4278
+ {
4279
+ "name": "language",
4280
+ "description": "the programming language of the active editor, or null if no editor is active"
4281
+ },
4282
+ {
4283
+ "name": "cursorLine",
4284
+ "description": "the line number where the cursor is positioned (1-based), or null if no editor is active"
4285
+ }
4286
+ ]
4287
+ },
4288
+ handler: {
4289
+ execute: async (context) => {
4290
+ const activeEditor = context.activeEditor;
4291
+ if (!isEditorContentProvider(activeEditor)) {
4292
+ return createNullEditorResponse(true);
4293
+ }
4294
+ try {
4295
+ const numLines = context.params?.lines ? parseInt(context.params.lines, 10) : 5;
4296
+ if (isNaN(numLines) || numLines < 0) {
4297
+ return createNullEditorResponse(true);
4298
+ }
4299
+ const snippetResult = activeEditor.getSnippet(numLines);
4300
+ if (!snippetResult) {
4301
+ return createNullEditorResponse(true);
4302
+ }
4303
+ return {
4304
+ snippet: snippetResult.snippet,
4305
+ filePath: activeEditor.getFilePath(),
4306
+ language: activeEditor.getLanguage(),
4307
+ cursorLine: snippetResult.cursorLine
4308
+ };
4309
+ } catch (err) {
4310
+ return createNullEditorResponse(true);
4311
+ }
4312
+ }
4313
+ }
4314
+ });
4315
+ registerAll({
4316
+ command: {
4317
+ "id": "show_version_info",
4318
+ "name": "Show Version Info",
4319
+ "description": "Shows application version information",
4320
+ "parameters": []
4321
+ },
4322
+ handler: {
4323
+ execute: async (_context) => {
4324
+ const app = appLoaderService.getCurrentApp();
4325
+ if (!app) {
4326
+ toastError("No app loaded");
4327
+ return;
4328
+ }
4329
+ const hasPackages = packageInfoService.hasPackages();
4330
+ const packagesTree = packageInfoService.renderTree();
4331
+ let dialogContainer = null;
4332
+ const getDialogContainer2 = () => {
4333
+ if (!dialogContainer) {
4334
+ dialogContainer = document.getElementById("global-dialog-container") || document.createElement("div");
4335
+ if (!dialogContainer.id) {
4336
+ dialogContainer.id = "global-dialog-container";
4337
+ document.body.appendChild(dialogContainer);
4338
+ }
4339
+ }
4340
+ return dialogContainer;
4341
+ };
4342
+ const cleanup = () => {
4343
+ if (dialogContainer) {
4344
+ render(html``, dialogContainer);
4345
+ }
4346
+ };
4347
+ const renderReleaseContent = (releaseContent) => {
4348
+ const htmlContent = marked.parse(releaseContent, { async: false });
4349
+ return html`${unsafeHTML(htmlContent)}`;
4350
+ };
4351
+ let releases = [];
4352
+ if (app.releaseHistory) {
4353
+ if (typeof app.releaseHistory === "function") {
4354
+ try {
4355
+ releases = await app.releaseHistory();
4356
+ } catch (error) {
4357
+ console.error("Failed to load release history from app:", error);
4358
+ releases = [];
4359
+ }
4360
+ } else {
4361
+ releases = app.releaseHistory;
4362
+ }
4363
+ }
4364
+ app.version === "0.0.0";
4365
+ const currentIndex = releases.length > 0 ? releases.findIndex((r) => r.tag_name === app.version) : -1;
4366
+ const startIndex = currentIndex >= 0 ? currentIndex : 0;
4367
+ let currentReleaseIndex = startIndex;
4368
+ const buildReleaseContent = (index) => {
4369
+ if (releases.length === 0) {
4370
+ return "";
4371
+ }
4372
+ const release = releases[index];
4373
+ const isCurrentVersion = release.tag_name === app.version;
4374
+ let message = `**Version:** ${release.tag_name}`;
4375
+ if (isCurrentVersion) {
4376
+ message += ` (Current)`;
4377
+ }
4378
+ message += `
4379
+
4380
+ `;
4381
+ const publishDate = new Date(release.published_at).toLocaleDateString();
4382
+ message += `**Released:** ${publishDate}
4383
+
4384
+ `;
4385
+ if (!isCurrentVersion) {
4386
+ const cleanCurrent = app.version.replace(/^v/, "");
4387
+ const cleanRelease = release.tag_name.replace(/^v/, "");
4388
+ const currentParts = cleanCurrent.split(".").map(Number);
4389
+ const releaseParts = cleanRelease.split(".").map(Number);
4390
+ let isNewer = false;
4391
+ for (let i = 0; i < Math.max(currentParts.length, releaseParts.length); i++) {
4392
+ const current = currentParts[i] || 0;
4393
+ const releasePart = releaseParts[i] || 0;
4394
+ if (releasePart > current) {
4395
+ isNewer = true;
4396
+ break;
4397
+ }
4398
+ if (releasePart < current) {
4399
+ break;
4400
+ }
4401
+ }
4402
+ if (isNewer) {
4403
+ message += `⚠️ **Update available - reload page to update**
4404
+
4405
+ `;
4406
+ }
4407
+ }
4408
+ if (release.body) {
4409
+ message += `---
4410
+
4411
+ ${release.body}`;
4412
+ }
4413
+ return message;
4414
+ };
4415
+ const handleClose = () => {
4416
+ cleanup();
4417
+ };
4418
+ const handleAfterHide = () => {
4419
+ cleanup();
4420
+ };
4421
+ const updateDialog = (index) => {
4422
+ const releaseContent = buildReleaseContent(index);
4423
+ const hasNavigation = releases.length > 0;
4424
+ const template = html`
4425
+ <wa-dialog
4426
+ label="About ${app.name} - ${app.version}"
4427
+ open
4428
+ light-dismiss
4429
+ style="--width: 600px;"
4430
+ @wa-request-close=${handleClose}
4431
+ @wa-after-hide=${handleAfterHide}
4432
+ >
4433
+ <style>
4434
+ .dialog-content {
4435
+ height: 600px;
4436
+ }
4437
+
4438
+ wa-tree-item > span small {
4439
+ color: var(--wa-color-neutral-60);
4440
+ font-size: 0.875em;
4441
+ margin-left: 0.5rem;
4442
+ }
4443
+ </style>
4444
+ <small>${app.description}</small>
4445
+ <div class="dialog-content">
4446
+ <wa-tab-group>
4447
+ ${releases.length > 0 ? html`
4448
+ <wa-tab slot="nav" panel="release">Release History</wa-tab>
4449
+ <wa-tab-panel name="release">
4450
+ ${renderReleaseContent(releaseContent)}
4451
+ </wa-tab-panel>
4452
+ ` : ""}
4453
+
4454
+ ${hasPackages ? html`
4455
+ <wa-tab slot="nav" panel="packages">NPM Packages</wa-tab>
4456
+ <wa-tab-panel name="packages">
4457
+ ${packagesTree}
4458
+ </wa-tab-panel>
4459
+ ` : ""}
4460
+ </wa-tab-group>
4461
+ </div>
4462
+ <div slot="footer">
4463
+ ${hasNavigation ? html`
4464
+ <wa-button
4465
+ variant="default"
4466
+ ?disabled=${index === releases.length - 1}
4467
+ @click=${() => {
4468
+ if (index < releases.length - 1) {
4469
+ currentReleaseIndex = index + 1;
4470
+ updateDialog(currentReleaseIndex);
4471
+ }
4472
+ }}
4473
+ >
4474
+ ← Previous
4475
+ </wa-button>
4476
+ <wa-button
4477
+ variant="default"
4478
+ ?disabled=${index === 0}
4479
+ @click=${() => {
4480
+ if (index > 0) {
4481
+ currentReleaseIndex = index - 1;
4482
+ updateDialog(currentReleaseIndex);
4483
+ }
4484
+ }}
4485
+ >
4486
+ Next →
4487
+ </wa-button>
4488
+ ` : ""}
4489
+ <wa-button variant="primary" data-dialog="close">Close</wa-button>
4490
+ </div>
4491
+ </wa-dialog>
4492
+ `;
4493
+ render(template, getDialogContainer2());
4494
+ };
4495
+ updateDialog(startIndex);
4496
+ return new Promise((resolve) => {
4497
+ const checkClosed = () => {
4498
+ if (!dialogContainer?.querySelector("wa-dialog[open]")) {
4499
+ resolve();
4500
+ } else {
4501
+ setTimeout(checkClosed, 100);
4502
+ }
4503
+ };
4504
+ checkClosed();
4505
+ });
4506
+ }
4507
+ }
4508
+ });
4509
+ registerAll({
4510
+ command: {
4511
+ "id": "save",
4512
+ "name": "Save editor",
4513
+ "description": "Saves the active/focused editor",
4514
+ "keyBinding": "CTRL+S",
4515
+ "parameters": []
4516
+ },
4517
+ handler: {
4518
+ execute: async (_context) => {
4519
+ const part = activeEditorSignal.get() || activePartSignal.get();
4520
+ if (part && part.isDirty()) {
4521
+ part.save();
4522
+ }
4523
+ }
4524
+ },
4525
+ contribution: {
4526
+ target: "toolbar:.system.editors",
4527
+ icon: "floppy-disk",
4528
+ label: "Save active editor",
4529
+ slot: "start",
4530
+ disabled: () => {
4531
+ const part = activePartSignal.get();
4532
+ return !part || !part.isDirty();
4533
+ }
4534
+ }
4535
+ });
4536
+ const THEME_SETTINGS_KEY = "theme";
4537
+ async function applyTheme(themeClass) {
4538
+ const root = document.documentElement;
4539
+ root.classList.remove("wa-dark", "wa-light");
4540
+ root.classList.add(themeClass);
4541
+ }
4542
+ async function loadTheme() {
4543
+ const theme = await appSettings.get(THEME_SETTINGS_KEY);
4544
+ await applyTheme(theme || "wa-dark");
4545
+ }
4546
+ async function saveTheme(themeClass) {
4547
+ await appSettings.set(THEME_SETTINGS_KEY, themeClass);
4548
+ }
4549
+ registerAll({
4550
+ command: {
4551
+ "id": "switch_theme",
4552
+ "name": "Switch theme",
4553
+ "description": "Switches between dark and light theme",
4554
+ "parameters": []
4555
+ },
4556
+ handler: {
4557
+ execute: async (_context) => {
4558
+ const isDark = document.documentElement.classList.contains("wa-dark");
4559
+ const newTheme = isDark ? "wa-light" : "wa-dark";
4560
+ await applyTheme(newTheme);
4561
+ await saveTheme(newTheme);
4562
+ }
4563
+ },
4564
+ contribution: {
4565
+ target: TOOLBAR_MAIN_RIGHT,
4566
+ icon: "circle-half-stroke",
4567
+ label: "Theme Switcher"
4568
+ }
4569
+ });
4570
+ loadTheme().catch((err) => {
4571
+ console.error("Failed to load theme preference:", err);
4572
+ });
4573
+ registerAll({
4574
+ command: {
4575
+ "id": "fullscreen",
4576
+ "name": "Toggle fullscreen",
4577
+ "description": "Toggles fullscreen mode",
4578
+ "parameters": []
4579
+ },
4580
+ handler: {
4581
+ execute: async (_context) => {
4582
+ if (document.fullscreenElement === document.body) {
4583
+ await document.exitFullscreen();
4584
+ } else {
4585
+ await document.body.requestFullscreen();
4586
+ }
4587
+ }
4588
+ },
4589
+ contribution: {
4590
+ target: TOOLBAR_MAIN_RIGHT,
4591
+ icon: "expand",
4592
+ label: "Fullscreen"
4593
+ }
4594
+ });
4595
+ registerAll({
4596
+ command: {
4597
+ "id": "open_extensions",
4598
+ "name": "Open Extensions",
4599
+ "description": "Opens the extensions registry",
4600
+ "parameters": []
4601
+ },
4602
+ handler: {
4603
+ execute: (_context) => {
4604
+ const editorInput = {
4605
+ title: "Extensions",
4606
+ data: {},
4607
+ key: "system.extensions",
4608
+ icon: "puzzle-piece",
4609
+ state: {},
4610
+ noOverflow: true,
4611
+ widgetFactory: () => html`<lyra-extensions></lyra-extensions>`
4612
+ };
4613
+ editorRegistry.loadEditor(editorInput, "extensions-editor").then();
4614
+ }
4615
+ },
4616
+ contribution: {
4617
+ target: TOOLBAR_MAIN_RIGHT,
4618
+ icon: "puzzle-piece",
4619
+ label: "Open Extensions"
4620
+ }
4621
+ });
4622
+ registerAll({
4623
+ command: {
4624
+ "id": "list_extensions",
4625
+ "name": "List extensions",
4626
+ "description": "Lists all available extensions with their status (enabled/disabled)",
4627
+ "parameters": [],
4628
+ "output": [
4629
+ {
4630
+ "name": "extensions",
4631
+ "description": "array of extension objects with id, name, description, experimental flag, and enabled status"
4632
+ }
4633
+ ]
4634
+ },
4635
+ handler: {
4636
+ execute: async (_context) => {
4637
+ const extensions = extensionRegistry.getExtensions().map((e) => {
4638
+ return {
4639
+ id: e.id,
4640
+ name: e.name,
4641
+ description: e.description,
4642
+ experimental: e.experimental,
4643
+ enabled: extensionRegistry.isEnabled(e.id)
4644
+ };
4645
+ });
4646
+ return extensions;
4647
+ }
4648
+ }
4649
+ });
4650
+ registerAll({
4651
+ command: {
4652
+ "id": "toast_message",
4653
+ "name": "Toast message to user",
4654
+ "description": "Shows a toast message",
4655
+ "parameters": [
4656
+ {
4657
+ "name": "message",
4658
+ "description": "the message to toast",
4659
+ "required": true
4660
+ },
4661
+ {
4662
+ "name": "type",
4663
+ "description": "the toast type: info (default), or error",
4664
+ "required": false
4665
+ }
4666
+ ]
4667
+ },
4668
+ handler: {
4669
+ execute: ({ params: { message, type } }) => {
4670
+ if (!message) {
4671
+ return;
4672
+ }
4673
+ if (type === "error") {
4674
+ toastError(message);
4675
+ } else {
4676
+ toastInfo(message);
4677
+ }
4678
+ }
4679
+ }
4680
+ });
4681
+ registerAll({
4682
+ command: {
4683
+ id: "open_view_as_editor",
4684
+ name: "Open view as editor",
4685
+ description: "Opens a dashboard view in the editor area",
4686
+ parameters: [
4687
+ { name: "name", description: "View contribution name", required: true },
4688
+ { name: "sourceContributionSlot", description: "source contribution slot (default: SYSTEM_VIEWS)", required: false }
4689
+ ]
4690
+ },
4691
+ handler: {
4692
+ execute: async ({ params }) => {
4693
+ const name = params?.name;
4694
+ if (!name) return;
4695
+ const slot = params?.sourceContributionSlot ?? SYSTEM_VIEWS;
4696
+ const contributions = contributionRegistry.getContributions(slot);
4697
+ const contribution = contributions.find((c) => c.name === name);
4698
+ if (!contribution?.component) return;
4699
+ await editorRegistry.openTab(contribution);
4700
+ }
4701
+ }
4702
+ });
4703
+ rootContext.put("constants", constants);
4704
+ uiContext.put("html", html);
4705
+ uiContext.put("render", render);
4706
+ uiContext.put("toastInfo", toastInfo);
4707
+ uiContext.put("toastError", toastError);
4708
+ uiContext.put("toastWarning", toastWarning);
4709
+ let applied = false;
4710
+ function applyAppHostConfig(config) {
4711
+ if (applied) return;
4712
+ applied = true;
4713
+ if (config.packageInfo) {
4714
+ packageInfoService.addPackage(config.packageInfo);
4715
+ }
4716
+ if (config.marketplaceCatalogUrls?.length) {
4717
+ config.marketplaceCatalogUrls.forEach((url) => {
4718
+ marketplaceRegistry.addCatalogUrl(url).catch(() => {
4719
+ });
4720
+ });
4721
+ }
4722
+ }
4723
+ let frameworkConfig = {};
4724
+ function configureFramework(config) {
4725
+ frameworkConfig = { ...frameworkConfig, ...config };
4726
+ }
4727
+ function getFrameworkConfig() {
4728
+ return { ...frameworkConfig };
4729
+ }
4730
+ var __defProp = Object.defineProperty;
4731
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4732
+ var __decorateClass = (decorators, target, key, kind) => {
4733
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
4734
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
4735
+ if (decorator = decorators[i])
4736
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
4737
+ if (kind && result) __defProp(target, key, result);
4738
+ return result;
4739
+ };
4740
+ let LyraStandardLayout = class extends LyraContainer {
4741
+ constructor() {
4742
+ super(...arguments);
4743
+ this.showBottomSidebar = false;
4744
+ this.showBottomPanel = false;
4745
+ this.showLeftSidebar = true;
4746
+ this.showAuxSidebar = true;
4747
+ }
4748
+ createRenderRoot() {
4749
+ return this;
4750
+ }
4751
+ getGridSizes() {
4752
+ if (this.showLeftSidebar && this.showAuxSidebar) {
4753
+ return "15%, 65%, 20%";
4754
+ }
4755
+ if (this.showLeftSidebar) {
4756
+ return "15%, 85%";
4757
+ }
4758
+ if (this.showAuxSidebar) {
4759
+ return "80%, 20%";
4760
+ }
4761
+ return "100%";
4762
+ }
4763
+ render() {
4764
+ return html`
4765
+ <style>
4766
+ *, *::before, *::after {
4767
+ box-sizing: border-box;
4768
+ }
4769
+
4770
+ html {
4771
+ height: 100%;
4772
+ margin: 0;
4773
+ padding: 0;
4774
+ overflow: hidden;
4775
+ }
4776
+
4777
+ body {
4778
+ height: 100%;
4779
+ width: 100%;
4780
+ margin: 0;
4781
+ padding: 0;
4782
+ overflow: hidden;
4783
+ display: flex;
4784
+ flex-direction: column;
4785
+ }
4786
+
4787
+ lyra-standard-layout {
4788
+ display: flex;
4789
+ flex-direction: column;
4790
+ height: 100vh;
4791
+ width: 100%;
4792
+ }
4793
+
4794
+ lyra-standard-layout .toolbar-top {
4795
+ width: 100%;
4796
+ display: grid;
4797
+ grid-template-columns: 1fr 2fr 1fr;
4798
+ align-items: center;
4799
+ border-bottom: solid var(--wa-border-width-s) var(--wa-color-neutral-border-loud);
4800
+ flex-shrink: 0;
4801
+ }
4802
+
4803
+ lyra-standard-layout .toolbar-bottom {
4804
+ width: 100%;
4805
+ border-top: solid var(--wa-border-width-s) var(--wa-color-neutral-border-loud);
4806
+ display: grid;
4807
+ grid-template-columns: 1fr 2fr auto;
4808
+ align-items: center;
4809
+ flex-shrink: 0;
4810
+ min-height: 32px;
4811
+ padding: 0 var(--wa-space-s);
4812
+ box-sizing: border-box;
4813
+ }
4814
+
4815
+ lyra-standard-layout .main-layout {
4816
+ flex: 1;
4817
+ min-height: 0;
4818
+ }
4819
+
4820
+ lyra-standard-layout .toolbar-end {
4821
+ justify-self: end;
4822
+ }
4823
+ </style>
4824
+
4825
+ <div class="toolbar-top">
4826
+ <lyra-toolbar id=${TOOLBAR_MAIN}></lyra-toolbar>
4827
+ <lyra-toolbar id=${TOOLBAR_MAIN_CENTER}></lyra-toolbar>
4828
+ <lyra-toolbar class="toolbar-end" id=${TOOLBAR_MAIN_RIGHT}></lyra-toolbar>
4829
+ </div>
4830
+
4831
+ <lyra-resizable-grid
4832
+ class="main-layout"
4833
+ id="main-layout"
4834
+ orientation="horizontal"
4835
+ sizes=${this.getGridSizes()}>
4836
+
4837
+ ${this.showLeftSidebar ? html`
4838
+ ${this.showBottomSidebar ? html`
4839
+ <lyra-resizable-grid
4840
+ id="left-sidebar-split"
4841
+ orientation="vertical"
4842
+ sizes="50%, 50%">
4843
+ <lyra-tabs id="${SIDEBAR_MAIN}"></lyra-tabs>
4844
+ <lyra-tabs id="${SIDEBAR_MAIN_BOTTOM}"></lyra-tabs>
4845
+ </lyra-resizable-grid>
4846
+ ` : html`<lyra-tabs id="${SIDEBAR_MAIN}"></lyra-tabs>`}
4847
+ ` : nothing}
4848
+
4849
+ ${this.showBottomPanel ? html`
4850
+ <lyra-resizable-grid
4851
+ id="editor-area-split"
4852
+ orientation="vertical"
4853
+ sizes="70%, 30%">
4854
+ <lyra-tabs id="${EDITOR_AREA_MAIN}"></lyra-tabs>
4855
+ <lyra-tabs id="${PANEL_BOTTOM}"></lyra-tabs>
4856
+ </lyra-resizable-grid>
4857
+ ` : html`<lyra-tabs id="${EDITOR_AREA_MAIN}"></lyra-tabs>`}
4858
+
4859
+ ${this.showAuxSidebar ? html`<lyra-tabs id="${SIDEBAR_AUXILIARY}"></lyra-tabs>` : nothing}
4860
+ </lyra-resizable-grid>
4861
+
4862
+ <div class="toolbar-bottom">
4863
+ <lyra-toolbar id=${TOOLBAR_BOTTOM}></lyra-toolbar>
4864
+ <lyra-toolbar id=${TOOLBAR_BOTTOM_CENTER}></lyra-toolbar>
4865
+ <lyra-toolbar class="toolbar-end" id=${TOOLBAR_BOTTOM_END}></lyra-toolbar>
4866
+ </div>
4867
+ `;
4868
+ }
4869
+ };
4870
+ __decorateClass([
4871
+ property({ type: Boolean, attribute: "show-bottom-sidebar" })
4872
+ ], LyraStandardLayout.prototype, "showBottomSidebar", 2);
4873
+ __decorateClass([
4874
+ property({ type: Boolean, attribute: "show-bottom-panel" })
4875
+ ], LyraStandardLayout.prototype, "showBottomPanel", 2);
4876
+ __decorateClass([
4877
+ property({ type: Boolean, attribute: "show-left-sidebar" })
4878
+ ], LyraStandardLayout.prototype, "showLeftSidebar", 2);
4879
+ __decorateClass([
4880
+ property({ type: Boolean, attribute: "show-aux-sidebar" })
4881
+ ], LyraStandardLayout.prototype, "showAuxSidebar", 2);
4882
+ LyraStandardLayout = __decorateClass([
4883
+ customElement("lyra-standard-layout")
4884
+ ], LyraStandardLayout);
4885
+ export {
4886
+ CompositeDirectory as C,
4887
+ Directory as D,
4888
+ File as F,
4889
+ LyraStandardLayout as L,
4890
+ SYSTEM_LANGUAGE_BUNDLES as S,
4891
+ TOPIC_WORKSPACE_CHANGED as T,
4892
+ WorkspaceService as W,
4893
+ FileContentType as a,
4894
+ FileSysDirHandleResource as b,
4895
+ StringFile as c,
4896
+ TOPIC_WORKSPACE_CONNECTED as d,
4897
+ applyAppHostConfig as e,
4898
+ configureFramework as f,
4899
+ editorRegistry as g,
4900
+ getFrameworkConfig as h,
4901
+ i18n as i,
4902
+ i18nLazy as j,
4903
+ packageInfoService as p,
4904
+ treeNodeComparator as t,
4905
+ workspaceService as w
4906
+ };
4907
+ //# sourceMappingURL=standard-layout-BSGa06lP.js.map