@kispace-io/core 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (272) 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 +80 -0
  8. package/dist/api/index.js.map +1 -0
  9. package/dist/api/services.d.ts +27 -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/files.d.ts +2 -0
  14. package/dist/commands/files.d.ts.map +1 -0
  15. package/dist/commands/global.d.ts +1 -0
  16. package/dist/commands/global.d.ts.map +1 -0
  17. package/dist/commands/index.d.ts +1 -0
  18. package/dist/commands/index.d.ts.map +1 -0
  19. package/dist/commands/version-info.d.ts +2 -0
  20. package/dist/commands/version-info.d.ts.map +1 -0
  21. package/dist/components/index.d.ts +1 -0
  22. package/dist/components/index.d.ts.map +1 -0
  23. package/dist/components/k-app-selector.d.ts +17 -0
  24. package/dist/components/k-app-selector.d.ts.map +1 -0
  25. package/dist/components/k-app-switcher.d.ts +13 -0
  26. package/dist/components/k-app-switcher.d.ts.map +1 -0
  27. package/dist/components/k-command.d.ts +31 -0
  28. package/dist/components/k-command.d.ts.map +1 -0
  29. package/dist/components/k-extensions.d.ts +32 -0
  30. package/dist/components/k-extensions.d.ts.map +1 -0
  31. package/dist/components/k-fastviews.d.ts +34 -0
  32. package/dist/components/k-fastviews.d.ts.map +1 -0
  33. package/dist/components/k-filebrowser.d.ts +40 -0
  34. package/dist/components/k-filebrowser.d.ts.map +1 -0
  35. package/dist/components/k-language-selector.d.ts +12 -0
  36. package/dist/components/k-language-selector.d.ts.map +1 -0
  37. package/dist/components/k-log-terminal.d.ts +36 -0
  38. package/dist/components/k-log-terminal.d.ts.map +1 -0
  39. package/dist/components/k-part-name.d.ts +12 -0
  40. package/dist/components/k-part-name.d.ts.map +1 -0
  41. package/dist/components/k-tasks.d.ts +13 -0
  42. package/dist/components/k-tasks.d.ts.map +1 -0
  43. package/dist/components/k-workspace-name.d.ts +14 -0
  44. package/dist/components/k-workspace-name.d.ts.map +1 -0
  45. package/dist/contributions/default-ui-contributions.d.ts +2 -0
  46. package/dist/contributions/default-ui-contributions.d.ts.map +1 -0
  47. package/dist/contributions/index.d.ts +1 -0
  48. package/dist/contributions/index.d.ts.map +1 -0
  49. package/dist/contributions/marketplace-catalog-contributions.d.ts +2 -0
  50. package/dist/contributions/marketplace-catalog-contributions.d.ts.map +1 -0
  51. package/dist/core/app-host-config.d.ts +7 -0
  52. package/dist/core/app-host-config.d.ts.map +1 -0
  53. package/dist/core/apploader.d.ts +214 -0
  54. package/dist/core/apploader.d.ts.map +1 -0
  55. package/dist/core/appstate.d.ts +12 -0
  56. package/dist/core/appstate.d.ts.map +1 -0
  57. package/dist/core/commandregistry.d.ts +79 -0
  58. package/dist/core/commandregistry.d.ts.map +1 -0
  59. package/dist/core/config.d.ts +15 -0
  60. package/dist/core/config.d.ts.map +1 -0
  61. package/dist/core/constants.d.ts +21 -0
  62. package/dist/core/constants.d.ts.map +1 -0
  63. package/dist/core/contributionregistry.d.ts +49 -0
  64. package/dist/core/contributionregistry.d.ts.map +1 -0
  65. package/dist/core/di.d.ts +18 -0
  66. package/dist/core/di.d.ts.map +1 -0
  67. package/dist/core/dialogservice.d.ts +33 -0
  68. package/dist/core/dialogservice.d.ts.map +1 -0
  69. package/dist/core/editorregistry.d.ts +73 -0
  70. package/dist/core/editorregistry.d.ts.map +1 -0
  71. package/dist/core/esmsh-service.d.ts +40 -0
  72. package/dist/core/esmsh-service.d.ts.map +1 -0
  73. package/dist/core/events.d.ts +7 -0
  74. package/dist/core/events.d.ts.map +1 -0
  75. package/dist/core/events.js +63 -0
  76. package/dist/core/events.js.map +1 -0
  77. package/dist/core/extensionregistry.d.ts +98 -0
  78. package/dist/core/extensionregistry.d.ts.map +1 -0
  79. package/dist/core/filesys.d.ts +139 -0
  80. package/dist/core/filesys.d.ts.map +1 -0
  81. package/dist/core/i18n.d.ts +50 -0
  82. package/dist/core/i18n.d.ts.map +1 -0
  83. package/dist/core/index.d.ts +1 -0
  84. package/dist/core/index.d.ts.map +1 -0
  85. package/dist/core/k-utils.d.ts +2 -0
  86. package/dist/core/k-utils.d.ts.map +1 -0
  87. package/dist/core/keybindings.d.ts +67 -0
  88. package/dist/core/keybindings.d.ts.map +1 -0
  89. package/dist/core/logger.d.ts +44 -0
  90. package/dist/core/logger.d.ts.map +1 -0
  91. package/dist/core/marketplaceregistry.d.ts +25 -0
  92. package/dist/core/marketplaceregistry.d.ts.map +1 -0
  93. package/dist/core/packageinfoservice.d.ts +16 -0
  94. package/dist/core/packageinfoservice.d.ts.map +1 -0
  95. package/dist/core/persistenceservice.d.ts +6 -0
  96. package/dist/core/persistenceservice.d.ts.map +1 -0
  97. package/dist/core/settingsservice.d.ts +19 -0
  98. package/dist/core/settingsservice.d.ts.map +1 -0
  99. package/dist/core/signals.d.ts +3 -0
  100. package/dist/core/signals.d.ts.map +1 -0
  101. package/dist/core/taskservice.d.ts +20 -0
  102. package/dist/core/taskservice.d.ts.map +1 -0
  103. package/dist/core/toast.d.ts +4 -0
  104. package/dist/core/toast.d.ts.map +1 -0
  105. package/dist/core/tree-utils.d.ts +16 -0
  106. package/dist/core/tree-utils.d.ts.map +1 -0
  107. package/dist/dialogs/confirm-dialog.d.ts +14 -0
  108. package/dist/dialogs/confirm-dialog.d.ts.map +1 -0
  109. package/dist/dialogs/index.d.ts +5 -0
  110. package/dist/dialogs/index.d.ts.map +1 -0
  111. package/dist/dialogs/info-dialog.d.ts +13 -0
  112. package/dist/dialogs/info-dialog.d.ts.map +1 -0
  113. package/dist/dialogs/navigable-info-dialog.d.ts +33 -0
  114. package/dist/dialogs/navigable-info-dialog.d.ts.map +1 -0
  115. package/dist/dialogs/prompt-dialog.d.ts +21 -0
  116. package/dist/dialogs/prompt-dialog.d.ts.map +1 -0
  117. package/dist/externals/lit.d.ts +20 -0
  118. package/dist/externals/lit.d.ts.map +1 -0
  119. package/dist/externals/lit.js +15 -0
  120. package/dist/externals/lit.js.map +1 -0
  121. package/dist/externals/third-party.d.ts +7 -0
  122. package/dist/externals/third-party.d.ts.map +1 -0
  123. package/dist/externals/third-party.js +2 -0
  124. package/dist/externals/third-party.js.map +1 -0
  125. package/dist/externals/webawesome.d.ts +1 -0
  126. package/dist/externals/webawesome.d.ts.map +1 -0
  127. package/dist/externals/webawesome.js +52 -0
  128. package/dist/externals/webawesome.js.map +1 -0
  129. package/dist/i18n/extensions.json.d.ts +42 -0
  130. package/dist/i18n/fastviews.json.d.ts +13 -0
  131. package/dist/i18n/filebrowser.json.d.ts +35 -0
  132. package/dist/i18n/index.d.ts +2 -0
  133. package/dist/i18n/index.d.ts.map +1 -0
  134. package/dist/i18n/logterminal.json.d.ts +45 -0
  135. package/dist/i18n/partname.json.d.ts +15 -0
  136. package/dist/i18n/tasks.json.d.ts +15 -0
  137. package/dist/i18n/workspace.json.d.ts +15 -0
  138. package/dist/index.d.ts +2 -0
  139. package/dist/index.d.ts.map +1 -0
  140. package/dist/index.js +80 -0
  141. package/dist/index.js.map +1 -0
  142. package/dist/k-icon-BZC7dQV0.js +492 -0
  143. package/dist/k-icon-BZC7dQV0.js.map +1 -0
  144. package/dist/k-nocontent-Bh_yToGh.js +48 -0
  145. package/dist/k-nocontent-Bh_yToGh.js.map +1 -0
  146. package/dist/k-resizable-grid-Ch3iWZaL.js +3157 -0
  147. package/dist/k-resizable-grid-Ch3iWZaL.js.map +1 -0
  148. package/dist/k-standard-layout-CQ1VZoxa.js +5011 -0
  149. package/dist/k-standard-layout-CQ1VZoxa.js.map +1 -0
  150. package/dist/layouts/k-standard-layout.d.ts +16 -0
  151. package/dist/layouts/k-standard-layout.d.ts.map +1 -0
  152. package/dist/parts/index.d.ts +1 -0
  153. package/dist/parts/index.d.ts.map +1 -0
  154. package/dist/parts/index.js +53 -0
  155. package/dist/parts/index.js.map +1 -0
  156. package/dist/parts/k-app.d.ts +11 -0
  157. package/dist/parts/k-app.d.ts.map +1 -0
  158. package/dist/parts/k-container.d.ts +4 -0
  159. package/dist/parts/k-container.d.ts.map +1 -0
  160. package/dist/parts/k-contextmenu.d.ts +38 -0
  161. package/dist/parts/k-contextmenu.d.ts.map +1 -0
  162. package/dist/parts/k-dialog-content.d.ts +9 -0
  163. package/dist/parts/k-dialog-content.d.ts.map +1 -0
  164. package/dist/parts/k-element.d.ts +36 -0
  165. package/dist/parts/k-element.d.ts.map +1 -0
  166. package/dist/parts/k-part.d.ts +96 -0
  167. package/dist/parts/k-part.d.ts.map +1 -0
  168. package/dist/parts/k-resizable-grid.d.ts +31 -0
  169. package/dist/parts/k-resizable-grid.d.ts.map +1 -0
  170. package/dist/parts/k-tabs.d.ts +74 -0
  171. package/dist/parts/k-tabs.d.ts.map +1 -0
  172. package/dist/parts/k-toolbar.d.ts +21 -0
  173. package/dist/parts/k-toolbar.d.ts.map +1 -0
  174. package/dist/widgets/index.d.ts +1 -0
  175. package/dist/widgets/index.d.ts.map +1 -0
  176. package/dist/widgets/index.js +3 -0
  177. package/dist/widgets/index.js.map +1 -0
  178. package/dist/widgets/k-icon.d.ts +10 -0
  179. package/dist/widgets/k-icon.d.ts.map +1 -0
  180. package/dist/widgets/k-nocontent.d.ts +13 -0
  181. package/dist/widgets/k-nocontent.d.ts.map +1 -0
  182. package/dist/widgets/k-widget.d.ts +25 -0
  183. package/dist/widgets/k-widget.d.ts.map +1 -0
  184. package/package.json +81 -0
  185. package/src/api/base-classes.ts +10 -0
  186. package/src/api/constants.ts +3 -0
  187. package/src/api/index.ts +31 -0
  188. package/src/api/services.ts +52 -0
  189. package/src/api/types.ts +46 -0
  190. package/src/commands/files.ts +829 -0
  191. package/src/commands/global.ts +225 -0
  192. package/src/commands/index.ts +4 -0
  193. package/src/commands/version-info.ts +214 -0
  194. package/src/components/index.ts +10 -0
  195. package/src/components/k-app-selector.ts +233 -0
  196. package/src/components/k-app-switcher.ts +126 -0
  197. package/src/components/k-command.ts +236 -0
  198. package/src/components/k-extensions.ts +615 -0
  199. package/src/components/k-fastviews.ts +314 -0
  200. package/src/components/k-filebrowser.ts +442 -0
  201. package/src/components/k-language-selector.ts +166 -0
  202. package/src/components/k-log-terminal.ts +337 -0
  203. package/src/components/k-part-name.ts +54 -0
  204. package/src/components/k-tasks.ts +267 -0
  205. package/src/components/k-workspace-name.ts +56 -0
  206. package/src/contributions/default-ui-contributions.ts +51 -0
  207. package/src/contributions/index.ts +3 -0
  208. package/src/contributions/marketplace-catalog-contributions.ts +6 -0
  209. package/src/core/app-host-config.ts +23 -0
  210. package/src/core/apploader.ts +630 -0
  211. package/src/core/appstate.ts +15 -0
  212. package/src/core/commandregistry.ts +210 -0
  213. package/src/core/config.ts +29 -0
  214. package/src/core/constants.ts +27 -0
  215. package/src/core/contributionregistry.ts +77 -0
  216. package/src/core/di.ts +54 -0
  217. package/src/core/dialogservice.ts +266 -0
  218. package/src/core/editorregistry.ts +303 -0
  219. package/src/core/esmsh-service.ts +404 -0
  220. package/src/core/events.ts +68 -0
  221. package/src/core/extensionregistry.ts +399 -0
  222. package/src/core/filesys.ts +618 -0
  223. package/src/core/i18n.ts +221 -0
  224. package/src/core/index.ts +51 -0
  225. package/src/core/k-utils.ts +11 -0
  226. package/src/core/keybindings.ts +274 -0
  227. package/src/core/logger.ts +187 -0
  228. package/src/core/marketplaceregistry.ts +197 -0
  229. package/src/core/packageinfoservice.ts +56 -0
  230. package/src/core/persistenceservice.ts +15 -0
  231. package/src/core/settingsservice.ts +70 -0
  232. package/src/core/signals.ts +18 -0
  233. package/src/core/taskservice.ts +72 -0
  234. package/src/core/toast.ts +11 -0
  235. package/src/core/tree-utils.ts +24 -0
  236. package/src/dialogs/confirm-dialog.ts +72 -0
  237. package/src/dialogs/index.ts +4 -0
  238. package/src/dialogs/info-dialog.ts +67 -0
  239. package/src/dialogs/navigable-info-dialog.ts +256 -0
  240. package/src/dialogs/prompt-dialog.ts +123 -0
  241. package/src/externals/lit.ts +26 -0
  242. package/src/externals/third-party.ts +9 -0
  243. package/src/externals/webawesome.ts +54 -0
  244. package/src/i18n/extensions.json +39 -0
  245. package/src/i18n/fastviews.json +10 -0
  246. package/src/i18n/filebrowser.json +33 -0
  247. package/src/i18n/index.ts +25 -0
  248. package/src/i18n/logterminal.json +42 -0
  249. package/src/i18n/partname.json +12 -0
  250. package/src/i18n/tasks.json +12 -0
  251. package/src/i18n/workspace.json +12 -0
  252. package/src/icons/icons.txt +3 -0
  253. package/src/icons/js.svg +6 -0
  254. package/src/icons/jupyter.svg +18 -0
  255. package/src/icons/python.svg +15 -0
  256. package/src/index.ts +3 -0
  257. package/src/layouts/k-standard-layout.ts +174 -0
  258. package/src/parts/index.ts +6 -0
  259. package/src/parts/k-app.ts +29 -0
  260. package/src/parts/k-container.ts +4 -0
  261. package/src/parts/k-contextmenu.ts +245 -0
  262. package/src/parts/k-dialog-content.ts +31 -0
  263. package/src/parts/k-element.ts +100 -0
  264. package/src/parts/k-part.ts +158 -0
  265. package/src/parts/k-resizable-grid.ts +366 -0
  266. package/src/parts/k-tabs.ts +574 -0
  267. package/src/parts/k-toolbar.ts +158 -0
  268. package/src/vite-env.d.ts +2 -0
  269. package/src/widgets/index.ts +2 -0
  270. package/src/widgets/k-icon.ts +39 -0
  271. package/src/widgets/k-nocontent.ts +40 -0
  272. package/src/widgets/k-widget.ts +90 -0
@@ -0,0 +1,630 @@
1
+ /**
2
+ * App Loader Service
3
+ *
4
+ * Provides a clean separation between the framework and applications built on it.
5
+ * This allows the framework (k-* components and core services) to be published
6
+ * as a reusable npm package, while applications like geospace can be loaded
7
+ * dynamically.
8
+ *
9
+ * Architecture:
10
+ * - Framework: Core services, k-* components (publishable as npm package)
11
+ * - Application: App-specific code, gs-* components, contributions
12
+ * - App Loader: Bridge between framework and application
13
+ */
14
+
15
+ import {render, TemplateResult, html} from "lit";
16
+ import {rootContext} from "./di";
17
+ import {createLogger} from "./logger";
18
+ import {extensionRegistry, Extension} from "./extensionregistry";
19
+ import {contributionRegistry, Contribution} from "./contributionregistry";
20
+ import {appSettings} from "./settingsservice";
21
+
22
+
23
+ const logger = createLogger('AppLoader');
24
+
25
+ /**
26
+ * Extracts error message from an error object.
27
+ */
28
+ function getErrorMessage(error: unknown): string {
29
+ return error instanceof Error ? error.message : String(error);
30
+ }
31
+
32
+ /**
33
+ * Extracts the last path segment from a URL string.
34
+ */
35
+ function extractLastPathSegment(urlString: string): string | undefined {
36
+ try {
37
+ const url = new URL(urlString);
38
+ const pathSegments = url.pathname.split('/').filter(Boolean);
39
+ return pathSegments.length > 0 ? pathSegments[pathSegments.length - 1] : undefined;
40
+ } catch {
41
+ const pathSegments = urlString.split('/').filter(Boolean);
42
+ return pathSegments.length > 0 ? pathSegments[pathSegments.length - 1] : undefined;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Extracts the first path segment from the current page URL.
48
+ * This allows loading apps by path, e.g., /geospace loads the geospace app.
49
+ */
50
+ function extractAppIdFromPath(): string | undefined {
51
+ const pathname = window.location.pathname;
52
+ const pathSegments = pathname.split('/').filter(Boolean);
53
+
54
+ if (pathSegments.length === 0) {
55
+ return undefined;
56
+ }
57
+
58
+ const firstSegment = pathSegments[0];
59
+
60
+ if (!firstSegment || firstSegment === 'index.html' || firstSegment.endsWith('.html')) {
61
+ return undefined;
62
+ }
63
+
64
+ return firstSegment;
65
+ }
66
+
67
+ /**
68
+ * Application contributions interface.
69
+ * Declaratively defines all contributions for an application.
70
+ */
71
+ export interface AppContributions {
72
+ /** UI contributions (tabs, toolbars, commands, etc.) */
73
+ ui?: Contribution[];
74
+
75
+ /** App-specific extensions to register */
76
+ extensions?: Extension[];
77
+ }
78
+
79
+ /**
80
+ * Represents a single release entry in the release history.
81
+ * Compatible with GitHub release format but with optional html_url.
82
+ */
83
+ export interface ReleaseEntry {
84
+ /** Release tag name (e.g., "v1.0.0") */
85
+ tag_name: string;
86
+
87
+ /** Human-readable release name */
88
+ name: string;
89
+
90
+ /** Release notes/description (markdown supported) */
91
+ body: string;
92
+
93
+ /** ISO 8601 timestamp when the release was published */
94
+ published_at: string;
95
+
96
+ /** Optional URL to the release page */
97
+ html_url?: string;
98
+
99
+ /** Whether this is a pre-release */
100
+ prerelease?: boolean;
101
+
102
+ /** Whether this release is a draft */
103
+ draft?: boolean;
104
+ }
105
+
106
+ /**
107
+ * Release history can be provided as:
108
+ * - A static array of releases
109
+ * - A function that returns releases asynchronously
110
+ * This allows apps to either provide pre-loaded releases or fetch them dynamically.
111
+ */
112
+ export type ReleaseHistory = ReleaseEntry[];
113
+
114
+ /** Descriptor for rendering a single custom element as the app root (no lit required). */
115
+ export interface RenderDescriptor {
116
+ tag: string;
117
+ attributes?: Record<string, string>;
118
+ }
119
+
120
+ /**
121
+ * Application definition interface.
122
+ * Applications implement this interface to integrate with the framework.
123
+ */
124
+ export interface AppDefinition {
125
+ /** Unique application identifier */
126
+ id: string;
127
+
128
+ /** Human-readable application name */
129
+ name: string;
130
+
131
+ /** Application version */
132
+ version: string;
133
+
134
+ /** Optional application description */
135
+ description?: string;
136
+
137
+ /**
138
+ * Custom application metadata (optional).
139
+ * Apps can define any custom metadata here for their own use.
140
+ * The framework may read certain metadata keys (e.g., `metadata.github` for release checking).
141
+ */
142
+ metadata?: Record<string, any>;
143
+
144
+ /**
145
+ * Core framework extensions required by the application.
146
+ * The app loader will enable these extensions when loading the app
147
+ * and disable them when unloading.
148
+ */
149
+ extensions?: string[];
150
+
151
+ /**
152
+ * Application contributions (tabs, toolbars, commands, app extensions).
153
+ * The app loader will register these contributions automatically.
154
+ */
155
+ contributions?: AppContributions;
156
+
157
+ /**
158
+ * Application initialization function.
159
+ * Called after extensions are enabled and contributions are registered.
160
+ * Use this for custom initialization logic that can't be expressed declaratively.
161
+ */
162
+ initialize?: () => void | Promise<void>;
163
+
164
+ /**
165
+ * Optional release history for the application.
166
+ * Can be a static array of releases or a callback function that returns releases (synchronously or asynchronously).
167
+ * If not provided, the version-info command will attempt to fetch releases from GitHub
168
+ * (if metadata.github is configured).
169
+ */
170
+ releaseHistory?: ReleaseHistory | (() => ReleaseHistory | Promise<ReleaseHistory>);
171
+
172
+ /**
173
+ * Root component to render. Can be:
174
+ * - A tag name string (e.g. "k-standard-layout") for a single custom element with no attributes.
175
+ * - A descriptor { tag, attributes? } for a single custom element with optional attributes.
176
+ * - A function returning a Lit TemplateResult for custom templates (requires lit in the app).
177
+ * If not provided, defaults to k-standard-layout.
178
+ */
179
+ render?: string | RenderDescriptor | (() => TemplateResult);
180
+
181
+ /**
182
+ * Optional cleanup function.
183
+ * Called when the app is being unloaded, before extensions are disabled.
184
+ */
185
+ dispose?: () => void | Promise<void>;
186
+ }
187
+
188
+ /**
189
+ * Options for registering an application with the apploader.
190
+ */
191
+ export interface RegisterAppOptions {
192
+ /**
193
+ * Default app ID to load if no app URL parameter is provided.
194
+ * If not specified, the first registered app will be loaded.
195
+ */
196
+ defaultAppId?: string;
197
+
198
+ /**
199
+ * Whether to automatically start the apploader after registration.
200
+ * If true, the apploader will start immediately after the app is registered.
201
+ * Defaults to false.
202
+ */
203
+ autoStart?: boolean;
204
+
205
+ /**
206
+ * DOM element to render the app into.
207
+ * Defaults to document.body.
208
+ */
209
+ container?: HTMLElement;
210
+ }
211
+
212
+ /**
213
+ * App Loader Service
214
+ *
215
+ * Manages application lifecycle:
216
+ * 1. Register app definitions
217
+ * 2. Initialize apps
218
+ * 3. Render apps to DOM
219
+ * 4. Manage app lifecycle
220
+ */
221
+ class AppLoaderService {
222
+ private apps: Map<string, AppDefinition> = new Map();
223
+ private currentApp?: AppDefinition;
224
+ private started: boolean = false;
225
+ private defaultAppId?: string;
226
+ private container: HTMLElement = document.body;
227
+ private systemRequiredExtensions: Set<string> = new Set();
228
+ private static readonly PREFERRED_APP_KEY = 'preferredAppId';
229
+
230
+ /**
231
+ * Register an application with the framework.
232
+ * Optionally starts the apploader automatically after registration.
233
+ *
234
+ * @param app - Application definition
235
+ * @param options - Optional configuration for registration and auto-starting
236
+ */
237
+ registerApp(app: AppDefinition, options?: RegisterAppOptions): void {
238
+ if (this.apps.has(app.id)) {
239
+ logger.warn(`App '${app.id}' is already registered. Overwriting.`);
240
+ }
241
+
242
+ this.apps.set(app.id, app);
243
+ logger.info(`Registered app: ${app.name} (${app.id}) v${app.version}`);
244
+
245
+ if (options?.defaultAppId) {
246
+ this.defaultAppId = options.defaultAppId;
247
+ }
248
+
249
+ if (options?.container) {
250
+ this.container = options.container;
251
+ }
252
+
253
+ if (options?.autoStart && !this.started) {
254
+ this.start();
255
+ }
256
+ }
257
+
258
+ registerSystemRequiredExtension(extensionId: string) {
259
+ this.systemRequiredExtensions.add(extensionId);
260
+ }
261
+
262
+
263
+ /**
264
+ * Load an application definition from a URL.
265
+ * The module at the URL must export an AppDefinition as the default export.
266
+ *
267
+ * @param url - URL to the app definition module
268
+ * @returns Promise that resolves to the loaded AppDefinition
269
+ */
270
+ async loadAppFromUrl(url: string): Promise<AppDefinition> {
271
+ logger.info(`Loading app from URL: ${url}...`);
272
+
273
+ try {
274
+ const module = await import(/* @vite-ignore */ url);
275
+
276
+ if (!module.default) {
277
+ throw new Error(`Module at ${url} does not have a default export`);
278
+ }
279
+
280
+ const app = module.default as AppDefinition;
281
+
282
+ if (!app.id || !app.name || !app.version) {
283
+ throw new Error(`Module at ${url} does not export a valid AppDefinition`);
284
+ }
285
+
286
+ logger.info(`Successfully loaded app definition from URL: ${app.name} (${app.id})`);
287
+ return app;
288
+ } catch (error) {
289
+ logger.error(`Failed to load app from URL ${url}: ${getErrorMessage(error)}`);
290
+ throw error;
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Start the application loader.
296
+ * Checks URL parameters for app=URL, loads that extension or app if found.
297
+ * URL parameter has higher precedence than defaultAppId.
298
+ * Then loads the default app or first registered app.
299
+ * This method is idempotent - calling it multiple times only starts once.
300
+ */
301
+ async start(): Promise<void> {
302
+ if (this.started) {
303
+ logger.debug('AppLoader already started');
304
+ return;
305
+ }
306
+
307
+ this.started = true;
308
+ logger.info('Starting AppLoader...');
309
+
310
+ const urlParams = new URLSearchParams(window.location.search);
311
+ const appUrl = urlParams.get('app');
312
+ const appIdFromUrl = urlParams.get('appId');
313
+ const appIdFromPath = extractAppIdFromPath();
314
+ const appsBeforeExtension = this.apps.size;
315
+
316
+ let appIdFromAppUrl: string | undefined;
317
+ if (appUrl) {
318
+ appIdFromAppUrl = extractLastPathSegment(appUrl);
319
+ if (appIdFromAppUrl) {
320
+ logger.info(`Extracted app ID from URL path: ${appIdFromAppUrl}`);
321
+ }
322
+ }
323
+
324
+ if (appIdFromPath) {
325
+ logger.info(`Extracted app ID from current page path: ${appIdFromPath}`);
326
+ }
327
+
328
+ if (appUrl) {
329
+ try {
330
+ logger.info(`URL parameter 'app' found: ${appUrl}, attempting to load extension or app`);
331
+
332
+ try {
333
+ await extensionRegistry.loadExtensionFromUrl(appUrl);
334
+ logger.info(`Successfully loaded extension from URL: ${appUrl}`);
335
+ } catch (extensionError) {
336
+ logger.info(`Failed to load as extension, trying as app definition: ${getErrorMessage(extensionError)}`);
337
+
338
+ try {
339
+ const app = await this.loadAppFromUrl(appUrl);
340
+ this.registerApp(app);
341
+ await this.loadApp(app.id, this.container);
342
+ logger.info(`Successfully loaded app from URL: ${appUrl}`);
343
+ return;
344
+ } catch (appError) {
345
+ logger.error(`Failed to load from URL as both extension and app: ${getErrorMessage(appError)}`);
346
+ throw appError;
347
+ }
348
+ }
349
+ } catch (error) {
350
+ logger.error(`Failed to load from URL parameter, falling back to default app: ${getErrorMessage(error)}`);
351
+ }
352
+ }
353
+
354
+ const appToLoad = await this.selectAppToLoad({
355
+ appIdFromUrl,
356
+ appIdFromPath,
357
+ appIdFromAppUrl,
358
+ appsBeforeExtension
359
+ });
360
+
361
+ if (!appToLoad) {
362
+ throw new Error('No apps registered');
363
+ }
364
+
365
+ await this.loadApp(appToLoad, this.container);
366
+ }
367
+
368
+ /**
369
+ * Load and initialize an application.
370
+ *
371
+ * @param appId - Application identifier (must be already registered)
372
+ * @param container - Optional DOM element to render into (if provided, auto-renders after loading)
373
+ * @returns Promise that resolves when app is initialized and rendered
374
+ */
375
+ async loadApp(appId: string, container?: HTMLElement): Promise<void> {
376
+ const app = this.apps.get(appId);
377
+ if (!app) {
378
+ throw new Error(`App '${appId}' not found. Make sure it's registered.`);
379
+ }
380
+
381
+ logger.info(`Loading app: ${app.name}...`);
382
+
383
+ // Dispose current app if exists
384
+ if (this.currentApp) {
385
+ logger.info(`Disposing current app: ${this.currentApp.name}`);
386
+
387
+ // Call app's dispose method
388
+ if (this.currentApp.dispose) {
389
+ await this.currentApp.dispose();
390
+ }
391
+
392
+ // Disable current app's extensions (but not system-required ones)
393
+ if (this.currentApp.extensions && this.currentApp.extensions.length > 0) {
394
+ logger.info(`Disabling ${this.currentApp.extensions.length} extensions...`);
395
+ this.currentApp.extensions.forEach(extId => {
396
+ extensionRegistry.disable(extId);
397
+ });
398
+ }
399
+ }
400
+
401
+ // Register app contributions
402
+ if (app.contributions) {
403
+ logger.info('Registering app contributions...');
404
+
405
+ // Register UI contributions (tabs, toolbars, commands, etc.)
406
+ if (app.contributions.ui) {
407
+ app.contributions.ui.forEach(contribution => {
408
+ const target = contribution.target;
409
+ if (target) {
410
+ contributionRegistry.registerContribution(target, contribution);
411
+ }
412
+ });
413
+ logger.info(`Registered ${app.contributions.ui.length} UI contributions`);
414
+ }
415
+
416
+ // Register app-specific extensions
417
+ if (app.contributions.extensions) {
418
+ app.contributions.extensions.forEach(extension => {
419
+ extensionRegistry.registerExtension(extension);
420
+ });
421
+ logger.info(`Registered ${app.contributions.extensions.length} app extensions`);
422
+ }
423
+ }
424
+
425
+ const extensionsSet = new Set<string>(app.extensions || []);
426
+ this.systemRequiredExtensions.forEach(extId => extensionsSet.add(extId));
427
+ app.extensions = Array.from(extensionsSet);
428
+
429
+ // Enable new app's extensions (after contributions are registered)
430
+ if (app.extensions.length > 0) {
431
+ logger.info(`Enabling ${app.extensions.length} extensions...`);
432
+ app.extensions.forEach(extId => {
433
+ extensionRegistry.enable(extId);
434
+ });
435
+ }
436
+
437
+ // Initialize new app
438
+ if (app.initialize) {
439
+ logger.info(`Initializing ${app.name}...`);
440
+ await app.initialize();
441
+ }
442
+
443
+ this.currentApp = app;
444
+ logger.info(`App ${app.name} loaded successfully`);
445
+
446
+ // Update document metadata from app
447
+ this.updateDocumentMetadata(app);
448
+
449
+ // Auto-render if container provided
450
+ if (container) {
451
+ this.renderApp(container);
452
+ }
453
+
454
+ // Dispatch event for components to react to app changes
455
+ window.dispatchEvent(new CustomEvent('app-loaded', { detail: { appId: app.id } }));
456
+ }
457
+
458
+ /**
459
+ * Updates document title and favicon from app metadata
460
+ */
461
+ private updateDocumentMetadata(app: AppDefinition): void {
462
+ // Set document title
463
+ document.title = app.name;
464
+
465
+ // Set favicon if provided in metadata
466
+ if (app.metadata?.favicon) {
467
+ const faviconPath = app.metadata.favicon;
468
+ let link = document.querySelector("link[rel*='icon']") as HTMLLinkElement;
469
+ if (!link) {
470
+ link = document.createElement('link');
471
+ link.rel = 'icon';
472
+ document.head.appendChild(link);
473
+ }
474
+ link.type = 'image/svg+xml';
475
+ link.href = faviconPath;
476
+ }
477
+ }
478
+
479
+ /**
480
+ * Render the current application to the DOM.
481
+ *
482
+ * @param container - DOM element to render into
483
+ */
484
+ renderApp(container: HTMLElement): void {
485
+ if (!this.currentApp) {
486
+ throw new Error('No app loaded. Call loadApp() first.');
487
+ }
488
+
489
+ const r = this.currentApp.render;
490
+ if (typeof r === 'string') {
491
+ const el = document.createElement(r);
492
+ container.innerHTML = '';
493
+ container.appendChild(el);
494
+ } else if (r && typeof r === 'object' && 'tag' in r) {
495
+ const el = document.createElement(r.tag);
496
+ for (const [key, value] of Object.entries(r.attributes ?? {})) {
497
+ el.setAttribute(key, value);
498
+ }
499
+ container.innerHTML = '';
500
+ container.appendChild(el);
501
+ } else if (typeof r === 'function') {
502
+ const template = r();
503
+ render(template, container);
504
+ } else {
505
+ render(html`<k-standard-layout></k-standard-layout>`, container);
506
+ }
507
+ logger.info(`Rendered ${this.currentApp.name}`);
508
+ }
509
+
510
+ /**
511
+ * Get the currently loaded application.
512
+ */
513
+ getCurrentApp(): AppDefinition | undefined {
514
+ return this.currentApp;
515
+ }
516
+
517
+ /**
518
+ * Get all registered applications.
519
+ */
520
+ getRegisteredApps(): AppDefinition[] {
521
+ return Array.from(this.apps.values());
522
+ }
523
+
524
+ /**
525
+ * Get the preferred app ID from settings.
526
+ */
527
+ async getPreferredAppId(): Promise<string | undefined> {
528
+ try {
529
+ return await appSettings.get(AppLoaderService.PREFERRED_APP_KEY);
530
+ } catch (error) {
531
+ logger.debug(`Failed to get preferred app ID from settings: ${getErrorMessage(error)}`);
532
+ return undefined;
533
+ }
534
+ }
535
+
536
+ /**
537
+ * Set the preferred app ID and persist it to settings.
538
+ */
539
+ async setPreferredAppId(appId: string): Promise<void> {
540
+ if (!this.apps.has(appId)) {
541
+ throw new Error(`App '${appId}' not found. Make sure it's registered.`);
542
+ }
543
+
544
+ try {
545
+ await appSettings.set(AppLoaderService.PREFERRED_APP_KEY, appId);
546
+ this.defaultAppId = appId;
547
+ logger.info(`Set preferred app to: ${appId}`);
548
+ } catch (error) {
549
+ logger.error(`Failed to persist preferred app ID: ${getErrorMessage(error)}`);
550
+ throw error;
551
+ }
552
+ }
553
+
554
+ /**
555
+ * Select which app to load based on priority:
556
+ * 1. appId URL parameter (?appId=...)
557
+ * 2. App ID from current page URL path (/geospace)
558
+ * 3. App ID extracted from app URL parameter (?app=...)
559
+ * 4. App registered by extension
560
+ * 5. Preferred app ID from settings
561
+ * 6. Default app ID
562
+ * 7. First registered app
563
+ */
564
+ private async selectAppToLoad(options: {
565
+ appIdFromUrl: string | null;
566
+ appIdFromPath: string | undefined;
567
+ appIdFromAppUrl: string | undefined;
568
+ appsBeforeExtension: number;
569
+ }): Promise<string | undefined> {
570
+ const { appIdFromUrl, appIdFromPath, appIdFromAppUrl, appsBeforeExtension } = options;
571
+
572
+ if (appIdFromUrl) {
573
+ if (this.apps.has(appIdFromUrl)) {
574
+ logger.info(`Loading app specified by URL parameter 'appId': ${appIdFromUrl}`);
575
+ return appIdFromUrl;
576
+ }
577
+ logger.warn(`App ID '${appIdFromUrl}' from URL parameter not found`);
578
+ }
579
+
580
+ if (appIdFromPath) {
581
+ if (this.apps.has(appIdFromPath)) {
582
+ logger.info(`Loading app from URL path: ${appIdFromPath}`);
583
+ return appIdFromPath;
584
+ }
585
+ logger.debug(`App ID '${appIdFromPath}' from URL path not found, continuing search`);
586
+ }
587
+
588
+ if (appIdFromAppUrl) {
589
+ if (this.apps.has(appIdFromAppUrl)) {
590
+ logger.info(`Loading app using ID extracted from app URL path: ${appIdFromAppUrl}`);
591
+ return appIdFromAppUrl;
592
+ }
593
+ }
594
+
595
+ if (this.apps.size > appsBeforeExtension) {
596
+ const newlyRegisteredApps = Array.from(this.apps.values()).slice(appsBeforeExtension);
597
+ if (newlyRegisteredApps.length > 0) {
598
+ const app = newlyRegisteredApps[0];
599
+ logger.info(`Loading app registered by extension: ${app.name} (${app.id})`);
600
+ return app.id;
601
+ }
602
+ }
603
+
604
+ const preferredAppId = await this.getPreferredAppId();
605
+ if (preferredAppId && this.apps.has(preferredAppId)) {
606
+ logger.info(`Loading preferred app from settings: ${preferredAppId}`);
607
+ return preferredAppId;
608
+ }
609
+
610
+ if (this.defaultAppId) {
611
+ if (this.apps.has(this.defaultAppId)) {
612
+ return this.defaultAppId;
613
+ }
614
+ logger.warn(`Default app '${this.defaultAppId}' not found`);
615
+ }
616
+
617
+ const registeredApps = this.getRegisteredApps();
618
+ if (registeredApps.length > 0) {
619
+ const app = registeredApps[0];
620
+ logger.info(`Loading first registered app: ${app.name} (${app.id})`);
621
+ return app.id;
622
+ }
623
+
624
+ return undefined;
625
+ }
626
+ }
627
+
628
+ export const appLoaderService = new AppLoaderService();
629
+ rootContext.put("appLoaderService", appLoaderService);
630
+
@@ -0,0 +1,15 @@
1
+ import {signal} from '@lit-labs/signals';
2
+ import {KPart} from "../parts/k-part";
3
+
4
+ export const EMPTY_SIGNALPORT = {} as any
5
+
6
+ export const activePartSignal = signal<KPart>(null as unknown as KPart)
7
+ export const activeEditorSignal = signal<KPart>(null as unknown as KPart)
8
+
9
+ export const partDirtySignal = signal<KPart>(null as unknown as KPart)
10
+
11
+ export const activeTasksSignal = signal<number>(0)
12
+
13
+ export const activeSelectionSignal = signal<any>(undefined)
14
+
15
+ export const perspectiveSwitchedSignal = signal<{name: string, timestamp: number}>({name: '', timestamp: 0})