@phis/ui 0.4.1 → 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/shared/markdown-body-client.d.ts +2 -1
- package/components/widgets/shared/markdown-body-client.js +3 -1
- package/components/widgets/shared/markdown-render.js +8 -1
- 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/config.d.ts +3 -1
- package/plugins/runtime-modules/core/widgets/markdown/config.js +14 -0
- 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 +29 -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 +17 -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
|
@@ -4,7 +4,7 @@ import { PHI_COMMON_CONTROL_DEFAULT_LABELS, readPhiCommonControlActionKey, } fro
|
|
|
4
4
|
import { PHI_BUTTON_VARIANT_FIELD_OPTIONS, readPhiButtonVariant } from "./button-variant";
|
|
5
5
|
import { createPhiCommonActionField, readPhiControlLabel } from "./common-action-field";
|
|
6
6
|
import { PHI_CONTROL_BADGE_FIELDS, parsePhiControlBadgeConfig } from "./control-signal-config";
|
|
7
|
-
import {
|
|
7
|
+
import { createPhiConfigReader } from "./strict-config-reader";
|
|
8
8
|
/*
|
|
9
9
|
* The buttons of a Widget that holds several: the Command Toolbar's row, and the Float Button's corner.
|
|
10
10
|
*
|
|
@@ -12,102 +12,59 @@ import { readBoolean, readNumber, readString } from "./parser-primitives";
|
|
|
12
12
|
* emit, and the Signals panel wires both. The two Widgets differ in how the buttons stand, never in what
|
|
13
13
|
* a button is, so the parser, the Inspector fields, the viewer filter and the scaffold's count are one.
|
|
14
14
|
*/
|
|
15
|
+
/** What a button sends: a text, a number, true/false, a list of texts or of numbers, or an object. */
|
|
15
16
|
function readButtonEmitValue(value) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const numberValue = readNumber(value);
|
|
21
|
-
if (numberValue !== undefined) {
|
|
22
|
-
return numberValue;
|
|
23
|
-
}
|
|
24
|
-
const booleanValue = readBoolean(value);
|
|
25
|
-
if (booleanValue !== undefined) {
|
|
26
|
-
return booleanValue;
|
|
27
|
-
}
|
|
28
|
-
if (value == null) {
|
|
29
|
-
return undefined;
|
|
30
|
-
}
|
|
17
|
+
if (typeof value === "string" || typeof value === "boolean")
|
|
18
|
+
return value;
|
|
19
|
+
if (typeof value === "number")
|
|
20
|
+
return Number.isFinite(value) ? value : undefined;
|
|
31
21
|
if (Array.isArray(value)) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const numberItems = value.filter((item) => typeof item === "number" && Number.isFinite(item));
|
|
37
|
-
if (numberItems.length === value.length) {
|
|
38
|
-
return numberItems;
|
|
39
|
-
}
|
|
22
|
+
if (value.every((item) => typeof item === "string"))
|
|
23
|
+
return value;
|
|
24
|
+
if (value.every((item) => typeof item === "number" && Number.isFinite(item)))
|
|
25
|
+
return value;
|
|
40
26
|
return undefined;
|
|
41
27
|
}
|
|
42
|
-
|
|
43
|
-
return value;
|
|
44
|
-
}
|
|
45
|
-
return undefined;
|
|
28
|
+
return isPhiRecord(value) ? value : undefined;
|
|
46
29
|
}
|
|
47
|
-
function readCommandButtonEmitConfig(
|
|
48
|
-
if (!isPhiRecord(value)) {
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
const record = value;
|
|
52
|
-
const capabilityId = readString(record.capabilityId);
|
|
53
|
-
if (!capabilityId) {
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
30
|
+
function readCommandButtonEmitConfig(reader) {
|
|
56
31
|
return {
|
|
57
|
-
capabilityId,
|
|
58
|
-
value:
|
|
32
|
+
capabilityId: reader.string("capabilityId") ?? reader.fail("capabilityId", "a capability id"),
|
|
33
|
+
value: reader.value("value", "a text, number, true/false, list or object", readButtonEmitValue),
|
|
59
34
|
};
|
|
60
35
|
}
|
|
61
|
-
function
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
.
|
|
68
|
-
}
|
|
69
|
-
function readCommandButtonConfig(value) {
|
|
70
|
-
if (!isPhiRecord(value)) {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
const record = value;
|
|
74
|
-
const key = readString(record.key);
|
|
75
|
-
const emits = readCommandButtonEmits(record.emits);
|
|
76
|
-
if (!key || emits.length === 0) {
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
const hasAccessPolicy = Object.prototype.hasOwnProperty.call(record, "accessPolicy");
|
|
80
|
-
const accessPolicy = hasAccessPolicy
|
|
81
|
-
? readPhiViewerAccessPolicy(record.accessPolicy)
|
|
36
|
+
function readCommandButtonConfig(reader) {
|
|
37
|
+
const key = reader.string("key") ?? reader.fail("key", "a text");
|
|
38
|
+
const emits = reader.list("emits", readCommandButtonEmitConfig) ?? [];
|
|
39
|
+
if (emits.length === 0)
|
|
40
|
+
reader.fail("emits", "at least one emit");
|
|
41
|
+
const accessPolicy = Object.prototype.hasOwnProperty.call(reader.record, "accessPolicy")
|
|
42
|
+
? readPhiViewerAccessPolicy(reader.record.accessPolicy) ?? reader.fail("accessPolicy", "an access policy")
|
|
82
43
|
: null;
|
|
83
|
-
if (hasAccessPolicy && !accessPolicy) {
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
44
|
return {
|
|
87
45
|
key,
|
|
88
46
|
emits,
|
|
89
47
|
...(accessPolicy ? { accessPolicy } : {}),
|
|
90
|
-
...parsePhiControlBadgeConfig(record),
|
|
91
|
-
action:
|
|
92
|
-
label: readPhiControlLabel(record.label),
|
|
93
|
-
tooltip:
|
|
94
|
-
icon:
|
|
95
|
-
display:
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
readOnly: readBoolean(record.readOnly),
|
|
101
|
-
variant: readPhiButtonVariant(record.variant),
|
|
48
|
+
...parsePhiControlBadgeConfig(reader.record, reader.path),
|
|
49
|
+
action: reader.value("action", "a common action", (value) => typeof value === "string" ? readPhiCommonControlActionKey(value) ?? undefined : undefined),
|
|
50
|
+
label: readPhiControlLabel(reader.record.label),
|
|
51
|
+
tooltip: reader.string("tooltip"),
|
|
52
|
+
icon: reader.string("icon"),
|
|
53
|
+
display: reader.oneOf("display", ["icon", "label", "icon-label"]),
|
|
54
|
+
danger: reader.boolean("danger"),
|
|
55
|
+
disabled: reader.boolean("disabled"),
|
|
56
|
+
readOnly: reader.boolean("readOnly"),
|
|
57
|
+
variant: reader.value("variant", "a button variant", readPhiButtonVariant),
|
|
102
58
|
};
|
|
103
59
|
}
|
|
104
|
-
|
|
105
|
-
|
|
60
|
+
/**
|
|
61
|
+
* A Widget's buttons. Strict (`strict-config-reader.ts`): a button without a key or without an emit is
|
|
62
|
+
* an error naming it, not a button left out.
|
|
63
|
+
*/
|
|
64
|
+
export function readPhiCommandButtons(value, path = "buttons") {
|
|
65
|
+
if (value === undefined || value === null)
|
|
106
66
|
return [];
|
|
107
|
-
}
|
|
108
|
-
return value
|
|
109
|
-
.map(readCommandButtonConfig)
|
|
110
|
-
.filter((item) => item != null);
|
|
67
|
+
return createPhiConfigReader({ [path]: value }).list(path, readCommandButtonConfig) ?? [];
|
|
111
68
|
}
|
|
112
69
|
export function filterPhiCommandButtonsForViewer(buttons, viewer, ownerProviderId) {
|
|
113
70
|
return buttons.filter((button) => canPhiViewerAccessOwnedPolicy(viewer, button.accessPolicy, ownerProviderId));
|
|
@@ -143,4 +143,6 @@ export declare function parsePhiControlSignalConfig(config: Record<string, unkno
|
|
|
143
143
|
export declare function parsePhiControlStateConfig(config: Record<string, unknown>, defaults: PhiControlSignalConfigDefaults): Required<Pick<PhiControlStateConfig, "key">> & Pick<PhiControlStateConfig, "signalRoutes" | "readOnly" | "disabled">;
|
|
144
144
|
export declare function parsePhiControlPresentationConfig<TSize extends PhiControlSize = PhiControlSize>(config: Record<string, unknown>, allowedSizes?: readonly TSize[]): PhiControlPresentationConfig<TSize>;
|
|
145
145
|
export declare function parsePhiControlConfig<TSize extends PhiControlSize = PhiControlSize>(config: Record<string, unknown>, defaults: PhiControlSignalConfigDefaults, allowedSizes?: readonly TSize[]): Required<Pick<PhiControlConfig<TSize>, "key">> & Pick<PhiControlConfig<TSize>, "signalRoutes" | "readOnly" | "disabled" | "controlSize">;
|
|
146
|
-
export declare function parsePhiControlBadgeConfig(config: Record<string, unknown
|
|
146
|
+
export declare function parsePhiControlBadgeConfig(config: Record<string, unknown>,
|
|
147
|
+
/** Where the config stands, when it is an entry of a list -- a button of a toolbar. */
|
|
148
|
+
path?: string): PhiControlBadgeConfig;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readPhiSignalRouteSet, } from "../../../types/signals";
|
|
2
|
-
import { PHI_CONTROL_SIZES,
|
|
3
|
-
import {
|
|
2
|
+
import { PHI_CONTROL_SIZES, } from "../../../types/control";
|
|
3
|
+
import { createPhiConfigReader } from "./strict-config-reader";
|
|
4
4
|
export const PHI_CONTROL_READ_ONLY_FIELD = {
|
|
5
5
|
key: "readOnly",
|
|
6
6
|
type: "boolean",
|
|
@@ -83,23 +83,22 @@ export const PHI_CONTROL_BADGE_FIELDS = [
|
|
|
83
83
|
export function parsePhiControlSignalConfig(config, defaults) {
|
|
84
84
|
return {
|
|
85
85
|
signalRoutes: readPhiSignalRouteSet(config.signalRoutes),
|
|
86
|
-
key:
|
|
86
|
+
key: createPhiConfigReader(config).string("key") ?? defaults.key,
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
89
|
export function parsePhiControlStateConfig(config, defaults) {
|
|
90
|
+
const reader = createPhiConfigReader(config);
|
|
90
91
|
return {
|
|
91
92
|
...parsePhiControlSignalConfig(config, defaults),
|
|
92
|
-
readOnly:
|
|
93
|
-
disabled:
|
|
93
|
+
readOnly: reader.boolean("readOnly"),
|
|
94
|
+
disabled: reader.boolean("disabled"),
|
|
94
95
|
};
|
|
95
96
|
}
|
|
96
97
|
export function parsePhiControlPresentationConfig(config, allowedSizes) {
|
|
97
|
-
|
|
98
|
-
const resolvedAllowedSizes = allowedSizes ?? PHI_CONTROL_SIZES;
|
|
98
|
+
// A size this Control does not offer is an error, not the default in its place.
|
|
99
|
+
const resolvedAllowedSizes = (allowedSizes ?? PHI_CONTROL_SIZES);
|
|
99
100
|
return {
|
|
100
|
-
controlSize: controlSize
|
|
101
|
-
? controlSize
|
|
102
|
-
: undefined,
|
|
101
|
+
controlSize: createPhiConfigReader(config).oneOf("controlSize", resolvedAllowedSizes),
|
|
103
102
|
};
|
|
104
103
|
}
|
|
105
104
|
export function parsePhiControlConfig(config, defaults, allowedSizes) {
|
|
@@ -113,13 +112,16 @@ export function parsePhiControlConfig(config, defaults, allowedSizes) {
|
|
|
113
112
|
controlSize: controlSize ?? ((allowedSizes ?? PHI_CONTROL_SIZES).includes("medium") ? "medium" : undefined),
|
|
114
113
|
};
|
|
115
114
|
}
|
|
116
|
-
export function parsePhiControlBadgeConfig(config
|
|
115
|
+
export function parsePhiControlBadgeConfig(config,
|
|
116
|
+
/** Where the config stands, when it is an entry of a list -- a button of a toolbar. */
|
|
117
|
+
path = "") {
|
|
118
|
+
const reader = createPhiConfigReader(config, path);
|
|
117
119
|
return {
|
|
118
|
-
badgeEnabled:
|
|
119
|
-
badgeText:
|
|
120
|
-
badgeCount:
|
|
121
|
-
badgeShowZero:
|
|
122
|
-
badgeOverflowCount:
|
|
123
|
-
badgeColor:
|
|
120
|
+
badgeEnabled: reader.boolean("badgeEnabled"),
|
|
121
|
+
badgeText: reader.string("badgeText"),
|
|
122
|
+
badgeCount: reader.number("badgeCount"),
|
|
123
|
+
badgeShowZero: reader.boolean("badgeShowZero"),
|
|
124
|
+
badgeOverflowCount: reader.number("badgeOverflowCount"),
|
|
125
|
+
badgeColor: reader.string("badgeColor"),
|
|
124
126
|
};
|
|
125
127
|
}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import type { PhiMediaImageSourceConfig } from "../../../types/media";
|
|
2
|
+
/** Where an image comes from: an Asset of the Media library, or a URL. Strict: an unknown kind is an error. */
|
|
2
3
|
export declare function readPhiMediaImageSourceConfig(value: Record<string, unknown>): PhiMediaImageSourceConfig;
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { normalizePhiImageAssetVariantKey } from "../../../constants/media";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
return readString(value) === "asset" ? "asset" : "url";
|
|
5
|
-
}
|
|
2
|
+
import { createPhiConfigReader } from "./strict-config-reader";
|
|
3
|
+
/** Where an image comes from: an Asset of the Media library, or a URL. Strict: an unknown kind is an error. */
|
|
6
4
|
export function readPhiMediaImageSourceConfig(value) {
|
|
7
|
-
|
|
5
|
+
const reader = createPhiConfigReader(value);
|
|
6
|
+
if ((reader.oneOf("sourceKind", ["url", "asset"]) ?? "url") === "asset") {
|
|
8
7
|
return {
|
|
9
8
|
sourceKind: "asset",
|
|
10
|
-
assetId:
|
|
9
|
+
assetId: reader.number("assetId"),
|
|
11
10
|
variantKey: normalizePhiImageAssetVariantKey(value.variantKey),
|
|
12
|
-
variantVersion:
|
|
11
|
+
variantVersion: reader.number("variantVersion"),
|
|
13
12
|
};
|
|
14
13
|
}
|
|
15
14
|
return {
|
|
16
15
|
sourceKind: "url",
|
|
17
|
-
sourceUrl:
|
|
16
|
+
sourceUrl: reader.string("sourceUrl"),
|
|
18
17
|
};
|
|
19
18
|
}
|
|
@@ -24,7 +24,12 @@ export declare const PHI_MASK_PRESET_OPTIONS: ReadonlyArray<{
|
|
|
24
24
|
label: string;
|
|
25
25
|
}>;
|
|
26
26
|
export declare function resolvePhiMaskStyle(mask: PhiMaskConfig | null | undefined): CSSProperties | undefined;
|
|
27
|
-
|
|
27
|
+
/**
|
|
28
|
+
* A stored mask, read strictly (`strict-config-reader.ts`) before it is normalized: a field that is
|
|
29
|
+
* there and of the wrong kind is an error naming it. The normalization below stays lenient for the
|
|
30
|
+
* editor, which normalizes drafts while an author is still choosing.
|
|
31
|
+
*/
|
|
32
|
+
export declare function readPhiMaskConfig(value: unknown, path?: string): PhiMaskConfig | undefined;
|
|
28
33
|
export declare function mergePhiMaskConfigDefaults(mask: PhiMaskConfig | null | undefined): PhiMaskConfig;
|
|
29
34
|
/** A picker's change on top of the current mask: the defaults filled, the patch applied, the mask enabled. */
|
|
30
35
|
export declare function mergePhiMaskConfigPatch(mask: PhiMaskConfig | null | undefined, patch: PhiMaskConfig): PhiMaskConfig;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isPhiRecord } from "../../../helpers/is-record";
|
|
2
2
|
import { readBoolean, readCssSize, readNumber, readString } from "./parser-primitives";
|
|
3
|
+
import { createPhiConfigReader } from "./strict-config-reader";
|
|
3
4
|
/** The preset shapes and their names, in the order a picker lists them. */
|
|
4
5
|
const PHI_MASK_PRESET_ENTRIES = [
|
|
5
6
|
["circle", "Circle"],
|
|
@@ -129,7 +130,29 @@ function readMaskRotation(value) {
|
|
|
129
130
|
}
|
|
130
131
|
return Math.min(180, Math.max(-180, rotation));
|
|
131
132
|
}
|
|
132
|
-
|
|
133
|
+
/**
|
|
134
|
+
* A stored mask, read strictly (`strict-config-reader.ts`) before it is normalized: a field that is
|
|
135
|
+
* there and of the wrong kind is an error naming it. The normalization below stays lenient for the
|
|
136
|
+
* editor, which normalizes drafts while an author is still choosing.
|
|
137
|
+
*/
|
|
138
|
+
export function readPhiMaskConfig(value, path = "mask") {
|
|
139
|
+
createPhiConfigReader({ [path]: value }).object(path, (reader) => {
|
|
140
|
+
reader.boolean("enabled");
|
|
141
|
+
reader.oneOf("source", ["asset", "preset"]);
|
|
142
|
+
reader.oneOf("preset", [...PHI_MASK_PRESETS]);
|
|
143
|
+
reader.number("assetId");
|
|
144
|
+
reader.string("assetUrl");
|
|
145
|
+
reader.number("scale");
|
|
146
|
+
reader.number("offsetX");
|
|
147
|
+
reader.number("offsetY");
|
|
148
|
+
reader.number("rotationDeg");
|
|
149
|
+
reader.value("position", "a CSS position", (position) => typeof position === "string" || (typeof position === "number" && Number.isFinite(position)) ? position : undefined);
|
|
150
|
+
reader.oneOf("repeat", ["repeat", "no-repeat"]);
|
|
151
|
+
reader.oneOf("mode", ["luminance", "alpha"]);
|
|
152
|
+
});
|
|
153
|
+
return normalizePhiMaskConfig(value);
|
|
154
|
+
}
|
|
155
|
+
function normalizePhiMaskConfig(value) {
|
|
133
156
|
if (!isPhiRecord(value)) {
|
|
134
157
|
return undefined;
|
|
135
158
|
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { isPhiRecord } from "../../../helpers/is-record";
|
|
2
2
|
import { mergePhiCmsRenderableBlockConfigDefaults } from "../../../helpers/cms-config-serialization";
|
|
3
|
+
import { readPhiViewerAccessPolicy } from "../../../types/access";
|
|
4
|
+
import { createPhiConfigReader } from "./strict-config-reader";
|
|
5
|
+
import { PHI_BACKGROUND_FILTERS, readPhiShadow } from "../../../types/layout-style";
|
|
6
|
+
import { PHI_CMS_BORDER_SOURCES } from "../../../types/cms-border-source";
|
|
7
|
+
import { PHI_SURFACE_TONES } from "../../../types/surface";
|
|
3
8
|
export function readString(value) {
|
|
4
9
|
return typeof value === "string" && value.trim() ? value : undefined;
|
|
5
10
|
}
|
|
@@ -22,7 +27,74 @@ export function readNumberList(value) {
|
|
|
22
27
|
export function readCssSize(value) {
|
|
23
28
|
return readNumber(value) ?? readString(value);
|
|
24
29
|
}
|
|
30
|
+
/*
|
|
31
|
+
* The fields every block carries, checked strictly before they are normalized
|
|
32
|
+
* (`strict-config-reader.ts`): a render mode, visibility or size of the wrong kind is an error naming the
|
|
33
|
+
* field, not the default put in its place. The check is one level deep -- a surface, a size or the
|
|
34
|
+
* effects must be what they claim to be; what is inside them is still normalized as before.
|
|
35
|
+
*
|
|
36
|
+
* The normalization itself stays lenient, because the Builder runs it on drafts while an author edits.
|
|
37
|
+
* This reader is what a stored config is read with: every Widget's `parseConfig` and the Layouts'.
|
|
38
|
+
*/
|
|
39
|
+
/*
|
|
40
|
+
* A Surface, one level into each of its parts: a background's base, overlay, filter and motion are what
|
|
41
|
+
* they claim to be, a border is an object of the border's fields, a shadow is one the Theme knows. What is
|
|
42
|
+
* inside a gradient's stops or an overlay's pattern is still normalized as before.
|
|
43
|
+
*/
|
|
44
|
+
function assertPhiSurfaceConfig(surface) {
|
|
45
|
+
const cssSize = (value) => (typeof value === "number" && Number.isFinite(value)) || typeof value === "string" ? value : undefined;
|
|
46
|
+
surface.object("background", (background) => {
|
|
47
|
+
background.value("base", "a background base: a CSS value or an object with a kind", (value) => typeof value === "string" ||
|
|
48
|
+
(isPhiRecord(value) && ["color", "gradient", "image", "none"].includes(String(value.kind)))
|
|
49
|
+
? value
|
|
50
|
+
: undefined);
|
|
51
|
+
background.value("overlay", "a background overlay", (value) => (isPhiRecord(value) ? value : undefined));
|
|
52
|
+
background.oneOf("filter", PHI_BACKGROUND_FILTERS);
|
|
53
|
+
background.value("motion", "a background motion", (value) => (isPhiRecord(value) ? value : undefined));
|
|
54
|
+
});
|
|
55
|
+
surface.oneOf("borderSource", PHI_CMS_BORDER_SOURCES);
|
|
56
|
+
surface.object("border", (border) => {
|
|
57
|
+
border.number("borderWidth");
|
|
58
|
+
border.string("borderStyle");
|
|
59
|
+
border.string("borderColor");
|
|
60
|
+
for (const corner of ["borderTopLeftRadius", "borderTopRightRadius", "borderBottomLeftRadius", "borderBottomRightRadius"]) {
|
|
61
|
+
border.value(corner, "a CSS size", cssSize);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
surface.value("shadow", "a shadow", readPhiShadow);
|
|
65
|
+
surface.oneOf("tone", PHI_SURFACE_TONES);
|
|
66
|
+
}
|
|
67
|
+
function assertPhiRenderableBlockBaseConfig(config) {
|
|
68
|
+
const reader = createPhiConfigReader(config);
|
|
69
|
+
const sizeLike = (value) => isPhiRecord(value) || typeof value === "string" || (typeof value === "number" && Number.isFinite(value))
|
|
70
|
+
? value
|
|
71
|
+
: undefined;
|
|
72
|
+
reader.oneOf("renderMode", ["live", "preview", "editor"]);
|
|
73
|
+
reader.oneOf("visibility", ["visible", "hidden", "collapsed"]);
|
|
74
|
+
reader.value("accessPolicy", "an access policy", (value) => readPhiViewerAccessPolicy(value) ?? undefined);
|
|
75
|
+
reader.value("viewportFlags", "a whole number from 0 to 6", (value) => typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 6 ? value : undefined);
|
|
76
|
+
reader.boolean("enabled");
|
|
77
|
+
reader.boolean("debugMode");
|
|
78
|
+
reader.value("anchor", "an anchor", (value) => (isPhiRecord(value) || typeof value === "string" ? value : undefined));
|
|
79
|
+
reader.integer("zIndex");
|
|
80
|
+
reader.number("opacity");
|
|
81
|
+
reader.object("surface", assertPhiSurfaceConfig);
|
|
82
|
+
reader.string("className");
|
|
83
|
+
reader.value("size", "a size", sizeLike);
|
|
84
|
+
reader.value("minSize", "a size", sizeLike);
|
|
85
|
+
reader.value("maxSize", "a size", sizeLike);
|
|
86
|
+
reader.value("collapsedSizeHint", "a size", sizeLike);
|
|
87
|
+
reader.object("effects", (effects) => {
|
|
88
|
+
effects.list("viewportEffects", (effect) => {
|
|
89
|
+
// `z` turns flat in the page plane; nothing moves or fades along it.
|
|
90
|
+
if (effect.record.axis === "z" && effect.record.property !== "rotate") {
|
|
91
|
+
effect.fail("axis", '"x" or "y" ("z" only turns a "rotate")');
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
}
|
|
25
96
|
export function readRenderableBlockConfig(config) {
|
|
97
|
+
assertPhiRenderableBlockBaseConfig(config);
|
|
26
98
|
return mergePhiCmsRenderableBlockConfigDefaults(config);
|
|
27
99
|
}
|
|
28
100
|
export function readNavItems(value) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { PhiCmsWidgetConfigBase } from "./parser-primitives";
|
|
2
2
|
import { type PhiStackChoiceControlConfig } from "./choice-shared";
|
|
3
3
|
export type PhiCmsTabBarWidgetConfig = PhiCmsWidgetConfigBase & PhiStackChoiceControlConfig & {
|
|
4
4
|
key?: string;
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createPhiConfigReader } from "./strict-config-reader";
|
|
2
2
|
import { PHI_CHOICE_CONTROL_OPTION_FIELDS, PHI_CHOICE_CONTROL_CONFIG_FIELDS, PHI_CHOICE_CONTROL_VALUE_MODE_FIELD, parsePhiStackChoiceControlConfig, } from "./choice-shared";
|
|
3
3
|
import { createPhiCmsWidgetDefinition } from "./helpers";
|
|
4
4
|
import { PHI_SELECT_CONTROL_SIGNALS } from "../signals/control-signal-capabilities";
|
|
5
5
|
function parsePhiCmsTabBarWidgetConfig(config) {
|
|
6
|
-
const placement = readString(config.placement);
|
|
7
6
|
return {
|
|
8
7
|
...parsePhiStackChoiceControlConfig(config),
|
|
9
|
-
placement: placement
|
|
8
|
+
placement: createPhiConfigReader(config).oneOf("placement", ["top", "bottom", "left", "right"]) ?? "top",
|
|
10
9
|
};
|
|
11
10
|
}
|
|
12
11
|
export const PHI_TAB_BAR_WIDGET_DEFINITION = createPhiCmsWidgetDefinition({
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export declare class PhiConfigError extends Error {
|
|
2
|
+
readonly path: string;
|
|
3
|
+
readonly expected: string;
|
|
4
|
+
readonly received: unknown;
|
|
5
|
+
constructor(path: string, expected: string, received: unknown);
|
|
6
|
+
}
|
|
7
|
+
export declare function isPhiConfigError(error: unknown): error is PhiConfigError;
|
|
8
|
+
/** The render issue code for an error: a config nobody can read is said as such, not as a crash. */
|
|
9
|
+
export declare function resolvePhiRenderFailureCode(error: unknown): "invalid-config" | "render-failed";
|
|
10
|
+
export type PhiConfigReader = {
|
|
11
|
+
/** Where this reader stands, for nested readers and errors. */
|
|
12
|
+
readonly path: string;
|
|
13
|
+
readonly record: Readonly<Record<string, unknown>>;
|
|
14
|
+
has(key: string): boolean;
|
|
15
|
+
string(key: string): string | undefined;
|
|
16
|
+
number(key: string): number | undefined;
|
|
17
|
+
integer(key: string): number | undefined;
|
|
18
|
+
boolean(key: string): boolean | undefined;
|
|
19
|
+
/** One of a closed list of values. */
|
|
20
|
+
oneOf<const T extends string | number>(key: string, values: readonly T[]): T | undefined;
|
|
21
|
+
/** A value a reader of the caller's checks; `read` answers `undefined` for a value it does not accept. */
|
|
22
|
+
value<T>(key: string, expected: string, read: (value: unknown) => T | undefined): T | undefined;
|
|
23
|
+
/** A nested object, read by a reader of its own. */
|
|
24
|
+
object<T>(key: string, read: (reader: PhiConfigReader) => T): T | undefined;
|
|
25
|
+
/** A list of objects, each read by a reader of its own. */
|
|
26
|
+
list<T>(key: string, read: (reader: PhiConfigReader, index: number) => T): T[] | undefined;
|
|
27
|
+
/** A list of texts. */
|
|
28
|
+
stringList(key: string): string[] | undefined;
|
|
29
|
+
/** An error at a field of this reader, for a rule the field types cannot state. */
|
|
30
|
+
fail(key: string, expected: string, received?: unknown): never;
|
|
31
|
+
};
|
|
32
|
+
export declare function createPhiConfigReader(record: Readonly<Record<string, unknown>>, path?: string): PhiConfigReader;
|
|
33
|
+
/**
|
|
34
|
+
* The same strict reader over a server's answer. `source` names who answered (`"The media listing"`),
|
|
35
|
+
* so the error says whose shape it was. A value is read as in a config: absent is allowed and reads as
|
|
36
|
+
* absent, present and wrong throws. What the answer must carry, the caller asks for with `required`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function createPhiResponseReader(value: unknown, source: string, path?: string): PhiConfigReader;
|
|
39
|
+
/** A field the record must carry: what `read` answers, or the reader's error for the field. */
|
|
40
|
+
export declare function requirePhiField<T>(reader: PhiConfigReader, key: string, expected: string, read: (key: string) => T | undefined): T;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { isPhiRecord } from "../../../helpers/is-record";
|
|
2
|
+
/*
|
|
3
|
+
* Reading a Widget's or Layout's stored config strictly.
|
|
4
|
+
*
|
|
5
|
+
* A value that is absent -- `undefined`, `null`, or an empty text -- is allowed and reads as absent; the
|
|
6
|
+
* parser decides what absent means. A value that is present and wrong is an error, never a silent
|
|
7
|
+
* `undefined` or a default put in its place: a config that says something the Widget cannot read is a
|
|
8
|
+
* config somebody has to correct, and they can only do that if it is said.
|
|
9
|
+
*
|
|
10
|
+
* The error names where it is (`columns[2].sizing.width`), what was expected and what was found. It is
|
|
11
|
+
* thrown from `parseConfig`; the renderer catches it per block (`phi-cms-layout-renderer.tsx`), so a live
|
|
12
|
+
* page leaves the one Widget out and logs it, while the Builder and the preview show it in the Widget's
|
|
13
|
+
* place, where the field can be corrected.
|
|
14
|
+
*/
|
|
15
|
+
export class PhiConfigError extends Error {
|
|
16
|
+
path;
|
|
17
|
+
expected;
|
|
18
|
+
received;
|
|
19
|
+
constructor(path, expected, received) {
|
|
20
|
+
super(`Config field "${path}" expects ${expected}, found ${describePhiConfigValue(received)}.`);
|
|
21
|
+
this.name = "PhiConfigError";
|
|
22
|
+
this.path = path;
|
|
23
|
+
this.expected = expected;
|
|
24
|
+
this.received = received;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function isPhiConfigError(error) {
|
|
28
|
+
return error instanceof PhiConfigError;
|
|
29
|
+
}
|
|
30
|
+
/** The render issue code for an error: a config nobody can read is said as such, not as a crash. */
|
|
31
|
+
export function resolvePhiRenderFailureCode(error) {
|
|
32
|
+
return error instanceof PhiConfigError || (error instanceof Error && error.name === "PhiConfigError")
|
|
33
|
+
? "invalid-config"
|
|
34
|
+
: "render-failed";
|
|
35
|
+
}
|
|
36
|
+
function describePhiConfigValue(value) {
|
|
37
|
+
if (value === undefined)
|
|
38
|
+
return "nothing";
|
|
39
|
+
if (value === null)
|
|
40
|
+
return "null";
|
|
41
|
+
if (Array.isArray(value))
|
|
42
|
+
return "a list";
|
|
43
|
+
if (isPhiRecord(value))
|
|
44
|
+
return "an object";
|
|
45
|
+
if (typeof value === "string")
|
|
46
|
+
return `the text ${JSON.stringify(value.length > 40 ? `${value.slice(0, 40)}...` : value)}`;
|
|
47
|
+
return `${typeof value} ${String(value)}`;
|
|
48
|
+
}
|
|
49
|
+
function isAbsent(value) {
|
|
50
|
+
return value === undefined || value === null || (typeof value === "string" && value.trim() === "");
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A field a reader found wrong, in a record that did not come from a stored config: a server's answer.
|
|
54
|
+
*
|
|
55
|
+
* The same reading, said as what it is. A response the client cannot read is the server's or the
|
|
56
|
+
* contract's fault, not something an author can correct, so it is not a `PhiConfigError` and no
|
|
57
|
+
* Builder offers to fix it.
|
|
58
|
+
*/
|
|
59
|
+
class PhiResponseShapeError extends Error {
|
|
60
|
+
source;
|
|
61
|
+
path;
|
|
62
|
+
expected;
|
|
63
|
+
received;
|
|
64
|
+
constructor(source, path, expected, received) {
|
|
65
|
+
super(`${source} answered "${path}" with ${describePhiConfigValue(received)}, expected ${expected}.`);
|
|
66
|
+
this.name = "PhiResponseShapeError";
|
|
67
|
+
this.source = source;
|
|
68
|
+
this.path = path;
|
|
69
|
+
this.expected = expected;
|
|
70
|
+
this.received = received;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export function createPhiConfigReader(record, path = "") {
|
|
74
|
+
return createPhiStrictReader(record, path, (at, expected, received) => new PhiConfigError(at, expected, received));
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The same strict reader over a server's answer. `source` names who answered (`"The media listing"`),
|
|
78
|
+
* so the error says whose shape it was. A value is read as in a config: absent is allowed and reads as
|
|
79
|
+
* absent, present and wrong throws. What the answer must carry, the caller asks for with `required`.
|
|
80
|
+
*/
|
|
81
|
+
export function createPhiResponseReader(value, source, path = "") {
|
|
82
|
+
if (!isPhiRecord(value)) {
|
|
83
|
+
throw new PhiResponseShapeError(source, path || "(answer)", "an object", value);
|
|
84
|
+
}
|
|
85
|
+
return createPhiStrictReader(value, path, (at, expected, received) => new PhiResponseShapeError(source, at, expected, received));
|
|
86
|
+
}
|
|
87
|
+
/** A field the record must carry: what `read` answers, or the reader's error for the field. */
|
|
88
|
+
export function requirePhiField(reader, key, expected, read) {
|
|
89
|
+
const value = read(key);
|
|
90
|
+
return value === undefined ? reader.fail(key, expected) : value;
|
|
91
|
+
}
|
|
92
|
+
function createPhiStrictReader(record, path, makeError) {
|
|
93
|
+
const at = (key) => (path ? `${path}.${key}` : key);
|
|
94
|
+
const fail = (key, expected, received = record[key]) => {
|
|
95
|
+
throw makeError(at(key), expected, received);
|
|
96
|
+
};
|
|
97
|
+
const reader = {
|
|
98
|
+
path,
|
|
99
|
+
record,
|
|
100
|
+
has: (key) => !isAbsent(record[key]),
|
|
101
|
+
string(key) {
|
|
102
|
+
const value = record[key];
|
|
103
|
+
if (isAbsent(value))
|
|
104
|
+
return undefined;
|
|
105
|
+
return typeof value === "string" ? value : fail(key, "a text");
|
|
106
|
+
},
|
|
107
|
+
number(key) {
|
|
108
|
+
const value = record[key];
|
|
109
|
+
if (isAbsent(value))
|
|
110
|
+
return undefined;
|
|
111
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fail(key, "a number");
|
|
112
|
+
},
|
|
113
|
+
integer(key) {
|
|
114
|
+
const value = record[key];
|
|
115
|
+
if (isAbsent(value))
|
|
116
|
+
return undefined;
|
|
117
|
+
return typeof value === "number" && Number.isInteger(value) ? value : fail(key, "a whole number");
|
|
118
|
+
},
|
|
119
|
+
boolean(key) {
|
|
120
|
+
const value = record[key];
|
|
121
|
+
if (isAbsent(value))
|
|
122
|
+
return undefined;
|
|
123
|
+
return typeof value === "boolean" ? value : fail(key, "true or false");
|
|
124
|
+
},
|
|
125
|
+
oneOf(key, values) {
|
|
126
|
+
const value = record[key];
|
|
127
|
+
if (isAbsent(value))
|
|
128
|
+
return undefined;
|
|
129
|
+
return values.includes(value)
|
|
130
|
+
? value
|
|
131
|
+
: fail(key, `one of ${values.map((entry) => JSON.stringify(entry)).join(", ")}`);
|
|
132
|
+
},
|
|
133
|
+
value(key, expected, read) {
|
|
134
|
+
const value = record[key];
|
|
135
|
+
if (isAbsent(value))
|
|
136
|
+
return undefined;
|
|
137
|
+
const result = read(value);
|
|
138
|
+
return result === undefined ? fail(key, expected) : result;
|
|
139
|
+
},
|
|
140
|
+
object(key, read) {
|
|
141
|
+
const value = record[key];
|
|
142
|
+
if (isAbsent(value))
|
|
143
|
+
return undefined;
|
|
144
|
+
return isPhiRecord(value) ? read(createPhiStrictReader(value, at(key), makeError)) : fail(key, "an object");
|
|
145
|
+
},
|
|
146
|
+
list(key, read) {
|
|
147
|
+
const value = record[key];
|
|
148
|
+
if (isAbsent(value))
|
|
149
|
+
return undefined;
|
|
150
|
+
if (!Array.isArray(value))
|
|
151
|
+
return fail(key, "a list");
|
|
152
|
+
return value.map((entry, index) => {
|
|
153
|
+
const entryPath = `${at(key)}[${index}]`;
|
|
154
|
+
if (!isPhiRecord(entry))
|
|
155
|
+
throw makeError(entryPath, "an object", entry);
|
|
156
|
+
return read(createPhiStrictReader(entry, entryPath, makeError), index);
|
|
157
|
+
});
|
|
158
|
+
},
|
|
159
|
+
stringList(key) {
|
|
160
|
+
const value = record[key];
|
|
161
|
+
if (isAbsent(value))
|
|
162
|
+
return undefined;
|
|
163
|
+
if (!Array.isArray(value))
|
|
164
|
+
return fail(key, "a list of texts");
|
|
165
|
+
return value.map((entry, index) => {
|
|
166
|
+
if (typeof entry !== "string")
|
|
167
|
+
throw makeError(`${at(key)}[${index}]`, "a text", entry);
|
|
168
|
+
return entry;
|
|
169
|
+
});
|
|
170
|
+
},
|
|
171
|
+
fail,
|
|
172
|
+
};
|
|
173
|
+
return reader;
|
|
174
|
+
}
|
|
@@ -81,6 +81,7 @@ const PHI_EFFECTS_WIDGET_LABEL_SET = definePhiLabelSet({
|
|
|
81
81
|
origin_bottom_right: PHI_EFFECTS_WIDGET_DEFAULT_LABELS.origins.bottomRight,
|
|
82
82
|
viewport_x: PHI_EFFECTS_WIDGET_DEFAULT_LABELS.viewport.x,
|
|
83
83
|
viewport_y: PHI_EFFECTS_WIDGET_DEFAULT_LABELS.viewport.y,
|
|
84
|
+
viewport_z: PHI_EFFECTS_WIDGET_DEFAULT_LABELS.viewport.z,
|
|
84
85
|
viewport_translate: PHI_EFFECTS_WIDGET_DEFAULT_LABELS.viewport.translate,
|
|
85
86
|
viewport_opacity: PHI_EFFECTS_WIDGET_DEFAULT_LABELS.viewport.opacity,
|
|
86
87
|
viewport_rotate: PHI_EFFECTS_WIDGET_DEFAULT_LABELS.viewport.rotate,
|
|
@@ -191,6 +192,7 @@ export async function getPhiEffectsWidgetLabels(options) {
|
|
|
191
192
|
viewport: {
|
|
192
193
|
x: labels.viewport_x,
|
|
193
194
|
y: labels.viewport_y,
|
|
195
|
+
z: labels.viewport_z,
|
|
194
196
|
translate: labels.viewport_translate,
|
|
195
197
|
opacity: labels.viewport_opacity,
|
|
196
198
|
rotate: labels.viewport_rotate,
|