@loomweaver/shell 0.7.4 → 0.7.6
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.
|
@@ -12,7 +12,7 @@ import { filter, map, of, catchError, forkJoin } from 'rxjs';
|
|
|
12
12
|
import { DomSanitizer } from '@angular/platform-browser';
|
|
13
13
|
import { WindowMessenger, connect } from 'penpal';
|
|
14
14
|
import DOMPurify from 'dompurify';
|
|
15
|
-
import { heroMinus, heroArrowsPointingIn, heroArrowTopRightOnSquare, heroArrowsPointingOut, heroArrowDownTray, heroPuzzlePiece, heroMagnifyingGlass, heroQuestionMarkCircle, heroCog6Tooth, heroXCircle, heroExclamationTriangle, heroCheck, heroLockClosed, heroInformationCircle, heroArrowUturnLeft, heroArrowPath, heroArrowsUpDown, heroEye, heroTrash, heroPencilSquare, heroPlus, heroListBullet, heroBars3, heroDocument, heroRectangleGroup, heroXMark, heroChevronDown, heroChevronDoubleRight, heroChevronDoubleLeft, heroComputerDesktop, heroMoon, heroSun } from '@ng-icons/heroicons/outline';
|
|
15
|
+
import { heroMinus, heroArrowsPointingIn, heroArrowTopRightOnSquare, heroArrowsPointingOut, heroArrowDownTray, heroPuzzlePiece, heroMagnifyingGlass, heroQuestionMarkCircle, heroCog6Tooth, heroXCircle, heroExclamationTriangle, heroCheck, heroLockClosed, heroInformationCircle, heroArrowUturnLeft, heroArrowPath, heroArrowsUpDown, heroEye, heroTrash, heroPencilSquare, heroPlus, heroListBullet, heroBars3, heroViewColumns, heroDocument, heroRectangleGroup, heroXMark, heroChevronDown, heroChevronDoubleRight, heroChevronDoubleLeft, heroComputerDesktop, heroMoon, heroSun } from '@ng-icons/heroicons/outline';
|
|
16
16
|
import { HttpClient, provideHttpClient } from '@angular/common/http';
|
|
17
17
|
import { SwUpdate, provideServiceWorker } from '@angular/service-worker';
|
|
18
18
|
import { marked } from 'marked';
|
|
@@ -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
|
}
|
|
@@ -4331,7 +4386,7 @@ class OpenTabsService {
|
|
|
4331
4386
|
activeViewInstance = computed(() => {
|
|
4332
4387
|
const path = this.activeViewPath();
|
|
4333
4388
|
if (path === null) {
|
|
4334
|
-
return
|
|
4389
|
+
return;
|
|
4335
4390
|
}
|
|
4336
4391
|
return this.paneTree
|
|
4337
4392
|
.primaryTabs(CONTENT_DOCK)
|
|
@@ -4367,7 +4422,7 @@ class OpenTabsService {
|
|
|
4367
4422
|
const facets = facetTabViews(routes, (route) => this.addressOf(route));
|
|
4368
4423
|
const open = this.openTabs().filter((tab) => this.strippable(routes, tab));
|
|
4369
4424
|
const dynamics = dynamicTabViews(routes, open);
|
|
4370
|
-
return [...facets, ...dynamics, ...this.viewTabs()].
|
|
4425
|
+
return [...facets, ...dynamics, ...this.viewTabs()].toSorted((a, b) => a.order - b.order);
|
|
4371
4426
|
}, /* @ts-ignore */
|
|
4372
4427
|
...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
|
|
4373
4428
|
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 */
|
|
@@ -4419,9 +4474,9 @@ class OpenTabsService {
|
|
|
4419
4474
|
navigateTo(path) {
|
|
4420
4475
|
this.navigate(path).catch((error) => console.error('Content navigation failed', error));
|
|
4421
4476
|
}
|
|
4422
|
-
updateOpen(
|
|
4477
|
+
updateOpen(function_) {
|
|
4423
4478
|
const current = this.openTabs();
|
|
4424
|
-
const next =
|
|
4479
|
+
const next = function_(current);
|
|
4425
4480
|
if (next === current) {
|
|
4426
4481
|
return;
|
|
4427
4482
|
}
|
|
@@ -4496,7 +4551,7 @@ class OpenTabsService {
|
|
|
4496
4551
|
const routes = this.registry.contentRoutes();
|
|
4497
4552
|
this.updateOpen((tabs) => {
|
|
4498
4553
|
const index = tabs.findIndex((tab) => tabRootOf(routes, tab.path) === root);
|
|
4499
|
-
if (index
|
|
4554
|
+
if (index !== -1) {
|
|
4500
4555
|
return withRefreshedPath(tabs, index, path);
|
|
4501
4556
|
}
|
|
4502
4557
|
const opened = autoOpenedTab(route, root, path);
|
|
@@ -4511,7 +4566,7 @@ class OpenTabsService {
|
|
|
4511
4566
|
return (route !== undefined &&
|
|
4512
4567
|
route.chromeless !== true &&
|
|
4513
4568
|
route.follows !== true &&
|
|
4514
|
-
|
|
4569
|
+
(normalizePath(route.path) !== '' || normalizePath(tab.path) === ''));
|
|
4515
4570
|
}
|
|
4516
4571
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: OpenTabsService, deps: [], target: i0.ɵɵFactoryTarget.Service });
|
|
4517
4572
|
static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.1.0", ngImport: i0, type: OpenTabsService });
|
|
@@ -4927,7 +4982,7 @@ class SurfaceCloseGuard {
|
|
|
4927
4982
|
}
|
|
4928
4983
|
async saveAll(dirty) {
|
|
4929
4984
|
try {
|
|
4930
|
-
await Promise.all(dirty.map((surface) => surface.surfaceSave?.()));
|
|
4985
|
+
await Promise.all(dirty.map(async (surface) => surface.surfaceSave?.()));
|
|
4931
4986
|
}
|
|
4932
4987
|
catch (error) {
|
|
4933
4988
|
console.error('Save before closing failed', error);
|
|
@@ -5074,9 +5129,17 @@ class TabClosingService {
|
|
|
5074
5129
|
const closing = this.state.openTabs().filter((tab) => roots.has(tabRootOf(routes, tab.path)));
|
|
5075
5130
|
const activeWentAway = roots.has(this.state.activeTabRoot());
|
|
5076
5131
|
this.state.updateOpen((tabs) => tabs.filter((tab) => !roots.has(tabRootOf(routes, tab.path))));
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5132
|
+
for (const tab of closing) {
|
|
5133
|
+
this.closeHooks.runSafely(tab.onClose);
|
|
5134
|
+
}
|
|
5135
|
+
for (const root of roots) {
|
|
5136
|
+
this.closeHooks.delete(root);
|
|
5137
|
+
}
|
|
5138
|
+
const evictAll = () => {
|
|
5139
|
+
for (const root of roots) {
|
|
5140
|
+
this.reuse.evict(root);
|
|
5141
|
+
}
|
|
5142
|
+
};
|
|
5080
5143
|
if (activeWentAway) {
|
|
5081
5144
|
void this.state.navigate(fallbackPath)
|
|
5082
5145
|
.catch((error) => console.error('Content navigation failed', error))
|
|
@@ -5108,7 +5171,7 @@ class TabClosingService {
|
|
|
5108
5171
|
neighbourPath(root) {
|
|
5109
5172
|
const routes = this.registry.contentRoutes();
|
|
5110
5173
|
const siblings = this.state.openTabs().filter((tab) => tabRootOf(routes, tab.path) !== root);
|
|
5111
|
-
return siblings.
|
|
5174
|
+
return siblings.at(-1)?.path ?? '';
|
|
5112
5175
|
}
|
|
5113
5176
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: TabClosingService, deps: [], target: i0.ɵɵFactoryTarget.Service });
|
|
5114
5177
|
static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.1.0", ngImport: i0, type: TabClosingService });
|
|
@@ -5193,7 +5256,7 @@ class ContentTabsService {
|
|
|
5193
5256
|
const seat = (tab, fallback) => rank.get(tabRootOf(routes, tab.path)) ?? fallback;
|
|
5194
5257
|
this.state.updateOpen((tabs) => tabs
|
|
5195
5258
|
.map((tab, index) => ({ tab, index }))
|
|
5196
|
-
.
|
|
5259
|
+
.toSorted((a, b) => seat(a.tab, a.index) - seat(b.tab, b.index))
|
|
5197
5260
|
.map((entry) => entry.tab));
|
|
5198
5261
|
}
|
|
5199
5262
|
/**
|
|
@@ -5209,7 +5272,7 @@ class ContentTabsService {
|
|
|
5209
5272
|
const { routes, root } = this.state.rootFor(path);
|
|
5210
5273
|
this.state.updateOpen((tabs) => {
|
|
5211
5274
|
const index = tabs.findIndex((tab) => tabRootOf(routes, tab.path) === root);
|
|
5212
|
-
return index
|
|
5275
|
+
return index === -1 || tabs[index].pinned
|
|
5213
5276
|
? tabs
|
|
5214
5277
|
: reseatPinned(tabs, index, tabs[index]);
|
|
5215
5278
|
});
|
|
@@ -5364,7 +5427,7 @@ class ContentTabsService {
|
|
|
5364
5427
|
const { routes, root } = this.state.rootFor(path);
|
|
5365
5428
|
this.state.updateOpen((tabs) => {
|
|
5366
5429
|
const index = tabs.findIndex((tab) => tabRootOf(routes, tab.path) === root);
|
|
5367
|
-
if (index
|
|
5430
|
+
if (index === -1 || tabs[index].pinned === pinned) {
|
|
5368
5431
|
return tabs;
|
|
5369
5432
|
}
|
|
5370
5433
|
const updated = pinned
|
|
@@ -5434,7 +5497,7 @@ class PanelViewsService {
|
|
|
5434
5497
|
const declared = this.registry
|
|
5435
5498
|
.views()
|
|
5436
5499
|
.filter((view) => view.region === regionId)
|
|
5437
|
-
.
|
|
5500
|
+
.toSorted((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
5438
5501
|
return this.order.applyOrder(panelViewsContainerId(regionId), declared, (view) => view.id);
|
|
5439
5502
|
}
|
|
5440
5503
|
candidatesFor(regionId) {
|
|
@@ -5449,7 +5512,7 @@ class PanelViewsService {
|
|
|
5449
5512
|
(panelRegions.has(view.region) &&
|
|
5450
5513
|
(holder === null || !panelRegions.has(holder))))
|
|
5451
5514
|
.map(({ view, holder }) => ({ view, here: holder === regionId }))
|
|
5452
|
-
.
|
|
5515
|
+
.toSorted((a, b) => Number(b.here) - Number(a.here) ||
|
|
5453
5516
|
(a.view.order ?? 0) - (b.view.order ?? 0));
|
|
5454
5517
|
}
|
|
5455
5518
|
holderOf(viewId) {
|
|
@@ -5485,7 +5548,7 @@ class HiddenViewsService {
|
|
|
5485
5548
|
...(ngDevMode ? [{ debugName: "ids" }] : /* istanbul ignore next */ []));
|
|
5486
5549
|
hidden = this.ids.asReadonly();
|
|
5487
5550
|
constructor() {
|
|
5488
|
-
|
|
5551
|
+
this.hydrateWhenWorkspaceReady();
|
|
5489
5552
|
}
|
|
5490
5553
|
isHidden(viewId) {
|
|
5491
5554
|
return this.ids().has(viewId);
|
|
@@ -5509,7 +5572,7 @@ class HiddenViewsService {
|
|
|
5509
5572
|
void this.store.set(this.storageKey(), this.serialize());
|
|
5510
5573
|
}
|
|
5511
5574
|
serialize() {
|
|
5512
|
-
return JSON.stringify([...this.ids()].
|
|
5575
|
+
return JSON.stringify([...this.ids()].toSorted((a, b) => a.localeCompare(b)));
|
|
5513
5576
|
}
|
|
5514
5577
|
commit(next) {
|
|
5515
5578
|
this.ids.set(next);
|
|
@@ -5518,6 +5581,9 @@ class HiddenViewsService {
|
|
|
5518
5581
|
storageKey() {
|
|
5519
5582
|
return this.workspace.scopedKey(STORAGE_KEY$b);
|
|
5520
5583
|
}
|
|
5584
|
+
hydrateWhenWorkspaceReady() {
|
|
5585
|
+
void this.workspace.ready.then(() => hydrateAsync(this.store, this.storageKey(), (raw) => this.ids.set(parseHiddenViews(raw))));
|
|
5586
|
+
}
|
|
5521
5587
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: HiddenViewsService, deps: [], target: i0.ɵɵFactoryTarget.Service });
|
|
5522
5588
|
static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.1.0", ngImport: i0, type: HiddenViewsService });
|
|
5523
5589
|
}
|
|
@@ -5624,9 +5690,9 @@ function tabGaps(definition, routes, id) {
|
|
|
5624
5690
|
}
|
|
5625
5691
|
function sidebarGaps(definition, declaredPaths, id) {
|
|
5626
5692
|
return Object.entries(definition.sidebars ?? {}).flatMap(([region, visible]) => {
|
|
5627
|
-
const declared = declaredPaths(region).map((path) => path.slice(VIEW_PANE_PREFIX.length));
|
|
5693
|
+
const declared = new Set(declaredPaths(region).map((path) => path.slice(VIEW_PANE_PREFIX.length)));
|
|
5628
5694
|
return visible
|
|
5629
|
-
.filter((viewId) => !declared.
|
|
5695
|
+
.filter((viewId) => !declared.has(viewId))
|
|
5630
5696
|
.map((viewId) => `Workspace "${id}": sidebar view "${viewId}" is not declared for region "${region}" — the entry has no effect.`);
|
|
5631
5697
|
});
|
|
5632
5698
|
}
|
|
@@ -5643,7 +5709,7 @@ function canonicalState(read, shape) {
|
|
|
5643
5709
|
}
|
|
5644
5710
|
function canonicalValue(key, raw, hidden, shape) {
|
|
5645
5711
|
if (key === shape.hiddenViewsKey) {
|
|
5646
|
-
return JSON.stringify([...hidden].
|
|
5712
|
+
return JSON.stringify([...hidden].toSorted((a, b) => a.localeCompare(b)));
|
|
5647
5713
|
}
|
|
5648
5714
|
if (key === shape.paneTreesKey) {
|
|
5649
5715
|
return canonicalTrees(raw, hidden, shape);
|
|
@@ -5657,7 +5723,8 @@ function canonicalTrees(raw, hidden, shape) {
|
|
|
5657
5723
|
try {
|
|
5658
5724
|
const parsed = JSON.parse(raw);
|
|
5659
5725
|
const out = {};
|
|
5660
|
-
|
|
5726
|
+
const docks = Object.keys(parsed ?? {}).toSorted((a, b) => a.localeCompare(b));
|
|
5727
|
+
for (const dock of docks) {
|
|
5661
5728
|
const entry = normalizeDockEntry(parsed[dock]);
|
|
5662
5729
|
if (entry === null) {
|
|
5663
5730
|
continue;
|
|
@@ -5706,7 +5773,7 @@ function comparableTab(tab) {
|
|
|
5706
5773
|
}
|
|
5707
5774
|
function comparableNode(node) {
|
|
5708
5775
|
if (node.kind === 'leaf') {
|
|
5709
|
-
return { ...node, tabs: node.tabs.map(comparableTab) };
|
|
5776
|
+
return { ...node, tabs: node.tabs.map((tab) => comparableTab(tab)) };
|
|
5710
5777
|
}
|
|
5711
5778
|
return {
|
|
5712
5779
|
...node,
|
|
@@ -5732,7 +5799,7 @@ function baseInitials(name) {
|
|
|
5732
5799
|
if (letters.length === 1) {
|
|
5733
5800
|
return letters[0].toUpperCase();
|
|
5734
5801
|
}
|
|
5735
|
-
return (letters[0] + letters
|
|
5802
|
+
return (letters[0] + (letters.at(-1) ?? '')).toUpperCase();
|
|
5736
5803
|
}
|
|
5737
5804
|
function* candidatesFor(name) {
|
|
5738
5805
|
const base = baseInitials(name);
|
|
@@ -5741,8 +5808,8 @@ function* candidatesFor(name) {
|
|
|
5741
5808
|
}
|
|
5742
5809
|
yield base;
|
|
5743
5810
|
const letters = [...(wordsOf(name)[0] ?? '')];
|
|
5744
|
-
for (let
|
|
5745
|
-
yield (letters[0] + letters[
|
|
5811
|
+
for (let index = 1; index < letters.length; index++) {
|
|
5812
|
+
yield (letters[0] + letters[index]).toUpperCase();
|
|
5746
5813
|
}
|
|
5747
5814
|
for (let digit = 2; digit <= LAST_RESORT_DIGITS; digit++) {
|
|
5748
5815
|
yield letters[0].toUpperCase() + digit;
|
|
@@ -5752,17 +5819,22 @@ function assignWorkspaceInitials(workspaces) {
|
|
|
5752
5819
|
const taken = new Set();
|
|
5753
5820
|
const assigned = new Map();
|
|
5754
5821
|
for (const workspace of workspaces) {
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
continue;
|
|
5758
|
-
}
|
|
5822
|
+
const candidate = firstFree(candidatesFor(workspace.name), taken);
|
|
5823
|
+
if (candidate !== undefined) {
|
|
5759
5824
|
taken.add(candidate);
|
|
5760
5825
|
assigned.set(workspace.id, candidate);
|
|
5761
|
-
break;
|
|
5762
5826
|
}
|
|
5763
5827
|
}
|
|
5764
5828
|
return assigned;
|
|
5765
5829
|
}
|
|
5830
|
+
function firstFree(candidates, taken) {
|
|
5831
|
+
for (const candidate of candidates) {
|
|
5832
|
+
if (!taken.has(candidate)) {
|
|
5833
|
+
return candidate;
|
|
5834
|
+
}
|
|
5835
|
+
}
|
|
5836
|
+
return undefined;
|
|
5837
|
+
}
|
|
5766
5838
|
|
|
5767
5839
|
const STORAGE_KEY$a = 'lw.shell.workspaces';
|
|
5768
5840
|
const HIDDEN_VIEWS_KEY = 'lw.shell.hidden-views';
|
|
@@ -5849,14 +5921,16 @@ class WorkspaceService {
|
|
|
5849
5921
|
}, /* @ts-ignore */
|
|
5850
5922
|
...(ngDevMode ? [{ debugName: "changedIds" }] : /* istanbul ignore next */ []));
|
|
5851
5923
|
constructor() {
|
|
5852
|
-
|
|
5853
|
-
this.
|
|
5924
|
+
const setList = (raw) => this.list.set(parse(raw));
|
|
5925
|
+
hydrateAsync(this.store, STORAGE_KEY$a, setList);
|
|
5926
|
+
this.sync.register('settings', STORAGE_KEY$a, setList);
|
|
5854
5927
|
if (isDevMode()) {
|
|
5855
|
-
|
|
5928
|
+
const all = this.definitionBatches.flat();
|
|
5929
|
+
for (const problem of auditWorkspaceDefinitions(all, this.panelRegions)) {
|
|
5856
5930
|
console.warn(problem);
|
|
5857
5931
|
}
|
|
5858
5932
|
}
|
|
5859
|
-
|
|
5933
|
+
this.layOutAdoptedWorkspaceWhenReady();
|
|
5860
5934
|
}
|
|
5861
5935
|
async saveCurrent(name) {
|
|
5862
5936
|
const baseline = await this.currentState();
|
|
@@ -5864,7 +5938,7 @@ class WorkspaceService {
|
|
|
5864
5938
|
const origin = this.originOf(this.active.id());
|
|
5865
5939
|
this.commit([
|
|
5866
5940
|
...this.list(),
|
|
5867
|
-
{ id, name, baseline, ...(origin
|
|
5941
|
+
{ id, name, baseline, ...(origin !== null && { origin }) },
|
|
5868
5942
|
]);
|
|
5869
5943
|
this.active.set(id);
|
|
5870
5944
|
this.applyState(baseline);
|
|
@@ -5976,10 +6050,8 @@ class WorkspaceService {
|
|
|
5976
6050
|
declaredPaths: (region) => this.panelGroups.declaredPaths(region),
|
|
5977
6051
|
});
|
|
5978
6052
|
return {
|
|
5979
|
-
...(state.hiddenViews
|
|
5980
|
-
|
|
5981
|
-
: { [HIDDEN_VIEWS_KEY]: state.hiddenViews }),
|
|
5982
|
-
...(state.trees === undefined ? {} : { [PANE_TREES_KEY]: state.trees }),
|
|
6053
|
+
...(state.hiddenViews !== undefined && { [HIDDEN_VIEWS_KEY]: state.hiddenViews }),
|
|
6054
|
+
...(state.trees !== undefined && { [PANE_TREES_KEY]: state.trees }),
|
|
5983
6055
|
};
|
|
5984
6056
|
}
|
|
5985
6057
|
warnDeclarationGaps(id) {
|
|
@@ -6043,6 +6115,9 @@ class WorkspaceService {
|
|
|
6043
6115
|
this.list.set(next);
|
|
6044
6116
|
void this.store.set(STORAGE_KEY$a, JSON.stringify(next));
|
|
6045
6117
|
}
|
|
6118
|
+
layOutAdoptedWorkspaceWhenReady() {
|
|
6119
|
+
void this.active.ready.then(() => this.layOutAdoptedWorkspace());
|
|
6120
|
+
}
|
|
6046
6121
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: WorkspaceService, deps: [], target: i0.ɵɵFactoryTarget.Service });
|
|
6047
6122
|
static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.1.0", ngImport: i0, type: WorkspaceService });
|
|
6048
6123
|
}
|
|
@@ -6081,7 +6156,7 @@ class ShellRail {
|
|
|
6081
6156
|
.filter((item) => this.railItems.regionOf(item.id, item.rail) === this.region().id)
|
|
6082
6157
|
.filter((item) => this.auth.visible(item.access))
|
|
6083
6158
|
.filter((item) => item.workspace !== undefined || this.commands.triggerable(item))
|
|
6084
|
-
.
|
|
6159
|
+
.toSorted((a, b) => (a.order ?? 0) - (b.order ?? 0)), /* @ts-ignore */
|
|
6085
6160
|
...(ngDevMode ? [{ debugName: "registered" }] : /* istanbul ignore next */ []));
|
|
6086
6161
|
items = computed(() => {
|
|
6087
6162
|
const inRail = this.registered().filter((item) => this.railItems.isVisible(item.id));
|
|
@@ -6089,7 +6164,7 @@ class ShellRail {
|
|
|
6089
6164
|
const id = this.containerId();
|
|
6090
6165
|
const key = (item) => item.id;
|
|
6091
6166
|
const top = this.userOrder.applyOrder(id, inRail.filter((item) => !isBottom(item)), key);
|
|
6092
|
-
const bottom = this.userOrder.applyOrder(id, inRail.filter(isBottom), key);
|
|
6167
|
+
const bottom = this.userOrder.applyOrder(id, inRail.filter((item) => isBottom(item)), key);
|
|
6093
6168
|
return [...top, ...bottom];
|
|
6094
6169
|
}, /* @ts-ignore */
|
|
6095
6170
|
...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
@@ -6234,10 +6309,11 @@ class ViewStateService {
|
|
|
6234
6309
|
hydrateAsync(this.store, key, (raw) => value.set(parseBlob$1(raw)));
|
|
6235
6310
|
let timer;
|
|
6236
6311
|
const cancelPendingSave = () => {
|
|
6237
|
-
if (timer
|
|
6238
|
-
|
|
6239
|
-
timer = undefined;
|
|
6312
|
+
if (timer === undefined) {
|
|
6313
|
+
return;
|
|
6240
6314
|
}
|
|
6315
|
+
clearTimeout(timer);
|
|
6316
|
+
timer = undefined;
|
|
6241
6317
|
};
|
|
6242
6318
|
const save = () => {
|
|
6243
6319
|
cancelPendingSave();
|
|
@@ -6275,12 +6351,12 @@ function parseRecord(viewId, raw) {
|
|
|
6275
6351
|
}
|
|
6276
6352
|
const record = parsed;
|
|
6277
6353
|
const instances = Array.isArray(record.instances)
|
|
6278
|
-
? record.instances.filter((
|
|
6354
|
+
? record.instances.filter((index) => !!index && typeof index.id === 'string' && typeof index.name === 'string')
|
|
6279
6355
|
: [];
|
|
6280
|
-
const withoutDefault = instances.filter((
|
|
6356
|
+
const withoutDefault = instances.filter((index) => index.id !== viewId);
|
|
6281
6357
|
const merged = [{ id: viewId, name: '' }, ...withoutDefault];
|
|
6282
6358
|
const activeId = typeof record.activeId === 'string' &&
|
|
6283
|
-
merged.some((
|
|
6359
|
+
merged.some((index) => index.id === record.activeId)
|
|
6284
6360
|
? record.activeId
|
|
6285
6361
|
: viewId;
|
|
6286
6362
|
return { instances: merged, activeId };
|
|
@@ -6313,7 +6389,7 @@ class ViewInstanceService {
|
|
|
6313
6389
|
}
|
|
6314
6390
|
setActive(viewId, instanceId) {
|
|
6315
6391
|
const record = this.recordFor(viewId);
|
|
6316
|
-
if (record().instances.some((
|
|
6392
|
+
if (record().instances.some((index) => index.id === instanceId)) {
|
|
6317
6393
|
this.commit(viewId, { ...record(), activeId: instanceId });
|
|
6318
6394
|
}
|
|
6319
6395
|
}
|
|
@@ -6332,7 +6408,7 @@ class ViewInstanceService {
|
|
|
6332
6408
|
const record = this.recordFor(viewId);
|
|
6333
6409
|
this.commit(viewId, {
|
|
6334
6410
|
...record(),
|
|
6335
|
-
instances: record().instances.map((
|
|
6411
|
+
instances: record().instances.map((index) => index.id === instanceId ? { ...index, name } : index),
|
|
6336
6412
|
});
|
|
6337
6413
|
}
|
|
6338
6414
|
remove(viewId, instanceId) {
|
|
@@ -6340,7 +6416,7 @@ class ViewInstanceService {
|
|
|
6340
6416
|
return;
|
|
6341
6417
|
}
|
|
6342
6418
|
const record = this.recordFor(viewId);
|
|
6343
|
-
const instances = record().instances.filter((
|
|
6419
|
+
const instances = record().instances.filter((index) => index.id !== instanceId);
|
|
6344
6420
|
const activeId = record().activeId === instanceId ? viewId : record().activeId;
|
|
6345
6421
|
this.commit(viewId, { instances, activeId });
|
|
6346
6422
|
this.viewStates.clear(instanceId);
|
|
@@ -6601,7 +6677,7 @@ class PaneChromeService {
|
|
|
6601
6677
|
clearMinimized(dock) {
|
|
6602
6678
|
const prefix = `${dock}:`;
|
|
6603
6679
|
const current = this.min();
|
|
6604
|
-
if (
|
|
6680
|
+
if ([...current].every((id) => !id.startsWith(prefix))) {
|
|
6605
6681
|
return;
|
|
6606
6682
|
}
|
|
6607
6683
|
this.min.set(new Set([...current].filter((id) => !id.startsWith(prefix))));
|
|
@@ -6668,7 +6744,7 @@ function bakeChild(dock, spec, entry, problems, context) {
|
|
|
6668
6744
|
return {
|
|
6669
6745
|
tab: {
|
|
6670
6746
|
...containerChildTab(dock, spec, declared.surface),
|
|
6671
|
-
...(declared.closable === false
|
|
6747
|
+
...(declared.closable === false && { closable: false }),
|
|
6672
6748
|
},
|
|
6673
6749
|
active: declared.active === true,
|
|
6674
6750
|
};
|
|
@@ -6705,13 +6781,11 @@ class PaneContainersService {
|
|
|
6705
6781
|
const tab = {
|
|
6706
6782
|
path,
|
|
6707
6783
|
instance: `${dock}::${path}`,
|
|
6708
|
-
...(label?.title
|
|
6709
|
-
|
|
6710
|
-
:
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
}),
|
|
6714
|
-
...(label?.icon === undefined ? {} : { icon: label.icon }),
|
|
6784
|
+
...(label?.title !== undefined && {
|
|
6785
|
+
title: label.title,
|
|
6786
|
+
literalTitle: label.titleIsLiteral ?? false,
|
|
6787
|
+
}),
|
|
6788
|
+
...(label?.icon !== undefined && { icon: label.icon }),
|
|
6715
6789
|
};
|
|
6716
6790
|
this.paneTree.commit(dock, setActiveTab(insertTab(tree, target, tab), target, path));
|
|
6717
6791
|
}
|
|
@@ -6737,11 +6811,11 @@ function offRouterPaneTargets(registry, auth) {
|
|
|
6737
6811
|
const routes = registry
|
|
6738
6812
|
.contentRoutes()
|
|
6739
6813
|
.filter((route) => offRouterMountable(registry, auth, route.path))
|
|
6740
|
-
.map(routeTarget);
|
|
6814
|
+
.map((route) => routeTarget(route));
|
|
6741
6815
|
const views = registry
|
|
6742
6816
|
.views()
|
|
6743
6817
|
.filter((view) => auth.meets(view.access))
|
|
6744
|
-
.map(viewTarget);
|
|
6818
|
+
.map((view) => viewTarget(view));
|
|
6745
6819
|
return [...routes, ...views];
|
|
6746
6820
|
}
|
|
6747
6821
|
function containerChildTargets(registry, auth, spec) {
|
|
@@ -6750,14 +6824,14 @@ function containerChildTargets(registry, auth, spec) {
|
|
|
6750
6824
|
.filter((child) => child.segment === undefined || isAddressable(child.segment))
|
|
6751
6825
|
.map((child) => views.find((view) => view.id === child.surface))
|
|
6752
6826
|
.filter((view) => view !== undefined && auth.meets(view.access))
|
|
6753
|
-
.map(viewTarget);
|
|
6827
|
+
.map((view) => viewTarget(view));
|
|
6754
6828
|
}
|
|
6755
6829
|
function routerPaneTargets(registry, auth) {
|
|
6756
6830
|
return registry
|
|
6757
6831
|
.contentRoutes()
|
|
6758
6832
|
.filter((route) => barePathHostableRoute(registry, route.path) !== null &&
|
|
6759
6833
|
auth.meets(route.access))
|
|
6760
|
-
.map(routeTarget);
|
|
6834
|
+
.map((route) => routeTarget(route));
|
|
6761
6835
|
}
|
|
6762
6836
|
function paneTargetEntries(targets, translate) {
|
|
6763
6837
|
return targets.map((target) => ({
|
|
@@ -7126,7 +7200,7 @@ class PaneTabStrip {
|
|
|
7126
7200
|
targetKind: 'view-tab',
|
|
7127
7201
|
viewId: tab.path.slice(VIEW_PANE_PREFIX.length),
|
|
7128
7202
|
region: this.contextGroup(),
|
|
7129
|
-
...(tab.instance
|
|
7203
|
+
...(tab.instance && { instance: tab.instance }),
|
|
7130
7204
|
};
|
|
7131
7205
|
}
|
|
7132
7206
|
return {
|
|
@@ -7180,22 +7254,22 @@ class PaneTabStrip {
|
|
|
7180
7254
|
this.reorderTabs.emit(tabs.filter((tab) => this.canReorder(tab)).map((tab) => tab.path));
|
|
7181
7255
|
}
|
|
7182
7256
|
observeResize() {
|
|
7183
|
-
const
|
|
7184
|
-
if (!
|
|
7257
|
+
const element = this.strip()?.nativeElement;
|
|
7258
|
+
if (!element || typeof ResizeObserver === 'undefined') {
|
|
7185
7259
|
return;
|
|
7186
7260
|
}
|
|
7187
7261
|
const observer = new ResizeObserver(() => this.overflow() && this.measureOverflow());
|
|
7188
|
-
observer.observe(
|
|
7262
|
+
observer.observe(element);
|
|
7189
7263
|
this.destroyRef.onDestroy(() => observer.disconnect());
|
|
7190
7264
|
}
|
|
7191
7265
|
measureOverflow() {
|
|
7192
|
-
const
|
|
7193
|
-
this.overflowing.set(!!
|
|
7266
|
+
const element = this.strip()?.nativeElement;
|
|
7267
|
+
this.overflowing.set(!!element && element.scrollWidth - element.clientWidth > EDGE_TOLERANCE_PX);
|
|
7194
7268
|
}
|
|
7195
7269
|
revealActiveTab() {
|
|
7196
7270
|
const strip = this.strip()?.nativeElement;
|
|
7197
7271
|
const active = this.activeId();
|
|
7198
|
-
const wrapper = strip?.querySelector(`[data-tab-path="${active}"]`)?.parentElement;
|
|
7272
|
+
const wrapper = strip?.querySelector(`[data-tab-path="${CSS.escape(active)}"]`)?.parentElement;
|
|
7199
7273
|
if (!strip || !wrapper) {
|
|
7200
7274
|
return;
|
|
7201
7275
|
}
|
|
@@ -7400,12 +7474,14 @@ class PluginStateService {
|
|
|
7400
7474
|
};
|
|
7401
7475
|
}
|
|
7402
7476
|
removePlugin(pluginId) {
|
|
7403
|
-
|
|
7404
|
-
|
|
7405
|
-
|
|
7406
|
-
|
|
7407
|
-
this.entries.delete(storageKey);
|
|
7477
|
+
const snapshot = [...this.entries];
|
|
7478
|
+
for (const [storageKey, entry] of snapshot) {
|
|
7479
|
+
if (entry.pluginId !== pluginId) {
|
|
7480
|
+
continue;
|
|
7408
7481
|
}
|
|
7482
|
+
this.cancelPending(entry);
|
|
7483
|
+
entry.value.set(undefined);
|
|
7484
|
+
this.entries.delete(storageKey);
|
|
7409
7485
|
}
|
|
7410
7486
|
this.keysByPlugin.delete(pluginId);
|
|
7411
7487
|
void readStoredValue(this.store, INDEX_PREFIX + pluginId).then((raw) => {
|
|
@@ -7495,10 +7571,11 @@ class PluginStateService {
|
|
|
7495
7571
|
entry.pending = undefined;
|
|
7496
7572
|
}
|
|
7497
7573
|
cancelTimer(entry) {
|
|
7498
|
-
if (entry.timer
|
|
7499
|
-
|
|
7500
|
-
entry.timer = undefined;
|
|
7574
|
+
if (entry.timer === undefined) {
|
|
7575
|
+
return;
|
|
7501
7576
|
}
|
|
7577
|
+
clearTimeout(entry.timer);
|
|
7578
|
+
entry.timer = undefined;
|
|
7502
7579
|
}
|
|
7503
7580
|
withinLimits(pluginId, key, serialised) {
|
|
7504
7581
|
const bytes = serialised.length;
|
|
@@ -7660,10 +7737,9 @@ function ownerByToken(registrations) {
|
|
|
7660
7737
|
...Object.keys(registration.dark ?? {}),
|
|
7661
7738
|
];
|
|
7662
7739
|
for (const name of names) {
|
|
7663
|
-
if (
|
|
7664
|
-
|
|
7740
|
+
if (known.has(name) && !owners.has(name)) {
|
|
7741
|
+
owners.set(name, registration);
|
|
7665
7742
|
}
|
|
7666
|
-
owners.set(name, registration);
|
|
7667
7743
|
}
|
|
7668
7744
|
}
|
|
7669
7745
|
return owners;
|
|
@@ -7702,7 +7778,7 @@ function createPluginLayerRules() {
|
|
|
7702
7778
|
}
|
|
7703
7779
|
const element = document.createElement('style');
|
|
7704
7780
|
element.dataset['lwPluginTheme'] = '';
|
|
7705
|
-
document.head.
|
|
7781
|
+
document.head.append(element);
|
|
7706
7782
|
const sheet = element.sheet;
|
|
7707
7783
|
if (!sheet) {
|
|
7708
7784
|
return null;
|
|
@@ -7854,6 +7930,7 @@ const LOOM_ICONS = {
|
|
|
7854
7930
|
workspaces: heroRectangleGroup,
|
|
7855
7931
|
menu: heroBars3,
|
|
7856
7932
|
document: heroDocument,
|
|
7933
|
+
openWork: heroViewColumns,
|
|
7857
7934
|
navigator: heroBars3,
|
|
7858
7935
|
outline: heroListBullet,
|
|
7859
7936
|
add: heroPlus,
|
|
@@ -8008,7 +8085,7 @@ class CapabilityGrantService {
|
|
|
8008
8085
|
})),
|
|
8009
8086
|
}))
|
|
8010
8087
|
.filter((entry) => entry.capabilities.length > 0)
|
|
8011
|
-
.
|
|
8088
|
+
.toSorted((a, b) => a.pluginId.localeCompare(b.pluginId));
|
|
8012
8089
|
}, /* @ts-ignore */
|
|
8013
8090
|
...(ngDevMode ? [{ debugName: "permissions" }] : /* istanbul ignore next */ []));
|
|
8014
8091
|
constructor() {
|
|
@@ -8145,7 +8222,7 @@ class IframeSurface {
|
|
|
8145
8222
|
...(ngDevMode ? [{ debugName: "activeTab" }] : /* istanbul ignore next */ []));
|
|
8146
8223
|
restPath = computed(() => {
|
|
8147
8224
|
if (!this.ownsRest) {
|
|
8148
|
-
return
|
|
8225
|
+
return;
|
|
8149
8226
|
}
|
|
8150
8227
|
return this.hostMounted
|
|
8151
8228
|
? this.hostSub()
|
|
@@ -8176,7 +8253,9 @@ class IframeSurface {
|
|
|
8176
8253
|
queueMicrotask(() => this.push({ ...snapshot, ...this.readResolved() }));
|
|
8177
8254
|
});
|
|
8178
8255
|
inject(DestroyRef).onDestroy(() => {
|
|
8179
|
-
|
|
8256
|
+
for (const entry of this.watched.values()) {
|
|
8257
|
+
entry.stop();
|
|
8258
|
+
}
|
|
8180
8259
|
this.watched.clear();
|
|
8181
8260
|
this.visibility?.disconnect();
|
|
8182
8261
|
this.connection?.destroy();
|
|
@@ -8212,12 +8291,12 @@ class IframeSurface {
|
|
|
8212
8291
|
this.tabs.keep(this.tabRoot);
|
|
8213
8292
|
}
|
|
8214
8293
|
},
|
|
8215
|
-
setDirty: (dirty) => this.dirty.set(dirty
|
|
8216
|
-
stateWatch: (key) => this.watchState(
|
|
8217
|
-
stateSet: (key, value) => this.watched.get(
|
|
8218
|
-
stateClear: (key) => this.watched.get(
|
|
8294
|
+
setDirty: (dirty) => this.dirty.set(dirty),
|
|
8295
|
+
stateWatch: (key) => this.watchState(key),
|
|
8296
|
+
stateSet: (key, value) => this.watched.get(key)?.handle.set(value),
|
|
8297
|
+
stateClear: (key) => this.watched.get(key)?.handle.clear(),
|
|
8219
8298
|
stateUnwatch: (key) => {
|
|
8220
|
-
const name =
|
|
8299
|
+
const name = key;
|
|
8221
8300
|
this.watched.get(name)?.stop();
|
|
8222
8301
|
this.watched.delete(name);
|
|
8223
8302
|
},
|
|
@@ -8227,7 +8306,9 @@ class IframeSurface {
|
|
|
8227
8306
|
.then((remote) => {
|
|
8228
8307
|
this.remote = remote;
|
|
8229
8308
|
this.push({ ...this.reactiveState(), ...this.readResolved() });
|
|
8230
|
-
|
|
8309
|
+
for (const [key, entry] of this.watched) {
|
|
8310
|
+
this.pushState(key, entry.handle.value(), entry.handle.loaded());
|
|
8311
|
+
}
|
|
8231
8312
|
})
|
|
8232
8313
|
.catch(() => undefined);
|
|
8233
8314
|
}
|
|
@@ -8263,7 +8344,7 @@ class IframeSurface {
|
|
|
8263
8344
|
return;
|
|
8264
8345
|
}
|
|
8265
8346
|
this.visibility = new IntersectionObserver((entries) => {
|
|
8266
|
-
const last = entries
|
|
8347
|
+
const last = entries.at(-1);
|
|
8267
8348
|
if (last) {
|
|
8268
8349
|
this.shown.set(last.isIntersecting);
|
|
8269
8350
|
}
|
|
@@ -8286,19 +8367,15 @@ class IframeSurface {
|
|
|
8286
8367
|
theme: this.theme.resolvedTheme(),
|
|
8287
8368
|
preview: this.isPreview(),
|
|
8288
8369
|
shown: this.shown(),
|
|
8289
|
-
...(this.instanceId
|
|
8290
|
-
...(Object.keys(this.routeParams).length > 0
|
|
8291
|
-
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8297
|
-
|
|
8298
|
-
roles: this.auth.roles(),
|
|
8299
|
-
},
|
|
8300
|
-
}
|
|
8301
|
-
: {}),
|
|
8370
|
+
...(this.instanceId && { instanceId: this.instanceId }),
|
|
8371
|
+
...(Object.keys(this.routeParams).length > 0 && { params: this.routeParams }),
|
|
8372
|
+
...(rest !== undefined && { rest }),
|
|
8373
|
+
...(this.sessionGranted() && {
|
|
8374
|
+
session: {
|
|
8375
|
+
authenticated: this.auth.authenticated(),
|
|
8376
|
+
roles: this.auth.roles(),
|
|
8377
|
+
},
|
|
8378
|
+
}),
|
|
8302
8379
|
};
|
|
8303
8380
|
}
|
|
8304
8381
|
readResolved() {
|
|
@@ -8311,19 +8388,19 @@ class IframeSurface {
|
|
|
8311
8388
|
return {
|
|
8312
8389
|
tokens,
|
|
8313
8390
|
rootFontSize: styles.fontSize,
|
|
8314
|
-
...(Object.keys(icons).length > 0
|
|
8391
|
+
...(Object.keys(icons).length > 0 && { icons }),
|
|
8315
8392
|
};
|
|
8316
8393
|
}
|
|
8317
8394
|
navigateWithinTabRoot(path) {
|
|
8318
8395
|
if (this.docked) {
|
|
8319
8396
|
if (isDevMode()) {
|
|
8320
|
-
console.warn(`[loom] a docked surface asked to navigate to "${
|
|
8397
|
+
console.warn(`[loom] a docked surface asked to navigate to "${path}" — ignored. ` +
|
|
8321
8398
|
`A docked surface has no address of its own; the channel's navigate is confined to a tab ` +
|
|
8322
8399
|
`root and there is none. Use ctx.navigateContent (the 'navigation' grant) instead.`);
|
|
8323
8400
|
}
|
|
8324
8401
|
return;
|
|
8325
8402
|
}
|
|
8326
|
-
const raw =
|
|
8403
|
+
const raw = path;
|
|
8327
8404
|
const suffix = suffixOf(raw);
|
|
8328
8405
|
const target = normalizePath(raw);
|
|
8329
8406
|
if (target !== this.tabRoot && !target.startsWith(this.tabRoot + '/')) {
|
|
@@ -8392,8 +8469,8 @@ function syntheticDockedRoute(view, instanceId, params = {}) {
|
|
|
8392
8469
|
url: [],
|
|
8393
8470
|
params,
|
|
8394
8471
|
data: {
|
|
8395
|
-
...(view.iframe !== undefined
|
|
8396
|
-
...(view.pluginId
|
|
8472
|
+
...(view.iframe !== undefined && { iframe: view.iframe }),
|
|
8473
|
+
...(view.pluginId && { pluginId: view.pluginId }),
|
|
8397
8474
|
docked: true,
|
|
8398
8475
|
instanceId,
|
|
8399
8476
|
},
|
|
@@ -8409,13 +8486,13 @@ function syntheticRouteFor(route, path, options = {}) {
|
|
|
8409
8486
|
url: segments.map((segment) => new UrlSegment(segment, {})),
|
|
8410
8487
|
params,
|
|
8411
8488
|
data: {
|
|
8412
|
-
...('iframe' in route
|
|
8413
|
-
...('container' in route
|
|
8414
|
-
...(route.pluginId
|
|
8415
|
-
...(route.rest === true
|
|
8416
|
-
...(sub
|
|
8417
|
-
...(options.urlDriven
|
|
8418
|
-
...(options.instanceId
|
|
8489
|
+
...('iframe' in route && { iframe: route.iframe }),
|
|
8490
|
+
...('container' in route && { container: route.container }),
|
|
8491
|
+
...(route.pluginId && { pluginId: route.pluginId }),
|
|
8492
|
+
...(route.rest === true && { rest: true }),
|
|
8493
|
+
...(sub && { sub }),
|
|
8494
|
+
...(options.urlDriven && { urlDriven: true }),
|
|
8495
|
+
...(options.instanceId && { instanceId: options.instanceId }),
|
|
8419
8496
|
},
|
|
8420
8497
|
});
|
|
8421
8498
|
}
|
|
@@ -8651,14 +8728,12 @@ class ContentSecondaryPane {
|
|
|
8651
8728
|
mountParams = computed(() => {
|
|
8652
8729
|
const ctx = this.containerCtx;
|
|
8653
8730
|
if (!ctx) {
|
|
8654
|
-
return
|
|
8731
|
+
return;
|
|
8655
8732
|
}
|
|
8656
8733
|
const match = containerChildForPath(this.registry.contentRoutes(), this.registry.views(), this.path());
|
|
8657
8734
|
return {
|
|
8658
8735
|
...ctx.params,
|
|
8659
|
-
...(match
|
|
8660
|
-
? paramsOfPattern(match.declaration.segment ?? '', match.segmentPath)
|
|
8661
|
-
: {}),
|
|
8736
|
+
...(match && paramsOfPattern(match.declaration.segment ?? '', match.segmentPath)),
|
|
8662
8737
|
};
|
|
8663
8738
|
}, /* @ts-ignore */
|
|
8664
8739
|
...(ngDevMode ? [{ debugName: "mountParams" }] : /* istanbul ignore next */ []));
|
|
@@ -8690,12 +8765,12 @@ class ContentSecondaryPane {
|
|
|
8690
8765
|
...(ngDevMode ? [{ debugName: "activeRoute" }] : /* istanbul ignore next */ []));
|
|
8691
8766
|
iframeSurface = computed(() => {
|
|
8692
8767
|
const route = this.activeRoute();
|
|
8693
|
-
return route?.iframe
|
|
8694
|
-
?
|
|
8768
|
+
return route?.iframe === undefined
|
|
8769
|
+
? null
|
|
8770
|
+
: {
|
|
8695
8771
|
component: IframeSurface,
|
|
8696
8772
|
injector: this.injectorFor(route, this.path(), this.surfaceKey()),
|
|
8697
|
-
}
|
|
8698
|
-
: null;
|
|
8773
|
+
};
|
|
8699
8774
|
}, /* @ts-ignore */
|
|
8700
8775
|
...(ngDevMode ? [{ debugName: "iframeSurface" }] : /* istanbul ignore next */ []));
|
|
8701
8776
|
surface = computed(() => {
|
|
@@ -9147,11 +9222,11 @@ class PaneView {
|
|
|
9147
9222
|
];
|
|
9148
9223
|
}
|
|
9149
9224
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: PaneView, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9150
|
-
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 <
|
|
9225
|
+
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" }] });
|
|
9151
9226
|
}
|
|
9152
9227
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: PaneView, decorators: [{
|
|
9153
9228
|
type: Component,
|
|
9154
|
-
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 <
|
|
9229
|
+
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" }]
|
|
9155
9230
|
}], 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 }] }] } });
|
|
9156
9231
|
|
|
9157
9232
|
class PaneDropZones {
|
|
@@ -9189,13 +9264,12 @@ class PaneDropZones {
|
|
|
9189
9264
|
...(ngDevMode ? [{ debugName: "accepts" }] : /* istanbul ignore next */ []));
|
|
9190
9265
|
constructor() {
|
|
9191
9266
|
effect((onCleanup) => {
|
|
9192
|
-
const
|
|
9193
|
-
|
|
9194
|
-
|
|
9195
|
-
|
|
9196
|
-
|
|
9197
|
-
|
|
9198
|
-
onCleanup(() => disposers.forEach((dispose) => dispose()));
|
|
9267
|
+
const disposers = this.zoneIds().map((id) => this.drag.registerZone(id));
|
|
9268
|
+
onCleanup(() => {
|
|
9269
|
+
for (const dispose of disposers) {
|
|
9270
|
+
dispose();
|
|
9271
|
+
}
|
|
9272
|
+
});
|
|
9199
9273
|
});
|
|
9200
9274
|
}
|
|
9201
9275
|
has(edge) {
|
|
@@ -9231,8 +9305,14 @@ class PaneDropZones {
|
|
|
9231
9305
|
this.paneMove.moveToEdge(source, String(event.item.data ?? ''), { dock: this.dock(), paneId: this.paneId() }, edge);
|
|
9232
9306
|
}
|
|
9233
9307
|
}
|
|
9308
|
+
zoneIds() {
|
|
9309
|
+
if (!this.fills()) {
|
|
9310
|
+
return this.edges().map((edge) => this.zoneId(edge));
|
|
9311
|
+
}
|
|
9312
|
+
return this.accepts() ? [this.fillZoneId()] : [];
|
|
9313
|
+
}
|
|
9234
9314
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: PaneDropZones, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9235
|
-
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
|
|
9315
|
+
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"] }] });
|
|
9236
9316
|
}
|
|
9237
9317
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: PaneDropZones, decorators: [{
|
|
9238
9318
|
type: Component,
|
|
@@ -9243,7 +9323,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
9243
9323
|
'[style.grid-template-columns]': '"1fr 2fr 1fr"',
|
|
9244
9324
|
'[style.grid-template-rows]': '"1fr 2fr 1fr"',
|
|
9245
9325
|
'[attr.aria-hidden]': 'true',
|
|
9246
|
-
}, template: "@if (fills()) {\n @if (accepts()) {\n <div\n class=\"lw-pane-drop-zone col-span-3
|
|
9326
|
+
}, 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" }]
|
|
9247
9327
|
}], ctorParameters: () => [], propDecorators: { dock: [{ type: i0.Input, args: [{ isSignal: true, alias: "dock", required: true }] }], paneId: [{ type: i0.Input, args: [{ isSignal: true, alias: "paneId", required: true }] }] } });
|
|
9248
9328
|
|
|
9249
9329
|
class PaneMinimizedStrip {
|
|
@@ -9316,18 +9396,18 @@ class PaneSplitHandle {
|
|
|
9316
9396
|
return;
|
|
9317
9397
|
}
|
|
9318
9398
|
const rect = parent.getBoundingClientRect();
|
|
9319
|
-
const
|
|
9320
|
-
|
|
9399
|
+
const element = this.host.nativeElement;
|
|
9400
|
+
element.setPointerCapture(event.pointerId);
|
|
9321
9401
|
event.preventDefault();
|
|
9322
9402
|
const move = (e) => this.ratioStream.emit(this.fraction(e, rect));
|
|
9323
9403
|
const up = (e) => {
|
|
9324
|
-
|
|
9325
|
-
|
|
9326
|
-
|
|
9404
|
+
element.releasePointerCapture(e.pointerId);
|
|
9405
|
+
element.removeEventListener('pointermove', move);
|
|
9406
|
+
element.removeEventListener('pointerup', up);
|
|
9327
9407
|
this.ratioCommit.emit();
|
|
9328
9408
|
};
|
|
9329
|
-
|
|
9330
|
-
|
|
9409
|
+
element.addEventListener('pointermove', move);
|
|
9410
|
+
element.addEventListener('pointerup', up);
|
|
9331
9411
|
}
|
|
9332
9412
|
onKeydown(event) {
|
|
9333
9413
|
const step = event.shiftKey ? STEP_COARSE$1 : STEP$1;
|
|
@@ -9634,11 +9714,12 @@ function parseWidths(raw) {
|
|
|
9634
9714
|
}
|
|
9635
9715
|
const result = {};
|
|
9636
9716
|
for (const [key, value] of Object.entries(parsed)) {
|
|
9637
|
-
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
9638
|
-
|
|
9639
|
-
|
|
9640
|
-
|
|
9641
|
-
|
|
9717
|
+
if (!(typeof value === 'number' && Number.isFinite(value))) {
|
|
9718
|
+
continue;
|
|
9719
|
+
}
|
|
9720
|
+
const clamped = clampWidth(value);
|
|
9721
|
+
if (clamped !== DEFAULT_PANEL_WIDTH) {
|
|
9722
|
+
result[key] = clamped;
|
|
9642
9723
|
}
|
|
9643
9724
|
}
|
|
9644
9725
|
return result;
|
|
@@ -9741,20 +9822,25 @@ class PanelSplitter {
|
|
|
9741
9822
|
const step = (event.shiftKey ? STEP_COARSE : STEP) * this.edgeSign();
|
|
9742
9823
|
let next;
|
|
9743
9824
|
switch (event.key) {
|
|
9744
|
-
case 'ArrowRight':
|
|
9825
|
+
case 'ArrowRight': {
|
|
9745
9826
|
next = this.width() + step;
|
|
9746
9827
|
break;
|
|
9747
|
-
|
|
9828
|
+
}
|
|
9829
|
+
case 'ArrowLeft': {
|
|
9748
9830
|
next = this.width() - step;
|
|
9749
9831
|
break;
|
|
9750
|
-
|
|
9832
|
+
}
|
|
9833
|
+
case 'Home': {
|
|
9751
9834
|
next = this.size.minWidth;
|
|
9752
9835
|
break;
|
|
9753
|
-
|
|
9836
|
+
}
|
|
9837
|
+
case 'End': {
|
|
9754
9838
|
next = this.size.maxWidth;
|
|
9755
9839
|
break;
|
|
9756
|
-
|
|
9840
|
+
}
|
|
9841
|
+
default: {
|
|
9757
9842
|
return;
|
|
9843
|
+
}
|
|
9758
9844
|
}
|
|
9759
9845
|
event.preventDefault();
|
|
9760
9846
|
this.size.setWidth(this.regionId(), next);
|
|
@@ -9811,7 +9897,7 @@ class ShellPanel {
|
|
|
9811
9897
|
activeView = computed(() => {
|
|
9812
9898
|
const path = this.activePath();
|
|
9813
9899
|
if (!path?.startsWith(VIEW_PANE_PREFIX)) {
|
|
9814
|
-
return
|
|
9900
|
+
return;
|
|
9815
9901
|
}
|
|
9816
9902
|
return viewForPanePath(this.registry.views(), path);
|
|
9817
9903
|
}, /* @ts-ignore */
|
|
@@ -9825,7 +9911,7 @@ class ShellPanel {
|
|
|
9825
9911
|
...(ngDevMode ? [{ debugName: "activeContentPath" }] : /* istanbul ignore next */ []));
|
|
9826
9912
|
actions = computed(() => [...(this.activeView()?.actions ?? [])]
|
|
9827
9913
|
.filter((action) => this.auth.visible(action.access))
|
|
9828
|
-
.
|
|
9914
|
+
.toSorted((a, b) => (a.order ?? 0) - (b.order ?? 0)), /* @ts-ignore */
|
|
9829
9915
|
...(ngDevMode ? [{ debugName: "actions" }] : /* istanbul ignore next */ []));
|
|
9830
9916
|
panelPaneOptions = PANEL_PANE_OPTIONS;
|
|
9831
9917
|
primaryScope = computed(() => paneRetentionScope(this.region().id, this.paneTree.primaryId(this.region().id)), /* @ts-ignore */
|
|
@@ -10119,10 +10205,14 @@ function registerTabContextMenu(registry, tabs, paneMove, popout, shell) {
|
|
|
10119
10205
|
},
|
|
10120
10206
|
];
|
|
10121
10207
|
const registered = new Set(commands.map((command) => command.id));
|
|
10122
|
-
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
|
|
10208
|
+
for (const command of commands) {
|
|
10209
|
+
registry.addCommand({ ...command, paletteHidden: true });
|
|
10210
|
+
}
|
|
10211
|
+
for (const item of items) {
|
|
10212
|
+
if (item.command !== undefined && registered.has(item.command)) {
|
|
10213
|
+
registry.addMenuItem(item);
|
|
10214
|
+
}
|
|
10215
|
+
}
|
|
10126
10216
|
}
|
|
10127
10217
|
|
|
10128
10218
|
class ContentArea {
|
|
@@ -10328,7 +10418,7 @@ class ContentGrid {
|
|
|
10328
10418
|
}, /* @ts-ignore */
|
|
10329
10419
|
...(ngDevMode ? [{ debugName: "tree" }] : /* istanbul ignore next */ []));
|
|
10330
10420
|
constructor() {
|
|
10331
|
-
const
|
|
10421
|
+
const document_ = inject(DOCUMENT);
|
|
10332
10422
|
const onKeydown = (event) => {
|
|
10333
10423
|
if (event.key === 'Escape') {
|
|
10334
10424
|
this.chrome.restore();
|
|
@@ -10338,8 +10428,8 @@ class ContentGrid {
|
|
|
10338
10428
|
if (!this.maximized()) {
|
|
10339
10429
|
return;
|
|
10340
10430
|
}
|
|
10341
|
-
|
|
10342
|
-
onCleanup(() =>
|
|
10431
|
+
document_.addEventListener('keydown', onKeydown);
|
|
10432
|
+
onCleanup(() => document_.removeEventListener('keydown', onKeydown));
|
|
10343
10433
|
});
|
|
10344
10434
|
effect(() => {
|
|
10345
10435
|
if (!this.layout.isSplit(CONTENT_DOCK)) {
|
|
@@ -10512,8 +10602,8 @@ class DialogOutlet {
|
|
|
10512
10602
|
if (!this.dialogs().length || !panels.length) {
|
|
10513
10603
|
return;
|
|
10514
10604
|
}
|
|
10515
|
-
const top = panels
|
|
10516
|
-
if (top.contains(this.document.activeElement)) {
|
|
10605
|
+
const top = panels.at(-1)?.nativeElement;
|
|
10606
|
+
if (!top || top.contains(this.document.activeElement)) {
|
|
10517
10607
|
return;
|
|
10518
10608
|
}
|
|
10519
10609
|
(top.querySelector('[data-lw-autofocus]') ?? top).focus();
|
|
@@ -10542,10 +10632,10 @@ class DialogOutlet {
|
|
|
10542
10632
|
return;
|
|
10543
10633
|
}
|
|
10544
10634
|
const first = focusables[0];
|
|
10545
|
-
const last = focusables
|
|
10635
|
+
const last = focusables.at(-1);
|
|
10546
10636
|
const active = this.document.activeElement;
|
|
10547
10637
|
if (backward && active === first) {
|
|
10548
|
-
last
|
|
10638
|
+
last?.focus();
|
|
10549
10639
|
event.preventDefault();
|
|
10550
10640
|
}
|
|
10551
10641
|
else if (!backward && active === last) {
|
|
@@ -10617,7 +10707,7 @@ class DialogOutlet {
|
|
|
10617
10707
|
}
|
|
10618
10708
|
topPanel() {
|
|
10619
10709
|
const panels = this.panels();
|
|
10620
|
-
return panels.
|
|
10710
|
+
return panels.at(-1)?.nativeElement;
|
|
10621
10711
|
}
|
|
10622
10712
|
focusable(root) {
|
|
10623
10713
|
const selector = 'button:not(:disabled), a[href], input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex="-1"])';
|
|
@@ -10625,7 +10715,7 @@ class DialogOutlet {
|
|
|
10625
10715
|
}
|
|
10626
10716
|
top() {
|
|
10627
10717
|
const list = this.dialogs();
|
|
10628
|
-
return list
|
|
10718
|
+
return list.at(-1);
|
|
10629
10719
|
}
|
|
10630
10720
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DialogOutlet, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
10631
10721
|
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" }] });
|
|
@@ -10775,7 +10865,7 @@ class SettingsRegistry {
|
|
|
10775
10865
|
return this.sections()
|
|
10776
10866
|
.map((section) => visibleSection(section, omitted))
|
|
10777
10867
|
.filter((section) => section !== null)
|
|
10778
|
-
.
|
|
10868
|
+
.toSorted((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
10779
10869
|
}, /* @ts-ignore */
|
|
10780
10870
|
...(ngDevMode ? [{ debugName: "all" }] : /* istanbul ignore next */ []));
|
|
10781
10871
|
register(section) {
|
|
@@ -11015,6 +11105,13 @@ function provideTranslationNamespaces(...namespaces) {
|
|
|
11015
11105
|
/** Directory the distribution serves its overlay bundles from, without a trailing slash. */
|
|
11016
11106
|
const TRANSLATION_OVERRIDES = new InjectionToken('TRANSLATION_OVERRIDES');
|
|
11017
11107
|
const DEFAULT_OVERRIDES_PATH = '/i18n/overrides';
|
|
11108
|
+
function withoutTrailingSlashes(path) {
|
|
11109
|
+
let end = path.length;
|
|
11110
|
+
while (end > 0 && path[end - 1] === '/') {
|
|
11111
|
+
end -= 1;
|
|
11112
|
+
}
|
|
11113
|
+
return path.slice(0, end);
|
|
11114
|
+
}
|
|
11018
11115
|
/**
|
|
11019
11116
|
* Load `<basePath>/<lang>.json` and merge it over everything else **key by key**, so a product
|
|
11020
11117
|
* can reword the shell in its own house language ("Save as" rather than "Save as new") without
|
|
@@ -11034,7 +11131,7 @@ const DEFAULT_OVERRIDES_PATH = '/i18n/overrides';
|
|
|
11034
11131
|
* nothing ships is dev-warned too, since a typo there would otherwise be a string that never appears.
|
|
11035
11132
|
*/
|
|
11036
11133
|
function provideTranslationOverrides(basePath = DEFAULT_OVERRIDES_PATH) {
|
|
11037
|
-
const normalized = basePath
|
|
11134
|
+
const normalized = withoutTrailingSlashes(basePath);
|
|
11038
11135
|
if (normalized === '') {
|
|
11039
11136
|
throw new Error('provideTranslationOverrides() needs a directory to load overlays from; ' +
|
|
11040
11137
|
`pass one or omit the argument for "${DEFAULT_OVERRIDES_PATH}".`);
|
|
@@ -11086,7 +11183,9 @@ class TranslocoHttpLoader {
|
|
|
11086
11183
|
return forkJoin([host$, ...namespaced$, this.overrides$(lang)]).pipe(map(([host, ...rest]) => {
|
|
11087
11184
|
const overlay = rest.pop();
|
|
11088
11185
|
const merged = { ...host };
|
|
11089
|
-
|
|
11186
|
+
for (const [index, name] of this.namespaces.entries()) {
|
|
11187
|
+
merged[name] = rest[index];
|
|
11188
|
+
}
|
|
11090
11189
|
return this.applyOverrides(merged, overlay, lang);
|
|
11091
11190
|
}));
|
|
11092
11191
|
}
|
|
@@ -11157,9 +11256,7 @@ class LwIconElement extends HTMLElement {
|
|
|
11157
11256
|
this.render();
|
|
11158
11257
|
}
|
|
11159
11258
|
attributeChangedCallback() {
|
|
11160
|
-
|
|
11161
|
-
this.render();
|
|
11162
|
-
}
|
|
11259
|
+
this.refresh();
|
|
11163
11260
|
}
|
|
11164
11261
|
/**
|
|
11165
11262
|
* Re-draws from the registry without changing the name. A sandboxed surface receives the product's
|
|
@@ -11266,7 +11363,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
11266
11363
|
// GENERATED — do not edit by hand.
|
|
11267
11364
|
// Written by tools/stamp-version.mjs from <Version> in Directory.Build.props.
|
|
11268
11365
|
// Single source of truth: Directory.Build.props (bump via scripts/bump-version.sh).
|
|
11269
|
-
const APP_VERSION = '0.7.
|
|
11366
|
+
const APP_VERSION = '0.7.6';
|
|
11270
11367
|
|
|
11271
11368
|
/**
|
|
11272
11369
|
* The running build's version, sourced from `<Version>` in Directory.Build.props
|
|
@@ -11482,7 +11579,7 @@ class UpdateService {
|
|
|
11482
11579
|
await bestEffort(async () => {
|
|
11483
11580
|
const registrations = (await container?.getRegistrations()) ?? [];
|
|
11484
11581
|
await Promise.all(registrations
|
|
11485
|
-
.filter(isShellWorker)
|
|
11582
|
+
.filter((registration) => isShellWorker(registration))
|
|
11486
11583
|
.map((registration) => registration.unregister()));
|
|
11487
11584
|
});
|
|
11488
11585
|
await bestEffort(async () => {
|
|
@@ -11490,14 +11587,14 @@ class UpdateService {
|
|
|
11490
11587
|
const keys = (await storage?.keys()) ?? [];
|
|
11491
11588
|
await Promise.all(keys
|
|
11492
11589
|
.filter((key) => key.startsWith(WORKER_CACHE_PREFIX))
|
|
11493
|
-
.map((key) => storage?.delete(key)));
|
|
11590
|
+
.map(async (key) => storage?.delete(key)));
|
|
11494
11591
|
});
|
|
11495
11592
|
}
|
|
11496
11593
|
onVersionEvent(event) {
|
|
11497
11594
|
if (event.type === 'VERSION_READY') {
|
|
11498
11595
|
this.onUpdateReady();
|
|
11499
11596
|
}
|
|
11500
|
-
if (event.type === 'VERSION_INSTALLATION_FAILED') {
|
|
11597
|
+
else if (event.type === 'VERSION_INSTALLATION_FAILED') {
|
|
11501
11598
|
this.onUpdateFailed();
|
|
11502
11599
|
}
|
|
11503
11600
|
}
|
|
@@ -11861,7 +11958,7 @@ class CommandInvocationService {
|
|
|
11861
11958
|
.filter((entry) => entry.command.callable === true &&
|
|
11862
11959
|
this.reachable(entry, callerId, granted))
|
|
11863
11960
|
.map((entry) => this.describe(entry.command))
|
|
11864
|
-
.
|
|
11961
|
+
.toSorted((a, b) => a.id.localeCompare(b.id));
|
|
11865
11962
|
}
|
|
11866
11963
|
async invoke(callerId, granted, id, args) {
|
|
11867
11964
|
const entry = this.registry
|
|
@@ -12181,17 +12278,21 @@ class LwTooltipElement extends HTMLElement {
|
|
|
12181
12278
|
const centerX = t.left + t.width / 2 - b.width / 2;
|
|
12182
12279
|
const centerY = t.top + t.height / 2 - b.height / 2;
|
|
12183
12280
|
switch (this.position) {
|
|
12184
|
-
case 'bottom':
|
|
12281
|
+
case 'bottom': {
|
|
12185
12282
|
[left, top] = [centerX, t.bottom + TOOLTIP_GAP];
|
|
12186
12283
|
break;
|
|
12187
|
-
|
|
12284
|
+
}
|
|
12285
|
+
case 'left': {
|
|
12188
12286
|
[left, top] = [t.left - b.width - TOOLTIP_GAP, centerY];
|
|
12189
12287
|
break;
|
|
12190
|
-
|
|
12288
|
+
}
|
|
12289
|
+
case 'right': {
|
|
12191
12290
|
[left, top] = [t.right + TOOLTIP_GAP, centerY];
|
|
12192
12291
|
break;
|
|
12193
|
-
|
|
12292
|
+
}
|
|
12293
|
+
default: {
|
|
12194
12294
|
[left, top] = [centerX, t.top - b.height - TOOLTIP_GAP];
|
|
12295
|
+
}
|
|
12195
12296
|
}
|
|
12196
12297
|
}
|
|
12197
12298
|
else {
|
|
@@ -12216,10 +12317,11 @@ class LwTooltipElement extends HTMLElement {
|
|
|
12216
12317
|
}
|
|
12217
12318
|
}
|
|
12218
12319
|
clearTimer() {
|
|
12219
|
-
if (this.showTimer
|
|
12220
|
-
|
|
12221
|
-
this.showTimer = undefined;
|
|
12320
|
+
if (this.showTimer === undefined) {
|
|
12321
|
+
return;
|
|
12222
12322
|
}
|
|
12323
|
+
clearTimeout(this.showTimer);
|
|
12324
|
+
this.showTimer = undefined;
|
|
12223
12325
|
}
|
|
12224
12326
|
}
|
|
12225
12327
|
/** Registers `<lw-tooltip>` once (idempotent) — called from {@link provideShell} at bootstrap. */
|
|
@@ -12230,11 +12332,7 @@ function defineLwTooltip() {
|
|
|
12230
12332
|
}
|
|
12231
12333
|
}
|
|
12232
12334
|
|
|
12233
|
-
const LW_SELECT_TAG = 'lw-select';
|
|
12234
12335
|
const LW_OPTION_TAG = 'lw-option';
|
|
12235
|
-
const LW_SELECT_CHANGE = 'lw-select-change';
|
|
12236
|
-
let nextSelectId = 0;
|
|
12237
|
-
const TYPEAHEAD_RESET_MS = 500;
|
|
12238
12336
|
class LwOptionElement extends HTMLElement {
|
|
12239
12337
|
get value() {
|
|
12240
12338
|
return this.getAttribute('value');
|
|
@@ -12253,6 +12351,87 @@ class LwOptionElement extends HTMLElement {
|
|
|
12253
12351
|
upgradeElementProperty(this, 'icon');
|
|
12254
12352
|
}
|
|
12255
12353
|
}
|
|
12354
|
+
|
|
12355
|
+
function readChoices(host) {
|
|
12356
|
+
return [...host.querySelectorAll(LW_OPTION_TAG)].map((option) => ({
|
|
12357
|
+
value: option.getAttribute('value') ?? '',
|
|
12358
|
+
label: (option.textContent ?? '').trim(),
|
|
12359
|
+
icon: option.getAttribute('icon'),
|
|
12360
|
+
disabled: option.hasAttribute('disabled'),
|
|
12361
|
+
}));
|
|
12362
|
+
}
|
|
12363
|
+
function createTrigger(options) {
|
|
12364
|
+
const trigger = document.createElement('button');
|
|
12365
|
+
trigger.type = 'button';
|
|
12366
|
+
trigger.className = 'lw-select-trigger';
|
|
12367
|
+
trigger.setAttribute('aria-haspopup', 'listbox');
|
|
12368
|
+
trigger.setAttribute('aria-expanded', 'false');
|
|
12369
|
+
trigger.setAttribute('aria-controls', options.listboxId);
|
|
12370
|
+
trigger.style.setProperty('anchor-name', options.anchorName);
|
|
12371
|
+
trigger.addEventListener('click', options.onToggle);
|
|
12372
|
+
trigger.addEventListener('keydown', options.onKeydown);
|
|
12373
|
+
const valueSlot = document.createElement('span');
|
|
12374
|
+
valueSlot.className = 'lw-select-value';
|
|
12375
|
+
const chevron = document.createElement('span');
|
|
12376
|
+
chevron.className = 'lw-select-chevron';
|
|
12377
|
+
chevron.setAttribute('aria-hidden', 'true');
|
|
12378
|
+
chevron.textContent = '▾';
|
|
12379
|
+
trigger.append(valueSlot, chevron);
|
|
12380
|
+
return { trigger, valueSlot };
|
|
12381
|
+
}
|
|
12382
|
+
function createListbox(options) {
|
|
12383
|
+
const listbox = document.createElement('div');
|
|
12384
|
+
listbox.id = options.listboxId;
|
|
12385
|
+
listbox.className = 'lw-select-listbox';
|
|
12386
|
+
listbox.setAttribute('role', 'listbox');
|
|
12387
|
+
listbox.hidden = true;
|
|
12388
|
+
listbox.style.setProperty('position-anchor', options.anchorName);
|
|
12389
|
+
listbox.addEventListener('keydown', options.onKeydown);
|
|
12390
|
+
return listbox;
|
|
12391
|
+
}
|
|
12392
|
+
function fillValueSlot(slot, text, icon) {
|
|
12393
|
+
slot.textContent = '';
|
|
12394
|
+
if (icon) {
|
|
12395
|
+
slot.append(createGlyph(icon));
|
|
12396
|
+
}
|
|
12397
|
+
slot.append(document.createTextNode(text));
|
|
12398
|
+
}
|
|
12399
|
+
function createOptionRow(options) {
|
|
12400
|
+
const choice = options.choice;
|
|
12401
|
+
const row = document.createElement('div');
|
|
12402
|
+
row.className = 'lw-select-option';
|
|
12403
|
+
row.setAttribute('role', 'option');
|
|
12404
|
+
row.id = options.id;
|
|
12405
|
+
row.dataset['value'] = choice.value;
|
|
12406
|
+
row.setAttribute('aria-selected', String(options.selected));
|
|
12407
|
+
row.tabIndex = -1;
|
|
12408
|
+
if (choice.disabled) {
|
|
12409
|
+
row.setAttribute('aria-disabled', 'true');
|
|
12410
|
+
}
|
|
12411
|
+
if (choice.icon) {
|
|
12412
|
+
row.append(createGlyph(choice.icon));
|
|
12413
|
+
}
|
|
12414
|
+
row.append(document.createTextNode(choice.label));
|
|
12415
|
+
row.addEventListener('click', () => {
|
|
12416
|
+
if (!choice.disabled) {
|
|
12417
|
+
options.onPick();
|
|
12418
|
+
}
|
|
12419
|
+
});
|
|
12420
|
+
row.addEventListener('pointermove', options.onHover);
|
|
12421
|
+
return row;
|
|
12422
|
+
}
|
|
12423
|
+
function createGlyph(icon) {
|
|
12424
|
+
const glyph = document.createElement('span');
|
|
12425
|
+
glyph.className = 'lw-select-glyph';
|
|
12426
|
+
glyph.setAttribute('aria-hidden', 'true');
|
|
12427
|
+
glyph.textContent = icon;
|
|
12428
|
+
return glyph;
|
|
12429
|
+
}
|
|
12430
|
+
|
|
12431
|
+
const LW_SELECT_TAG = 'lw-select';
|
|
12432
|
+
const LW_SELECT_CHANGE = 'lw-select-change';
|
|
12433
|
+
let nextSelectId = 0;
|
|
12434
|
+
const TYPEAHEAD_RESET_MS = 500;
|
|
12256
12435
|
class LwSelectElement extends HTMLElement {
|
|
12257
12436
|
static observedAttributes = [
|
|
12258
12437
|
'value',
|
|
@@ -12297,10 +12476,7 @@ class LwSelectElement extends HTMLElement {
|
|
|
12297
12476
|
if (!this.trigger) {
|
|
12298
12477
|
return;
|
|
12299
12478
|
}
|
|
12300
|
-
if (name
|
|
12301
|
-
name === 'label' ||
|
|
12302
|
-
name === 'placeholder' ||
|
|
12303
|
-
name === 'disabled') {
|
|
12479
|
+
if (LwSelectElement.observedAttributes.includes(name)) {
|
|
12304
12480
|
this.syncTrigger();
|
|
12305
12481
|
}
|
|
12306
12482
|
if (name === 'disabled' && this.hasAttribute('disabled')) {
|
|
@@ -12320,51 +12496,34 @@ class LwSelectElement extends HTMLElement {
|
|
|
12320
12496
|
attributes: true,
|
|
12321
12497
|
});
|
|
12322
12498
|
}
|
|
12323
|
-
write(
|
|
12499
|
+
write(function_) {
|
|
12324
12500
|
this.observer?.disconnect();
|
|
12325
12501
|
try {
|
|
12326
|
-
|
|
12502
|
+
function_();
|
|
12327
12503
|
}
|
|
12328
12504
|
finally {
|
|
12329
12505
|
this.observe();
|
|
12330
12506
|
}
|
|
12331
12507
|
}
|
|
12332
12508
|
choices() {
|
|
12333
|
-
return
|
|
12334
|
-
value: option.getAttribute('value') ?? '',
|
|
12335
|
-
label: (option.textContent ?? '').trim(),
|
|
12336
|
-
icon: option.getAttribute('icon'),
|
|
12337
|
-
disabled: option.hasAttribute('disabled'),
|
|
12338
|
-
}));
|
|
12509
|
+
return readChoices(this);
|
|
12339
12510
|
}
|
|
12340
12511
|
selectedChoice() {
|
|
12341
12512
|
const value = this.value;
|
|
12342
12513
|
return this.choices().find((choice) => choice.value === value);
|
|
12343
12514
|
}
|
|
12344
12515
|
buildControl() {
|
|
12345
|
-
const trigger =
|
|
12346
|
-
|
|
12347
|
-
|
|
12348
|
-
|
|
12349
|
-
|
|
12350
|
-
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
|
|
12354
|
-
|
|
12355
|
-
|
|
12356
|
-
const chevron = document.createElement('span');
|
|
12357
|
-
chevron.className = 'lw-select-chevron';
|
|
12358
|
-
chevron.setAttribute('aria-hidden', 'true');
|
|
12359
|
-
chevron.textContent = '▾';
|
|
12360
|
-
trigger.append(valueSlot, chevron);
|
|
12361
|
-
const listbox = document.createElement('div');
|
|
12362
|
-
listbox.id = this.listboxId;
|
|
12363
|
-
listbox.className = 'lw-select-listbox';
|
|
12364
|
-
listbox.setAttribute('role', 'listbox');
|
|
12365
|
-
listbox.hidden = true;
|
|
12366
|
-
listbox.style.setProperty('position-anchor', this.anchorName);
|
|
12367
|
-
listbox.addEventListener('keydown', (event) => this.onListboxKeydown(event));
|
|
12516
|
+
const { trigger, valueSlot } = createTrigger({
|
|
12517
|
+
anchorName: this.anchorName,
|
|
12518
|
+
listboxId: this.listboxId,
|
|
12519
|
+
onToggle: () => this.toggle(),
|
|
12520
|
+
onKeydown: (event) => this.onTriggerKeydown(event),
|
|
12521
|
+
});
|
|
12522
|
+
const listbox = createListbox({
|
|
12523
|
+
anchorName: this.anchorName,
|
|
12524
|
+
listboxId: this.listboxId,
|
|
12525
|
+
onKeydown: (event) => this.onListboxKeydown(event),
|
|
12526
|
+
});
|
|
12368
12527
|
this.append(trigger, listbox);
|
|
12369
12528
|
this.trigger = trigger;
|
|
12370
12529
|
this.valueSlot = valueSlot;
|
|
@@ -12379,22 +12538,13 @@ class LwSelectElement extends HTMLElement {
|
|
|
12379
12538
|
const label = this.getAttribute('label');
|
|
12380
12539
|
const selected = this.selectedChoice();
|
|
12381
12540
|
const text = selected?.label ?? this.getAttribute('placeholder') ?? '';
|
|
12382
|
-
const icon = selected?.icon ?? null;
|
|
12383
12541
|
this.write(() => {
|
|
12384
12542
|
trigger.disabled = this.hasAttribute('disabled');
|
|
12385
12543
|
if (label !== null) {
|
|
12386
12544
|
trigger.setAttribute('aria-label', label);
|
|
12387
12545
|
this.listbox?.setAttribute('aria-label', label);
|
|
12388
12546
|
}
|
|
12389
|
-
valueSlot
|
|
12390
|
-
if (icon) {
|
|
12391
|
-
const glyph = document.createElement('span');
|
|
12392
|
-
glyph.className = 'lw-select-glyph';
|
|
12393
|
-
glyph.setAttribute('aria-hidden', 'true');
|
|
12394
|
-
glyph.textContent = icon;
|
|
12395
|
-
valueSlot.append(glyph);
|
|
12396
|
-
}
|
|
12397
|
-
valueSlot.append(document.createTextNode(text));
|
|
12547
|
+
fillValueSlot(valueSlot, text, selected?.icon ?? null);
|
|
12398
12548
|
});
|
|
12399
12549
|
}
|
|
12400
12550
|
toggle() {
|
|
@@ -12420,7 +12570,9 @@ class LwSelectElement extends HTMLElement {
|
|
|
12420
12570
|
const choices = this.choices();
|
|
12421
12571
|
const selected = choices.findIndex((choice) => choice.value === this.value);
|
|
12422
12572
|
this.setActive(Math.max(0, selected));
|
|
12423
|
-
document.addEventListener('pointerdown', this.onOutsidePointer,
|
|
12573
|
+
document.addEventListener('pointerdown', this.onOutsidePointer, {
|
|
12574
|
+
capture: true,
|
|
12575
|
+
});
|
|
12424
12576
|
}
|
|
12425
12577
|
close(refocusTrigger = true) {
|
|
12426
12578
|
const listbox = this.listbox;
|
|
@@ -12440,94 +12592,83 @@ class LwSelectElement extends HTMLElement {
|
|
|
12440
12592
|
}
|
|
12441
12593
|
}
|
|
12442
12594
|
renderOptions() {
|
|
12443
|
-
|
|
12595
|
+
const listbox = this.listbox;
|
|
12596
|
+
if (!listbox) {
|
|
12444
12597
|
return;
|
|
12445
12598
|
}
|
|
12446
|
-
const
|
|
12447
|
-
|
|
12448
|
-
|
|
12449
|
-
|
|
12450
|
-
|
|
12451
|
-
|
|
12452
|
-
|
|
12453
|
-
|
|
12454
|
-
if (choice.disabled) {
|
|
12455
|
-
option.setAttribute('aria-disabled', 'true');
|
|
12456
|
-
}
|
|
12457
|
-
if (choice.icon) {
|
|
12458
|
-
const glyph = document.createElement('span');
|
|
12459
|
-
glyph.className = 'lw-select-glyph';
|
|
12460
|
-
glyph.setAttribute('aria-hidden', 'true');
|
|
12461
|
-
glyph.textContent = choice.icon;
|
|
12462
|
-
option.append(glyph);
|
|
12463
|
-
}
|
|
12464
|
-
option.append(document.createTextNode(choice.label));
|
|
12465
|
-
option.addEventListener('click', () => {
|
|
12466
|
-
if (!choice.disabled) {
|
|
12467
|
-
this.commit(choice.value);
|
|
12468
|
-
}
|
|
12469
|
-
});
|
|
12470
|
-
option.addEventListener('pointermove', () => this.setActive(index));
|
|
12471
|
-
return option;
|
|
12472
|
-
});
|
|
12473
|
-
const listbox = this.listbox;
|
|
12474
|
-
this.write(() => listbox.replaceChildren(...items));
|
|
12599
|
+
const rows = this.choices().map((choice, index) => createOptionRow({
|
|
12600
|
+
choice,
|
|
12601
|
+
id: `${this.listboxId}-opt-${index}`,
|
|
12602
|
+
selected: choice.value === this.value,
|
|
12603
|
+
onPick: () => this.commit(choice.value),
|
|
12604
|
+
onHover: () => this.setActive(index),
|
|
12605
|
+
}));
|
|
12606
|
+
this.write(() => listbox.replaceChildren(...rows));
|
|
12475
12607
|
}
|
|
12476
12608
|
setActive(index) {
|
|
12477
12609
|
if (!this.listbox) {
|
|
12478
12610
|
return;
|
|
12479
12611
|
}
|
|
12480
|
-
const
|
|
12481
|
-
if (
|
|
12612
|
+
const rows = [...this.listbox.children];
|
|
12613
|
+
if (rows.length === 0) {
|
|
12482
12614
|
return;
|
|
12483
12615
|
}
|
|
12484
|
-
this.activeIndex = Math.max(0, Math.min(index,
|
|
12616
|
+
this.activeIndex = Math.max(0, Math.min(index, rows.length - 1));
|
|
12485
12617
|
this.write(() => {
|
|
12486
|
-
|
|
12487
|
-
|
|
12488
|
-
|
|
12489
|
-
}
|
|
12618
|
+
for (const [index_, row] of rows.entries()) {
|
|
12619
|
+
row.classList.toggle('is-active', index_ === this.activeIndex);
|
|
12620
|
+
row.tabIndex = index_ === this.activeIndex ? 0 : -1;
|
|
12621
|
+
}
|
|
12490
12622
|
});
|
|
12491
|
-
const active =
|
|
12623
|
+
const active = rows[this.activeIndex];
|
|
12492
12624
|
active.focus();
|
|
12493
12625
|
active.scrollIntoView?.({ block: 'nearest' });
|
|
12494
12626
|
}
|
|
12495
12627
|
onTriggerKeydown(event) {
|
|
12496
|
-
if (['ArrowDown', 'ArrowUp', 'Enter', ' '].includes(event.key)) {
|
|
12497
|
-
|
|
12498
|
-
this.openListbox();
|
|
12628
|
+
if (!['ArrowDown', 'ArrowUp', 'Enter', ' '].includes(event.key)) {
|
|
12629
|
+
return;
|
|
12499
12630
|
}
|
|
12631
|
+
event.preventDefault();
|
|
12632
|
+
this.openListbox();
|
|
12500
12633
|
}
|
|
12501
12634
|
onListboxKeydown(event) {
|
|
12502
12635
|
const last = this.choices().length - 1;
|
|
12503
12636
|
switch (event.key) {
|
|
12504
|
-
case 'ArrowDown':
|
|
12637
|
+
case 'ArrowDown': {
|
|
12505
12638
|
this.setActive(this.activeIndex >= last ? 0 : this.activeIndex + 1);
|
|
12506
12639
|
break;
|
|
12507
|
-
|
|
12640
|
+
}
|
|
12641
|
+
case 'ArrowUp': {
|
|
12508
12642
|
this.setActive(this.activeIndex <= 0 ? last : this.activeIndex - 1);
|
|
12509
12643
|
break;
|
|
12510
|
-
|
|
12644
|
+
}
|
|
12645
|
+
case 'Home': {
|
|
12511
12646
|
this.setActive(0);
|
|
12512
12647
|
break;
|
|
12513
|
-
|
|
12648
|
+
}
|
|
12649
|
+
case 'End': {
|
|
12514
12650
|
this.setActive(last);
|
|
12515
12651
|
break;
|
|
12652
|
+
}
|
|
12516
12653
|
case 'Enter':
|
|
12517
|
-
case ' ':
|
|
12654
|
+
case ' ': {
|
|
12518
12655
|
this.commitActive();
|
|
12519
12656
|
break;
|
|
12520
|
-
|
|
12657
|
+
}
|
|
12658
|
+
case 'Escape': {
|
|
12521
12659
|
this.close();
|
|
12522
12660
|
break;
|
|
12523
|
-
|
|
12661
|
+
}
|
|
12662
|
+
case 'Tab': {
|
|
12524
12663
|
this.close(false);
|
|
12525
12664
|
return;
|
|
12526
|
-
|
|
12665
|
+
}
|
|
12666
|
+
default: {
|
|
12527
12667
|
if (event.key.length === 1) {
|
|
12528
12668
|
this.onTypeahead(event.key);
|
|
12529
12669
|
}
|
|
12530
12670
|
return;
|
|
12671
|
+
}
|
|
12531
12672
|
}
|
|
12532
12673
|
event.preventDefault();
|
|
12533
12674
|
}
|
|
@@ -12539,7 +12680,7 @@ class LwSelectElement extends HTMLElement {
|
|
|
12539
12680
|
this.typeaheadTimer = setTimeout(() => (this.typeahead = ''), TYPEAHEAD_RESET_MS);
|
|
12540
12681
|
const match = this.choices().findIndex((choice) => !choice.disabled &&
|
|
12541
12682
|
choice.label.toLowerCase().startsWith(this.typeahead));
|
|
12542
|
-
if (match
|
|
12683
|
+
if (match !== -1) {
|
|
12543
12684
|
this.setActive(match);
|
|
12544
12685
|
}
|
|
12545
12686
|
}
|
|
@@ -12688,10 +12829,11 @@ class LwButtonElement extends HTMLElement {
|
|
|
12688
12829
|
}
|
|
12689
12830
|
}
|
|
12690
12831
|
onKeydown = (event) => {
|
|
12691
|
-
if ((event.key === 'Enter' || event.key === ' ')
|
|
12692
|
-
|
|
12693
|
-
this.click();
|
|
12832
|
+
if (!(event.key === 'Enter' || event.key === ' ') || this.disabled) {
|
|
12833
|
+
return;
|
|
12694
12834
|
}
|
|
12835
|
+
event.preventDefault();
|
|
12836
|
+
this.click();
|
|
12695
12837
|
};
|
|
12696
12838
|
render() {
|
|
12697
12839
|
const stale = [...this.classList].filter((cls) => cls.startsWith('lw-btn'));
|
|
@@ -12870,7 +13012,7 @@ class ViewVisibilityService {
|
|
|
12870
13012
|
return this.stash
|
|
12871
13013
|
.keyedInstances()
|
|
12872
13014
|
.filter((entry) => !entry.key.startsWith(CONTAINER_DOCK_PREFIX) &&
|
|
12873
|
-
entry.key.split('|')[1] === path)
|
|
13015
|
+
entry.key.split('|', 2)[1] === path)
|
|
12874
13016
|
.map((entry) => entry.instance);
|
|
12875
13017
|
}
|
|
12876
13018
|
removeTabs(path) {
|
|
@@ -12942,10 +13084,11 @@ class RailWorkspaceEntries {
|
|
|
12942
13084
|
reconcile() {
|
|
12943
13085
|
const wanted = this.wantedItems();
|
|
12944
13086
|
for (const [id, registration] of this.registered) {
|
|
12945
|
-
if (
|
|
12946
|
-
|
|
12947
|
-
this.registered.delete(id);
|
|
13087
|
+
if (wanted.has(id)) {
|
|
13088
|
+
continue;
|
|
12948
13089
|
}
|
|
13090
|
+
registration.disposable.dispose();
|
|
13091
|
+
this.registered.delete(id);
|
|
12949
13092
|
}
|
|
12950
13093
|
for (const [id, item] of wanted) {
|
|
12951
13094
|
const current = this.registered.get(id);
|
|
@@ -13187,10 +13330,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
13187
13330
|
type: Service
|
|
13188
13331
|
}] });
|
|
13189
13332
|
function installCompositionReport(report) {
|
|
13190
|
-
if (
|
|
13333
|
+
if (globalThis.window === undefined) {
|
|
13191
13334
|
return;
|
|
13192
13335
|
}
|
|
13193
|
-
const host =
|
|
13336
|
+
const host = globalThis;
|
|
13194
13337
|
if (host['loomweaver'] !== undefined) {
|
|
13195
13338
|
return;
|
|
13196
13339
|
}
|
|
@@ -13261,7 +13404,7 @@ class PluginEnablementService {
|
|
|
13261
13404
|
const disabled = this.disabledSet();
|
|
13262
13405
|
return [...this.names().entries()]
|
|
13263
13406
|
.map(([id, name]) => ({ id, name, enabled: !disabled.has(id) }))
|
|
13264
|
-
.
|
|
13407
|
+
.toSorted((a, b) => a.name.localeCompare(b.name));
|
|
13265
13408
|
}, /* @ts-ignore */
|
|
13266
13409
|
...(ngDevMode ? [{ debugName: "plugins" }] : /* istanbul ignore next */ []));
|
|
13267
13410
|
constructor() {
|
|
@@ -13415,7 +13558,7 @@ function parseCatalogEntry(raw) {
|
|
|
13415
13558
|
function dedupeById(items) {
|
|
13416
13559
|
const result = [];
|
|
13417
13560
|
for (const item of items) {
|
|
13418
|
-
if (item &&
|
|
13561
|
+
if (item && result.every((existing) => existing.id !== item.id)) {
|
|
13419
13562
|
result.push(item);
|
|
13420
13563
|
}
|
|
13421
13564
|
}
|
|
@@ -13430,7 +13573,7 @@ function parseInstalledList(raw) {
|
|
|
13430
13573
|
if (!Array.isArray(parsed)) {
|
|
13431
13574
|
return [];
|
|
13432
13575
|
}
|
|
13433
|
-
return dedupeById(parsed.map(parseInstalledPlugin));
|
|
13576
|
+
return dedupeById(parsed.map((raw) => parseInstalledPlugin(raw)));
|
|
13434
13577
|
}
|
|
13435
13578
|
catch {
|
|
13436
13579
|
return [];
|
|
@@ -13440,7 +13583,7 @@ function parseCatalogList(raw) {
|
|
|
13440
13583
|
if (!Array.isArray(raw)) {
|
|
13441
13584
|
return [];
|
|
13442
13585
|
}
|
|
13443
|
-
return dedupeById(raw.map(parseCatalogEntry));
|
|
13586
|
+
return dedupeById(raw.map((entry) => parseCatalogEntry(entry)));
|
|
13444
13587
|
}
|
|
13445
13588
|
|
|
13446
13589
|
const STORAGE_KEY$2 = 'lw.shell.deployed-plugins';
|
|
@@ -13468,7 +13611,7 @@ class PluginDeploymentService {
|
|
|
13468
13611
|
adopt(entries) {
|
|
13469
13612
|
this.persist(entries
|
|
13470
13613
|
.filter((entry) => entry.deployed === true)
|
|
13471
|
-
.map(withoutCatalogMetadata));
|
|
13614
|
+
.map((entry) => withoutCatalogMetadata(entry)));
|
|
13472
13615
|
}
|
|
13473
13616
|
isDeployed(id) {
|
|
13474
13617
|
return this.entries().some((entry) => entry.id === id);
|
|
@@ -13586,10 +13729,10 @@ function registerDefaultSettings(settings) {
|
|
|
13586
13729
|
|
|
13587
13730
|
function fuzzyScore(query, label) {
|
|
13588
13731
|
const needle = query.toLowerCase();
|
|
13589
|
-
const haystack = label.toLowerCase();
|
|
13590
13732
|
if (!needle) {
|
|
13591
13733
|
return 0;
|
|
13592
13734
|
}
|
|
13735
|
+
const haystack = label.toLowerCase();
|
|
13593
13736
|
let score = 0;
|
|
13594
13737
|
let searchFrom = 0;
|
|
13595
13738
|
let previous = -2;
|
|
@@ -13619,7 +13762,7 @@ function formatterFor(locale) {
|
|
|
13619
13762
|
}
|
|
13620
13763
|
catch {
|
|
13621
13764
|
try {
|
|
13622
|
-
return new Intl.RelativeTimeFormat(locale.
|
|
13765
|
+
return new Intl.RelativeTimeFormat(locale.replaceAll('_', '-'), {
|
|
13623
13766
|
numeric: 'auto',
|
|
13624
13767
|
});
|
|
13625
13768
|
}
|
|
@@ -13696,7 +13839,7 @@ function ranked(query, entries) {
|
|
|
13696
13839
|
return entries
|
|
13697
13840
|
.map((entry) => ({ entry, score: fuzzyScore(query, entry.label) }))
|
|
13698
13841
|
.filter((scored) => scored.score !== null)
|
|
13699
|
-
.
|
|
13842
|
+
.toSorted((a, b) => b.score - a.score)
|
|
13700
13843
|
.map((scored) => scored.entry);
|
|
13701
13844
|
}
|
|
13702
13845
|
class CommandPalette {
|
|
@@ -13753,9 +13896,9 @@ class CommandPalette {
|
|
|
13753
13896
|
pinned: tab.pinned,
|
|
13754
13897
|
closable: tab.closable,
|
|
13755
13898
|
lastActive: tab.lastActive,
|
|
13756
|
-
time: tab.lastActive
|
|
13757
|
-
?
|
|
13758
|
-
:
|
|
13899
|
+
time: tab.lastActive === undefined
|
|
13900
|
+
? undefined
|
|
13901
|
+
: formatRelativeTime(locale, tab.lastActive, now),
|
|
13759
13902
|
}));
|
|
13760
13903
|
}, /* @ts-ignore */
|
|
13761
13904
|
...(ngDevMode ? [{ debugName: "tabEntries" }] : /* istanbul ignore next */ []));
|
|
@@ -13784,7 +13927,7 @@ class CommandPalette {
|
|
|
13784
13927
|
const query = this.query().trim();
|
|
13785
13928
|
const entries = this.tabEntries();
|
|
13786
13929
|
if (!query) {
|
|
13787
|
-
return [...entries].
|
|
13930
|
+
return [...entries].toSorted((a, b) => (b.lastActive ?? 0) - (a.lastActive ?? 0));
|
|
13788
13931
|
}
|
|
13789
13932
|
return ranked(query, entries);
|
|
13790
13933
|
}, /* @ts-ignore */
|
|
@@ -13859,7 +14002,7 @@ class CommandPalette {
|
|
|
13859
14002
|
return;
|
|
13860
14003
|
}
|
|
13861
14004
|
const entry = this.results()[this.activeIndex()];
|
|
13862
|
-
if (
|
|
14005
|
+
if (entry?.kind !== 'tab') {
|
|
13863
14006
|
return;
|
|
13864
14007
|
}
|
|
13865
14008
|
event.preventDefault();
|
|
@@ -14139,7 +14282,7 @@ function seedHostCommands(registry, layout, deps) {
|
|
|
14139
14282
|
registry.addCommand({
|
|
14140
14283
|
id: QUICK_OPEN_COMMAND_ID,
|
|
14141
14284
|
title: 'palette.quickOpenTitle',
|
|
14142
|
-
icon: '
|
|
14285
|
+
icon: 'openWork',
|
|
14143
14286
|
shortcut: 'mod+p',
|
|
14144
14287
|
run: () => {
|
|
14145
14288
|
dialogs.open(CommandPalette, {
|
|
@@ -14260,9 +14403,12 @@ function seedBuiltInMenus(registry, layout, deps) {
|
|
|
14260
14403
|
return;
|
|
14261
14404
|
}
|
|
14262
14405
|
registerTabContextMenu(registry, deps.tabs, deps.paneMove, deps.popout, deps.features);
|
|
14406
|
+
seedRailMenus(registry, layout, deps);
|
|
14407
|
+
seedViewMenus(registry, layout, deps);
|
|
14408
|
+
}
|
|
14409
|
+
function seedRailMenus(registry, layout, deps) {
|
|
14263
14410
|
const railCount = layout.regions.filter((region) => region.type === 'rail').length;
|
|
14264
14411
|
const rail = deps.features.rail;
|
|
14265
|
-
const sidebar = deps.features.sidebar;
|
|
14266
14412
|
if (railCount >= 1 && rail.hideItems) {
|
|
14267
14413
|
registerRailContextMenu(registry, deps.railItems);
|
|
14268
14414
|
}
|
|
@@ -14272,6 +14418,9 @@ function seedBuiltInMenus(registry, layout, deps) {
|
|
|
14272
14418
|
if (railCount >= 1 && rail.curate) {
|
|
14273
14419
|
registerRailCustomizeMenu(registry);
|
|
14274
14420
|
}
|
|
14421
|
+
}
|
|
14422
|
+
function seedViewMenus(registry, layout, deps) {
|
|
14423
|
+
const sidebar = deps.features.sidebar;
|
|
14275
14424
|
if (sidebar.resetViewState) {
|
|
14276
14425
|
registerViewResetMenu(registry, deps.viewStates, deps.viewInstances);
|
|
14277
14426
|
}
|
|
@@ -14420,7 +14569,7 @@ class BootLatchedIdentity {
|
|
|
14420
14569
|
return this.latched;
|
|
14421
14570
|
}
|
|
14422
14571
|
const id = this.read();
|
|
14423
|
-
if (id
|
|
14572
|
+
if (!id) {
|
|
14424
14573
|
return null;
|
|
14425
14574
|
}
|
|
14426
14575
|
this.latched = id;
|
|
@@ -14710,7 +14859,7 @@ function surfaceRoute(route, retained) {
|
|
|
14710
14859
|
function subStub(path, pathMatch) {
|
|
14711
14860
|
return {
|
|
14712
14861
|
path,
|
|
14713
|
-
...(pathMatch
|
|
14862
|
+
...(pathMatch && { pathMatch }),
|
|
14714
14863
|
component: ContentSubStub,
|
|
14715
14864
|
data: { content: true, sub: true },
|
|
14716
14865
|
};
|
|
@@ -15001,13 +15150,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
15001
15150
|
type: Service
|
|
15002
15151
|
}] });
|
|
15003
15152
|
function pathOfKey(key) {
|
|
15004
|
-
return key.split('|')[1] ?? '';
|
|
15153
|
+
return key.split('|', 2)[1] ?? '';
|
|
15005
15154
|
}
|
|
15006
15155
|
function stashKeyLive(key, open, routes, views) {
|
|
15007
15156
|
if (key.startsWith(PRIMARY_RETENTION_PREFIX)) {
|
|
15008
15157
|
return true;
|
|
15009
15158
|
}
|
|
15010
|
-
const [scope, path] = key.split('|');
|
|
15159
|
+
const [scope, path] = key.split('|', 2);
|
|
15011
15160
|
if (!tabOpen(open.get(scope), routes, path)) {
|
|
15012
15161
|
return false;
|
|
15013
15162
|
}
|
|
@@ -15540,7 +15689,8 @@ class IconRegistry {
|
|
|
15540
15689
|
setIcon(name, safe);
|
|
15541
15690
|
added.push(name);
|
|
15542
15691
|
}
|
|
15543
|
-
return { dispose: () =>
|
|
15692
|
+
return { dispose: () => { for (const name of added)
|
|
15693
|
+
removeIcon(name); } };
|
|
15544
15694
|
}
|
|
15545
15695
|
resolve(name) {
|
|
15546
15696
|
return resolveIcon(name);
|
|
@@ -15690,6 +15840,9 @@ function providePlugins(...plugins) {
|
|
|
15690
15840
|
];
|
|
15691
15841
|
}
|
|
15692
15842
|
|
|
15843
|
+
/** Multi-provider token: each contribution adds one sandboxed plugin to load. */
|
|
15844
|
+
const FRAME_PLUGIN = new InjectionToken('FRAME_PLUGIN');
|
|
15845
|
+
|
|
15693
15846
|
const STORAGE_KEY = 'lw.shell.installed-plugins';
|
|
15694
15847
|
/**
|
|
15695
15848
|
* The user's installed community plugins. Holds only the state: which catalog entries the
|
|
@@ -15721,7 +15874,7 @@ class PluginInstallService {
|
|
|
15721
15874
|
return this.entries().some((entry) => entry.id === id);
|
|
15722
15875
|
}
|
|
15723
15876
|
/** The installed entry for an id, or `undefined` — the baseline an update is compared against. */
|
|
15724
|
-
|
|
15877
|
+
byId(id) {
|
|
15725
15878
|
return this.entries().find((entry) => entry.id === id);
|
|
15726
15879
|
}
|
|
15727
15880
|
/**
|
|
@@ -15786,202 +15939,86 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
15786
15939
|
type: Service
|
|
15787
15940
|
}], ctorParameters: () => [] });
|
|
15788
15941
|
|
|
15789
|
-
|
|
15790
|
-
|
|
15791
|
-
if (!Array.isArray(raw)) {
|
|
15792
|
-
return [];
|
|
15793
|
-
}
|
|
15794
|
-
return raw
|
|
15795
|
-
.filter((option) => typeof option === 'object' &&
|
|
15796
|
-
option !== null &&
|
|
15797
|
-
typeof option['value'] === 'string' &&
|
|
15798
|
-
typeof option['label'] === 'string')
|
|
15799
|
-
.map((option) => ({ value: option.value, label: option.label }));
|
|
15800
|
-
}
|
|
15801
|
-
function optionalString(value) {
|
|
15802
|
-
return typeof value === 'string' ? value : undefined;
|
|
15803
|
-
}
|
|
15804
|
-
function optionalNumber(value) {
|
|
15805
|
-
return typeof value === 'number' ? value : undefined;
|
|
15806
|
-
}
|
|
15807
|
-
function buildTextControl(value, control) {
|
|
15808
|
-
return {
|
|
15809
|
-
kind: 'text',
|
|
15810
|
-
value,
|
|
15811
|
-
inputType: INPUT_TYPES.find((type) => type === control['inputType']),
|
|
15812
|
-
placeholder: optionalString(control['placeholder']),
|
|
15813
|
-
};
|
|
15942
|
+
function levelOf(plugin) {
|
|
15943
|
+
return plugin.level ?? DEFAULT_ISOLATION_LEVEL;
|
|
15814
15944
|
}
|
|
15815
|
-
function
|
|
15816
|
-
const
|
|
15817
|
-
|
|
15818
|
-
|
|
15945
|
+
function signatureOf(plugin) {
|
|
15946
|
+
const sorted = (values) => [...(values ?? [])].toSorted((a, b) => a.localeCompare(b)).join(',');
|
|
15947
|
+
return `${plugin.entryUrl}|${sorted(plugin.capabilities)}|${sorted(plugin.granted)}|${plugin.version ?? ''}|${levelOf(plugin)}`;
|
|
15948
|
+
}
|
|
15949
|
+
function runnablePlugins(composed, installed, deployed, catalogCap) {
|
|
15950
|
+
const claimed = new Set(composed.map((plugin) => plugin.id));
|
|
15951
|
+
const provided = new Set(deployed.map((plugin) => plugin.id));
|
|
15952
|
+
const fromCatalog = [];
|
|
15953
|
+
for (const plugin of [...deployed, ...installed]) {
|
|
15954
|
+
if (claimed.has(plugin.id)) {
|
|
15955
|
+
continue;
|
|
15956
|
+
}
|
|
15957
|
+
const asked = plugin.level ?? DEFAULT_ISOLATION_LEVEL;
|
|
15958
|
+
if (exceedsLevel(asked, catalogCap)) {
|
|
15959
|
+
console.error(`Plugin "${plugin.id}" asks to run ${asked}, which this catalog may not confer ` +
|
|
15960
|
+
`(its cap is ${catalogCap}). It is not started.`);
|
|
15961
|
+
continue;
|
|
15962
|
+
}
|
|
15963
|
+
claimed.add(plugin.id);
|
|
15964
|
+
fromCatalog.push({
|
|
15965
|
+
id: plugin.id,
|
|
15966
|
+
entryUrl: plugin.entryUrl,
|
|
15967
|
+
capabilities: plugin.capabilities,
|
|
15968
|
+
name: plugin.name,
|
|
15969
|
+
granted: plugin.capabilities ?? [],
|
|
15970
|
+
version: plugin.version,
|
|
15971
|
+
level: asked,
|
|
15972
|
+
provided: provided.has(plugin.id) || undefined,
|
|
15973
|
+
});
|
|
15819
15974
|
}
|
|
15820
|
-
return
|
|
15975
|
+
return [...composed, ...fromCatalog];
|
|
15821
15976
|
}
|
|
15822
|
-
|
|
15823
|
-
|
|
15824
|
-
|
|
15825
|
-
|
|
15826
|
-
|
|
15827
|
-
|
|
15828
|
-
|
|
15829
|
-
|
|
15830
|
-
|
|
15831
|
-
|
|
15832
|
-
|
|
15833
|
-
|
|
15834
|
-
const value = control['value'];
|
|
15835
|
-
if (kind === 'toggle' && typeof value === 'boolean') {
|
|
15836
|
-
return { kind, value };
|
|
15837
|
-
}
|
|
15838
|
-
if (kind === 'text' && typeof value === 'string') {
|
|
15839
|
-
return buildTextControl(value, control);
|
|
15840
|
-
}
|
|
15841
|
-
if (kind === 'select' && typeof value === 'string') {
|
|
15842
|
-
return buildSelectControl(pluginId, value, control);
|
|
15843
|
-
}
|
|
15844
|
-
if (kind === 'slider' && typeof value === 'number') {
|
|
15845
|
-
return buildSliderControl(value, control);
|
|
15846
|
-
}
|
|
15847
|
-
throw new Error(`Sandbox plugin "${pluginId}": a settings control must be toggle/text/select/slider with a matching default 'value'.`);
|
|
15848
|
-
}
|
|
15849
|
-
function sanitizeRow(pluginId, raw) {
|
|
15850
|
-
const row = (raw ?? {});
|
|
15851
|
-
if (typeof row['id'] !== 'string' || row['id'].length === 0) {
|
|
15852
|
-
throw new Error(`Sandbox plugin "${pluginId}": every settings row needs a non-empty 'id'.`);
|
|
15853
|
-
}
|
|
15854
|
-
if (typeof row['label'] !== 'string' || row['label'].length === 0) {
|
|
15855
|
-
throw new Error(`Sandbox plugin "${pluginId}": every settings row needs a non-empty 'label'.`);
|
|
15856
|
-
}
|
|
15857
|
-
return {
|
|
15858
|
-
id: row['id'],
|
|
15859
|
-
label: row['label'],
|
|
15860
|
-
description: typeof row['description'] === 'string' ? row['description'] : undefined,
|
|
15861
|
-
control: sanitizeControl(pluginId, row['control']),
|
|
15862
|
-
};
|
|
15863
|
-
}
|
|
15864
|
-
function sanitizeRpcSettingsSection(pluginId, section) {
|
|
15865
|
-
const raw = (section ?? {});
|
|
15866
|
-
if (typeof raw['id'] !== 'string' || raw['id'].length === 0) {
|
|
15867
|
-
throw new Error(`Sandbox plugin "${pluginId}": registerSettingsSection requires a non-empty 'id'.`);
|
|
15868
|
-
}
|
|
15869
|
-
if (typeof raw['title'] !== 'string' || raw['title'].length === 0) {
|
|
15870
|
-
throw new Error(`Sandbox plugin "${pluginId}": registerSettingsSection requires a non-empty 'title'.`);
|
|
15871
|
-
}
|
|
15872
|
-
if (!Array.isArray(raw['rows']) || raw['rows'].length === 0) {
|
|
15873
|
-
throw new Error(`Sandbox plugin "${pluginId}": registerSettingsSection requires at least one row.`);
|
|
15874
|
-
}
|
|
15875
|
-
return {
|
|
15876
|
-
id: raw['id'],
|
|
15877
|
-
title: raw['title'],
|
|
15878
|
-
order: typeof raw['order'] === 'number' ? raw['order'] : undefined,
|
|
15879
|
-
rows: raw['rows'].map((row) => sanitizeRow(pluginId, row)),
|
|
15880
|
-
};
|
|
15881
|
-
}
|
|
15882
|
-
function defaultsOf(wire) {
|
|
15883
|
-
const defaults = {};
|
|
15884
|
-
for (const row of wire.rows) {
|
|
15885
|
-
defaults[row.id] = row.control.value;
|
|
15886
|
-
}
|
|
15887
|
-
return defaults;
|
|
15888
|
-
}
|
|
15889
|
-
function typedOverlay(defaults, raw) {
|
|
15890
|
-
if (!raw) {
|
|
15891
|
-
return defaults;
|
|
15892
|
-
}
|
|
15893
|
-
try {
|
|
15894
|
-
const parsed = JSON.parse(raw);
|
|
15895
|
-
if (typeof parsed !== 'object' || parsed === null) {
|
|
15896
|
-
return defaults;
|
|
15897
|
-
}
|
|
15898
|
-
const merged = { ...defaults };
|
|
15899
|
-
for (const [key, value] of Object.entries(parsed)) {
|
|
15900
|
-
if (key in defaults && typeof value === typeof defaults[key]) {
|
|
15901
|
-
merged[key] = value;
|
|
15902
|
-
}
|
|
15903
|
-
}
|
|
15904
|
-
return merged;
|
|
15905
|
-
}
|
|
15906
|
-
catch {
|
|
15907
|
-
return defaults;
|
|
15908
|
-
}
|
|
15909
|
-
}
|
|
15910
|
-
function buildFrameSection(deps) {
|
|
15911
|
-
const { pluginId, wire, group, store, sync, notify } = deps;
|
|
15912
|
-
const key = `lw.plugin-settings:${pluginId}:${wire.id}`;
|
|
15913
|
-
const defaults = defaultsOf(wire);
|
|
15914
|
-
const values = signal(typedOverlay(defaults, store.peek?.(key)), /* @ts-ignore */
|
|
15915
|
-
...(ngDevMode ? [{ debugName: "values" }] : /* istanbul ignore next */ []));
|
|
15916
|
-
const applyStored = (raw) => {
|
|
15917
|
-
values.set(typedOverlay(defaults, raw));
|
|
15918
|
-
notify(wire.id, values());
|
|
15919
|
-
};
|
|
15920
|
-
if (store.peek) {
|
|
15921
|
-
notify(wire.id, values());
|
|
15922
|
-
}
|
|
15923
|
-
else {
|
|
15924
|
-
hydrateAsync(store, key, applyStored);
|
|
15925
|
-
}
|
|
15926
|
-
const disposeSync = sync.register('settings', key, applyStored);
|
|
15927
|
-
const set = (rowId, value) => {
|
|
15928
|
-
values.update((current) => ({ ...current, [rowId]: value }));
|
|
15929
|
-
void store.set(key, JSON.stringify(values()));
|
|
15930
|
-
notify(wire.id, values());
|
|
15931
|
-
};
|
|
15932
|
-
const section = {
|
|
15933
|
-
id: `${pluginId}.${wire.id}`,
|
|
15934
|
-
title: wire.title,
|
|
15935
|
-
group,
|
|
15936
|
-
order: wire.order,
|
|
15937
|
-
rows: wire.rows.map((row) => ({
|
|
15938
|
-
id: `${pluginId}.${wire.id}.${row.id}`,
|
|
15939
|
-
label: row.label,
|
|
15940
|
-
description: row.description,
|
|
15941
|
-
control: hostControl(row, values, set),
|
|
15942
|
-
})),
|
|
15943
|
-
};
|
|
15944
|
-
return { section, disposeSync };
|
|
15945
|
-
}
|
|
15946
|
-
function hostControl(row, values, set) {
|
|
15947
|
-
const control = row.control;
|
|
15948
|
-
switch (control.kind) {
|
|
15949
|
-
case 'toggle':
|
|
15950
|
-
return {
|
|
15951
|
-
kind: 'toggle',
|
|
15952
|
-
value: () => values()[row.id] === true,
|
|
15953
|
-
set: (value) => set(row.id, value),
|
|
15954
|
-
};
|
|
15955
|
-
case 'text':
|
|
15956
|
-
return {
|
|
15957
|
-
kind: 'text',
|
|
15958
|
-
inputType: control.inputType,
|
|
15959
|
-
placeholder: control.placeholder,
|
|
15960
|
-
value: () => String(values()[row.id] ?? ''),
|
|
15961
|
-
set: (value) => set(row.id, value),
|
|
15962
|
-
};
|
|
15963
|
-
case 'select':
|
|
15964
|
-
return {
|
|
15965
|
-
kind: 'select',
|
|
15966
|
-
options: control.options,
|
|
15967
|
-
value: () => String(values()[row.id] ?? control.value),
|
|
15968
|
-
set: (value) => set(row.id, value),
|
|
15969
|
-
};
|
|
15970
|
-
case 'slider':
|
|
15971
|
-
return {
|
|
15972
|
-
kind: 'slider',
|
|
15973
|
-
min: control.min,
|
|
15974
|
-
max: control.max,
|
|
15975
|
-
step: control.step,
|
|
15976
|
-
value: () => Number(values()[row.id] ?? control.value),
|
|
15977
|
-
set: (value) => set(row.id, value),
|
|
15978
|
-
};
|
|
15979
|
-
}
|
|
15977
|
+
|
|
15978
|
+
const UNCARRIABLE_ARGUMENTS = {
|
|
15979
|
+
outcome: 'refused',
|
|
15980
|
+
reason: 'invalid-arguments',
|
|
15981
|
+
message: 'Arguments must be an object of single values or lists of them; anything else cannot cross the ' +
|
|
15982
|
+
'sandbox boundary as the value it was.',
|
|
15983
|
+
};
|
|
15984
|
+
function invokeRpcCommand(ctx, id, args) {
|
|
15985
|
+
const carried = args === undefined ? undefined : asCommandArguments(args);
|
|
15986
|
+
return carried === null
|
|
15987
|
+
? Promise.resolve(UNCARRIABLE_ARGUMENTS)
|
|
15988
|
+
: ctx.invokeCommand(String(id), carried);
|
|
15980
15989
|
}
|
|
15981
15990
|
|
|
15982
15991
|
const MAX_RPC_AREA_DEPTH = 8;
|
|
15983
15992
|
function sanitizeRpcSurface(pluginId, surface, permitted) {
|
|
15984
15993
|
const raw = (surface ?? {});
|
|
15994
|
+
const { id, title } = rpcSurfaceIdentity(pluginId, raw);
|
|
15995
|
+
const container = sanitizeRpcContainer(raw['container']);
|
|
15996
|
+
const iframe = container === undefined
|
|
15997
|
+
? rpcIframeUrl(pluginId, raw['iframe'], permitted)
|
|
15998
|
+
: undefined;
|
|
15999
|
+
const routable = sanitizeRpcRoutable(raw['routable']);
|
|
16000
|
+
const docks = sanitizeRpcDocks(raw['docks']);
|
|
16001
|
+
assertRpcSurfaceAddress(pluginId, container, routable, docks);
|
|
16002
|
+
const shared = {
|
|
16003
|
+
id,
|
|
16004
|
+
title,
|
|
16005
|
+
icon: typeof raw['icon'] === 'string' ? raw['icon'] : undefined,
|
|
16006
|
+
order: typeof raw['order'] === 'number' ? raw['order'] : undefined,
|
|
16007
|
+
instanceable: raw['instanceable'] === true ? true : undefined,
|
|
16008
|
+
retain: raw['retain'] === 'always' || raw['retain'] === 'never'
|
|
16009
|
+
? raw['retain']
|
|
16010
|
+
: undefined,
|
|
16011
|
+
saveOn: raw['saveOn'] === 'hide' ? 'hide' : undefined,
|
|
16012
|
+
closable: raw['closable'] === false ? false : undefined,
|
|
16013
|
+
padded: raw['padded'] === false ? false : undefined,
|
|
16014
|
+
routable,
|
|
16015
|
+
docks,
|
|
16016
|
+
};
|
|
16017
|
+
return container === undefined
|
|
16018
|
+
? { ...shared, iframe: iframe }
|
|
16019
|
+
: { ...shared, container };
|
|
16020
|
+
}
|
|
16021
|
+
function rpcSurfaceIdentity(pluginId, raw) {
|
|
15985
16022
|
if (typeof raw['id'] !== 'string' || raw['id'].length === 0) {
|
|
15986
16023
|
throw new Error(`Sandbox plugin "${pluginId}": registerSurface requires a non-empty 'id'.`);
|
|
15987
16024
|
}
|
|
@@ -15996,20 +16033,20 @@ function sanitizeRpcSurface(pluginId, surface, permitted) {
|
|
|
15996
16033
|
throw new Error(`Sandbox plugin "${pluginId}": 'access' does not cross the RPC boundary — ` +
|
|
15997
16034
|
`a sandboxed surface gates itself from the pushed session state.`);
|
|
15998
16035
|
}
|
|
15999
|
-
|
|
16000
|
-
|
|
16001
|
-
|
|
16002
|
-
|
|
16003
|
-
|
|
16004
|
-
}
|
|
16005
|
-
const origin = surfaceOrigin(iframe);
|
|
16006
|
-
if (origin === null || !permittedOrigins(permitted).has(origin)) {
|
|
16007
|
-
throw new Error(`Sandbox plugin "${pluginId}": the iframe surface must be served from an origin this ` +
|
|
16008
|
-
`distribution permitted for it, got "${iframe}".`);
|
|
16009
|
-
}
|
|
16036
|
+
return { id: raw['id'], title: raw['title'] };
|
|
16037
|
+
}
|
|
16038
|
+
function rpcIframeUrl(pluginId, value, permitted) {
|
|
16039
|
+
if (typeof value !== 'string') {
|
|
16040
|
+
throw new TypeError(`Sandbox plugin "${pluginId}": registerSurface needs an { iframe } URL or a { container } spec.`);
|
|
16010
16041
|
}
|
|
16011
|
-
const
|
|
16012
|
-
|
|
16042
|
+
const origin = surfaceOrigin(value);
|
|
16043
|
+
if (origin === null || !permittedOrigins(permitted).has(origin)) {
|
|
16044
|
+
throw new Error(`Sandbox plugin "${pluginId}": the iframe surface must be served from an origin this ` +
|
|
16045
|
+
`distribution permitted for it, got "${value}".`);
|
|
16046
|
+
}
|
|
16047
|
+
return value;
|
|
16048
|
+
}
|
|
16049
|
+
function assertRpcSurfaceAddress(pluginId, container, routable, docks) {
|
|
16013
16050
|
if (routable === undefined && docks === undefined) {
|
|
16014
16051
|
throw new Error(`Sandbox plugin "${pluginId}": registerSurface needs 'routable.path' (a URL-addressed surface) ` +
|
|
16015
16052
|
`or 'docks' (a surface hosted at a dock).`);
|
|
@@ -16018,24 +16055,6 @@ function sanitizeRpcSurface(pluginId, surface, permitted) {
|
|
|
16018
16055
|
throw new Error(`Sandbox plugin "${pluginId}": a container surface must be routable — a container tab holds ` +
|
|
16019
16056
|
`its own ':id'.`);
|
|
16020
16057
|
}
|
|
16021
|
-
const shared = {
|
|
16022
|
-
id: raw['id'],
|
|
16023
|
-
title: raw['title'],
|
|
16024
|
-
icon: typeof raw['icon'] === 'string' ? raw['icon'] : undefined,
|
|
16025
|
-
order: typeof raw['order'] === 'number' ? raw['order'] : undefined,
|
|
16026
|
-
instanceable: raw['instanceable'] === true ? true : undefined,
|
|
16027
|
-
retain: raw['retain'] === 'always' || raw['retain'] === 'never'
|
|
16028
|
-
? raw['retain']
|
|
16029
|
-
: undefined,
|
|
16030
|
-
saveOn: raw['saveOn'] === 'hide' ? 'hide' : undefined,
|
|
16031
|
-
closable: raw['closable'] === false ? false : undefined,
|
|
16032
|
-
padded: raw['padded'] === false ? false : undefined,
|
|
16033
|
-
routable,
|
|
16034
|
-
docks,
|
|
16035
|
-
};
|
|
16036
|
-
return container !== undefined
|
|
16037
|
-
? { ...shared, container }
|
|
16038
|
-
: { ...shared, iframe: iframe };
|
|
16039
16058
|
}
|
|
16040
16059
|
function sanitizeRpcRoutable(value) {
|
|
16041
16060
|
if (typeof value !== 'object' || value === null) {
|
|
@@ -16088,7 +16107,7 @@ function sanitizeRpcArea(value, depth) {
|
|
|
16088
16107
|
const raw = value;
|
|
16089
16108
|
const size = typeof raw['size'] === 'number' ? { size: raw['size'] } : {};
|
|
16090
16109
|
if (Array.isArray(raw['tabs'])) {
|
|
16091
|
-
return { ...size, tabs: raw['tabs'].flatMap(sanitizeRpcContainerTab) };
|
|
16110
|
+
return { ...size, tabs: raw['tabs'].flatMap((value) => sanitizeRpcContainerTab(value)) };
|
|
16092
16111
|
}
|
|
16093
16112
|
for (const kind of ['rows', 'columns']) {
|
|
16094
16113
|
const declared = raw[kind];
|
|
@@ -16115,8 +16134,8 @@ function sanitizeRpcContainerTab(value) {
|
|
|
16115
16134
|
return [
|
|
16116
16135
|
{
|
|
16117
16136
|
surface: raw['surface'],
|
|
16118
|
-
...(raw['closable'] === false
|
|
16119
|
-
...(raw['active'] === true
|
|
16137
|
+
...(raw['closable'] === false && { closable: false }),
|
|
16138
|
+
...(raw['active'] === true && { active: true }),
|
|
16120
16139
|
},
|
|
16121
16140
|
];
|
|
16122
16141
|
}
|
|
@@ -16173,11 +16192,14 @@ function sanitizeRpcToastInput(input) {
|
|
|
16173
16192
|
id: typeof raw['id'] === 'string' ? raw['id'] : undefined,
|
|
16174
16193
|
};
|
|
16175
16194
|
}
|
|
16195
|
+
const NOTIFICATION_KINDS = new Set([
|
|
16196
|
+
'info',
|
|
16197
|
+
'success',
|
|
16198
|
+
'warning',
|
|
16199
|
+
'error',
|
|
16200
|
+
]);
|
|
16176
16201
|
function isNotificationKind(value) {
|
|
16177
|
-
return (value
|
|
16178
|
-
value === 'success' ||
|
|
16179
|
-
value === 'warning' ||
|
|
16180
|
-
value === 'error');
|
|
16202
|
+
return NOTIFICATION_KINDS.has(value);
|
|
16181
16203
|
}
|
|
16182
16204
|
function sanitizeRpcMenuItem(item) {
|
|
16183
16205
|
const raw = (item ?? {});
|
|
@@ -16210,29 +16232,267 @@ function sanitizeMenuContext(value) {
|
|
|
16210
16232
|
return clean;
|
|
16211
16233
|
}
|
|
16212
16234
|
|
|
16213
|
-
const
|
|
16214
|
-
|
|
16215
|
-
|
|
16216
|
-
|
|
16217
|
-
|
|
16218
|
-
|
|
16219
|
-
|
|
16220
|
-
|
|
16221
|
-
|
|
16222
|
-
|
|
16223
|
-
|
|
16235
|
+
const INPUT_TYPES = ['text', 'date', 'email', 'number', 'password'];
|
|
16236
|
+
function sanitizeOptions(raw) {
|
|
16237
|
+
if (!Array.isArray(raw)) {
|
|
16238
|
+
return [];
|
|
16239
|
+
}
|
|
16240
|
+
return raw
|
|
16241
|
+
.filter((option) => typeof option === 'object' &&
|
|
16242
|
+
option !== null &&
|
|
16243
|
+
typeof option['value'] === 'string' &&
|
|
16244
|
+
typeof option['label'] === 'string')
|
|
16245
|
+
.map((option) => ({ value: option.value, label: option.label }));
|
|
16224
16246
|
}
|
|
16225
|
-
|
|
16226
|
-
|
|
16227
|
-
const FRAME_PLUGIN = new InjectionToken('FRAME_PLUGIN');
|
|
16228
|
-
function levelOf(plugin) {
|
|
16229
|
-
return plugin.level ?? DEFAULT_ISOLATION_LEVEL;
|
|
16247
|
+
function optionalString(value) {
|
|
16248
|
+
return typeof value === 'string' ? value : undefined;
|
|
16230
16249
|
}
|
|
16231
|
-
function
|
|
16232
|
-
|
|
16233
|
-
const granted = [...(plugin.granted ?? [])].sort().join(',');
|
|
16234
|
-
return `${plugin.entryUrl}|${caps}|${granted}|${plugin.version ?? ''}|${levelOf(plugin)}`;
|
|
16250
|
+
function optionalNumber(value) {
|
|
16251
|
+
return typeof value === 'number' ? value : undefined;
|
|
16235
16252
|
}
|
|
16253
|
+
function buildTextControl(value, control) {
|
|
16254
|
+
return {
|
|
16255
|
+
kind: 'text',
|
|
16256
|
+
value,
|
|
16257
|
+
inputType: INPUT_TYPES.find((type) => type === control['inputType']),
|
|
16258
|
+
placeholder: optionalString(control['placeholder']),
|
|
16259
|
+
};
|
|
16260
|
+
}
|
|
16261
|
+
function buildSelectControl(pluginId, value, control) {
|
|
16262
|
+
const options = sanitizeOptions(control['options']);
|
|
16263
|
+
if (options.length === 0) {
|
|
16264
|
+
throw new Error(`Sandbox plugin "${pluginId}": a select control needs at least one { value, label } option.`);
|
|
16265
|
+
}
|
|
16266
|
+
return { kind: 'select', value, options };
|
|
16267
|
+
}
|
|
16268
|
+
function buildSliderControl(value, control) {
|
|
16269
|
+
return {
|
|
16270
|
+
kind: 'slider',
|
|
16271
|
+
value,
|
|
16272
|
+
min: optionalNumber(control['min']),
|
|
16273
|
+
max: optionalNumber(control['max']),
|
|
16274
|
+
step: optionalNumber(control['step']),
|
|
16275
|
+
};
|
|
16276
|
+
}
|
|
16277
|
+
function sanitizeControl(pluginId, raw) {
|
|
16278
|
+
const control = (raw ?? {});
|
|
16279
|
+
const kind = control['kind'];
|
|
16280
|
+
const value = control['value'];
|
|
16281
|
+
if (kind === 'toggle' && typeof value === 'boolean') {
|
|
16282
|
+
return { kind, value };
|
|
16283
|
+
}
|
|
16284
|
+
if (kind === 'text' && typeof value === 'string') {
|
|
16285
|
+
return buildTextControl(value, control);
|
|
16286
|
+
}
|
|
16287
|
+
if (kind === 'select' && typeof value === 'string') {
|
|
16288
|
+
return buildSelectControl(pluginId, value, control);
|
|
16289
|
+
}
|
|
16290
|
+
if (kind === 'slider' && typeof value === 'number') {
|
|
16291
|
+
return buildSliderControl(value, control);
|
|
16292
|
+
}
|
|
16293
|
+
throw new Error(`Sandbox plugin "${pluginId}": a settings control must be toggle/text/select/slider with a matching default 'value'.`);
|
|
16294
|
+
}
|
|
16295
|
+
function sanitizeRow(pluginId, raw) {
|
|
16296
|
+
const row = (raw ?? {});
|
|
16297
|
+
if (typeof row['id'] !== 'string' || row['id'].length === 0) {
|
|
16298
|
+
throw new Error(`Sandbox plugin "${pluginId}": every settings row needs a non-empty 'id'.`);
|
|
16299
|
+
}
|
|
16300
|
+
if (typeof row['label'] !== 'string' || row['label'].length === 0) {
|
|
16301
|
+
throw new Error(`Sandbox plugin "${pluginId}": every settings row needs a non-empty 'label'.`);
|
|
16302
|
+
}
|
|
16303
|
+
return {
|
|
16304
|
+
id: row['id'],
|
|
16305
|
+
label: row['label'],
|
|
16306
|
+
description: typeof row['description'] === 'string' ? row['description'] : undefined,
|
|
16307
|
+
control: sanitizeControl(pluginId, row['control']),
|
|
16308
|
+
};
|
|
16309
|
+
}
|
|
16310
|
+
function sanitizeRpcSettingsSection(pluginId, section) {
|
|
16311
|
+
const raw = (section ?? {});
|
|
16312
|
+
if (typeof raw['id'] !== 'string' || raw['id'].length === 0) {
|
|
16313
|
+
throw new Error(`Sandbox plugin "${pluginId}": registerSettingsSection requires a non-empty 'id'.`);
|
|
16314
|
+
}
|
|
16315
|
+
if (typeof raw['title'] !== 'string' || raw['title'].length === 0) {
|
|
16316
|
+
throw new Error(`Sandbox plugin "${pluginId}": registerSettingsSection requires a non-empty 'title'.`);
|
|
16317
|
+
}
|
|
16318
|
+
if (!Array.isArray(raw['rows']) || raw['rows'].length === 0) {
|
|
16319
|
+
throw new Error(`Sandbox plugin "${pluginId}": registerSettingsSection requires at least one row.`);
|
|
16320
|
+
}
|
|
16321
|
+
return {
|
|
16322
|
+
id: raw['id'],
|
|
16323
|
+
title: raw['title'],
|
|
16324
|
+
order: typeof raw['order'] === 'number' ? raw['order'] : undefined,
|
|
16325
|
+
rows: raw['rows'].map((row) => sanitizeRow(pluginId, row)),
|
|
16326
|
+
};
|
|
16327
|
+
}
|
|
16328
|
+
function defaultsOf(wire) {
|
|
16329
|
+
const defaults = {};
|
|
16330
|
+
for (const row of wire.rows) {
|
|
16331
|
+
defaults[row.id] = row.control.value;
|
|
16332
|
+
}
|
|
16333
|
+
return defaults;
|
|
16334
|
+
}
|
|
16335
|
+
function typedOverlay(defaults, raw) {
|
|
16336
|
+
if (!raw) {
|
|
16337
|
+
return defaults;
|
|
16338
|
+
}
|
|
16339
|
+
try {
|
|
16340
|
+
const parsed = JSON.parse(raw);
|
|
16341
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
16342
|
+
return defaults;
|
|
16343
|
+
}
|
|
16344
|
+
const merged = { ...defaults };
|
|
16345
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
16346
|
+
if (Object.hasOwn(defaults, key) && typeof value === typeof defaults[key]) {
|
|
16347
|
+
merged[key] = value;
|
|
16348
|
+
}
|
|
16349
|
+
}
|
|
16350
|
+
return merged;
|
|
16351
|
+
}
|
|
16352
|
+
catch {
|
|
16353
|
+
return defaults;
|
|
16354
|
+
}
|
|
16355
|
+
}
|
|
16356
|
+
function buildFrameSection(deps) {
|
|
16357
|
+
const { pluginId, wire, group, store, sync, notify } = deps;
|
|
16358
|
+
const key = `lw.plugin-settings:${pluginId}:${wire.id}`;
|
|
16359
|
+
const defaults = defaultsOf(wire);
|
|
16360
|
+
const values = signal(typedOverlay(defaults, store.peek?.(key)), /* @ts-ignore */
|
|
16361
|
+
...(ngDevMode ? [{ debugName: "values" }] : /* istanbul ignore next */ []));
|
|
16362
|
+
const applyStored = (raw) => {
|
|
16363
|
+
values.set(typedOverlay(defaults, raw));
|
|
16364
|
+
notify(wire.id, values());
|
|
16365
|
+
};
|
|
16366
|
+
if (store.peek) {
|
|
16367
|
+
notify(wire.id, values());
|
|
16368
|
+
}
|
|
16369
|
+
else {
|
|
16370
|
+
hydrateAsync(store, key, applyStored);
|
|
16371
|
+
}
|
|
16372
|
+
const disposeSync = sync.register('settings', key, applyStored);
|
|
16373
|
+
const set = (rowId, value) => {
|
|
16374
|
+
values.update((current) => ({ ...current, [rowId]: value }));
|
|
16375
|
+
void store.set(key, JSON.stringify(values()));
|
|
16376
|
+
notify(wire.id, values());
|
|
16377
|
+
};
|
|
16378
|
+
const section = {
|
|
16379
|
+
id: `${pluginId}.${wire.id}`,
|
|
16380
|
+
title: wire.title,
|
|
16381
|
+
group,
|
|
16382
|
+
order: wire.order,
|
|
16383
|
+
rows: wire.rows.map((row) => ({
|
|
16384
|
+
id: `${pluginId}.${wire.id}.${row.id}`,
|
|
16385
|
+
label: row.label,
|
|
16386
|
+
description: row.description,
|
|
16387
|
+
control: hostControl(row, values, set),
|
|
16388
|
+
})),
|
|
16389
|
+
};
|
|
16390
|
+
return { section, disposeSync };
|
|
16391
|
+
}
|
|
16392
|
+
function hostControl(row, values, set) {
|
|
16393
|
+
const control = row.control;
|
|
16394
|
+
switch (control.kind) {
|
|
16395
|
+
case 'toggle': {
|
|
16396
|
+
return {
|
|
16397
|
+
kind: 'toggle',
|
|
16398
|
+
value: () => values()[row.id] === true,
|
|
16399
|
+
set: (value) => set(row.id, value),
|
|
16400
|
+
};
|
|
16401
|
+
}
|
|
16402
|
+
case 'text': {
|
|
16403
|
+
return {
|
|
16404
|
+
kind: 'text',
|
|
16405
|
+
inputType: control.inputType,
|
|
16406
|
+
placeholder: control.placeholder,
|
|
16407
|
+
value: () => String(values()[row.id] ?? ''),
|
|
16408
|
+
set: (value) => set(row.id, value),
|
|
16409
|
+
};
|
|
16410
|
+
}
|
|
16411
|
+
case 'select': {
|
|
16412
|
+
return {
|
|
16413
|
+
kind: 'select',
|
|
16414
|
+
options: control.options,
|
|
16415
|
+
value: () => String(values()[row.id] ?? control.value),
|
|
16416
|
+
set: (value) => set(row.id, value),
|
|
16417
|
+
};
|
|
16418
|
+
}
|
|
16419
|
+
case 'slider': {
|
|
16420
|
+
return {
|
|
16421
|
+
kind: 'slider',
|
|
16422
|
+
min: control.min,
|
|
16423
|
+
max: control.max,
|
|
16424
|
+
step: control.step,
|
|
16425
|
+
value: () => Number(values()[row.id] ?? control.value),
|
|
16426
|
+
set: (value) => set(row.id, value),
|
|
16427
|
+
};
|
|
16428
|
+
}
|
|
16429
|
+
}
|
|
16430
|
+
}
|
|
16431
|
+
|
|
16432
|
+
function frameRpcMethods(deps) {
|
|
16433
|
+
const { pluginId, ctx, origins, watched } = deps;
|
|
16434
|
+
return reportingRefusals({
|
|
16435
|
+
registerSurface: (surface) => {
|
|
16436
|
+
ctx.registerSurface(sanitizeRpcSurface(pluginId, surface, origins));
|
|
16437
|
+
},
|
|
16438
|
+
registerMenuItem: (item) => {
|
|
16439
|
+
ctx.registerMenuItem(sanitizeRpcMenuItem(item));
|
|
16440
|
+
},
|
|
16441
|
+
registerSettingsSection: (section) => {
|
|
16442
|
+
const built = buildFrameSection({
|
|
16443
|
+
pluginId,
|
|
16444
|
+
wire: sanitizeRpcSettingsSection(pluginId, section),
|
|
16445
|
+
group: deps.install.isInstalled(pluginId)
|
|
16446
|
+
? 'settings.group.community'
|
|
16447
|
+
: 'settings.group.plugins',
|
|
16448
|
+
store: deps.store,
|
|
16449
|
+
sync: deps.sync,
|
|
16450
|
+
notify: (sectionId, values) => deps.notify((remote) => remote.settingsChanged(sectionId, values)),
|
|
16451
|
+
});
|
|
16452
|
+
deps.syncCleanups.push(built.disposeSync);
|
|
16453
|
+
ctx.registerSettingsSection(built.section);
|
|
16454
|
+
},
|
|
16455
|
+
navigateContent: (path) => ctx.navigateContent(path),
|
|
16456
|
+
openContentTab: (input) => {
|
|
16457
|
+
const sanitized = sanitizeRpcTabInput(input);
|
|
16458
|
+
ctx.openContentTab({
|
|
16459
|
+
...sanitized,
|
|
16460
|
+
onClose: () => deps.notify((remote) => remote.contentTabClosed(sanitized.path)),
|
|
16461
|
+
});
|
|
16462
|
+
},
|
|
16463
|
+
keepContentTab: (path) => ctx.keepContentTab(path),
|
|
16464
|
+
pinContentTab: (path) => ctx.pinContentTab(path),
|
|
16465
|
+
unpinContentTab: (path) => ctx.unpinContentTab(path),
|
|
16466
|
+
closeContentTab: (path) => ctx.closeContentTab(path),
|
|
16467
|
+
revealSurface: (id) => ctx.revealSurface(id),
|
|
16468
|
+
invokeCommand: (id, args) => invokeRpcCommand(ctx, id, args),
|
|
16469
|
+
invocableCommands: () => ctx.invocableCommands(),
|
|
16470
|
+
toast: (input) => ctx.ui.toast(sanitizeRpcToastInput(input)),
|
|
16471
|
+
stateWatch: (key) => deps.watchState(key),
|
|
16472
|
+
stateSet: (key, value) => watched.get(key)?.handle.set(value),
|
|
16473
|
+
stateClear: (key) => watched.get(key)?.handle.clear(),
|
|
16474
|
+
stateUnwatch: (key) => {
|
|
16475
|
+
watched.get(key)?.stop();
|
|
16476
|
+
watched.delete(key);
|
|
16477
|
+
},
|
|
16478
|
+
}, deps.reportRefusal);
|
|
16479
|
+
}
|
|
16480
|
+
function reportingRefusals(methods, report) {
|
|
16481
|
+
const reported = Object.entries(methods).map(([name, method]) => [
|
|
16482
|
+
name,
|
|
16483
|
+
(...args) => {
|
|
16484
|
+
try {
|
|
16485
|
+
return method(...args);
|
|
16486
|
+
}
|
|
16487
|
+
catch (error) {
|
|
16488
|
+
report(error);
|
|
16489
|
+
throw error;
|
|
16490
|
+
}
|
|
16491
|
+
},
|
|
16492
|
+
]);
|
|
16493
|
+
return Object.fromEntries(reported);
|
|
16494
|
+
}
|
|
16495
|
+
|
|
16236
16496
|
/**
|
|
16237
16497
|
* Second {@link PluginRuntime} implementation:
|
|
16238
16498
|
* runs each plugin in an isolated `<iframe sandbox="allow-scripts">` and hands it `ctx` over **Penpal**
|
|
@@ -16290,9 +16550,13 @@ class FramePluginRuntime {
|
|
|
16290
16550
|
this.instances.delete(id);
|
|
16291
16551
|
instance.connection.destroy();
|
|
16292
16552
|
instance.frame.remove();
|
|
16293
|
-
|
|
16553
|
+
for (const entry of instance.watched.values()) {
|
|
16554
|
+
entry.stop();
|
|
16555
|
+
}
|
|
16294
16556
|
instance.ctx.disposeAll();
|
|
16295
|
-
instance.syncCleanups
|
|
16557
|
+
for (const cleanup of instance.syncCleanups) {
|
|
16558
|
+
cleanup();
|
|
16559
|
+
}
|
|
16296
16560
|
this.grants.unregister(id);
|
|
16297
16561
|
this.isolation.unregister(id);
|
|
16298
16562
|
}
|
|
@@ -16304,7 +16568,7 @@ class FramePluginRuntime {
|
|
|
16304
16568
|
}
|
|
16305
16569
|
}
|
|
16306
16570
|
reconcile(disabled, installed, deployed) {
|
|
16307
|
-
const runnable = this.
|
|
16571
|
+
const runnable = runnablePlugins(this.plugins, installed, deployed, this.catalogCap);
|
|
16308
16572
|
for (const plugin of runnable) {
|
|
16309
16573
|
this.enablement.register(plugin.id, plugin.name ?? plugin.id);
|
|
16310
16574
|
const enabled = plugin.provided === true || !disabled.has(plugin.id);
|
|
@@ -16322,34 +16586,6 @@ class FramePluginRuntime {
|
|
|
16322
16586
|
}
|
|
16323
16587
|
this.dropUninstalled(runnable);
|
|
16324
16588
|
}
|
|
16325
|
-
runnablePlugins(installed, deployed) {
|
|
16326
|
-
const claimed = new Set(this.plugins.map((plugin) => plugin.id));
|
|
16327
|
-
const provided = new Set(deployed.map((plugin) => plugin.id));
|
|
16328
|
-
const fromCatalog = [];
|
|
16329
|
-
for (const plugin of [...deployed, ...installed]) {
|
|
16330
|
-
if (claimed.has(plugin.id)) {
|
|
16331
|
-
continue;
|
|
16332
|
-
}
|
|
16333
|
-
const asked = plugin.level ?? DEFAULT_ISOLATION_LEVEL;
|
|
16334
|
-
if (exceedsLevel(asked, this.catalogCap)) {
|
|
16335
|
-
console.error(`Plugin "${plugin.id}" asks to run ${asked}, which this catalog may not confer ` +
|
|
16336
|
-
`(its cap is ${this.catalogCap}). It is not started.`);
|
|
16337
|
-
continue;
|
|
16338
|
-
}
|
|
16339
|
-
claimed.add(plugin.id);
|
|
16340
|
-
fromCatalog.push({
|
|
16341
|
-
id: plugin.id,
|
|
16342
|
-
entryUrl: plugin.entryUrl,
|
|
16343
|
-
capabilities: plugin.capabilities,
|
|
16344
|
-
name: plugin.name,
|
|
16345
|
-
granted: plugin.capabilities ?? [],
|
|
16346
|
-
version: plugin.version,
|
|
16347
|
-
level: asked,
|
|
16348
|
-
provided: provided.has(plugin.id) || undefined,
|
|
16349
|
-
});
|
|
16350
|
-
}
|
|
16351
|
-
return [...this.plugins, ...fromCatalog];
|
|
16352
|
-
}
|
|
16353
16589
|
dropUninstalled(runnable) {
|
|
16354
16590
|
const known = new Set(runnable.map((plugin) => plugin.id));
|
|
16355
16591
|
for (const id of this.instances.keys()) {
|
|
@@ -16373,7 +16609,19 @@ class FramePluginRuntime {
|
|
|
16373
16609
|
const watched = new Map();
|
|
16374
16610
|
const connection = connect({
|
|
16375
16611
|
messenger,
|
|
16376
|
-
methods:
|
|
16612
|
+
methods: frameRpcMethods({
|
|
16613
|
+
pluginId: plugin.id,
|
|
16614
|
+
ctx,
|
|
16615
|
+
origins: plugin.origins,
|
|
16616
|
+
install: this.install,
|
|
16617
|
+
store: this.store,
|
|
16618
|
+
sync: this.sync,
|
|
16619
|
+
syncCleanups,
|
|
16620
|
+
watched,
|
|
16621
|
+
watchState: (key) => this.watchState(plugin.id, ctx, watched, key),
|
|
16622
|
+
notify: (send) => this.notify(plugin.id, send),
|
|
16623
|
+
reportRefusal: (error) => this.refusals.report(error),
|
|
16624
|
+
}),
|
|
16377
16625
|
});
|
|
16378
16626
|
this.instances.set(plugin.id, {
|
|
16379
16627
|
ctx,
|
|
@@ -16384,10 +16632,11 @@ class FramePluginRuntime {
|
|
|
16384
16632
|
watched,
|
|
16385
16633
|
});
|
|
16386
16634
|
connection.promise.catch((error) => {
|
|
16387
|
-
if (this.instances.has(plugin.id)) {
|
|
16388
|
-
|
|
16389
|
-
this.deactivate(plugin.id);
|
|
16635
|
+
if (!this.instances.has(plugin.id)) {
|
|
16636
|
+
return;
|
|
16390
16637
|
}
|
|
16638
|
+
console.error(`Sandbox plugin "${plugin.id}" failed to connect`, error);
|
|
16639
|
+
this.deactivate(plugin.id);
|
|
16391
16640
|
});
|
|
16392
16641
|
}
|
|
16393
16642
|
createFrame(entryUrl, level) {
|
|
@@ -16398,71 +16647,9 @@ class FramePluginRuntime {
|
|
|
16398
16647
|
frame.setAttribute('aria-hidden', 'true');
|
|
16399
16648
|
frame.style.display = 'none';
|
|
16400
16649
|
frame.src = entryUrl;
|
|
16401
|
-
document.body.
|
|
16650
|
+
document.body.append(frame);
|
|
16402
16651
|
return frame;
|
|
16403
16652
|
}
|
|
16404
|
-
reportingRefusals(methods) {
|
|
16405
|
-
const reported = Object.entries(methods).map(([name, method]) => [
|
|
16406
|
-
name,
|
|
16407
|
-
(...args) => {
|
|
16408
|
-
try {
|
|
16409
|
-
return method(...args);
|
|
16410
|
-
}
|
|
16411
|
-
catch (error) {
|
|
16412
|
-
this.refusals.report(error);
|
|
16413
|
-
throw error;
|
|
16414
|
-
}
|
|
16415
|
-
},
|
|
16416
|
-
]);
|
|
16417
|
-
return Object.fromEntries(reported);
|
|
16418
|
-
}
|
|
16419
|
-
rpcMethods(pluginId, ctx, syncCleanups, watched, origins) {
|
|
16420
|
-
return {
|
|
16421
|
-
registerSurface: (surface) => {
|
|
16422
|
-
ctx.registerSurface(sanitizeRpcSurface(pluginId, surface, origins));
|
|
16423
|
-
},
|
|
16424
|
-
registerMenuItem: (item) => {
|
|
16425
|
-
ctx.registerMenuItem(sanitizeRpcMenuItem(item));
|
|
16426
|
-
},
|
|
16427
|
-
registerSettingsSection: (section) => {
|
|
16428
|
-
const built = buildFrameSection({
|
|
16429
|
-
pluginId,
|
|
16430
|
-
wire: sanitizeRpcSettingsSection(pluginId, section),
|
|
16431
|
-
group: this.install.isInstalled(pluginId)
|
|
16432
|
-
? 'settings.group.community'
|
|
16433
|
-
: 'settings.group.plugins',
|
|
16434
|
-
store: this.store,
|
|
16435
|
-
sync: this.sync,
|
|
16436
|
-
notify: (sectionId, values) => this.notifySettings(pluginId, sectionId, values),
|
|
16437
|
-
});
|
|
16438
|
-
syncCleanups.push(built.disposeSync);
|
|
16439
|
-
ctx.registerSettingsSection(built.section);
|
|
16440
|
-
},
|
|
16441
|
-
navigateContent: (path) => ctx.navigateContent(path),
|
|
16442
|
-
openContentTab: (input) => {
|
|
16443
|
-
const sanitized = sanitizeRpcTabInput(input);
|
|
16444
|
-
ctx.openContentTab({
|
|
16445
|
-
...sanitized,
|
|
16446
|
-
onClose: () => this.notifyTabClosed(pluginId, sanitized.path),
|
|
16447
|
-
});
|
|
16448
|
-
},
|
|
16449
|
-
keepContentTab: (path) => ctx.keepContentTab(path),
|
|
16450
|
-
pinContentTab: (path) => ctx.pinContentTab(path),
|
|
16451
|
-
unpinContentTab: (path) => ctx.unpinContentTab(path),
|
|
16452
|
-
closeContentTab: (path) => ctx.closeContentTab(path),
|
|
16453
|
-
revealSurface: (id) => ctx.revealSurface(id),
|
|
16454
|
-
invokeCommand: (id, args) => invokeRpcCommand(ctx, id, args),
|
|
16455
|
-
invocableCommands: () => ctx.invocableCommands(),
|
|
16456
|
-
toast: (input) => ctx.ui.toast(sanitizeRpcToastInput(input)),
|
|
16457
|
-
stateWatch: (key) => this.watchState(pluginId, ctx, watched, key),
|
|
16458
|
-
stateSet: (key, value) => watched.get(key)?.handle.set(value),
|
|
16459
|
-
stateClear: (key) => watched.get(key)?.handle.clear(),
|
|
16460
|
-
stateUnwatch: (key) => {
|
|
16461
|
-
watched.get(key)?.stop();
|
|
16462
|
-
watched.delete(key);
|
|
16463
|
-
},
|
|
16464
|
-
};
|
|
16465
|
-
}
|
|
16466
16653
|
watchState(pluginId, ctx, watched, key) {
|
|
16467
16654
|
if (watched.has(key)) {
|
|
16468
16655
|
return;
|
|
@@ -16471,7 +16658,7 @@ class FramePluginRuntime {
|
|
|
16471
16658
|
const ref = effect(() => {
|
|
16472
16659
|
const value = handle.value();
|
|
16473
16660
|
const loaded = handle.loaded();
|
|
16474
|
-
untracked(() => this.
|
|
16661
|
+
untracked(() => this.notify(pluginId, (remote) => remote.stateChanged(key, value, loaded)));
|
|
16475
16662
|
}, { ...(ngDevMode ? { debugName: "ref" } : /* istanbul ignore next */ {}), injector: this.injector });
|
|
16476
16663
|
watched.set(key, {
|
|
16477
16664
|
handle,
|
|
@@ -16481,32 +16668,12 @@ class FramePluginRuntime {
|
|
|
16481
16668
|
},
|
|
16482
16669
|
});
|
|
16483
16670
|
}
|
|
16484
|
-
|
|
16485
|
-
const instance = this.instances.get(pluginId);
|
|
16486
|
-
if (!instance) {
|
|
16487
|
-
return;
|
|
16488
|
-
}
|
|
16489
|
-
void instance.connection.promise
|
|
16490
|
-
.then((remote) => remote.stateChanged(key, value, loaded))
|
|
16491
|
-
.catch(() => undefined);
|
|
16492
|
-
}
|
|
16493
|
-
notifyTabClosed(pluginId, path) {
|
|
16671
|
+
notify(pluginId, send) {
|
|
16494
16672
|
const instance = this.instances.get(pluginId);
|
|
16495
16673
|
if (!instance) {
|
|
16496
16674
|
return;
|
|
16497
16675
|
}
|
|
16498
|
-
void instance.connection.promise
|
|
16499
|
-
.then((remote) => remote.contentTabClosed(path))
|
|
16500
|
-
.catch(() => undefined);
|
|
16501
|
-
}
|
|
16502
|
-
notifySettings(pluginId, sectionId, values) {
|
|
16503
|
-
const instance = this.instances.get(pluginId);
|
|
16504
|
-
if (!instance) {
|
|
16505
|
-
return;
|
|
16506
|
-
}
|
|
16507
|
-
void instance.connection.promise
|
|
16508
|
-
.then((remote) => remote.settingsChanged(sectionId, values))
|
|
16509
|
-
.catch(() => undefined);
|
|
16676
|
+
void instance.connection.promise.then(send).catch(() => undefined);
|
|
16510
16677
|
}
|
|
16511
16678
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FramePluginRuntime, deps: [], target: i0.ɵɵFactoryTarget.Service });
|
|
16512
16679
|
static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.1.0", ngImport: i0, type: FramePluginRuntime });
|
|
@@ -16650,12 +16817,12 @@ function availableUpdate(installed, entry) {
|
|
|
16650
16817
|
return isNewerVersion(entry.version, installed.version) ? entry : undefined;
|
|
16651
16818
|
}
|
|
16652
16819
|
function addedCapabilities(entry, installed) {
|
|
16653
|
-
const consented = new Set(installed.capabilities
|
|
16820
|
+
const consented = new Set(installed.capabilities);
|
|
16654
16821
|
return (entry.capabilities ?? []).filter((capability) => !consented.has(capability));
|
|
16655
16822
|
}
|
|
16656
16823
|
|
|
16657
16824
|
async function confirmUpdate(deps, entry) {
|
|
16658
|
-
const installed = deps.installs.
|
|
16825
|
+
const installed = deps.installs.byId(entry.id);
|
|
16659
16826
|
if (!installed) {
|
|
16660
16827
|
return;
|
|
16661
16828
|
}
|
|
@@ -16821,7 +16988,7 @@ class PluginStoreDetail {
|
|
|
16821
16988
|
transloco = inject(TranslocoService);
|
|
16822
16989
|
readme = signal(undefined, /* @ts-ignore */
|
|
16823
16990
|
...(ngDevMode ? [{ debugName: "readme" }] : /* istanbul ignore next */ []));
|
|
16824
|
-
update = computed(() => availableUpdate(this.installs.
|
|
16991
|
+
update = computed(() => availableUpdate(this.installs.byId(this.entry().id), this.entry()), /* @ts-ignore */
|
|
16825
16992
|
...(ngDevMode ? [{ debugName: "update" }] : /* istanbul ignore next */ []));
|
|
16826
16993
|
constructor() {
|
|
16827
16994
|
effect(() => {
|
|
@@ -16907,7 +17074,7 @@ class PluginStoreDialog {
|
|
|
16907
17074
|
...(ngDevMode ? [{ debugName: "selectedId" }] : /* istanbul ignore next */ []));
|
|
16908
17075
|
filtered = computed(() => {
|
|
16909
17076
|
const list = (this.entries() ?? []).filter((entry) => matchesQuery([entry.name, entry.author, entry.category, entry.description], this.query()));
|
|
16910
|
-
return list.
|
|
17077
|
+
return list.toSorted((a, b) => (b.downloads ?? 0) - (a.downloads ?? 0) || a.name.localeCompare(b.name));
|
|
16911
17078
|
}, /* @ts-ignore */
|
|
16912
17079
|
...(ngDevMode ? [{ debugName: "filtered" }] : /* istanbul ignore next */ []));
|
|
16913
17080
|
selected = computed(() => this.filtered().find((entry) => entry.id === this.selectedId()), /* @ts-ignore */
|
|
@@ -16919,7 +17086,7 @@ class PluginStoreDialog {
|
|
|
16919
17086
|
void confirmInstall(this.consentDeps, entry);
|
|
16920
17087
|
}
|
|
16921
17088
|
hasUpdate(entry) {
|
|
16922
|
-
return availableUpdate(this.installs.
|
|
17089
|
+
return availableUpdate(this.installs.byId(entry.id), entry) !== undefined;
|
|
16923
17090
|
}
|
|
16924
17091
|
requestUpdate(entry) {
|
|
16925
17092
|
void confirmUpdate(this.consentDeps, entry);
|