@loomweaver/shell 0.7.5 → 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.
- package/fesm2022/loomweaver-shell.mjs +1049 -857
- package/fesm2022/loomweaver-shell.mjs.map +1 -1
- package/i18n/de.json +3 -3
- package/i18n/en.json +2 -2
- package/package.json +2 -2
- package/types/loomweaver-shell.d.ts +3 -7
- package/types/loomweaver-shell.d.ts.map +1 -1
|
@@ -65,8 +65,15 @@ function withoutQuery(url) {
|
|
|
65
65
|
const end = url.search(/[?#]/);
|
|
66
66
|
return end === -1 ? url : url.slice(0, end);
|
|
67
67
|
}
|
|
68
|
+
function withoutTrailingSlashes$1(path) {
|
|
69
|
+
let end = path.length;
|
|
70
|
+
while (end > 0 && path[end - 1] === '/') {
|
|
71
|
+
end -= 1;
|
|
72
|
+
}
|
|
73
|
+
return path.slice(0, end);
|
|
74
|
+
}
|
|
68
75
|
function bare(url) {
|
|
69
|
-
return withoutQuery(url).replace(/^\/+/, '')
|
|
76
|
+
return withoutTrailingSlashes$1(withoutQuery(url).replace(/^\/+/, ''));
|
|
70
77
|
}
|
|
71
78
|
function isPopoutUrl(url) {
|
|
72
79
|
const path = bare(url);
|
|
@@ -94,7 +101,7 @@ function popoutUrlFor(paneTarget) {
|
|
|
94
101
|
}
|
|
95
102
|
|
|
96
103
|
function upsertBy(items, item, matches) {
|
|
97
|
-
const index = items.findIndex(matches);
|
|
104
|
+
const index = items.findIndex((existing) => matches(existing));
|
|
98
105
|
if (index === -1) {
|
|
99
106
|
return [...items, item];
|
|
100
107
|
}
|
|
@@ -310,7 +317,7 @@ class ContributionRegistry {
|
|
|
310
317
|
const visible = omitted.size === 0
|
|
311
318
|
? docked
|
|
312
319
|
: docked.filter((entry) => entry.id === undefined || !omitted.has(entry.id));
|
|
313
|
-
return visible.map(entryToView);
|
|
320
|
+
return visible.map((entry) => entryToView(entry));
|
|
314
321
|
}, /* @ts-ignore */
|
|
315
322
|
...(ngDevMode ? [{ debugName: "views" }] : /* istanbul ignore next */ []));
|
|
316
323
|
barItems = this.visible(this.barItemsSignal);
|
|
@@ -321,7 +328,7 @@ class ContributionRegistry {
|
|
|
321
328
|
* picker, `matchRoute` — drops them without knowing about omission.
|
|
322
329
|
*/
|
|
323
330
|
contentRoutes = computed(() => {
|
|
324
|
-
const routes = this.routableSurfaces().map(entryToContentRoute);
|
|
331
|
+
const routes = this.routableSurfaces().map((entry) => entryToContentRoute(entry));
|
|
325
332
|
const omitted = this.omittedSignal();
|
|
326
333
|
if (omitted.size === 0) {
|
|
327
334
|
return routes;
|
|
@@ -340,7 +347,7 @@ class ContributionRegistry {
|
|
|
340
347
|
return NO_ROUTES;
|
|
341
348
|
}
|
|
342
349
|
return this.routableSurfaces()
|
|
343
|
-
.map(entryToContentRoute)
|
|
350
|
+
.map((entry) => entryToContentRoute(entry))
|
|
344
351
|
.filter((route) => isRouteOmitted(route, omitted));
|
|
345
352
|
}, /* @ts-ignore */
|
|
346
353
|
...(ngDevMode ? [{ debugName: "omittedContentRoutes" }] : /* istanbul ignore next */ []));
|
|
@@ -436,13 +443,13 @@ class ContributionRegistry {
|
|
|
436
443
|
}
|
|
437
444
|
/** Adds a menu-slot item. With an `id` a re-registration replaces in place (last-in wins); without one the item is additive and dispose removes this exact contribution. */
|
|
438
445
|
addMenuItem(item) {
|
|
439
|
-
this.menuItemsSignal.update((items) => upsertBy(items, item, (
|
|
446
|
+
this.menuItemsSignal.update((items) => upsertBy(items, item, (index) => item.id !== undefined && index.id === item.id));
|
|
440
447
|
return {
|
|
441
|
-
dispose: () => this.menuItemsSignal.update((items) => items.filter((
|
|
448
|
+
dispose: () => this.menuItemsSignal.update((items) => items.filter((index) => index !== item)),
|
|
442
449
|
};
|
|
443
450
|
}
|
|
444
451
|
removeMenuItemById(id) {
|
|
445
|
-
this.menuItemsSignal.update((items) => items.filter((
|
|
452
|
+
this.menuItemsSignal.update((items) => items.filter((index) => index.id !== id));
|
|
446
453
|
}
|
|
447
454
|
addSurface(entry) {
|
|
448
455
|
this.surfacesSignal.update((entries) => upsertBy(entries, entry, sameSlotAs(entry)));
|
|
@@ -462,11 +469,11 @@ class ContributionRegistry {
|
|
|
462
469
|
add(target, item) {
|
|
463
470
|
target.update((items) => upsertById(items, item));
|
|
464
471
|
return {
|
|
465
|
-
dispose: () => target.update((items) => items.filter((
|
|
472
|
+
dispose: () => target.update((items) => items.filter((index) => index !== item)),
|
|
466
473
|
};
|
|
467
474
|
}
|
|
468
475
|
removeById(target, id) {
|
|
469
|
-
target.update((items) => items.filter((
|
|
476
|
+
target.update((items) => items.filter((index) => index.id !== id));
|
|
470
477
|
}
|
|
471
478
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ContributionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Service });
|
|
472
479
|
static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.1.0", ngImport: i0, type: ContributionRegistry });
|
|
@@ -485,7 +492,7 @@ function sameSlotAs(entry) {
|
|
|
485
492
|
}
|
|
486
493
|
|
|
487
494
|
function normalizePath(url) {
|
|
488
|
-
return url.split(/[?#]
|
|
495
|
+
return url.split(/[?#]/, 1)[0].replace(/^\/+/, '');
|
|
489
496
|
}
|
|
490
497
|
function segmentsOf(path) {
|
|
491
498
|
return normalizePath(path).split('/').filter(Boolean);
|
|
@@ -538,11 +545,11 @@ function paramsOfPattern(pattern, path) {
|
|
|
538
545
|
const parts = segmentsOf(pattern);
|
|
539
546
|
const segments = segmentsOf(path);
|
|
540
547
|
const params = {};
|
|
541
|
-
parts.
|
|
548
|
+
for (const [index, part] of parts.entries()) {
|
|
542
549
|
if (part.startsWith(':') && segments[index] !== undefined) {
|
|
543
550
|
params[part.slice(1)] = segments[index];
|
|
544
551
|
}
|
|
545
|
-
}
|
|
552
|
+
}
|
|
546
553
|
return params;
|
|
547
554
|
}
|
|
548
555
|
function routeParams(route, path) {
|
|
@@ -647,7 +654,7 @@ function reusableRoute(route) {
|
|
|
647
654
|
return route.routeConfig?.data?.['chromeless'] !== true;
|
|
648
655
|
}
|
|
649
656
|
function effectiveRetain(declared, fallback) {
|
|
650
|
-
return declared
|
|
657
|
+
return declared === undefined ? fallback === 'retain' : declared === 'always';
|
|
651
658
|
}
|
|
652
659
|
function routeRetains(route, fallback) {
|
|
653
660
|
if (route.container !== undefined) {
|
|
@@ -672,7 +679,7 @@ function surfaceRetentionMode(routes, path) {
|
|
|
672
679
|
if (!route || route.container !== undefined) {
|
|
673
680
|
return 'rebuild';
|
|
674
681
|
}
|
|
675
|
-
return route.iframe
|
|
682
|
+
return route.iframe === undefined ? 'move' : 'in-place';
|
|
676
683
|
}
|
|
677
684
|
function containerChildInstances(entries, tabPath) {
|
|
678
685
|
const scoped = containerDockFor(tabPath) + ':';
|
|
@@ -732,11 +739,11 @@ class ContentReuseStrategy {
|
|
|
732
739
|
changes = signal(0, /* @ts-ignore */
|
|
733
740
|
...(ngDevMode ? [{ debugName: "changes" }] : /* istanbul ignore next */ []));
|
|
734
741
|
version = this.changes.asReadonly();
|
|
735
|
-
shouldReuseRoute(future,
|
|
736
|
-
if (!isContentRoute(future) || !isContentRoute(
|
|
737
|
-
return future.routeConfig ===
|
|
742
|
+
shouldReuseRoute(future, current) {
|
|
743
|
+
if (!isContentRoute(future) || !isContentRoute(current)) {
|
|
744
|
+
return future.routeConfig === current.routeConfig;
|
|
738
745
|
}
|
|
739
|
-
return future.routeConfig ===
|
|
746
|
+
return future.routeConfig === current.routeConfig && sameParams(future, current);
|
|
740
747
|
}
|
|
741
748
|
shouldDetach(route) {
|
|
742
749
|
return reusableRoute(route);
|
|
@@ -782,11 +789,12 @@ class ContentReuseStrategy {
|
|
|
782
789
|
}
|
|
783
790
|
pruneExcept(isLive) {
|
|
784
791
|
for (const [key, handle] of this.handles) {
|
|
785
|
-
if (
|
|
786
|
-
|
|
787
|
-
this.handles.delete(key);
|
|
788
|
-
this.changes.update((value) => value + 1);
|
|
792
|
+
if (isLive(key)) {
|
|
793
|
+
continue;
|
|
789
794
|
}
|
|
795
|
+
handle.componentRef?.destroy();
|
|
796
|
+
this.handles.delete(key);
|
|
797
|
+
this.changes.update((value) => value + 1);
|
|
790
798
|
}
|
|
791
799
|
}
|
|
792
800
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ContentReuseStrategy, deps: [], target: i0.ɵɵFactoryTarget.Service });
|
|
@@ -884,7 +892,7 @@ class StateSyncChannel {
|
|
|
884
892
|
}
|
|
885
893
|
try {
|
|
886
894
|
const opened = new BroadcastChannel(CHANNEL_NAME);
|
|
887
|
-
opened.
|
|
895
|
+
opened.addEventListener('message', (event) => this.receive(event.data));
|
|
888
896
|
return opened;
|
|
889
897
|
}
|
|
890
898
|
catch {
|
|
@@ -1024,7 +1032,7 @@ function cleanArea(area, options, problems, depth) {
|
|
|
1024
1032
|
problems.push(`${options.context}: the arrangement nests deeper than ${MAX_DEPTH} levels — the deeper areas are dropped.`);
|
|
1025
1033
|
return null;
|
|
1026
1034
|
}
|
|
1027
|
-
const kinds = ['tabs', 'rows', 'columns'].filter((key) => key
|
|
1035
|
+
const kinds = ['tabs', 'rows', 'columns'].filter((key) => Object.hasOwn(area, key));
|
|
1028
1036
|
if (kinds.length !== 1) {
|
|
1029
1037
|
problems.push(`${options.context}: a pane area must be exactly one of tabs, rows or columns.`);
|
|
1030
1038
|
return null;
|
|
@@ -1093,7 +1101,7 @@ function cleanTabsArea(area, size, options, problems) {
|
|
|
1093
1101
|
function collectTabs$1(area) {
|
|
1094
1102
|
return area.kind === 'tabs'
|
|
1095
1103
|
? [...area.tabs]
|
|
1096
|
-
: area.children.flatMap(collectTabs$1);
|
|
1104
|
+
: area.children.flatMap((child) => collectTabs$1(child));
|
|
1097
1105
|
}
|
|
1098
1106
|
function buildNode(area, idPrefix, ctx, indexPath) {
|
|
1099
1107
|
if (area.kind === 'tabs') {
|
|
@@ -1122,6 +1130,12 @@ function buildNode(area, idPrefix, ctx, indexPath) {
|
|
|
1122
1130
|
};
|
|
1123
1131
|
return chain(0, 1);
|
|
1124
1132
|
}
|
|
1133
|
+
function evenShareOf(remainder, declaredSum, children, unspecified) {
|
|
1134
|
+
if (unspecified === 0) {
|
|
1135
|
+
return 0;
|
|
1136
|
+
}
|
|
1137
|
+
return remainder > 0 ? remainder / unspecified : declaredSum / children;
|
|
1138
|
+
}
|
|
1125
1139
|
function fractionsOf(children) {
|
|
1126
1140
|
const declared = children
|
|
1127
1141
|
.map((child) => child.size)
|
|
@@ -1129,11 +1143,7 @@ function fractionsOf(children) {
|
|
|
1129
1143
|
const declaredSum = declared.reduce((sum, size) => sum + size, 0);
|
|
1130
1144
|
const unspecified = children.length - declared.length;
|
|
1131
1145
|
const remainder = Math.max(0, 100 - declaredSum);
|
|
1132
|
-
const evenShare =
|
|
1133
|
-
? remainder > 0
|
|
1134
|
-
? remainder / unspecified
|
|
1135
|
-
: declaredSum / children.length
|
|
1136
|
-
: 0;
|
|
1146
|
+
const evenShare = evenShareOf(remainder, declaredSum, children.length, unspecified);
|
|
1137
1147
|
const weights = children.map((child) => child.size ?? evenShare);
|
|
1138
1148
|
const total = weights.reduce((sum, weight) => sum + weight, 0);
|
|
1139
1149
|
return weights.map((weight) => weight / total);
|
|
@@ -1168,7 +1178,12 @@ function claimFor(claims, path) {
|
|
|
1168
1178
|
if (matching.length === 0) {
|
|
1169
1179
|
return null;
|
|
1170
1180
|
}
|
|
1171
|
-
|
|
1181
|
+
let best = matching[0];
|
|
1182
|
+
for (const claim of matching) {
|
|
1183
|
+
if (narrower(claim.pattern, best.pattern) > 0) {
|
|
1184
|
+
best = claim;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1172
1187
|
const tied = matching.filter((claim) => claim.workspaceId !== best.workspaceId &&
|
|
1173
1188
|
narrower(claim.pattern, best.pattern) === 0);
|
|
1174
1189
|
return tied.length === 0 ? best : null;
|
|
@@ -1186,9 +1201,12 @@ function contestedShapes(claims) {
|
|
|
1186
1201
|
return new Map([...byShape].filter(([, owners]) => owners.length > 1));
|
|
1187
1202
|
}
|
|
1188
1203
|
function conflictingClaims(claims) {
|
|
1189
|
-
return [...contestedShapes(claims)].map(([shape, owners]) =>
|
|
1190
|
-
|
|
1191
|
-
`
|
|
1204
|
+
return [...contestedShapes(claims)].map(([shape, owners]) => {
|
|
1205
|
+
const named = owners.map((id) => `"${id}"`).join(' and ');
|
|
1206
|
+
return (`Workspaces ${named} both claim "${shape}" — ` +
|
|
1207
|
+
`neither is narrower than the other, so the claim is dropped and that address ` +
|
|
1208
|
+
`behaves as though nothing claimed it. Give the address one home.`);
|
|
1209
|
+
});
|
|
1192
1210
|
}
|
|
1193
1211
|
function withoutConflicts(claims) {
|
|
1194
1212
|
const contested = contestedShapes(claims);
|
|
@@ -1230,8 +1248,8 @@ function leafWith(id, tabs, candidateActive, declared) {
|
|
|
1230
1248
|
kind: 'leaf',
|
|
1231
1249
|
id,
|
|
1232
1250
|
tabs,
|
|
1233
|
-
...(active
|
|
1234
|
-
...(declared
|
|
1251
|
+
...(active !== undefined && { active }),
|
|
1252
|
+
...(declared && { declared: true }),
|
|
1235
1253
|
};
|
|
1236
1254
|
}
|
|
1237
1255
|
|
|
@@ -1324,15 +1342,13 @@ function normalizeTab(value) {
|
|
|
1324
1342
|
}
|
|
1325
1343
|
return {
|
|
1326
1344
|
path: tab['path'],
|
|
1327
|
-
...(tab['pinned'] === true
|
|
1328
|
-
...(tab['preview'] === true
|
|
1329
|
-
...(tab['closable'] === false
|
|
1330
|
-
...(typeof tab['title'] === 'string'
|
|
1331
|
-
...(tab['literalTitle'] === true
|
|
1332
|
-
...(typeof tab['icon'] === 'string'
|
|
1333
|
-
...(typeof tab['instance'] === 'string'
|
|
1334
|
-
? { instance: tab['instance'] }
|
|
1335
|
-
: {}),
|
|
1345
|
+
...(tab['pinned'] === true && { pinned: true }),
|
|
1346
|
+
...(tab['preview'] === true && { preview: true }),
|
|
1347
|
+
...(tab['closable'] === false && { closable: false }),
|
|
1348
|
+
...(typeof tab['title'] === 'string' && { title: tab['title'] }),
|
|
1349
|
+
...(tab['literalTitle'] === true && { literalTitle: true }),
|
|
1350
|
+
...(typeof tab['icon'] === 'string' && { icon: tab['icon'] }),
|
|
1351
|
+
...(typeof tab['instance'] === 'string' && { instance: tab['instance'] }),
|
|
1336
1352
|
};
|
|
1337
1353
|
}
|
|
1338
1354
|
function normalizeLeafNode(id, node) {
|
|
@@ -1342,7 +1358,7 @@ function normalizeLeafNode(id, node) {
|
|
|
1342
1358
|
}
|
|
1343
1359
|
const rawTabs = node['tabs'];
|
|
1344
1360
|
const tabs = Array.isArray(rawTabs)
|
|
1345
|
-
? rawTabs.map(normalizeTab).filter((tab) => tab !== null)
|
|
1361
|
+
? rawTabs.map((value) => normalizeTab(value)).filter((tab) => tab !== null)
|
|
1346
1362
|
: [];
|
|
1347
1363
|
const rawActive = node['active'];
|
|
1348
1364
|
return leafWith(id, tabs, typeof rawActive === 'string' ? rawActive : undefined, node['declared'] === true);
|
|
@@ -1415,11 +1431,11 @@ function auditWorkspaceDefinitions(definitions, panelRegions) {
|
|
|
1415
1431
|
}
|
|
1416
1432
|
seen.add(definition.id);
|
|
1417
1433
|
if (definition.initial) {
|
|
1418
|
-
if (initial
|
|
1419
|
-
|
|
1434
|
+
if (initial === null) {
|
|
1435
|
+
initial = definition.id;
|
|
1420
1436
|
}
|
|
1421
1437
|
else {
|
|
1422
|
-
|
|
1438
|
+
problems.push(`Workspace "${definition.id}" also declares initial: true — "${initial}" already does, so this one is ignored.`);
|
|
1423
1439
|
}
|
|
1424
1440
|
}
|
|
1425
1441
|
auditDefinition(definition, panelRegions, problems);
|
|
@@ -1477,13 +1493,13 @@ function baselineSidebars(definition, deps) {
|
|
|
1477
1493
|
if (sidebars === undefined) {
|
|
1478
1494
|
return {};
|
|
1479
1495
|
}
|
|
1480
|
-
const listed = deps.panelRegions.filter((region) => region
|
|
1496
|
+
const listed = deps.panelRegions.filter((region) => Object.hasOwn(sidebars, region));
|
|
1481
1497
|
const hidden = listed
|
|
1482
1498
|
.flatMap((region) => deps
|
|
1483
1499
|
.declaredPaths(region)
|
|
1484
1500
|
.map((path) => path.slice(VIEW_PANE_PREFIX.length))
|
|
1485
1501
|
.filter((id) => !sidebars[region].includes(id)))
|
|
1486
|
-
.
|
|
1502
|
+
.toSorted((a, b) => a.localeCompare(b));
|
|
1487
1503
|
return hidden.length === 0 ? {} : { hiddenViews: JSON.stringify(hidden) };
|
|
1488
1504
|
}
|
|
1489
1505
|
function declaredTabPaths(definition) {
|
|
@@ -1511,7 +1527,7 @@ function bakeTab(definitionId, entry, problems) {
|
|
|
1511
1527
|
return {
|
|
1512
1528
|
tab: {
|
|
1513
1529
|
path: tab.path,
|
|
1514
|
-
...(tab.closable === false
|
|
1530
|
+
...(tab.closable === false && { closable: false }),
|
|
1515
1531
|
},
|
|
1516
1532
|
active: tab.active === true,
|
|
1517
1533
|
};
|
|
@@ -1592,9 +1608,16 @@ function movable(parent) {
|
|
|
1592
1608
|
function supportsAtomicMove(document) {
|
|
1593
1609
|
return movable(document.body ?? document.documentElement);
|
|
1594
1610
|
}
|
|
1611
|
+
function insertNode(parent, node, before) {
|
|
1612
|
+
if (before === null) {
|
|
1613
|
+
parent.append(node);
|
|
1614
|
+
return;
|
|
1615
|
+
}
|
|
1616
|
+
before.before(node);
|
|
1617
|
+
}
|
|
1595
1618
|
function moveNode(parent, node, before) {
|
|
1596
1619
|
if (!movable(parent) || !node.isConnected) {
|
|
1597
|
-
parent
|
|
1620
|
+
insertNode(parent, node, before);
|
|
1598
1621
|
return false;
|
|
1599
1622
|
}
|
|
1600
1623
|
try {
|
|
@@ -1602,7 +1625,7 @@ function moveNode(parent, node, before) {
|
|
|
1602
1625
|
return true;
|
|
1603
1626
|
}
|
|
1604
1627
|
catch {
|
|
1605
|
-
parent
|
|
1628
|
+
insertNode(parent, node, before);
|
|
1606
1629
|
return false;
|
|
1607
1630
|
}
|
|
1608
1631
|
}
|
|
@@ -1652,10 +1675,11 @@ class RetainedViewStash {
|
|
|
1652
1675
|
},
|
|
1653
1676
|
stale: () => entry.tracked && (!owns() || this.entries.get(key) !== entry),
|
|
1654
1677
|
describe: (mode, retain) => {
|
|
1655
|
-
if (owns()) {
|
|
1656
|
-
|
|
1657
|
-
entry.keep = retain;
|
|
1678
|
+
if (!owns()) {
|
|
1679
|
+
return;
|
|
1658
1680
|
}
|
|
1681
|
+
entry.mode = mode;
|
|
1682
|
+
entry.keep = retain;
|
|
1659
1683
|
},
|
|
1660
1684
|
release: (retained) => {
|
|
1661
1685
|
if (owns()) {
|
|
@@ -1722,14 +1746,16 @@ class RetainedViewStash {
|
|
|
1722
1746
|
}
|
|
1723
1747
|
}
|
|
1724
1748
|
evictWorkspace(workspaceId) {
|
|
1725
|
-
|
|
1749
|
+
const snapshot = [...this.entries.values()];
|
|
1750
|
+
for (const entry of snapshot) {
|
|
1726
1751
|
if (!entry.inUse && entry.workspace === workspaceId) {
|
|
1727
1752
|
this.destroyEntry(entry);
|
|
1728
1753
|
}
|
|
1729
1754
|
}
|
|
1730
1755
|
}
|
|
1731
1756
|
ngOnDestroy() {
|
|
1732
|
-
|
|
1757
|
+
const snapshot = [...this.entries.values()];
|
|
1758
|
+
for (const entry of snapshot) {
|
|
1733
1759
|
this.destroyEntry(entry);
|
|
1734
1760
|
}
|
|
1735
1761
|
this.holdingArea?.remove();
|
|
@@ -1835,7 +1861,7 @@ class RetainedViewStash {
|
|
|
1835
1861
|
const area = this.document.createElement('div');
|
|
1836
1862
|
area.dataset['lwRetentionHold'] = '';
|
|
1837
1863
|
area.style.display = 'none';
|
|
1838
|
-
this.document.body.
|
|
1864
|
+
this.document.body.append(area);
|
|
1839
1865
|
this.holdingArea = area;
|
|
1840
1866
|
return area;
|
|
1841
1867
|
}
|
|
@@ -1846,7 +1872,7 @@ class RetainedViewStash {
|
|
|
1846
1872
|
}
|
|
1847
1873
|
pullNodes(entry) {
|
|
1848
1874
|
for (const node of liveRootNodes(entry)) {
|
|
1849
|
-
node.
|
|
1875
|
+
node.remove();
|
|
1850
1876
|
}
|
|
1851
1877
|
}
|
|
1852
1878
|
destroyEntry(entry) {
|
|
@@ -1870,7 +1896,8 @@ class RetainedViewStash {
|
|
|
1870
1896
|
this.sweepQueued = true;
|
|
1871
1897
|
queueMicrotask(() => {
|
|
1872
1898
|
this.sweepQueued = false;
|
|
1873
|
-
|
|
1899
|
+
const snapshot = [...this.entries.values()];
|
|
1900
|
+
for (const entry of snapshot) {
|
|
1874
1901
|
if (entry.inUse) {
|
|
1875
1902
|
continue;
|
|
1876
1903
|
}
|
|
@@ -1950,7 +1977,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
1950
1977
|
type: Service
|
|
1951
1978
|
}] });
|
|
1952
1979
|
function pathOfStashKey(key) {
|
|
1953
|
-
return key.split('|')[1] ?? '';
|
|
1980
|
+
return key.split('|', 2)[1] ?? '';
|
|
1954
1981
|
}
|
|
1955
1982
|
|
|
1956
1983
|
class RetentionUnloadGuard {
|
|
@@ -1983,7 +2010,6 @@ class RetentionUnloadGuard {
|
|
|
1983
2010
|
return;
|
|
1984
2011
|
}
|
|
1985
2012
|
event.preventDefault();
|
|
1986
|
-
event.returnValue = '';
|
|
1987
2013
|
};
|
|
1988
2014
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: RetentionUnloadGuard, deps: [], target: i0.ɵɵFactoryTarget.Service });
|
|
1989
2015
|
static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.1.0", ngImport: i0, type: RetentionUnloadGuard });
|
|
@@ -2169,16 +2195,20 @@ function isMacPlatform() {
|
|
|
2169
2195
|
function normaliseKey(key) {
|
|
2170
2196
|
const lower = key.toLowerCase();
|
|
2171
2197
|
switch (lower) {
|
|
2172
|
-
case 'esc':
|
|
2198
|
+
case 'esc': {
|
|
2173
2199
|
return 'escape';
|
|
2174
|
-
|
|
2200
|
+
}
|
|
2201
|
+
case 'return': {
|
|
2175
2202
|
return 'enter';
|
|
2203
|
+
}
|
|
2176
2204
|
case 'space':
|
|
2177
2205
|
case 'spacebar':
|
|
2178
|
-
case ' ':
|
|
2206
|
+
case ' ': {
|
|
2179
2207
|
return 'space';
|
|
2180
|
-
|
|
2208
|
+
}
|
|
2209
|
+
default: {
|
|
2181
2210
|
return lower;
|
|
2211
|
+
}
|
|
2182
2212
|
}
|
|
2183
2213
|
}
|
|
2184
2214
|
function toSignature(parts) {
|
|
@@ -2202,72 +2232,89 @@ function chordSignature(chord, isMac) {
|
|
|
2202
2232
|
};
|
|
2203
2233
|
for (const raw of chord.split('+')) {
|
|
2204
2234
|
const token = raw.trim().toLowerCase();
|
|
2205
|
-
if (
|
|
2206
|
-
|
|
2235
|
+
if (token) {
|
|
2236
|
+
applyToken(parts, token, isMac);
|
|
2207
2237
|
}
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
case 'ctrl':
|
|
2216
|
-
case 'control':
|
|
2217
|
-
parts.ctrl = true;
|
|
2218
|
-
break;
|
|
2219
|
-
case 'meta':
|
|
2220
|
-
case 'cmd':
|
|
2221
|
-
case 'command':
|
|
2222
|
-
case 'win':
|
|
2238
|
+
}
|
|
2239
|
+
return parts.key ? toSignature(parts) : null;
|
|
2240
|
+
}
|
|
2241
|
+
function applyToken(parts, token, isMac) {
|
|
2242
|
+
switch (token) {
|
|
2243
|
+
case 'mod': {
|
|
2244
|
+
if (isMac)
|
|
2223
2245
|
parts.meta = true;
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2246
|
+
else
|
|
2247
|
+
parts.ctrl = true;
|
|
2248
|
+
return;
|
|
2249
|
+
}
|
|
2250
|
+
case 'ctrl':
|
|
2251
|
+
case 'control': {
|
|
2252
|
+
parts.ctrl = true;
|
|
2253
|
+
return;
|
|
2254
|
+
}
|
|
2255
|
+
case 'meta':
|
|
2256
|
+
case 'cmd':
|
|
2257
|
+
case 'command':
|
|
2258
|
+
case 'win': {
|
|
2259
|
+
parts.meta = true;
|
|
2260
|
+
return;
|
|
2261
|
+
}
|
|
2262
|
+
case 'alt':
|
|
2263
|
+
case 'option': {
|
|
2264
|
+
parts.alt = true;
|
|
2265
|
+
return;
|
|
2266
|
+
}
|
|
2267
|
+
case 'shift': {
|
|
2268
|
+
parts.shift = true;
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2271
|
+
default: {
|
|
2272
|
+
parts.key = normaliseKey(token);
|
|
2234
2273
|
}
|
|
2235
2274
|
}
|
|
2236
|
-
return parts.key ? toSignature(parts) : null;
|
|
2237
2275
|
}
|
|
2238
2276
|
function formatShortcut(chord, isMac) {
|
|
2239
2277
|
const tokens = chord.split('+').map((raw) => {
|
|
2240
2278
|
const token = raw.trim().toLowerCase();
|
|
2241
2279
|
switch (token) {
|
|
2242
|
-
case 'mod':
|
|
2280
|
+
case 'mod': {
|
|
2243
2281
|
return isMac ? '⌘' : 'Ctrl';
|
|
2282
|
+
}
|
|
2244
2283
|
case 'ctrl':
|
|
2245
|
-
case 'control':
|
|
2284
|
+
case 'control': {
|
|
2246
2285
|
return isMac ? '⌃' : 'Ctrl';
|
|
2286
|
+
}
|
|
2247
2287
|
case 'meta':
|
|
2248
2288
|
case 'cmd':
|
|
2249
2289
|
case 'command':
|
|
2250
|
-
case 'win':
|
|
2290
|
+
case 'win': {
|
|
2251
2291
|
return isMac ? '⌘' : 'Meta';
|
|
2292
|
+
}
|
|
2252
2293
|
case 'alt':
|
|
2253
|
-
case 'option':
|
|
2294
|
+
case 'option': {
|
|
2254
2295
|
return isMac ? '⌥' : 'Alt';
|
|
2255
|
-
|
|
2296
|
+
}
|
|
2297
|
+
case 'shift': {
|
|
2256
2298
|
return isMac ? '⇧' : 'Shift';
|
|
2299
|
+
}
|
|
2257
2300
|
case 'enter':
|
|
2258
|
-
case 'return':
|
|
2301
|
+
case 'return': {
|
|
2259
2302
|
return isMac ? '↵' : 'Enter';
|
|
2303
|
+
}
|
|
2260
2304
|
case 'escape':
|
|
2261
|
-
case 'esc':
|
|
2305
|
+
case 'esc': {
|
|
2262
2306
|
return 'Esc';
|
|
2307
|
+
}
|
|
2263
2308
|
case 'space':
|
|
2264
2309
|
case 'spacebar':
|
|
2265
|
-
case ' ':
|
|
2310
|
+
case ' ': {
|
|
2266
2311
|
return 'Space';
|
|
2267
|
-
|
|
2312
|
+
}
|
|
2313
|
+
default: {
|
|
2268
2314
|
return token.length === 1
|
|
2269
2315
|
? token.toUpperCase()
|
|
2270
2316
|
: token.charAt(0).toUpperCase() + token.slice(1);
|
|
2317
|
+
}
|
|
2271
2318
|
}
|
|
2272
2319
|
});
|
|
2273
2320
|
return tokens.join(isMac ? '' : '+');
|
|
@@ -2428,20 +2475,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
2428
2475
|
type: Service
|
|
2429
2476
|
}] });
|
|
2430
2477
|
|
|
2431
|
-
function upgradeElementProperty(
|
|
2432
|
-
const self =
|
|
2433
|
-
if (Object.
|
|
2478
|
+
function upgradeElementProperty(element, name) {
|
|
2479
|
+
const self = element;
|
|
2480
|
+
if (Object.hasOwn(element, name)) {
|
|
2434
2481
|
const value = self[name];
|
|
2435
2482
|
delete self[name];
|
|
2436
2483
|
self[name] = value;
|
|
2437
2484
|
}
|
|
2438
2485
|
}
|
|
2439
|
-
function reflectAttribute(
|
|
2486
|
+
function reflectAttribute(element, name, value) {
|
|
2440
2487
|
if (value === null || value === undefined) {
|
|
2441
|
-
|
|
2488
|
+
element.removeAttribute(name);
|
|
2442
2489
|
}
|
|
2443
2490
|
else {
|
|
2444
|
-
|
|
2491
|
+
element.setAttribute(name, value);
|
|
2445
2492
|
}
|
|
2446
2493
|
}
|
|
2447
2494
|
|
|
@@ -2568,7 +2615,9 @@ class LwMenuElement extends HTMLElement {
|
|
|
2568
2615
|
return;
|
|
2569
2616
|
}
|
|
2570
2617
|
this.active = (index + items.length) % items.length;
|
|
2571
|
-
|
|
2618
|
+
for (const [index_, item] of items.entries()) {
|
|
2619
|
+
item.tabIndex = index_ === this.active ? 0 : -1;
|
|
2620
|
+
}
|
|
2572
2621
|
const item = items[this.active];
|
|
2573
2622
|
item.focus();
|
|
2574
2623
|
item.scrollIntoView?.({ block: 'nearest' });
|
|
@@ -2581,18 +2630,22 @@ class LwMenuElement extends HTMLElement {
|
|
|
2581
2630
|
}
|
|
2582
2631
|
handleKeydown(event) {
|
|
2583
2632
|
switch (event.key) {
|
|
2584
|
-
case 'ArrowDown':
|
|
2633
|
+
case 'ArrowDown': {
|
|
2585
2634
|
this.setActive(this.active < 0 ? 0 : this.active + 1);
|
|
2586
2635
|
break;
|
|
2587
|
-
|
|
2588
|
-
|
|
2636
|
+
}
|
|
2637
|
+
case 'ArrowUp': {
|
|
2638
|
+
this.setActive((this.active < 0 ? this.items().length : this.active) - 1);
|
|
2589
2639
|
break;
|
|
2590
|
-
|
|
2640
|
+
}
|
|
2641
|
+
case 'Home': {
|
|
2591
2642
|
this.setActive(0);
|
|
2592
2643
|
break;
|
|
2593
|
-
|
|
2644
|
+
}
|
|
2645
|
+
case 'End': {
|
|
2594
2646
|
this.setActive(this.items().length - 1);
|
|
2595
2647
|
break;
|
|
2648
|
+
}
|
|
2596
2649
|
case 'Enter':
|
|
2597
2650
|
case ' ': {
|
|
2598
2651
|
const item = this.active >= 0 ? this.items()[this.active] : undefined;
|
|
@@ -2602,11 +2655,13 @@ class LwMenuElement extends HTMLElement {
|
|
|
2602
2655
|
break;
|
|
2603
2656
|
}
|
|
2604
2657
|
case 'Escape':
|
|
2605
|
-
case 'Tab':
|
|
2658
|
+
case 'Tab': {
|
|
2606
2659
|
this.dispatchEvent(new CustomEvent(LW_MENU_DISMISS, { bubbles: true }));
|
|
2607
2660
|
return;
|
|
2608
|
-
|
|
2661
|
+
}
|
|
2662
|
+
default: {
|
|
2609
2663
|
return;
|
|
2664
|
+
}
|
|
2610
2665
|
}
|
|
2611
2666
|
event.preventDefault();
|
|
2612
2667
|
}
|
|
@@ -2685,7 +2740,7 @@ class MenuService {
|
|
|
2685
2740
|
document.body.append(menu);
|
|
2686
2741
|
document.body.classList.add('lw-menu-open');
|
|
2687
2742
|
menu.openAt(at.x, at.y);
|
|
2688
|
-
const listenTimer = setTimeout(() => document.addEventListener('pointerdown', onOutside, true));
|
|
2743
|
+
const listenTimer = setTimeout(() => document.addEventListener('pointerdown', onOutside, { capture: true }), 0);
|
|
2689
2744
|
this.current = { menu, onOutside, restore, listenTimer };
|
|
2690
2745
|
}
|
|
2691
2746
|
resolve(menuIds, context) {
|
|
@@ -2718,7 +2773,7 @@ class MenuService {
|
|
|
2718
2773
|
};
|
|
2719
2774
|
})
|
|
2720
2775
|
.filter((entry) => entry !== null)
|
|
2721
|
-
.
|
|
2776
|
+
.toSorted((a, b) => a.group.localeCompare(b.group) || a.order - b.order);
|
|
2722
2777
|
}
|
|
2723
2778
|
createMenu(resolved) {
|
|
2724
2779
|
const menu = document.createElement(LW_MENU_TAG);
|
|
@@ -2871,7 +2926,7 @@ class ShellBarItem {
|
|
|
2871
2926
|
shortcut = computed(() => {
|
|
2872
2927
|
const button = this.asButton();
|
|
2873
2928
|
if (!button?.showShortcut || !button.command) {
|
|
2874
|
-
return
|
|
2929
|
+
return;
|
|
2875
2930
|
}
|
|
2876
2931
|
return this.commands.shortcutOf(this.commands.commands().find((entry) => entry.id === button.command));
|
|
2877
2932
|
}, /* @ts-ignore */
|
|
@@ -2911,7 +2966,7 @@ class ShellBar {
|
|
|
2911
2966
|
.filter((item) => item.bar === this.region().id && item.slot === slot)
|
|
2912
2967
|
.filter((item) => this.auth.visible(item.access))
|
|
2913
2968
|
.filter((item) => 'component' in item || this.commands.triggerable(item))
|
|
2914
|
-
.
|
|
2969
|
+
.toSorted((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
2915
2970
|
}
|
|
2916
2971
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ShellBar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2917
2972
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: ShellBar, isStandalone: true, selector: "lw-shell-bar", inputs: { region: { classPropertyName: "region", publicName: "region", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<header\n class=\"flex items-center gap-2 overflow-hidden border-border bg-surface px-2 md:px-4\"\n [class.border-b]=\"dock() === 'top'\"\n [class.h-12]=\"dock() === 'top'\"\n [class.border-t]=\"dock() === 'bottom' || footer()\"\n [class.py-1]=\"dock() === 'bottom' || footer()\"\n [class.text-xs]=\"dock() === 'bottom'\"\n [class.text-content-muted]=\"dock() === 'bottom'\"\n>\n <div class=\"flex min-w-0 items-center gap-2\">\n @for (item of startItems(); track item.id) {\n <lw-shell-bar-item [item]=\"item\" [dock]=\"dock()\" />\n }\n </div>\n\n @if (centerItems().length) {\n <div class=\"mx-auto flex shrink-0 items-center gap-2\">\n @for (item of centerItems(); track item.id) {\n <lw-shell-bar-item [item]=\"item\" [dock]=\"dock()\" />\n }\n </div>\n }\n\n <div class=\"ml-auto flex shrink-0 items-center gap-2\">\n @for (item of endItems(); track item.id) {\n <lw-shell-bar-item [item]=\"item\" [dock]=\"dock()\" />\n }\n </div>\n</header>\n", dependencies: [{ kind: "component", type: ShellBarItem, selector: "lw-shell-bar-item", inputs: ["item", "dock"] }] });
|
|
@@ -3182,7 +3237,7 @@ class RailItemsService {
|
|
|
3182
3237
|
}
|
|
3183
3238
|
isVisible(itemId) {
|
|
3184
3239
|
if (isWorkspaceRailItem(itemId)) {
|
|
3185
|
-
return
|
|
3240
|
+
return Object.hasOwn(this.state().placed, itemId);
|
|
3186
3241
|
}
|
|
3187
3242
|
return !this.state().hidden.includes(itemId);
|
|
3188
3243
|
}
|
|
@@ -3318,15 +3373,15 @@ class Reorderable {
|
|
|
3318
3373
|
moveByKeyboard(item, direction) {
|
|
3319
3374
|
const id = item.dataset['reorderId'] ?? '';
|
|
3320
3375
|
const band = item.dataset['reorderBand'] ?? '';
|
|
3321
|
-
const inBand = this.items().filter((
|
|
3322
|
-
const bandIds = inBand.map((
|
|
3376
|
+
const inBand = this.items().filter((element) => (element.dataset['reorderBand'] ?? '') === band);
|
|
3377
|
+
const bandIds = inBand.map((element) => element.dataset['reorderId'] ?? '');
|
|
3323
3378
|
const target = bandIds.indexOf(id) + direction;
|
|
3324
3379
|
if (target < 0 || target >= bandIds.length) {
|
|
3325
3380
|
return false;
|
|
3326
3381
|
}
|
|
3327
3382
|
const neighbourId = bandIds[target];
|
|
3328
3383
|
const without = this.items()
|
|
3329
|
-
.map((
|
|
3384
|
+
.map((element) => element.dataset['reorderId'] ?? '')
|
|
3330
3385
|
.filter((x) => x !== id);
|
|
3331
3386
|
const insertAt = without.indexOf(neighbourId) + (direction > 0 ? 1 : 0);
|
|
3332
3387
|
without.splice(insertAt, 0, id);
|
|
@@ -3341,7 +3396,7 @@ class Reorderable {
|
|
|
3341
3396
|
}
|
|
3342
3397
|
focusItem(id) {
|
|
3343
3398
|
this.items()
|
|
3344
|
-
.find((
|
|
3399
|
+
.find((element) => (element.dataset['reorderId'] ?? '') === id)
|
|
3345
3400
|
?.focus();
|
|
3346
3401
|
}
|
|
3347
3402
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Reorderable, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
@@ -3392,8 +3447,8 @@ class UserOrderService {
|
|
|
3392
3447
|
const rank = new Map(sequence.map((id, index) => [id, index]));
|
|
3393
3448
|
const ranked = (item) => rank.has(key(item));
|
|
3394
3449
|
const knownInUserOrder = items
|
|
3395
|
-
.filter(ranked)
|
|
3396
|
-
.
|
|
3450
|
+
.filter((item) => ranked(item))
|
|
3451
|
+
.toSorted((a, b) => (rank.get(key(a)) ?? 0) - (rank.get(key(b)) ?? 0));
|
|
3397
3452
|
let next = 0;
|
|
3398
3453
|
return items.map((item) => ranked(item) ? knownInUserOrder[next++] : item);
|
|
3399
3454
|
}
|
|
@@ -3513,7 +3568,7 @@ function defaultTabTitle(route, root) {
|
|
|
3513
3568
|
function withRefreshedPath(tabs, index, path) {
|
|
3514
3569
|
return tabs[index].path === path
|
|
3515
3570
|
? tabs
|
|
3516
|
-
: tabs.map((tab,
|
|
3571
|
+
: tabs.map((tab, index_) => (index_ === index ? { ...tab, path } : tab));
|
|
3517
3572
|
}
|
|
3518
3573
|
function autoOpenedTab(route, root, path) {
|
|
3519
3574
|
if (!route ||
|
|
@@ -3553,11 +3608,11 @@ function toPaneTab(tab) {
|
|
|
3553
3608
|
return {
|
|
3554
3609
|
path: tab.path,
|
|
3555
3610
|
title: tab.title,
|
|
3556
|
-
...(tab.literalTitle
|
|
3557
|
-
...(tab.icon
|
|
3558
|
-
...(tab.pinned
|
|
3559
|
-
...(tab.preview
|
|
3560
|
-
...(tab.closable
|
|
3611
|
+
...(tab.literalTitle && { literalTitle: true }),
|
|
3612
|
+
...(tab.icon !== undefined && { icon: tab.icon }),
|
|
3613
|
+
...(tab.pinned && { pinned: true }),
|
|
3614
|
+
...(tab.preview && { preview: true }),
|
|
3615
|
+
...(!tab.closable && { closable: false }),
|
|
3561
3616
|
};
|
|
3562
3617
|
}
|
|
3563
3618
|
function facetTabViews(routes, addressOf = (route) => route.path) {
|
|
@@ -3565,7 +3620,7 @@ function facetTabViews(routes, addressOf = (route) => route.path) {
|
|
|
3565
3620
|
.filter((route) => route.follows === true)
|
|
3566
3621
|
.map((route) => ({ route, address: addressOf(route) }))
|
|
3567
3622
|
.filter((entry) => entry.address !== null)
|
|
3568
|
-
.
|
|
3623
|
+
.toSorted((a, b) => (a.route.order ?? 0) - (b.route.order ?? 0) ||
|
|
3569
3624
|
a.route.path.localeCompare(b.route.path))
|
|
3570
3625
|
.map(({ route, address }, index) => ({
|
|
3571
3626
|
path: route.path,
|
|
@@ -3656,11 +3711,11 @@ function collidingParam(pattern, other) {
|
|
|
3656
3711
|
function paramPrefixes(pattern) {
|
|
3657
3712
|
const prefixes = new Map();
|
|
3658
3713
|
const segments = segmentsOf(pattern);
|
|
3659
|
-
segments.
|
|
3714
|
+
for (const [index, segment] of segments.entries()) {
|
|
3660
3715
|
if (segment.startsWith(':')) {
|
|
3661
3716
|
prefixes.set(segment.slice(1), segments.slice(0, index).join('/'));
|
|
3662
3717
|
}
|
|
3663
|
-
}
|
|
3718
|
+
}
|
|
3664
3719
|
return prefixes;
|
|
3665
3720
|
}
|
|
3666
3721
|
|
|
@@ -3748,22 +3803,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
3748
3803
|
type: Service
|
|
3749
3804
|
}] });
|
|
3750
3805
|
|
|
3751
|
-
function transformLeaf(node, paneId,
|
|
3806
|
+
function transformLeaf(node, paneId, function_) {
|
|
3752
3807
|
if (node.kind === 'leaf') {
|
|
3753
|
-
return node.id === paneId ?
|
|
3808
|
+
return node.id === paneId ? function_(node) : node;
|
|
3754
3809
|
}
|
|
3755
|
-
const first = transformLeaf(node.first, paneId,
|
|
3756
|
-
const second = transformLeaf(node.second, paneId,
|
|
3810
|
+
const first = transformLeaf(node.first, paneId, function_);
|
|
3811
|
+
const second = transformLeaf(node.second, paneId, function_);
|
|
3757
3812
|
return first === node.first && second === node.second
|
|
3758
3813
|
? node
|
|
3759
3814
|
: { ...node, first, second };
|
|
3760
3815
|
}
|
|
3761
|
-
function collapseLeaf(node, paneId,
|
|
3816
|
+
function collapseLeaf(node, paneId, function_) {
|
|
3762
3817
|
if (node.kind === 'leaf') {
|
|
3763
|
-
return node.id === paneId ?
|
|
3818
|
+
return node.id === paneId ? function_(node) : node;
|
|
3764
3819
|
}
|
|
3765
|
-
const first = collapseLeaf(node.first, paneId,
|
|
3766
|
-
const second = collapseLeaf(node.second, paneId,
|
|
3820
|
+
const first = collapseLeaf(node.first, paneId, function_);
|
|
3821
|
+
const second = collapseLeaf(node.second, paneId, function_);
|
|
3767
3822
|
if (first === null) {
|
|
3768
3823
|
return second;
|
|
3769
3824
|
}
|
|
@@ -3878,8 +3933,8 @@ function pruneEmptyLeaves(node, primaryId, spare) {
|
|
|
3878
3933
|
function insertTab(node, paneId, tab, index) {
|
|
3879
3934
|
return transformLeaf(node, paneId, (leaf) => {
|
|
3880
3935
|
const existingIndex = leaf.tabs.findIndex((existing) => existing.path === tab.path);
|
|
3881
|
-
if (existingIndex
|
|
3882
|
-
const tabs = leaf.tabs.map((existing,
|
|
3936
|
+
if (existingIndex !== -1) {
|
|
3937
|
+
const tabs = leaf.tabs.map((existing, index_) => index_ === existingIndex ? tab : existing);
|
|
3883
3938
|
return { ...leaf, tabs, active: tab.path };
|
|
3884
3939
|
}
|
|
3885
3940
|
const at = index === undefined
|
|
@@ -3917,7 +3972,7 @@ function refineLeafTitles(leaf, skipLeaf, matches, patch) {
|
|
|
3917
3972
|
...tab,
|
|
3918
3973
|
title: patch.title,
|
|
3919
3974
|
literalTitle: patch.literalTitle,
|
|
3920
|
-
...(patch.icon
|
|
3975
|
+
...(patch.icon !== undefined && { icon: patch.icon }),
|
|
3921
3976
|
};
|
|
3922
3977
|
});
|
|
3923
3978
|
return found
|
|
@@ -3955,7 +4010,7 @@ function reseatPinned(tabs, index, updated) {
|
|
|
3955
4010
|
function pinTab(node, paneId, tabPath) {
|
|
3956
4011
|
return transformLeaf(node, paneId, (leaf) => {
|
|
3957
4012
|
const index = leaf.tabs.findIndex((tab) => tab.path === tabPath);
|
|
3958
|
-
if (index
|
|
4013
|
+
if (index === -1 || leaf.tabs[index].pinned === true) {
|
|
3959
4014
|
return leaf;
|
|
3960
4015
|
}
|
|
3961
4016
|
const pinned = {
|
|
@@ -3968,7 +4023,7 @@ function pinTab(node, paneId, tabPath) {
|
|
|
3968
4023
|
function unpinTab(node, paneId, tabPath) {
|
|
3969
4024
|
return transformLeaf(node, paneId, (leaf) => {
|
|
3970
4025
|
const index = leaf.tabs.findIndex((tab) => tab.path === tabPath && tab.pinned);
|
|
3971
|
-
if (index
|
|
4026
|
+
if (index === -1) {
|
|
3972
4027
|
return leaf;
|
|
3973
4028
|
}
|
|
3974
4029
|
const unpinned = tabWithout(leaf.tabs[index], 'pinned');
|
|
@@ -4025,7 +4080,7 @@ class PaneTreeService {
|
|
|
4025
4080
|
}
|
|
4026
4081
|
stackView(dock, viewId) {
|
|
4027
4082
|
const segments = paneSegments(this.tree(dock));
|
|
4028
|
-
const last = segments
|
|
4083
|
+
const last = segments.at(-1);
|
|
4029
4084
|
if (last) {
|
|
4030
4085
|
this.splitPane(dock, last.id, 'column', VIEW_PANE_PREFIX + viewId);
|
|
4031
4086
|
}
|
|
@@ -4058,7 +4113,7 @@ class PaneTreeService {
|
|
|
4058
4113
|
return;
|
|
4059
4114
|
}
|
|
4060
4115
|
const rank = new Map(order.map((path, index) => [path, index]));
|
|
4061
|
-
const tabs = [...leaf.tabs].
|
|
4116
|
+
const tabs = [...leaf.tabs].toSorted((a, b) => (rank.get(a.path) ?? leaf.tabs.indexOf(a)) -
|
|
4062
4117
|
(rank.get(b.path) ?? leaf.tabs.indexOf(b)));
|
|
4063
4118
|
this.commit(dock, setTabs(this.tree(dock), paneId, tabs));
|
|
4064
4119
|
}
|
|
@@ -4110,7 +4165,7 @@ class PaneTreeService {
|
|
|
4110
4165
|
const tree = this.tree(dock);
|
|
4111
4166
|
const primary = this.primaryId(dock);
|
|
4112
4167
|
const leaf = paneId === primary ? findLeaf(tree, primary) : undefined;
|
|
4113
|
-
if (leaf
|
|
4168
|
+
if (leaf?.tabs.every((tab) => tab.path !== tabPath)) {
|
|
4114
4169
|
return;
|
|
4115
4170
|
}
|
|
4116
4171
|
if (leaf && !leaf.declared && leaf.tabs.length <= 1) {
|
|
@@ -4165,7 +4220,7 @@ class PaneTreeService {
|
|
|
4165
4220
|
return this.docks()[dock] !== undefined;
|
|
4166
4221
|
}
|
|
4167
4222
|
dropDock(dock) {
|
|
4168
|
-
if (!this.
|
|
4223
|
+
if (!this.hasDock(dock)) {
|
|
4169
4224
|
return;
|
|
4170
4225
|
}
|
|
4171
4226
|
this.docks.update((docks) => {
|
|
@@ -4196,7 +4251,7 @@ class PaneTreeService {
|
|
|
4196
4251
|
this.docks.update((current) => {
|
|
4197
4252
|
const merged = { ...persisted };
|
|
4198
4253
|
for (const [dock, entry] of Object.entries(current)) {
|
|
4199
|
-
if (isContainerDock(dock) && !(dock
|
|
4254
|
+
if (isContainerDock(dock) && !Object.hasOwn(persisted, dock)) {
|
|
4200
4255
|
merged[dock] = entry;
|
|
4201
4256
|
}
|
|
4202
4257
|
}
|
|
@@ -4245,6 +4300,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4245
4300
|
type: Service
|
|
4246
4301
|
}], ctorParameters: () => [] });
|
|
4247
4302
|
|
|
4303
|
+
function popoutNavigationRefusal(path) {
|
|
4304
|
+
return (`Content navigation to "${path}" was ignored: this is a pop-out window, which shows one ` +
|
|
4305
|
+
`surface and has no content area to navigate. A command reaches a pop-out's palette only ` +
|
|
4306
|
+
`if it declares popout: true, so leave that off anything that navigates.`);
|
|
4307
|
+
}
|
|
4308
|
+
|
|
4248
4309
|
class OpenTabsService {
|
|
4249
4310
|
router = inject(Router);
|
|
4250
4311
|
registry = inject(ContributionRegistry);
|
|
@@ -4331,7 +4392,7 @@ class OpenTabsService {
|
|
|
4331
4392
|
activeViewInstance = computed(() => {
|
|
4332
4393
|
const path = this.activeViewPath();
|
|
4333
4394
|
if (path === null) {
|
|
4334
|
-
return
|
|
4395
|
+
return;
|
|
4335
4396
|
}
|
|
4336
4397
|
return this.paneTree
|
|
4337
4398
|
.primaryTabs(CONTENT_DOCK)
|
|
@@ -4367,7 +4428,7 @@ class OpenTabsService {
|
|
|
4367
4428
|
const facets = facetTabViews(routes, (route) => this.addressOf(route));
|
|
4368
4429
|
const open = this.openTabs().filter((tab) => this.strippable(routes, tab));
|
|
4369
4430
|
const dynamics = dynamicTabViews(routes, open);
|
|
4370
|
-
return [...facets, ...dynamics, ...this.viewTabs()].
|
|
4431
|
+
return [...facets, ...dynamics, ...this.viewTabs()].toSorted((a, b) => a.order - b.order);
|
|
4371
4432
|
}, /* @ts-ignore */
|
|
4372
4433
|
...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
|
|
4373
4434
|
viewTabs = computed(() => viewTabViews(this.paneTree.primaryTabs(CONTENT_DOCK), (id) => this.registry.views().find((view) => view.id === id), (access) => this.auth.meets(access), VIEW_PANE_PREFIX), /* @ts-ignore */
|
|
@@ -4383,9 +4444,11 @@ class OpenTabsService {
|
|
|
4383
4444
|
this.paneTree.hydrated();
|
|
4384
4445
|
untracked(() => {
|
|
4385
4446
|
if (url !== this.lastUrl) {
|
|
4447
|
+
const previous = this.lastUrl;
|
|
4386
4448
|
this.lastUrl = url;
|
|
4387
4449
|
if (this.ownNavigation !== normalizePath(url)) {
|
|
4388
4450
|
this.viewTabSelection.set(null);
|
|
4451
|
+
this.focusHolderOf(path, this.rootFor(previous).root);
|
|
4389
4452
|
}
|
|
4390
4453
|
this.ownNavigation = null;
|
|
4391
4454
|
}
|
|
@@ -4404,14 +4467,12 @@ class OpenTabsService {
|
|
|
4404
4467
|
navigate(path) {
|
|
4405
4468
|
if (this.inPopout) {
|
|
4406
4469
|
if (isDevMode()) {
|
|
4407
|
-
console.warn(
|
|
4408
|
-
`surface and has no content area to navigate. A command reaches a pop-out's palette only ` +
|
|
4409
|
-
`if it declares popout: true, so leave that off anything that navigates.`);
|
|
4470
|
+
console.warn(popoutNavigationRefusal(path));
|
|
4410
4471
|
}
|
|
4411
4472
|
return Promise.resolve(false);
|
|
4412
4473
|
}
|
|
4413
4474
|
const target = normalizePath(path);
|
|
4414
|
-
this.focusHolderOf(target);
|
|
4475
|
+
this.focusHolderOf(target, this.activeTabRoot());
|
|
4415
4476
|
this.ownNavigation = target;
|
|
4416
4477
|
this.viewTabSelection.set(null);
|
|
4417
4478
|
return this.router.navigateByUrl('/' + target + suffixOf(path));
|
|
@@ -4419,9 +4480,9 @@ class OpenTabsService {
|
|
|
4419
4480
|
navigateTo(path) {
|
|
4420
4481
|
this.navigate(path).catch((error) => console.error('Content navigation failed', error));
|
|
4421
4482
|
}
|
|
4422
|
-
updateOpen(
|
|
4483
|
+
updateOpen(function_) {
|
|
4423
4484
|
const current = this.openTabs();
|
|
4424
|
-
const next =
|
|
4485
|
+
const next = function_(current);
|
|
4425
4486
|
if (next === current) {
|
|
4426
4487
|
return;
|
|
4427
4488
|
}
|
|
@@ -4467,7 +4528,7 @@ class OpenTabsService {
|
|
|
4467
4528
|
return (owner.rest === true ||
|
|
4468
4529
|
segmentsOf(owner.path).length === segmentsOf(address).length);
|
|
4469
4530
|
}
|
|
4470
|
-
focusHolderOf(target) {
|
|
4531
|
+
focusHolderOf(target, previousContent) {
|
|
4471
4532
|
const routes = this.registry.contentRoutes();
|
|
4472
4533
|
const root = tabRootOf(routes, target);
|
|
4473
4534
|
if (root === '') {
|
|
@@ -4485,7 +4546,7 @@ class OpenTabsService {
|
|
|
4485
4546
|
return;
|
|
4486
4547
|
}
|
|
4487
4548
|
this.paneTree.setActiveTab(CONTENT_DOCK, holder.id, held.path);
|
|
4488
|
-
this.paneTree.focusPane(CONTENT_DOCK, holder.id,
|
|
4549
|
+
this.paneTree.focusPane(CONTENT_DOCK, holder.id, previousContent);
|
|
4489
4550
|
}
|
|
4490
4551
|
stampActive(root) {
|
|
4491
4552
|
const next = new Map(this.lastActive());
|
|
@@ -4496,7 +4557,7 @@ class OpenTabsService {
|
|
|
4496
4557
|
const routes = this.registry.contentRoutes();
|
|
4497
4558
|
this.updateOpen((tabs) => {
|
|
4498
4559
|
const index = tabs.findIndex((tab) => tabRootOf(routes, tab.path) === root);
|
|
4499
|
-
if (index
|
|
4560
|
+
if (index !== -1) {
|
|
4500
4561
|
return withRefreshedPath(tabs, index, path);
|
|
4501
4562
|
}
|
|
4502
4563
|
const opened = autoOpenedTab(route, root, path);
|
|
@@ -4511,7 +4572,7 @@ class OpenTabsService {
|
|
|
4511
4572
|
return (route !== undefined &&
|
|
4512
4573
|
route.chromeless !== true &&
|
|
4513
4574
|
route.follows !== true &&
|
|
4514
|
-
|
|
4575
|
+
(normalizePath(route.path) !== '' || normalizePath(tab.path) === ''));
|
|
4515
4576
|
}
|
|
4516
4577
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: OpenTabsService, deps: [], target: i0.ɵɵFactoryTarget.Service });
|
|
4517
4578
|
static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.1.0", ngImport: i0, type: OpenTabsService });
|
|
@@ -4927,7 +4988,7 @@ class SurfaceCloseGuard {
|
|
|
4927
4988
|
}
|
|
4928
4989
|
async saveAll(dirty) {
|
|
4929
4990
|
try {
|
|
4930
|
-
await Promise.all(dirty.map((surface) => surface.surfaceSave?.()));
|
|
4991
|
+
await Promise.all(dirty.map(async (surface) => surface.surfaceSave?.()));
|
|
4931
4992
|
}
|
|
4932
4993
|
catch (error) {
|
|
4933
4994
|
console.error('Save before closing failed', error);
|
|
@@ -5074,9 +5135,17 @@ class TabClosingService {
|
|
|
5074
5135
|
const closing = this.state.openTabs().filter((tab) => roots.has(tabRootOf(routes, tab.path)));
|
|
5075
5136
|
const activeWentAway = roots.has(this.state.activeTabRoot());
|
|
5076
5137
|
this.state.updateOpen((tabs) => tabs.filter((tab) => !roots.has(tabRootOf(routes, tab.path))));
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5138
|
+
for (const tab of closing) {
|
|
5139
|
+
this.closeHooks.runSafely(tab.onClose);
|
|
5140
|
+
}
|
|
5141
|
+
for (const root of roots) {
|
|
5142
|
+
this.closeHooks.delete(root);
|
|
5143
|
+
}
|
|
5144
|
+
const evictAll = () => {
|
|
5145
|
+
for (const root of roots) {
|
|
5146
|
+
this.reuse.evict(root);
|
|
5147
|
+
}
|
|
5148
|
+
};
|
|
5080
5149
|
if (activeWentAway) {
|
|
5081
5150
|
void this.state.navigate(fallbackPath)
|
|
5082
5151
|
.catch((error) => console.error('Content navigation failed', error))
|
|
@@ -5108,7 +5177,7 @@ class TabClosingService {
|
|
|
5108
5177
|
neighbourPath(root) {
|
|
5109
5178
|
const routes = this.registry.contentRoutes();
|
|
5110
5179
|
const siblings = this.state.openTabs().filter((tab) => tabRootOf(routes, tab.path) !== root);
|
|
5111
|
-
return siblings.
|
|
5180
|
+
return siblings.at(-1)?.path ?? '';
|
|
5112
5181
|
}
|
|
5113
5182
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: TabClosingService, deps: [], target: i0.ɵɵFactoryTarget.Service });
|
|
5114
5183
|
static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.1.0", ngImport: i0, type: TabClosingService });
|
|
@@ -5193,7 +5262,7 @@ class ContentTabsService {
|
|
|
5193
5262
|
const seat = (tab, fallback) => rank.get(tabRootOf(routes, tab.path)) ?? fallback;
|
|
5194
5263
|
this.state.updateOpen((tabs) => tabs
|
|
5195
5264
|
.map((tab, index) => ({ tab, index }))
|
|
5196
|
-
.
|
|
5265
|
+
.toSorted((a, b) => seat(a.tab, a.index) - seat(b.tab, b.index))
|
|
5197
5266
|
.map((entry) => entry.tab));
|
|
5198
5267
|
}
|
|
5199
5268
|
/**
|
|
@@ -5209,7 +5278,7 @@ class ContentTabsService {
|
|
|
5209
5278
|
const { routes, root } = this.state.rootFor(path);
|
|
5210
5279
|
this.state.updateOpen((tabs) => {
|
|
5211
5280
|
const index = tabs.findIndex((tab) => tabRootOf(routes, tab.path) === root);
|
|
5212
|
-
return index
|
|
5281
|
+
return index === -1 || tabs[index].pinned
|
|
5213
5282
|
? tabs
|
|
5214
5283
|
: reseatPinned(tabs, index, tabs[index]);
|
|
5215
5284
|
});
|
|
@@ -5364,7 +5433,7 @@ class ContentTabsService {
|
|
|
5364
5433
|
const { routes, root } = this.state.rootFor(path);
|
|
5365
5434
|
this.state.updateOpen((tabs) => {
|
|
5366
5435
|
const index = tabs.findIndex((tab) => tabRootOf(routes, tab.path) === root);
|
|
5367
|
-
if (index
|
|
5436
|
+
if (index === -1 || tabs[index].pinned === pinned) {
|
|
5368
5437
|
return tabs;
|
|
5369
5438
|
}
|
|
5370
5439
|
const updated = pinned
|
|
@@ -5434,7 +5503,7 @@ class PanelViewsService {
|
|
|
5434
5503
|
const declared = this.registry
|
|
5435
5504
|
.views()
|
|
5436
5505
|
.filter((view) => view.region === regionId)
|
|
5437
|
-
.
|
|
5506
|
+
.toSorted((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
5438
5507
|
return this.order.applyOrder(panelViewsContainerId(regionId), declared, (view) => view.id);
|
|
5439
5508
|
}
|
|
5440
5509
|
candidatesFor(regionId) {
|
|
@@ -5449,7 +5518,7 @@ class PanelViewsService {
|
|
|
5449
5518
|
(panelRegions.has(view.region) &&
|
|
5450
5519
|
(holder === null || !panelRegions.has(holder))))
|
|
5451
5520
|
.map(({ view, holder }) => ({ view, here: holder === regionId }))
|
|
5452
|
-
.
|
|
5521
|
+
.toSorted((a, b) => Number(b.here) - Number(a.here) ||
|
|
5453
5522
|
(a.view.order ?? 0) - (b.view.order ?? 0));
|
|
5454
5523
|
}
|
|
5455
5524
|
holderOf(viewId) {
|
|
@@ -5485,7 +5554,7 @@ class HiddenViewsService {
|
|
|
5485
5554
|
...(ngDevMode ? [{ debugName: "ids" }] : /* istanbul ignore next */ []));
|
|
5486
5555
|
hidden = this.ids.asReadonly();
|
|
5487
5556
|
constructor() {
|
|
5488
|
-
|
|
5557
|
+
this.hydrateWhenWorkspaceReady();
|
|
5489
5558
|
}
|
|
5490
5559
|
isHidden(viewId) {
|
|
5491
5560
|
return this.ids().has(viewId);
|
|
@@ -5509,7 +5578,7 @@ class HiddenViewsService {
|
|
|
5509
5578
|
void this.store.set(this.storageKey(), this.serialize());
|
|
5510
5579
|
}
|
|
5511
5580
|
serialize() {
|
|
5512
|
-
return JSON.stringify([...this.ids()].
|
|
5581
|
+
return JSON.stringify([...this.ids()].toSorted((a, b) => a.localeCompare(b)));
|
|
5513
5582
|
}
|
|
5514
5583
|
commit(next) {
|
|
5515
5584
|
this.ids.set(next);
|
|
@@ -5518,6 +5587,9 @@ class HiddenViewsService {
|
|
|
5518
5587
|
storageKey() {
|
|
5519
5588
|
return this.workspace.scopedKey(STORAGE_KEY$b);
|
|
5520
5589
|
}
|
|
5590
|
+
hydrateWhenWorkspaceReady() {
|
|
5591
|
+
void this.workspace.ready.then(() => hydrateAsync(this.store, this.storageKey(), (raw) => this.ids.set(parseHiddenViews(raw))));
|
|
5592
|
+
}
|
|
5521
5593
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: HiddenViewsService, deps: [], target: i0.ɵɵFactoryTarget.Service });
|
|
5522
5594
|
static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.1.0", ngImport: i0, type: HiddenViewsService });
|
|
5523
5595
|
}
|
|
@@ -5624,9 +5696,9 @@ function tabGaps(definition, routes, id) {
|
|
|
5624
5696
|
}
|
|
5625
5697
|
function sidebarGaps(definition, declaredPaths, id) {
|
|
5626
5698
|
return Object.entries(definition.sidebars ?? {}).flatMap(([region, visible]) => {
|
|
5627
|
-
const declared = declaredPaths(region).map((path) => path.slice(VIEW_PANE_PREFIX.length));
|
|
5699
|
+
const declared = new Set(declaredPaths(region).map((path) => path.slice(VIEW_PANE_PREFIX.length)));
|
|
5628
5700
|
return visible
|
|
5629
|
-
.filter((viewId) => !declared.
|
|
5701
|
+
.filter((viewId) => !declared.has(viewId))
|
|
5630
5702
|
.map((viewId) => `Workspace "${id}": sidebar view "${viewId}" is not declared for region "${region}" — the entry has no effect.`);
|
|
5631
5703
|
});
|
|
5632
5704
|
}
|
|
@@ -5643,7 +5715,7 @@ function canonicalState(read, shape) {
|
|
|
5643
5715
|
}
|
|
5644
5716
|
function canonicalValue(key, raw, hidden, shape) {
|
|
5645
5717
|
if (key === shape.hiddenViewsKey) {
|
|
5646
|
-
return JSON.stringify([...hidden].
|
|
5718
|
+
return JSON.stringify([...hidden].toSorted((a, b) => a.localeCompare(b)));
|
|
5647
5719
|
}
|
|
5648
5720
|
if (key === shape.paneTreesKey) {
|
|
5649
5721
|
return canonicalTrees(raw, hidden, shape);
|
|
@@ -5657,7 +5729,8 @@ function canonicalTrees(raw, hidden, shape) {
|
|
|
5657
5729
|
try {
|
|
5658
5730
|
const parsed = JSON.parse(raw);
|
|
5659
5731
|
const out = {};
|
|
5660
|
-
|
|
5732
|
+
const docks = Object.keys(parsed ?? {}).toSorted((a, b) => a.localeCompare(b));
|
|
5733
|
+
for (const dock of docks) {
|
|
5661
5734
|
const entry = normalizeDockEntry(parsed[dock]);
|
|
5662
5735
|
if (entry === null) {
|
|
5663
5736
|
continue;
|
|
@@ -5706,7 +5779,7 @@ function comparableTab(tab) {
|
|
|
5706
5779
|
}
|
|
5707
5780
|
function comparableNode(node) {
|
|
5708
5781
|
if (node.kind === 'leaf') {
|
|
5709
|
-
return { ...node, tabs: node.tabs.map(comparableTab) };
|
|
5782
|
+
return { ...node, tabs: node.tabs.map((tab) => comparableTab(tab)) };
|
|
5710
5783
|
}
|
|
5711
5784
|
return {
|
|
5712
5785
|
...node,
|
|
@@ -5732,7 +5805,7 @@ function baseInitials(name) {
|
|
|
5732
5805
|
if (letters.length === 1) {
|
|
5733
5806
|
return letters[0].toUpperCase();
|
|
5734
5807
|
}
|
|
5735
|
-
return (letters[0] + letters
|
|
5808
|
+
return (letters[0] + (letters.at(-1) ?? '')).toUpperCase();
|
|
5736
5809
|
}
|
|
5737
5810
|
function* candidatesFor(name) {
|
|
5738
5811
|
const base = baseInitials(name);
|
|
@@ -5741,8 +5814,8 @@ function* candidatesFor(name) {
|
|
|
5741
5814
|
}
|
|
5742
5815
|
yield base;
|
|
5743
5816
|
const letters = [...(wordsOf(name)[0] ?? '')];
|
|
5744
|
-
for (let
|
|
5745
|
-
yield (letters[0] + letters[
|
|
5817
|
+
for (let index = 1; index < letters.length; index++) {
|
|
5818
|
+
yield (letters[0] + letters[index]).toUpperCase();
|
|
5746
5819
|
}
|
|
5747
5820
|
for (let digit = 2; digit <= LAST_RESORT_DIGITS; digit++) {
|
|
5748
5821
|
yield letters[0].toUpperCase() + digit;
|
|
@@ -5752,17 +5825,22 @@ function assignWorkspaceInitials(workspaces) {
|
|
|
5752
5825
|
const taken = new Set();
|
|
5753
5826
|
const assigned = new Map();
|
|
5754
5827
|
for (const workspace of workspaces) {
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
continue;
|
|
5758
|
-
}
|
|
5828
|
+
const candidate = firstFree(candidatesFor(workspace.name), taken);
|
|
5829
|
+
if (candidate !== undefined) {
|
|
5759
5830
|
taken.add(candidate);
|
|
5760
5831
|
assigned.set(workspace.id, candidate);
|
|
5761
|
-
break;
|
|
5762
5832
|
}
|
|
5763
5833
|
}
|
|
5764
5834
|
return assigned;
|
|
5765
5835
|
}
|
|
5836
|
+
function firstFree(candidates, taken) {
|
|
5837
|
+
for (const candidate of candidates) {
|
|
5838
|
+
if (!taken.has(candidate)) {
|
|
5839
|
+
return candidate;
|
|
5840
|
+
}
|
|
5841
|
+
}
|
|
5842
|
+
return undefined;
|
|
5843
|
+
}
|
|
5766
5844
|
|
|
5767
5845
|
const STORAGE_KEY$a = 'lw.shell.workspaces';
|
|
5768
5846
|
const HIDDEN_VIEWS_KEY = 'lw.shell.hidden-views';
|
|
@@ -5849,14 +5927,16 @@ class WorkspaceService {
|
|
|
5849
5927
|
}, /* @ts-ignore */
|
|
5850
5928
|
...(ngDevMode ? [{ debugName: "changedIds" }] : /* istanbul ignore next */ []));
|
|
5851
5929
|
constructor() {
|
|
5852
|
-
|
|
5853
|
-
this.
|
|
5930
|
+
const setList = (raw) => this.list.set(parse(raw));
|
|
5931
|
+
hydrateAsync(this.store, STORAGE_KEY$a, setList);
|
|
5932
|
+
this.sync.register('settings', STORAGE_KEY$a, setList);
|
|
5854
5933
|
if (isDevMode()) {
|
|
5855
|
-
|
|
5934
|
+
const all = this.definitionBatches.flat();
|
|
5935
|
+
for (const problem of auditWorkspaceDefinitions(all, this.panelRegions)) {
|
|
5856
5936
|
console.warn(problem);
|
|
5857
5937
|
}
|
|
5858
5938
|
}
|
|
5859
|
-
|
|
5939
|
+
this.layOutAdoptedWorkspaceWhenReady();
|
|
5860
5940
|
}
|
|
5861
5941
|
async saveCurrent(name) {
|
|
5862
5942
|
const baseline = await this.currentState();
|
|
@@ -5864,7 +5944,7 @@ class WorkspaceService {
|
|
|
5864
5944
|
const origin = this.originOf(this.active.id());
|
|
5865
5945
|
this.commit([
|
|
5866
5946
|
...this.list(),
|
|
5867
|
-
{ id, name, baseline, ...(origin
|
|
5947
|
+
{ id, name, baseline, ...(origin !== null && { origin }) },
|
|
5868
5948
|
]);
|
|
5869
5949
|
this.active.set(id);
|
|
5870
5950
|
this.applyState(baseline);
|
|
@@ -5976,10 +6056,8 @@ class WorkspaceService {
|
|
|
5976
6056
|
declaredPaths: (region) => this.panelGroups.declaredPaths(region),
|
|
5977
6057
|
});
|
|
5978
6058
|
return {
|
|
5979
|
-
...(state.hiddenViews
|
|
5980
|
-
|
|
5981
|
-
: { [HIDDEN_VIEWS_KEY]: state.hiddenViews }),
|
|
5982
|
-
...(state.trees === undefined ? {} : { [PANE_TREES_KEY]: state.trees }),
|
|
6059
|
+
...(state.hiddenViews !== undefined && { [HIDDEN_VIEWS_KEY]: state.hiddenViews }),
|
|
6060
|
+
...(state.trees !== undefined && { [PANE_TREES_KEY]: state.trees }),
|
|
5983
6061
|
};
|
|
5984
6062
|
}
|
|
5985
6063
|
warnDeclarationGaps(id) {
|
|
@@ -6043,6 +6121,9 @@ class WorkspaceService {
|
|
|
6043
6121
|
this.list.set(next);
|
|
6044
6122
|
void this.store.set(STORAGE_KEY$a, JSON.stringify(next));
|
|
6045
6123
|
}
|
|
6124
|
+
layOutAdoptedWorkspaceWhenReady() {
|
|
6125
|
+
void this.active.ready.then(() => this.layOutAdoptedWorkspace());
|
|
6126
|
+
}
|
|
6046
6127
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: WorkspaceService, deps: [], target: i0.ɵɵFactoryTarget.Service });
|
|
6047
6128
|
static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.1.0", ngImport: i0, type: WorkspaceService });
|
|
6048
6129
|
}
|
|
@@ -6081,7 +6162,7 @@ class ShellRail {
|
|
|
6081
6162
|
.filter((item) => this.railItems.regionOf(item.id, item.rail) === this.region().id)
|
|
6082
6163
|
.filter((item) => this.auth.visible(item.access))
|
|
6083
6164
|
.filter((item) => item.workspace !== undefined || this.commands.triggerable(item))
|
|
6084
|
-
.
|
|
6165
|
+
.toSorted((a, b) => (a.order ?? 0) - (b.order ?? 0)), /* @ts-ignore */
|
|
6085
6166
|
...(ngDevMode ? [{ debugName: "registered" }] : /* istanbul ignore next */ []));
|
|
6086
6167
|
items = computed(() => {
|
|
6087
6168
|
const inRail = this.registered().filter((item) => this.railItems.isVisible(item.id));
|
|
@@ -6089,7 +6170,7 @@ class ShellRail {
|
|
|
6089
6170
|
const id = this.containerId();
|
|
6090
6171
|
const key = (item) => item.id;
|
|
6091
6172
|
const top = this.userOrder.applyOrder(id, inRail.filter((item) => !isBottom(item)), key);
|
|
6092
|
-
const bottom = this.userOrder.applyOrder(id, inRail.filter(isBottom), key);
|
|
6173
|
+
const bottom = this.userOrder.applyOrder(id, inRail.filter((item) => isBottom(item)), key);
|
|
6093
6174
|
return [...top, ...bottom];
|
|
6094
6175
|
}, /* @ts-ignore */
|
|
6095
6176
|
...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
@@ -6234,10 +6315,11 @@ class ViewStateService {
|
|
|
6234
6315
|
hydrateAsync(this.store, key, (raw) => value.set(parseBlob$1(raw)));
|
|
6235
6316
|
let timer;
|
|
6236
6317
|
const cancelPendingSave = () => {
|
|
6237
|
-
if (timer
|
|
6238
|
-
|
|
6239
|
-
timer = undefined;
|
|
6318
|
+
if (timer === undefined) {
|
|
6319
|
+
return;
|
|
6240
6320
|
}
|
|
6321
|
+
clearTimeout(timer);
|
|
6322
|
+
timer = undefined;
|
|
6241
6323
|
};
|
|
6242
6324
|
const save = () => {
|
|
6243
6325
|
cancelPendingSave();
|
|
@@ -6275,12 +6357,12 @@ function parseRecord(viewId, raw) {
|
|
|
6275
6357
|
}
|
|
6276
6358
|
const record = parsed;
|
|
6277
6359
|
const instances = Array.isArray(record.instances)
|
|
6278
|
-
? record.instances.filter((
|
|
6360
|
+
? record.instances.filter((index) => !!index && typeof index.id === 'string' && typeof index.name === 'string')
|
|
6279
6361
|
: [];
|
|
6280
|
-
const withoutDefault = instances.filter((
|
|
6362
|
+
const withoutDefault = instances.filter((index) => index.id !== viewId);
|
|
6281
6363
|
const merged = [{ id: viewId, name: '' }, ...withoutDefault];
|
|
6282
6364
|
const activeId = typeof record.activeId === 'string' &&
|
|
6283
|
-
merged.some((
|
|
6365
|
+
merged.some((index) => index.id === record.activeId)
|
|
6284
6366
|
? record.activeId
|
|
6285
6367
|
: viewId;
|
|
6286
6368
|
return { instances: merged, activeId };
|
|
@@ -6313,7 +6395,7 @@ class ViewInstanceService {
|
|
|
6313
6395
|
}
|
|
6314
6396
|
setActive(viewId, instanceId) {
|
|
6315
6397
|
const record = this.recordFor(viewId);
|
|
6316
|
-
if (record().instances.some((
|
|
6398
|
+
if (record().instances.some((index) => index.id === instanceId)) {
|
|
6317
6399
|
this.commit(viewId, { ...record(), activeId: instanceId });
|
|
6318
6400
|
}
|
|
6319
6401
|
}
|
|
@@ -6332,7 +6414,7 @@ class ViewInstanceService {
|
|
|
6332
6414
|
const record = this.recordFor(viewId);
|
|
6333
6415
|
this.commit(viewId, {
|
|
6334
6416
|
...record(),
|
|
6335
|
-
instances: record().instances.map((
|
|
6417
|
+
instances: record().instances.map((index) => index.id === instanceId ? { ...index, name } : index),
|
|
6336
6418
|
});
|
|
6337
6419
|
}
|
|
6338
6420
|
remove(viewId, instanceId) {
|
|
@@ -6340,7 +6422,7 @@ class ViewInstanceService {
|
|
|
6340
6422
|
return;
|
|
6341
6423
|
}
|
|
6342
6424
|
const record = this.recordFor(viewId);
|
|
6343
|
-
const instances = record().instances.filter((
|
|
6425
|
+
const instances = record().instances.filter((index) => index.id !== instanceId);
|
|
6344
6426
|
const activeId = record().activeId === instanceId ? viewId : record().activeId;
|
|
6345
6427
|
this.commit(viewId, { instances, activeId });
|
|
6346
6428
|
this.viewStates.clear(instanceId);
|
|
@@ -6601,7 +6683,7 @@ class PaneChromeService {
|
|
|
6601
6683
|
clearMinimized(dock) {
|
|
6602
6684
|
const prefix = `${dock}:`;
|
|
6603
6685
|
const current = this.min();
|
|
6604
|
-
if (
|
|
6686
|
+
if ([...current].every((id) => !id.startsWith(prefix))) {
|
|
6605
6687
|
return;
|
|
6606
6688
|
}
|
|
6607
6689
|
this.min.set(new Set([...current].filter((id) => !id.startsWith(prefix))));
|
|
@@ -6668,7 +6750,7 @@ function bakeChild(dock, spec, entry, problems, context) {
|
|
|
6668
6750
|
return {
|
|
6669
6751
|
tab: {
|
|
6670
6752
|
...containerChildTab(dock, spec, declared.surface),
|
|
6671
|
-
...(declared.closable === false
|
|
6753
|
+
...(declared.closable === false && { closable: false }),
|
|
6672
6754
|
},
|
|
6673
6755
|
active: declared.active === true,
|
|
6674
6756
|
};
|
|
@@ -6705,13 +6787,11 @@ class PaneContainersService {
|
|
|
6705
6787
|
const tab = {
|
|
6706
6788
|
path,
|
|
6707
6789
|
instance: `${dock}::${path}`,
|
|
6708
|
-
...(label?.title
|
|
6709
|
-
|
|
6710
|
-
:
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
}),
|
|
6714
|
-
...(label?.icon === undefined ? {} : { icon: label.icon }),
|
|
6790
|
+
...(label?.title !== undefined && {
|
|
6791
|
+
title: label.title,
|
|
6792
|
+
literalTitle: label.titleIsLiteral ?? false,
|
|
6793
|
+
}),
|
|
6794
|
+
...(label?.icon !== undefined && { icon: label.icon }),
|
|
6715
6795
|
};
|
|
6716
6796
|
this.paneTree.commit(dock, setActiveTab(insertTab(tree, target, tab), target, path));
|
|
6717
6797
|
}
|
|
@@ -6737,11 +6817,11 @@ function offRouterPaneTargets(registry, auth) {
|
|
|
6737
6817
|
const routes = registry
|
|
6738
6818
|
.contentRoutes()
|
|
6739
6819
|
.filter((route) => offRouterMountable(registry, auth, route.path))
|
|
6740
|
-
.map(routeTarget);
|
|
6820
|
+
.map((route) => routeTarget(route));
|
|
6741
6821
|
const views = registry
|
|
6742
6822
|
.views()
|
|
6743
6823
|
.filter((view) => auth.meets(view.access))
|
|
6744
|
-
.map(viewTarget);
|
|
6824
|
+
.map((view) => viewTarget(view));
|
|
6745
6825
|
return [...routes, ...views];
|
|
6746
6826
|
}
|
|
6747
6827
|
function containerChildTargets(registry, auth, spec) {
|
|
@@ -6750,14 +6830,14 @@ function containerChildTargets(registry, auth, spec) {
|
|
|
6750
6830
|
.filter((child) => child.segment === undefined || isAddressable(child.segment))
|
|
6751
6831
|
.map((child) => views.find((view) => view.id === child.surface))
|
|
6752
6832
|
.filter((view) => view !== undefined && auth.meets(view.access))
|
|
6753
|
-
.map(viewTarget);
|
|
6833
|
+
.map((view) => viewTarget(view));
|
|
6754
6834
|
}
|
|
6755
6835
|
function routerPaneTargets(registry, auth) {
|
|
6756
6836
|
return registry
|
|
6757
6837
|
.contentRoutes()
|
|
6758
6838
|
.filter((route) => barePathHostableRoute(registry, route.path) !== null &&
|
|
6759
6839
|
auth.meets(route.access))
|
|
6760
|
-
.map(routeTarget);
|
|
6840
|
+
.map((route) => routeTarget(route));
|
|
6761
6841
|
}
|
|
6762
6842
|
function paneTargetEntries(targets, translate) {
|
|
6763
6843
|
return targets.map((target) => ({
|
|
@@ -7126,7 +7206,7 @@ class PaneTabStrip {
|
|
|
7126
7206
|
targetKind: 'view-tab',
|
|
7127
7207
|
viewId: tab.path.slice(VIEW_PANE_PREFIX.length),
|
|
7128
7208
|
region: this.contextGroup(),
|
|
7129
|
-
...(tab.instance
|
|
7209
|
+
...(tab.instance && { instance: tab.instance }),
|
|
7130
7210
|
};
|
|
7131
7211
|
}
|
|
7132
7212
|
return {
|
|
@@ -7180,22 +7260,22 @@ class PaneTabStrip {
|
|
|
7180
7260
|
this.reorderTabs.emit(tabs.filter((tab) => this.canReorder(tab)).map((tab) => tab.path));
|
|
7181
7261
|
}
|
|
7182
7262
|
observeResize() {
|
|
7183
|
-
const
|
|
7184
|
-
if (!
|
|
7263
|
+
const element = this.strip()?.nativeElement;
|
|
7264
|
+
if (!element || typeof ResizeObserver === 'undefined') {
|
|
7185
7265
|
return;
|
|
7186
7266
|
}
|
|
7187
7267
|
const observer = new ResizeObserver(() => this.overflow() && this.measureOverflow());
|
|
7188
|
-
observer.observe(
|
|
7268
|
+
observer.observe(element);
|
|
7189
7269
|
this.destroyRef.onDestroy(() => observer.disconnect());
|
|
7190
7270
|
}
|
|
7191
7271
|
measureOverflow() {
|
|
7192
|
-
const
|
|
7193
|
-
this.overflowing.set(!!
|
|
7272
|
+
const element = this.strip()?.nativeElement;
|
|
7273
|
+
this.overflowing.set(!!element && element.scrollWidth - element.clientWidth > EDGE_TOLERANCE_PX);
|
|
7194
7274
|
}
|
|
7195
7275
|
revealActiveTab() {
|
|
7196
7276
|
const strip = this.strip()?.nativeElement;
|
|
7197
7277
|
const active = this.activeId();
|
|
7198
|
-
const wrapper = strip?.querySelector(`[data-tab-path="${active}"]`)?.parentElement;
|
|
7278
|
+
const wrapper = strip?.querySelector(`[data-tab-path="${CSS.escape(active)}"]`)?.parentElement;
|
|
7199
7279
|
if (!strip || !wrapper) {
|
|
7200
7280
|
return;
|
|
7201
7281
|
}
|
|
@@ -7400,12 +7480,14 @@ class PluginStateService {
|
|
|
7400
7480
|
};
|
|
7401
7481
|
}
|
|
7402
7482
|
removePlugin(pluginId) {
|
|
7403
|
-
|
|
7404
|
-
|
|
7405
|
-
|
|
7406
|
-
|
|
7407
|
-
this.entries.delete(storageKey);
|
|
7483
|
+
const snapshot = [...this.entries];
|
|
7484
|
+
for (const [storageKey, entry] of snapshot) {
|
|
7485
|
+
if (entry.pluginId !== pluginId) {
|
|
7486
|
+
continue;
|
|
7408
7487
|
}
|
|
7488
|
+
this.cancelPending(entry);
|
|
7489
|
+
entry.value.set(undefined);
|
|
7490
|
+
this.entries.delete(storageKey);
|
|
7409
7491
|
}
|
|
7410
7492
|
this.keysByPlugin.delete(pluginId);
|
|
7411
7493
|
void readStoredValue(this.store, INDEX_PREFIX + pluginId).then((raw) => {
|
|
@@ -7495,10 +7577,11 @@ class PluginStateService {
|
|
|
7495
7577
|
entry.pending = undefined;
|
|
7496
7578
|
}
|
|
7497
7579
|
cancelTimer(entry) {
|
|
7498
|
-
if (entry.timer
|
|
7499
|
-
|
|
7500
|
-
entry.timer = undefined;
|
|
7580
|
+
if (entry.timer === undefined) {
|
|
7581
|
+
return;
|
|
7501
7582
|
}
|
|
7583
|
+
clearTimeout(entry.timer);
|
|
7584
|
+
entry.timer = undefined;
|
|
7502
7585
|
}
|
|
7503
7586
|
withinLimits(pluginId, key, serialised) {
|
|
7504
7587
|
const bytes = serialised.length;
|
|
@@ -7660,10 +7743,9 @@ function ownerByToken(registrations) {
|
|
|
7660
7743
|
...Object.keys(registration.dark ?? {}),
|
|
7661
7744
|
];
|
|
7662
7745
|
for (const name of names) {
|
|
7663
|
-
if (
|
|
7664
|
-
|
|
7746
|
+
if (known.has(name) && !owners.has(name)) {
|
|
7747
|
+
owners.set(name, registration);
|
|
7665
7748
|
}
|
|
7666
|
-
owners.set(name, registration);
|
|
7667
7749
|
}
|
|
7668
7750
|
}
|
|
7669
7751
|
return owners;
|
|
@@ -7702,7 +7784,7 @@ function createPluginLayerRules() {
|
|
|
7702
7784
|
}
|
|
7703
7785
|
const element = document.createElement('style');
|
|
7704
7786
|
element.dataset['lwPluginTheme'] = '';
|
|
7705
|
-
document.head.
|
|
7787
|
+
document.head.append(element);
|
|
7706
7788
|
const sheet = element.sheet;
|
|
7707
7789
|
if (!sheet) {
|
|
7708
7790
|
return null;
|
|
@@ -8009,7 +8091,7 @@ class CapabilityGrantService {
|
|
|
8009
8091
|
})),
|
|
8010
8092
|
}))
|
|
8011
8093
|
.filter((entry) => entry.capabilities.length > 0)
|
|
8012
|
-
.
|
|
8094
|
+
.toSorted((a, b) => a.pluginId.localeCompare(b.pluginId));
|
|
8013
8095
|
}, /* @ts-ignore */
|
|
8014
8096
|
...(ngDevMode ? [{ debugName: "permissions" }] : /* istanbul ignore next */ []));
|
|
8015
8097
|
constructor() {
|
|
@@ -8146,7 +8228,7 @@ class IframeSurface {
|
|
|
8146
8228
|
...(ngDevMode ? [{ debugName: "activeTab" }] : /* istanbul ignore next */ []));
|
|
8147
8229
|
restPath = computed(() => {
|
|
8148
8230
|
if (!this.ownsRest) {
|
|
8149
|
-
return
|
|
8231
|
+
return;
|
|
8150
8232
|
}
|
|
8151
8233
|
return this.hostMounted
|
|
8152
8234
|
? this.hostSub()
|
|
@@ -8177,7 +8259,9 @@ class IframeSurface {
|
|
|
8177
8259
|
queueMicrotask(() => this.push({ ...snapshot, ...this.readResolved() }));
|
|
8178
8260
|
});
|
|
8179
8261
|
inject(DestroyRef).onDestroy(() => {
|
|
8180
|
-
|
|
8262
|
+
for (const entry of this.watched.values()) {
|
|
8263
|
+
entry.stop();
|
|
8264
|
+
}
|
|
8181
8265
|
this.watched.clear();
|
|
8182
8266
|
this.visibility?.disconnect();
|
|
8183
8267
|
this.connection?.destroy();
|
|
@@ -8213,12 +8297,12 @@ class IframeSurface {
|
|
|
8213
8297
|
this.tabs.keep(this.tabRoot);
|
|
8214
8298
|
}
|
|
8215
8299
|
},
|
|
8216
|
-
setDirty: (dirty) => this.dirty.set(dirty
|
|
8217
|
-
stateWatch: (key) => this.watchState(
|
|
8218
|
-
stateSet: (key, value) => this.watched.get(
|
|
8219
|
-
stateClear: (key) => this.watched.get(
|
|
8300
|
+
setDirty: (dirty) => this.dirty.set(dirty),
|
|
8301
|
+
stateWatch: (key) => this.watchState(key),
|
|
8302
|
+
stateSet: (key, value) => this.watched.get(key)?.handle.set(value),
|
|
8303
|
+
stateClear: (key) => this.watched.get(key)?.handle.clear(),
|
|
8220
8304
|
stateUnwatch: (key) => {
|
|
8221
|
-
const name =
|
|
8305
|
+
const name = key;
|
|
8222
8306
|
this.watched.get(name)?.stop();
|
|
8223
8307
|
this.watched.delete(name);
|
|
8224
8308
|
},
|
|
@@ -8228,7 +8312,9 @@ class IframeSurface {
|
|
|
8228
8312
|
.then((remote) => {
|
|
8229
8313
|
this.remote = remote;
|
|
8230
8314
|
this.push({ ...this.reactiveState(), ...this.readResolved() });
|
|
8231
|
-
|
|
8315
|
+
for (const [key, entry] of this.watched) {
|
|
8316
|
+
this.pushState(key, entry.handle.value(), entry.handle.loaded());
|
|
8317
|
+
}
|
|
8232
8318
|
})
|
|
8233
8319
|
.catch(() => undefined);
|
|
8234
8320
|
}
|
|
@@ -8264,7 +8350,7 @@ class IframeSurface {
|
|
|
8264
8350
|
return;
|
|
8265
8351
|
}
|
|
8266
8352
|
this.visibility = new IntersectionObserver((entries) => {
|
|
8267
|
-
const last = entries
|
|
8353
|
+
const last = entries.at(-1);
|
|
8268
8354
|
if (last) {
|
|
8269
8355
|
this.shown.set(last.isIntersecting);
|
|
8270
8356
|
}
|
|
@@ -8287,19 +8373,15 @@ class IframeSurface {
|
|
|
8287
8373
|
theme: this.theme.resolvedTheme(),
|
|
8288
8374
|
preview: this.isPreview(),
|
|
8289
8375
|
shown: this.shown(),
|
|
8290
|
-
...(this.instanceId
|
|
8291
|
-
...(Object.keys(this.routeParams).length > 0
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8297
|
-
|
|
8298
|
-
|
|
8299
|
-
roles: this.auth.roles(),
|
|
8300
|
-
},
|
|
8301
|
-
}
|
|
8302
|
-
: {}),
|
|
8376
|
+
...(this.instanceId && { instanceId: this.instanceId }),
|
|
8377
|
+
...(Object.keys(this.routeParams).length > 0 && { params: this.routeParams }),
|
|
8378
|
+
...(rest !== undefined && { rest }),
|
|
8379
|
+
...(this.sessionGranted() && {
|
|
8380
|
+
session: {
|
|
8381
|
+
authenticated: this.auth.authenticated(),
|
|
8382
|
+
roles: this.auth.roles(),
|
|
8383
|
+
},
|
|
8384
|
+
}),
|
|
8303
8385
|
};
|
|
8304
8386
|
}
|
|
8305
8387
|
readResolved() {
|
|
@@ -8312,19 +8394,19 @@ class IframeSurface {
|
|
|
8312
8394
|
return {
|
|
8313
8395
|
tokens,
|
|
8314
8396
|
rootFontSize: styles.fontSize,
|
|
8315
|
-
...(Object.keys(icons).length > 0
|
|
8397
|
+
...(Object.keys(icons).length > 0 && { icons }),
|
|
8316
8398
|
};
|
|
8317
8399
|
}
|
|
8318
8400
|
navigateWithinTabRoot(path) {
|
|
8319
8401
|
if (this.docked) {
|
|
8320
8402
|
if (isDevMode()) {
|
|
8321
|
-
console.warn(`[loom] a docked surface asked to navigate to "${
|
|
8403
|
+
console.warn(`[loom] a docked surface asked to navigate to "${path}" — ignored. ` +
|
|
8322
8404
|
`A docked surface has no address of its own; the channel's navigate is confined to a tab ` +
|
|
8323
8405
|
`root and there is none. Use ctx.navigateContent (the 'navigation' grant) instead.`);
|
|
8324
8406
|
}
|
|
8325
8407
|
return;
|
|
8326
8408
|
}
|
|
8327
|
-
const raw =
|
|
8409
|
+
const raw = path;
|
|
8328
8410
|
const suffix = suffixOf(raw);
|
|
8329
8411
|
const target = normalizePath(raw);
|
|
8330
8412
|
if (target !== this.tabRoot && !target.startsWith(this.tabRoot + '/')) {
|
|
@@ -8393,8 +8475,8 @@ function syntheticDockedRoute(view, instanceId, params = {}) {
|
|
|
8393
8475
|
url: [],
|
|
8394
8476
|
params,
|
|
8395
8477
|
data: {
|
|
8396
|
-
...(view.iframe !== undefined
|
|
8397
|
-
...(view.pluginId
|
|
8478
|
+
...(view.iframe !== undefined && { iframe: view.iframe }),
|
|
8479
|
+
...(view.pluginId && { pluginId: view.pluginId }),
|
|
8398
8480
|
docked: true,
|
|
8399
8481
|
instanceId,
|
|
8400
8482
|
},
|
|
@@ -8410,13 +8492,13 @@ function syntheticRouteFor(route, path, options = {}) {
|
|
|
8410
8492
|
url: segments.map((segment) => new UrlSegment(segment, {})),
|
|
8411
8493
|
params,
|
|
8412
8494
|
data: {
|
|
8413
|
-
...('iframe' in route
|
|
8414
|
-
...('container' in route
|
|
8415
|
-
...(route.pluginId
|
|
8416
|
-
...(route.rest === true
|
|
8417
|
-
...(sub
|
|
8418
|
-
...(options.urlDriven
|
|
8419
|
-
...(options.instanceId
|
|
8495
|
+
...('iframe' in route && { iframe: route.iframe }),
|
|
8496
|
+
...('container' in route && { container: route.container }),
|
|
8497
|
+
...(route.pluginId && { pluginId: route.pluginId }),
|
|
8498
|
+
...(route.rest === true && { rest: true }),
|
|
8499
|
+
...(sub && { sub }),
|
|
8500
|
+
...(options.urlDriven && { urlDriven: true }),
|
|
8501
|
+
...(options.instanceId && { instanceId: options.instanceId }),
|
|
8420
8502
|
},
|
|
8421
8503
|
});
|
|
8422
8504
|
}
|
|
@@ -8652,14 +8734,12 @@ class ContentSecondaryPane {
|
|
|
8652
8734
|
mountParams = computed(() => {
|
|
8653
8735
|
const ctx = this.containerCtx;
|
|
8654
8736
|
if (!ctx) {
|
|
8655
|
-
return
|
|
8737
|
+
return;
|
|
8656
8738
|
}
|
|
8657
8739
|
const match = containerChildForPath(this.registry.contentRoutes(), this.registry.views(), this.path());
|
|
8658
8740
|
return {
|
|
8659
8741
|
...ctx.params,
|
|
8660
|
-
...(match
|
|
8661
|
-
? paramsOfPattern(match.declaration.segment ?? '', match.segmentPath)
|
|
8662
|
-
: {}),
|
|
8742
|
+
...(match && paramsOfPattern(match.declaration.segment ?? '', match.segmentPath)),
|
|
8663
8743
|
};
|
|
8664
8744
|
}, /* @ts-ignore */
|
|
8665
8745
|
...(ngDevMode ? [{ debugName: "mountParams" }] : /* istanbul ignore next */ []));
|
|
@@ -8691,12 +8771,12 @@ class ContentSecondaryPane {
|
|
|
8691
8771
|
...(ngDevMode ? [{ debugName: "activeRoute" }] : /* istanbul ignore next */ []));
|
|
8692
8772
|
iframeSurface = computed(() => {
|
|
8693
8773
|
const route = this.activeRoute();
|
|
8694
|
-
return route?.iframe
|
|
8695
|
-
?
|
|
8774
|
+
return route?.iframe === undefined
|
|
8775
|
+
? null
|
|
8776
|
+
: {
|
|
8696
8777
|
component: IframeSurface,
|
|
8697
8778
|
injector: this.injectorFor(route, this.path(), this.surfaceKey()),
|
|
8698
|
-
}
|
|
8699
|
-
: null;
|
|
8779
|
+
};
|
|
8700
8780
|
}, /* @ts-ignore */
|
|
8701
8781
|
...(ngDevMode ? [{ debugName: "iframeSurface" }] : /* istanbul ignore next */ []));
|
|
8702
8782
|
surface = computed(() => {
|
|
@@ -9148,11 +9228,11 @@ class PaneView {
|
|
|
9148
9228
|
];
|
|
9149
9229
|
}
|
|
9150
9230
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: PaneView, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9151
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: PaneView, isStandalone: true, selector: "lw-pane-view", inputs: { dock: { classPropertyName: "dock", publicName: "dock", isSignal: true, isRequired: true, transformFunction: null }, leaf: { classPropertyName: "leaf", publicName: "leaf", isSignal: true, isRequired: true, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "flex min-h-0 min-w-0 flex-1 flex-col" }, ngImport: i0, template: "<lw-pane-tab-strip\n class=\"shrink-0\"\n [variant]=\"options().variant\"\n [tabs]=\"stripTabs()\"\n [activeId]=\"activeTabId()\"\n [urlDriven]=\"pointed()\"\n [source]=\"source()\"\n [reorderable]=\"tabsReorderable()\"\n [draggable]=\"tabsDraggable()\"\n [acceptsTabs]=\"acceptsTabs()\"\n [canAddTab]=\"canAddTab()\"\n [viewContextMenuSlot]=\"viewContextMenu()\"\n [contextGroup]=\"dock()\"\n [paneActions]=\"paneActions\"\n (selectTab)=\"onSelectTab($event)\"\n (escalate)=\"onEscalate($event)\"\n (closeTab)=\"onCloseTab($event)\"\n (unpinTab)=\"onUnpinTab($event)\"\n (addTab)=\"openPicker($event)\"\n (reorderTabs)=\"onReorderTabs($event)\"\n/>\n<ng-template #paneActions>\n <lw-pane-toolbar\n [canSplitRight]=\"canSplitRight()\"\n [canSplitDown]=\"canSplitDown()\"\n [canMinimize]=\"canMinimize()\"\n [canMaximize]=\"canMaximize()\"\n [maximized]=\"maximized()\"\n [canClose]=\"canClose()\"\n [closeLabel]=\"options().closeLabel\"\n (splitRight)=\"splitPane('row')\"\n (splitDown)=\"splitPane('column')\"\n (minimize)=\"minimize()\"\n (toggleMaximize)=\"toggleMaximize()\"\n (closePane)=\"closePane()\"\n />\n</ng-template>\n\n<div class=\"flex min-h-0 min-w-0 flex-1 flex-col\" (pointerdown)=\"onBodyPointerDown()\">\n @if (awaitingContent()) {\n <
|
|
9231
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: PaneView, isStandalone: true, selector: "lw-pane-view", inputs: { dock: { classPropertyName: "dock", publicName: "dock", isSignal: true, isRequired: true, transformFunction: null }, leaf: { classPropertyName: "leaf", publicName: "leaf", isSignal: true, isRequired: true, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "flex min-h-0 min-w-0 flex-1 flex-col" }, ngImport: i0, template: "<lw-pane-tab-strip\n class=\"shrink-0\"\n [variant]=\"options().variant\"\n [tabs]=\"stripTabs()\"\n [activeId]=\"activeTabId()\"\n [urlDriven]=\"pointed()\"\n [source]=\"source()\"\n [reorderable]=\"tabsReorderable()\"\n [draggable]=\"tabsDraggable()\"\n [acceptsTabs]=\"acceptsTabs()\"\n [canAddTab]=\"canAddTab()\"\n [viewContextMenuSlot]=\"viewContextMenu()\"\n [contextGroup]=\"dock()\"\n [paneActions]=\"paneActions\"\n (selectTab)=\"onSelectTab($event)\"\n (escalate)=\"onEscalate($event)\"\n (closeTab)=\"onCloseTab($event)\"\n (unpinTab)=\"onUnpinTab($event)\"\n (addTab)=\"openPicker($event)\"\n (reorderTabs)=\"onReorderTabs($event)\"\n/>\n<ng-template #paneActions>\n <lw-pane-toolbar\n [canSplitRight]=\"canSplitRight()\"\n [canSplitDown]=\"canSplitDown()\"\n [canMinimize]=\"canMinimize()\"\n [canMaximize]=\"canMaximize()\"\n [maximized]=\"maximized()\"\n [canClose]=\"canClose()\"\n [closeLabel]=\"options().closeLabel\"\n (splitRight)=\"splitPane('row')\"\n (splitDown)=\"splitPane('column')\"\n (minimize)=\"minimize()\"\n (toggleMaximize)=\"toggleMaximize()\"\n (closePane)=\"closePane()\"\n />\n</ng-template>\n\n<div class=\"flex min-h-0 min-w-0 flex-1 flex-col\" (pointerdown)=\"onBodyPointerDown()\">\n @if (awaitingContent()) {\n <output\n class=\"flex h-full items-center justify-center p-6 text-center text-sm text-content-faint\"\n data-testid=\"pane-awaiting-content\"\n >\n {{ 'content.split.awaiting' | transloco }}\n </output>\n } @else {\n <lw-content-secondary-pane\n class=\"min-h-0 min-w-0 flex-1\"\n [path]=\"path()\"\n [instanceId]=\"instanceId()\"\n [variant]=\"options().body\"\n [retentionScope]=\"retentionScope()\"\n (instanceReleased)=\"releaseTabInstance()\"\n />\n }\n</div>\n", dependencies: [{ kind: "component", type: ContentSecondaryPane, selector: "lw-content-secondary-pane", inputs: ["path", "variant", "instanceId", "retentionScope"], outputs: ["instanceReleased"] }, { kind: "component", type: PaneTabStrip, selector: "lw-pane-tab-strip", inputs: ["tabs", "activeId", "reorderable", "draggable", "acceptsTabs", "source", "variant", "urlDriven", "contextMenuSlot", "viewContextMenuSlot", "contextGroup", "overflow", "canAddTab", "paneActions"], outputs: ["selectTab", "escalate", "closeTab", "unpinTab", "reorderTabs", "runAction", "addTab", "revealRequest"] }, { kind: "component", type: PaneToolbar, selector: "lw-pane-toolbar", inputs: ["canNewTab", "canSplitRight", "canSplitDown", "canMinimize", "canMaximize", "maximized", "canClose", "closeLabel", "newTabTestId", "splitRightTestId", "splitDownTestId"], outputs: ["newTab", "splitRight", "splitDown", "minimize", "toggleMaximize", "closePane"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
|
|
9152
9232
|
}
|
|
9153
9233
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: PaneView, decorators: [{
|
|
9154
9234
|
type: Component,
|
|
9155
|
-
args: [{ selector: 'lw-pane-view', imports: [ContentSecondaryPane, PaneTabStrip, PaneToolbar, TranslocoPipe], schemas: [CUSTOM_ELEMENTS_SCHEMA], host: { class: 'flex min-h-0 min-w-0 flex-1 flex-col' }, template: "<lw-pane-tab-strip\n class=\"shrink-0\"\n [variant]=\"options().variant\"\n [tabs]=\"stripTabs()\"\n [activeId]=\"activeTabId()\"\n [urlDriven]=\"pointed()\"\n [source]=\"source()\"\n [reorderable]=\"tabsReorderable()\"\n [draggable]=\"tabsDraggable()\"\n [acceptsTabs]=\"acceptsTabs()\"\n [canAddTab]=\"canAddTab()\"\n [viewContextMenuSlot]=\"viewContextMenu()\"\n [contextGroup]=\"dock()\"\n [paneActions]=\"paneActions\"\n (selectTab)=\"onSelectTab($event)\"\n (escalate)=\"onEscalate($event)\"\n (closeTab)=\"onCloseTab($event)\"\n (unpinTab)=\"onUnpinTab($event)\"\n (addTab)=\"openPicker($event)\"\n (reorderTabs)=\"onReorderTabs($event)\"\n/>\n<ng-template #paneActions>\n <lw-pane-toolbar\n [canSplitRight]=\"canSplitRight()\"\n [canSplitDown]=\"canSplitDown()\"\n [canMinimize]=\"canMinimize()\"\n [canMaximize]=\"canMaximize()\"\n [maximized]=\"maximized()\"\n [canClose]=\"canClose()\"\n [closeLabel]=\"options().closeLabel\"\n (splitRight)=\"splitPane('row')\"\n (splitDown)=\"splitPane('column')\"\n (minimize)=\"minimize()\"\n (toggleMaximize)=\"toggleMaximize()\"\n (closePane)=\"closePane()\"\n />\n</ng-template>\n\n<div class=\"flex min-h-0 min-w-0 flex-1 flex-col\" (pointerdown)=\"onBodyPointerDown()\">\n @if (awaitingContent()) {\n <
|
|
9235
|
+
args: [{ selector: 'lw-pane-view', imports: [ContentSecondaryPane, PaneTabStrip, PaneToolbar, TranslocoPipe], schemas: [CUSTOM_ELEMENTS_SCHEMA], host: { class: 'flex min-h-0 min-w-0 flex-1 flex-col' }, template: "<lw-pane-tab-strip\n class=\"shrink-0\"\n [variant]=\"options().variant\"\n [tabs]=\"stripTabs()\"\n [activeId]=\"activeTabId()\"\n [urlDriven]=\"pointed()\"\n [source]=\"source()\"\n [reorderable]=\"tabsReorderable()\"\n [draggable]=\"tabsDraggable()\"\n [acceptsTabs]=\"acceptsTabs()\"\n [canAddTab]=\"canAddTab()\"\n [viewContextMenuSlot]=\"viewContextMenu()\"\n [contextGroup]=\"dock()\"\n [paneActions]=\"paneActions\"\n (selectTab)=\"onSelectTab($event)\"\n (escalate)=\"onEscalate($event)\"\n (closeTab)=\"onCloseTab($event)\"\n (unpinTab)=\"onUnpinTab($event)\"\n (addTab)=\"openPicker($event)\"\n (reorderTabs)=\"onReorderTabs($event)\"\n/>\n<ng-template #paneActions>\n <lw-pane-toolbar\n [canSplitRight]=\"canSplitRight()\"\n [canSplitDown]=\"canSplitDown()\"\n [canMinimize]=\"canMinimize()\"\n [canMaximize]=\"canMaximize()\"\n [maximized]=\"maximized()\"\n [canClose]=\"canClose()\"\n [closeLabel]=\"options().closeLabel\"\n (splitRight)=\"splitPane('row')\"\n (splitDown)=\"splitPane('column')\"\n (minimize)=\"minimize()\"\n (toggleMaximize)=\"toggleMaximize()\"\n (closePane)=\"closePane()\"\n />\n</ng-template>\n\n<div class=\"flex min-h-0 min-w-0 flex-1 flex-col\" (pointerdown)=\"onBodyPointerDown()\">\n @if (awaitingContent()) {\n <output\n class=\"flex h-full items-center justify-center p-6 text-center text-sm text-content-faint\"\n data-testid=\"pane-awaiting-content\"\n >\n {{ 'content.split.awaiting' | transloco }}\n </output>\n } @else {\n <lw-content-secondary-pane\n class=\"min-h-0 min-w-0 flex-1\"\n [path]=\"path()\"\n [instanceId]=\"instanceId()\"\n [variant]=\"options().body\"\n [retentionScope]=\"retentionScope()\"\n (instanceReleased)=\"releaseTabInstance()\"\n />\n }\n</div>\n" }]
|
|
9156
9236
|
}], propDecorators: { dock: [{ type: i0.Input, args: [{ isSignal: true, alias: "dock", required: true }] }], leaf: [{ type: i0.Input, args: [{ isSignal: true, alias: "leaf", required: true }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }] } });
|
|
9157
9237
|
|
|
9158
9238
|
class PaneDropZones {
|
|
@@ -9190,13 +9270,12 @@ class PaneDropZones {
|
|
|
9190
9270
|
...(ngDevMode ? [{ debugName: "accepts" }] : /* istanbul ignore next */ []));
|
|
9191
9271
|
constructor() {
|
|
9192
9272
|
effect((onCleanup) => {
|
|
9193
|
-
const
|
|
9194
|
-
|
|
9195
|
-
|
|
9196
|
-
|
|
9197
|
-
|
|
9198
|
-
|
|
9199
|
-
onCleanup(() => disposers.forEach((dispose) => dispose()));
|
|
9273
|
+
const disposers = this.zoneIds().map((id) => this.drag.registerZone(id));
|
|
9274
|
+
onCleanup(() => {
|
|
9275
|
+
for (const dispose of disposers) {
|
|
9276
|
+
dispose();
|
|
9277
|
+
}
|
|
9278
|
+
});
|
|
9200
9279
|
});
|
|
9201
9280
|
}
|
|
9202
9281
|
has(edge) {
|
|
@@ -9232,8 +9311,14 @@ class PaneDropZones {
|
|
|
9232
9311
|
this.paneMove.moveToEdge(source, String(event.item.data ?? ''), { dock: this.dock(), paneId: this.paneId() }, edge);
|
|
9233
9312
|
}
|
|
9234
9313
|
}
|
|
9314
|
+
zoneIds() {
|
|
9315
|
+
if (!this.fills()) {
|
|
9316
|
+
return this.edges().map((edge) => this.zoneId(edge));
|
|
9317
|
+
}
|
|
9318
|
+
return this.accepts() ? [this.fillZoneId()] : [];
|
|
9319
|
+
}
|
|
9235
9320
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: PaneDropZones, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9236
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: PaneDropZones, isStandalone: true, selector: "lw-pane-drop-zones", inputs: { dock: { classPropertyName: "dock", publicName: "dock", isSignal: true, isRequired: true, transformFunction: null }, paneId: { classPropertyName: "paneId", publicName: "paneId", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "class.opacity-0": "!active()", "class.pointer-events-none": "!active()", "style.grid-template-columns": "\"1fr 2fr 1fr\"", "style.grid-template-rows": "\"1fr 2fr 1fr\"", "attr.aria-hidden": "true" }, classAttribute: "absolute inset-0 z-20 grid transition-opacity" }, ngImport: i0, template: "@if (fills()) {\n @if (accepts()) {\n <div\n class=\"lw-pane-drop-zone col-span-3
|
|
9321
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: PaneDropZones, isStandalone: true, selector: "lw-pane-drop-zones", inputs: { dock: { classPropertyName: "dock", publicName: "dock", isSignal: true, isRequired: true, transformFunction: null }, paneId: { classPropertyName: "paneId", publicName: "paneId", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "class.opacity-0": "!active()", "class.pointer-events-none": "!active()", "style.grid-template-columns": "\"1fr 2fr 1fr\"", "style.grid-template-rows": "\"1fr 2fr 1fr\"", "attr.aria-hidden": "true" }, classAttribute: "absolute inset-0 z-20 grid transition-opacity" }, ngImport: i0, template: "@if (fills()) {\n @if (accepts()) {\n <div\n class=\"lw-pane-drop-zone col-span-3 col-start-1 row-span-3 row-start-1\"\n cdkDropList\n [cdkDropListEnterPredicate]=\"enterPredicate\"\n [id]=\"fillZoneId()\"\n (cdkDropListDropped)=\"onFill($event)\"\n >\n <div class=\"lw-pane-drop-preview lw-pane-drop-preview--fill\"></div>\n </div>\n }\n} @else {\n @if (has('left')) {\n <div\n class=\"lw-pane-drop-zone col-start-1 row-span-3 row-start-1\"\n cdkDropList\n [cdkDropListEnterPredicate]=\"enterPredicate\"\n [id]=\"zoneId('left')\"\n (cdkDropListDropped)=\"onDrop('left', $event)\"\n >\n <div class=\"lw-pane-drop-preview lw-pane-drop-preview--left\"></div>\n </div>\n }\n @if (has('right')) {\n <div\n class=\"lw-pane-drop-zone col-start-3 row-span-3 row-start-1\"\n cdkDropList\n [cdkDropListEnterPredicate]=\"enterPredicate\"\n [id]=\"zoneId('right')\"\n (cdkDropListDropped)=\"onDrop('right', $event)\"\n >\n <div class=\"lw-pane-drop-preview lw-pane-drop-preview--right\"></div>\n </div>\n }\n @if (has('top')) {\n <div\n class=\"lw-pane-drop-zone col-start-2 row-start-1\"\n cdkDropList\n [cdkDropListEnterPredicate]=\"enterPredicate\"\n [id]=\"zoneId('top')\"\n (cdkDropListDropped)=\"onDrop('top', $event)\"\n >\n <div class=\"lw-pane-drop-preview lw-pane-drop-preview--top\"></div>\n </div>\n }\n @if (has('bottom')) {\n <div\n class=\"lw-pane-drop-zone col-start-2 row-start-3\"\n cdkDropList\n [cdkDropListEnterPredicate]=\"enterPredicate\"\n [id]=\"zoneId('bottom')\"\n (cdkDropListDropped)=\"onDrop('bottom', $event)\"\n >\n <div class=\"lw-pane-drop-preview lw-pane-drop-preview--bottom\"></div>\n </div>\n }\n}\n", dependencies: [{ kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }] });
|
|
9237
9322
|
}
|
|
9238
9323
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: PaneDropZones, decorators: [{
|
|
9239
9324
|
type: Component,
|
|
@@ -9244,7 +9329,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
9244
9329
|
'[style.grid-template-columns]': '"1fr 2fr 1fr"',
|
|
9245
9330
|
'[style.grid-template-rows]': '"1fr 2fr 1fr"',
|
|
9246
9331
|
'[attr.aria-hidden]': 'true',
|
|
9247
|
-
}, template: "@if (fills()) {\n @if (accepts()) {\n <div\n class=\"lw-pane-drop-zone col-span-3
|
|
9332
|
+
}, template: "@if (fills()) {\n @if (accepts()) {\n <div\n class=\"lw-pane-drop-zone col-span-3 col-start-1 row-span-3 row-start-1\"\n cdkDropList\n [cdkDropListEnterPredicate]=\"enterPredicate\"\n [id]=\"fillZoneId()\"\n (cdkDropListDropped)=\"onFill($event)\"\n >\n <div class=\"lw-pane-drop-preview lw-pane-drop-preview--fill\"></div>\n </div>\n }\n} @else {\n @if (has('left')) {\n <div\n class=\"lw-pane-drop-zone col-start-1 row-span-3 row-start-1\"\n cdkDropList\n [cdkDropListEnterPredicate]=\"enterPredicate\"\n [id]=\"zoneId('left')\"\n (cdkDropListDropped)=\"onDrop('left', $event)\"\n >\n <div class=\"lw-pane-drop-preview lw-pane-drop-preview--left\"></div>\n </div>\n }\n @if (has('right')) {\n <div\n class=\"lw-pane-drop-zone col-start-3 row-span-3 row-start-1\"\n cdkDropList\n [cdkDropListEnterPredicate]=\"enterPredicate\"\n [id]=\"zoneId('right')\"\n (cdkDropListDropped)=\"onDrop('right', $event)\"\n >\n <div class=\"lw-pane-drop-preview lw-pane-drop-preview--right\"></div>\n </div>\n }\n @if (has('top')) {\n <div\n class=\"lw-pane-drop-zone col-start-2 row-start-1\"\n cdkDropList\n [cdkDropListEnterPredicate]=\"enterPredicate\"\n [id]=\"zoneId('top')\"\n (cdkDropListDropped)=\"onDrop('top', $event)\"\n >\n <div class=\"lw-pane-drop-preview lw-pane-drop-preview--top\"></div>\n </div>\n }\n @if (has('bottom')) {\n <div\n class=\"lw-pane-drop-zone col-start-2 row-start-3\"\n cdkDropList\n [cdkDropListEnterPredicate]=\"enterPredicate\"\n [id]=\"zoneId('bottom')\"\n (cdkDropListDropped)=\"onDrop('bottom', $event)\"\n >\n <div class=\"lw-pane-drop-preview lw-pane-drop-preview--bottom\"></div>\n </div>\n }\n}\n" }]
|
|
9248
9333
|
}], ctorParameters: () => [], propDecorators: { dock: [{ type: i0.Input, args: [{ isSignal: true, alias: "dock", required: true }] }], paneId: [{ type: i0.Input, args: [{ isSignal: true, alias: "paneId", required: true }] }] } });
|
|
9249
9334
|
|
|
9250
9335
|
class PaneMinimizedStrip {
|
|
@@ -9317,18 +9402,18 @@ class PaneSplitHandle {
|
|
|
9317
9402
|
return;
|
|
9318
9403
|
}
|
|
9319
9404
|
const rect = parent.getBoundingClientRect();
|
|
9320
|
-
const
|
|
9321
|
-
|
|
9405
|
+
const element = this.host.nativeElement;
|
|
9406
|
+
element.setPointerCapture(event.pointerId);
|
|
9322
9407
|
event.preventDefault();
|
|
9323
9408
|
const move = (e) => this.ratioStream.emit(this.fraction(e, rect));
|
|
9324
9409
|
const up = (e) => {
|
|
9325
|
-
|
|
9326
|
-
|
|
9327
|
-
|
|
9410
|
+
element.releasePointerCapture(e.pointerId);
|
|
9411
|
+
element.removeEventListener('pointermove', move);
|
|
9412
|
+
element.removeEventListener('pointerup', up);
|
|
9328
9413
|
this.ratioCommit.emit();
|
|
9329
9414
|
};
|
|
9330
|
-
|
|
9331
|
-
|
|
9415
|
+
element.addEventListener('pointermove', move);
|
|
9416
|
+
element.addEventListener('pointerup', up);
|
|
9332
9417
|
}
|
|
9333
9418
|
onKeydown(event) {
|
|
9334
9419
|
const step = event.shiftKey ? STEP_COARSE$1 : STEP$1;
|
|
@@ -9635,11 +9720,12 @@ function parseWidths(raw) {
|
|
|
9635
9720
|
}
|
|
9636
9721
|
const result = {};
|
|
9637
9722
|
for (const [key, value] of Object.entries(parsed)) {
|
|
9638
|
-
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
9639
|
-
|
|
9640
|
-
|
|
9641
|
-
|
|
9642
|
-
|
|
9723
|
+
if (!(typeof value === 'number' && Number.isFinite(value))) {
|
|
9724
|
+
continue;
|
|
9725
|
+
}
|
|
9726
|
+
const clamped = clampWidth(value);
|
|
9727
|
+
if (clamped !== DEFAULT_PANEL_WIDTH) {
|
|
9728
|
+
result[key] = clamped;
|
|
9643
9729
|
}
|
|
9644
9730
|
}
|
|
9645
9731
|
return result;
|
|
@@ -9742,20 +9828,25 @@ class PanelSplitter {
|
|
|
9742
9828
|
const step = (event.shiftKey ? STEP_COARSE : STEP) * this.edgeSign();
|
|
9743
9829
|
let next;
|
|
9744
9830
|
switch (event.key) {
|
|
9745
|
-
case 'ArrowRight':
|
|
9831
|
+
case 'ArrowRight': {
|
|
9746
9832
|
next = this.width() + step;
|
|
9747
9833
|
break;
|
|
9748
|
-
|
|
9834
|
+
}
|
|
9835
|
+
case 'ArrowLeft': {
|
|
9749
9836
|
next = this.width() - step;
|
|
9750
9837
|
break;
|
|
9751
|
-
|
|
9838
|
+
}
|
|
9839
|
+
case 'Home': {
|
|
9752
9840
|
next = this.size.minWidth;
|
|
9753
9841
|
break;
|
|
9754
|
-
|
|
9842
|
+
}
|
|
9843
|
+
case 'End': {
|
|
9755
9844
|
next = this.size.maxWidth;
|
|
9756
9845
|
break;
|
|
9757
|
-
|
|
9846
|
+
}
|
|
9847
|
+
default: {
|
|
9758
9848
|
return;
|
|
9849
|
+
}
|
|
9759
9850
|
}
|
|
9760
9851
|
event.preventDefault();
|
|
9761
9852
|
this.size.setWidth(this.regionId(), next);
|
|
@@ -9812,7 +9903,7 @@ class ShellPanel {
|
|
|
9812
9903
|
activeView = computed(() => {
|
|
9813
9904
|
const path = this.activePath();
|
|
9814
9905
|
if (!path?.startsWith(VIEW_PANE_PREFIX)) {
|
|
9815
|
-
return
|
|
9906
|
+
return;
|
|
9816
9907
|
}
|
|
9817
9908
|
return viewForPanePath(this.registry.views(), path);
|
|
9818
9909
|
}, /* @ts-ignore */
|
|
@@ -9826,7 +9917,7 @@ class ShellPanel {
|
|
|
9826
9917
|
...(ngDevMode ? [{ debugName: "activeContentPath" }] : /* istanbul ignore next */ []));
|
|
9827
9918
|
actions = computed(() => [...(this.activeView()?.actions ?? [])]
|
|
9828
9919
|
.filter((action) => this.auth.visible(action.access))
|
|
9829
|
-
.
|
|
9920
|
+
.toSorted((a, b) => (a.order ?? 0) - (b.order ?? 0)), /* @ts-ignore */
|
|
9830
9921
|
...(ngDevMode ? [{ debugName: "actions" }] : /* istanbul ignore next */ []));
|
|
9831
9922
|
panelPaneOptions = PANEL_PANE_OPTIONS;
|
|
9832
9923
|
primaryScope = computed(() => paneRetentionScope(this.region().id, this.paneTree.primaryId(this.region().id)), /* @ts-ignore */
|
|
@@ -10120,10 +10211,14 @@ function registerTabContextMenu(registry, tabs, paneMove, popout, shell) {
|
|
|
10120
10211
|
},
|
|
10121
10212
|
];
|
|
10122
10213
|
const registered = new Set(commands.map((command) => command.id));
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
|
|
10126
|
-
|
|
10214
|
+
for (const command of commands) {
|
|
10215
|
+
registry.addCommand({ ...command, paletteHidden: true });
|
|
10216
|
+
}
|
|
10217
|
+
for (const item of items) {
|
|
10218
|
+
if (item.command !== undefined && registered.has(item.command)) {
|
|
10219
|
+
registry.addMenuItem(item);
|
|
10220
|
+
}
|
|
10221
|
+
}
|
|
10127
10222
|
}
|
|
10128
10223
|
|
|
10129
10224
|
class ContentArea {
|
|
@@ -10329,7 +10424,7 @@ class ContentGrid {
|
|
|
10329
10424
|
}, /* @ts-ignore */
|
|
10330
10425
|
...(ngDevMode ? [{ debugName: "tree" }] : /* istanbul ignore next */ []));
|
|
10331
10426
|
constructor() {
|
|
10332
|
-
const
|
|
10427
|
+
const document_ = inject(DOCUMENT);
|
|
10333
10428
|
const onKeydown = (event) => {
|
|
10334
10429
|
if (event.key === 'Escape') {
|
|
10335
10430
|
this.chrome.restore();
|
|
@@ -10339,8 +10434,8 @@ class ContentGrid {
|
|
|
10339
10434
|
if (!this.maximized()) {
|
|
10340
10435
|
return;
|
|
10341
10436
|
}
|
|
10342
|
-
|
|
10343
|
-
onCleanup(() =>
|
|
10437
|
+
document_.addEventListener('keydown', onKeydown);
|
|
10438
|
+
onCleanup(() => document_.removeEventListener('keydown', onKeydown));
|
|
10344
10439
|
});
|
|
10345
10440
|
effect(() => {
|
|
10346
10441
|
if (!this.layout.isSplit(CONTENT_DOCK)) {
|
|
@@ -10513,8 +10608,8 @@ class DialogOutlet {
|
|
|
10513
10608
|
if (!this.dialogs().length || !panels.length) {
|
|
10514
10609
|
return;
|
|
10515
10610
|
}
|
|
10516
|
-
const top = panels
|
|
10517
|
-
if (top.contains(this.document.activeElement)) {
|
|
10611
|
+
const top = panels.at(-1)?.nativeElement;
|
|
10612
|
+
if (!top || top.contains(this.document.activeElement)) {
|
|
10518
10613
|
return;
|
|
10519
10614
|
}
|
|
10520
10615
|
(top.querySelector('[data-lw-autofocus]') ?? top).focus();
|
|
@@ -10543,10 +10638,10 @@ class DialogOutlet {
|
|
|
10543
10638
|
return;
|
|
10544
10639
|
}
|
|
10545
10640
|
const first = focusables[0];
|
|
10546
|
-
const last = focusables
|
|
10641
|
+
const last = focusables.at(-1);
|
|
10547
10642
|
const active = this.document.activeElement;
|
|
10548
10643
|
if (backward && active === first) {
|
|
10549
|
-
last
|
|
10644
|
+
last?.focus();
|
|
10550
10645
|
event.preventDefault();
|
|
10551
10646
|
}
|
|
10552
10647
|
else if (!backward && active === last) {
|
|
@@ -10618,7 +10713,7 @@ class DialogOutlet {
|
|
|
10618
10713
|
}
|
|
10619
10714
|
topPanel() {
|
|
10620
10715
|
const panels = this.panels();
|
|
10621
|
-
return panels.
|
|
10716
|
+
return panels.at(-1)?.nativeElement;
|
|
10622
10717
|
}
|
|
10623
10718
|
focusable(root) {
|
|
10624
10719
|
const selector = 'button:not(:disabled), a[href], input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex="-1"])';
|
|
@@ -10626,7 +10721,7 @@ class DialogOutlet {
|
|
|
10626
10721
|
}
|
|
10627
10722
|
top() {
|
|
10628
10723
|
const list = this.dialogs();
|
|
10629
|
-
return list
|
|
10724
|
+
return list.at(-1);
|
|
10630
10725
|
}
|
|
10631
10726
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DialogOutlet, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
10632
10727
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: DialogOutlet, isStandalone: true, selector: "lw-dialog-outlet", host: { listeners: { "document:keydown.escape": "onEscape()", "document:focusin": "onFocusIn($event)", "document:keydown.tab": "onTab($event, false)", "document:keydown.shift.tab": "onTab($event, true)" } }, viewQueries: [{ propertyName: "panels", predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "@for (d of dialogs(); track d.id) {\n <div [class]=\"wrapperClasses(d)\">\n <button\n type=\"button\"\n class=\"lw-scrim absolute inset-0 cursor-default\"\n [disabled]=\"!d.dismissable\"\n [attr.aria-label]=\"'dialog.close' | transloco\"\n (click)=\"dismiss(d)\"\n ></button>\n\n @if (d.bare) {\n <dialog\n #panel\n open\n class=\"relative z-10 m-0 flex max-h-full w-full flex-col overflow-hidden rounded-t-xl border border-border bg-surface-raised shadow-xl sm:rounded-xl\"\n [class.rounded-xl]=\"d.align === 'top'\"\n [class]=\"panelWidth(d)\"\n aria-modal=\"true\"\n [attr.aria-label]=\"d.title ? (d.title | transloco) : null\"\n tabindex=\"-1\"\n >\n <ng-container\n [ngComponentOutlet]=\"d.component ?? null\"\n [ngComponentOutletInjector]=\"d.injector\"\n />\n </dialog>\n } @else {\n <dialog\n #panel\n open\n class=\"relative z-10 m-0 flex max-h-full w-full flex-col gap-4 overflow-y-auto rounded-t-xl border border-border bg-surface-raised p-5 shadow-xl sm:rounded-xl\"\n [class.rounded-xl]=\"d.align === 'top'\"\n [class]=\"panelWidth(d)\"\n aria-modal=\"true\"\n [attr.aria-label]=\"d.title ? (d.title | transloco) : null\"\n tabindex=\"-1\"\n >\n <div class=\"flex gap-3.5\">\n @if (d.icon; as icon) {\n <span\n class=\"grid h-10 w-10 shrink-0 place-items-center rounded-full\"\n [class]=\"toneCircle(d.tone)\"\n aria-hidden=\"true\"\n >\n <lw-icon [name]=\"icon\" size=\"1.25rem\" />\n </span>\n }\n\n <div class=\"flex min-w-0 flex-1 flex-col gap-3\">\n @if (d.title || (d.kind === 'custom' && d.dismissable)) {\n <div class=\"flex items-start justify-between gap-4\">\n <h2 class=\"min-w-0 text-lg font-semibold text-content\">\n @if (d.title) {\n {{ d.title | transloco }}\n }\n </h2>\n <span class=\"flex shrink-0 items-center gap-1\">\n @if (d.maximizable) {\n <button\n lwButton\n variant=\"ghost\"\n size=\"sm\"\n iconOnly\n type=\"button\"\n class=\"-mt-1\"\n [attr.aria-label]=\"\n (d.ref.maximized() ? 'dialog.restore' : 'dialog.maximize') | transloco\n \"\n (click)=\"d.ref.toggleMaximized()\"\n >\n <lw-icon [name]=\"d.ref.maximized() ? 'restore' : 'maximize'\" size=\"1rem\" />\n </button>\n }\n @if (d.kind === 'custom' && d.dismissable) {\n <button\n lwButton\n variant=\"ghost\"\n size=\"sm\"\n iconOnly\n type=\"button\"\n class=\"-mt-1 -mr-1\"\n [attr.aria-label]=\"'dialog.close' | transloco\"\n (click)=\"dismiss(d)\"\n >\n <lw-icon name=\"close\" size=\"1rem\" />\n </button>\n }\n </span>\n </div>\n }\n\n @if (d.component) {\n <ng-container\n [ngComponentOutlet]=\"d.component\"\n [ngComponentOutletInjector]=\"d.injector\"\n />\n } @else if (d.kind === 'progress') {\n <div class=\"flex items-center gap-3 text-sm text-content\">\n <lw-spinner [label]=\"'progress.busy' | transloco\" />\n @if (d.progressMessage) {\n <span>{{ d.progressMessage() | transloco }}</span>\n }\n </div>\n } @else {\n @if (d.message) {\n <lw-markdown [source]=\"d.message | transloco\" />\n }\n @if (d.requireLabel) {\n <lw-markdown [source]=\"d.requireLabel | transloco\" />\n }\n @if (d.promptValue) {\n <input\n type=\"text\"\n class=\"lw-field\"\n [class.lw-field--invalid]=\"guardError(d)\"\n [value]=\"d.promptValue()\"\n [attr.placeholder]=\"d.placeholder ? (d.placeholder | transloco) : null\"\n (input)=\"onPromptInput(d, $event)\"\n (keydown.enter)=\"onEnter(d)\"\n data-lw-autofocus\n />\n @if (guardError(d); as error) {\n <p class=\"text-xs text-negative\">{{ error | transloco }}</p>\n }\n }\n }\n </div>\n </div>\n\n @if (d.buttons.length) {\n <div class=\"flex justify-end gap-2\">\n @for (b of d.buttons; track $index) {\n <button\n lwButton\n [variant]=\"b.variant\"\n type=\"button\"\n [disabled]=\"confirmBlocked(d, b)\"\n [attr.data-lw-autofocus]=\"b.autofocus ? '' : null\"\n (click)=\"onButton(d, b)\"\n >\n {{ b.label | transloco }}\n </button>\n }\n </div>\n }\n </dialog>\n }\n </div>\n}\n", dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: LwButton, selector: "button[lwButton], a[lwButton]", inputs: ["variant", "size", "iconOnly"] }, { kind: "component", type: LwSpinner, selector: "lw-spinner", inputs: ["size", "label"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
|
|
@@ -10776,7 +10871,7 @@ class SettingsRegistry {
|
|
|
10776
10871
|
return this.sections()
|
|
10777
10872
|
.map((section) => visibleSection(section, omitted))
|
|
10778
10873
|
.filter((section) => section !== null)
|
|
10779
|
-
.
|
|
10874
|
+
.toSorted((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
10780
10875
|
}, /* @ts-ignore */
|
|
10781
10876
|
...(ngDevMode ? [{ debugName: "all" }] : /* istanbul ignore next */ []));
|
|
10782
10877
|
register(section) {
|
|
@@ -11016,6 +11111,13 @@ function provideTranslationNamespaces(...namespaces) {
|
|
|
11016
11111
|
/** Directory the distribution serves its overlay bundles from, without a trailing slash. */
|
|
11017
11112
|
const TRANSLATION_OVERRIDES = new InjectionToken('TRANSLATION_OVERRIDES');
|
|
11018
11113
|
const DEFAULT_OVERRIDES_PATH = '/i18n/overrides';
|
|
11114
|
+
function withoutTrailingSlashes(path) {
|
|
11115
|
+
let end = path.length;
|
|
11116
|
+
while (end > 0 && path[end - 1] === '/') {
|
|
11117
|
+
end -= 1;
|
|
11118
|
+
}
|
|
11119
|
+
return path.slice(0, end);
|
|
11120
|
+
}
|
|
11019
11121
|
/**
|
|
11020
11122
|
* Load `<basePath>/<lang>.json` and merge it over everything else **key by key**, so a product
|
|
11021
11123
|
* can reword the shell in its own house language ("Save as" rather than "Save as new") without
|
|
@@ -11035,7 +11137,7 @@ const DEFAULT_OVERRIDES_PATH = '/i18n/overrides';
|
|
|
11035
11137
|
* nothing ships is dev-warned too, since a typo there would otherwise be a string that never appears.
|
|
11036
11138
|
*/
|
|
11037
11139
|
function provideTranslationOverrides(basePath = DEFAULT_OVERRIDES_PATH) {
|
|
11038
|
-
const normalized = basePath
|
|
11140
|
+
const normalized = withoutTrailingSlashes(basePath);
|
|
11039
11141
|
if (normalized === '') {
|
|
11040
11142
|
throw new Error('provideTranslationOverrides() needs a directory to load overlays from; ' +
|
|
11041
11143
|
`pass one or omit the argument for "${DEFAULT_OVERRIDES_PATH}".`);
|
|
@@ -11087,7 +11189,9 @@ class TranslocoHttpLoader {
|
|
|
11087
11189
|
return forkJoin([host$, ...namespaced$, this.overrides$(lang)]).pipe(map(([host, ...rest]) => {
|
|
11088
11190
|
const overlay = rest.pop();
|
|
11089
11191
|
const merged = { ...host };
|
|
11090
|
-
|
|
11192
|
+
for (const [index, name] of this.namespaces.entries()) {
|
|
11193
|
+
merged[name] = rest[index];
|
|
11194
|
+
}
|
|
11091
11195
|
return this.applyOverrides(merged, overlay, lang);
|
|
11092
11196
|
}));
|
|
11093
11197
|
}
|
|
@@ -11158,9 +11262,7 @@ class LwIconElement extends HTMLElement {
|
|
|
11158
11262
|
this.render();
|
|
11159
11263
|
}
|
|
11160
11264
|
attributeChangedCallback() {
|
|
11161
|
-
|
|
11162
|
-
this.render();
|
|
11163
|
-
}
|
|
11265
|
+
this.refresh();
|
|
11164
11266
|
}
|
|
11165
11267
|
/**
|
|
11166
11268
|
* Re-draws from the registry without changing the name. A sandboxed surface receives the product's
|
|
@@ -11267,7 +11369,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
11267
11369
|
// GENERATED — do not edit by hand.
|
|
11268
11370
|
// Written by tools/stamp-version.mjs from <Version> in Directory.Build.props.
|
|
11269
11371
|
// Single source of truth: Directory.Build.props (bump via scripts/bump-version.sh).
|
|
11270
|
-
const APP_VERSION = '0.7.
|
|
11372
|
+
const APP_VERSION = '0.7.7';
|
|
11271
11373
|
|
|
11272
11374
|
/**
|
|
11273
11375
|
* The running build's version, sourced from `<Version>` in Directory.Build.props
|
|
@@ -11483,7 +11585,7 @@ class UpdateService {
|
|
|
11483
11585
|
await bestEffort(async () => {
|
|
11484
11586
|
const registrations = (await container?.getRegistrations()) ?? [];
|
|
11485
11587
|
await Promise.all(registrations
|
|
11486
|
-
.filter(isShellWorker)
|
|
11588
|
+
.filter((registration) => isShellWorker(registration))
|
|
11487
11589
|
.map((registration) => registration.unregister()));
|
|
11488
11590
|
});
|
|
11489
11591
|
await bestEffort(async () => {
|
|
@@ -11491,14 +11593,14 @@ class UpdateService {
|
|
|
11491
11593
|
const keys = (await storage?.keys()) ?? [];
|
|
11492
11594
|
await Promise.all(keys
|
|
11493
11595
|
.filter((key) => key.startsWith(WORKER_CACHE_PREFIX))
|
|
11494
|
-
.map((key) => storage?.delete(key)));
|
|
11596
|
+
.map(async (key) => storage?.delete(key)));
|
|
11495
11597
|
});
|
|
11496
11598
|
}
|
|
11497
11599
|
onVersionEvent(event) {
|
|
11498
11600
|
if (event.type === 'VERSION_READY') {
|
|
11499
11601
|
this.onUpdateReady();
|
|
11500
11602
|
}
|
|
11501
|
-
if (event.type === 'VERSION_INSTALLATION_FAILED') {
|
|
11603
|
+
else if (event.type === 'VERSION_INSTALLATION_FAILED') {
|
|
11502
11604
|
this.onUpdateFailed();
|
|
11503
11605
|
}
|
|
11504
11606
|
}
|
|
@@ -11862,7 +11964,7 @@ class CommandInvocationService {
|
|
|
11862
11964
|
.filter((entry) => entry.command.callable === true &&
|
|
11863
11965
|
this.reachable(entry, callerId, granted))
|
|
11864
11966
|
.map((entry) => this.describe(entry.command))
|
|
11865
|
-
.
|
|
11967
|
+
.toSorted((a, b) => a.id.localeCompare(b.id));
|
|
11866
11968
|
}
|
|
11867
11969
|
async invoke(callerId, granted, id, args) {
|
|
11868
11970
|
const entry = this.registry
|
|
@@ -12182,17 +12284,21 @@ class LwTooltipElement extends HTMLElement {
|
|
|
12182
12284
|
const centerX = t.left + t.width / 2 - b.width / 2;
|
|
12183
12285
|
const centerY = t.top + t.height / 2 - b.height / 2;
|
|
12184
12286
|
switch (this.position) {
|
|
12185
|
-
case 'bottom':
|
|
12287
|
+
case 'bottom': {
|
|
12186
12288
|
[left, top] = [centerX, t.bottom + TOOLTIP_GAP];
|
|
12187
12289
|
break;
|
|
12188
|
-
|
|
12290
|
+
}
|
|
12291
|
+
case 'left': {
|
|
12189
12292
|
[left, top] = [t.left - b.width - TOOLTIP_GAP, centerY];
|
|
12190
12293
|
break;
|
|
12191
|
-
|
|
12294
|
+
}
|
|
12295
|
+
case 'right': {
|
|
12192
12296
|
[left, top] = [t.right + TOOLTIP_GAP, centerY];
|
|
12193
12297
|
break;
|
|
12194
|
-
|
|
12298
|
+
}
|
|
12299
|
+
default: {
|
|
12195
12300
|
[left, top] = [centerX, t.top - b.height - TOOLTIP_GAP];
|
|
12301
|
+
}
|
|
12196
12302
|
}
|
|
12197
12303
|
}
|
|
12198
12304
|
else {
|
|
@@ -12217,10 +12323,11 @@ class LwTooltipElement extends HTMLElement {
|
|
|
12217
12323
|
}
|
|
12218
12324
|
}
|
|
12219
12325
|
clearTimer() {
|
|
12220
|
-
if (this.showTimer
|
|
12221
|
-
|
|
12222
|
-
this.showTimer = undefined;
|
|
12326
|
+
if (this.showTimer === undefined) {
|
|
12327
|
+
return;
|
|
12223
12328
|
}
|
|
12329
|
+
clearTimeout(this.showTimer);
|
|
12330
|
+
this.showTimer = undefined;
|
|
12224
12331
|
}
|
|
12225
12332
|
}
|
|
12226
12333
|
/** Registers `<lw-tooltip>` once (idempotent) — called from {@link provideShell} at bootstrap. */
|
|
@@ -12231,11 +12338,7 @@ function defineLwTooltip() {
|
|
|
12231
12338
|
}
|
|
12232
12339
|
}
|
|
12233
12340
|
|
|
12234
|
-
const LW_SELECT_TAG = 'lw-select';
|
|
12235
12341
|
const LW_OPTION_TAG = 'lw-option';
|
|
12236
|
-
const LW_SELECT_CHANGE = 'lw-select-change';
|
|
12237
|
-
let nextSelectId = 0;
|
|
12238
|
-
const TYPEAHEAD_RESET_MS = 500;
|
|
12239
12342
|
class LwOptionElement extends HTMLElement {
|
|
12240
12343
|
get value() {
|
|
12241
12344
|
return this.getAttribute('value');
|
|
@@ -12254,6 +12357,87 @@ class LwOptionElement extends HTMLElement {
|
|
|
12254
12357
|
upgradeElementProperty(this, 'icon');
|
|
12255
12358
|
}
|
|
12256
12359
|
}
|
|
12360
|
+
|
|
12361
|
+
function readChoices(host) {
|
|
12362
|
+
return [...host.querySelectorAll(LW_OPTION_TAG)].map((option) => ({
|
|
12363
|
+
value: option.getAttribute('value') ?? '',
|
|
12364
|
+
label: (option.textContent ?? '').trim(),
|
|
12365
|
+
icon: option.getAttribute('icon'),
|
|
12366
|
+
disabled: option.hasAttribute('disabled'),
|
|
12367
|
+
}));
|
|
12368
|
+
}
|
|
12369
|
+
function createTrigger(options) {
|
|
12370
|
+
const trigger = document.createElement('button');
|
|
12371
|
+
trigger.type = 'button';
|
|
12372
|
+
trigger.className = 'lw-select-trigger';
|
|
12373
|
+
trigger.setAttribute('aria-haspopup', 'listbox');
|
|
12374
|
+
trigger.setAttribute('aria-expanded', 'false');
|
|
12375
|
+
trigger.setAttribute('aria-controls', options.listboxId);
|
|
12376
|
+
trigger.style.setProperty('anchor-name', options.anchorName);
|
|
12377
|
+
trigger.addEventListener('click', options.onToggle);
|
|
12378
|
+
trigger.addEventListener('keydown', options.onKeydown);
|
|
12379
|
+
const valueSlot = document.createElement('span');
|
|
12380
|
+
valueSlot.className = 'lw-select-value';
|
|
12381
|
+
const chevron = document.createElement('span');
|
|
12382
|
+
chevron.className = 'lw-select-chevron';
|
|
12383
|
+
chevron.setAttribute('aria-hidden', 'true');
|
|
12384
|
+
chevron.textContent = '▾';
|
|
12385
|
+
trigger.append(valueSlot, chevron);
|
|
12386
|
+
return { trigger, valueSlot };
|
|
12387
|
+
}
|
|
12388
|
+
function createListbox(options) {
|
|
12389
|
+
const listbox = document.createElement('div');
|
|
12390
|
+
listbox.id = options.listboxId;
|
|
12391
|
+
listbox.className = 'lw-select-listbox';
|
|
12392
|
+
listbox.setAttribute('role', 'listbox');
|
|
12393
|
+
listbox.hidden = true;
|
|
12394
|
+
listbox.style.setProperty('position-anchor', options.anchorName);
|
|
12395
|
+
listbox.addEventListener('keydown', options.onKeydown);
|
|
12396
|
+
return listbox;
|
|
12397
|
+
}
|
|
12398
|
+
function fillValueSlot(slot, text, icon) {
|
|
12399
|
+
slot.textContent = '';
|
|
12400
|
+
if (icon) {
|
|
12401
|
+
slot.append(createGlyph(icon));
|
|
12402
|
+
}
|
|
12403
|
+
slot.append(document.createTextNode(text));
|
|
12404
|
+
}
|
|
12405
|
+
function createOptionRow(options) {
|
|
12406
|
+
const choice = options.choice;
|
|
12407
|
+
const row = document.createElement('div');
|
|
12408
|
+
row.className = 'lw-select-option';
|
|
12409
|
+
row.setAttribute('role', 'option');
|
|
12410
|
+
row.id = options.id;
|
|
12411
|
+
row.dataset['value'] = choice.value;
|
|
12412
|
+
row.setAttribute('aria-selected', String(options.selected));
|
|
12413
|
+
row.tabIndex = -1;
|
|
12414
|
+
if (choice.disabled) {
|
|
12415
|
+
row.setAttribute('aria-disabled', 'true');
|
|
12416
|
+
}
|
|
12417
|
+
if (choice.icon) {
|
|
12418
|
+
row.append(createGlyph(choice.icon));
|
|
12419
|
+
}
|
|
12420
|
+
row.append(document.createTextNode(choice.label));
|
|
12421
|
+
row.addEventListener('click', () => {
|
|
12422
|
+
if (!choice.disabled) {
|
|
12423
|
+
options.onPick();
|
|
12424
|
+
}
|
|
12425
|
+
});
|
|
12426
|
+
row.addEventListener('pointermove', options.onHover);
|
|
12427
|
+
return row;
|
|
12428
|
+
}
|
|
12429
|
+
function createGlyph(icon) {
|
|
12430
|
+
const glyph = document.createElement('span');
|
|
12431
|
+
glyph.className = 'lw-select-glyph';
|
|
12432
|
+
glyph.setAttribute('aria-hidden', 'true');
|
|
12433
|
+
glyph.textContent = icon;
|
|
12434
|
+
return glyph;
|
|
12435
|
+
}
|
|
12436
|
+
|
|
12437
|
+
const LW_SELECT_TAG = 'lw-select';
|
|
12438
|
+
const LW_SELECT_CHANGE = 'lw-select-change';
|
|
12439
|
+
let nextSelectId = 0;
|
|
12440
|
+
const TYPEAHEAD_RESET_MS = 500;
|
|
12257
12441
|
class LwSelectElement extends HTMLElement {
|
|
12258
12442
|
static observedAttributes = [
|
|
12259
12443
|
'value',
|
|
@@ -12298,10 +12482,7 @@ class LwSelectElement extends HTMLElement {
|
|
|
12298
12482
|
if (!this.trigger) {
|
|
12299
12483
|
return;
|
|
12300
12484
|
}
|
|
12301
|
-
if (name
|
|
12302
|
-
name === 'label' ||
|
|
12303
|
-
name === 'placeholder' ||
|
|
12304
|
-
name === 'disabled') {
|
|
12485
|
+
if (LwSelectElement.observedAttributes.includes(name)) {
|
|
12305
12486
|
this.syncTrigger();
|
|
12306
12487
|
}
|
|
12307
12488
|
if (name === 'disabled' && this.hasAttribute('disabled')) {
|
|
@@ -12321,51 +12502,34 @@ class LwSelectElement extends HTMLElement {
|
|
|
12321
12502
|
attributes: true,
|
|
12322
12503
|
});
|
|
12323
12504
|
}
|
|
12324
|
-
write(
|
|
12505
|
+
write(function_) {
|
|
12325
12506
|
this.observer?.disconnect();
|
|
12326
12507
|
try {
|
|
12327
|
-
|
|
12508
|
+
function_();
|
|
12328
12509
|
}
|
|
12329
12510
|
finally {
|
|
12330
12511
|
this.observe();
|
|
12331
12512
|
}
|
|
12332
12513
|
}
|
|
12333
12514
|
choices() {
|
|
12334
|
-
return
|
|
12335
|
-
value: option.getAttribute('value') ?? '',
|
|
12336
|
-
label: (option.textContent ?? '').trim(),
|
|
12337
|
-
icon: option.getAttribute('icon'),
|
|
12338
|
-
disabled: option.hasAttribute('disabled'),
|
|
12339
|
-
}));
|
|
12515
|
+
return readChoices(this);
|
|
12340
12516
|
}
|
|
12341
12517
|
selectedChoice() {
|
|
12342
12518
|
const value = this.value;
|
|
12343
12519
|
return this.choices().find((choice) => choice.value === value);
|
|
12344
12520
|
}
|
|
12345
12521
|
buildControl() {
|
|
12346
|
-
const trigger =
|
|
12347
|
-
|
|
12348
|
-
|
|
12349
|
-
|
|
12350
|
-
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
|
|
12354
|
-
|
|
12355
|
-
|
|
12356
|
-
|
|
12357
|
-
const chevron = document.createElement('span');
|
|
12358
|
-
chevron.className = 'lw-select-chevron';
|
|
12359
|
-
chevron.setAttribute('aria-hidden', 'true');
|
|
12360
|
-
chevron.textContent = '▾';
|
|
12361
|
-
trigger.append(valueSlot, chevron);
|
|
12362
|
-
const listbox = document.createElement('div');
|
|
12363
|
-
listbox.id = this.listboxId;
|
|
12364
|
-
listbox.className = 'lw-select-listbox';
|
|
12365
|
-
listbox.setAttribute('role', 'listbox');
|
|
12366
|
-
listbox.hidden = true;
|
|
12367
|
-
listbox.style.setProperty('position-anchor', this.anchorName);
|
|
12368
|
-
listbox.addEventListener('keydown', (event) => this.onListboxKeydown(event));
|
|
12522
|
+
const { trigger, valueSlot } = createTrigger({
|
|
12523
|
+
anchorName: this.anchorName,
|
|
12524
|
+
listboxId: this.listboxId,
|
|
12525
|
+
onToggle: () => this.toggle(),
|
|
12526
|
+
onKeydown: (event) => this.onTriggerKeydown(event),
|
|
12527
|
+
});
|
|
12528
|
+
const listbox = createListbox({
|
|
12529
|
+
anchorName: this.anchorName,
|
|
12530
|
+
listboxId: this.listboxId,
|
|
12531
|
+
onKeydown: (event) => this.onListboxKeydown(event),
|
|
12532
|
+
});
|
|
12369
12533
|
this.append(trigger, listbox);
|
|
12370
12534
|
this.trigger = trigger;
|
|
12371
12535
|
this.valueSlot = valueSlot;
|
|
@@ -12380,22 +12544,13 @@ class LwSelectElement extends HTMLElement {
|
|
|
12380
12544
|
const label = this.getAttribute('label');
|
|
12381
12545
|
const selected = this.selectedChoice();
|
|
12382
12546
|
const text = selected?.label ?? this.getAttribute('placeholder') ?? '';
|
|
12383
|
-
const icon = selected?.icon ?? null;
|
|
12384
12547
|
this.write(() => {
|
|
12385
12548
|
trigger.disabled = this.hasAttribute('disabled');
|
|
12386
12549
|
if (label !== null) {
|
|
12387
12550
|
trigger.setAttribute('aria-label', label);
|
|
12388
12551
|
this.listbox?.setAttribute('aria-label', label);
|
|
12389
12552
|
}
|
|
12390
|
-
valueSlot
|
|
12391
|
-
if (icon) {
|
|
12392
|
-
const glyph = document.createElement('span');
|
|
12393
|
-
glyph.className = 'lw-select-glyph';
|
|
12394
|
-
glyph.setAttribute('aria-hidden', 'true');
|
|
12395
|
-
glyph.textContent = icon;
|
|
12396
|
-
valueSlot.append(glyph);
|
|
12397
|
-
}
|
|
12398
|
-
valueSlot.append(document.createTextNode(text));
|
|
12553
|
+
fillValueSlot(valueSlot, text, selected?.icon ?? null);
|
|
12399
12554
|
});
|
|
12400
12555
|
}
|
|
12401
12556
|
toggle() {
|
|
@@ -12421,7 +12576,9 @@ class LwSelectElement extends HTMLElement {
|
|
|
12421
12576
|
const choices = this.choices();
|
|
12422
12577
|
const selected = choices.findIndex((choice) => choice.value === this.value);
|
|
12423
12578
|
this.setActive(Math.max(0, selected));
|
|
12424
|
-
document.addEventListener('pointerdown', this.onOutsidePointer,
|
|
12579
|
+
document.addEventListener('pointerdown', this.onOutsidePointer, {
|
|
12580
|
+
capture: true,
|
|
12581
|
+
});
|
|
12425
12582
|
}
|
|
12426
12583
|
close(refocusTrigger = true) {
|
|
12427
12584
|
const listbox = this.listbox;
|
|
@@ -12441,94 +12598,83 @@ class LwSelectElement extends HTMLElement {
|
|
|
12441
12598
|
}
|
|
12442
12599
|
}
|
|
12443
12600
|
renderOptions() {
|
|
12444
|
-
|
|
12601
|
+
const listbox = this.listbox;
|
|
12602
|
+
if (!listbox) {
|
|
12445
12603
|
return;
|
|
12446
12604
|
}
|
|
12447
|
-
const
|
|
12448
|
-
|
|
12449
|
-
|
|
12450
|
-
|
|
12451
|
-
|
|
12452
|
-
|
|
12453
|
-
|
|
12454
|
-
|
|
12455
|
-
if (choice.disabled) {
|
|
12456
|
-
option.setAttribute('aria-disabled', 'true');
|
|
12457
|
-
}
|
|
12458
|
-
if (choice.icon) {
|
|
12459
|
-
const glyph = document.createElement('span');
|
|
12460
|
-
glyph.className = 'lw-select-glyph';
|
|
12461
|
-
glyph.setAttribute('aria-hidden', 'true');
|
|
12462
|
-
glyph.textContent = choice.icon;
|
|
12463
|
-
option.append(glyph);
|
|
12464
|
-
}
|
|
12465
|
-
option.append(document.createTextNode(choice.label));
|
|
12466
|
-
option.addEventListener('click', () => {
|
|
12467
|
-
if (!choice.disabled) {
|
|
12468
|
-
this.commit(choice.value);
|
|
12469
|
-
}
|
|
12470
|
-
});
|
|
12471
|
-
option.addEventListener('pointermove', () => this.setActive(index));
|
|
12472
|
-
return option;
|
|
12473
|
-
});
|
|
12474
|
-
const listbox = this.listbox;
|
|
12475
|
-
this.write(() => listbox.replaceChildren(...items));
|
|
12605
|
+
const rows = this.choices().map((choice, index) => createOptionRow({
|
|
12606
|
+
choice,
|
|
12607
|
+
id: `${this.listboxId}-opt-${index}`,
|
|
12608
|
+
selected: choice.value === this.value,
|
|
12609
|
+
onPick: () => this.commit(choice.value),
|
|
12610
|
+
onHover: () => this.setActive(index),
|
|
12611
|
+
}));
|
|
12612
|
+
this.write(() => listbox.replaceChildren(...rows));
|
|
12476
12613
|
}
|
|
12477
12614
|
setActive(index) {
|
|
12478
12615
|
if (!this.listbox) {
|
|
12479
12616
|
return;
|
|
12480
12617
|
}
|
|
12481
|
-
const
|
|
12482
|
-
if (
|
|
12618
|
+
const rows = [...this.listbox.children];
|
|
12619
|
+
if (rows.length === 0) {
|
|
12483
12620
|
return;
|
|
12484
12621
|
}
|
|
12485
|
-
this.activeIndex = Math.max(0, Math.min(index,
|
|
12622
|
+
this.activeIndex = Math.max(0, Math.min(index, rows.length - 1));
|
|
12486
12623
|
this.write(() => {
|
|
12487
|
-
|
|
12488
|
-
|
|
12489
|
-
|
|
12490
|
-
}
|
|
12624
|
+
for (const [index_, row] of rows.entries()) {
|
|
12625
|
+
row.classList.toggle('is-active', index_ === this.activeIndex);
|
|
12626
|
+
row.tabIndex = index_ === this.activeIndex ? 0 : -1;
|
|
12627
|
+
}
|
|
12491
12628
|
});
|
|
12492
|
-
const active =
|
|
12629
|
+
const active = rows[this.activeIndex];
|
|
12493
12630
|
active.focus();
|
|
12494
12631
|
active.scrollIntoView?.({ block: 'nearest' });
|
|
12495
12632
|
}
|
|
12496
12633
|
onTriggerKeydown(event) {
|
|
12497
|
-
if (['ArrowDown', 'ArrowUp', 'Enter', ' '].includes(event.key)) {
|
|
12498
|
-
|
|
12499
|
-
this.openListbox();
|
|
12634
|
+
if (!['ArrowDown', 'ArrowUp', 'Enter', ' '].includes(event.key)) {
|
|
12635
|
+
return;
|
|
12500
12636
|
}
|
|
12637
|
+
event.preventDefault();
|
|
12638
|
+
this.openListbox();
|
|
12501
12639
|
}
|
|
12502
12640
|
onListboxKeydown(event) {
|
|
12503
12641
|
const last = this.choices().length - 1;
|
|
12504
12642
|
switch (event.key) {
|
|
12505
|
-
case 'ArrowDown':
|
|
12643
|
+
case 'ArrowDown': {
|
|
12506
12644
|
this.setActive(this.activeIndex >= last ? 0 : this.activeIndex + 1);
|
|
12507
12645
|
break;
|
|
12508
|
-
|
|
12646
|
+
}
|
|
12647
|
+
case 'ArrowUp': {
|
|
12509
12648
|
this.setActive(this.activeIndex <= 0 ? last : this.activeIndex - 1);
|
|
12510
12649
|
break;
|
|
12511
|
-
|
|
12650
|
+
}
|
|
12651
|
+
case 'Home': {
|
|
12512
12652
|
this.setActive(0);
|
|
12513
12653
|
break;
|
|
12514
|
-
|
|
12654
|
+
}
|
|
12655
|
+
case 'End': {
|
|
12515
12656
|
this.setActive(last);
|
|
12516
12657
|
break;
|
|
12658
|
+
}
|
|
12517
12659
|
case 'Enter':
|
|
12518
|
-
case ' ':
|
|
12660
|
+
case ' ': {
|
|
12519
12661
|
this.commitActive();
|
|
12520
12662
|
break;
|
|
12521
|
-
|
|
12663
|
+
}
|
|
12664
|
+
case 'Escape': {
|
|
12522
12665
|
this.close();
|
|
12523
12666
|
break;
|
|
12524
|
-
|
|
12667
|
+
}
|
|
12668
|
+
case 'Tab': {
|
|
12525
12669
|
this.close(false);
|
|
12526
12670
|
return;
|
|
12527
|
-
|
|
12671
|
+
}
|
|
12672
|
+
default: {
|
|
12528
12673
|
if (event.key.length === 1) {
|
|
12529
12674
|
this.onTypeahead(event.key);
|
|
12530
12675
|
}
|
|
12531
12676
|
return;
|
|
12677
|
+
}
|
|
12532
12678
|
}
|
|
12533
12679
|
event.preventDefault();
|
|
12534
12680
|
}
|
|
@@ -12540,7 +12686,7 @@ class LwSelectElement extends HTMLElement {
|
|
|
12540
12686
|
this.typeaheadTimer = setTimeout(() => (this.typeahead = ''), TYPEAHEAD_RESET_MS);
|
|
12541
12687
|
const match = this.choices().findIndex((choice) => !choice.disabled &&
|
|
12542
12688
|
choice.label.toLowerCase().startsWith(this.typeahead));
|
|
12543
|
-
if (match
|
|
12689
|
+
if (match !== -1) {
|
|
12544
12690
|
this.setActive(match);
|
|
12545
12691
|
}
|
|
12546
12692
|
}
|
|
@@ -12689,10 +12835,11 @@ class LwButtonElement extends HTMLElement {
|
|
|
12689
12835
|
}
|
|
12690
12836
|
}
|
|
12691
12837
|
onKeydown = (event) => {
|
|
12692
|
-
if ((event.key === 'Enter' || event.key === ' ')
|
|
12693
|
-
|
|
12694
|
-
this.click();
|
|
12838
|
+
if (!(event.key === 'Enter' || event.key === ' ') || this.disabled) {
|
|
12839
|
+
return;
|
|
12695
12840
|
}
|
|
12841
|
+
event.preventDefault();
|
|
12842
|
+
this.click();
|
|
12696
12843
|
};
|
|
12697
12844
|
render() {
|
|
12698
12845
|
const stale = [...this.classList].filter((cls) => cls.startsWith('lw-btn'));
|
|
@@ -12871,7 +13018,7 @@ class ViewVisibilityService {
|
|
|
12871
13018
|
return this.stash
|
|
12872
13019
|
.keyedInstances()
|
|
12873
13020
|
.filter((entry) => !entry.key.startsWith(CONTAINER_DOCK_PREFIX) &&
|
|
12874
|
-
entry.key.split('|')[1] === path)
|
|
13021
|
+
entry.key.split('|', 2)[1] === path)
|
|
12875
13022
|
.map((entry) => entry.instance);
|
|
12876
13023
|
}
|
|
12877
13024
|
removeTabs(path) {
|
|
@@ -12943,10 +13090,11 @@ class RailWorkspaceEntries {
|
|
|
12943
13090
|
reconcile() {
|
|
12944
13091
|
const wanted = this.wantedItems();
|
|
12945
13092
|
for (const [id, registration] of this.registered) {
|
|
12946
|
-
if (
|
|
12947
|
-
|
|
12948
|
-
this.registered.delete(id);
|
|
13093
|
+
if (wanted.has(id)) {
|
|
13094
|
+
continue;
|
|
12949
13095
|
}
|
|
13096
|
+
registration.disposable.dispose();
|
|
13097
|
+
this.registered.delete(id);
|
|
12950
13098
|
}
|
|
12951
13099
|
for (const [id, item] of wanted) {
|
|
12952
13100
|
const current = this.registered.get(id);
|
|
@@ -13188,10 +13336,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
13188
13336
|
type: Service
|
|
13189
13337
|
}] });
|
|
13190
13338
|
function installCompositionReport(report) {
|
|
13191
|
-
if (
|
|
13339
|
+
if (globalThis.window === undefined) {
|
|
13192
13340
|
return;
|
|
13193
13341
|
}
|
|
13194
|
-
const host =
|
|
13342
|
+
const host = globalThis;
|
|
13195
13343
|
if (host['loomweaver'] !== undefined) {
|
|
13196
13344
|
return;
|
|
13197
13345
|
}
|
|
@@ -13262,7 +13410,7 @@ class PluginEnablementService {
|
|
|
13262
13410
|
const disabled = this.disabledSet();
|
|
13263
13411
|
return [...this.names().entries()]
|
|
13264
13412
|
.map(([id, name]) => ({ id, name, enabled: !disabled.has(id) }))
|
|
13265
|
-
.
|
|
13413
|
+
.toSorted((a, b) => a.name.localeCompare(b.name));
|
|
13266
13414
|
}, /* @ts-ignore */
|
|
13267
13415
|
...(ngDevMode ? [{ debugName: "plugins" }] : /* istanbul ignore next */ []));
|
|
13268
13416
|
constructor() {
|
|
@@ -13416,7 +13564,7 @@ function parseCatalogEntry(raw) {
|
|
|
13416
13564
|
function dedupeById(items) {
|
|
13417
13565
|
const result = [];
|
|
13418
13566
|
for (const item of items) {
|
|
13419
|
-
if (item &&
|
|
13567
|
+
if (item && result.every((existing) => existing.id !== item.id)) {
|
|
13420
13568
|
result.push(item);
|
|
13421
13569
|
}
|
|
13422
13570
|
}
|
|
@@ -13431,7 +13579,7 @@ function parseInstalledList(raw) {
|
|
|
13431
13579
|
if (!Array.isArray(parsed)) {
|
|
13432
13580
|
return [];
|
|
13433
13581
|
}
|
|
13434
|
-
return dedupeById(parsed.map(parseInstalledPlugin));
|
|
13582
|
+
return dedupeById(parsed.map((raw) => parseInstalledPlugin(raw)));
|
|
13435
13583
|
}
|
|
13436
13584
|
catch {
|
|
13437
13585
|
return [];
|
|
@@ -13441,7 +13589,7 @@ function parseCatalogList(raw) {
|
|
|
13441
13589
|
if (!Array.isArray(raw)) {
|
|
13442
13590
|
return [];
|
|
13443
13591
|
}
|
|
13444
|
-
return dedupeById(raw.map(parseCatalogEntry));
|
|
13592
|
+
return dedupeById(raw.map((entry) => parseCatalogEntry(entry)));
|
|
13445
13593
|
}
|
|
13446
13594
|
|
|
13447
13595
|
const STORAGE_KEY$2 = 'lw.shell.deployed-plugins';
|
|
@@ -13469,7 +13617,7 @@ class PluginDeploymentService {
|
|
|
13469
13617
|
adopt(entries) {
|
|
13470
13618
|
this.persist(entries
|
|
13471
13619
|
.filter((entry) => entry.deployed === true)
|
|
13472
|
-
.map(withoutCatalogMetadata));
|
|
13620
|
+
.map((entry) => withoutCatalogMetadata(entry)));
|
|
13473
13621
|
}
|
|
13474
13622
|
isDeployed(id) {
|
|
13475
13623
|
return this.entries().some((entry) => entry.id === id);
|
|
@@ -13587,10 +13735,10 @@ function registerDefaultSettings(settings) {
|
|
|
13587
13735
|
|
|
13588
13736
|
function fuzzyScore(query, label) {
|
|
13589
13737
|
const needle = query.toLowerCase();
|
|
13590
|
-
const haystack = label.toLowerCase();
|
|
13591
13738
|
if (!needle) {
|
|
13592
13739
|
return 0;
|
|
13593
13740
|
}
|
|
13741
|
+
const haystack = label.toLowerCase();
|
|
13594
13742
|
let score = 0;
|
|
13595
13743
|
let searchFrom = 0;
|
|
13596
13744
|
let previous = -2;
|
|
@@ -13620,7 +13768,7 @@ function formatterFor(locale) {
|
|
|
13620
13768
|
}
|
|
13621
13769
|
catch {
|
|
13622
13770
|
try {
|
|
13623
|
-
return new Intl.RelativeTimeFormat(locale.
|
|
13771
|
+
return new Intl.RelativeTimeFormat(locale.replaceAll('_', '-'), {
|
|
13624
13772
|
numeric: 'auto',
|
|
13625
13773
|
});
|
|
13626
13774
|
}
|
|
@@ -13697,7 +13845,7 @@ function ranked(query, entries) {
|
|
|
13697
13845
|
return entries
|
|
13698
13846
|
.map((entry) => ({ entry, score: fuzzyScore(query, entry.label) }))
|
|
13699
13847
|
.filter((scored) => scored.score !== null)
|
|
13700
|
-
.
|
|
13848
|
+
.toSorted((a, b) => b.score - a.score)
|
|
13701
13849
|
.map((scored) => scored.entry);
|
|
13702
13850
|
}
|
|
13703
13851
|
class CommandPalette {
|
|
@@ -13719,6 +13867,8 @@ class CommandPalette {
|
|
|
13719
13867
|
? 'tabs'
|
|
13720
13868
|
: 'commands', /* @ts-ignore */
|
|
13721
13869
|
...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
|
|
13870
|
+
title = computed(() => this.mode() === 'tabs' ? 'palette.quickOpenTitle' : 'palette.title', /* @ts-ignore */
|
|
13871
|
+
...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
13722
13872
|
query = signal('', /* @ts-ignore */
|
|
13723
13873
|
...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
|
|
13724
13874
|
rawIndex = signal(0, /* @ts-ignore */
|
|
@@ -13754,9 +13904,9 @@ class CommandPalette {
|
|
|
13754
13904
|
pinned: tab.pinned,
|
|
13755
13905
|
closable: tab.closable,
|
|
13756
13906
|
lastActive: tab.lastActive,
|
|
13757
|
-
time: tab.lastActive
|
|
13758
|
-
?
|
|
13759
|
-
:
|
|
13907
|
+
time: tab.lastActive === undefined
|
|
13908
|
+
? undefined
|
|
13909
|
+
: formatRelativeTime(locale, tab.lastActive, now),
|
|
13760
13910
|
}));
|
|
13761
13911
|
}, /* @ts-ignore */
|
|
13762
13912
|
...(ngDevMode ? [{ debugName: "tabEntries" }] : /* istanbul ignore next */ []));
|
|
@@ -13785,7 +13935,7 @@ class CommandPalette {
|
|
|
13785
13935
|
const query = this.query().trim();
|
|
13786
13936
|
const entries = this.tabEntries();
|
|
13787
13937
|
if (!query) {
|
|
13788
|
-
return [...entries].
|
|
13938
|
+
return [...entries].toSorted((a, b) => (b.lastActive ?? 0) - (a.lastActive ?? 0));
|
|
13789
13939
|
}
|
|
13790
13940
|
return ranked(query, entries);
|
|
13791
13941
|
}, /* @ts-ignore */
|
|
@@ -13860,7 +14010,7 @@ class CommandPalette {
|
|
|
13860
14010
|
return;
|
|
13861
14011
|
}
|
|
13862
14012
|
const entry = this.results()[this.activeIndex()];
|
|
13863
|
-
if (
|
|
14013
|
+
if (entry?.kind !== 'tab') {
|
|
13864
14014
|
return;
|
|
13865
14015
|
}
|
|
13866
14016
|
event.preventDefault();
|
|
@@ -13884,11 +14034,11 @@ class CommandPalette {
|
|
|
13884
14034
|
}
|
|
13885
14035
|
}
|
|
13886
14036
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: CommandPalette, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13887
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: CommandPalette, isStandalone: true, selector: "lw-command-palette", ngImport: i0, template: "<div class=\"flex max-h-[60vh] w-full flex-col overflow-hidden\">\n <div class=\"flex items-center gap-2 border-b border-border px-4\">\n <lw-icon name=\"search\" size=\"1.1rem\" class=\"text-content-muted\" />\n <input\n data-lw-autofocus\n type=\"text\"\n role=\"combobox\"\n aria-controls=\"lw-palette-list\"\n [attr.aria-expanded]=\"true\"\n [attr.aria-activedescendant]=\"activeId()\"\n [attr.aria-label]=\"
|
|
14037
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: CommandPalette, isStandalone: true, selector: "lw-command-palette", ngImport: i0, template: "<div class=\"flex max-h-[60vh] w-full flex-col overflow-hidden\">\n <div class=\"flex items-center gap-2 border-b border-border px-4\">\n <lw-icon name=\"search\" size=\"1.1rem\" class=\"text-content-muted\" />\n <input\n data-lw-autofocus\n type=\"text\"\n role=\"combobox\"\n aria-controls=\"lw-palette-list\"\n [attr.aria-expanded]=\"true\"\n [attr.aria-activedescendant]=\"activeId()\"\n [attr.aria-label]=\"title() | transloco\"\n [value]=\"query()\"\n [placeholder]=\"\n (mode() === 'tabs' ? 'palette.tabsPlaceholder' : 'palette.placeholder')\n | transloco\n \"\n class=\"flex-1 bg-transparent py-3 text-sm text-content outline-none placeholder:text-content-faint\"\n (input)=\"onQuery($event)\"\n (keydown.arrowdown)=\"move($event, 1)\"\n (keydown.arrowup)=\"move($event, -1)\"\n (keydown.arrowright)=\"openTabActions($event)\"\n (keydown.enter)=\"runActive($event)\"\n />\n </div>\n\n <ul\n id=\"lw-palette-list\"\n role=\"listbox\"\n class=\"min-h-0 flex-1 overflow-y-auto p-1\"\n >\n @for (entry of results(); track entry.id; let i = $index) {\n @if (recentCount() > 0 && i === 0) {\n <li\n role=\"presentation\"\n data-testid=\"palette-section-recent\"\n class=\"px-3 pt-2 pb-1 text-xs font-medium text-content-faint\"\n >\n {{ 'palette.recent' | transloco }}\n </li>\n }\n @if (recentCount() > 0 && i === recentCount()) {\n <li\n role=\"presentation\"\n data-testid=\"palette-section-all\"\n class=\"px-3 pt-2 pb-1 text-xs font-medium text-content-faint\"\n >\n {{ 'palette.all' | transloco }}\n </li>\n }\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"optionId(i)\"\n [attr.aria-selected]=\"i === activeIndex()\"\n class=\"flex cursor-pointer items-center gap-3 rounded-md px-3 py-2 text-sm\"\n [class]=\"\n i === activeIndex()\n ? 'bg-brand/10 text-brand-text'\n : 'text-content hover:bg-surface-overlay'\n \"\n (click)=\"select(entry)\"\n (mouseenter)=\"setActive(i)\"\n >\n @if (entry.icon; as icon) {\n <lw-icon [name]=\"icon\" size=\"1rem\" class=\"text-content-muted\" />\n }\n <span class=\"flex-1 truncate\">{{ entry.label }}</span>\n @if (timeOf(entry); as time) {\n <span class=\"shrink-0 text-xs text-content-faint\">{{ time }}</span>\n }\n @if (shortcutOf(entry); as shortcut) {\n <kbd\n class=\"rounded border border-border px-1.5 py-0.5 text-xs text-content-muted\"\n >{{ shortcut }}</kbd\n >\n }\n </li>\n } @empty {\n <li class=\"px-3 py-6 text-center text-sm text-content-muted\">\n {{\n (mode() === 'tabs' ? 'palette.tabsEmpty' : 'palette.empty') | transloco\n }}\n </li>\n }\n </ul>\n\n <div\n data-testid=\"palette-footer\"\n class=\"flex items-center gap-4 border-t border-border px-4 py-2 text-xs text-content-faint\"\n >\n <span class=\"flex items-center gap-1.5\">\n <kbd class=\"rounded border border-border px-1 py-0.5\">\u2191\u2193</kbd>\n {{ 'palette.hint.navigate' | transloco }}\n </span>\n <span class=\"flex items-center gap-1.5\">\n <kbd class=\"rounded border border-border px-1 py-0.5\">\u21B5</kbd>\n {{ 'palette.hint.run' | transloco }}\n </span>\n @if (mode() === 'tabs') {\n <span class=\"flex items-center gap-1.5\" data-testid=\"palette-hint-actions\">\n <kbd class=\"rounded border border-border px-1 py-0.5\">\u2192</kbd>\n {{ 'palette.hint.actions' | transloco }}\n </span>\n }\n <span class=\"ml-auto flex items-center gap-1.5\">\n <kbd class=\"rounded border border-border px-1 py-0.5\">Esc</kbd>\n {{ 'palette.hint.close' | transloco }}\n </span>\n </div>\n</div>\n", dependencies: [{ kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
|
|
13888
14038
|
}
|
|
13889
14039
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: CommandPalette, decorators: [{
|
|
13890
14040
|
type: Component,
|
|
13891
|
-
args: [{ selector: 'lw-command-palette', schemas: [CUSTOM_ELEMENTS_SCHEMA], imports: [TranslocoPipe], template: "<div class=\"flex max-h-[60vh] w-full flex-col overflow-hidden\">\n <div class=\"flex items-center gap-2 border-b border-border px-4\">\n <lw-icon name=\"search\" size=\"1.1rem\" class=\"text-content-muted\" />\n <input\n data-lw-autofocus\n type=\"text\"\n role=\"combobox\"\n aria-controls=\"lw-palette-list\"\n [attr.aria-expanded]=\"true\"\n [attr.aria-activedescendant]=\"activeId()\"\n [attr.aria-label]=\"
|
|
14041
|
+
args: [{ selector: 'lw-command-palette', schemas: [CUSTOM_ELEMENTS_SCHEMA], imports: [TranslocoPipe], template: "<div class=\"flex max-h-[60vh] w-full flex-col overflow-hidden\">\n <div class=\"flex items-center gap-2 border-b border-border px-4\">\n <lw-icon name=\"search\" size=\"1.1rem\" class=\"text-content-muted\" />\n <input\n data-lw-autofocus\n type=\"text\"\n role=\"combobox\"\n aria-controls=\"lw-palette-list\"\n [attr.aria-expanded]=\"true\"\n [attr.aria-activedescendant]=\"activeId()\"\n [attr.aria-label]=\"title() | transloco\"\n [value]=\"query()\"\n [placeholder]=\"\n (mode() === 'tabs' ? 'palette.tabsPlaceholder' : 'palette.placeholder')\n | transloco\n \"\n class=\"flex-1 bg-transparent py-3 text-sm text-content outline-none placeholder:text-content-faint\"\n (input)=\"onQuery($event)\"\n (keydown.arrowdown)=\"move($event, 1)\"\n (keydown.arrowup)=\"move($event, -1)\"\n (keydown.arrowright)=\"openTabActions($event)\"\n (keydown.enter)=\"runActive($event)\"\n />\n </div>\n\n <ul\n id=\"lw-palette-list\"\n role=\"listbox\"\n class=\"min-h-0 flex-1 overflow-y-auto p-1\"\n >\n @for (entry of results(); track entry.id; let i = $index) {\n @if (recentCount() > 0 && i === 0) {\n <li\n role=\"presentation\"\n data-testid=\"palette-section-recent\"\n class=\"px-3 pt-2 pb-1 text-xs font-medium text-content-faint\"\n >\n {{ 'palette.recent' | transloco }}\n </li>\n }\n @if (recentCount() > 0 && i === recentCount()) {\n <li\n role=\"presentation\"\n data-testid=\"palette-section-all\"\n class=\"px-3 pt-2 pb-1 text-xs font-medium text-content-faint\"\n >\n {{ 'palette.all' | transloco }}\n </li>\n }\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <li\n role=\"option\"\n [id]=\"optionId(i)\"\n [attr.aria-selected]=\"i === activeIndex()\"\n class=\"flex cursor-pointer items-center gap-3 rounded-md px-3 py-2 text-sm\"\n [class]=\"\n i === activeIndex()\n ? 'bg-brand/10 text-brand-text'\n : 'text-content hover:bg-surface-overlay'\n \"\n (click)=\"select(entry)\"\n (mouseenter)=\"setActive(i)\"\n >\n @if (entry.icon; as icon) {\n <lw-icon [name]=\"icon\" size=\"1rem\" class=\"text-content-muted\" />\n }\n <span class=\"flex-1 truncate\">{{ entry.label }}</span>\n @if (timeOf(entry); as time) {\n <span class=\"shrink-0 text-xs text-content-faint\">{{ time }}</span>\n }\n @if (shortcutOf(entry); as shortcut) {\n <kbd\n class=\"rounded border border-border px-1.5 py-0.5 text-xs text-content-muted\"\n >{{ shortcut }}</kbd\n >\n }\n </li>\n } @empty {\n <li class=\"px-3 py-6 text-center text-sm text-content-muted\">\n {{\n (mode() === 'tabs' ? 'palette.tabsEmpty' : 'palette.empty') | transloco\n }}\n </li>\n }\n </ul>\n\n <div\n data-testid=\"palette-footer\"\n class=\"flex items-center gap-4 border-t border-border px-4 py-2 text-xs text-content-faint\"\n >\n <span class=\"flex items-center gap-1.5\">\n <kbd class=\"rounded border border-border px-1 py-0.5\">\u2191\u2193</kbd>\n {{ 'palette.hint.navigate' | transloco }}\n </span>\n <span class=\"flex items-center gap-1.5\">\n <kbd class=\"rounded border border-border px-1 py-0.5\">\u21B5</kbd>\n {{ 'palette.hint.run' | transloco }}\n </span>\n @if (mode() === 'tabs') {\n <span class=\"flex items-center gap-1.5\" data-testid=\"palette-hint-actions\">\n <kbd class=\"rounded border border-border px-1 py-0.5\">\u2192</kbd>\n {{ 'palette.hint.actions' | transloco }}\n </span>\n }\n <span class=\"ml-auto flex items-center gap-1.5\">\n <kbd class=\"rounded border border-border px-1 py-0.5\">Esc</kbd>\n {{ 'palette.hint.close' | transloco }}\n </span>\n </div>\n</div>\n" }]
|
|
13892
14042
|
}] });
|
|
13893
14043
|
|
|
13894
14044
|
class WorkspaceDialog {
|
|
@@ -14122,7 +14272,12 @@ function seedHostCommands(registry, layout, deps) {
|
|
|
14122
14272
|
shortcut: 'mod+k',
|
|
14123
14273
|
popout: true,
|
|
14124
14274
|
run: () => {
|
|
14125
|
-
dialogs.open(CommandPalette, {
|
|
14275
|
+
dialogs.open(CommandPalette, {
|
|
14276
|
+
bare: true,
|
|
14277
|
+
size: 'lg',
|
|
14278
|
+
align: 'top',
|
|
14279
|
+
title: 'palette.title',
|
|
14280
|
+
});
|
|
14126
14281
|
},
|
|
14127
14282
|
});
|
|
14128
14283
|
registry.addCommand({
|
|
@@ -14147,6 +14302,7 @@ function seedHostCommands(registry, layout, deps) {
|
|
|
14147
14302
|
bare: true,
|
|
14148
14303
|
size: 'lg',
|
|
14149
14304
|
align: 'top',
|
|
14305
|
+
title: 'palette.quickOpenTitle',
|
|
14150
14306
|
data: { mode: 'tabs' },
|
|
14151
14307
|
});
|
|
14152
14308
|
},
|
|
@@ -14261,9 +14417,12 @@ function seedBuiltInMenus(registry, layout, deps) {
|
|
|
14261
14417
|
return;
|
|
14262
14418
|
}
|
|
14263
14419
|
registerTabContextMenu(registry, deps.tabs, deps.paneMove, deps.popout, deps.features);
|
|
14420
|
+
seedRailMenus(registry, layout, deps);
|
|
14421
|
+
seedViewMenus(registry, layout, deps);
|
|
14422
|
+
}
|
|
14423
|
+
function seedRailMenus(registry, layout, deps) {
|
|
14264
14424
|
const railCount = layout.regions.filter((region) => region.type === 'rail').length;
|
|
14265
14425
|
const rail = deps.features.rail;
|
|
14266
|
-
const sidebar = deps.features.sidebar;
|
|
14267
14426
|
if (railCount >= 1 && rail.hideItems) {
|
|
14268
14427
|
registerRailContextMenu(registry, deps.railItems);
|
|
14269
14428
|
}
|
|
@@ -14273,6 +14432,9 @@ function seedBuiltInMenus(registry, layout, deps) {
|
|
|
14273
14432
|
if (railCount >= 1 && rail.curate) {
|
|
14274
14433
|
registerRailCustomizeMenu(registry);
|
|
14275
14434
|
}
|
|
14435
|
+
}
|
|
14436
|
+
function seedViewMenus(registry, layout, deps) {
|
|
14437
|
+
const sidebar = deps.features.sidebar;
|
|
14276
14438
|
if (sidebar.resetViewState) {
|
|
14277
14439
|
registerViewResetMenu(registry, deps.viewStates, deps.viewInstances);
|
|
14278
14440
|
}
|
|
@@ -14421,7 +14583,7 @@ class BootLatchedIdentity {
|
|
|
14421
14583
|
return this.latched;
|
|
14422
14584
|
}
|
|
14423
14585
|
const id = this.read();
|
|
14424
|
-
if (id
|
|
14586
|
+
if (!id) {
|
|
14425
14587
|
return null;
|
|
14426
14588
|
}
|
|
14427
14589
|
this.latched = id;
|
|
@@ -14653,6 +14815,16 @@ function accessCanMatch(access) {
|
|
|
14653
14815
|
};
|
|
14654
14816
|
}
|
|
14655
14817
|
|
|
14818
|
+
const keepPopout = (_route, state) => {
|
|
14819
|
+
if (!isPopoutUrl(inject(BootAddress).path)) {
|
|
14820
|
+
return true;
|
|
14821
|
+
}
|
|
14822
|
+
if (isDevMode()) {
|
|
14823
|
+
console.warn(popoutNavigationRefusal(normalizePath(state.url)));
|
|
14824
|
+
}
|
|
14825
|
+
return false;
|
|
14826
|
+
};
|
|
14827
|
+
|
|
14656
14828
|
const settleWorkspace = async (_route, state) => {
|
|
14657
14829
|
const claims = inject(WORKSPACE_CLAIMS);
|
|
14658
14830
|
await claims.settle(normalizePath(state.url));
|
|
@@ -14693,6 +14865,7 @@ function buildContentRoutes(contentRoutes, omitted = [], retention = 'destroy')
|
|
|
14693
14865
|
const placeholders = omitted.map((route) => ({
|
|
14694
14866
|
path: route.path,
|
|
14695
14867
|
component: RouteUnavailableView,
|
|
14868
|
+
canActivate: [keepPopout],
|
|
14696
14869
|
data: { content: true, routePlaceholder: true },
|
|
14697
14870
|
}));
|
|
14698
14871
|
return [...buildRegisteredRoutes(contentRoutes, retention), ...placeholders];
|
|
@@ -14711,7 +14884,7 @@ function surfaceRoute(route, retained) {
|
|
|
14711
14884
|
function subStub(path, pathMatch) {
|
|
14712
14885
|
return {
|
|
14713
14886
|
path,
|
|
14714
|
-
...(pathMatch
|
|
14887
|
+
...(pathMatch && { pathMatch }),
|
|
14715
14888
|
component: ContentSubStub,
|
|
14716
14889
|
data: { content: true, sub: true },
|
|
14717
14890
|
};
|
|
@@ -14738,7 +14911,7 @@ function buildRegisteredRoutes(contentRoutes, retention) {
|
|
|
14738
14911
|
const angular = {
|
|
14739
14912
|
path: route.path,
|
|
14740
14913
|
...surfaceRoute(route, retained),
|
|
14741
|
-
canActivate: [settleWorkspace],
|
|
14914
|
+
canActivate: [keepPopout, settleWorkspace],
|
|
14742
14915
|
data: {
|
|
14743
14916
|
content: true,
|
|
14744
14917
|
chromeless: route.chromeless,
|
|
@@ -14757,6 +14930,7 @@ function buildRegisteredRoutes(contentRoutes, retention) {
|
|
|
14757
14930
|
const placeholder = {
|
|
14758
14931
|
path: route.path,
|
|
14759
14932
|
component: AuthRequiredView,
|
|
14933
|
+
canActivate: [keepPopout],
|
|
14760
14934
|
data: {
|
|
14761
14935
|
content: true,
|
|
14762
14936
|
authPlaceholder: true,
|
|
@@ -15002,13 +15176,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
15002
15176
|
type: Service
|
|
15003
15177
|
}] });
|
|
15004
15178
|
function pathOfKey(key) {
|
|
15005
|
-
return key.split('|')[1] ?? '';
|
|
15179
|
+
return key.split('|', 2)[1] ?? '';
|
|
15006
15180
|
}
|
|
15007
15181
|
function stashKeyLive(key, open, routes, views) {
|
|
15008
15182
|
if (key.startsWith(PRIMARY_RETENTION_PREFIX)) {
|
|
15009
15183
|
return true;
|
|
15010
15184
|
}
|
|
15011
|
-
const [scope, path] = key.split('|');
|
|
15185
|
+
const [scope, path] = key.split('|', 2);
|
|
15012
15186
|
if (!tabOpen(open.get(scope), routes, path)) {
|
|
15013
15187
|
return false;
|
|
15014
15188
|
}
|
|
@@ -15541,7 +15715,8 @@ class IconRegistry {
|
|
|
15541
15715
|
setIcon(name, safe);
|
|
15542
15716
|
added.push(name);
|
|
15543
15717
|
}
|
|
15544
|
-
return { dispose: () =>
|
|
15718
|
+
return { dispose: () => { for (const name of added)
|
|
15719
|
+
removeIcon(name); } };
|
|
15545
15720
|
}
|
|
15546
15721
|
resolve(name) {
|
|
15547
15722
|
return resolveIcon(name);
|
|
@@ -15691,6 +15866,9 @@ function providePlugins(...plugins) {
|
|
|
15691
15866
|
];
|
|
15692
15867
|
}
|
|
15693
15868
|
|
|
15869
|
+
/** Multi-provider token: each contribution adds one sandboxed plugin to load. */
|
|
15870
|
+
const FRAME_PLUGIN = new InjectionToken('FRAME_PLUGIN');
|
|
15871
|
+
|
|
15694
15872
|
const STORAGE_KEY = 'lw.shell.installed-plugins';
|
|
15695
15873
|
/**
|
|
15696
15874
|
* The user's installed community plugins. Holds only the state: which catalog entries the
|
|
@@ -15722,7 +15900,7 @@ class PluginInstallService {
|
|
|
15722
15900
|
return this.entries().some((entry) => entry.id === id);
|
|
15723
15901
|
}
|
|
15724
15902
|
/** The installed entry for an id, or `undefined` — the baseline an update is compared against. */
|
|
15725
|
-
|
|
15903
|
+
byId(id) {
|
|
15726
15904
|
return this.entries().find((entry) => entry.id === id);
|
|
15727
15905
|
}
|
|
15728
15906
|
/**
|
|
@@ -15787,202 +15965,86 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
15787
15965
|
type: Service
|
|
15788
15966
|
}], ctorParameters: () => [] });
|
|
15789
15967
|
|
|
15790
|
-
|
|
15791
|
-
|
|
15792
|
-
|
|
15793
|
-
|
|
15968
|
+
function levelOf(plugin) {
|
|
15969
|
+
return plugin.level ?? DEFAULT_ISOLATION_LEVEL;
|
|
15970
|
+
}
|
|
15971
|
+
function signatureOf(plugin) {
|
|
15972
|
+
const sorted = (values) => [...(values ?? [])].toSorted((a, b) => a.localeCompare(b)).join(',');
|
|
15973
|
+
return `${plugin.entryUrl}|${sorted(plugin.capabilities)}|${sorted(plugin.granted)}|${plugin.version ?? ''}|${levelOf(plugin)}`;
|
|
15974
|
+
}
|
|
15975
|
+
function runnablePlugins(composed, installed, deployed, catalogCap) {
|
|
15976
|
+
const claimed = new Set(composed.map((plugin) => plugin.id));
|
|
15977
|
+
const provided = new Set(deployed.map((plugin) => plugin.id));
|
|
15978
|
+
const fromCatalog = [];
|
|
15979
|
+
for (const plugin of [...deployed, ...installed]) {
|
|
15980
|
+
if (claimed.has(plugin.id)) {
|
|
15981
|
+
continue;
|
|
15982
|
+
}
|
|
15983
|
+
const asked = plugin.level ?? DEFAULT_ISOLATION_LEVEL;
|
|
15984
|
+
if (exceedsLevel(asked, catalogCap)) {
|
|
15985
|
+
console.error(`Plugin "${plugin.id}" asks to run ${asked}, which this catalog may not confer ` +
|
|
15986
|
+
`(its cap is ${catalogCap}). It is not started.`);
|
|
15987
|
+
continue;
|
|
15988
|
+
}
|
|
15989
|
+
claimed.add(plugin.id);
|
|
15990
|
+
fromCatalog.push({
|
|
15991
|
+
id: plugin.id,
|
|
15992
|
+
entryUrl: plugin.entryUrl,
|
|
15993
|
+
capabilities: plugin.capabilities,
|
|
15994
|
+
name: plugin.name,
|
|
15995
|
+
granted: plugin.capabilities ?? [],
|
|
15996
|
+
version: plugin.version,
|
|
15997
|
+
level: asked,
|
|
15998
|
+
provided: provided.has(plugin.id) || undefined,
|
|
15999
|
+
});
|
|
15794
16000
|
}
|
|
15795
|
-
return
|
|
15796
|
-
.filter((option) => typeof option === 'object' &&
|
|
15797
|
-
option !== null &&
|
|
15798
|
-
typeof option['value'] === 'string' &&
|
|
15799
|
-
typeof option['label'] === 'string')
|
|
15800
|
-
.map((option) => ({ value: option.value, label: option.label }));
|
|
16001
|
+
return [...composed, ...fromCatalog];
|
|
15801
16002
|
}
|
|
15802
|
-
|
|
15803
|
-
|
|
15804
|
-
|
|
15805
|
-
|
|
15806
|
-
|
|
15807
|
-
|
|
15808
|
-
|
|
15809
|
-
|
|
15810
|
-
|
|
15811
|
-
|
|
15812
|
-
|
|
15813
|
-
|
|
15814
|
-
};
|
|
15815
|
-
}
|
|
15816
|
-
function buildSelectControl(pluginId, value, control) {
|
|
15817
|
-
const options = sanitizeOptions(control['options']);
|
|
15818
|
-
if (options.length === 0) {
|
|
15819
|
-
throw new Error(`Sandbox plugin "${pluginId}": a select control needs at least one { value, label } option.`);
|
|
15820
|
-
}
|
|
15821
|
-
return { kind: 'select', value, options };
|
|
15822
|
-
}
|
|
15823
|
-
function buildSliderControl(value, control) {
|
|
15824
|
-
return {
|
|
15825
|
-
kind: 'slider',
|
|
15826
|
-
value,
|
|
15827
|
-
min: optionalNumber(control['min']),
|
|
15828
|
-
max: optionalNumber(control['max']),
|
|
15829
|
-
step: optionalNumber(control['step']),
|
|
15830
|
-
};
|
|
15831
|
-
}
|
|
15832
|
-
function sanitizeControl(pluginId, raw) {
|
|
15833
|
-
const control = (raw ?? {});
|
|
15834
|
-
const kind = control['kind'];
|
|
15835
|
-
const value = control['value'];
|
|
15836
|
-
if (kind === 'toggle' && typeof value === 'boolean') {
|
|
15837
|
-
return { kind, value };
|
|
15838
|
-
}
|
|
15839
|
-
if (kind === 'text' && typeof value === 'string') {
|
|
15840
|
-
return buildTextControl(value, control);
|
|
15841
|
-
}
|
|
15842
|
-
if (kind === 'select' && typeof value === 'string') {
|
|
15843
|
-
return buildSelectControl(pluginId, value, control);
|
|
15844
|
-
}
|
|
15845
|
-
if (kind === 'slider' && typeof value === 'number') {
|
|
15846
|
-
return buildSliderControl(value, control);
|
|
15847
|
-
}
|
|
15848
|
-
throw new Error(`Sandbox plugin "${pluginId}": a settings control must be toggle/text/select/slider with a matching default 'value'.`);
|
|
15849
|
-
}
|
|
15850
|
-
function sanitizeRow(pluginId, raw) {
|
|
15851
|
-
const row = (raw ?? {});
|
|
15852
|
-
if (typeof row['id'] !== 'string' || row['id'].length === 0) {
|
|
15853
|
-
throw new Error(`Sandbox plugin "${pluginId}": every settings row needs a non-empty 'id'.`);
|
|
15854
|
-
}
|
|
15855
|
-
if (typeof row['label'] !== 'string' || row['label'].length === 0) {
|
|
15856
|
-
throw new Error(`Sandbox plugin "${pluginId}": every settings row needs a non-empty 'label'.`);
|
|
15857
|
-
}
|
|
15858
|
-
return {
|
|
15859
|
-
id: row['id'],
|
|
15860
|
-
label: row['label'],
|
|
15861
|
-
description: typeof row['description'] === 'string' ? row['description'] : undefined,
|
|
15862
|
-
control: sanitizeControl(pluginId, row['control']),
|
|
15863
|
-
};
|
|
15864
|
-
}
|
|
15865
|
-
function sanitizeRpcSettingsSection(pluginId, section) {
|
|
15866
|
-
const raw = (section ?? {});
|
|
15867
|
-
if (typeof raw['id'] !== 'string' || raw['id'].length === 0) {
|
|
15868
|
-
throw new Error(`Sandbox plugin "${pluginId}": registerSettingsSection requires a non-empty 'id'.`);
|
|
15869
|
-
}
|
|
15870
|
-
if (typeof raw['title'] !== 'string' || raw['title'].length === 0) {
|
|
15871
|
-
throw new Error(`Sandbox plugin "${pluginId}": registerSettingsSection requires a non-empty 'title'.`);
|
|
15872
|
-
}
|
|
15873
|
-
if (!Array.isArray(raw['rows']) || raw['rows'].length === 0) {
|
|
15874
|
-
throw new Error(`Sandbox plugin "${pluginId}": registerSettingsSection requires at least one row.`);
|
|
15875
|
-
}
|
|
15876
|
-
return {
|
|
15877
|
-
id: raw['id'],
|
|
15878
|
-
title: raw['title'],
|
|
15879
|
-
order: typeof raw['order'] === 'number' ? raw['order'] : undefined,
|
|
15880
|
-
rows: raw['rows'].map((row) => sanitizeRow(pluginId, row)),
|
|
15881
|
-
};
|
|
15882
|
-
}
|
|
15883
|
-
function defaultsOf(wire) {
|
|
15884
|
-
const defaults = {};
|
|
15885
|
-
for (const row of wire.rows) {
|
|
15886
|
-
defaults[row.id] = row.control.value;
|
|
15887
|
-
}
|
|
15888
|
-
return defaults;
|
|
15889
|
-
}
|
|
15890
|
-
function typedOverlay(defaults, raw) {
|
|
15891
|
-
if (!raw) {
|
|
15892
|
-
return defaults;
|
|
15893
|
-
}
|
|
15894
|
-
try {
|
|
15895
|
-
const parsed = JSON.parse(raw);
|
|
15896
|
-
if (typeof parsed !== 'object' || parsed === null) {
|
|
15897
|
-
return defaults;
|
|
15898
|
-
}
|
|
15899
|
-
const merged = { ...defaults };
|
|
15900
|
-
for (const [key, value] of Object.entries(parsed)) {
|
|
15901
|
-
if (key in defaults && typeof value === typeof defaults[key]) {
|
|
15902
|
-
merged[key] = value;
|
|
15903
|
-
}
|
|
15904
|
-
}
|
|
15905
|
-
return merged;
|
|
15906
|
-
}
|
|
15907
|
-
catch {
|
|
15908
|
-
return defaults;
|
|
15909
|
-
}
|
|
15910
|
-
}
|
|
15911
|
-
function buildFrameSection(deps) {
|
|
15912
|
-
const { pluginId, wire, group, store, sync, notify } = deps;
|
|
15913
|
-
const key = `lw.plugin-settings:${pluginId}:${wire.id}`;
|
|
15914
|
-
const defaults = defaultsOf(wire);
|
|
15915
|
-
const values = signal(typedOverlay(defaults, store.peek?.(key)), /* @ts-ignore */
|
|
15916
|
-
...(ngDevMode ? [{ debugName: "values" }] : /* istanbul ignore next */ []));
|
|
15917
|
-
const applyStored = (raw) => {
|
|
15918
|
-
values.set(typedOverlay(defaults, raw));
|
|
15919
|
-
notify(wire.id, values());
|
|
15920
|
-
};
|
|
15921
|
-
if (store.peek) {
|
|
15922
|
-
notify(wire.id, values());
|
|
15923
|
-
}
|
|
15924
|
-
else {
|
|
15925
|
-
hydrateAsync(store, key, applyStored);
|
|
15926
|
-
}
|
|
15927
|
-
const disposeSync = sync.register('settings', key, applyStored);
|
|
15928
|
-
const set = (rowId, value) => {
|
|
15929
|
-
values.update((current) => ({ ...current, [rowId]: value }));
|
|
15930
|
-
void store.set(key, JSON.stringify(values()));
|
|
15931
|
-
notify(wire.id, values());
|
|
15932
|
-
};
|
|
15933
|
-
const section = {
|
|
15934
|
-
id: `${pluginId}.${wire.id}`,
|
|
15935
|
-
title: wire.title,
|
|
15936
|
-
group,
|
|
15937
|
-
order: wire.order,
|
|
15938
|
-
rows: wire.rows.map((row) => ({
|
|
15939
|
-
id: `${pluginId}.${wire.id}.${row.id}`,
|
|
15940
|
-
label: row.label,
|
|
15941
|
-
description: row.description,
|
|
15942
|
-
control: hostControl(row, values, set),
|
|
15943
|
-
})),
|
|
15944
|
-
};
|
|
15945
|
-
return { section, disposeSync };
|
|
15946
|
-
}
|
|
15947
|
-
function hostControl(row, values, set) {
|
|
15948
|
-
const control = row.control;
|
|
15949
|
-
switch (control.kind) {
|
|
15950
|
-
case 'toggle':
|
|
15951
|
-
return {
|
|
15952
|
-
kind: 'toggle',
|
|
15953
|
-
value: () => values()[row.id] === true,
|
|
15954
|
-
set: (value) => set(row.id, value),
|
|
15955
|
-
};
|
|
15956
|
-
case 'text':
|
|
15957
|
-
return {
|
|
15958
|
-
kind: 'text',
|
|
15959
|
-
inputType: control.inputType,
|
|
15960
|
-
placeholder: control.placeholder,
|
|
15961
|
-
value: () => String(values()[row.id] ?? ''),
|
|
15962
|
-
set: (value) => set(row.id, value),
|
|
15963
|
-
};
|
|
15964
|
-
case 'select':
|
|
15965
|
-
return {
|
|
15966
|
-
kind: 'select',
|
|
15967
|
-
options: control.options,
|
|
15968
|
-
value: () => String(values()[row.id] ?? control.value),
|
|
15969
|
-
set: (value) => set(row.id, value),
|
|
15970
|
-
};
|
|
15971
|
-
case 'slider':
|
|
15972
|
-
return {
|
|
15973
|
-
kind: 'slider',
|
|
15974
|
-
min: control.min,
|
|
15975
|
-
max: control.max,
|
|
15976
|
-
step: control.step,
|
|
15977
|
-
value: () => Number(values()[row.id] ?? control.value),
|
|
15978
|
-
set: (value) => set(row.id, value),
|
|
15979
|
-
};
|
|
15980
|
-
}
|
|
16003
|
+
|
|
16004
|
+
const UNCARRIABLE_ARGUMENTS = {
|
|
16005
|
+
outcome: 'refused',
|
|
16006
|
+
reason: 'invalid-arguments',
|
|
16007
|
+
message: 'Arguments must be an object of single values or lists of them; anything else cannot cross the ' +
|
|
16008
|
+
'sandbox boundary as the value it was.',
|
|
16009
|
+
};
|
|
16010
|
+
function invokeRpcCommand(ctx, id, args) {
|
|
16011
|
+
const carried = args === undefined ? undefined : asCommandArguments(args);
|
|
16012
|
+
return carried === null
|
|
16013
|
+
? Promise.resolve(UNCARRIABLE_ARGUMENTS)
|
|
16014
|
+
: ctx.invokeCommand(String(id), carried);
|
|
15981
16015
|
}
|
|
15982
16016
|
|
|
15983
16017
|
const MAX_RPC_AREA_DEPTH = 8;
|
|
15984
16018
|
function sanitizeRpcSurface(pluginId, surface, permitted) {
|
|
15985
16019
|
const raw = (surface ?? {});
|
|
16020
|
+
const { id, title } = rpcSurfaceIdentity(pluginId, raw);
|
|
16021
|
+
const container = sanitizeRpcContainer(raw['container']);
|
|
16022
|
+
const iframe = container === undefined
|
|
16023
|
+
? rpcIframeUrl(pluginId, raw['iframe'], permitted)
|
|
16024
|
+
: undefined;
|
|
16025
|
+
const routable = sanitizeRpcRoutable(raw['routable']);
|
|
16026
|
+
const docks = sanitizeRpcDocks(raw['docks']);
|
|
16027
|
+
assertRpcSurfaceAddress(pluginId, container, routable, docks);
|
|
16028
|
+
const shared = {
|
|
16029
|
+
id,
|
|
16030
|
+
title,
|
|
16031
|
+
icon: typeof raw['icon'] === 'string' ? raw['icon'] : undefined,
|
|
16032
|
+
order: typeof raw['order'] === 'number' ? raw['order'] : undefined,
|
|
16033
|
+
instanceable: raw['instanceable'] === true ? true : undefined,
|
|
16034
|
+
retain: raw['retain'] === 'always' || raw['retain'] === 'never'
|
|
16035
|
+
? raw['retain']
|
|
16036
|
+
: undefined,
|
|
16037
|
+
saveOn: raw['saveOn'] === 'hide' ? 'hide' : undefined,
|
|
16038
|
+
closable: raw['closable'] === false ? false : undefined,
|
|
16039
|
+
padded: raw['padded'] === false ? false : undefined,
|
|
16040
|
+
routable,
|
|
16041
|
+
docks,
|
|
16042
|
+
};
|
|
16043
|
+
return container === undefined
|
|
16044
|
+
? { ...shared, iframe: iframe }
|
|
16045
|
+
: { ...shared, container };
|
|
16046
|
+
}
|
|
16047
|
+
function rpcSurfaceIdentity(pluginId, raw) {
|
|
15986
16048
|
if (typeof raw['id'] !== 'string' || raw['id'].length === 0) {
|
|
15987
16049
|
throw new Error(`Sandbox plugin "${pluginId}": registerSurface requires a non-empty 'id'.`);
|
|
15988
16050
|
}
|
|
@@ -15997,20 +16059,20 @@ function sanitizeRpcSurface(pluginId, surface, permitted) {
|
|
|
15997
16059
|
throw new Error(`Sandbox plugin "${pluginId}": 'access' does not cross the RPC boundary — ` +
|
|
15998
16060
|
`a sandboxed surface gates itself from the pushed session state.`);
|
|
15999
16061
|
}
|
|
16000
|
-
|
|
16001
|
-
|
|
16002
|
-
|
|
16003
|
-
|
|
16004
|
-
|
|
16005
|
-
}
|
|
16006
|
-
const origin = surfaceOrigin(iframe);
|
|
16007
|
-
if (origin === null || !permittedOrigins(permitted).has(origin)) {
|
|
16008
|
-
throw new Error(`Sandbox plugin "${pluginId}": the iframe surface must be served from an origin this ` +
|
|
16009
|
-
`distribution permitted for it, got "${iframe}".`);
|
|
16010
|
-
}
|
|
16062
|
+
return { id: raw['id'], title: raw['title'] };
|
|
16063
|
+
}
|
|
16064
|
+
function rpcIframeUrl(pluginId, value, permitted) {
|
|
16065
|
+
if (typeof value !== 'string') {
|
|
16066
|
+
throw new TypeError(`Sandbox plugin "${pluginId}": registerSurface needs an { iframe } URL or a { container } spec.`);
|
|
16011
16067
|
}
|
|
16012
|
-
const
|
|
16013
|
-
|
|
16068
|
+
const origin = surfaceOrigin(value);
|
|
16069
|
+
if (origin === null || !permittedOrigins(permitted).has(origin)) {
|
|
16070
|
+
throw new Error(`Sandbox plugin "${pluginId}": the iframe surface must be served from an origin this ` +
|
|
16071
|
+
`distribution permitted for it, got "${value}".`);
|
|
16072
|
+
}
|
|
16073
|
+
return value;
|
|
16074
|
+
}
|
|
16075
|
+
function assertRpcSurfaceAddress(pluginId, container, routable, docks) {
|
|
16014
16076
|
if (routable === undefined && docks === undefined) {
|
|
16015
16077
|
throw new Error(`Sandbox plugin "${pluginId}": registerSurface needs 'routable.path' (a URL-addressed surface) ` +
|
|
16016
16078
|
`or 'docks' (a surface hosted at a dock).`);
|
|
@@ -16019,24 +16081,6 @@ function sanitizeRpcSurface(pluginId, surface, permitted) {
|
|
|
16019
16081
|
throw new Error(`Sandbox plugin "${pluginId}": a container surface must be routable — a container tab holds ` +
|
|
16020
16082
|
`its own ':id'.`);
|
|
16021
16083
|
}
|
|
16022
|
-
const shared = {
|
|
16023
|
-
id: raw['id'],
|
|
16024
|
-
title: raw['title'],
|
|
16025
|
-
icon: typeof raw['icon'] === 'string' ? raw['icon'] : undefined,
|
|
16026
|
-
order: typeof raw['order'] === 'number' ? raw['order'] : undefined,
|
|
16027
|
-
instanceable: raw['instanceable'] === true ? true : undefined,
|
|
16028
|
-
retain: raw['retain'] === 'always' || raw['retain'] === 'never'
|
|
16029
|
-
? raw['retain']
|
|
16030
|
-
: undefined,
|
|
16031
|
-
saveOn: raw['saveOn'] === 'hide' ? 'hide' : undefined,
|
|
16032
|
-
closable: raw['closable'] === false ? false : undefined,
|
|
16033
|
-
padded: raw['padded'] === false ? false : undefined,
|
|
16034
|
-
routable,
|
|
16035
|
-
docks,
|
|
16036
|
-
};
|
|
16037
|
-
return container !== undefined
|
|
16038
|
-
? { ...shared, container }
|
|
16039
|
-
: { ...shared, iframe: iframe };
|
|
16040
16084
|
}
|
|
16041
16085
|
function sanitizeRpcRoutable(value) {
|
|
16042
16086
|
if (typeof value !== 'object' || value === null) {
|
|
@@ -16089,7 +16133,7 @@ function sanitizeRpcArea(value, depth) {
|
|
|
16089
16133
|
const raw = value;
|
|
16090
16134
|
const size = typeof raw['size'] === 'number' ? { size: raw['size'] } : {};
|
|
16091
16135
|
if (Array.isArray(raw['tabs'])) {
|
|
16092
|
-
return { ...size, tabs: raw['tabs'].flatMap(sanitizeRpcContainerTab) };
|
|
16136
|
+
return { ...size, tabs: raw['tabs'].flatMap((value) => sanitizeRpcContainerTab(value)) };
|
|
16093
16137
|
}
|
|
16094
16138
|
for (const kind of ['rows', 'columns']) {
|
|
16095
16139
|
const declared = raw[kind];
|
|
@@ -16116,8 +16160,8 @@ function sanitizeRpcContainerTab(value) {
|
|
|
16116
16160
|
return [
|
|
16117
16161
|
{
|
|
16118
16162
|
surface: raw['surface'],
|
|
16119
|
-
...(raw['closable'] === false
|
|
16120
|
-
...(raw['active'] === true
|
|
16163
|
+
...(raw['closable'] === false && { closable: false }),
|
|
16164
|
+
...(raw['active'] === true && { active: true }),
|
|
16121
16165
|
},
|
|
16122
16166
|
];
|
|
16123
16167
|
}
|
|
@@ -16174,11 +16218,14 @@ function sanitizeRpcToastInput(input) {
|
|
|
16174
16218
|
id: typeof raw['id'] === 'string' ? raw['id'] : undefined,
|
|
16175
16219
|
};
|
|
16176
16220
|
}
|
|
16221
|
+
const NOTIFICATION_KINDS = new Set([
|
|
16222
|
+
'info',
|
|
16223
|
+
'success',
|
|
16224
|
+
'warning',
|
|
16225
|
+
'error',
|
|
16226
|
+
]);
|
|
16177
16227
|
function isNotificationKind(value) {
|
|
16178
|
-
return (value
|
|
16179
|
-
value === 'success' ||
|
|
16180
|
-
value === 'warning' ||
|
|
16181
|
-
value === 'error');
|
|
16228
|
+
return NOTIFICATION_KINDS.has(value);
|
|
16182
16229
|
}
|
|
16183
16230
|
function sanitizeRpcMenuItem(item) {
|
|
16184
16231
|
const raw = (item ?? {});
|
|
@@ -16211,29 +16258,267 @@ function sanitizeMenuContext(value) {
|
|
|
16211
16258
|
return clean;
|
|
16212
16259
|
}
|
|
16213
16260
|
|
|
16214
|
-
const
|
|
16215
|
-
|
|
16216
|
-
|
|
16217
|
-
|
|
16218
|
-
|
|
16219
|
-
|
|
16220
|
-
|
|
16221
|
-
|
|
16222
|
-
|
|
16223
|
-
|
|
16224
|
-
|
|
16261
|
+
const INPUT_TYPES = ['text', 'date', 'email', 'number', 'password'];
|
|
16262
|
+
function sanitizeOptions(raw) {
|
|
16263
|
+
if (!Array.isArray(raw)) {
|
|
16264
|
+
return [];
|
|
16265
|
+
}
|
|
16266
|
+
return raw
|
|
16267
|
+
.filter((option) => typeof option === 'object' &&
|
|
16268
|
+
option !== null &&
|
|
16269
|
+
typeof option['value'] === 'string' &&
|
|
16270
|
+
typeof option['label'] === 'string')
|
|
16271
|
+
.map((option) => ({ value: option.value, label: option.label }));
|
|
16225
16272
|
}
|
|
16226
|
-
|
|
16227
|
-
|
|
16228
|
-
const FRAME_PLUGIN = new InjectionToken('FRAME_PLUGIN');
|
|
16229
|
-
function levelOf(plugin) {
|
|
16230
|
-
return plugin.level ?? DEFAULT_ISOLATION_LEVEL;
|
|
16273
|
+
function optionalString(value) {
|
|
16274
|
+
return typeof value === 'string' ? value : undefined;
|
|
16231
16275
|
}
|
|
16232
|
-
function
|
|
16233
|
-
|
|
16234
|
-
|
|
16235
|
-
|
|
16276
|
+
function optionalNumber(value) {
|
|
16277
|
+
return typeof value === 'number' ? value : undefined;
|
|
16278
|
+
}
|
|
16279
|
+
function buildTextControl(value, control) {
|
|
16280
|
+
return {
|
|
16281
|
+
kind: 'text',
|
|
16282
|
+
value,
|
|
16283
|
+
inputType: INPUT_TYPES.find((type) => type === control['inputType']),
|
|
16284
|
+
placeholder: optionalString(control['placeholder']),
|
|
16285
|
+
};
|
|
16286
|
+
}
|
|
16287
|
+
function buildSelectControl(pluginId, value, control) {
|
|
16288
|
+
const options = sanitizeOptions(control['options']);
|
|
16289
|
+
if (options.length === 0) {
|
|
16290
|
+
throw new Error(`Sandbox plugin "${pluginId}": a select control needs at least one { value, label } option.`);
|
|
16291
|
+
}
|
|
16292
|
+
return { kind: 'select', value, options };
|
|
16293
|
+
}
|
|
16294
|
+
function buildSliderControl(value, control) {
|
|
16295
|
+
return {
|
|
16296
|
+
kind: 'slider',
|
|
16297
|
+
value,
|
|
16298
|
+
min: optionalNumber(control['min']),
|
|
16299
|
+
max: optionalNumber(control['max']),
|
|
16300
|
+
step: optionalNumber(control['step']),
|
|
16301
|
+
};
|
|
16302
|
+
}
|
|
16303
|
+
function sanitizeControl(pluginId, raw) {
|
|
16304
|
+
const control = (raw ?? {});
|
|
16305
|
+
const kind = control['kind'];
|
|
16306
|
+
const value = control['value'];
|
|
16307
|
+
if (kind === 'toggle' && typeof value === 'boolean') {
|
|
16308
|
+
return { kind, value };
|
|
16309
|
+
}
|
|
16310
|
+
if (kind === 'text' && typeof value === 'string') {
|
|
16311
|
+
return buildTextControl(value, control);
|
|
16312
|
+
}
|
|
16313
|
+
if (kind === 'select' && typeof value === 'string') {
|
|
16314
|
+
return buildSelectControl(pluginId, value, control);
|
|
16315
|
+
}
|
|
16316
|
+
if (kind === 'slider' && typeof value === 'number') {
|
|
16317
|
+
return buildSliderControl(value, control);
|
|
16318
|
+
}
|
|
16319
|
+
throw new Error(`Sandbox plugin "${pluginId}": a settings control must be toggle/text/select/slider with a matching default 'value'.`);
|
|
16320
|
+
}
|
|
16321
|
+
function sanitizeRow(pluginId, raw) {
|
|
16322
|
+
const row = (raw ?? {});
|
|
16323
|
+
if (typeof row['id'] !== 'string' || row['id'].length === 0) {
|
|
16324
|
+
throw new Error(`Sandbox plugin "${pluginId}": every settings row needs a non-empty 'id'.`);
|
|
16325
|
+
}
|
|
16326
|
+
if (typeof row['label'] !== 'string' || row['label'].length === 0) {
|
|
16327
|
+
throw new Error(`Sandbox plugin "${pluginId}": every settings row needs a non-empty 'label'.`);
|
|
16328
|
+
}
|
|
16329
|
+
return {
|
|
16330
|
+
id: row['id'],
|
|
16331
|
+
label: row['label'],
|
|
16332
|
+
description: typeof row['description'] === 'string' ? row['description'] : undefined,
|
|
16333
|
+
control: sanitizeControl(pluginId, row['control']),
|
|
16334
|
+
};
|
|
16335
|
+
}
|
|
16336
|
+
function sanitizeRpcSettingsSection(pluginId, section) {
|
|
16337
|
+
const raw = (section ?? {});
|
|
16338
|
+
if (typeof raw['id'] !== 'string' || raw['id'].length === 0) {
|
|
16339
|
+
throw new Error(`Sandbox plugin "${pluginId}": registerSettingsSection requires a non-empty 'id'.`);
|
|
16340
|
+
}
|
|
16341
|
+
if (typeof raw['title'] !== 'string' || raw['title'].length === 0) {
|
|
16342
|
+
throw new Error(`Sandbox plugin "${pluginId}": registerSettingsSection requires a non-empty 'title'.`);
|
|
16343
|
+
}
|
|
16344
|
+
if (!Array.isArray(raw['rows']) || raw['rows'].length === 0) {
|
|
16345
|
+
throw new Error(`Sandbox plugin "${pluginId}": registerSettingsSection requires at least one row.`);
|
|
16346
|
+
}
|
|
16347
|
+
return {
|
|
16348
|
+
id: raw['id'],
|
|
16349
|
+
title: raw['title'],
|
|
16350
|
+
order: typeof raw['order'] === 'number' ? raw['order'] : undefined,
|
|
16351
|
+
rows: raw['rows'].map((row) => sanitizeRow(pluginId, row)),
|
|
16352
|
+
};
|
|
16353
|
+
}
|
|
16354
|
+
function defaultsOf(wire) {
|
|
16355
|
+
const defaults = {};
|
|
16356
|
+
for (const row of wire.rows) {
|
|
16357
|
+
defaults[row.id] = row.control.value;
|
|
16358
|
+
}
|
|
16359
|
+
return defaults;
|
|
16360
|
+
}
|
|
16361
|
+
function typedOverlay(defaults, raw) {
|
|
16362
|
+
if (!raw) {
|
|
16363
|
+
return defaults;
|
|
16364
|
+
}
|
|
16365
|
+
try {
|
|
16366
|
+
const parsed = JSON.parse(raw);
|
|
16367
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
16368
|
+
return defaults;
|
|
16369
|
+
}
|
|
16370
|
+
const merged = { ...defaults };
|
|
16371
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
16372
|
+
if (Object.hasOwn(defaults, key) && typeof value === typeof defaults[key]) {
|
|
16373
|
+
merged[key] = value;
|
|
16374
|
+
}
|
|
16375
|
+
}
|
|
16376
|
+
return merged;
|
|
16377
|
+
}
|
|
16378
|
+
catch {
|
|
16379
|
+
return defaults;
|
|
16380
|
+
}
|
|
16236
16381
|
}
|
|
16382
|
+
function buildFrameSection(deps) {
|
|
16383
|
+
const { pluginId, wire, group, store, sync, notify } = deps;
|
|
16384
|
+
const key = `lw.plugin-settings:${pluginId}:${wire.id}`;
|
|
16385
|
+
const defaults = defaultsOf(wire);
|
|
16386
|
+
const values = signal(typedOverlay(defaults, store.peek?.(key)), /* @ts-ignore */
|
|
16387
|
+
...(ngDevMode ? [{ debugName: "values" }] : /* istanbul ignore next */ []));
|
|
16388
|
+
const applyStored = (raw) => {
|
|
16389
|
+
values.set(typedOverlay(defaults, raw));
|
|
16390
|
+
notify(wire.id, values());
|
|
16391
|
+
};
|
|
16392
|
+
if (store.peek) {
|
|
16393
|
+
notify(wire.id, values());
|
|
16394
|
+
}
|
|
16395
|
+
else {
|
|
16396
|
+
hydrateAsync(store, key, applyStored);
|
|
16397
|
+
}
|
|
16398
|
+
const disposeSync = sync.register('settings', key, applyStored);
|
|
16399
|
+
const set = (rowId, value) => {
|
|
16400
|
+
values.update((current) => ({ ...current, [rowId]: value }));
|
|
16401
|
+
void store.set(key, JSON.stringify(values()));
|
|
16402
|
+
notify(wire.id, values());
|
|
16403
|
+
};
|
|
16404
|
+
const section = {
|
|
16405
|
+
id: `${pluginId}.${wire.id}`,
|
|
16406
|
+
title: wire.title,
|
|
16407
|
+
group,
|
|
16408
|
+
order: wire.order,
|
|
16409
|
+
rows: wire.rows.map((row) => ({
|
|
16410
|
+
id: `${pluginId}.${wire.id}.${row.id}`,
|
|
16411
|
+
label: row.label,
|
|
16412
|
+
description: row.description,
|
|
16413
|
+
control: hostControl(row, values, set),
|
|
16414
|
+
})),
|
|
16415
|
+
};
|
|
16416
|
+
return { section, disposeSync };
|
|
16417
|
+
}
|
|
16418
|
+
function hostControl(row, values, set) {
|
|
16419
|
+
const control = row.control;
|
|
16420
|
+
switch (control.kind) {
|
|
16421
|
+
case 'toggle': {
|
|
16422
|
+
return {
|
|
16423
|
+
kind: 'toggle',
|
|
16424
|
+
value: () => values()[row.id] === true,
|
|
16425
|
+
set: (value) => set(row.id, value),
|
|
16426
|
+
};
|
|
16427
|
+
}
|
|
16428
|
+
case 'text': {
|
|
16429
|
+
return {
|
|
16430
|
+
kind: 'text',
|
|
16431
|
+
inputType: control.inputType,
|
|
16432
|
+
placeholder: control.placeholder,
|
|
16433
|
+
value: () => String(values()[row.id] ?? ''),
|
|
16434
|
+
set: (value) => set(row.id, value),
|
|
16435
|
+
};
|
|
16436
|
+
}
|
|
16437
|
+
case 'select': {
|
|
16438
|
+
return {
|
|
16439
|
+
kind: 'select',
|
|
16440
|
+
options: control.options,
|
|
16441
|
+
value: () => String(values()[row.id] ?? control.value),
|
|
16442
|
+
set: (value) => set(row.id, value),
|
|
16443
|
+
};
|
|
16444
|
+
}
|
|
16445
|
+
case 'slider': {
|
|
16446
|
+
return {
|
|
16447
|
+
kind: 'slider',
|
|
16448
|
+
min: control.min,
|
|
16449
|
+
max: control.max,
|
|
16450
|
+
step: control.step,
|
|
16451
|
+
value: () => Number(values()[row.id] ?? control.value),
|
|
16452
|
+
set: (value) => set(row.id, value),
|
|
16453
|
+
};
|
|
16454
|
+
}
|
|
16455
|
+
}
|
|
16456
|
+
}
|
|
16457
|
+
|
|
16458
|
+
function frameRpcMethods(deps) {
|
|
16459
|
+
const { pluginId, ctx, origins, watched } = deps;
|
|
16460
|
+
return reportingRefusals({
|
|
16461
|
+
registerSurface: (surface) => {
|
|
16462
|
+
ctx.registerSurface(sanitizeRpcSurface(pluginId, surface, origins));
|
|
16463
|
+
},
|
|
16464
|
+
registerMenuItem: (item) => {
|
|
16465
|
+
ctx.registerMenuItem(sanitizeRpcMenuItem(item));
|
|
16466
|
+
},
|
|
16467
|
+
registerSettingsSection: (section) => {
|
|
16468
|
+
const built = buildFrameSection({
|
|
16469
|
+
pluginId,
|
|
16470
|
+
wire: sanitizeRpcSettingsSection(pluginId, section),
|
|
16471
|
+
group: deps.install.isInstalled(pluginId)
|
|
16472
|
+
? 'settings.group.community'
|
|
16473
|
+
: 'settings.group.plugins',
|
|
16474
|
+
store: deps.store,
|
|
16475
|
+
sync: deps.sync,
|
|
16476
|
+
notify: (sectionId, values) => deps.notify((remote) => remote.settingsChanged(sectionId, values)),
|
|
16477
|
+
});
|
|
16478
|
+
deps.syncCleanups.push(built.disposeSync);
|
|
16479
|
+
ctx.registerSettingsSection(built.section);
|
|
16480
|
+
},
|
|
16481
|
+
navigateContent: (path) => ctx.navigateContent(path),
|
|
16482
|
+
openContentTab: (input) => {
|
|
16483
|
+
const sanitized = sanitizeRpcTabInput(input);
|
|
16484
|
+
ctx.openContentTab({
|
|
16485
|
+
...sanitized,
|
|
16486
|
+
onClose: () => deps.notify((remote) => remote.contentTabClosed(sanitized.path)),
|
|
16487
|
+
});
|
|
16488
|
+
},
|
|
16489
|
+
keepContentTab: (path) => ctx.keepContentTab(path),
|
|
16490
|
+
pinContentTab: (path) => ctx.pinContentTab(path),
|
|
16491
|
+
unpinContentTab: (path) => ctx.unpinContentTab(path),
|
|
16492
|
+
closeContentTab: (path) => ctx.closeContentTab(path),
|
|
16493
|
+
revealSurface: (id) => ctx.revealSurface(id),
|
|
16494
|
+
invokeCommand: (id, args) => invokeRpcCommand(ctx, id, args),
|
|
16495
|
+
invocableCommands: () => ctx.invocableCommands(),
|
|
16496
|
+
toast: (input) => ctx.ui.toast(sanitizeRpcToastInput(input)),
|
|
16497
|
+
stateWatch: (key) => deps.watchState(key),
|
|
16498
|
+
stateSet: (key, value) => watched.get(key)?.handle.set(value),
|
|
16499
|
+
stateClear: (key) => watched.get(key)?.handle.clear(),
|
|
16500
|
+
stateUnwatch: (key) => {
|
|
16501
|
+
watched.get(key)?.stop();
|
|
16502
|
+
watched.delete(key);
|
|
16503
|
+
},
|
|
16504
|
+
}, deps.reportRefusal);
|
|
16505
|
+
}
|
|
16506
|
+
function reportingRefusals(methods, report) {
|
|
16507
|
+
const reported = Object.entries(methods).map(([name, method]) => [
|
|
16508
|
+
name,
|
|
16509
|
+
(...args) => {
|
|
16510
|
+
try {
|
|
16511
|
+
return method(...args);
|
|
16512
|
+
}
|
|
16513
|
+
catch (error) {
|
|
16514
|
+
report(error);
|
|
16515
|
+
throw error;
|
|
16516
|
+
}
|
|
16517
|
+
},
|
|
16518
|
+
]);
|
|
16519
|
+
return Object.fromEntries(reported);
|
|
16520
|
+
}
|
|
16521
|
+
|
|
16237
16522
|
/**
|
|
16238
16523
|
* Second {@link PluginRuntime} implementation:
|
|
16239
16524
|
* runs each plugin in an isolated `<iframe sandbox="allow-scripts">` and hands it `ctx` over **Penpal**
|
|
@@ -16291,9 +16576,13 @@ class FramePluginRuntime {
|
|
|
16291
16576
|
this.instances.delete(id);
|
|
16292
16577
|
instance.connection.destroy();
|
|
16293
16578
|
instance.frame.remove();
|
|
16294
|
-
|
|
16579
|
+
for (const entry of instance.watched.values()) {
|
|
16580
|
+
entry.stop();
|
|
16581
|
+
}
|
|
16295
16582
|
instance.ctx.disposeAll();
|
|
16296
|
-
instance.syncCleanups
|
|
16583
|
+
for (const cleanup of instance.syncCleanups) {
|
|
16584
|
+
cleanup();
|
|
16585
|
+
}
|
|
16297
16586
|
this.grants.unregister(id);
|
|
16298
16587
|
this.isolation.unregister(id);
|
|
16299
16588
|
}
|
|
@@ -16305,7 +16594,7 @@ class FramePluginRuntime {
|
|
|
16305
16594
|
}
|
|
16306
16595
|
}
|
|
16307
16596
|
reconcile(disabled, installed, deployed) {
|
|
16308
|
-
const runnable = this.
|
|
16597
|
+
const runnable = runnablePlugins(this.plugins, installed, deployed, this.catalogCap);
|
|
16309
16598
|
for (const plugin of runnable) {
|
|
16310
16599
|
this.enablement.register(plugin.id, plugin.name ?? plugin.id);
|
|
16311
16600
|
const enabled = plugin.provided === true || !disabled.has(plugin.id);
|
|
@@ -16323,34 +16612,6 @@ class FramePluginRuntime {
|
|
|
16323
16612
|
}
|
|
16324
16613
|
this.dropUninstalled(runnable);
|
|
16325
16614
|
}
|
|
16326
|
-
runnablePlugins(installed, deployed) {
|
|
16327
|
-
const claimed = new Set(this.plugins.map((plugin) => plugin.id));
|
|
16328
|
-
const provided = new Set(deployed.map((plugin) => plugin.id));
|
|
16329
|
-
const fromCatalog = [];
|
|
16330
|
-
for (const plugin of [...deployed, ...installed]) {
|
|
16331
|
-
if (claimed.has(plugin.id)) {
|
|
16332
|
-
continue;
|
|
16333
|
-
}
|
|
16334
|
-
const asked = plugin.level ?? DEFAULT_ISOLATION_LEVEL;
|
|
16335
|
-
if (exceedsLevel(asked, this.catalogCap)) {
|
|
16336
|
-
console.error(`Plugin "${plugin.id}" asks to run ${asked}, which this catalog may not confer ` +
|
|
16337
|
-
`(its cap is ${this.catalogCap}). It is not started.`);
|
|
16338
|
-
continue;
|
|
16339
|
-
}
|
|
16340
|
-
claimed.add(plugin.id);
|
|
16341
|
-
fromCatalog.push({
|
|
16342
|
-
id: plugin.id,
|
|
16343
|
-
entryUrl: plugin.entryUrl,
|
|
16344
|
-
capabilities: plugin.capabilities,
|
|
16345
|
-
name: plugin.name,
|
|
16346
|
-
granted: plugin.capabilities ?? [],
|
|
16347
|
-
version: plugin.version,
|
|
16348
|
-
level: asked,
|
|
16349
|
-
provided: provided.has(plugin.id) || undefined,
|
|
16350
|
-
});
|
|
16351
|
-
}
|
|
16352
|
-
return [...this.plugins, ...fromCatalog];
|
|
16353
|
-
}
|
|
16354
16615
|
dropUninstalled(runnable) {
|
|
16355
16616
|
const known = new Set(runnable.map((plugin) => plugin.id));
|
|
16356
16617
|
for (const id of this.instances.keys()) {
|
|
@@ -16374,7 +16635,19 @@ class FramePluginRuntime {
|
|
|
16374
16635
|
const watched = new Map();
|
|
16375
16636
|
const connection = connect({
|
|
16376
16637
|
messenger,
|
|
16377
|
-
methods:
|
|
16638
|
+
methods: frameRpcMethods({
|
|
16639
|
+
pluginId: plugin.id,
|
|
16640
|
+
ctx,
|
|
16641
|
+
origins: plugin.origins,
|
|
16642
|
+
install: this.install,
|
|
16643
|
+
store: this.store,
|
|
16644
|
+
sync: this.sync,
|
|
16645
|
+
syncCleanups,
|
|
16646
|
+
watched,
|
|
16647
|
+
watchState: (key) => this.watchState(plugin.id, ctx, watched, key),
|
|
16648
|
+
notify: (send) => this.notify(plugin.id, send),
|
|
16649
|
+
reportRefusal: (error) => this.refusals.report(error),
|
|
16650
|
+
}),
|
|
16378
16651
|
});
|
|
16379
16652
|
this.instances.set(plugin.id, {
|
|
16380
16653
|
ctx,
|
|
@@ -16385,10 +16658,11 @@ class FramePluginRuntime {
|
|
|
16385
16658
|
watched,
|
|
16386
16659
|
});
|
|
16387
16660
|
connection.promise.catch((error) => {
|
|
16388
|
-
if (this.instances.has(plugin.id)) {
|
|
16389
|
-
|
|
16390
|
-
this.deactivate(plugin.id);
|
|
16661
|
+
if (!this.instances.has(plugin.id)) {
|
|
16662
|
+
return;
|
|
16391
16663
|
}
|
|
16664
|
+
console.error(`Sandbox plugin "${plugin.id}" failed to connect`, error);
|
|
16665
|
+
this.deactivate(plugin.id);
|
|
16392
16666
|
});
|
|
16393
16667
|
}
|
|
16394
16668
|
createFrame(entryUrl, level) {
|
|
@@ -16399,71 +16673,9 @@ class FramePluginRuntime {
|
|
|
16399
16673
|
frame.setAttribute('aria-hidden', 'true');
|
|
16400
16674
|
frame.style.display = 'none';
|
|
16401
16675
|
frame.src = entryUrl;
|
|
16402
|
-
document.body.
|
|
16676
|
+
document.body.append(frame);
|
|
16403
16677
|
return frame;
|
|
16404
16678
|
}
|
|
16405
|
-
reportingRefusals(methods) {
|
|
16406
|
-
const reported = Object.entries(methods).map(([name, method]) => [
|
|
16407
|
-
name,
|
|
16408
|
-
(...args) => {
|
|
16409
|
-
try {
|
|
16410
|
-
return method(...args);
|
|
16411
|
-
}
|
|
16412
|
-
catch (error) {
|
|
16413
|
-
this.refusals.report(error);
|
|
16414
|
-
throw error;
|
|
16415
|
-
}
|
|
16416
|
-
},
|
|
16417
|
-
]);
|
|
16418
|
-
return Object.fromEntries(reported);
|
|
16419
|
-
}
|
|
16420
|
-
rpcMethods(pluginId, ctx, syncCleanups, watched, origins) {
|
|
16421
|
-
return {
|
|
16422
|
-
registerSurface: (surface) => {
|
|
16423
|
-
ctx.registerSurface(sanitizeRpcSurface(pluginId, surface, origins));
|
|
16424
|
-
},
|
|
16425
|
-
registerMenuItem: (item) => {
|
|
16426
|
-
ctx.registerMenuItem(sanitizeRpcMenuItem(item));
|
|
16427
|
-
},
|
|
16428
|
-
registerSettingsSection: (section) => {
|
|
16429
|
-
const built = buildFrameSection({
|
|
16430
|
-
pluginId,
|
|
16431
|
-
wire: sanitizeRpcSettingsSection(pluginId, section),
|
|
16432
|
-
group: this.install.isInstalled(pluginId)
|
|
16433
|
-
? 'settings.group.community'
|
|
16434
|
-
: 'settings.group.plugins',
|
|
16435
|
-
store: this.store,
|
|
16436
|
-
sync: this.sync,
|
|
16437
|
-
notify: (sectionId, values) => this.notifySettings(pluginId, sectionId, values),
|
|
16438
|
-
});
|
|
16439
|
-
syncCleanups.push(built.disposeSync);
|
|
16440
|
-
ctx.registerSettingsSection(built.section);
|
|
16441
|
-
},
|
|
16442
|
-
navigateContent: (path) => ctx.navigateContent(path),
|
|
16443
|
-
openContentTab: (input) => {
|
|
16444
|
-
const sanitized = sanitizeRpcTabInput(input);
|
|
16445
|
-
ctx.openContentTab({
|
|
16446
|
-
...sanitized,
|
|
16447
|
-
onClose: () => this.notifyTabClosed(pluginId, sanitized.path),
|
|
16448
|
-
});
|
|
16449
|
-
},
|
|
16450
|
-
keepContentTab: (path) => ctx.keepContentTab(path),
|
|
16451
|
-
pinContentTab: (path) => ctx.pinContentTab(path),
|
|
16452
|
-
unpinContentTab: (path) => ctx.unpinContentTab(path),
|
|
16453
|
-
closeContentTab: (path) => ctx.closeContentTab(path),
|
|
16454
|
-
revealSurface: (id) => ctx.revealSurface(id),
|
|
16455
|
-
invokeCommand: (id, args) => invokeRpcCommand(ctx, id, args),
|
|
16456
|
-
invocableCommands: () => ctx.invocableCommands(),
|
|
16457
|
-
toast: (input) => ctx.ui.toast(sanitizeRpcToastInput(input)),
|
|
16458
|
-
stateWatch: (key) => this.watchState(pluginId, ctx, watched, key),
|
|
16459
|
-
stateSet: (key, value) => watched.get(key)?.handle.set(value),
|
|
16460
|
-
stateClear: (key) => watched.get(key)?.handle.clear(),
|
|
16461
|
-
stateUnwatch: (key) => {
|
|
16462
|
-
watched.get(key)?.stop();
|
|
16463
|
-
watched.delete(key);
|
|
16464
|
-
},
|
|
16465
|
-
};
|
|
16466
|
-
}
|
|
16467
16679
|
watchState(pluginId, ctx, watched, key) {
|
|
16468
16680
|
if (watched.has(key)) {
|
|
16469
16681
|
return;
|
|
@@ -16472,7 +16684,7 @@ class FramePluginRuntime {
|
|
|
16472
16684
|
const ref = effect(() => {
|
|
16473
16685
|
const value = handle.value();
|
|
16474
16686
|
const loaded = handle.loaded();
|
|
16475
|
-
untracked(() => this.
|
|
16687
|
+
untracked(() => this.notify(pluginId, (remote) => remote.stateChanged(key, value, loaded)));
|
|
16476
16688
|
}, { ...(ngDevMode ? { debugName: "ref" } : /* istanbul ignore next */ {}), injector: this.injector });
|
|
16477
16689
|
watched.set(key, {
|
|
16478
16690
|
handle,
|
|
@@ -16482,32 +16694,12 @@ class FramePluginRuntime {
|
|
|
16482
16694
|
},
|
|
16483
16695
|
});
|
|
16484
16696
|
}
|
|
16485
|
-
|
|
16486
|
-
const instance = this.instances.get(pluginId);
|
|
16487
|
-
if (!instance) {
|
|
16488
|
-
return;
|
|
16489
|
-
}
|
|
16490
|
-
void instance.connection.promise
|
|
16491
|
-
.then((remote) => remote.stateChanged(key, value, loaded))
|
|
16492
|
-
.catch(() => undefined);
|
|
16493
|
-
}
|
|
16494
|
-
notifyTabClosed(pluginId, path) {
|
|
16697
|
+
notify(pluginId, send) {
|
|
16495
16698
|
const instance = this.instances.get(pluginId);
|
|
16496
16699
|
if (!instance) {
|
|
16497
16700
|
return;
|
|
16498
16701
|
}
|
|
16499
|
-
void instance.connection.promise
|
|
16500
|
-
.then((remote) => remote.contentTabClosed(path))
|
|
16501
|
-
.catch(() => undefined);
|
|
16502
|
-
}
|
|
16503
|
-
notifySettings(pluginId, sectionId, values) {
|
|
16504
|
-
const instance = this.instances.get(pluginId);
|
|
16505
|
-
if (!instance) {
|
|
16506
|
-
return;
|
|
16507
|
-
}
|
|
16508
|
-
void instance.connection.promise
|
|
16509
|
-
.then((remote) => remote.settingsChanged(sectionId, values))
|
|
16510
|
-
.catch(() => undefined);
|
|
16702
|
+
void instance.connection.promise.then(send).catch(() => undefined);
|
|
16511
16703
|
}
|
|
16512
16704
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FramePluginRuntime, deps: [], target: i0.ɵɵFactoryTarget.Service });
|
|
16513
16705
|
static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.1.0", ngImport: i0, type: FramePluginRuntime });
|
|
@@ -16651,12 +16843,12 @@ function availableUpdate(installed, entry) {
|
|
|
16651
16843
|
return isNewerVersion(entry.version, installed.version) ? entry : undefined;
|
|
16652
16844
|
}
|
|
16653
16845
|
function addedCapabilities(entry, installed) {
|
|
16654
|
-
const consented = new Set(installed.capabilities
|
|
16846
|
+
const consented = new Set(installed.capabilities);
|
|
16655
16847
|
return (entry.capabilities ?? []).filter((capability) => !consented.has(capability));
|
|
16656
16848
|
}
|
|
16657
16849
|
|
|
16658
16850
|
async function confirmUpdate(deps, entry) {
|
|
16659
|
-
const installed = deps.installs.
|
|
16851
|
+
const installed = deps.installs.byId(entry.id);
|
|
16660
16852
|
if (!installed) {
|
|
16661
16853
|
return;
|
|
16662
16854
|
}
|
|
@@ -16822,7 +17014,7 @@ class PluginStoreDetail {
|
|
|
16822
17014
|
transloco = inject(TranslocoService);
|
|
16823
17015
|
readme = signal(undefined, /* @ts-ignore */
|
|
16824
17016
|
...(ngDevMode ? [{ debugName: "readme" }] : /* istanbul ignore next */ []));
|
|
16825
|
-
update = computed(() => availableUpdate(this.installs.
|
|
17017
|
+
update = computed(() => availableUpdate(this.installs.byId(this.entry().id), this.entry()), /* @ts-ignore */
|
|
16826
17018
|
...(ngDevMode ? [{ debugName: "update" }] : /* istanbul ignore next */ []));
|
|
16827
17019
|
constructor() {
|
|
16828
17020
|
effect(() => {
|
|
@@ -16908,7 +17100,7 @@ class PluginStoreDialog {
|
|
|
16908
17100
|
...(ngDevMode ? [{ debugName: "selectedId" }] : /* istanbul ignore next */ []));
|
|
16909
17101
|
filtered = computed(() => {
|
|
16910
17102
|
const list = (this.entries() ?? []).filter((entry) => matchesQuery([entry.name, entry.author, entry.category, entry.description], this.query()));
|
|
16911
|
-
return list.
|
|
17103
|
+
return list.toSorted((a, b) => (b.downloads ?? 0) - (a.downloads ?? 0) || a.name.localeCompare(b.name));
|
|
16912
17104
|
}, /* @ts-ignore */
|
|
16913
17105
|
...(ngDevMode ? [{ debugName: "filtered" }] : /* istanbul ignore next */ []));
|
|
16914
17106
|
selected = computed(() => this.filtered().find((entry) => entry.id === this.selectedId()), /* @ts-ignore */
|
|
@@ -16920,7 +17112,7 @@ class PluginStoreDialog {
|
|
|
16920
17112
|
void confirmInstall(this.consentDeps, entry);
|
|
16921
17113
|
}
|
|
16922
17114
|
hasUpdate(entry) {
|
|
16923
|
-
return availableUpdate(this.installs.
|
|
17115
|
+
return availableUpdate(this.installs.byId(entry.id), entry) !== undefined;
|
|
16924
17116
|
}
|
|
16925
17117
|
requestUpdate(entry) {
|
|
16926
17118
|
void confirmUpdate(this.consentDeps, entry);
|