@phis/ui 0.4.2 → 0.4.3
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/components/cms/phi-cms-layout-renderer.js +4 -3
- package/components/cms/phi-cms-page-render-scope.d.ts +4 -5
- package/components/cms/phi-cms-page-render-scope.js +43 -22
- package/components/cms/phi-cms-render-diagnostic-client.js +7 -3
- package/components/cms/phi-cms-render-error-boundary.js +2 -1
- package/components/controls/phi-bullet-list-control.js +31 -2
- package/components/controls/phi-ribbon-control.d.ts +29 -0
- package/components/controls/phi-ribbon-control.js +53 -0
- package/components/controls/phi-table-control.d.ts +3 -0
- package/components/controls/phi-table-control.js +13 -1
- package/components/controls/phi-tree-control.js +3 -3
- package/components/forms/shared-form-provider-registry.js +1 -4
- package/components/media/asset-collection-runtime.js +6 -16
- package/components/media/media-font-metrics.d.ts +3 -0
- package/components/media/media-font-metrics.js +43 -0
- package/components/media/phi-area-upload-widget.js +53 -97
- package/components/media/phi-image-preview-data.d.ts +13 -0
- package/components/media/phi-image-preview-data.js +184 -9
- package/components/media/phi-media-picker-binding.js +16 -13
- package/components/media/phi-media-upload.d.ts +15 -8
- package/components/media/phi-media-upload.js +18 -9
- package/components/media/phi-site-font-assets.js +7 -8
- package/components/overlays/phi-overlay-container-client.js +16 -1
- package/components/regions/presets/phi-settings-page-shell-tree.js +48 -1
- package/components/runtime/runtime-controller-materialization.js +12 -1
- package/components/state/scoped-state-store.js +20 -3
- package/components/tables/client/phi-table-binding.d.ts +2 -1
- package/components/tables/client/phi-table-binding.js +45 -21
- package/components/trees/client/phi-tree-binding.js +19 -11
- package/components/widgets/client/bullet-list-entries-editor.d.ts +26 -0
- package/components/widgets/client/bullet-list-entries-editor.js +103 -0
- package/components/widgets/config/choice-shared.js +12 -13
- package/components/widgets/config/command-buttons.d.ts +5 -1
- package/components/widgets/config/command-buttons.js +38 -81
- package/components/widgets/config/control-signal-config.d.ts +3 -1
- package/components/widgets/config/control-signal-config.js +19 -17
- package/components/widgets/config/image-source-parser.d.ts +1 -0
- package/components/widgets/config/image-source-parser.js +7 -8
- package/components/widgets/config/mask.d.ts +6 -1
- package/components/widgets/config/mask.js +24 -1
- package/components/widgets/config/parser-primitives.js +72 -0
- package/components/widgets/config/stack-tabs.d.ts +1 -1
- package/components/widgets/config/stack-tabs.js +2 -3
- package/components/widgets/config/strict-config-reader.d.ts +40 -0
- package/components/widgets/config/strict-config-reader.js +174 -0
- package/components/widgets/label-sets/effects.js +2 -0
- package/components/widgets/label-types/effects.d.ts +1 -0
- package/components/widgets/label-types/effects.js +1 -0
- package/components/widgets/navigation-descriptor-resolver.server.js +20 -8
- package/components/widgets/shared/bullet-list-vocabulary.d.ts +17 -1
- package/components/widgets/shared/bullet-list-vocabulary.js +19 -2
- package/components/widgets/shared/card-body-client.d.ts +16 -4
- package/components/widgets/shared/card-body-client.js +81 -24
- package/components/widgets/shared/card-vocabulary.d.ts +21 -4
- package/components/widgets/shared/card-vocabulary.js +28 -2
- package/components/widgets/signals/control-signal-capabilities.d.ts +0 -18
- package/components/widgets/signals/control-signal-capabilities.js +10 -22
- package/components/workspace/catalog-store.d.ts +4 -3
- package/components/workspace/catalog-store.js +6 -5
- package/gateway/dashboard-cards-route.js +1 -0
- package/gateway/internal-references.d.ts +6 -1
- package/gateway/internal-references.js +48 -23
- package/gateway/site-cms-write-validation.d.ts +9 -0
- package/gateway/site-cms-write-validation.js +78 -0
- package/gateway/site-proxy.js +19 -3
- package/helpers/renderable-block-serialization.js +1 -1
- package/next/config.d.mts +3 -0
- package/next/config.mjs +28 -1
- package/package.json +2 -2
- package/plugins/runtime/phi-slot-child-viewport-effects-observer.js +5 -1
- package/plugins/runtime-modules/accounting/presets.d.ts +9 -0
- package/plugins/runtime-modules/accounting/presets.js +37 -0
- package/plugins/runtime-modules/accounting/server.d.ts +1 -4
- package/plugins/runtime-modules/accounting/server.js +4 -7
- package/plugins/runtime-modules/app/presets.d.ts +18 -0
- package/plugins/runtime-modules/app/presets.js +67 -0
- package/plugins/runtime-modules/app/server.d.ts +1 -4
- package/plugins/runtime-modules/app/server.js +4 -7
- package/plugins/runtime-modules/asset/controller/runtime.js +2 -2
- package/plugins/runtime-modules/asset/services/collection.js +9 -1
- package/plugins/runtime-modules/asset/services/options.js +2 -2
- package/plugins/runtime-modules/builder/area-settings-answer.js +2 -1
- package/plugins/runtime-modules/builder/authoring-canvas.js +6 -6
- package/plugins/runtime-modules/builder/cards.js +3 -5
- package/plugins/runtime-modules/builder/clients/builder-region-overrides.js +3 -3
- package/plugins/runtime-modules/builder/clients/canvas-widget.js +15 -54
- package/plugins/runtime-modules/builder/clients/inspector-config-field.d.ts +4 -1
- package/plugins/runtime-modules/builder/clients/inspector-config-field.js +5 -34
- package/plugins/runtime-modules/builder/clients/inspector-link-target-field.js +1 -1
- package/plugins/runtime-modules/builder/clients/inspector-section-widget.js +8 -8
- package/plugins/runtime-modules/builder/clients/inspector-title.js +6 -6
- package/plugins/runtime-modules/builder/clients/layout-inspector.d.ts +1 -1
- package/plugins/runtime-modules/builder/clients/layout-inspector.js +11 -25
- package/plugins/runtime-modules/builder/clients/mode-switch.js +1 -1
- package/plugins/runtime-modules/builder/clients/pages-header.js +11 -11
- package/plugins/runtime-modules/builder/clients/pages-workspace.js +1 -1
- package/plugins/runtime-modules/builder/clients/region-visibility-fields.js +1 -1
- package/plugins/runtime-modules/builder/clients/runtime-module-authoring-boundary.js +1 -1
- package/plugins/runtime-modules/builder/clients/shells-workspace.js +15 -31
- package/plugins/runtime-modules/builder/clients/structure-canvas.js +1 -1
- package/plugins/runtime-modules/builder/clients/use-region-draft-hydration.d.ts +26 -0
- package/plugins/runtime-modules/builder/clients/use-region-draft-hydration.js +36 -0
- package/plugins/runtime-modules/builder/clients/widget-effects-editor.js +1 -1
- package/plugins/runtime-modules/builder/clients/widget-inspector.js +1 -0
- package/plugins/runtime-modules/builder/clients/workspace-scope-boundary.js +2 -2
- package/plugins/runtime-modules/builder/cms-instance-allocator.js +8 -28
- package/plugins/runtime-modules/builder/controller/client.d.ts +0 -15
- package/plugins/runtime-modules/builder/controller/definition.d.ts +0 -15
- package/plugins/runtime-modules/builder/controller/definition.js +0 -8
- package/plugins/runtime-modules/builder/controller/draft-command-controller.d.ts +1 -2
- package/plugins/runtime-modules/builder/controller/draft-command-controller.js +11 -30
- package/plugins/runtime-modules/builder/controller/draft-status-controller.js +15 -2
- package/plugins/runtime-modules/builder/controller/inspector-controller.d.ts +1 -2
- package/plugins/runtime-modules/builder/controller/inspector-controller.js +4 -7
- package/plugins/runtime-modules/builder/controller/page-controller.d.ts +1 -2
- package/plugins/runtime-modules/builder/controller/page-controller.js +16 -68
- package/plugins/runtime-modules/builder/controller/server-definition.d.ts +0 -15
- package/plugins/runtime-modules/builder/controller/signal-wiring-controller.d.ts +2 -5
- package/plugins/runtime-modules/builder/controller/signal-wiring-controller.js +6 -50
- package/plugins/runtime-modules/builder/controller/workspace-controller.d.ts +2 -3
- package/plugins/runtime-modules/builder/controller/workspace-controller.js +89 -153
- package/plugins/runtime-modules/builder/data-providers.d.ts +1 -0
- package/plugins/runtime-modules/builder/data-providers.js +1 -0
- package/plugins/runtime-modules/builder/definition.d.ts +1 -0
- package/plugins/runtime-modules/builder/developer-workspace-store.d.ts +64 -30
- package/plugins/runtime-modules/builder/developer-workspace-store.js +255 -61
- package/plugins/runtime-modules/builder/edit-scaffold-drawer.js +1 -1
- package/plugins/runtime-modules/builder/history.d.ts +3 -1
- package/plugins/runtime-modules/builder/options-providers.js +3 -2
- package/plugins/runtime-modules/builder/page-catalog-client.js +19 -7
- package/plugins/runtime-modules/builder/page-meta-form.js +1 -0
- package/plugins/runtime-modules/builder/page-references-provider.js +1 -1
- package/plugins/runtime-modules/builder/persistence.js +33 -69
- package/plugins/runtime-modules/builder/public-route-collisions.d.ts +2 -2
- package/plugins/runtime-modules/builder/public-route-collisions.js +6 -19
- package/plugins/runtime-modules/builder/render-root-node-preview.server.js +20 -3
- package/plugins/runtime-modules/builder/render-root-node-scaffold.js +3 -3
- package/plugins/runtime-modules/builder/runtime-module-selection.d.ts +2 -12
- package/plugins/runtime-modules/builder/runtime-module-selection.js +16 -42
- package/plugins/runtime-modules/builder/services/navigation-table.js +9 -15
- package/plugins/runtime-modules/builder/services/page-source-tree.js +8 -8
- package/plugins/runtime-modules/builder/services/runtime-modules-table.js +10 -10
- package/plugins/runtime-modules/builder/services/signal-routes-table.js +3 -3
- package/plugins/runtime-modules/builder/signal-route-selection.d.ts +1 -2
- package/plugins/runtime-modules/builder/signal-route-selection.js +2 -2
- package/plugins/runtime-modules/builder/signal-wiring-options.js +3 -2
- package/plugins/runtime-modules/builder/structure-dnd.js +8 -61
- package/plugins/runtime-modules/builder/trees/phi-default-builder-area-preset-tree.js +13 -10
- package/plugins/runtime-modules/builder/widgets/structure-region/built-in.js +12 -18
- package/plugins/runtime-modules/builder/workspace-catalog-actions.d.ts +55 -0
- package/plugins/runtime-modules/builder/workspace-catalog-actions.js +142 -0
- package/plugins/runtime-modules/client-manifests/common.js +1 -1
- package/plugins/runtime-modules/{runtime-calendar-adapters.js → core/calendar-adapters.js} +2 -2
- package/plugins/runtime-modules/{client-calendar-adapters/core.js → core/client-calendar-adapters.js} +1 -1
- package/plugins/runtime-modules/core/content-entries/table.js +1 -1
- package/plugins/runtime-modules/core/data-providers.d.ts +3 -0
- package/plugins/runtime-modules/core/data-providers.js +2 -1
- package/plugins/runtime-modules/core/definition.d.ts +3 -0
- package/plugins/runtime-modules/core/definition.js +1 -1
- package/plugins/runtime-modules/core/messaging/data-providers.d.ts +1 -0
- package/plugins/runtime-modules/core/messaging/data-providers.js +1 -0
- package/plugins/runtime-modules/core/messaging/thread-inbox-table.js +1 -1
- package/plugins/runtime-modules/core/server.js +1 -1
- package/plugins/runtime-modules/core/themes.d.ts +44 -0
- package/plugins/runtime-modules/core/themes.js +12 -0
- package/plugins/runtime-modules/core/widgets/card/collection-view-binding.js +25 -9
- package/plugins/runtime-modules/core/widgets/card/config.d.ts +296 -254
- package/plugins/runtime-modules/core/widgets/card/config.js +52 -3
- package/plugins/runtime-modules/core/widgets/card/editor.js +30 -3
- package/plugins/runtime-modules/core/widgets/card/server.d.ts +3 -0
- package/plugins/runtime-modules/core/widgets/card/server.js +31 -3
- package/plugins/runtime-modules/core/widgets/collection-view/asset-binding.js +7 -6
- package/plugins/runtime-modules/core/widgets/collection-view/config.js +9 -2
- package/plugins/runtime-modules/core/widgets/description/config.d.ts +1 -25
- package/plugins/runtime-modules/core/widgets/description/editor.js +4 -61
- package/plugins/runtime-modules/core/widgets/entry-list/config.d.ts +1 -25
- package/plugins/runtime-modules/core/widgets/gallery/config.d.ts +1 -25
- package/plugins/runtime-modules/core/widgets/image/config.js +2 -2
- package/plugins/runtime-modules/core/widgets/markdown/editor.js +2 -3
- package/plugins/runtime-modules/core/widgets/result/config.d.ts +4 -25
- package/plugins/runtime-modules/core/widgets/result/config.js +2 -0
- package/plugins/runtime-modules/core/widgets/result/server.js +25 -19
- package/plugins/runtime-modules/core/widgets/simple-text/config.d.ts +1 -25
- package/plugins/runtime-modules/core/widgets/slot-upload/client.js +11 -2
- package/plugins/runtime-modules/core/widgets/table/client.js +8 -4
- package/plugins/runtime-modules/core/widgets/table/config.d.ts +3 -83
- package/plugins/runtime-modules/core/widgets/table/config.js +302 -452
- package/plugins/runtime-modules/core/widgets/tree/config.js +5 -2
- package/plugins/runtime-modules/dashboard/card-rows.d.ts +5 -0
- package/plugins/runtime-modules/dashboard/card-rows.js +59 -0
- package/plugins/runtime-modules/dashboard/clients/dashboard-card-view.js +4 -6
- package/plugins/runtime-modules/dashboard/services/collection.js +17 -7
- package/plugins/runtime-modules/groups/data-providers.d.ts +2 -0
- package/plugins/runtime-modules/groups/data-providers.js +3 -3
- package/plugins/runtime-modules/groups/definition.d.ts +2 -0
- package/plugins/runtime-modules/localization/data-providers.d.ts +3 -0
- package/plugins/runtime-modules/localization/data-providers.js +2 -0
- package/plugins/runtime-modules/localization/definition.d.ts +3 -0
- package/plugins/runtime-modules/localization/services/table.js +3 -7
- package/plugins/runtime-modules/observability/data-providers.d.ts +1 -0
- package/plugins/runtime-modules/observability/data-providers.js +1 -0
- package/plugins/runtime-modules/observability/definition.d.ts +1 -0
- package/plugins/runtime-modules/observability/module.d.ts +1 -0
- package/plugins/runtime-modules/observability/services/table.js +1 -1
- package/plugins/runtime-modules/phi-default-site-area-preset-tree.js +2 -105
- package/plugins/runtime-modules/public/presets.d.ts +115 -0
- package/plugins/runtime-modules/{area-base-presets.js → public/presets.js} +19 -119
- package/plugins/runtime-modules/public/server.d.ts +1 -4
- package/plugins/runtime-modules/public/server.js +4 -7
- package/plugins/runtime-modules/public/trees/phi-default-pub-error-page-tree.js +7 -8
- package/plugins/runtime-modules/public-base-page-references.d.ts +1 -1
- package/plugins/runtime-modules/public-base-page-references.js +1 -1
- package/plugins/runtime-modules/revisions/controller/client.js +11 -11
- package/plugins/runtime-modules/revisions/services/table.js +3 -3
- package/plugins/runtime-modules/theme/trees/phi-default-builder-theme-page-tree.js +22 -0
- package/plugins/runtime-modules/theme/widgets/brand-controls/client.d.ts +2 -1
- package/plugins/runtime-modules/theme/widgets/brand-controls/client.js +93 -58
- package/plugins/runtime-modules/theme/widgets/brand-controls/config.d.ts +2 -1
- package/plugins/runtime-modules/theme/widgets/brand-controls/config.js +23 -22
- package/plugins/runtime-modules/theme/widgets/brand-controls/plugin.js +1 -1
- package/plugins/runtime-modules/theme-descriptors.js +1 -1
- package/plugins/runtime-modules/user-management/data-providers.d.ts +2 -0
- package/plugins/runtime-modules/user-management/data-providers.js +1 -0
- package/plugins/runtime-modules/user-management/definition.d.ts +2 -0
- package/styles/control-shape.css +11 -11
- package/styles/controls.css +3 -3
- package/styles/layout.css +16 -7
- package/types/cms-config.js +142 -185
- package/types/cms-plugins.d.ts +4 -11
- package/types/core-widget-placements.d.ts +14 -3
- package/types/dashboard-cards.d.ts +9 -0
- package/types/media.d.ts +0 -3
- package/types/renderable-block.d.ts +6 -1
- package/types/table-widget.d.ts +9 -0
- package/types/table-widget.js +20 -0
- package/types.d.ts +1 -1
- package/widget-config.d.ts +2 -0
- package/widget-config.js +6 -0
- package/plugins/runtime-modules/area-base-presets.d.ts +0 -139
- /package/plugins/runtime-modules/{runtime-calendar-adapters.d.ts → core/calendar-adapters.d.ts} +0 -0
- /package/plugins/runtime-modules/{client-calendar-adapters/core.d.ts → core/client-calendar-adapters.d.ts} +0 -0
|
@@ -11,7 +11,12 @@ export type PhiResolvedInternalReferences = {
|
|
|
11
11
|
pages: readonly PhiResolvedPageReference[];
|
|
12
12
|
assets: ReadonlyMap<number, PhiPublicMediaAssetReference>;
|
|
13
13
|
};
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* One Asset of the public reference projection, as the server writes it; a shape it does not match throws.
|
|
16
|
+
*
|
|
17
|
+
* Every field, not five of them: what passed here went straight into a rendered page under a type that
|
|
18
|
+
* promised a focal rectangle and font metrics nobody had looked at.
|
|
19
|
+
*/
|
|
15
20
|
export declare function readPhiPublicMediaAssetReference(entry: unknown): PhiPublicMediaAssetReference;
|
|
16
21
|
/**
|
|
17
22
|
* Resolves both internal reference kinds, together with whatever else asks in the same turn.
|
|
@@ -1,37 +1,62 @@
|
|
|
1
|
-
import { isPhiRecord } from "../helpers/is-record";
|
|
2
1
|
import "server-only";
|
|
3
2
|
import { buildApiHeaders, buildApiUrl } from "../helpers/site-api";
|
|
3
|
+
import { PhiMediaKind } from "../constants/media";
|
|
4
|
+
import { readPhiFontMetricsFields } from "../components/media/media-font-metrics";
|
|
5
|
+
import { createPhiResponseReader, requirePhiField } from "../components/widgets/config/strict-config-reader";
|
|
4
6
|
import { readPhiPageReference } from "../types/references";
|
|
5
7
|
import { throwPhiCmsGatewayError } from "./errors";
|
|
6
8
|
import { peekPhiSiteReadCache, writePhiSiteReadCache } from "./site-read-cache";
|
|
7
9
|
function readResolvedPage(entry) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const value = entry;
|
|
12
|
-
const parsed = readPhiPageReference(value.reference);
|
|
13
|
-
if (!parsed || (value.path !== null && typeof value.path !== "string") || typeof value.deleted !== "boolean") {
|
|
14
|
-
throw new Error("Invalid internal Page reference projection.");
|
|
15
|
-
}
|
|
10
|
+
const reader = createPhiResponseReader(entry, "The internal reference projection");
|
|
11
|
+
const parsed = readPhiPageReference(reader.record.reference) ?? reader.fail("reference", "a Page reference");
|
|
12
|
+
const path = reader.record.path;
|
|
16
13
|
return {
|
|
17
14
|
reference: parsed.reference,
|
|
18
|
-
path:
|
|
19
|
-
deleted:
|
|
20
|
-
targetKind:
|
|
15
|
+
path: path === null ? null : requirePhiField(reader, "path", "a path or null", reader.string),
|
|
16
|
+
deleted: requirePhiField(reader, "deleted", "true or false", reader.boolean),
|
|
17
|
+
targetKind: requirePhiField(reader, "targetKind", '"site" or "module"', (key) => reader.oneOf(key, ["site", "module"])),
|
|
21
18
|
};
|
|
22
19
|
}
|
|
23
|
-
|
|
20
|
+
const PHI_MEDIA_KINDS = Object.values(PhiMediaKind);
|
|
21
|
+
/**
|
|
22
|
+
* One Asset of the public reference projection, as the server writes it; a shape it does not match throws.
|
|
23
|
+
*
|
|
24
|
+
* Every field, not five of them: what passed here went straight into a rendered page under a type that
|
|
25
|
+
* promised a focal rectangle and font metrics nobody had looked at.
|
|
26
|
+
*/
|
|
24
27
|
export function readPhiPublicMediaAssetReference(entry) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
28
|
+
const reader = createPhiResponseReader(entry, "The internal reference projection");
|
|
29
|
+
const integer = (key) => requirePhiField(reader, key, "a whole number", reader.integer);
|
|
30
|
+
const text = (key) => requirePhiField(reader, key, "a text", reader.string);
|
|
31
|
+
const nullableNumber = (key) => reader.number(key) ?? null;
|
|
32
|
+
const nullableText = (key) => reader.string(key) ?? null;
|
|
33
|
+
const id = integer("id");
|
|
34
|
+
if (id <= 0)
|
|
35
|
+
reader.fail("id", "a positive whole number");
|
|
36
|
+
return {
|
|
37
|
+
id,
|
|
38
|
+
deliveryUrl: text("deliveryUrl"),
|
|
39
|
+
deliveryRevision: integer("deliveryRevision"),
|
|
40
|
+
variantVersion: reader.integer("variantVersion") ?? null,
|
|
41
|
+
contentType: text("contentType"),
|
|
42
|
+
kind: requirePhiField(reader, "kind", "a media kind", (key) => reader.oneOf(key, PHI_MEDIA_KINDS)),
|
|
43
|
+
originalName: text("originalName"),
|
|
44
|
+
width: nullableNumber("width"),
|
|
45
|
+
height: nullableNumber("height"),
|
|
46
|
+
blurDataUrl: nullableText("blurDataUrl"),
|
|
47
|
+
title: nullableText("title"),
|
|
48
|
+
altText: nullableText("altText"),
|
|
49
|
+
thumbnailUrl: nullableText("thumbnailUrl"),
|
|
50
|
+
previewUrl: nullableText("previewUrl"),
|
|
51
|
+
focalRect: reader.object("focalRect", (rect) => {
|
|
52
|
+
const fraction = (key) => {
|
|
53
|
+
const value = requirePhiField(rect, key, "a number from 0 to 1", rect.number);
|
|
54
|
+
return value >= 0 && value <= 1 ? value : rect.fail(key, "a number from 0 to 1");
|
|
55
|
+
};
|
|
56
|
+
return { x: fraction("x"), y: fraction("y"), width: fraction("width"), height: fraction("height") };
|
|
57
|
+
}) ?? null,
|
|
58
|
+
font: reader.object("font", readPhiFontMetricsFields) ?? null,
|
|
59
|
+
};
|
|
35
60
|
}
|
|
36
61
|
/** What the server takes per list in one request (`MAX_REFERENCES_PER_REQUEST`). */
|
|
37
62
|
const PHI_REFERENCES_PER_REQUEST = 256;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import "server-only";
|
|
2
|
+
import type { NextRequest } from "next/server";
|
|
3
|
+
import type { PhiSiteAreaBridgeLoader } from "./site-area-bridges";
|
|
4
|
+
/**
|
|
5
|
+
* The 422 for a write that carries an unreadable Widget config, or `null` to let it through.
|
|
6
|
+
*
|
|
7
|
+
* The body is read from a clone, so the request still goes upstream untouched.
|
|
8
|
+
*/
|
|
9
|
+
export declare function validatePhiCmsStructureWrite(request: NextRequest, loadAreaBridge: PhiSiteAreaBridgeLoader): Promise<Response | null>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import "server-only";
|
|
2
|
+
import { isPhiCmsAreaKey } from "../constants/cms-areas";
|
|
3
|
+
import { isPhiRecord } from "../helpers/is-record";
|
|
4
|
+
import { buildPhiRuntimeModuleWidgetType } from "../plugins/runtime-modules/contracts";
|
|
5
|
+
import { isPhiConfigError } from "../components/widgets/config/strict-config-reader";
|
|
6
|
+
import { jsonResponse } from "./route-handler-helpers";
|
|
7
|
+
function buildWidgetDefinitionsByType(catalog) {
|
|
8
|
+
const definitions = new Map();
|
|
9
|
+
for (const entry of catalog.values()) {
|
|
10
|
+
for (const widget of entry.widgets) {
|
|
11
|
+
definitions.set(buildPhiRuntimeModuleWidgetType(widget), widget.definition);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return definitions;
|
|
15
|
+
}
|
|
16
|
+
function findConfigIssue(body, definitions) {
|
|
17
|
+
const pending = [body];
|
|
18
|
+
while (pending.length > 0) {
|
|
19
|
+
const value = pending.pop();
|
|
20
|
+
if (Array.isArray(value)) {
|
|
21
|
+
pending.push(...value);
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (!isPhiRecord(value))
|
|
25
|
+
continue;
|
|
26
|
+
const widgetType = value.widgetType;
|
|
27
|
+
const config = value.config;
|
|
28
|
+
if (typeof widgetType === "string" && isPhiRecord(config)) {
|
|
29
|
+
const definition = definitions.get(widgetType);
|
|
30
|
+
if (definition) {
|
|
31
|
+
try {
|
|
32
|
+
definition.parseConfig(config);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
/*
|
|
36
|
+
* Any error, not only the strict reader's: a Widget from another package may refuse its config
|
|
37
|
+
* in its own words, and the live page leaves that Widget out just the same. Only the strict
|
|
38
|
+
* reader can say which field it was.
|
|
39
|
+
*/
|
|
40
|
+
return {
|
|
41
|
+
widgetType,
|
|
42
|
+
nodeId: value.id ?? null,
|
|
43
|
+
path: isPhiConfigError(error) ? error.path : null,
|
|
44
|
+
message: error instanceof Error ? error.message : String(error),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
pending.push(...Object.values(value));
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The 422 for a write that carries an unreadable Widget config, or `null` to let it through.
|
|
55
|
+
*
|
|
56
|
+
* The body is read from a clone, so the request still goes upstream untouched.
|
|
57
|
+
*/
|
|
58
|
+
export async function validatePhiCmsStructureWrite(request, loadAreaBridge) {
|
|
59
|
+
const body = await request.clone().json().catch(() => null);
|
|
60
|
+
if (!isPhiRecord(body))
|
|
61
|
+
return null;
|
|
62
|
+
const area = typeof body.area === "string" ? body.area.trim().toLowerCase() : "";
|
|
63
|
+
if (!isPhiCmsAreaKey(area))
|
|
64
|
+
return null;
|
|
65
|
+
const bridge = await loadAreaBridge(area);
|
|
66
|
+
if (!bridge)
|
|
67
|
+
return null;
|
|
68
|
+
const issue = findConfigIssue(body, buildWidgetDefinitionsByType(bridge.runtimeModuleCatalog));
|
|
69
|
+
if (!issue)
|
|
70
|
+
return null;
|
|
71
|
+
return jsonResponse({
|
|
72
|
+
error: `${issue.widgetType}: ${issue.message}`,
|
|
73
|
+
code: "invalid-config",
|
|
74
|
+
widgetType: issue.widgetType,
|
|
75
|
+
nodeId: issue.nodeId,
|
|
76
|
+
field: issue.path,
|
|
77
|
+
}, 422);
|
|
78
|
+
}
|
package/gateway/site-proxy.js
CHANGED
|
@@ -76,6 +76,22 @@ function clearingReadCacheOnWrite(handler) {
|
|
|
76
76
|
return response;
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* A CMS structure write is checked against the Widgets' own parsers before it goes upstream
|
|
81
|
+
* (`site-cms-write-validation.ts`), so a config no Widget can read is refused with the field it is in
|
|
82
|
+
* rather than stored and left out of the live page. Loaded with the first such write.
|
|
83
|
+
*/
|
|
84
|
+
function validatingCmsStructureWrite(handler, loadAreaBridge) {
|
|
85
|
+
return async (request, context) => {
|
|
86
|
+
if ((await context.params).path?.[0] === "cms") {
|
|
87
|
+
const { validatePhiCmsStructureWrite } = await import("./site-cms-write-validation");
|
|
88
|
+
const refusal = await validatePhiCmsStructureWrite(request, loadAreaBridge);
|
|
89
|
+
if (refusal)
|
|
90
|
+
return refusal;
|
|
91
|
+
}
|
|
92
|
+
return handler(request, context);
|
|
93
|
+
};
|
|
94
|
+
}
|
|
79
95
|
export function buildPhiSiteProxyHandlers(options) {
|
|
80
96
|
const { upstreamBaseUrl, timeoutMs, buildHeaders, logLabel = "[phis-ui][/api/site proxy]", missingBaseUrlMessage = "Missing apiBaseUrl for /api/site proxy.", } = options;
|
|
81
97
|
const handlers = PhiNextProxy({
|
|
@@ -122,9 +138,9 @@ export function buildPhiSiteProxyHandlers(options) {
|
|
|
122
138
|
GET: answeringLocally("GET", handlers.GET),
|
|
123
139
|
HEAD: answeringLocally("HEAD", handlers.HEAD),
|
|
124
140
|
OPTIONS: answeringLocally("OPTIONS", handlers.OPTIONS),
|
|
125
|
-
POST: answeringLocally("POST", clearingReadCacheOnWrite(handlers.POST)),
|
|
126
|
-
PUT: answeringLocally("PUT", clearingReadCacheOnWrite(handlers.PUT)),
|
|
127
|
-
PATCH: answeringLocally("PATCH", clearingReadCacheOnWrite(handlers.PATCH)),
|
|
141
|
+
POST: answeringLocally("POST", validatingCmsStructureWrite(clearingReadCacheOnWrite(handlers.POST), options.loadAreaBridge)),
|
|
142
|
+
PUT: answeringLocally("PUT", validatingCmsStructureWrite(clearingReadCacheOnWrite(handlers.PUT), options.loadAreaBridge)),
|
|
143
|
+
PATCH: answeringLocally("PATCH", validatingCmsStructureWrite(clearingReadCacheOnWrite(handlers.PATCH), options.loadAreaBridge)),
|
|
128
144
|
DELETE: answeringLocally("DELETE", clearingReadCacheOnWrite(handlers.DELETE)),
|
|
129
145
|
};
|
|
130
146
|
}
|
|
@@ -65,7 +65,7 @@ function normalizeRenderableBlockTransitionTrigger(value) {
|
|
|
65
65
|
: undefined;
|
|
66
66
|
}
|
|
67
67
|
function normalizeRenderableBlockViewportEffectAxis(value) {
|
|
68
|
-
return value === "x" || value === "y" ? value : undefined;
|
|
68
|
+
return value === "x" || value === "y" || value === "z" ? value : undefined;
|
|
69
69
|
}
|
|
70
70
|
function normalizeRenderableBlockViewportEffectProperty(value) {
|
|
71
71
|
return value === "translate" ||
|
package/next/config.d.mts
CHANGED
|
@@ -15,6 +15,9 @@ export declare function generatePhiAntdStaticCss(projectDir?: string): {
|
|
|
15
15
|
/** The generated file for the Ant Design installed for `projectDir`, written once per input. */
|
|
16
16
|
export declare function ensurePhiAntdStaticCss(projectDir?: string): string;
|
|
17
17
|
|
|
18
|
+
/** The host in `config/site-runtime.json`'s `site.publicUrl` under `dir`, or `null` where there is none. */
|
|
19
|
+
export declare function readPhiSitePublicHost(dir?: string): string | null;
|
|
20
|
+
|
|
18
21
|
/**
|
|
19
22
|
* A Site's `next.config`, with `@phis/ui/antd-static.css` resolved to the structure generated for its
|
|
20
23
|
* Ant Design. `dir` is the Site's root, the directory its `package.json` is in.
|
package/next/config.mjs
CHANGED
|
@@ -257,15 +257,42 @@ export function ensurePhiAntdStaticCss(projectDir = process.cwd()) {
|
|
|
257
257
|
return file;
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
+
/**
|
|
261
|
+
* The host the Site is reached at, from `config/site-runtime.json` (`site.publicUrl`), or `null` where the
|
|
262
|
+
* file or the field is absent -- a build outside a Site checkout has neither.
|
|
263
|
+
*
|
|
264
|
+
* The dev server serves its own resources only to the hosts in `allowedDevOrigins`; a page opened through
|
|
265
|
+
* any other host arrives as HTML and never hydrates -- no menu opens, nothing measured in the browser lays
|
|
266
|
+
* out. The public host was a list a person had to keep, and the one Site that moved to `dev.phis.dev`
|
|
267
|
+
* was missing from it. The CLI writes the file when it scaffolds a Site, so the host is known here.
|
|
268
|
+
*/
|
|
269
|
+
export function readPhiSitePublicHost(dir = process.cwd()) {
|
|
270
|
+
const file = path.join(dir, "config", "site-runtime.json");
|
|
271
|
+
if (!existsSync(file)) return null;
|
|
272
|
+
const publicUrl = JSON.parse(readFileSync(file, "utf8"))?.site?.publicUrl;
|
|
273
|
+
if (publicUrl === undefined || publicUrl === null || publicUrl === "") return null;
|
|
274
|
+
if (typeof publicUrl !== "string" || !URL.canParse(publicUrl)) {
|
|
275
|
+
throw new Error(`config/site-runtime.json: site.publicUrl must be a URL, found ${JSON.stringify(publicUrl)}.`);
|
|
276
|
+
}
|
|
277
|
+
return new URL(publicUrl).hostname;
|
|
278
|
+
}
|
|
279
|
+
|
|
260
280
|
/**
|
|
261
281
|
* A Site's `next.config`, with `@phis/ui/antd-static.css` resolved to the structure generated for its
|
|
262
|
-
* Ant Design
|
|
282
|
+
* Ant Design, and the Site's own public host allowed to reach the dev server. `dir` is the Site's root,
|
|
283
|
+
* the directory its `package.json` is in.
|
|
263
284
|
*/
|
|
264
285
|
export function withPhiNextConfig(nextConfig, { dir = process.cwd() } = {}) {
|
|
265
286
|
// Turbopack takes no absolute path as an alias target; a relative one is read from the Site's root.
|
|
266
287
|
const file = `./${path.relative(dir, ensurePhiAntdStaticCss(dir)).split(path.sep).join("/")}`;
|
|
288
|
+
const publicHost = readPhiSitePublicHost(dir);
|
|
289
|
+
const allowedDevOrigins = [...new Set([
|
|
290
|
+
...(nextConfig.allowedDevOrigins ?? []),
|
|
291
|
+
...(publicHost ? [publicHost] : []),
|
|
292
|
+
])];
|
|
267
293
|
return {
|
|
268
294
|
...nextConfig,
|
|
295
|
+
...(allowedDevOrigins.length > 0 ? { allowedDevOrigins } : {}),
|
|
269
296
|
turbopack: {
|
|
270
297
|
...nextConfig.turbopack,
|
|
271
298
|
resolveAlias: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phis/ui",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -503,7 +503,7 @@
|
|
|
503
503
|
"@lexical/rich-text": "0.50.0",
|
|
504
504
|
"@lexical/selection": "0.50.0",
|
|
505
505
|
"@lexical/utils": "0.50.0",
|
|
506
|
-
"@phis/contracts": "^0.
|
|
506
|
+
"@phis/contracts": "^0.7.0",
|
|
507
507
|
"dayjs": "^1.11.23",
|
|
508
508
|
"dom-serializer": "^2.0.0",
|
|
509
509
|
"htmlparser2": "^10.1.0",
|
|
@@ -29,7 +29,8 @@ function applyEasing(progress, easing) {
|
|
|
29
29
|
return progress;
|
|
30
30
|
}
|
|
31
31
|
function resolveProgress(effect, rect, viewportWidth, viewportHeight) {
|
|
32
|
-
|
|
32
|
+
// `z` has no extent on the page, so a turn about it follows the page's own scroll direction.
|
|
33
|
+
const axis = (effect.axis ?? PHI_RENDERABLE_BLOCK_DEFAULT_VIEWPORT_EFFECT.axis) === "x" ? "x" : "y";
|
|
33
34
|
const targetStart = axis === "x" ? rect.left : rect.top;
|
|
34
35
|
const rangeStart = resolveRangePoint(effect.rangeStart ?? PHI_RENDERABLE_BLOCK_DEFAULT_VIEWPORT_EFFECT.rangeStart, rect, viewportWidth, viewportHeight, axis);
|
|
35
36
|
const rangeEnd = resolveRangePoint(effect.rangeEnd ?? PHI_RENDERABLE_BLOCK_DEFAULT_VIEWPORT_EFFECT.rangeEnd, rect, viewportWidth, viewportHeight, axis);
|
|
@@ -77,6 +78,9 @@ function resolveViewportEffectValues(effects, target) {
|
|
|
77
78
|
if (axis === "x") {
|
|
78
79
|
values.rotateX = rotateValue;
|
|
79
80
|
}
|
|
81
|
+
else if (axis === "z") {
|
|
82
|
+
values.rotateZ = rotateValue;
|
|
83
|
+
}
|
|
80
84
|
else {
|
|
81
85
|
values.rotateY = rotateValue;
|
|
82
86
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PhiCmsDescriptorBuildContext, PhiCmsRoutePresetDescriptor } from "../../../types/cms-module-descriptors";
|
|
2
|
+
export declare const PHI_ACCOUNTING_RUNTIME_MODULE_AREA_SHELLS: {
|
|
3
|
+
ownerModuleId: "@phis/ui/modules/accounting";
|
|
4
|
+
presetKey: string;
|
|
5
|
+
shellPresetVersion: number;
|
|
6
|
+
area: "accounting";
|
|
7
|
+
loadTree: ({ page, runtime }: PhiCmsDescriptorBuildContext) => Promise<import("../../../types").PhiResolvedCmsPageTree>;
|
|
8
|
+
}[];
|
|
9
|
+
export declare const PHI_ACCOUNTING_RUNTIME_MODULE_ROUTES: PhiCmsRoutePresetDescriptor[];
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { PHI_BASE_PAGE_LAYOUT_VERSION } from "../../../components/regions/presets/phi-base-page-layout";
|
|
2
|
+
import { buildPhiAreaRootRoutePresetDescriptor } from "../area-root-route";
|
|
3
|
+
import { PHI_ACCOUNTING_RUNTIME_MODULE_ID } from "./ids";
|
|
4
|
+
/* What the Accounting base Module brings: the Site shell for its Area, the Area root and the overview. */
|
|
5
|
+
export const PHI_ACCOUNTING_RUNTIME_MODULE_AREA_SHELLS = [
|
|
6
|
+
{
|
|
7
|
+
ownerModuleId: PHI_ACCOUNTING_RUNTIME_MODULE_ID,
|
|
8
|
+
presetKey: "accounting-area-preset",
|
|
9
|
+
shellPresetVersion: 1,
|
|
10
|
+
area: "accounting",
|
|
11
|
+
loadTree: ({ page, runtime }) => import("../phi-default-site-area-preset-tree")
|
|
12
|
+
.then((module) => module.buildPhiDefaultSiteAreaPresetTree({
|
|
13
|
+
page,
|
|
14
|
+
runtime,
|
|
15
|
+
presetKey: "accounting-area-preset",
|
|
16
|
+
ownerModuleId: PHI_ACCOUNTING_RUNTIME_MODULE_ID,
|
|
17
|
+
runtimeModuleArea: "accounting",
|
|
18
|
+
})),
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
export const PHI_ACCOUNTING_RUNTIME_MODULE_ROUTES = [
|
|
22
|
+
buildPhiAreaRootRoutePresetDescriptor({
|
|
23
|
+
ownerModuleId: PHI_ACCOUNTING_RUNTIME_MODULE_ID,
|
|
24
|
+
area: "accounting",
|
|
25
|
+
title: "Accounting",
|
|
26
|
+
}),
|
|
27
|
+
{
|
|
28
|
+
ownerModuleId: PHI_ACCOUNTING_RUNTIME_MODULE_ID,
|
|
29
|
+
presetKey: "accounting-overview-page",
|
|
30
|
+
presetVersion: 1 + PHI_BASE_PAGE_LAYOUT_VERSION,
|
|
31
|
+
area: "accounting",
|
|
32
|
+
title: "Overview",
|
|
33
|
+
path: "/overview",
|
|
34
|
+
loadTree: ({ page, runtime }) => import("./trees/phi-default-accounting-page-tree")
|
|
35
|
+
.then((module) => module.buildPhiDefaultAccountingPageTree({ page, runtime })),
|
|
36
|
+
},
|
|
37
|
+
];
|
|
@@ -1,5 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The unselectable base module of its Area. Shells and routes come from the shared Area-base preset
|
|
3
|
-
* list and are filtered by owner, so each base module states its own half of it.
|
|
4
|
-
*/
|
|
1
|
+
/** The unselectable base module of its Area. Its shell and Pages are its own (`./presets`). */
|
|
5
2
|
export declare const PHI_ACCOUNTING_RUNTIME_MODULE_SERVER_AREA_CONTRIBUTION: import("../area-contributions").PhiRuntimeModuleServerAreaContribution;
|
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PHI_ACCOUNTING_RUNTIME_MODULE_AREA_SHELLS, PHI_ACCOUNTING_RUNTIME_MODULE_ROUTES, } from "./presets";
|
|
2
2
|
import { definePhiRuntimeModuleServerAreaContribution } from "../area-contributions";
|
|
3
3
|
import { PHI_ACCOUNTING_RUNTIME_MODULE_DEFINITION } from "../accounting/definition";
|
|
4
|
-
/**
|
|
5
|
-
* The unselectable base module of its Area. Shells and routes come from the shared Area-base preset
|
|
6
|
-
* list and are filtered by owner, so each base module states its own half of it.
|
|
7
|
-
*/
|
|
4
|
+
/** The unselectable base module of its Area. Its shell and Pages are its own (`./presets`). */
|
|
8
5
|
export const PHI_ACCOUNTING_RUNTIME_MODULE_SERVER_AREA_CONTRIBUTION = definePhiRuntimeModuleServerAreaContribution({
|
|
9
6
|
moduleId: PHI_ACCOUNTING_RUNTIME_MODULE_DEFINITION.moduleId,
|
|
10
7
|
catalogEntry: {
|
|
11
8
|
definition: PHI_ACCOUNTING_RUNTIME_MODULE_DEFINITION,
|
|
12
9
|
widgets: [],
|
|
13
10
|
layouts: [],
|
|
14
|
-
areaShells:
|
|
15
|
-
routes:
|
|
11
|
+
areaShells: PHI_ACCOUNTING_RUNTIME_MODULE_AREA_SHELLS,
|
|
12
|
+
routes: PHI_ACCOUNTING_RUNTIME_MODULE_ROUTES,
|
|
16
13
|
load: () => import("./module").then((module) => module.PHI_ACCOUNTING_RUNTIME_MODULE),
|
|
17
14
|
},
|
|
18
15
|
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { PhiCmsDescriptorBuildContext, PhiCmsRoutePresetDescriptor } from "../../../types/cms-module-descriptors";
|
|
2
|
+
export declare const PHI_APP_RUNTIME_MODULE_AREA_SHELLS: {
|
|
3
|
+
ownerModuleId: "@phis/ui/modules/app";
|
|
4
|
+
presetKey: string;
|
|
5
|
+
shellPresetVersion: number;
|
|
6
|
+
area: "app";
|
|
7
|
+
loadTree: ({ page, runtime }: PhiCmsDescriptorBuildContext) => Promise<{
|
|
8
|
+
pageMeta: import("../../../types").PhiCmsResolvedPageMeta | null | undefined;
|
|
9
|
+
runtimeModuleIds: `${string}/${string}`[] | null | undefined;
|
|
10
|
+
regions: import("../../../types").PhiCmsRegionNode[];
|
|
11
|
+
overlays: import("../../../types").PhiCmsOverlayNode[];
|
|
12
|
+
layoutNodes: import("../../../types").PhiCmsLayoutNode[];
|
|
13
|
+
contentWidgets: import("../../../types").PhiCmsContentWidgetNode[];
|
|
14
|
+
controllerSettings: import("../../../types/cms").PhiCmsTreeControllerSettings;
|
|
15
|
+
page: import("../../../types").PhiCmsPageNode;
|
|
16
|
+
}>;
|
|
17
|
+
}[];
|
|
18
|
+
export declare const PHI_APP_RUNTIME_MODULE_ROUTES: PhiCmsRoutePresetDescriptor[];
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { PHI_BASE_PAGE_LAYOUT_VERSION } from "../../../components/regions/presets/phi-base-page-layout";
|
|
2
|
+
import { buildPhiAreaRootRoutePresetDescriptor } from "../area-root-route";
|
|
3
|
+
import { PHI_APP_RUNTIME_MODULE_ID } from "./ids";
|
|
4
|
+
/* What the App base Module brings: its Area shell, the Area root and the profile Page. */
|
|
5
|
+
export const PHI_APP_RUNTIME_MODULE_AREA_SHELLS = [
|
|
6
|
+
{
|
|
7
|
+
ownerModuleId: PHI_APP_RUNTIME_MODULE_ID,
|
|
8
|
+
presetKey: "app-area-preset",
|
|
9
|
+
shellPresetVersion: 1,
|
|
10
|
+
area: "app",
|
|
11
|
+
/*
|
|
12
|
+
* The Site shell with a left sider added, rather than a shell of its own.
|
|
13
|
+
*
|
|
14
|
+
* App's header and footer are Public's -- one Brand, one account trigger, one description of both.
|
|
15
|
+
* What App has that Public does not is somewhere for the Modules a signed-in person works with,
|
|
16
|
+
* and that is the whole of the overlay. Nothing is omitted from the base, so the two trees cannot
|
|
17
|
+
* disagree about a Region: composition refuses a Region type stated twice.
|
|
18
|
+
*/
|
|
19
|
+
loadTree: async ({ page, runtime }) => {
|
|
20
|
+
const [{ buildPhiDefaultSiteAreaPresetTree }, { buildPhiDefaultAppAreaPresetTree }, { mergePhiCmsShellTrees },] = await Promise.all([
|
|
21
|
+
import("../phi-default-site-area-preset-tree"),
|
|
22
|
+
import("./trees/phi-default-app-area-preset-tree"),
|
|
23
|
+
import("../shell-tree-composition"),
|
|
24
|
+
]);
|
|
25
|
+
const [base, overlay] = await Promise.all([
|
|
26
|
+
buildPhiDefaultSiteAreaPresetTree({
|
|
27
|
+
page,
|
|
28
|
+
runtime,
|
|
29
|
+
presetKey: "app-area-preset",
|
|
30
|
+
ownerModuleId: PHI_APP_RUNTIME_MODULE_ID,
|
|
31
|
+
runtimeModuleArea: "app",
|
|
32
|
+
}),
|
|
33
|
+
buildPhiDefaultAppAreaPresetTree({ page, runtime }),
|
|
34
|
+
]);
|
|
35
|
+
return mergePhiCmsShellTrees(base, overlay);
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
export const PHI_APP_RUNTIME_MODULE_ROUTES = [
|
|
40
|
+
buildPhiAreaRootRoutePresetDescriptor({
|
|
41
|
+
ownerModuleId: PHI_APP_RUNTIME_MODULE_ID,
|
|
42
|
+
area: "app",
|
|
43
|
+
title: "App",
|
|
44
|
+
}),
|
|
45
|
+
/*
|
|
46
|
+
* What a person has settled about their own account: their name, the language they read in.
|
|
47
|
+
*
|
|
48
|
+
* It sat with the Auth Module, which owned the route while Core owned every Widget on it. Nothing
|
|
49
|
+
* about a name or a language depends on how somebody signs in -- a Site that switches to a
|
|
50
|
+
* directory, or switches Auth off while sessions keep working, would have lost the Page that says
|
|
51
|
+
* what they are called. AUTHENTICATION.md section 5 already said so: profile administration does
|
|
52
|
+
* not become Auth's merely by appearing in its menu.
|
|
53
|
+
*
|
|
54
|
+
* The Area's own Settings child, so it is declared here rather than injected -- SETTINGS.md section
|
|
55
|
+
* 5, the same standing Admin and Builder give their General page.
|
|
56
|
+
*/
|
|
57
|
+
{
|
|
58
|
+
ownerModuleId: PHI_APP_RUNTIME_MODULE_ID,
|
|
59
|
+
presetKey: "app-profile-page",
|
|
60
|
+
presetVersion: 1 + PHI_BASE_PAGE_LAYOUT_VERSION,
|
|
61
|
+
area: "app",
|
|
62
|
+
title: "Profile",
|
|
63
|
+
path: "/settings/profile",
|
|
64
|
+
loadTree: ({ page, runtime }) => import("./trees/phi-default-app-profile-page-tree")
|
|
65
|
+
.then((module) => module.buildPhiDefaultAppProfilePageTree({ page, runtime })),
|
|
66
|
+
},
|
|
67
|
+
];
|
|
@@ -1,5 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The unselectable base module of its Area. Shells and routes come from the shared Area-base preset
|
|
3
|
-
* list and are filtered by owner, so each base module states its own half of it.
|
|
4
|
-
*/
|
|
1
|
+
/** The unselectable base module of its Area. Its shell and Pages are its own (`./presets`). */
|
|
5
2
|
export declare const PHI_APP_RUNTIME_MODULE_SERVER_AREA_CONTRIBUTION: import("../area-contributions").PhiRuntimeModuleServerAreaContribution;
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PHI_APP_RUNTIME_MODULE_AREA_SHELLS, PHI_APP_RUNTIME_MODULE_ROUTES, } from "./presets";
|
|
2
2
|
import { definePhiRuntimeModuleServerAreaContribution } from "../area-contributions";
|
|
3
3
|
import { PHI_APP_RUNTIME_MODULE_DEFINITION } from "../app/definition";
|
|
4
4
|
import { PHI_APP_RUNTIME_MODULE_FORMS } from "./forms";
|
|
5
|
-
/**
|
|
6
|
-
* The unselectable base module of its Area. Shells and routes come from the shared Area-base preset
|
|
7
|
-
* list and are filtered by owner, so each base module states its own half of it.
|
|
8
|
-
*/
|
|
5
|
+
/** The unselectable base module of its Area. Its shell and Pages are its own (`./presets`). */
|
|
9
6
|
export const PHI_APP_RUNTIME_MODULE_SERVER_AREA_CONTRIBUTION = definePhiRuntimeModuleServerAreaContribution({
|
|
10
7
|
moduleId: PHI_APP_RUNTIME_MODULE_DEFINITION.moduleId,
|
|
11
8
|
catalogEntry: {
|
|
@@ -13,8 +10,8 @@ export const PHI_APP_RUNTIME_MODULE_SERVER_AREA_CONTRIBUTION = definePhiRuntimeM
|
|
|
13
10
|
widgets: [],
|
|
14
11
|
forms: PHI_APP_RUNTIME_MODULE_FORMS,
|
|
15
12
|
layouts: [],
|
|
16
|
-
areaShells:
|
|
17
|
-
routes:
|
|
13
|
+
areaShells: PHI_APP_RUNTIME_MODULE_AREA_SHELLS,
|
|
14
|
+
routes: PHI_APP_RUNTIME_MODULE_ROUTES,
|
|
18
15
|
load: () => import("./module").then((module) => module.PHI_APP_RUNTIME_MODULE),
|
|
19
16
|
},
|
|
20
17
|
});
|
|
@@ -271,11 +271,11 @@ export function usePhiAssetRuntimeController({ address, mountScope, config, }) {
|
|
|
271
271
|
value: {
|
|
272
272
|
page: state.page,
|
|
273
273
|
pageSize: state.pageSize,
|
|
274
|
-
total: state.pagination?.total ??
|
|
274
|
+
total: state.pagination?.total ?? 0,
|
|
275
275
|
},
|
|
276
276
|
receiver: "broadcast",
|
|
277
277
|
timestamp: Date.now(),
|
|
278
278
|
});
|
|
279
|
-
}, [emitAssetSignal, mountScope, state.
|
|
279
|
+
}, [emitAssetSignal, mountScope, state.page, state.pageSize, state.pagination?.total]);
|
|
280
280
|
return state;
|
|
281
281
|
}
|
|
@@ -62,10 +62,18 @@ async function queryMediaCollection(request) {
|
|
|
62
62
|
signal: request.signal,
|
|
63
63
|
});
|
|
64
64
|
const payload = await readPhiImagePreviewResponse(response);
|
|
65
|
+
/*
|
|
66
|
+
* The server counts the listing on every read (`pagination.total`), and the paging below is drawn
|
|
67
|
+
* from it. Without it the count is unknown, which the assets of one page would only pretend to answer.
|
|
68
|
+
*/
|
|
69
|
+
const total = payload.pagination?.total;
|
|
70
|
+
if (typeof total !== "number" || !Number.isFinite(total) || total < 0) {
|
|
71
|
+
throw new Error("The media listing answered no total.");
|
|
72
|
+
}
|
|
65
73
|
return {
|
|
66
74
|
resourceKey: request.resourceKey,
|
|
67
75
|
items: payload.assets.map((asset) => ({ ...asset })),
|
|
68
|
-
total
|
|
76
|
+
total,
|
|
69
77
|
loading: false,
|
|
70
78
|
error: null,
|
|
71
79
|
meta: {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { isPhiRecord } from "../../../../helpers/is-record";
|
|
3
3
|
import { createPhiControlOptionsProviderClient, } from "../../../../components/controls/phi-options-provider";
|
|
4
4
|
import { PHI_ASSET_RUNTIME_DATA_PROVIDER_KEYS } from "../ids";
|
|
5
|
+
import { readPhiMediaFolders } from "../../../../components/media/phi-image-preview-data";
|
|
5
6
|
import { PHI_ASSET_CONTROLLER_STORE_KEY } from "../../../../components/media/asset-controller-signals";
|
|
6
7
|
import { phiImagePreviewStore } from "../../../../components/media/phi-image-preview-store";
|
|
7
8
|
import { buildPhiMediaFolderCascaderOptions, buildPhiMediaFolderNamesById, buildPhiMediaFolderValueById, } from "../../../../components/media/media-folder-options";
|
|
@@ -59,8 +60,7 @@ async function loadFolders() {
|
|
|
59
60
|
const payload = await response.json().catch(() => null);
|
|
60
61
|
if (!response.ok)
|
|
61
62
|
throw new Error("Failed to load the Site's media folders.");
|
|
62
|
-
|
|
63
|
-
return folders.filter(isPhiRecord);
|
|
63
|
+
return readPhiMediaFolders(isPhiRecord(payload) ? payload.folders : undefined, "The media folders route");
|
|
64
64
|
}
|
|
65
65
|
export const PhiMediaFolderChoicesOptionsProviderClient = createPhiControlOptionsProviderClient({
|
|
66
66
|
key: PHI_ASSET_RUNTIME_DATA_PROVIDER_KEYS.mediaFolderChoices,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PHI_WORKSPACE_CATALOG_SCOPE } from "../../../components/workspace/catalog-store";
|
|
1
2
|
import { PHI_AREA_META_PUBLIC_DEFAULTS, } from "../../../helpers/cms-area-config";
|
|
2
3
|
import { isPhiRecord } from "../../../helpers/is-record";
|
|
3
4
|
import { builderWorkspaceStore, readPhiBuilderEffectiveAreaMeta, readPhiBuilderEffectiveAreaRootRoute, restorePhiDeveloperBuilderAreaMeta, setPhiDeveloperBuilderAreaRootRoute, } from "./developer-workspace-store";
|
|
@@ -54,7 +55,7 @@ function isSamePhiBuilderAreaAnswer(left, right) {
|
|
|
54
55
|
* unsaved. `rootRoute` is `undefined` when the dialog did not ask it.
|
|
55
56
|
*/
|
|
56
57
|
export function applyPhiBuilderAreaSettingsAnswer({ area, historyContext, rootRoute, meta, }) {
|
|
57
|
-
const current = builderWorkspaceStore.getSnapshot(
|
|
58
|
+
const current = builderWorkspaceStore.getSnapshot(PHI_WORKSPACE_CATALOG_SCOPE);
|
|
58
59
|
const before = [];
|
|
59
60
|
const after = [];
|
|
60
61
|
if (rootRoute !== undefined && !isSamePhiBuilderAreaAnswer(readPhiBuilderEffectiveAreaRootRoute(current, area), rootRoute)) {
|
|
@@ -13,12 +13,12 @@ import { getPhiBuilderNavigationDraftSnapshot } from "./navigation-store";
|
|
|
13
13
|
* learn which Area they are being edited in.
|
|
14
14
|
*/
|
|
15
15
|
export function usePhiBuilderAuthoringCanvas() {
|
|
16
|
-
const area = usePhiDeveloperBuilderStateValue(
|
|
17
|
-
const catalogHydrated = usePhiDeveloperBuilderStateValue(
|
|
18
|
-
const navigationSurfaces = usePhiDeveloperBuilderStateValue(
|
|
19
|
-
const modulePresetPagesByArea = usePhiDeveloperBuilderStateValue(
|
|
20
|
-
const customPages = usePhiDeveloperBuilderStateValue(
|
|
21
|
-
const persistedPageCatalogByArea = usePhiDeveloperBuilderStateValue(
|
|
16
|
+
const area = usePhiDeveloperBuilderStateValue((state) => state.area);
|
|
17
|
+
const catalogHydrated = usePhiDeveloperBuilderStateValue((state) => state.catalogHydrated);
|
|
18
|
+
const navigationSurfaces = usePhiDeveloperBuilderStateValue((state) => state.navigationSurfacesByArea[state.area] ?? null);
|
|
19
|
+
const modulePresetPagesByArea = usePhiDeveloperBuilderStateValue((state) => state.modulePresetPagesByArea);
|
|
20
|
+
const customPages = usePhiDeveloperBuilderStateValue((state) => state.customPages);
|
|
21
|
+
const persistedPageCatalogByArea = usePhiDeveloperBuilderStateValue((state) => state.persistedPageCatalogByArea);
|
|
22
22
|
return useMemo(() => ({
|
|
23
23
|
area,
|
|
24
24
|
catalogHydrated,
|