@jskit-ai/shell-web 0.1.162 → 0.1.164
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fixtures/adaptive-shell/src/ScreenPage.vue +1 -1
- package/package.json +8 -253
- package/patterns/application-shell/PATTERN.md +66 -0
- package/{templates → patterns/application-shell/example}/expected-existing/src/pages/home/index.vue +8 -8
- package/{templates → patterns/application-shell/example}/src/pages/home/index.vue +8 -8
- package/{templates → patterns/application-shell/example}/src/pages/home/settings/general/index.vue +1 -1
- package/{templates → patterns/application-shell/example}/src/pages/home/settings.vue +8 -8
- package/patterns/page-and-placement/PATTERN.md +76 -0
- package/patterns/page-and-placement/example/packages/main/src/client/providers/MainClientProvider.js +14 -0
- package/patterns/page-and-placement/example/src/components/SyncStatusElement.vue +16 -0
- package/patterns/page-and-placement/example/src/pages/home/reports/activity.vue +8 -0
- package/patterns/page-and-placement/example/src/pages/home/reports/index.vue +7 -0
- package/patterns/page-and-placement/example/src/pages/home/reports/overview.vue +8 -0
- package/patterns/page-and-placement/example/src/pages/home/reports.vue +33 -0
- package/patterns/page-and-placement/example/src/placement.js +52 -0
- package/patterns/page-and-placement/example/src/placementTopology.js +29 -0
- package/src/client/bootstrap/bootstrapPayloadHandlerRegistry.js +30 -42
- package/src/client/bootstrap/index.js +2 -3
- package/src/client/index.js +2 -11
- package/src/client/placement/runtime.js +9 -12
- package/src/client/providers/ShellWebClientProvider.js +95 -129
- package/src/client/requestRecovery/runtime.js +9 -29
- package/src/client/runtime/bootstrapRuntime.js +18 -29
- package/src/test/adaptiveShellSmoke.js +3 -9
- package/test/bootstrapRuntime.test.js +10 -61
- package/test/pageAndPlacementPattern.test.js +38 -0
- package/test/placementRuntime.test.js +36 -50
- package/test/playwrightContract.test.js +2 -12
- package/test/provider.test.js +126 -737
- package/test/settingsPlacementContract.test.js +75 -126
- package/src/server/support/localLinkItemScaffolds.js +0 -80
- package/test/bootstrapClaimContract.test.js +0 -76
- package/test/linkItemScaffoldContract.test.js +0 -248
- /package/{templates → patterns/application-shell/example}/expected-existing/src/App.vue +0 -0
- /package/{templates → patterns/application-shell/example}/expected-existing/src/pages/home.vue +0 -0
- /package/{templates → patterns/application-shell/example}/src/App.vue +0 -0
- /package/{templates → patterns/application-shell/example}/src/components/ShellLayout.vue +0 -0
- /package/{templates → patterns/application-shell/example}/src/components/menus/MenuLinkItem.vue +0 -0
- /package/{templates → patterns/application-shell/example}/src/components/menus/SurfaceAwareMenuLinkItem.vue +0 -0
- /package/{templates → patterns/application-shell/example}/src/components/menus/TabLinkItem.vue +0 -0
- /package/{templates → patterns/application-shell/example}/src/error.js +0 -0
- /package/{templates → patterns/application-shell/example}/src/pages/home/settings/index.vue +0 -0
- /package/{templates → patterns/application-shell/example}/src/pages/home.vue +0 -0
- /package/{templates → patterns/application-shell/example}/src/placement.js +0 -0
- /package/{templates → patterns/application-shell/example}/src/placementTopology.js +0 -0
- /package/{templates → patterns/application-shell/example}/tests/e2e/adaptive-shell.spec.ts +0 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section aria-labelledby="reports-overview-title">
|
|
3
|
+
<h2 id="reports-overview-title" class="text-h5 mb-2">Overview</h2>
|
|
4
|
+
<p class="text-body-1 text-medium-emphasis mb-0">
|
|
5
|
+
Your reading and catalogue summary appears here.
|
|
6
|
+
</p>
|
|
7
|
+
</section>
|
|
8
|
+
</template>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import ShellOutlet from "@jskit-ai/shell-web/client/components/ShellOutlet";
|
|
3
|
+
import { RouterView } from "vue-router";
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<section class="reports-section d-flex flex-column ga-4">
|
|
8
|
+
<header>
|
|
9
|
+
<p class="text-overline text-medium-emphasis mb-1">Insights</p>
|
|
10
|
+
<h1 class="text-h4 font-weight-medium mb-1">Reports</h1>
|
|
11
|
+
<p class="text-body-2 text-medium-emphasis mb-0">
|
|
12
|
+
Review catalogue activity and completion trends.
|
|
13
|
+
</p>
|
|
14
|
+
</header>
|
|
15
|
+
|
|
16
|
+
<nav aria-label="Report sections">
|
|
17
|
+
<v-tabs density="comfortable">
|
|
18
|
+
<ShellOutlet target="reports:section-nav" />
|
|
19
|
+
</v-tabs>
|
|
20
|
+
</nav>
|
|
21
|
+
|
|
22
|
+
<main class="reports-section__content">
|
|
23
|
+
<RouterView />
|
|
24
|
+
</main>
|
|
25
|
+
</section>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<style scoped>
|
|
29
|
+
.reports-section,
|
|
30
|
+
.reports-section__content {
|
|
31
|
+
min-width: 0;
|
|
32
|
+
}
|
|
33
|
+
</style>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { createPlacementRegistry } from "@jskit-ai/shell-web/client/placement";
|
|
2
|
+
|
|
3
|
+
const registry = createPlacementRegistry();
|
|
4
|
+
const { addPlacement } = registry;
|
|
5
|
+
|
|
6
|
+
addPlacement({
|
|
7
|
+
id: "catalogue.reports.link",
|
|
8
|
+
target: "shell.primary-nav",
|
|
9
|
+
kind: "link",
|
|
10
|
+
surfaces: ["home"],
|
|
11
|
+
order: 120,
|
|
12
|
+
props: {
|
|
13
|
+
label: "Reports",
|
|
14
|
+
icon: "mdi-chart-box-outline",
|
|
15
|
+
surface: "home",
|
|
16
|
+
scopedSuffix: "/reports",
|
|
17
|
+
unscopedSuffix: "/reports"
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
for (const [id, label, suffix, order] of [
|
|
22
|
+
["overview", "Overview", "/reports/overview", 100],
|
|
23
|
+
["activity", "Activity", "/reports/activity", 110]
|
|
24
|
+
]) {
|
|
25
|
+
addPlacement({
|
|
26
|
+
id: `catalogue.reports.${id}.link`,
|
|
27
|
+
target: "page.section-nav",
|
|
28
|
+
owner: "reports",
|
|
29
|
+
kind: "link",
|
|
30
|
+
surfaces: ["home"],
|
|
31
|
+
order,
|
|
32
|
+
props: {
|
|
33
|
+
label,
|
|
34
|
+
surface: "home",
|
|
35
|
+
scopedSuffix: suffix,
|
|
36
|
+
unscopedSuffix: suffix
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
addPlacement({
|
|
42
|
+
id: "catalogue.sync-status",
|
|
43
|
+
target: "shell.status",
|
|
44
|
+
kind: "component",
|
|
45
|
+
surfaces: ["home"],
|
|
46
|
+
order: 100,
|
|
47
|
+
componentToken: "local.main.ui.sync-status"
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
export default function getPlacements() {
|
|
51
|
+
return registry.build();
|
|
52
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const placements = [];
|
|
2
|
+
|
|
3
|
+
function addPlacementTopology(value = {}) {
|
|
4
|
+
placements.push(value);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
addPlacementTopology({
|
|
8
|
+
id: "page.section-nav",
|
|
9
|
+
owner: "reports",
|
|
10
|
+
description: "Navigation between report pages.",
|
|
11
|
+
surfaces: ["home"],
|
|
12
|
+
variants: {
|
|
13
|
+
compact: {
|
|
14
|
+
outlet: "reports:section-nav",
|
|
15
|
+
renderers: { link: "local.main.ui.tab-link-item" }
|
|
16
|
+
},
|
|
17
|
+
medium: {
|
|
18
|
+
outlet: "reports:section-nav",
|
|
19
|
+
renderers: { link: "local.main.ui.tab-link-item" }
|
|
20
|
+
},
|
|
21
|
+
expanded: {
|
|
22
|
+
outlet: "reports:section-nav",
|
|
23
|
+
renderers: { link: "local.main.ui.tab-link-item" }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export { addPlacementTopology };
|
|
29
|
+
export default { placements };
|
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
const BOOTSTRAP_PAYLOAD_HANDLER_TAG = "runtime.web-bootstrap.handlers.client";
|
|
2
|
-
|
|
3
|
-
function assertTaggableApp(app, context = "bootstrap payload handler registry") {
|
|
4
|
-
if (!app || typeof app.singleton !== "function" || typeof app.tag !== "function") {
|
|
5
|
-
throw new Error(`${context} requires application singleton()/tag().`);
|
|
6
|
-
}
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function registerBootstrapPayloadHandler(app, token, factory) {
|
|
10
|
-
assertTaggableApp(app, "registerBootstrapPayloadHandler");
|
|
11
|
-
app.singleton(token, factory);
|
|
12
|
-
app.tag(token, BOOTSTRAP_PAYLOAD_HANDLER_TAG);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
1
|
function normalizeBootstrapPayloadHandler(entry) {
|
|
16
2
|
if (typeof entry === "function") {
|
|
17
3
|
return Object.freeze({
|
|
@@ -22,47 +8,49 @@ function normalizeBootstrapPayloadHandler(entry) {
|
|
|
22
8
|
}
|
|
23
9
|
|
|
24
10
|
if (!entry || typeof entry !== "object" || typeof entry.applyBootstrapPayload !== "function") {
|
|
25
|
-
|
|
11
|
+
throw new TypeError("Bootstrap payload handlers require applyBootstrapPayload().");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const handlerId = String(entry.handlerId || "").trim();
|
|
15
|
+
if (!handlerId) {
|
|
16
|
+
throw new TypeError("Bootstrap payload handlers require handlerId.");
|
|
26
17
|
}
|
|
27
18
|
|
|
28
19
|
return Object.freeze({
|
|
29
20
|
...entry,
|
|
30
|
-
handlerId
|
|
21
|
+
handlerId,
|
|
31
22
|
order: Number.isFinite(entry.order) ? Number(entry.order) : 0
|
|
32
23
|
});
|
|
33
24
|
}
|
|
34
25
|
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
return [];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const rawEntries = scope.resolveTag(BOOTSTRAP_PAYLOAD_HANDLER_TAG);
|
|
41
|
-
const queue = Array.isArray(rawEntries) ? [...rawEntries] : [rawEntries];
|
|
42
|
-
const entries = [];
|
|
26
|
+
function createBootstrapPayloadHandlerRegistry() {
|
|
27
|
+
const handlers = new Map();
|
|
43
28
|
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
const normalized = normalizeBootstrapPayloadHandler(entry);
|
|
51
|
-
if (normalized) {
|
|
52
|
-
entries.push(normalized);
|
|
29
|
+
function register(entry) {
|
|
30
|
+
const handler = normalizeBootstrapPayloadHandler(entry);
|
|
31
|
+
if (handlers.has(handler.handlerId)) {
|
|
32
|
+
throw new Error(`Bootstrap payload handler "${handler.handlerId}" is duplicated.`);
|
|
53
33
|
}
|
|
34
|
+
handlers.set(handler.handlerId, handler);
|
|
35
|
+
return api;
|
|
54
36
|
}
|
|
55
37
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
38
|
+
function list() {
|
|
39
|
+
return Object.freeze(
|
|
40
|
+
[...handlers.values()].sort((left, right) => {
|
|
41
|
+
if (left.order !== right.order) {
|
|
42
|
+
return left.order - right.order;
|
|
43
|
+
}
|
|
44
|
+
return left.handlerId.localeCompare(right.handlerId);
|
|
45
|
+
})
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const api = Object.freeze({ register, list });
|
|
50
|
+
return api;
|
|
62
51
|
}
|
|
63
52
|
|
|
64
53
|
export {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
resolveBootstrapPayloadHandlers
|
|
54
|
+
createBootstrapPayloadHandlerRegistry,
|
|
55
|
+
normalizeBootstrapPayloadHandler
|
|
68
56
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
resolveBootstrapPayloadHandlers
|
|
2
|
+
createBootstrapPayloadHandlerRegistry,
|
|
3
|
+
normalizeBootstrapPayloadHandler
|
|
5
4
|
} from "./bootstrapPayloadHandlerRegistry.js";
|
|
6
5
|
export { resolveBootstrapErrorStatusCode } from "./bootstrapErrorStatus.js";
|
package/src/client/index.js
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ShellWebClientProvider
|
|
3
|
-
} from "./providers/ShellWebClientProvider.js";
|
|
4
|
-
|
|
5
1
|
export {
|
|
6
2
|
ShellWebClientProvider
|
|
7
3
|
} from "./providers/ShellWebClientProvider.js";
|
|
@@ -28,11 +24,6 @@ export {
|
|
|
28
24
|
useShellRequestRecoveryRuntime
|
|
29
25
|
} from "./requestRecovery/index.js";
|
|
30
26
|
export {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
resolveBootstrapPayloadHandlers
|
|
27
|
+
createBootstrapPayloadHandlerRegistry,
|
|
28
|
+
normalizeBootstrapPayloadHandler
|
|
34
29
|
} from "./bootstrap/index.js";
|
|
35
|
-
|
|
36
|
-
const clientProviders = Object.freeze([ShellWebClientProvider]);
|
|
37
|
-
|
|
38
|
-
export { clientProviders };
|
|
@@ -207,8 +207,7 @@ function resolveRenderablePlacement({
|
|
|
207
207
|
});
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
function resolveContextContributors(
|
|
211
|
-
const contributors = app.resolveTag("web-placement.context.client");
|
|
210
|
+
function resolveContextContributors(contributors, baseContext = {}, logger) {
|
|
212
211
|
let merged = {};
|
|
213
212
|
|
|
214
213
|
for (const contributor of ensureArray(contributors)) {
|
|
@@ -235,7 +234,7 @@ function resolveContextContributors(app, baseContext = {}, logger) {
|
|
|
235
234
|
}
|
|
236
235
|
|
|
237
236
|
function resolvePlacementComponent(
|
|
238
|
-
|
|
237
|
+
components,
|
|
239
238
|
placement,
|
|
240
239
|
logger,
|
|
241
240
|
missingTokens,
|
|
@@ -251,7 +250,7 @@ function resolvePlacementComponent(
|
|
|
251
250
|
return null;
|
|
252
251
|
}
|
|
253
252
|
|
|
254
|
-
if (!
|
|
253
|
+
if (!components.has(componentToken)) {
|
|
255
254
|
if (!missingTokens.has(componentToken)) {
|
|
256
255
|
missingTokens.add(componentToken);
|
|
257
256
|
logger.warn(
|
|
@@ -267,7 +266,7 @@ function resolvePlacementComponent(
|
|
|
267
266
|
|
|
268
267
|
let component = null;
|
|
269
268
|
try {
|
|
270
|
-
component =
|
|
269
|
+
component = components.get(componentToken);
|
|
271
270
|
} catch (error) {
|
|
272
271
|
if (!failedTokens.has(componentToken)) {
|
|
273
272
|
failedTokens.add(componentToken);
|
|
@@ -319,9 +318,9 @@ function shouldIncludePlacement(placement, placementContext, logger) {
|
|
|
319
318
|
}
|
|
320
319
|
}
|
|
321
320
|
|
|
322
|
-
function createWebPlacementRuntime({
|
|
323
|
-
if (!
|
|
324
|
-
throw new Error("createWebPlacementRuntime requires
|
|
321
|
+
function createWebPlacementRuntime({ components, contextContributors = [], logger = null } = {}) {
|
|
322
|
+
if (!components || typeof components.has !== "function" || typeof components.get !== "function") {
|
|
323
|
+
throw new Error("createWebPlacementRuntime requires a client component registry.");
|
|
325
324
|
}
|
|
326
325
|
|
|
327
326
|
const runtimeLogger = createRuntimeLogger(logger);
|
|
@@ -438,9 +437,8 @@ function createWebPlacementRuntime({ app, logger = null } = {}) {
|
|
|
438
437
|
const baseContext = isRecord(context) ? { ...context } : {};
|
|
439
438
|
const contextFromRuntime = isRecord(sharedContext) ? sharedContext : {};
|
|
440
439
|
const contextFromContributors = resolveContextContributors(
|
|
441
|
-
|
|
440
|
+
contextContributors,
|
|
442
441
|
{
|
|
443
|
-
app,
|
|
444
442
|
surface: normalizedSurface,
|
|
445
443
|
target: normalizedTarget,
|
|
446
444
|
layoutClass: normalizedLayoutClass,
|
|
@@ -455,7 +453,6 @@ function createWebPlacementRuntime({ app, logger = null } = {}) {
|
|
|
455
453
|
...contextFromContributors,
|
|
456
454
|
...contextFromRuntime,
|
|
457
455
|
...baseContext,
|
|
458
|
-
app,
|
|
459
456
|
surface: normalizedSurface,
|
|
460
457
|
target: normalizedTarget,
|
|
461
458
|
layoutClass: normalizedLayoutClass
|
|
@@ -503,7 +500,7 @@ function createWebPlacementRuntime({ app, logger = null } = {}) {
|
|
|
503
500
|
}
|
|
504
501
|
|
|
505
502
|
const component = resolvePlacementComponent(
|
|
506
|
-
|
|
503
|
+
components,
|
|
507
504
|
renderablePlacement,
|
|
508
505
|
runtimeLogger,
|
|
509
506
|
missingTokens,
|