@eclipse-docks/core 0.7.127 → 0.7.130
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/index.js +3 -3
- package/dist/api/services.d.ts +3 -1
- package/dist/api/services.d.ts.map +1 -1
- package/dist/{api-Da9zQcfd.js → api-oPg3Bv1S.js} +243 -264
- package/dist/api-oPg3Bv1S.js.map +1 -0
- package/dist/components/attribution.d.ts +1 -0
- package/dist/components/attribution.d.ts.map +1 -1
- package/dist/components/command.d.ts +0 -1
- package/dist/components/command.d.ts.map +1 -1
- package/dist/components/extensions.d.ts +3 -0
- package/dist/components/extensions.d.ts.map +1 -1
- package/dist/components/language-selector.d.ts.map +1 -1
- package/dist/components/layout-switcher.d.ts +1 -1
- package/dist/components/layout-switcher.d.ts.map +1 -1
- package/dist/components/npm-packages.d.ts +1 -0
- package/dist/components/npm-packages.d.ts.map +1 -1
- package/dist/components/release-history.d.ts +9 -2
- package/dist/components/release-history.d.ts.map +1 -1
- package/dist/components/tasks.d.ts.map +1 -1
- package/dist/core/apploader.d.ts +2 -9
- package/dist/core/apploader.d.ts.map +1 -1
- package/dist/core/contribution-targets.d.ts +1 -0
- package/dist/core/contribution-targets.d.ts.map +1 -1
- package/dist/core/dialogservice.d.ts +3 -1
- package/dist/core/dialogservice.d.ts.map +1 -1
- package/dist/core/dropdown-item.d.ts +8 -0
- package/dist/core/dropdown-item.d.ts.map +1 -1
- package/dist/core/layout-panels.d.ts +25 -0
- package/dist/core/layout-panels.d.ts.map +1 -0
- package/dist/dialogs/navigable-info-dialog.d.ts.map +1 -1
- package/dist/dialogs/version-info-dialog.d.ts +18 -2
- package/dist/dialogs/version-info-dialog.d.ts.map +1 -1
- package/dist/externals/lit.js +1 -1
- package/dist/externals/webawesome.js +1 -1
- package/dist/index.js +3 -3
- package/dist/layouts/standard-layout.d.ts +9 -1
- package/dist/layouts/standard-layout.d.ts.map +1 -1
- package/dist/parts/contextmenu.d.ts.map +1 -1
- package/dist/parts/index.js +1 -1
- package/dist/parts/part.d.ts +2 -0
- package/dist/parts/part.d.ts.map +1 -1
- package/dist/parts/resizable-grid.d.ts +10 -3
- package/dist/parts/resizable-grid.d.ts.map +1 -1
- package/dist/parts/tabs.d.ts +6 -0
- package/dist/parts/tabs.d.ts.map +1 -1
- package/dist/{parts-CRVxaDd-.js → parts-CAt8aeU8.js} +1050 -586
- package/dist/parts-CAt8aeU8.js.map +1 -0
- package/dist/{webawesome-Y1_HnKa_.js → webawesome-BGXn04Mw.js} +18 -10
- package/dist/webawesome-BGXn04Mw.js.map +1 -0
- package/package.json +1 -1
- package/src/api/services.ts +12 -0
- package/src/components/attribution.ts +2 -0
- package/src/components/command.ts +2 -8
- package/src/components/extensions.ts +20 -3
- package/src/components/language-selector.ts +0 -1
- package/src/components/layout-switcher.ts +39 -26
- package/src/components/npm-packages.ts +2 -0
- package/src/components/release-history.ts +29 -40
- package/src/components/tasks.ts +0 -1
- package/src/contributions/default-layout-contributions.ts +7 -43
- package/src/contributions/default-ui-contributions.ts +4 -4
- package/src/core/apploader.ts +7 -54
- package/src/core/contribution-targets.ts +1 -0
- package/src/core/dialogservice.ts +76 -19
- package/src/core/dropdown-item.ts +52 -17
- package/src/core/layout-panels.ts +121 -0
- package/src/dialogs/navigable-info-dialog.ts +5 -2
- package/src/dialogs/version-info-dialog.ts +163 -21
- package/src/icons/icons.txt +4 -2
- package/src/icons/layout-standard-bottom-panel.svg +4 -4
- package/src/icons/layout-standard-full.svg +4 -4
- package/src/icons/layout-standard-left-aux.svg +8 -0
- package/src/icons/layout-standard-left-sidebar.svg +5 -0
- package/src/icons/layout-standard-right-aux.svg +8 -0
- package/src/icons/layout-standard-right-sidebar.svg +5 -0
- package/src/layouts/standard-layout.ts +81 -5
- package/src/parts/contextmenu.ts +2 -1
- package/src/parts/part.ts +24 -0
- package/src/parts/resizable-grid.ts +125 -57
- package/src/parts/tabs.ts +103 -0
- package/dist/api-Da9zQcfd.js.map +0 -1
- package/dist/parts-CRVxaDd-.js.map +0 -1
- package/dist/webawesome-Y1_HnKa_.js.map +0 -1
- package/src/icons/layout-standard-bottom-sidebar.svg +0 -6
- package/src/icons/layout-standard.svg +0 -6
package/src/core/apploader.ts
CHANGED
|
@@ -26,21 +26,12 @@ import { publish } from "./events";
|
|
|
26
26
|
|
|
27
27
|
const logger = createLogger('AppLoader');
|
|
28
28
|
|
|
29
|
-
/** Layout reference: layout id string, or id + props
|
|
29
|
+
/** Layout reference: layout id string, or id + optional props read by the layout on init. */
|
|
30
30
|
export type LayoutDescriptor = string | { id: string; props?: Record<string, string | boolean> };
|
|
31
31
|
|
|
32
32
|
function getLayoutIdFromApp(app: AppDefinition | undefined): string {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return typeof l === 'object' ? l.id : (l ?? 'standard');
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function propsToAttributes(props: Record<string, string | boolean>): Record<string, string> {
|
|
39
|
-
const out: Record<string, string> = {};
|
|
40
|
-
for (const [k, v] of Object.entries(props)) {
|
|
41
|
-
out[k] = typeof v === 'boolean' ? (v ? 'true' : 'false') : v;
|
|
42
|
-
}
|
|
43
|
-
return out;
|
|
33
|
+
const layout = app?.layout ?? 'standard';
|
|
34
|
+
return typeof layout === 'object' ? layout.id : layout;
|
|
44
35
|
}
|
|
45
36
|
|
|
46
37
|
/**
|
|
@@ -177,15 +168,11 @@ export interface AppDefinition {
|
|
|
177
168
|
releaseHistory?: ReleaseHistory | (() => ReleaseHistory | Promise<ReleaseHistory>);
|
|
178
169
|
|
|
179
170
|
/**
|
|
180
|
-
* Layout: id (string) or { id, props }
|
|
181
|
-
* App root is the chosen layout's component. Props are merged as attributes when rendering (e.g. show-bottom-panel).
|
|
171
|
+
* Layout: id (string) or { id, props }. The layout component reads props on init (e.g. panel visibility).
|
|
182
172
|
* Defaults to 'standard' when omitted.
|
|
183
173
|
*/
|
|
184
174
|
layout?: LayoutDescriptor;
|
|
185
175
|
|
|
186
|
-
/** @deprecated Use layout (string or { id, props }) instead. */
|
|
187
|
-
layoutId?: string;
|
|
188
|
-
|
|
189
176
|
/**
|
|
190
177
|
* Optional cleanup function.
|
|
191
178
|
* Called when the app is being unloaded, before extensions are disabled.
|
|
@@ -248,8 +235,6 @@ class AppLoaderService {
|
|
|
248
235
|
private container: HTMLElement = document.body;
|
|
249
236
|
private systemRequiredExtensions: Set<string> = new Set();
|
|
250
237
|
private static readonly PREFERRED_APP_KEY = 'preferredAppName';
|
|
251
|
-
private static readonly PREFERRED_LAYOUT_KEY = 'preferredLayoutId';
|
|
252
|
-
private preferredLayoutId?: string;
|
|
253
238
|
|
|
254
239
|
/**
|
|
255
240
|
* Register an application with the framework.
|
|
@@ -269,6 +254,7 @@ class AppLoaderService {
|
|
|
269
254
|
}
|
|
270
255
|
app.name = app.name ?? 'app';
|
|
271
256
|
app.version = app.version ?? '0.0.0';
|
|
257
|
+
app.layout = app.layout ?? 'standard';
|
|
272
258
|
|
|
273
259
|
if (this.apps.has(app.name)) {
|
|
274
260
|
logger.warn(`App '${app.name}' is already registered. Overwriting.`);
|
|
@@ -443,7 +429,6 @@ class AppLoaderService {
|
|
|
443
429
|
|
|
444
430
|
this.currentApp = app;
|
|
445
431
|
logger.info(`App ${app.name} loaded successfully`);
|
|
446
|
-
this.preferredLayoutId = await this.getPreferredLayoutId();
|
|
447
432
|
this.updateDocumentMetadata(app);
|
|
448
433
|
if (container) {
|
|
449
434
|
this.dispatchLoadProgress('Rendering layout…');
|
|
@@ -485,7 +470,7 @@ class AppLoaderService {
|
|
|
485
470
|
throw new Error('No app loaded. Call loadApp() first.');
|
|
486
471
|
}
|
|
487
472
|
|
|
488
|
-
const layoutId =
|
|
473
|
+
const layoutId = getLayoutIdFromApp(this.currentApp);
|
|
489
474
|
const layouts = contributionRegistry.getContributions<LayoutContribution>(SYSTEM_LAYOUTS);
|
|
490
475
|
let layout = layouts.find((c) => c.id === layoutId);
|
|
491
476
|
if (!layout) {
|
|
@@ -501,10 +486,6 @@ class AppLoaderService {
|
|
|
501
486
|
if (r && typeof r === 'object' && 'tag' in r && r.attributes) {
|
|
502
487
|
effectiveAttributes = { ...r.attributes };
|
|
503
488
|
}
|
|
504
|
-
const appLayout = this.currentApp?.layout;
|
|
505
|
-
if (typeof appLayout === 'object' && appLayout.id === layoutId && appLayout.props) {
|
|
506
|
-
Object.assign(effectiveAttributes, propsToAttributes(appLayout.props));
|
|
507
|
-
}
|
|
508
489
|
|
|
509
490
|
container.innerHTML = '';
|
|
510
491
|
if (typeof r === 'string') {
|
|
@@ -582,35 +563,7 @@ class AppLoaderService {
|
|
|
582
563
|
}
|
|
583
564
|
|
|
584
565
|
getCurrentLayoutId(): string {
|
|
585
|
-
return
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
async getPreferredLayoutId(): Promise<string | undefined> {
|
|
589
|
-
try {
|
|
590
|
-
return await appSettings.get(AppLoaderService.PREFERRED_LAYOUT_KEY);
|
|
591
|
-
} catch (error) {
|
|
592
|
-
logger.debug(`Failed to get preferred layout ID: ${getErrorMessage(error)}`);
|
|
593
|
-
return undefined;
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
async setPreferredLayoutId(layoutId: string): Promise<void> {
|
|
598
|
-
const layouts = this.getRegisteredLayouts();
|
|
599
|
-
if (!layouts.some((l) => l.id === layoutId)) {
|
|
600
|
-
throw new Error(`Layout '${layoutId}' not found.`);
|
|
601
|
-
}
|
|
602
|
-
try {
|
|
603
|
-
await appSettings.set(AppLoaderService.PREFERRED_LAYOUT_KEY, layoutId);
|
|
604
|
-
this.preferredLayoutId = layoutId;
|
|
605
|
-
logger.info(`Set preferred layout to: ${layoutId}`);
|
|
606
|
-
if (this.currentApp && this.container) {
|
|
607
|
-
this.renderApp(this.container);
|
|
608
|
-
}
|
|
609
|
-
window.dispatchEvent(new CustomEvent('layout-changed', { detail: { layoutId } }));
|
|
610
|
-
} catch (error) {
|
|
611
|
-
logger.error(`Failed to persist preferred layout: ${getErrorMessage(error)}`);
|
|
612
|
-
throw error;
|
|
613
|
-
}
|
|
566
|
+
return getLayoutIdFromApp(this.currentApp);
|
|
614
567
|
}
|
|
615
568
|
|
|
616
569
|
/**
|
|
@@ -21,4 +21,5 @@ export const EDITOR_AREA_MAIN = "editor-area-main"
|
|
|
21
21
|
export const SIDEBAR_MAIN = "sidebar-main"
|
|
22
22
|
export const SIDEBAR_MAIN_BOTTOM = "sidebar-main-bottom"
|
|
23
23
|
export const SIDEBAR_AUXILIARY = "sidebar-auxiliary"
|
|
24
|
+
export const SIDEBAR_AUXILIARY_BOTTOM = "sidebar-auxiliary-bottom"
|
|
24
25
|
export const PANEL_BOTTOM = "panel-bottom"
|
|
@@ -60,9 +60,11 @@ export const DELETE_BUTTON: DialogButton = {
|
|
|
60
60
|
|
|
61
61
|
export interface DialogContribution extends Contribution {
|
|
62
62
|
id: string;
|
|
63
|
+
/** When true, `component` renders a complete `wa-dialog`. The service only mounts and manages lifecycle. */
|
|
64
|
+
selfContained?: boolean;
|
|
63
65
|
buttons?: DialogButton[];
|
|
64
66
|
component: (state?: any) => TemplateResult;
|
|
65
|
-
onButton
|
|
67
|
+
onButton?: (id: string, result: any, state?: any) => boolean | Promise<boolean> | void | Promise<void>;
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
let dialogContainer: HTMLElement | null = null;
|
|
@@ -76,6 +78,38 @@ function getDialogContainer(): HTMLElement {
|
|
|
76
78
|
return dialogContainer;
|
|
77
79
|
}
|
|
78
80
|
|
|
81
|
+
type WaDialogElement = HTMLElement & { open?: boolean };
|
|
82
|
+
|
|
83
|
+
function findWaDialog(root: ParentNode): WaDialogElement | null {
|
|
84
|
+
if (root instanceof Element && root.tagName.toLowerCase() === 'wa-dialog') {
|
|
85
|
+
return root as WaDialogElement;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const children = root instanceof Document || root instanceof DocumentFragment || root instanceof Element
|
|
89
|
+
? Array.from(root.children)
|
|
90
|
+
: root instanceof ShadowRoot
|
|
91
|
+
? Array.from(root.children)
|
|
92
|
+
: [];
|
|
93
|
+
|
|
94
|
+
for (const child of children) {
|
|
95
|
+
if (child instanceof Element && child.tagName.toLowerCase() === 'wa-dialog') {
|
|
96
|
+
return child as WaDialogElement;
|
|
97
|
+
}
|
|
98
|
+
if (child instanceof Element && child.shadowRoot) {
|
|
99
|
+
const nested = findWaDialog(child.shadowRoot);
|
|
100
|
+
if (nested) {
|
|
101
|
+
return nested;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const nested = findWaDialog(child);
|
|
105
|
+
if (nested) {
|
|
106
|
+
return nested;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
79
113
|
class DialogService {
|
|
80
114
|
private contributions: Map<string, DialogContribution> = new Map();
|
|
81
115
|
private contributionsChangeScheduled = false;
|
|
@@ -111,7 +145,7 @@ class DialogService {
|
|
|
111
145
|
continue;
|
|
112
146
|
}
|
|
113
147
|
|
|
114
|
-
if (!contribution.onButton) {
|
|
148
|
+
if (!contribution.onButton && !contribution.selfContained) {
|
|
115
149
|
logger.warn(`Dialog contribution "${contribution.id}" has no onButton callback, skipping`);
|
|
116
150
|
continue;
|
|
117
151
|
}
|
|
@@ -130,13 +164,13 @@ class DialogService {
|
|
|
130
164
|
|
|
131
165
|
return new Promise((resolve) => {
|
|
132
166
|
const container = getDialogContainer();
|
|
133
|
-
let
|
|
167
|
+
let closed = false;
|
|
134
168
|
let dialogContentElement: DocksDialogContent | null = null;
|
|
135
169
|
|
|
136
170
|
const cleanup = async () => {
|
|
137
|
-
if (
|
|
138
|
-
|
|
139
|
-
|
|
171
|
+
if (closed) return;
|
|
172
|
+
closed = true;
|
|
173
|
+
|
|
140
174
|
if (dialogContentElement) {
|
|
141
175
|
try {
|
|
142
176
|
await dialogContentElement.dispose();
|
|
@@ -145,50 +179,72 @@ class DialogService {
|
|
|
145
179
|
logger.error(`Error disposing dialog content for "${dialogId}": ${errorMessage}`);
|
|
146
180
|
}
|
|
147
181
|
}
|
|
148
|
-
|
|
182
|
+
|
|
149
183
|
try {
|
|
150
184
|
const result = dialogContentElement ? dialogContentElement.getResult() : undefined;
|
|
151
|
-
|
|
185
|
+
if (contribution.onButton) {
|
|
186
|
+
await contribution.onButton('close', result, stateWithClose);
|
|
187
|
+
}
|
|
152
188
|
} catch (error) {
|
|
153
189
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
154
190
|
logger.error(`Error executing close callback for dialog "${dialogId}": ${errorMessage}`);
|
|
155
191
|
}
|
|
156
|
-
|
|
192
|
+
|
|
157
193
|
render(html``, container);
|
|
158
194
|
resolve();
|
|
159
195
|
};
|
|
160
196
|
|
|
197
|
+
const closeDialog = () => {
|
|
198
|
+
if (closed) return;
|
|
199
|
+
const dialog = findWaDialog(container);
|
|
200
|
+
if (dialog && dialog.open !== false) {
|
|
201
|
+
dialog.open = false;
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
void cleanup();
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const handleAfterHide = () => {
|
|
208
|
+
void cleanup();
|
|
209
|
+
};
|
|
210
|
+
|
|
161
211
|
const handleButtonClick = async (buttonId: string) => {
|
|
212
|
+
if (!contribution.onButton) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
162
215
|
try {
|
|
163
216
|
const result = dialogContentElement ? dialogContentElement.getResult() : undefined;
|
|
164
217
|
const shouldClose = await contribution.onButton(buttonId, result, stateWithClose);
|
|
165
|
-
|
|
218
|
+
|
|
166
219
|
if (shouldClose !== false) {
|
|
167
|
-
|
|
220
|
+
closeDialog();
|
|
168
221
|
}
|
|
169
222
|
} catch (error) {
|
|
170
223
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
171
224
|
logger.error(`Error executing button callback for dialog "${dialogId}": ${errorMessage}`);
|
|
172
|
-
|
|
225
|
+
closeDialog();
|
|
173
226
|
}
|
|
174
227
|
};
|
|
175
228
|
|
|
176
|
-
const buttons = contribution.buttons && contribution.buttons.length > 0
|
|
177
|
-
? contribution.buttons
|
|
229
|
+
const buttons = contribution.buttons && contribution.buttons.length > 0
|
|
230
|
+
? contribution.buttons
|
|
178
231
|
: [OK_BUTTON];
|
|
179
232
|
|
|
233
|
+
const stateWithClose = { ...state, close: closeDialog, onAfterHide: handleAfterHide };
|
|
180
234
|
if (state && typeof state === 'object') {
|
|
181
|
-
(state as
|
|
235
|
+
(state as { close?: () => void }).close = closeDialog;
|
|
236
|
+
(state as { onAfterHide?: () => void }).onAfterHide = handleAfterHide;
|
|
182
237
|
}
|
|
183
|
-
const stateWithClose = { ...state, close: cleanup };
|
|
184
238
|
|
|
185
239
|
const dialogLabel =
|
|
186
240
|
state && typeof state === 'object' && typeof (state as { label?: unknown }).label === 'string'
|
|
187
241
|
? (state as { label: string }).label
|
|
188
242
|
: (contribution.label || dialogId);
|
|
189
243
|
|
|
190
|
-
const template =
|
|
191
|
-
|
|
244
|
+
const template = contribution.selfContained
|
|
245
|
+
? contribution.component(stateWithClose)
|
|
246
|
+
: html`
|
|
247
|
+
<wa-dialog label="${dialogLabel}" open @wa-after-hide=${handleAfterHide}>
|
|
192
248
|
<style>
|
|
193
249
|
.dialog-service-content {
|
|
194
250
|
display: flex;
|
|
@@ -224,7 +280,7 @@ class DialogService {
|
|
|
224
280
|
if (cancelButton) {
|
|
225
281
|
handleButtonClick(cancelButton.id);
|
|
226
282
|
} else {
|
|
227
|
-
|
|
283
|
+
closeDialog();
|
|
228
284
|
}
|
|
229
285
|
}}>
|
|
230
286
|
${contribution.component(state)}
|
|
@@ -232,6 +288,7 @@ class DialogService {
|
|
|
232
288
|
<div class="dialog-service-footer">
|
|
233
289
|
${buttons.map(button => html`
|
|
234
290
|
<wa-button
|
|
291
|
+
data-dialog-button="${button.id}"
|
|
235
292
|
variant="${button.variant || 'default'}"
|
|
236
293
|
?disabled=${button.disabled}
|
|
237
294
|
@click=${() => handleButtonClick(button.id)}
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
* A part can expose both surfaces with the same part id but different prefixes, registering
|
|
12
12
|
* different contributions on each. Feature-scoped targets (e.g. `filebrowser.create`) may also
|
|
13
13
|
* be referenced explicitly from either surface when several controls share the same item set.
|
|
14
|
+
*
|
|
15
|
+
* Command-backed items rely on the parent {@link https://webawesome.com/docs/components/dropdown | wa-dropdown}
|
|
16
|
+
* `wa-select` event (see {@link handleDropdownWaSelect}) so the popup closes cleanly before
|
|
17
|
+
* native dialogs such as `showDirectoryPicker` open.
|
|
14
18
|
*/
|
|
15
19
|
import { html, nothing, type TemplateResult } from 'lit';
|
|
16
20
|
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
|
@@ -41,28 +45,51 @@ export interface DropdownItemOptions {
|
|
|
41
45
|
action?: (event: Event) => void;
|
|
42
46
|
}
|
|
43
47
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
type WaSelectDetail = {
|
|
49
|
+
item?: HTMLElement & { value?: string; disabled?: boolean };
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
function parseDropdownItemParams(item: HTMLElement): Record<string, unknown> {
|
|
53
|
+
const raw = item.getAttribute('data-params');
|
|
54
|
+
if (!raw) {
|
|
55
|
+
return {};
|
|
48
56
|
}
|
|
57
|
+
try {
|
|
58
|
+
return JSON.parse(raw) as Record<string, unknown>;
|
|
59
|
+
} catch {
|
|
60
|
+
return {};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getDropdownItemCommand(item: HTMLElement): string | undefined {
|
|
65
|
+
return item.getAttribute('data-cmd') || item.getAttribute('value') || undefined;
|
|
49
66
|
}
|
|
50
67
|
|
|
51
|
-
|
|
68
|
+
/** Handles `wa-select` from a parent `wa-dropdown` and runs the backing command. */
|
|
69
|
+
export function handleDropdownWaSelect(event: CustomEvent<WaSelectDetail>): void {
|
|
70
|
+
const item = event.detail?.item;
|
|
71
|
+
if (!item || item.disabled || item.hasAttribute('disabled')) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const cmd = getDropdownItemCommand(item);
|
|
76
|
+
if (!cmd) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
void commandRegistry.execute(
|
|
81
|
+
cmd,
|
|
82
|
+
commandRegistry.createExecutionContext(parseDropdownItemParams(item)),
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function onDropdownActionClick(options: DropdownItemOptions) {
|
|
52
87
|
return (event: Event) => {
|
|
53
|
-
if (readReactiveBoolean(options.disabled, false))
|
|
54
|
-
event.stopPropagation();
|
|
55
|
-
closeParentDropdown(event);
|
|
56
|
-
if (options.action) {
|
|
57
|
-
options.action(event);
|
|
88
|
+
if (readReactiveBoolean(options.disabled, false)) {
|
|
58
89
|
return;
|
|
59
90
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
options.cmd,
|
|
63
|
-
commandRegistry.createExecutionContext(options.params ?? {})
|
|
64
|
-
);
|
|
65
|
-
}
|
|
91
|
+
event.stopPropagation();
|
|
92
|
+
options.action?.(event);
|
|
66
93
|
};
|
|
67
94
|
}
|
|
68
95
|
|
|
@@ -76,12 +103,20 @@ function keybindingDetails(cmd?: string): TemplateResult | typeof nothing {
|
|
|
76
103
|
/** Renders a native {@link https://webawesome.com/docs/components/dropdown-item | wa-dropdown-item}. */
|
|
77
104
|
export function renderDropdownItem(options: DropdownItemOptions): TemplateResult {
|
|
78
105
|
const iconSpec = options.icon ?? '';
|
|
106
|
+
const paramsJson =
|
|
107
|
+
options.params && Object.keys(options.params).length > 0
|
|
108
|
+
? JSON.stringify(options.params)
|
|
109
|
+
: undefined;
|
|
110
|
+
|
|
79
111
|
return html`
|
|
80
112
|
<wa-dropdown-item
|
|
81
113
|
slot=${options.slot ?? nothing}
|
|
82
114
|
variant=${options.variant ?? 'default'}
|
|
115
|
+
value=${options.cmd ?? nothing}
|
|
116
|
+
data-cmd=${options.cmd ?? nothing}
|
|
117
|
+
data-params=${paramsJson ?? nothing}
|
|
83
118
|
?disabled=${readReactiveBoolean(options.disabled, false)}
|
|
84
|
-
@click=${
|
|
119
|
+
@click=${options.action ? onDropdownActionClick(options) : nothing}>
|
|
85
120
|
${iconSpec ? icon(iconSpec, { label: options.title ?? options.label, slot: 'icon' }) : nothing}
|
|
86
121
|
${options.label}
|
|
87
122
|
${keybindingDetails(options.cmd)}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { LayoutDescriptor } from './apploader';
|
|
2
|
+
import { appSettings } from './settingsservice';
|
|
3
|
+
import type { DocksStandardLayout } from '../layouts/standard-layout';
|
|
4
|
+
|
|
5
|
+
export type LayoutPanelVisibility = {
|
|
6
|
+
showLeftSidebar: boolean;
|
|
7
|
+
showAuxSidebar: boolean;
|
|
8
|
+
showBottomPanel: boolean;
|
|
9
|
+
showLeftAux: boolean;
|
|
10
|
+
showRightAux: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const DEFAULT_LAYOUT_PANEL_VISIBILITY: LayoutPanelVisibility = {
|
|
14
|
+
showLeftSidebar: true,
|
|
15
|
+
showAuxSidebar: true,
|
|
16
|
+
showBottomPanel: true,
|
|
17
|
+
showLeftAux: false,
|
|
18
|
+
showRightAux: false,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function normalizePanelVisibility(stored: Partial<LayoutPanelVisibility>): LayoutPanelVisibility {
|
|
22
|
+
return {
|
|
23
|
+
showLeftSidebar: stored.showLeftSidebar ?? DEFAULT_LAYOUT_PANEL_VISIBILITY.showLeftSidebar,
|
|
24
|
+
showAuxSidebar: stored.showAuxSidebar ?? DEFAULT_LAYOUT_PANEL_VISIBILITY.showAuxSidebar,
|
|
25
|
+
showBottomPanel: stored.showBottomPanel ?? DEFAULT_LAYOUT_PANEL_VISIBILITY.showBottomPanel,
|
|
26
|
+
showLeftAux: stored.showLeftAux ?? DEFAULT_LAYOUT_PANEL_VISIBILITY.showLeftAux,
|
|
27
|
+
showRightAux: stored.showRightAux ?? DEFAULT_LAYOUT_PANEL_VISIBILITY.showRightAux,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const LAYOUT_PANEL_SETTINGS_KEY = 'layoutPanelVisibility';
|
|
32
|
+
|
|
33
|
+
/** @deprecated Legacy layout id persisted before panel visibility toggles. */
|
|
34
|
+
export const LEGACY_PREFERRED_LAYOUT_KEY = 'preferredLayoutId';
|
|
35
|
+
|
|
36
|
+
/** Maps legacy parameterized layout ids to panel visibility. */
|
|
37
|
+
export function layoutIdToPanelVisibility(layoutId: string): LayoutPanelVisibility {
|
|
38
|
+
switch (layoutId) {
|
|
39
|
+
case 'standard-bottom-panel':
|
|
40
|
+
return { ...DEFAULT_LAYOUT_PANEL_VISIBILITY, showBottomPanel: true, showLeftAux: false, showRightAux: false };
|
|
41
|
+
case 'standard-bottom-sidebar':
|
|
42
|
+
return { ...DEFAULT_LAYOUT_PANEL_VISIBILITY, showBottomPanel: false, showLeftAux: true, showRightAux: false };
|
|
43
|
+
case 'standard-full':
|
|
44
|
+
return {
|
|
45
|
+
...DEFAULT_LAYOUT_PANEL_VISIBILITY,
|
|
46
|
+
showLeftAux: true,
|
|
47
|
+
showRightAux: true,
|
|
48
|
+
};
|
|
49
|
+
case 'standard':
|
|
50
|
+
default:
|
|
51
|
+
return {
|
|
52
|
+
...DEFAULT_LAYOUT_PANEL_VISIBILITY,
|
|
53
|
+
showBottomPanel: false,
|
|
54
|
+
showLeftAux: false,
|
|
55
|
+
showRightAux: false,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function resolveInitialPanelVisibilityFromApp(appLayout?: LayoutDescriptor): LayoutPanelVisibility {
|
|
61
|
+
if (typeof appLayout === 'object' && appLayout.props) {
|
|
62
|
+
return {
|
|
63
|
+
...DEFAULT_LAYOUT_PANEL_VISIBILITY,
|
|
64
|
+
showLeftSidebar: appLayout.props.showLeftSidebar !== false,
|
|
65
|
+
showAuxSidebar: appLayout.props.showAuxSidebar !== false,
|
|
66
|
+
showBottomPanel: appLayout.props.showBottomPanel === true,
|
|
67
|
+
showLeftAux: appLayout.props.showLeftAux === true,
|
|
68
|
+
showRightAux: appLayout.props.showRightAux === true,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (typeof appLayout === 'string') {
|
|
72
|
+
return layoutIdToPanelVisibility(appLayout);
|
|
73
|
+
}
|
|
74
|
+
return { ...DEFAULT_LAYOUT_PANEL_VISIBILITY };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function resolveStoredPanelVisibility(
|
|
78
|
+
appLayout?: LayoutDescriptor,
|
|
79
|
+
): Promise<LayoutPanelVisibility> {
|
|
80
|
+
try {
|
|
81
|
+
const saved = await appSettings.get(LAYOUT_PANEL_SETTINGS_KEY) as Partial<LayoutPanelVisibility> | undefined;
|
|
82
|
+
if (saved && typeof saved === 'object') {
|
|
83
|
+
return normalizePanelVisibility(saved);
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
// fall through to legacy/app defaults
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const legacyLayoutId = await appSettings.get(LEGACY_PREFERRED_LAYOUT_KEY) as string | undefined;
|
|
91
|
+
if (legacyLayoutId) {
|
|
92
|
+
return layoutIdToPanelVisibility(legacyLayoutId);
|
|
93
|
+
}
|
|
94
|
+
} catch {
|
|
95
|
+
// fall through to app defaults
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return resolveInitialPanelVisibilityFromApp(appLayout);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function findStandardLayout(container?: ParentNode | null): DocksStandardLayout | null {
|
|
102
|
+
const root = container ?? document.getElementById('app-root') ?? document.body;
|
|
103
|
+
if (!root || typeof root.querySelector !== 'function') {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
return root.querySelector('docks-standard-layout');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export type LayoutPanelOption = {
|
|
110
|
+
key: keyof LayoutPanelVisibility;
|
|
111
|
+
label: string;
|
|
112
|
+
icon: string;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export const LAYOUT_PANEL_OPTIONS: LayoutPanelOption[] = [
|
|
116
|
+
{ key: 'showLeftSidebar', label: 'Left sidebar', icon: 'docks layout-standard-left-sidebar' },
|
|
117
|
+
{ key: 'showAuxSidebar', label: 'Right sidebar', icon: 'docks layout-standard-right-sidebar' },
|
|
118
|
+
{ key: 'showBottomPanel', label: 'Bottom panel', icon: 'docks layout-standard-bottom-panel' },
|
|
119
|
+
{ key: 'showLeftAux', label: 'Left Aux', icon: 'docks layout-standard-left-aux' },
|
|
120
|
+
{ key: 'showRightAux', label: 'Right Aux', icon: 'docks layout-standard-right-aux' },
|
|
121
|
+
];
|
|
@@ -85,10 +85,13 @@ export class DocksNavigableInfoDialogContent extends DocksDialogContent {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
private handleClose() {
|
|
88
|
-
|
|
89
|
-
if (dialog && this.resolveCallback) {
|
|
88
|
+
if (this.resolveCallback) {
|
|
90
89
|
this.resolveCallback();
|
|
91
90
|
}
|
|
91
|
+
const dialog = this.closest('wa-dialog') as (HTMLElement & { open?: boolean }) | null;
|
|
92
|
+
if (dialog && dialog.open !== false) {
|
|
93
|
+
dialog.open = false;
|
|
94
|
+
}
|
|
92
95
|
}
|
|
93
96
|
|
|
94
97
|
static styles = [
|