@phis/ui 0.4.2 → 0.4.4
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-anchor-nav-control.js +4 -1
- package/components/controls/phi-background-control.js +21 -6
- package/components/controls/phi-bullet-list-control.d.ts +7 -2
- package/components/controls/phi-bullet-list-control.js +35 -5
- package/components/controls/phi-color-control.js +4 -2
- package/components/controls/phi-drawer-control-adapter.js +3 -2
- package/components/controls/phi-inline-text-editor.js +7 -1
- package/components/controls/phi-length-control.js +1 -1
- package/components/controls/phi-ribbon-control.d.ts +38 -0
- package/components/controls/phi-ribbon-control.js +57 -0
- package/components/controls/phi-ribbon-wrap-control.d.ts +26 -0
- package/components/controls/phi-ribbon-wrap-control.js +25 -0
- package/components/controls/phi-select-control.js +5 -2
- package/components/controls/phi-series-chart-control.d.ts +48 -0
- package/components/controls/phi-series-chart-control.js +82 -0
- package/components/controls/phi-table-control.d.ts +3 -0
- package/components/controls/phi-table-control.js +13 -1
- package/components/controls/phi-text-control.js +57 -3
- package/components/controls/phi-tree-control.js +4 -4
- package/components/controls/phi-truncated-hover-text.d.ts +15 -0
- package/components/controls/phi-truncated-hover-text.js +25 -0
- package/components/forms/link-target-form-control.d.ts +1 -1
- package/components/forms/link-target-form-control.js +2 -45
- package/components/forms/phi-form-widget-frame.js +4 -2
- package/components/forms/shared-form-provider-registry.js +1 -4
- package/components/layouts/clients/phi-three-column-layout-client.js +10 -1
- 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/navigation/phi-link.js +6 -1
- package/components/overlays/phi-overlay-container-client.js +16 -1
- package/components/regions/clients/cms-region-container-client.js +6 -1
- package/components/regions/phi-cms-region-container.js +3 -0
- package/components/regions/phi-cms-region-shell.d.ts +12 -1
- package/components/regions/phi-cms-region-shell.js +81 -19
- package/components/regions/presets/phi-settings-page-shell-tree.js +48 -1
- package/components/root/phi-config-provider.d.ts +13 -0
- package/components/root/phi-config-provider.js +12 -1
- package/components/root/phi-root-background.d.ts +7 -0
- package/components/root/phi-root-background.js +20 -1
- package/components/root/phi-root-theme-resolver.js +3 -0
- package/components/root/phi-shell-chrome-overlay.d.ts +12 -1
- package/components/root/phi-shell-chrome-overlay.js +19 -3
- package/components/runtime/runtime-controller-materialization.js +12 -1
- package/components/shell/phi-antd-icon.d.ts +1 -0
- package/components/shell/phi-antd-icon.js +3 -0
- package/components/shell/use-phi-shell-sticky-top.js +61 -16
- package/components/state/scoped-state-store.js +20 -3
- package/components/surface/phi-surface-tone.d.ts +13 -0
- package/components/surface/phi-surface-tone.js +21 -0
- 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 +109 -0
- package/components/widgets/client/html-editor.js +1 -1
- package/components/widgets/client/shared/use-page-reference-options.d.ts +17 -0
- package/components/widgets/client/shared/use-page-reference-options.js +57 -0
- package/components/widgets/config/background.d.ts +32 -1
- package/components/widgets/config/background.js +41 -2
- 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/background.js +2 -0
- package/components/widgets/label-sets/effects.js +2 -0
- package/components/widgets/label-types/background.d.ts +1 -0
- package/components/widgets/label-types/background.js +1 -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 +27 -1
- package/components/widgets/shared/bullet-list-vocabulary.js +24 -4
- package/components/widgets/shared/card-body-client.d.ts +18 -4
- package/components/widgets/shared/card-body-client.js +126 -26
- package/components/widgets/shared/card-vocabulary.d.ts +31 -4
- package/components/widgets/shared/card-vocabulary.js +35 -2
- package/components/widgets/shared/markdown-body-client.js +39 -10
- 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/cms-container-chrome.js +6 -1
- package/helpers/locale.d.ts +8 -0
- package/helpers/locale.js +18 -0
- package/helpers/renderable-block-serialization.js +1 -1
- package/helpers/shell-region-style.js +2 -1
- package/helpers/surface-style.d.ts +12 -1
- package/helpers/surface-style.js +28 -2
- package/module-dashboard-cards.d.ts +1 -1
- package/module-dashboard-cards.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/area-definitions.d.ts +0 -42
- package/plugins/runtime-modules/area-definitions.js +3 -8
- 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 +25 -35
- 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/layout-scaffold-overlays.js +10 -5
- 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 +4 -3
- 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 +28 -18
- 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 +17 -10
- package/plugins/runtime-modules/builder/widgets/structure-region/built-in.js +27 -22
- 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-authoring-module.d.ts +8 -1
- package/plugins/runtime-modules/client-authoring-module.js +13 -3
- 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/brand/client.d.ts +12 -3
- package/plugins/runtime-modules/core/widgets/brand/client.js +3 -11
- package/plugins/runtime-modules/core/widgets/brand/home.d.ts +15 -0
- package/plugins/runtime-modules/core/widgets/brand/home.js +18 -0
- package/plugins/runtime-modules/core/widgets/brand/plugin.js +3 -2
- package/plugins/runtime-modules/core/widgets/brand/server.d.ts +2 -2
- package/plugins/runtime-modules/core/widgets/brand/server.js +2 -2
- package/plugins/runtime-modules/core/widgets/card/collection-view-binding.js +25 -9
- package/plugins/runtime-modules/core/widgets/card/config.d.ts +301 -254
- package/plugins/runtime-modules/core/widgets/card/config.js +68 -3
- package/plugins/runtime-modules/core/widgets/card/editor.js +41 -4
- package/plugins/runtime-modules/core/widgets/card/server.d.ts +3 -0
- package/plugins/runtime-modules/core/widgets/card/server.js +32 -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/client.js +3 -3
- package/plugins/runtime-modules/core/widgets/description/config.d.ts +2 -25
- package/plugins/runtime-modules/core/widgets/description/editor.js +7 -63
- package/plugins/runtime-modules/core/widgets/description/server.js +3 -2
- package/plugins/runtime-modules/core/widgets/entry-list/config.d.ts +2 -25
- package/plugins/runtime-modules/core/widgets/gallery/config.d.ts +2 -25
- package/plugins/runtime-modules/core/widgets/header-navigation/client.js +2 -2
- package/plugins/runtime-modules/core/widgets/html/client.js +1 -1
- package/plugins/runtime-modules/core/widgets/image/config.js +2 -2
- package/plugins/runtime-modules/core/widgets/markdown/config.d.ts +320 -2
- package/plugins/runtime-modules/core/widgets/markdown/config.js +18 -4
- package/plugins/runtime-modules/core/widgets/markdown/editor.js +11 -4
- package/plugins/runtime-modules/core/widgets/markdown-toc/client.js +6 -1
- package/plugins/runtime-modules/core/widgets/markdown-toc/config.d.ts +14 -0
- package/plugins/runtime-modules/core/widgets/markdown-toc/config.js +14 -2
- package/plugins/runtime-modules/core/widgets/quick-links/authoring.js +7 -2
- package/plugins/runtime-modules/core/widgets/quick-links/client.js +2 -2
- package/plugins/runtime-modules/core/widgets/quick-links/items.d.ts +0 -2
- package/plugins/runtime-modules/core/widgets/quick-links/items.js +0 -15
- package/plugins/runtime-modules/core/widgets/result/config.d.ts +5 -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/sidebar-navigation/authoring.js +1 -1
- package/plugins/runtime-modules/core/widgets/sidebar-navigation/client.js +1 -1
- package/plugins/runtime-modules/core/widgets/simple-text/client.js +8 -3
- package/plugins/runtime-modules/core/widgets/simple-text/config.d.ts +2 -25
- package/plugins/runtime-modules/core/widgets/simple-text/editor.js +2 -2
- 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 +6 -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 +7 -0
- package/plugins/runtime-modules/dashboard/card-rows.js +87 -0
- package/plugins/runtime-modules/dashboard/clients/dashboard-card-view.js +32 -12
- package/plugins/runtime-modules/dashboard/services/collection.js +17 -7
- package/plugins/runtime-modules/dashboard/trees/phi-default-builder-dashboard-page-tree.js +5 -40
- 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 +111 -72
- package/plugins/runtime-modules/theme/widgets/brand-controls/config.d.ts +2 -1
- package/plugins/runtime-modules/theme/widgets/brand-controls/config.js +28 -31
- 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/server-helpers/cms-link-targets.js +6 -0
- package/styles/control-shape.css +11 -11
- package/styles/controls.css +67 -5
- package/styles/layout.css +61 -9
- package/styles/shell.css +73 -0
- package/theme/phi-antd-token-resolver.js +20 -1
- package/theme/phi-theme-tokens.d.ts +1 -1
- package/theme/phi-theme-tokens.js +1 -0
- package/theme/phi-theme.d.ts +5 -0
- package/theme/phi-theme.js +21 -0
- package/theme/phi-tokens.d.ts +16 -15
- package/theme/phi-tokens.js +16 -15
- package/types/cms-config.js +142 -185
- package/types/cms-plugins.d.ts +6 -11
- package/types/core-widget-placements.d.ts +14 -3
- package/types/dashboard-cards.d.ts +48 -4
- package/types/dashboard-cards.js +6 -0
- package/types/media.d.ts +0 -3
- package/types/renderable-block.d.ts +6 -1
- package/types/site-theme.d.ts +10 -2
- 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,13 +4,13 @@ import { resolvePhiMediaAssetTypeLabel } from "./asset-type-label";
|
|
|
4
4
|
import { PhiTagControl } from "../controls/phi-tag-control";
|
|
5
5
|
import { PhiButtonControl } from "../controls/phi-button-control";
|
|
6
6
|
import NextImage from "next/image";
|
|
7
|
-
import {
|
|
7
|
+
import { useMemo, useRef, useState } from "react";
|
|
8
8
|
import { PhiCollectionLayoutControl } from "../controls/phi-collection-layout-control";
|
|
9
9
|
import { resolvePhiMediaKindFromContentType, } from "../../constants/media";
|
|
10
10
|
import { normalizePhiImagePreviewTile, resolvePhiMediaAssetDisplayDimensions, } from "./phi-image-preview-data";
|
|
11
11
|
import { PhiMediaKindIcon } from "./phi-media-kind-icon";
|
|
12
|
-
import {
|
|
13
|
-
import { PHI_MEDIA_UPLOAD_DEFAULT_LABELS,
|
|
12
|
+
import { resolvePhiMediaUploadInitOptions, } from "./media-upload-flow";
|
|
13
|
+
import { PHI_MEDIA_UPLOAD_DEFAULT_LABELS, usePhiMediaUpload, } from "./phi-media-upload";
|
|
14
14
|
import { usePhiImagePreviewStore } from "./phi-image-preview-store";
|
|
15
15
|
import { PHI_ASSET_CONTROLLER_STORE_KEY } from "./asset-controller-signals";
|
|
16
16
|
import { applyPhiAssetCollectionData, buildPhiAssetCollectionQuery, PHI_ASSET_COLLECTION_DATA_SOURCE, } from "./asset-collection-runtime";
|
|
@@ -55,25 +55,37 @@ function resolveUploadStatusLabel(item, labels) {
|
|
|
55
55
|
return labels.doneLabel;
|
|
56
56
|
}
|
|
57
57
|
/**
|
|
58
|
-
* The Widget's own wording over the shared reading of every refusal
|
|
59
|
-
*
|
|
60
|
-
* It used to know about duplicates and nothing else, and fell through to `error.message` -- which put a
|
|
61
|
-
* control plane string in front of a person for every other refusal, including the two the Site itself
|
|
62
|
-
* now states. Only the duplicate has a label of its own here; the rest come from one place.
|
|
58
|
+
* The Widget's own wording over the shared reading of every refusal: its duplicate, failure and storage
|
|
59
|
+
* texts, and the shared ones for the rest -- so no control plane string reaches a person.
|
|
63
60
|
*/
|
|
64
|
-
function
|
|
65
|
-
|
|
66
|
-
if ((error instanceof PhiMediaUploadError && error.code === "media_asset_exists") ||
|
|
67
|
-
typedError?.code === "media_asset_exists" ||
|
|
68
|
-
typedError?.message === "media_asset_exists") {
|
|
69
|
-
return labels.assetExistsLabel;
|
|
70
|
-
}
|
|
71
|
-
return readPhiMediaUploadErrorMessage(error, {
|
|
61
|
+
function resolveAreaUploadLabels(labels) {
|
|
62
|
+
return {
|
|
72
63
|
...PHI_MEDIA_UPLOAD_DEFAULT_LABELS,
|
|
73
64
|
errorGeneric: labels.uploadFailedText,
|
|
74
65
|
errorDuplicate: labels.assetExistsLabel,
|
|
75
66
|
errorStorageUnreachable: labels.uploadStorageUnreachableText,
|
|
76
|
-
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/** A file still on its way, or one that failed, drawn as a tile like the Assets that arrived. */
|
|
70
|
+
function buildPendingWallItem(item, presentationFlags) {
|
|
71
|
+
return {
|
|
72
|
+
localId: item.localId,
|
|
73
|
+
assetId: -1,
|
|
74
|
+
file: item.file,
|
|
75
|
+
title: item.file.name.replace(/\.[^.]+$/, "").trim() || item.file.name,
|
|
76
|
+
originalName: item.file.name,
|
|
77
|
+
thumbnailUrl: null,
|
|
78
|
+
previewUrl: null,
|
|
79
|
+
contentType: item.file.type,
|
|
80
|
+
kind: resolvePhiMediaKindFromContentType(item.file.type),
|
|
81
|
+
width: null,
|
|
82
|
+
height: null,
|
|
83
|
+
presentationFlags,
|
|
84
|
+
progress: item.progress,
|
|
85
|
+
status: item.status,
|
|
86
|
+
deleting: false,
|
|
87
|
+
error: item.error,
|
|
88
|
+
};
|
|
77
89
|
}
|
|
78
90
|
function resolveUploadWallDisplayDimensions(item) {
|
|
79
91
|
return resolvePhiMediaAssetDisplayDimensions(item);
|
|
@@ -88,20 +100,6 @@ export function PhiAreaUploadBinding({ config, labels, onUploadComplete, collect
|
|
|
88
100
|
const previewState = usePhiImagePreviewStore(PHI_ASSET_CONTROLLER_STORE_KEY);
|
|
89
101
|
const activateCollectionAction = usePhiCollectionProviderAction(PHI_ASSET_COLLECTION_DATA_SOURCE);
|
|
90
102
|
const dragDepth = useRef(0);
|
|
91
|
-
/*
|
|
92
|
-
* Uploads still on their way. The wall belongs to whatever shows it -- a Media picker in an Overlay
|
|
93
|
-
* more often than not -- and closing that stops them, so no Asset is finalized that nobody sees
|
|
94
|
-
* arrive. The Server is told `cancelled` and clears up what had arrived.
|
|
95
|
-
*/
|
|
96
|
-
const runningUploads = useRef(new Set());
|
|
97
|
-
useEffect(() => {
|
|
98
|
-
const controllers = runningUploads.current;
|
|
99
|
-
return () => {
|
|
100
|
-
for (const controller of controllers)
|
|
101
|
-
controller.abort();
|
|
102
|
-
controllers.clear();
|
|
103
|
-
};
|
|
104
|
-
}, []);
|
|
105
103
|
const [uploadWall, setUploadWall] = useState([]);
|
|
106
104
|
const [isDragActive, setIsDragActive] = useState(false);
|
|
107
105
|
const allowDelete = config?.allowDelete ?? true;
|
|
@@ -166,73 +164,31 @@ export function PhiAreaUploadBinding({ config, labels, onUploadComplete, collect
|
|
|
166
164
|
showUploadMessage("error", error instanceof Error ? error.message : labels.deleteFailedText);
|
|
167
165
|
}
|
|
168
166
|
}
|
|
169
|
-
function startUploadWallItem(file, localId) {
|
|
170
|
-
const normalizedTitle = file.name.replace(/\.[^.]+$/, "").trim() || file.name;
|
|
171
|
-
const nextLocalId = localId ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
172
|
-
setUploadWall((current) => [
|
|
173
|
-
{
|
|
174
|
-
localId: nextLocalId,
|
|
175
|
-
assetId: -1,
|
|
176
|
-
file,
|
|
177
|
-
title: normalizedTitle,
|
|
178
|
-
originalName: file.name,
|
|
179
|
-
thumbnailUrl: null,
|
|
180
|
-
previewUrl: null,
|
|
181
|
-
contentType: file.type,
|
|
182
|
-
kind: resolvePhiMediaKindFromContentType(file.type),
|
|
183
|
-
width: null,
|
|
184
|
-
height: null,
|
|
185
|
-
presentationFlags: uploadInitOptions.presentationFlags ?? 0,
|
|
186
|
-
progress: 0,
|
|
187
|
-
status: "uploading",
|
|
188
|
-
deleting: false,
|
|
189
|
-
error: null,
|
|
190
|
-
},
|
|
191
|
-
...current.filter((item) => item.localId !== nextLocalId),
|
|
192
|
-
]);
|
|
193
|
-
return nextLocalId;
|
|
194
|
-
}
|
|
195
167
|
/*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
* came from `uploadWall` below. Dropping the primitive dropped that second, invisible copy with it.
|
|
201
|
-
* A retry from the wall comes through here as well, into the tile it already has (`localId`).
|
|
168
|
+
* The transport is the shared one (`usePhiMediaUpload`): progress, refusals and their wording, retry,
|
|
169
|
+
* and stopping what is still on its way when the wall goes away -- a Media picker in an Overlay more
|
|
170
|
+
* often than not -- so no Asset is finalized that nobody sees arrive. What is this Widget's own is the
|
|
171
|
+
* wall: an Asset that arrived becomes a tile, and the listing behind it hears of it.
|
|
202
172
|
*/
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
}, uploadInitOptions, controller.signal);
|
|
211
|
-
if (controller.signal.aborted)
|
|
212
|
-
return;
|
|
213
|
-
const uploadedTile = normalizePhiImagePreviewTile(payload.asset, []);
|
|
214
|
-
setUploadWall((current) => current.map((item) => item.localId === nextLocalId ? buildUploadWallItem(uploadedTile, nextLocalId) : item));
|
|
173
|
+
const areaUploadLabels = useMemo(() => resolveAreaUploadLabels(labels), [labels]);
|
|
174
|
+
const upload = usePhiMediaUpload({
|
|
175
|
+
labels: areaUploadLabels,
|
|
176
|
+
initOptions: uploadInitOptions,
|
|
177
|
+
onUploaded: (asset, file) => {
|
|
178
|
+
const uploadedTile = normalizePhiImagePreviewTile(asset, []);
|
|
179
|
+
setUploadWall((current) => [buildUploadWallItem(uploadedTile, `asset-${uploadedTile.id}`), ...current]);
|
|
215
180
|
onUploadComplete?.();
|
|
216
181
|
showUploadMessage("success", labels.uploadSuccessTemplate.replace("%1", file.name));
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
error: errorMessage,
|
|
228
|
-
}
|
|
229
|
-
: item));
|
|
230
|
-
showUploadMessage("error", errorMessage);
|
|
231
|
-
}
|
|
232
|
-
finally {
|
|
233
|
-
runningUploads.current.delete(controller);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
182
|
+
},
|
|
183
|
+
onRejected: (message) => showUploadMessage("error", message),
|
|
184
|
+
});
|
|
185
|
+
// The shared items are on their way or failed; an arrived one is a tile in the wall instead.
|
|
186
|
+
const wallItems = [
|
|
187
|
+
...upload.items
|
|
188
|
+
.filter((item) => item.status !== "done")
|
|
189
|
+
.map((item) => buildPendingWallItem(item, uploadInitOptions.presentationFlags ?? 0)),
|
|
190
|
+
...uploadWall,
|
|
191
|
+
];
|
|
236
192
|
return (_jsxs(PhiFlexControl, { vertical: true, gap: 12, style: { width: "100%" }, children: [_jsx(PhiFileDropGuard, {}), _jsx("div", { "data-phi-media-dropzone": "true", onDragEnter: () => {
|
|
237
193
|
dragDepth.current += 1;
|
|
238
194
|
setIsDragActive(true);
|
|
@@ -248,7 +204,7 @@ export function PhiAreaUploadBinding({ config, labels, onUploadComplete, collect
|
|
|
248
204
|
}, onDrop: () => {
|
|
249
205
|
dragDepth.current = 0;
|
|
250
206
|
setIsDragActive(false);
|
|
251
|
-
}, style: { width: "100%" }, children: _jsx(PhiFileDropControl, { dropZone: true, multiple: multiple, accept: accept, onFile: (file) => void
|
|
207
|
+
}, style: { width: "100%" }, children: _jsx(PhiFileDropControl, { dropZone: true, multiple: multiple, accept: accept, onFile: (file) => void upload.upload(file), style: {
|
|
252
208
|
padding: 0,
|
|
253
209
|
width: "100%",
|
|
254
210
|
borderColor: isDragActive ? token.colorPrimary : undefined,
|
|
@@ -258,7 +214,7 @@ export function PhiAreaUploadBinding({ config, labels, onUploadComplete, collect
|
|
|
258
214
|
minHeight: uploadTileMinHeight,
|
|
259
215
|
justifyContent: "center",
|
|
260
216
|
cursor: isDragActive ? "copy" : "default",
|
|
261
|
-
}, children: [_jsx(PhiIcon, { name: "upload", size: "inherit", style: { fontSize: uploadTileIconSize, color: token.colorTextSecondary } }), _jsx(PhiTypographyControl, { strong: true, children: labels.dropTitle }), _jsx(PhiTypographyControl, { type: "secondary", children: labels.dropHint })] }) }) }), _jsx(PhiCollectionLayoutControl, { mode: "grid", gap: token.paddingSM, minColumnWidth: uploadTileFrameSize, items:
|
|
217
|
+
}, children: [_jsx(PhiIcon, { name: "upload", size: "inherit", style: { fontSize: uploadTileIconSize, color: token.colorTextSecondary } }), _jsx(PhiTypographyControl, { strong: true, children: labels.dropTitle }), _jsx(PhiTypographyControl, { type: "secondary", children: labels.dropHint })] }) }) }), _jsx(PhiCollectionLayoutControl, { mode: "grid", gap: token.paddingSM, minColumnWidth: uploadTileFrameSize, items: wallItems.map((item) => {
|
|
262
218
|
const displayDimensions = resolveUploadWallDisplayDimensions(item);
|
|
263
219
|
return (_jsxs("div", { style: {
|
|
264
220
|
border: resolvePhiThemeBorderLine(token.colorBorderSecondary),
|
|
@@ -316,7 +272,7 @@ export function PhiAreaUploadBinding({ config, labels, onUploadComplete, collect
|
|
|
316
272
|
}, children: _jsx(PhiSpinControl, {}) })) : null] }), _jsxs(PhiFlexControl, { vertical: true, gap: 6, style: { padding: token.paddingSM }, children: [_jsx(PhiFlexControl, { align: "center", justify: "space-between", gap: 8, children: _jsx(PhiTypographyControl, { strong: true, ellipsis: true, title: item.title ?? item.originalName, style: { minWidth: 0 }, children: item.title ?? item.originalName }) }), _jsxs(PhiFlexControl, { align: "center", justify: "space-between", gap: 8, wrap: true, children: [_jsx(PhiTypographyControl, { type: "secondary", ellipsis: true, style: { display: "block", minWidth: 0 }, children: resolvePhiMediaAssetTypeLabel(item.kind, item.contentType, item.originalName) }), allowDelete && item.assetId > 0 ? (_jsx(PhiButtonControl, { ariaLabel: labels.deleteLabel, icon: _jsx(PhiIcon, { name: "delete", size: "inherit" }), type: "text", size: "small", danger: true, onClick: () => {
|
|
317
273
|
void deleteUploadedAsset(item.localId, item.assetId);
|
|
318
274
|
} })) : null] }), _jsxs(PhiFlexControl, { align: "center", gap: 6, wrap: true, children: [_jsx(PhiTagControl, { color: item.status === "error" ? "red" : item.status === "uploading" ? "blue" : "green", children: resolveUploadStatusLabel(item, labels) }), item.status === "error" && item.file ? (_jsx(PhiButtonControl, { type: "primary", size: "small", onClick: () => {
|
|
319
|
-
void
|
|
275
|
+
void upload.retry(item.localId);
|
|
320
276
|
}, label: labels.retryLabel })) : null] })] })] }, item.localId));
|
|
321
277
|
}) })] }));
|
|
322
278
|
}
|
|
@@ -3,6 +3,19 @@ export type PhiImagePreviewApiRecord = Omit<PhiMediaAsset, "kind"> & {
|
|
|
3
3
|
thumbnailUrl?: string | null;
|
|
4
4
|
previewUrl?: string | null;
|
|
5
5
|
};
|
|
6
|
+
/** The media API's Folders, read strictly; `source` names who answered. */
|
|
7
|
+
export declare function readPhiMediaFolders(value: unknown, source?: string): PhiMediaAssetFolder[];
|
|
8
|
+
/** The media API's Spaces, read strictly. */
|
|
9
|
+
export declare function readPhiMediaSpaces(value: unknown, source?: string): PhiMediaSpaceOption[];
|
|
10
|
+
/** One Space, or `null` where none is reported. */
|
|
11
|
+
export declare function readPhiMediaSpace(value: unknown, source?: string): PhiMediaSpaceOption | null;
|
|
12
|
+
/** The paging the media API reports, or `null` where it reports none. */
|
|
13
|
+
export declare function readPhiMediaPagination(value: unknown, source?: string): PhiMediaPickerPagination | null;
|
|
14
|
+
/**
|
|
15
|
+
* The tiles a media Collection carries. The provider read them from the API already; a Collection
|
|
16
|
+
* holds items of no particular type, so whoever takes them out reads them again instead of casting.
|
|
17
|
+
*/
|
|
18
|
+
export declare function readPhiMediaAssetTiles(value: unknown, source?: string): PhiMediaAssetTile[];
|
|
6
19
|
export declare function resolvePhiMediaAssetDisplayDimensions(asset: Pick<PhiMediaAssetTile, "contentType" | "width" | "height">): {
|
|
7
20
|
width: number;
|
|
8
21
|
height: number;
|
|
@@ -1,4 +1,174 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PHI_MEDIA_SPACE_KINDS } from "@phis/contracts/media";
|
|
2
|
+
import { PhiImageAssetVariantKey, PhiMediaKind, resolvePhiMediaKindFromContentType } from "../../constants/media";
|
|
3
|
+
import { createPhiResponseReader, requirePhiField, } from "../widgets/config/strict-config-reader";
|
|
4
|
+
/*
|
|
5
|
+
* The media API's answer, read field by field.
|
|
6
|
+
*
|
|
7
|
+
* It was cast to its type on arrival, and every reader after it cast again from the Collection's untyped
|
|
8
|
+
* items. A field the server stopped sending, or sent under another name, came through as `undefined`
|
|
9
|
+
* under a type that said otherwise -- the tile type promised a `checksumSha256` the server has never
|
|
10
|
+
* sent. Now a wrong answer is an error naming the field, at the one place the answer arrives, and what a
|
|
11
|
+
* Collection carries afterwards is read with the same readers rather than cast.
|
|
12
|
+
*/
|
|
13
|
+
const PHI_MEDIA_LISTING = "The media listing";
|
|
14
|
+
const PHI_MEDIA_VARIANT_KEYS = Object.values(PhiImageAssetVariantKey);
|
|
15
|
+
const PHI_MEDIA_KINDS = Object.values(PhiMediaKind);
|
|
16
|
+
const nullableInteger = (reader, key) => reader.integer(key) ?? null;
|
|
17
|
+
const nullableNumber = (reader, key) => reader.number(key) ?? null;
|
|
18
|
+
const nullableString = (reader, key) => reader.string(key) ?? null;
|
|
19
|
+
const requiredInteger = (reader, key) => requirePhiField(reader, key, "a whole number", reader.integer);
|
|
20
|
+
const requiredString = (reader, key) => requirePhiField(reader, key, "a text", reader.string);
|
|
21
|
+
function readPhiMediaFolderFields(reader) {
|
|
22
|
+
const createdAt = reader.string("createdAt");
|
|
23
|
+
const updatedAt = reader.string("updatedAt");
|
|
24
|
+
return {
|
|
25
|
+
id: requiredInteger(reader, "id"),
|
|
26
|
+
siteId: requiredInteger(reader, "siteId"),
|
|
27
|
+
spaceId: requiredInteger(reader, "spaceId"),
|
|
28
|
+
parentId: nullableInteger(reader, "parentId"),
|
|
29
|
+
name: requiredString(reader, "name"),
|
|
30
|
+
sortOrder: requiredInteger(reader, "sortOrder"),
|
|
31
|
+
flags: requiredInteger(reader, "flags"),
|
|
32
|
+
...(createdAt ? { createdAt } : {}),
|
|
33
|
+
...(updatedAt ? { updatedAt } : {}),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function readPhiMediaSpaceFields(reader) {
|
|
37
|
+
return {
|
|
38
|
+
id: nullableInteger(reader, "id"),
|
|
39
|
+
address: requiredString(reader, "address"),
|
|
40
|
+
kind: requirePhiField(reader, "kind", "a Media Space kind", (key) => reader.oneOf(key, PHI_MEDIA_SPACE_KINDS)),
|
|
41
|
+
groupId: nullableInteger(reader, "groupId"),
|
|
42
|
+
name: nullableString(reader, "name"),
|
|
43
|
+
quotaBytes: nullableInteger(reader, "quotaBytes"),
|
|
44
|
+
usedBytes: requiredInteger(reader, "usedBytes"),
|
|
45
|
+
reservedBytes: requiredInteger(reader, "reservedBytes"),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function readPhiMediaPaginationFields(reader) {
|
|
49
|
+
const total = reader.integer("total");
|
|
50
|
+
return {
|
|
51
|
+
page: requiredInteger(reader, "page"),
|
|
52
|
+
pageSize: requiredInteger(reader, "pageSize"),
|
|
53
|
+
...(total === undefined ? {} : { total }),
|
|
54
|
+
hasMore: requirePhiField(reader, "hasMore", "true or false", reader.boolean),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function readPhiMediaVariantFields(reader) {
|
|
58
|
+
const createdAt = reader.string("createdAt");
|
|
59
|
+
const updatedAt = reader.string("updatedAt");
|
|
60
|
+
return {
|
|
61
|
+
assetId: requiredInteger(reader, "assetId"),
|
|
62
|
+
variantKey: requirePhiField(reader, "variantKey", "an image variant key", (key) => reader.oneOf(key, PHI_MEDIA_VARIANT_KEYS)),
|
|
63
|
+
deliveryUrl: requiredString(reader, "deliveryUrl"),
|
|
64
|
+
contentType: requiredString(reader, "contentType"),
|
|
65
|
+
bytes: nullableInteger(reader, "bytes"),
|
|
66
|
+
width: requiredInteger(reader, "width"),
|
|
67
|
+
height: requiredInteger(reader, "height"),
|
|
68
|
+
blurDataUrl: nullableString(reader, "blurDataUrl"),
|
|
69
|
+
flags: requiredInteger(reader, "flags"),
|
|
70
|
+
...(createdAt ? { createdAt } : {}),
|
|
71
|
+
...(updatedAt ? { updatedAt } : {}),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** One Asset as the media API sends it; `kind` is derived from the content type, not taken. */
|
|
75
|
+
function readPhiMediaAssetFields(reader) {
|
|
76
|
+
const createdAt = reader.string("createdAt");
|
|
77
|
+
const updatedAt = reader.string("updatedAt");
|
|
78
|
+
const variantVersion = reader.integer("variantVersion");
|
|
79
|
+
const folder = reader.object("folder", readPhiMediaFolderFields);
|
|
80
|
+
const variants = reader.list("variants", readPhiMediaVariantFields);
|
|
81
|
+
return {
|
|
82
|
+
id: requiredInteger(reader, "id"),
|
|
83
|
+
siteId: requiredInteger(reader, "siteId"),
|
|
84
|
+
spaceId: requiredInteger(reader, "spaceId"),
|
|
85
|
+
folderId: nullableInteger(reader, "folderId"),
|
|
86
|
+
createdByUserId: nullableInteger(reader, "createdByUserId"),
|
|
87
|
+
updatedByUserId: nullableInteger(reader, "updatedByUserId"),
|
|
88
|
+
...(variantVersion === undefined ? {} : { variantVersion }),
|
|
89
|
+
deliveryUrl: requiredString(reader, "deliveryUrl"),
|
|
90
|
+
originalName: requiredString(reader, "originalName"),
|
|
91
|
+
contentType: requiredString(reader, "contentType"),
|
|
92
|
+
bytes: nullableInteger(reader, "bytes"),
|
|
93
|
+
width: nullableNumber(reader, "width"),
|
|
94
|
+
height: nullableNumber(reader, "height"),
|
|
95
|
+
blurDataUrl: nullableString(reader, "blurDataUrl"),
|
|
96
|
+
thumbnailUrl: nullableString(reader, "thumbnailUrl"),
|
|
97
|
+
previewUrl: nullableString(reader, "previewUrl"),
|
|
98
|
+
meta: reader.value("meta", "an object", (value) => (isPlainRecord(value) ? value : undefined)) ?? null,
|
|
99
|
+
title: nullableString(reader, "title"),
|
|
100
|
+
altText: nullableString(reader, "altText"),
|
|
101
|
+
lifecycleStatus: requiredInteger(reader, "lifecycleStatus"),
|
|
102
|
+
deliveryPolicy: requiredInteger(reader, "deliveryPolicy"),
|
|
103
|
+
deliveryRevision: requiredInteger(reader, "deliveryRevision"),
|
|
104
|
+
presentationFlags: requiredInteger(reader, "presentationFlags"),
|
|
105
|
+
...(createdAt ? { createdAt } : {}),
|
|
106
|
+
...(updatedAt ? { updatedAt } : {}),
|
|
107
|
+
...(folder ? { folder } : {}),
|
|
108
|
+
...(variants ? { variants } : {}),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function isPlainRecord(value) {
|
|
112
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
113
|
+
}
|
|
114
|
+
/** The media API's Folders, read strictly; `source` names who answered. */
|
|
115
|
+
export function readPhiMediaFolders(value, source = PHI_MEDIA_LISTING) {
|
|
116
|
+
return createPhiResponseReader({ folders: value }, source).list("folders", readPhiMediaFolderFields) ?? [];
|
|
117
|
+
}
|
|
118
|
+
/** The media API's Spaces, read strictly. */
|
|
119
|
+
export function readPhiMediaSpaces(value, source = PHI_MEDIA_LISTING) {
|
|
120
|
+
return createPhiResponseReader({ spaces: value }, source).list("spaces", readPhiMediaSpaceFields) ?? [];
|
|
121
|
+
}
|
|
122
|
+
/** One Space, or `null` where none is reported. */
|
|
123
|
+
export function readPhiMediaSpace(value, source = PHI_MEDIA_LISTING) {
|
|
124
|
+
return createPhiResponseReader({ activeSpace: value }, source).object("activeSpace", readPhiMediaSpaceFields) ?? null;
|
|
125
|
+
}
|
|
126
|
+
/** The paging the media API reports, or `null` where it reports none. */
|
|
127
|
+
export function readPhiMediaPagination(value, source = PHI_MEDIA_LISTING) {
|
|
128
|
+
return createPhiResponseReader({ pagination: value }, source).object("pagination", readPhiMediaPaginationFields) ?? null;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The tiles a media Collection carries. The provider read them from the API already; a Collection
|
|
132
|
+
* holds items of no particular type, so whoever takes them out reads them again instead of casting.
|
|
133
|
+
*/
|
|
134
|
+
export function readPhiMediaAssetTiles(value, source = PHI_MEDIA_LISTING) {
|
|
135
|
+
return createPhiResponseReader({ assets: value }, source).list("assets", (reader) => {
|
|
136
|
+
const variantVersion = reader.integer("variantVersion");
|
|
137
|
+
const meta = reader.value("meta", "an object", (entry) => (isPlainRecord(entry) ? entry : undefined));
|
|
138
|
+
const blurDataUrl = reader.string("blurDataUrl");
|
|
139
|
+
const folder = reader.object("folder", (folderReader) => ({
|
|
140
|
+
id: requiredInteger(folderReader, "id"),
|
|
141
|
+
name: requiredString(folderReader, "name"),
|
|
142
|
+
}));
|
|
143
|
+
return {
|
|
144
|
+
id: requiredInteger(reader, "id"),
|
|
145
|
+
siteId: requiredInteger(reader, "siteId"),
|
|
146
|
+
spaceId: requiredInteger(reader, "spaceId"),
|
|
147
|
+
folderId: nullableInteger(reader, "folderId"),
|
|
148
|
+
createdByUserId: nullableInteger(reader, "createdByUserId"),
|
|
149
|
+
updatedByUserId: nullableInteger(reader, "updatedByUserId"),
|
|
150
|
+
...(variantVersion === undefined ? {} : { variantVersion }),
|
|
151
|
+
originalName: requiredString(reader, "originalName"),
|
|
152
|
+
title: nullableString(reader, "title"),
|
|
153
|
+
altText: nullableString(reader, "altText"),
|
|
154
|
+
deliveryUrl: requiredString(reader, "deliveryUrl"),
|
|
155
|
+
thumbnailUrl: nullableString(reader, "thumbnailUrl"),
|
|
156
|
+
previewUrl: nullableString(reader, "previewUrl"),
|
|
157
|
+
contentType: requiredString(reader, "contentType"),
|
|
158
|
+
kind: requirePhiField(reader, "kind", "a media kind", (key) => reader.oneOf(key, PHI_MEDIA_KINDS)),
|
|
159
|
+
width: nullableNumber(reader, "width"),
|
|
160
|
+
height: nullableNumber(reader, "height"),
|
|
161
|
+
bytes: nullableInteger(reader, "bytes"),
|
|
162
|
+
lifecycleStatus: requiredInteger(reader, "lifecycleStatus"),
|
|
163
|
+
deliveryPolicy: requiredInteger(reader, "deliveryPolicy"),
|
|
164
|
+
deliveryRevision: requiredInteger(reader, "deliveryRevision"),
|
|
165
|
+
presentationFlags: requiredInteger(reader, "presentationFlags"),
|
|
166
|
+
...(blurDataUrl === undefined ? {} : { blurDataUrl }),
|
|
167
|
+
...(meta === undefined ? {} : { meta }),
|
|
168
|
+
folder: folder ?? null,
|
|
169
|
+
};
|
|
170
|
+
}) ?? [];
|
|
171
|
+
}
|
|
2
172
|
const PHI_SVG_MEDIA_MIN_DISPLAY_DIMENSION = 48;
|
|
3
173
|
function normalizePhiImagePreviewAsset(asset) {
|
|
4
174
|
return {
|
|
@@ -74,16 +244,21 @@ export function normalizePhiImagePreviewSelectionAsset(tile) {
|
|
|
74
244
|
};
|
|
75
245
|
}
|
|
76
246
|
export async function readPhiImagePreviewResponse(response) {
|
|
77
|
-
const payload =
|
|
247
|
+
const payload = await response.json();
|
|
78
248
|
if (!response.ok) {
|
|
79
|
-
|
|
249
|
+
const error = isPlainRecord(payload) && typeof payload.error === "string" ? payload.error : null;
|
|
250
|
+
throw new Error(error ?? "Failed to load media assets.");
|
|
80
251
|
}
|
|
252
|
+
const reader = createPhiResponseReader(payload, PHI_MEDIA_LISTING);
|
|
253
|
+
const folders = reader.list("folders", readPhiMediaFolderFields) ?? [];
|
|
254
|
+
const asset = reader.object("asset", readPhiMediaAssetFields);
|
|
81
255
|
return {
|
|
82
|
-
activeSpace:
|
|
83
|
-
spaces:
|
|
84
|
-
assets: (
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
256
|
+
activeSpace: reader.object("activeSpace", readPhiMediaSpaceFields) ?? null,
|
|
257
|
+
spaces: reader.list("spaces", readPhiMediaSpaceFields) ?? [],
|
|
258
|
+
assets: (reader.list("assets", readPhiMediaAssetFields) ?? [])
|
|
259
|
+
.map((entry) => normalizePhiImagePreviewTile(entry, folders)),
|
|
260
|
+
folders,
|
|
261
|
+
pagination: reader.object("pagination", readPhiMediaPaginationFields) ?? null,
|
|
262
|
+
asset: asset ? normalizePhiImagePreviewAsset(asset) : null,
|
|
88
263
|
};
|
|
89
264
|
}
|
|
@@ -10,6 +10,7 @@ import { PHI_ASSET_COLLECTION_DATA_SOURCE } from "./asset-collection-runtime";
|
|
|
10
10
|
import { buildPhiMediaFolderCascaderOptions, buildPhiMediaFolderValueById, resolvePhiMediaFolderIdFromValue, } from "./media-folder-options";
|
|
11
11
|
import { PHI_ASSET_CONTROLLER_STORE_KEY, PHI_ASSET_SIGNAL_CHANNELS, } from "./asset-controller-signals";
|
|
12
12
|
import { setPhiImagePreviewSelection } from "./phi-image-preview-store";
|
|
13
|
+
import { readPhiMediaAssetTiles, readPhiMediaFolders, readPhiMediaSpace, readPhiMediaSpaces, } from "./phi-image-preview-data";
|
|
13
14
|
import { buildPhiMediaSpaceOptions, usePhiMediaSpaceSelectionAllowed, } from "./media-space-selection";
|
|
14
15
|
import { isPhiRecord } from "../../helpers/is-record";
|
|
15
16
|
export function usePhiMediaPickerBinding({ config, labels, searchLabels, value: controlledValue, onAssetSelect, onAssetClear, open: controlledOpen, onOpenChange, onCommit, onDiscard, onPopupOpenChange, trigger, getPopupContainer, popupRootClassName, }) {
|
|
@@ -27,6 +28,13 @@ export function usePhiMediaPickerBinding({ config, labels, searchLabels, value:
|
|
|
27
28
|
// Null asks for no particular Space, which the control plane answers with the Site Space. An
|
|
28
29
|
// authoring surface never leaves this state, because it never renders the selector.
|
|
29
30
|
const [spaceAddress, setSpaceAddress] = useState(null);
|
|
31
|
+
/*
|
|
32
|
+
* The Space the reader chose, which is what the query asks for -- kept apart from the Space the last
|
|
33
|
+
* answer served, which is what the selector shows. Writing the served Space back into the query made
|
|
34
|
+
* the first open ask twice: once for nothing in particular, and once more for the Space it had just
|
|
35
|
+
* been given.
|
|
36
|
+
*/
|
|
37
|
+
const [requestedSpaceAddress, setRequestedSpaceAddress] = useState(null);
|
|
30
38
|
const spaceSelectionAllowed = usePhiMediaSpaceSelectionAllowed();
|
|
31
39
|
const [total, setTotal] = useState(0);
|
|
32
40
|
const [loading, setLoading] = useState(false);
|
|
@@ -53,9 +61,9 @@ export function usePhiMediaPickerBinding({ config, labels, searchLabels, value:
|
|
|
53
61
|
folderId,
|
|
54
62
|
kind: mediaType,
|
|
55
63
|
presentationFlags,
|
|
56
|
-
...(spaceSelectionAllowed &&
|
|
64
|
+
...(spaceSelectionAllowed && requestedSpaceAddress ? { spaceId: requestedSpaceAddress } : {}),
|
|
57
65
|
},
|
|
58
|
-
}), [presentationFlags, folderId, mediaType, page, pageSize,
|
|
66
|
+
}), [presentationFlags, folderId, mediaType, page, pageSize, requestedSpaceAddress, searchQuery, spaceSelectionAllowed]);
|
|
59
67
|
const querySignals = usePhiControlSignalController({ key: PHI_ASSET_SIGNAL_CHANNELS.query, signalRoutes: config?.signalRoutes, valueType: "string", typeKey: "media-picker-query", clearValue: "", onSetValue: (next) => { setSearchQuery(next); setPage(1); }, coerceValue: (next) => typeof next === "string" ? next : null });
|
|
60
68
|
const pathSignals = usePhiControlSignalController({ key: PHI_ASSET_SIGNAL_CHANNELS.path, signalRoutes: config?.signalRoutes, valueType: "path", typeKey: "media-picker-path", clearValue: "/", onSetValue: (next) => { setFolderId(resolvePhiMediaFolderIdFromValue(folders, next)); setPage(1); }, coerceValue: (next) => typeof next === "string" ? next : null });
|
|
61
69
|
const reloadSignals = usePhiControlSignalController({ key: PHI_ASSET_SIGNAL_CHANNELS.reload, signalRoutes: config?.signalRoutes, typeKey: "media-picker-reload", onReceiveCapability: (capabilityId) => { if (capabilityId !== "reload")
|
|
@@ -84,25 +92,19 @@ export function usePhiMediaPickerBinding({ config, labels, searchLabels, value:
|
|
|
84
92
|
});
|
|
85
93
|
if (abortController.signal.aborted)
|
|
86
94
|
return;
|
|
87
|
-
const nextFolders =
|
|
88
|
-
|
|
89
|
-
: [];
|
|
90
|
-
const nextAssets = data.items.filter(isPhiRecord);
|
|
95
|
+
const nextFolders = readPhiMediaFolders(data.meta?.folders);
|
|
96
|
+
const nextAssets = readPhiMediaAssetTiles(data.items);
|
|
91
97
|
setAssets(nextAssets);
|
|
92
98
|
for (const asset of nextAssets)
|
|
93
99
|
assetCacheRef.current.set(asset.id, asset);
|
|
94
100
|
setFolders(nextFolders);
|
|
95
|
-
setSpaces(
|
|
96
|
-
? data.meta.spaces.filter(isPhiRecord)
|
|
97
|
-
: []);
|
|
101
|
+
setSpaces(readPhiMediaSpaces(data.meta?.spaces));
|
|
98
102
|
// The answer says which Space it actually served, so the selector follows the control plane
|
|
99
103
|
// rather than the other way round -- including the first load, which asked for nothing.
|
|
100
|
-
const activeAddress =
|
|
101
|
-
? data.meta.activeSpace.address
|
|
102
|
-
: null;
|
|
104
|
+
const activeAddress = readPhiMediaSpace(data.meta?.activeSpace)?.address ?? null;
|
|
103
105
|
if (activeAddress)
|
|
104
106
|
setSpaceAddress(activeAddress);
|
|
105
|
-
setTotal(
|
|
107
|
+
setTotal(data.total);
|
|
106
108
|
}
|
|
107
109
|
catch (error) {
|
|
108
110
|
if (!abortController.signal.aborted) {
|
|
@@ -185,6 +187,7 @@ export function usePhiMediaPickerBinding({ config, labels, searchLabels, value:
|
|
|
185
187
|
return;
|
|
186
188
|
// Folders belong to the Space they were listed from, so switching Space starts at its root.
|
|
187
189
|
setSpaceAddress(nextSpace);
|
|
190
|
+
setRequestedSpaceAddress(nextSpace);
|
|
188
191
|
setFolderId(null);
|
|
189
192
|
setPage(1);
|
|
190
193
|
},
|
|
@@ -74,7 +74,11 @@ type PhiMediaUploadItem = {
|
|
|
74
74
|
* upload settled -- a form field that receives the Asset id in `onUploaded` -- reads it to hold back
|
|
75
75
|
* whatever would act on the incomplete value.
|
|
76
76
|
*/
|
|
77
|
-
export declare function usePhiMediaUpload
|
|
77
|
+
export declare function usePhiMediaUpload<TAsset extends {
|
|
78
|
+
id: number;
|
|
79
|
+
} = {
|
|
80
|
+
id: number;
|
|
81
|
+
}>(input: {
|
|
78
82
|
labels: PhiMediaUploadLabels;
|
|
79
83
|
acceptance?: PhiMediaUploadAcceptance;
|
|
80
84
|
initOptions?: PhiMediaUploadInitOptions;
|
|
@@ -85,18 +89,21 @@ export declare function usePhiMediaUpload(input: {
|
|
|
85
89
|
* between, so it is this one thing that differs -- and everything a person sees while it happens is
|
|
86
90
|
* shared rather than reimplemented alongside.
|
|
87
91
|
*/
|
|
88
|
-
runSession?: PhiMediaUploadSessionRunner
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
+
runSession?: (...args: Parameters<PhiMediaUploadSessionRunner>) => Promise<{
|
|
93
|
+
asset: TAsset;
|
|
94
|
+
}>;
|
|
95
|
+
/**
|
|
96
|
+
* The Asset as the session answered it. A surface that shows the new Asset at once -- a tile in the
|
|
97
|
+
* upload wall -- reads more of it than the id, and the default runner answers with the whole record.
|
|
98
|
+
*/
|
|
99
|
+
onUploaded?: (asset: TAsset, file: File) => void | Promise<void>;
|
|
92
100
|
onRejected?: (message: string, file: File) => void;
|
|
93
101
|
}): {
|
|
94
102
|
accept: string;
|
|
95
103
|
items: readonly PhiMediaUploadItem[];
|
|
96
104
|
uploading: boolean;
|
|
97
|
-
upload: (file: File) => Promise<
|
|
98
|
-
|
|
99
|
-
} | null>;
|
|
105
|
+
upload: (file: File) => Promise<TAsset | null>;
|
|
106
|
+
retry: (localId: string) => Promise<TAsset | null>;
|
|
100
107
|
cancel: () => void;
|
|
101
108
|
reset: () => void;
|
|
102
109
|
};
|
|
@@ -120,7 +120,8 @@ export function readPhiMediaUploadErrorMessage(error, labels) {
|
|
|
120
120
|
*/
|
|
121
121
|
export function usePhiMediaUpload(input) {
|
|
122
122
|
const { labels, acceptance, initOptions, onUploaded, onRejected } = input;
|
|
123
|
-
const runSession = input.runSession
|
|
123
|
+
const runSession = input.runSession
|
|
124
|
+
?? runPhiMediaUploadSession;
|
|
124
125
|
const [items, setItems] = useState([]);
|
|
125
126
|
const sequence = useRef(0);
|
|
126
127
|
const running = useRef(new Set());
|
|
@@ -136,18 +137,20 @@ export function usePhiMediaUpload(input) {
|
|
|
136
137
|
const patch = useCallback((localId, next) => {
|
|
137
138
|
setItems((current) => current.map((item) => item.localId === localId ? { ...item, ...next } : item));
|
|
138
139
|
}, []);
|
|
139
|
-
|
|
140
|
+
/** One file into one item: a new item, or -- for a retry -- the failed one it had. */
|
|
141
|
+
const run = useCallback(async (file, retryLocalId) => {
|
|
140
142
|
const rejection = readPhiMediaUploadRejection(file, acceptance, labels);
|
|
141
143
|
if (rejection) {
|
|
142
144
|
onRejected?.(rejection, file);
|
|
143
145
|
return null;
|
|
144
146
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
147
|
+
let localId = retryLocalId;
|
|
148
|
+
if (localId == null) {
|
|
149
|
+
sequence.current += 1;
|
|
150
|
+
localId = `upload-${sequence.current}`;
|
|
151
|
+
}
|
|
152
|
+
const startedItem = { localId, file, progress: 0, status: "uploading", error: null, assetId: null };
|
|
153
|
+
setItems((current) => [startedItem, ...current.filter((item) => item.localId !== localId)]);
|
|
151
154
|
const controller = new AbortController();
|
|
152
155
|
running.current.add(controller);
|
|
153
156
|
let asset;
|
|
@@ -182,11 +185,17 @@ export function usePhiMediaUpload(input) {
|
|
|
182
185
|
}
|
|
183
186
|
return asset;
|
|
184
187
|
}, [acceptance, initOptions, labels, onRejected, onUploaded, patch, runSession]);
|
|
188
|
+
const upload = useCallback((file) => run(file), [run]);
|
|
189
|
+
/** Starts a failed item's file again, in the place it had. */
|
|
190
|
+
const retry = useCallback((localId) => {
|
|
191
|
+
const failed = items.find((item) => item.localId === localId && item.status === "error");
|
|
192
|
+
return failed ? run(failed.file, localId) : Promise.resolve(null);
|
|
193
|
+
}, [items, run]);
|
|
185
194
|
const cancel = useCallback(() => {
|
|
186
195
|
for (const controller of running.current)
|
|
187
196
|
controller.abort();
|
|
188
197
|
}, []);
|
|
189
198
|
const reset = useCallback(() => setItems([]), []);
|
|
190
199
|
const uploading = items.some((item) => item.status === "uploading");
|
|
191
|
-
return { accept, items, uploading, upload, cancel, reset };
|
|
200
|
+
return { accept, items, uploading, upload, retry, cancel, reset };
|
|
192
201
|
}
|