@eclipse-lyra/core 0.7.6 → 0.7.7

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 (60) hide show
  1. package/dist/api/index.js +28 -29
  2. package/dist/api/services.d.ts +0 -4
  3. package/dist/api/services.d.ts.map +1 -1
  4. package/dist/api/types.d.ts +1 -1
  5. package/dist/api/types.d.ts.map +1 -1
  6. package/dist/components/fastviews.d.ts +1 -1
  7. package/dist/components/index.d.ts.map +1 -1
  8. package/dist/components/{app-switcher.d.ts → layout-switcher.d.ts} +5 -4
  9. package/dist/components/layout-switcher.d.ts.map +1 -0
  10. package/dist/{standard-layout-Efok-voU.js → config-BiRvaEoO.js} +243 -454
  11. package/dist/config-BiRvaEoO.js.map +1 -0
  12. package/dist/contributions/default-layout-contributions.d.ts +1 -0
  13. package/dist/contributions/default-layout-contributions.d.ts.map +1 -0
  14. package/dist/contributions/index.d.ts.map +1 -1
  15. package/dist/core/apploader.d.ts +40 -30
  16. package/dist/core/apploader.d.ts.map +1 -1
  17. package/dist/core/constants.d.ts +1 -0
  18. package/dist/core/constants.d.ts.map +1 -1
  19. package/dist/core/contributionregistry.d.ts +9 -0
  20. package/dist/core/contributionregistry.d.ts.map +1 -1
  21. package/dist/core/index.d.ts.map +1 -1
  22. package/dist/icon-DN6fp0dg.js.map +1 -1
  23. package/dist/index.js +28 -29
  24. package/dist/parts/contextmenu.d.ts +1 -1
  25. package/dist/parts/index.js +1 -1
  26. package/dist/parts/resizable-grid.d.ts +1 -1
  27. package/dist/{resizable-grid-BP9wOk_x.js → resizable-grid-oWYRVx30.js} +315 -94
  28. package/dist/resizable-grid-oWYRVx30.js.map +1 -0
  29. package/dist/vite-plugin-resolve-deps.d.ts +18 -0
  30. package/dist/vite-plugin-resolve-deps.d.ts.map +1 -0
  31. package/dist/widgets/icon.d.ts +1 -1
  32. package/package.json +8 -1
  33. package/src/api/services.ts +0 -4
  34. package/src/api/types.ts +1 -0
  35. package/src/commands/version-info.ts +24 -10
  36. package/src/components/index.ts +1 -1
  37. package/src/components/layout-switcher.ts +83 -0
  38. package/src/contributions/default-layout-contributions.ts +10 -0
  39. package/src/contributions/default-ui-contributions.ts +1 -1
  40. package/src/contributions/index.ts +1 -0
  41. package/src/contributions/marketplace-catalog-contributions.ts +1 -1
  42. package/src/core/apploader.ts +182 -99
  43. package/src/core/constants.ts +1 -0
  44. package/src/core/contributionregistry.ts +7 -0
  45. package/src/core/index.ts +0 -1
  46. package/src/vite-env.d.ts +9 -0
  47. package/src/vite-plugin-resolve-deps.ts +112 -0
  48. package/dist/components/app-selector.d.ts +0 -17
  49. package/dist/components/app-selector.d.ts.map +0 -1
  50. package/dist/components/app-switcher.d.ts.map +0 -1
  51. package/dist/core/app-host-config.d.ts +0 -7
  52. package/dist/core/app-host-config.d.ts.map +0 -1
  53. package/dist/core/packageinfoservice.d.ts +0 -16
  54. package/dist/core/packageinfoservice.d.ts.map +0 -1
  55. package/dist/resizable-grid-BP9wOk_x.js.map +0 -1
  56. package/dist/standard-layout-Efok-voU.js.map +0 -1
  57. package/src/components/app-selector.ts +0 -233
  58. package/src/components/app-switcher.ts +0 -126
  59. package/src/core/app-host-config.ts +0 -23
  60. package/src/core/packageinfoservice.ts +0 -56
@@ -16,8 +16,10 @@ import {render, TemplateResult, html} from "lit";
16
16
  import {rootContext} from "./di";
17
17
  import {createLogger} from "./logger";
18
18
  import {extensionRegistry, Extension} from "./extensionregistry";
19
- import {contributionRegistry, Contribution} from "./contributionregistry";
19
+ import {contributionRegistry, Contribution, LayoutContribution} from "./contributionregistry";
20
+ import {SYSTEM_LAYOUTS} from "./constants";
20
21
  import {appSettings} from "./settingsservice";
22
+ import { marketplaceRegistry } from "./marketplaceregistry";
21
23
 
22
24
 
23
25
  const logger = createLogger('AppLoader');
@@ -122,17 +124,17 @@ export interface RenderDescriptor {
122
124
  * Applications implement this interface to integrate with the framework.
123
125
  */
124
126
  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 */
127
+ /** Application name (from package.json). Unique key; set by hostConfig resolution when omitted. */
128
+ name?: string;
129
+
130
+ /** Application version. Set by hostConfig resolution from package.json when omitted. */
131
+ version?: string;
132
+
133
+ /** Application description. Set by hostConfig resolution from package.json when omitted. */
135
134
  description?: string;
135
+
136
+ /** Optional URL path segment for routing (e.g. "geospace"). When absent, name is used for lookup. */
137
+ path?: string;
136
138
 
137
139
  /**
138
140
  * Custom application metadata (optional).
@@ -168,32 +170,32 @@ export interface AppDefinition {
168
170
  * (if metadata.github is configured).
169
171
  */
170
172
  releaseHistory?: ReleaseHistory | (() => ReleaseHistory | Promise<ReleaseHistory>);
171
-
173
+
172
174
  /**
173
- * Root component to render. Can be:
174
- * - A tag name string (e.g. "lyra-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 lyra-standard-layout.
175
+ * Id of a layout registered to the system.layouts contribution slot.
176
+ * The app root is always the chosen layout's component. Defaults to 'standard' when omitted.
178
177
  */
179
- component?: string | RenderDescriptor | (() => TemplateResult);
178
+ layoutId?: string;
180
179
 
181
180
  /**
182
181
  * Optional cleanup function.
183
182
  * Called when the app is being unloaded, before extensions are disabled.
184
183
  */
185
184
  dispose?: () => void | Promise<void>;
185
+
186
+ /** Resolved dependency versions (e.g. from build plugin). Shown in About / version info. */
187
+ dependencies?: Record<string, string>;
188
+
189
+ /** Marketplace catalog URLs for this app. Registered when the app is registered. */
190
+ marketplaceCatalogUrls?: string[];
186
191
  }
187
192
 
188
193
  /**
189
194
  * Options for registering an application with the apploader.
190
195
  */
191
196
  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
+ /** Default app name to load if no app URL parameter is provided. If not specified, the first registered app is loaded. */
198
+ defaultAppName?: string;
197
199
 
198
200
  /**
199
201
  * Whether to automatically start the apploader after registration.
@@ -207,6 +209,11 @@ export interface RegisterAppOptions {
207
209
  * Defaults to document.body.
208
210
  */
209
211
  container?: HTMLElement;
212
+
213
+ /**
214
+ * When true, fill name, version, description, dependencies, marketplaceCatalogUrls from __RESOLVED_PACKAGE_INFO__ only when not already set on the app.
215
+ */
216
+ hostConfig?: boolean;
210
217
  }
211
218
 
212
219
  /**
@@ -222,10 +229,12 @@ class AppLoaderService {
222
229
  private apps: Map<string, AppDefinition> = new Map();
223
230
  private currentApp?: AppDefinition;
224
231
  private started: boolean = false;
225
- private defaultAppId?: string;
232
+ private defaultAppName?: string;
226
233
  private container: HTMLElement = document.body;
227
234
  private systemRequiredExtensions: Set<string> = new Set();
228
- private static readonly PREFERRED_APP_KEY = 'preferredAppId';
235
+ private static readonly PREFERRED_APP_KEY = 'preferredAppName';
236
+ private static readonly PREFERRED_LAYOUT_KEY = 'preferredLayoutId';
237
+ private preferredLayoutId?: string;
229
238
 
230
239
  /**
231
240
  * Register an application with the framework.
@@ -235,15 +244,29 @@ class AppLoaderService {
235
244
  * @param options - Optional configuration for registration and auto-starting
236
245
  */
237
246
  registerApp(app: AppDefinition, options?: RegisterAppOptions): void {
238
- if (this.apps.has(app.id)) {
239
- logger.warn(`App '${app.id}' is already registered. Overwriting.`);
247
+ if (options?.hostConfig === true && typeof __RESOLVED_PACKAGE_INFO__ !== 'undefined') {
248
+ const resolved = __RESOLVED_PACKAGE_INFO__;
249
+ if (app.name === undefined) app.name = resolved.name;
250
+ if (app.version === undefined) app.version = resolved.version;
251
+ if (app.description === undefined) app.description = resolved.description;
252
+ if (app.dependencies === undefined) app.dependencies = resolved.dependencies;
253
+ if (app.marketplaceCatalogUrls === undefined) app.marketplaceCatalogUrls = resolved.marketplaceCatalogUrls;
240
254
  }
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;
255
+ app.name = app.name ?? 'app';
256
+ app.version = app.version ?? '0.0.0';
257
+
258
+ if (this.apps.has(app.name)) {
259
+ logger.warn(`App '${app.name}' is already registered. Overwriting.`);
260
+ }
261
+ if (app.marketplaceCatalogUrls?.length) {
262
+ app.marketplaceCatalogUrls.forEach((url) => marketplaceRegistry.addCatalogUrl(url).catch(() => {}));
263
+ }
264
+
265
+ this.apps.set(app.name, app);
266
+ logger.info(`Registered app: ${app.name} v${app.version}`);
267
+
268
+ if (options?.defaultAppName) {
269
+ this.defaultAppName = options.defaultAppName;
247
270
  }
248
271
 
249
272
  if (options?.container) {
@@ -279,11 +302,10 @@ class AppLoaderService {
279
302
 
280
303
  const app = module.default as AppDefinition;
281
304
 
282
- if (!app.id || !app.name || !app.version) {
283
- throw new Error(`Module at ${url} does not export a valid AppDefinition`);
305
+ if (!app.name || !app.version) {
306
+ throw new Error(`Module at ${url} does not export a valid AppDefinition (name and version required)`);
284
307
  }
285
-
286
- logger.info(`Successfully loaded app definition from URL: ${app.name} (${app.id})`);
308
+ logger.info(`Successfully loaded app definition from URL: ${app.name}`);
287
309
  return app;
288
310
  } catch (error) {
289
311
  logger.error(`Failed to load app from URL ${url}: ${getErrorMessage(error)}`);
@@ -294,7 +316,7 @@ class AppLoaderService {
294
316
  /**
295
317
  * Start the application loader.
296
318
  * Checks URL parameters for app=URL, loads that extension or app if found.
297
- * URL parameter has higher precedence than defaultAppId.
319
+ * URL parameter has higher precedence than defaultAppName.
298
320
  * Then loads the default app or first registered app.
299
321
  * This method is idempotent - calling it multiple times only starts once.
300
322
  */
@@ -338,7 +360,8 @@ class AppLoaderService {
338
360
  try {
339
361
  const app = await this.loadAppFromUrl(appUrl);
340
362
  this.registerApp(app);
341
- await this.loadApp(app.id, this.container);
363
+ if (!app.name) throw new Error('App from URL has no name after registration');
364
+ await this.loadApp(app.name, this.container);
342
365
  logger.info(`Successfully loaded app from URL: ${appUrl}`);
343
366
  return;
344
367
  } catch (appError) {
@@ -365,17 +388,26 @@ class AppLoaderService {
365
388
  await this.loadApp(appToLoad, this.container);
366
389
  }
367
390
 
391
+ /**
392
+ * Resolve a path/URL segment to an app name (map key). Matches app.path, app.name, or name ending with /segment.
393
+ */
394
+ private findAppNameBySegment(segment: string): string | undefined {
395
+ if (this.apps.has(segment)) return segment;
396
+ for (const app of this.apps.values()) {
397
+ if (app.path === segment || (app.name && app.name.endsWith('/' + segment))) return app.name ?? undefined;
398
+ }
399
+ return undefined;
400
+ }
401
+
368
402
  /**
369
403
  * Load and initialize an application.
370
- *
371
- * @param appId - Application identifier (must be already registered)
404
+ * @param appName - Application name (must be already registered)
372
405
  * @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
406
  */
375
- async loadApp(appId: string, container?: HTMLElement): Promise<void> {
376
- const app = this.apps.get(appId);
407
+ async loadApp(appName: string, container?: HTMLElement): Promise<void> {
408
+ const app = this.apps.get(appName);
377
409
  if (!app) {
378
- throw new Error(`App '${appId}' not found. Make sure it's registered.`);
410
+ throw new Error(`App '${appName}' not found. Make sure it's registered.`);
379
411
  }
380
412
 
381
413
  logger.info(`Loading app: ${app.name}...`);
@@ -442,25 +474,21 @@ class AppLoaderService {
442
474
 
443
475
  this.currentApp = app;
444
476
  logger.info(`App ${app.name} loaded successfully`);
445
-
446
- // Update document metadata from app
477
+ this.preferredLayoutId = await this.getPreferredLayoutId();
447
478
  this.updateDocumentMetadata(app);
448
-
449
- // Auto-render if container provided
450
479
  if (container) {
451
480
  this.renderApp(container);
452
481
  }
453
482
 
454
483
  // Dispatch event for components to react to app changes
455
- window.dispatchEvent(new CustomEvent('app-loaded', { detail: { appId: app.id } }));
484
+ window.dispatchEvent(new CustomEvent('app-loaded', { detail: { appName: app.name } }));
456
485
  }
457
486
 
458
487
  /**
459
488
  * Updates document title and favicon from app metadata
460
489
  */
461
490
  private updateDocumentMetadata(app: AppDefinition): void {
462
- // Set document title
463
- document.title = app.name;
491
+ document.title = app.name ?? '';
464
492
 
465
493
  // Set favicon if provided in metadata
466
494
  if (app.metadata?.favicon) {
@@ -478,7 +506,8 @@ class AppLoaderService {
478
506
 
479
507
  /**
480
508
  * Render the current application to the DOM.
481
- *
509
+ * Resolves the layout by layoutId (default 'standard'), renders its component, then calls layout.onShow if defined.
510
+ *
482
511
  * @param container - DOM element to render into
483
512
  */
484
513
  renderApp(container: HTMLElement): void {
@@ -486,23 +515,39 @@ class AppLoaderService {
486
515
  throw new Error('No app loaded. Call loadApp() first.');
487
516
  }
488
517
 
489
- const r = this.currentApp.component;
518
+ const layoutId = this.preferredLayoutId ?? this.currentApp.layoutId ?? 'standard';
519
+ const layouts = contributionRegistry.getContributions<LayoutContribution>(SYSTEM_LAYOUTS);
520
+ let layout = layouts.find((c) => c.id === layoutId);
521
+ if (!layout) {
522
+ logger.warn(`Layout '${layoutId}' not found, falling back to 'standard'`);
523
+ layout = layouts.find((c) => c.id === 'standard');
524
+ }
525
+ if (!layout) {
526
+ throw new Error(`No layout found for layoutId '${layoutId}' and no 'standard' layout registered.`);
527
+ }
528
+
529
+ const r = layout.component;
530
+ container.innerHTML = '';
490
531
  if (typeof r === 'string') {
491
- const el = document.createElement(r);
492
- container.innerHTML = '';
493
- container.appendChild(el);
532
+ container.appendChild(document.createElement(r));
494
533
  } else if (r && typeof r === 'object' && 'tag' in r) {
495
534
  const el = document.createElement(r.tag);
496
535
  for (const [key, value] of Object.entries(r.attributes ?? {})) {
497
536
  el.setAttribute(key, value);
498
537
  }
499
- container.innerHTML = '';
500
538
  container.appendChild(el);
501
539
  } else if (typeof r === 'function') {
502
- const template = r();
503
- render(template, container);
540
+ render(r(), container);
504
541
  } else {
505
- render(html`<lyra-standard-layout></lyra-standard-layout>`, container);
542
+ throw new Error(`Layout '${layout.id}' has invalid component.`);
543
+ }
544
+
545
+ if (layout.onShow) {
546
+ requestAnimationFrame(() => {
547
+ void Promise.resolve(layout!.onShow!()).catch((err) =>
548
+ logger.error(`Layout onShow failed for '${layout!.id}': ${getErrorMessage(err)}`)
549
+ );
550
+ });
506
551
  }
507
552
  logger.info(`Rendered ${this.currentApp.name}`);
508
553
  }
@@ -540,13 +585,48 @@ class AppLoaderService {
540
585
  if (!this.apps.has(appId)) {
541
586
  throw new Error(`App '${appId}' not found. Make sure it's registered.`);
542
587
  }
543
-
544
588
  try {
545
589
  await appSettings.set(AppLoaderService.PREFERRED_APP_KEY, appId);
546
- this.defaultAppId = appId;
590
+ this.defaultAppName = appId;
547
591
  logger.info(`Set preferred app to: ${appId}`);
548
592
  } catch (error) {
549
- logger.error(`Failed to persist preferred app ID: ${getErrorMessage(error)}`);
593
+ logger.error(`Failed to persist preferred app: ${getErrorMessage(error)}`);
594
+ throw error;
595
+ }
596
+ }
597
+
598
+ getRegisteredLayouts(): LayoutContribution[] {
599
+ return contributionRegistry.getContributions<LayoutContribution>(SYSTEM_LAYOUTS);
600
+ }
601
+
602
+ getCurrentLayoutId(): string {
603
+ return this.preferredLayoutId ?? this.currentApp?.layoutId ?? 'standard';
604
+ }
605
+
606
+ async getPreferredLayoutId(): Promise<string | undefined> {
607
+ try {
608
+ return await appSettings.get(AppLoaderService.PREFERRED_LAYOUT_KEY);
609
+ } catch (error) {
610
+ logger.debug(`Failed to get preferred layout ID: ${getErrorMessage(error)}`);
611
+ return undefined;
612
+ }
613
+ }
614
+
615
+ async setPreferredLayoutId(layoutId: string): Promise<void> {
616
+ const layouts = this.getRegisteredLayouts();
617
+ if (!layouts.some((l) => l.id === layoutId)) {
618
+ throw new Error(`Layout '${layoutId}' not found.`);
619
+ }
620
+ try {
621
+ await appSettings.set(AppLoaderService.PREFERRED_LAYOUT_KEY, layoutId);
622
+ this.preferredLayoutId = layoutId;
623
+ logger.info(`Set preferred layout to: ${layoutId}`);
624
+ if (this.currentApp && this.container) {
625
+ this.renderApp(this.container);
626
+ }
627
+ window.dispatchEvent(new CustomEvent('layout-changed', { detail: { layoutId } }));
628
+ } catch (error) {
629
+ logger.error(`Failed to persist preferred layout: ${getErrorMessage(error)}`);
550
630
  throw error;
551
631
  }
552
632
  }
@@ -554,11 +634,11 @@ class AppLoaderService {
554
634
  /**
555
635
  * Select which app to load based on priority:
556
636
  * 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=...)
637
+ * 2. App from current page URL path (/geospace)
638
+ * 3. App from app URL parameter (?app=...)
559
639
  * 4. App registered by extension
560
- * 5. Preferred app ID from settings
561
- * 6. Default app ID
640
+ * 5. Preferred app from settings
641
+ * 6. Default app
562
642
  * 7. First registered app
563
643
  */
564
644
  private async selectAppToLoad(options: {
@@ -568,59 +648,62 @@ class AppLoaderService {
568
648
  appsBeforeExtension: number;
569
649
  }): Promise<string | undefined> {
570
650
  const { appIdFromUrl, appIdFromPath, appIdFromAppUrl, appsBeforeExtension } = options;
571
-
651
+
572
652
  if (appIdFromUrl) {
573
- if (this.apps.has(appIdFromUrl)) {
574
- logger.info(`Loading app specified by URL parameter 'appId': ${appIdFromUrl}`);
575
- return appIdFromUrl;
653
+ const name = this.findAppNameBySegment(appIdFromUrl) ?? appIdFromUrl;
654
+ if (this.apps.has(name)) {
655
+ logger.info(`Loading app specified by URL parameter 'appId': ${name}`);
656
+ return name;
576
657
  }
577
- logger.warn(`App ID '${appIdFromUrl}' from URL parameter not found`);
658
+ logger.warn(`App '${appIdFromUrl}' from URL parameter not found`);
578
659
  }
579
-
660
+
580
661
  if (appIdFromPath) {
581
- if (this.apps.has(appIdFromPath)) {
662
+ const name = this.findAppNameBySegment(appIdFromPath);
663
+ if (name) {
582
664
  logger.info(`Loading app from URL path: ${appIdFromPath}`);
583
- return appIdFromPath;
665
+ return name;
584
666
  }
585
- logger.debug(`App ID '${appIdFromPath}' from URL path not found, continuing search`);
667
+ logger.debug(`App for path '${appIdFromPath}' not found, continuing search`);
586
668
  }
587
-
669
+
588
670
  if (appIdFromAppUrl) {
589
- if (this.apps.has(appIdFromAppUrl)) {
590
- logger.info(`Loading app using ID extracted from app URL path: ${appIdFromAppUrl}`);
591
- return appIdFromAppUrl;
671
+ const name = this.findAppNameBySegment(appIdFromAppUrl) ?? appIdFromAppUrl;
672
+ if (this.apps.has(name)) {
673
+ logger.info(`Loading app using segment from app URL path: ${name}`);
674
+ return name;
592
675
  }
593
676
  }
594
-
677
+
595
678
  if (this.apps.size > appsBeforeExtension) {
596
679
  const newlyRegisteredApps = Array.from(this.apps.values()).slice(appsBeforeExtension);
597
680
  if (newlyRegisteredApps.length > 0) {
598
681
  const app = newlyRegisteredApps[0];
599
- logger.info(`Loading app registered by extension: ${app.name} (${app.id})`);
600
- return app.id;
682
+ logger.info(`Loading app registered by extension: ${app.name}`);
683
+ return app.name;
601
684
  }
602
685
  }
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;
686
+
687
+ const preferred = await this.getPreferredAppId();
688
+ if (preferred && this.apps.has(preferred)) {
689
+ logger.info(`Loading preferred app from settings: ${preferred}`);
690
+ return preferred;
608
691
  }
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`);
692
+
693
+ if (this.defaultAppName && this.apps.has(this.defaultAppName)) {
694
+ return this.defaultAppName;
615
695
  }
616
-
696
+ if (this.defaultAppName) {
697
+ logger.warn(`Default app '${this.defaultAppName}' not found`);
698
+ }
699
+
617
700
  const registeredApps = this.getRegisteredApps();
618
701
  if (registeredApps.length > 0) {
619
702
  const app = registeredApps[0];
620
- logger.info(`Loading first registered app: ${app.name} (${app.id})`);
621
- return app.id;
703
+ logger.info(`Loading first registered app: ${app.name}`);
704
+ return app.name;
622
705
  }
623
-
706
+
624
707
  return undefined;
625
708
  }
626
709
  }
@@ -7,6 +7,7 @@ export const TOOLBAR_BOTTOM_CENTER = "app-toolbars-bottom-center"
7
7
  export const TOOLBAR_BOTTOM_END = "app-toolbars-bottom-end"
8
8
 
9
9
  export const SYSTEM_VIEWS = "system-views"
10
+ export const SYSTEM_LAYOUTS = "system.layouts"
10
11
 
11
12
  // VS Code-style layout containers
12
13
  export const EDITOR_AREA_MAIN = "editor-area-main"
@@ -45,6 +45,13 @@ export interface IconContribution extends Contribution {
45
45
  priority?: number;
46
46
  }
47
47
 
48
+ export interface LayoutContribution extends Contribution {
49
+ id: string;
50
+ name: string;
51
+ component: string | { tag: string; attributes?: Record<string, string> } | (() => TemplateResult);
52
+ onShow?: () => void | Promise<void>;
53
+ }
54
+
48
55
  class ContributionRegistry {
49
56
  private contributions: Map<string, Contribution[]> = new Map();
50
57
 
package/src/core/index.ts CHANGED
@@ -18,7 +18,6 @@ import './settingsservice';
18
18
 
19
19
  // 2. Registries and task service (contribution first – others depend on it)
20
20
  import './contributionregistry';
21
- import './packageinfoservice';
22
21
  import './taskservice';
23
22
 
24
23
  // 3. i18n and ESM
package/src/vite-env.d.ts CHANGED
@@ -1,5 +1,14 @@
1
1
  /// <reference types="vite/client" />
2
2
 
3
+ /** Injected by resolveDepVersionsPlugin when hostConfig is true. */
4
+ declare const __RESOLVED_PACKAGE_INFO__: {
5
+ name: string;
6
+ version: string;
7
+ description?: string;
8
+ dependencies: Record<string, string>;
9
+ marketplaceCatalogUrls?: string[];
10
+ } | undefined;
11
+
3
12
  declare module 'toastify-js' {
4
13
  interface ToastifyOptions {
5
14
  text?: string;
@@ -0,0 +1,112 @@
1
+ import { readFileSync, existsSync } from 'fs';
2
+ import path from 'path';
3
+ import type { Plugin } from 'vite';
4
+
5
+ export interface ResolvedPackageInfo {
6
+ name: string;
7
+ version: string;
8
+ description?: string;
9
+ dependencies: Record<string, string>;
10
+ marketplaceCatalogUrls?: string[];
11
+ }
12
+
13
+ interface PackageJson {
14
+ name?: string;
15
+ version?: string;
16
+ description?: string;
17
+ dependencies?: Record<string, string>;
18
+ devDependencies?: Record<string, string>;
19
+ }
20
+
21
+ function findPackageVersion(appRoot: string, depName: string): string | null {
22
+ const segments = depName.startsWith('@')
23
+ ? depName.split('/')
24
+ : [depName];
25
+ const relativePath = path.join('node_modules', ...segments, 'package.json');
26
+ let dir = path.resolve(appRoot);
27
+ const root = path.parse(dir).root;
28
+
29
+ while (true) {
30
+ const pkgPath = path.join(dir, relativePath);
31
+ if (existsSync(pkgPath)) {
32
+ try {
33
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as PackageJson;
34
+ if (typeof pkg.version === 'string') return pkg.version;
35
+ } catch {
36
+ // ignore parse errors
37
+ }
38
+ return null;
39
+ }
40
+ if (dir === root) break;
41
+ dir = path.dirname(dir);
42
+ }
43
+ return null;
44
+ }
45
+
46
+ function resolveDepVersionsFromPkg(
47
+ appRoot: string,
48
+ pkg: PackageJson,
49
+ options?: { includeDevDependencies?: boolean }
50
+ ): Record<string, string> {
51
+ const deps = { ...pkg.dependencies };
52
+ if (options?.includeDevDependencies && pkg.devDependencies) {
53
+ Object.assign(deps, pkg.devDependencies);
54
+ }
55
+ const result: Record<string, string> = {};
56
+ for (const [name, specifier] of Object.entries(deps)) {
57
+ const version = findPackageVersion(appRoot, name);
58
+ result[name] = version ?? specifier;
59
+ }
60
+ return result;
61
+ }
62
+
63
+ export function resolvePackageInfo(
64
+ appRoot: string,
65
+ options?: { includeDevDependencies?: boolean }
66
+ ): ResolvedPackageInfo | null {
67
+ const pkgPath = path.join(appRoot, 'package.json');
68
+ if (!existsSync(pkgPath)) return null;
69
+
70
+ let pkg: PackageJson;
71
+ try {
72
+ pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as PackageJson;
73
+ } catch {
74
+ return null;
75
+ }
76
+
77
+ const name = typeof pkg.name === 'string' ? pkg.name : '';
78
+ const version = typeof pkg.version === 'string' ? pkg.version : '0.0.0';
79
+ const description = typeof pkg.description === 'string' ? pkg.description : undefined;
80
+ const dependencies = resolveDepVersionsFromPkg(appRoot, pkg, options);
81
+ const marketplaceCatalogUrls = (pkg as { marketplace?: { catalogUrls?: string[] } }).marketplace?.catalogUrls;
82
+
83
+ return { name, version, description, dependencies, marketplaceCatalogUrls };
84
+ }
85
+
86
+ export function resolveDepVersions(
87
+ appRoot: string,
88
+ options?: { includeDevDependencies?: boolean }
89
+ ): Record<string, string> {
90
+ const info = resolvePackageInfo(appRoot, options);
91
+ return info?.dependencies ?? {};
92
+ }
93
+
94
+ const RESOLVED_PACKAGE_INFO_KEY = '__RESOLVED_PACKAGE_INFO__';
95
+
96
+ export function resolveDepVersionsPlugin(options?: {
97
+ includeDevDependencies?: boolean;
98
+ }): Plugin {
99
+ return {
100
+ name: 'resolve-dep-versions',
101
+ config(config) {
102
+ const root = config.root ? path.resolve(config.root) : process.cwd();
103
+ const info = resolvePackageInfo(root, options);
104
+ const value = info ?? { name: '', version: '0.0.0', description: undefined, dependencies: {}, marketplaceCatalogUrls: undefined };
105
+ return {
106
+ define: {
107
+ [RESOLVED_PACKAGE_INFO_KEY]: JSON.stringify(value),
108
+ },
109
+ };
110
+ },
111
+ };
112
+ }
@@ -1,17 +0,0 @@
1
- import { LyraElement } from '../parts/element';
2
- export declare class LyraAppSelector extends LyraElement {
3
- private apps;
4
- private loading;
5
- private error;
6
- protected doBeforeUI(): Promise<void>;
7
- private loadApps;
8
- private selectApp;
9
- protected render(): import('lit-html').TemplateResult<1>;
10
- static styles: import('lit').CSSResult;
11
- }
12
- declare global {
13
- interface HTMLElementTagNameMap {
14
- 'lyra-app-selector': LyraAppSelector;
15
- }
16
- }
17
- //# sourceMappingURL=app-selector.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"app-selector.d.ts","sourceRoot":"","sources":["../../src/components/app-selector.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/C,qBACa,eAAgB,SAAQ,WAAW;IAE5C,OAAO,CAAC,IAAI,CAAuB;IAGnC,OAAO,CAAC,OAAO,CAAQ;IAGvB,OAAO,CAAC,KAAK,CAAuB;cAEpB,UAAU;YAIZ,QAAQ;YAWR,SAAS;IASvB,SAAS,CAAC,MAAM;IA+DhB,MAAM,CAAC,MAAM,0BAyHX;CACL;AAED,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,qBAAqB;QAC3B,mBAAmB,EAAE,eAAe,CAAC;KACxC;CACJ"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"app-switcher.d.ts","sourceRoot":"","sources":["../../src/components/app-switcher.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAuE/C,qBACa,eAAgB,SAAQ,WAAW;IAE5C,OAAO,CAAC,UAAU,CAA4B;IAE9C,SAAS,CAAC,UAAU;IAepB,SAAS,CAAC,MAAM;IAoBhB,MAAM,CAAC,MAAM,0BAIX;CACL;AAED,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,qBAAqB;QAC3B,mBAAmB,EAAE,eAAe,CAAC;KACxC;CACJ"}
@@ -1,7 +0,0 @@
1
- import { PackageInfo } from './packageinfoservice';
2
- export interface AppHostConfig {
3
- packageInfo?: PackageInfo;
4
- marketplaceCatalogUrls?: string[];
5
- }
6
- export declare function applyAppHostConfig(config: AppHostConfig): void;
7
- //# sourceMappingURL=app-host-config.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"app-host-config.d.ts","sourceRoot":"","sources":["../../src/core/app-host-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAMxD,MAAM,WAAW,aAAa;IAC1B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,sBAAsB,CAAC,EAAE,MAAM,EAAE,CAAC;CACrC;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAW9D"}