@elytracms/studio 0.0.64 → 0.0.66
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/convex/assets.ts +11 -0
- package/convex/delivery.ts +5 -0
- package/convex/schema.ts +23 -0
- package/dist/components/account/self-account-view-source.js +9 -2
- package/dist/components/account/self-account-view-source.js.map +1 -1
- package/dist/components/app-views/default-modules.d.ts +28 -1
- package/dist/components/app-views/default-modules.js +47 -2
- package/dist/components/app-views/default-modules.js.map +1 -1
- package/dist/components/assets/asset-thumb.d.ts +4 -2
- package/dist/components/assets/asset-thumb.js +26 -4
- package/dist/components/assets/asset-thumb.js.map +1 -1
- package/dist/components/assets/assets-module.js +48 -2
- package/dist/components/assets/assets-module.js.map +1 -1
- package/dist/components/assets/assets-store.d.ts +11 -1
- package/dist/components/assets/assets-store.js +18 -1
- package/dist/components/assets/assets-store.js.map +1 -1
- package/dist/components/auth-context.js +32 -1
- package/dist/components/auth-context.js.map +1 -1
- package/dist/components/builder/blocks-model.d.ts +11 -0
- package/dist/components/builder/blocks-model.js +30 -10
- package/dist/components/builder/blocks-model.js.map +1 -1
- package/dist/components/builder/canvas-chrome-css.js +90 -8
- package/dist/components/builder/canvas-chrome-css.js.map +1 -1
- package/dist/components/builder/canvas-dnd.js +4 -2
- package/dist/components/builder/canvas-dnd.js.map +1 -1
- package/dist/components/builder/canvas-inline-editing.d.ts +7 -0
- package/dist/components/builder/canvas-inline-editing.js +3 -1
- package/dist/components/builder/canvas-inline-editing.js.map +1 -1
- package/dist/components/builder/canvas-overlay-geometry.d.ts +72 -0
- package/dist/components/builder/canvas-overlay-geometry.js +80 -0
- package/dist/components/builder/canvas-overlay-geometry.js.map +1 -0
- package/dist/components/builder/canvas-rect-tracking.d.ts +124 -0
- package/dist/components/builder/canvas-rect-tracking.js +198 -0
- package/dist/components/builder/canvas-rect-tracking.js.map +1 -0
- package/dist/components/builder/canvas.js +61 -12
- package/dist/components/builder/canvas.js.map +1 -1
- package/dist/components/builder/composition-field-editor.js +22 -3
- package/dist/components/builder/composition-field-editor.js.map +1 -1
- package/dist/components/builder/delete-key.d.ts +38 -0
- package/dist/components/builder/delete-key.js +46 -0
- package/dist/components/builder/delete-key.js.map +1 -0
- package/dist/components/builder/drag-session.d.ts +8 -0
- package/dist/components/builder/drag-session.js +15 -0
- package/dist/components/builder/drag-session.js.map +1 -1
- package/dist/components/builder/insert-affordance-context.d.ts +19 -0
- package/dist/components/builder/insert-affordance-context.js +7 -0
- package/dist/components/builder/insert-affordance-context.js.map +1 -0
- package/dist/components/builder/insert-context.d.ts +40 -0
- package/dist/components/builder/insert-context.js +97 -5
- package/dist/components/builder/insert-context.js.map +1 -1
- package/dist/components/builder/node-title.d.ts +60 -0
- package/dist/components/builder/node-title.js +135 -0
- package/dist/components/builder/node-title.js.map +1 -0
- package/dist/components/builder/page-tree.js +25 -5
- package/dist/components/builder/page-tree.js.map +1 -1
- package/dist/components/builder/prop-selection.d.ts +52 -0
- package/dist/components/builder/prop-selection.js +38 -0
- package/dist/components/builder/prop-selection.js.map +1 -0
- package/dist/components/builder/prop-sync-context.d.ts +49 -0
- package/dist/components/builder/prop-sync-context.js +23 -0
- package/dist/components/builder/prop-sync-context.js.map +1 -0
- package/dist/components/builder/property-inspector.js +24 -1
- package/dist/components/builder/property-inspector.js.map +1 -1
- package/dist/components/builder/reuse-context.js +1 -1
- package/dist/components/builder/reuse-context.js.map +1 -1
- package/dist/components/builder/selection-overlay.d.ts +79 -0
- package/dist/components/builder/selection-overlay.js +244 -0
- package/dist/components/builder/selection-overlay.js.map +1 -0
- package/dist/components/builder/spacing-handles.d.ts +57 -1
- package/dist/components/builder/spacing-handles.js +142 -26
- package/dist/components/builder/spacing-handles.js.map +1 -1
- package/dist/components/cms/asset-field.js +21 -5
- package/dist/components/cms/asset-field.js.map +1 -1
- package/dist/components/cms/asset-picker.js +11 -5
- package/dist/components/cms/asset-picker.js.map +1 -1
- package/dist/components/cms/bulk-actions.d.ts +22 -3
- package/dist/components/cms/bulk-actions.js +27 -3
- package/dist/components/cms/bulk-actions.js.map +1 -1
- package/dist/components/cms/cms-store.js +19 -0
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/components/cms/detail-editor.js +18 -19
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/prop-fields.js +23 -16
- package/dist/components/cms/prop-fields.js.map +1 -1
- package/dist/components/cms/publish-clean.d.ts +21 -0
- package/dist/components/cms/publish-clean.js +30 -0
- package/dist/components/cms/publish-clean.js.map +1 -0
- package/dist/components/cms/rich-text/block-style.d.ts +48 -0
- package/dist/components/cms/rich-text/block-style.js +30 -0
- package/dist/components/cms/rich-text/block-style.js.map +1 -0
- package/dist/components/cms/rich-text/embed-picker.js +4 -3
- package/dist/components/cms/rich-text/embed-picker.js.map +1 -1
- package/dist/components/cms/rich-text/extensions.d.ts +2 -0
- package/dist/components/cms/rich-text/extensions.js +4 -0
- package/dist/components/cms/rich-text/extensions.js.map +1 -1
- package/dist/components/cms/rich-text/font-size-control.d.ts +31 -0
- package/dist/components/cms/rich-text/font-size-control.js +40 -0
- package/dist/components/cms/rich-text/font-size-control.js.map +1 -0
- package/dist/components/cms/rich-text/font-size.d.ts +16 -0
- package/dist/components/cms/rich-text/font-size.js +40 -0
- package/dist/components/cms/rich-text/font-size.js.map +1 -0
- package/dist/components/cms/rich-text/toolbar-state.d.ts +3 -0
- package/dist/components/cms/rich-text/toolbar-state.js +3 -0
- package/dist/components/cms/rich-text/toolbar-state.js.map +1 -1
- package/dist/components/cms/rich-text/toolbar.d.ts +13 -0
- package/dist/components/cms/rich-text/toolbar.js +17 -5
- package/dist/components/cms/rich-text/toolbar.js.map +1 -1
- package/dist/components/cms/use-live-url.d.ts +24 -1
- package/dist/components/cms/use-live-url.js +26 -4
- package/dist/components/cms/use-live-url.js.map +1 -1
- package/dist/components/cms/use-publish-controller.js +8 -6
- package/dist/components/cms/use-publish-controller.js.map +1 -1
- package/dist/components/collection-view/cms-collection-view-source.js +15 -7
- package/dist/components/collection-view/cms-collection-view-source.js.map +1 -1
- package/dist/components/components/component-detail.js +3 -3
- package/dist/components/components/component-detail.js.map +1 -1
- package/dist/components/components/component-preview.js +1 -1
- package/dist/components/components/component-preview.js.map +1 -1
- package/dist/components/components/registry-browser.js +2 -2
- package/dist/components/components/registry-browser.js.map +1 -1
- package/dist/components/components/taxonomy.js +3 -2
- package/dist/components/components/taxonomy.js.map +1 -1
- package/dist/components/components/transfer-dialog.js +1 -1
- package/dist/components/components/transfer-dialog.js.map +1 -1
- package/dist/lib/assets/backfill-thumbs.d.ts +67 -0
- package/dist/lib/assets/backfill-thumbs.js +120 -0
- package/dist/lib/assets/backfill-thumbs.js.map +1 -0
- package/dist/lib/assets/delete-asset.d.ts +2 -1
- package/dist/lib/assets/delete-asset.js +12 -0
- package/dist/lib/assets/delete-asset.js.map +1 -1
- package/dist/lib/assets/index.d.ts +2 -0
- package/dist/lib/assets/index.js +2 -0
- package/dist/lib/assets/index.js.map +1 -1
- package/dist/lib/assets/thumbnail.d.ts +145 -0
- package/dist/lib/assets/thumbnail.js +220 -0
- package/dist/lib/assets/thumbnail.js.map +1 -0
- package/dist/lib/assets/upload.d.ts +9 -0
- package/dist/lib/assets/upload.js +31 -0
- package/dist/lib/assets/upload.js.map +1 -1
- package/dist/lib/auth/auth-adapter.d.ts +22 -0
- package/dist/lib/auth/auth-adapter.js.map +1 -1
- package/dist/lib/auth/convex-auth-adapter.d.ts +42 -1
- package/dist/lib/auth/convex-auth-adapter.js +69 -0
- package/dist/lib/auth/convex-auth-adapter.js.map +1 -1
- package/dist/lib/auth/dev-auto-login-adapter.d.ts +2 -1
- package/dist/lib/auth/dev-auto-login-adapter.js +6 -0
- package/dist/lib/auth/dev-auto-login-adapter.js.map +1 -1
- package/dist/lib/auth/index.d.ts +1 -1
- package/dist/lib/auth/index.js.map +1 -1
- package/dist/lib/auth/local-auth-adapter.d.ts +2 -1
- package/dist/lib/auth/local-auth-adapter.js +7 -0
- package/dist/lib/auth/local-auth-adapter.js.map +1 -1
- package/dist/lib/auth-errors.d.ts +45 -3
- package/dist/lib/auth-errors.js +46 -5
- package/dist/lib/auth-errors.js.map +1 -1
- package/dist/lib/clipboard/paste-target.js +5 -2
- package/dist/lib/clipboard/paste-target.js.map +1 -1
- package/dist/lib/dnd/drop-rules.d.ts +13 -0
- package/dist/lib/dnd/drop-rules.js +15 -0
- package/dist/lib/dnd/drop-rules.js.map +1 -1
- package/dist/lib/i18n/messages.de.js +19 -0
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +16 -0
- package/dist/lib/i18n/messages.en.js +24 -0
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/persistence/convex-adapter.js +4 -0
- package/dist/lib/persistence/convex-adapter.js.map +1 -1
- package/dist/lib/persistence/index.js +6 -3
- package/dist/lib/persistence/index.js.map +1 -1
- package/dist/lib/persona/labels.d.ts +9 -5
- package/dist/lib/persona/labels.js +13 -8
- package/dist/lib/persona/labels.js.map +1 -1
- package/dist/lib/references/references-store.js +7 -1
- package/dist/lib/references/references-store.js.map +1 -1
- package/dist/lib/session-expiry.d.ts +19 -6
- package/dist/lib/session-expiry.js +13 -1
- package/dist/lib/session-expiry.js.map +1 -1
- package/dist/lib/session-revalidation.d.ts +81 -0
- package/dist/lib/session-revalidation.js +94 -0
- package/dist/lib/session-revalidation.js.map +1 -0
- package/package.json +2 -2
- package/src/styles/studio.css +13 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"asset-field.js","sourceRoot":"","sources":["../../../src/components/cms/asset-field.tsx"],"names":[],"mappings":"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","sourcesContent":["import { AlertTriangle, ImagePlus, RotateCcw, SlidersHorizontal, Trash2, Upload, X } from 'lucide-react'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport type { AssetFieldDef, FieldDef } from '@elytracms/core/cms-core'\nimport {\n assetAcceptAttr,\n assetMatchesAccept,\n assetNode,\n assetSubfieldsOf,\n describeAccept,\n readAssetId,\n} from '@elytracms/core/cms-core'\nimport {\n Button,\n Card,\n Code,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n Field,\n Row,\n Spacer,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { applyAssetSelection, assetIdsOf, singleAssetIdOf } from '../assets/asset-selection'\nimport { useOptionalAssets } from '../assets/assets-store'\nimport { AssetThumb, FocalCropPreviews, FocalPointPicker, useAssetRecord } from '../assets/asset-thumb'\nimport { newlyAddedAssetIds } from '../assets/focal-point'\nimport { assetMetaFieldsOf, pruneMeta } from '../assets/asset-metadata'\nimport { fieldLabel } from './cms-labels'\nimport { useCollectionView } from '../collection-view/collection-view-context'\nimport { useFieldDensity } from './field-density'\nimport { AssetPicker } from './asset-picker'\nimport { FieldControl } from './field-control'\nimport { ObjectItemForm } from './object-array-field'\n\n/**\n * The asset-field family (EC-152 + EC-248 + EC-301) — split from\n * `field-control.tsx` (EC-330): the {@link AssetField} control plus its\n * preview/gallery/options sub-components. Per-use sub-fields and the options\n * dialog's metadata form render through the shared object item form /\n * `FieldControl`, so asset metadata stays schema-driven.\n */\n\n/**\n * The asset field (EC-152 + EC-248). Beyond picking from the library it now shows\n * a focal-point-aware preview, uploads directly from the field (reusing the EC-152\n * upload path — no parallel uploader), and edits the selected image's alt + focal\n * point inline (persisted on the asset document, where delivery reads them, EC-230).\n * With no `<AssetsProvider>` (fixtures/playground) it degrades to the labelled-badge\n * picker — no preview/upload — so it stays usable without a real library.\n */\nexport function AssetField({\n field,\n value,\n onChange,\n readOnly,\n}: {\n field: AssetFieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n}) {\n const source = useCollectionView()\n const assets = useOptionalAssets()\n const density = useFieldDensity()\n const compact = density === 'compact'\n const [open, setOpen] = useState(false)\n const [editing, setEditing] = useState(false)\n const [dragging, setDragging] = useState(false)\n // Names of files a drop/pick rejected against the field's `accept` (EC-301) — shown\n // as a transient inline issue, cleared on the next valid upload. Never a silent drop.\n const [rejected, setRejected] = useState<string[]>([])\n // Asset ids known when an upload-from-field started; the effect below diffs the\n // library against it to learn the new id (the `upload()` API returns nothing).\n const pendingRef = useRef<ReadonlySet<string> | null>(null)\n const assetCollectionId = source.assetCollectionId ?? 'asset'\n const many = field.multiple\n const ids = many ? assetIdsOf(value) : []\n const single = !many ? singleAssetIdOf(value) : null\n // EC-301 per-use sub-fields: with `field.fields` the value stores asset ENVELOPES\n // carrying the sub-field siblings (`{ type:'asset', id, caption }`); without them it\n // stays bare ids (self-typed on save). Picks/edits preserve a placement's sub-fields.\n const subFields = field.fields ?? []\n const hasSub = subFields.length > 0\n // The raw `many` entries (envelopes or bare ids), index-aligned with `ids`, so each\n // gallery item can read/write its own sub-fields.\n const entries = many && Array.isArray(value) ? (value as unknown[]) : []\n\n const libraryIds = useMemo(\n () => (assets?.status === 'ready' ? assets.assets.map((a) => a.id) : []),\n [assets],\n )\n\n // Auto-select an asset uploaded FROM this field once it lands in the library.\n useEffect(() => {\n if (!pendingRef.current) return\n const added = newlyAddedAssetIds(pendingRef.current, libraryIds)\n if (added.length === 0) return\n pendingRef.current = null\n if (many) {\n const current = Array.isArray(value) ? (value as unknown[]) : []\n onChange([...current, ...added.map((id) => (hasSub ? assetNode(id) : id))])\n } else {\n onChange(hasSub ? assetNode(added[0]!, assetSubfieldsOf(value)) : applyAssetSelection(value, added[0]!, false))\n }\n // Reacts to the library set growing (an upload landing), not to unrelated\n // value edits — `value`/`onChange` are read from the closure on purpose.\n }, [libraryIds, many, hasSub, onChange, value])\n\n function uploadFiles(files: File[]) {\n if (!assets || files.length === 0) return\n // EC-301: a drag-drop bypasses the file dialog's `accept` filter — re-check each\n // file against the field's `accept` and surface a visible issue for the rejects\n // instead of silently uploading the wrong kind.\n const ok: File[] = []\n const bad: string[] = []\n for (const file of files) {\n if (assetMatchesAccept({ contentType: file.type, filename: file.name }, field.accept)) ok.push(file)\n else bad.push(file.name)\n }\n setRejected(bad)\n if (ok.length === 0) return\n // Remember the library set so the effect above can learn the new id and\n // auto-select it once the upload lands (the upload API returns nothing).\n pendingRef.current = new Set(libraryIds)\n assets.upload(ok)\n }\n\n // Apply a picked asset id, preserving a placement's per-use sub-fields (EC-301).\n // Without sub-fields it stays the plain id-list behaviour (self-typed on save).\n function applyPicked(picked: string | null) {\n if (!hasSub) {\n onChange(applyAssetSelection(value, picked, field.multiple))\n return\n }\n if (many) {\n if (picked === null || entries.some((e) => readAssetId(e) === picked)) return\n onChange([...entries, assetNode(picked)])\n } else {\n onChange(picked ? assetNode(picked, assetSubfieldsOf(value)) : undefined)\n }\n }\n\n const canUpload = !readOnly && assets !== null\n\n // Drag-to-upload onto the field (EC-301): a depth counter keeps the overlay\n // steady as the pointer crosses child element boundaries. `dragenter`/`dragleave`\n // fire per descendant, so toggling on a bare `dragleave` blinks constantly; we\n // only hide the overlay when the counter returns to zero (the pointer has truly\n // left the field). The overlay is pointer-events-none, so it never fires its own\n // enter/leave to confuse the count.\n const dragDepth = useRef(0)\n function onDragEnter(e: React.DragEvent) {\n if (!canUpload) return\n e.preventDefault()\n dragDepth.current += 1\n if (!dragging) setDragging(true)\n }\n function onDragOver(e: React.DragEvent) {\n if (!canUpload) return\n e.preventDefault() // required so a drop is allowed\n }\n function onDragLeave() {\n if (!canUpload) return\n dragDepth.current -= 1\n if (dragDepth.current <= 0) {\n dragDepth.current = 0\n setDragging(false)\n }\n }\n function onDrop(e: React.DragEvent) {\n if (!canUpload) return\n e.preventDefault()\n dragDepth.current = 0\n setDragging(false)\n uploadFiles(Array.from(e.dataTransfer.files))\n }\n // While the picker is open the field is behind the modal, so any drop is happening\n // INSIDE the dialog — detach the field's drag handlers entirely so a drop there can\n // only be handled by the picker, never double-counted by the field too (EC-301). This\n // is the belt to the picker's stopPropagation braces: the field cannot react at all.\n const dragProps = canUpload && !open ? { onDragEnter, onDragOver, onDragLeave, onDrop } : {}\n\n const selectButton = !readOnly ? (\n <Button variant=\"outline\" size=\"sm\" onClick={() => setOpen(true)}>\n <ImagePlus /> {translate('common.select')}\n </Button>\n ) : null\n\n return (\n <Stack\n gap=\"2\"\n className={cn(\n 'relative rounded-lg transition-shadow',\n dragging && 'ring-2 ring-primary ring-offset-2 ring-offset-background',\n )}\n {...dragProps}\n >\n {many && hasSub ? (\n // Gallery with per-use sub-fields (EC-301): a card per placement — thumbnail +\n // its own sub-field form — reusing the object/repeater item rendering.\n <Stack gap=\"2\">\n {entries.length > 0 ? (\n <Stack gap=\"2\">\n {entries.map((entry, i) => {\n const aid = readAssetId(entry)\n if (!aid) return null\n return (\n <AssetGalleryItem\n key={`${aid}-${i}`}\n assetId={aid}\n collectionId={assetCollectionId}\n fields={subFields}\n value={assetSubfieldsOf(entry)}\n readOnly={readOnly}\n idPrefix={`asset-${i}`}\n onChangeSubs={(next) => {\n const nextEntries = entries.slice()\n nextEntries[i] = assetNode(aid, next)\n onChange(nextEntries)\n }}\n onRemove={readOnly ? undefined : () => onChange(entries.filter((_, j) => j !== i))}\n />\n )\n })}\n </Stack>\n ) : (\n <EmptyAssetState compact={compact} canUpload={canUpload} onSelect={() => setOpen(true)} />\n )}\n <Row gap=\"2\" wrap align=\"center\">\n {selectButton}\n </Row>\n </Stack>\n ) : many ? (\n // Gallery: a grid of focal-aware previews + the single Select entry.\n <Stack gap=\"2\">\n {ids.length > 0 ? (\n <div\n className={cn(\n 'grid gap-2',\n compact ? 'grid-cols-2' : 'grid-cols-3 sm:grid-cols-4',\n )}\n >\n {ids.map((aid, i) => (\n <AssetChip\n key={`${aid}-${i}`}\n assetId={aid}\n collectionId={assetCollectionId}\n onRemove={readOnly ? undefined : () => onChange(ids.filter((x) => x !== aid))}\n />\n ))}\n </div>\n ) : (\n <EmptyAssetState compact={compact} canUpload={canUpload} onSelect={() => setOpen(true)} />\n )}\n <Row gap=\"2\" wrap align=\"center\">\n {selectButton}\n </Row>\n </Stack>\n ) : single && hasSub ? (\n // With per-use sub-fields the asset + its fields read as ONE grouped unit\n // (like an object), so it's clear they belong together (EC-301 feedback).\n <Card className=\"border-l-2 border-l-border p-3\">\n <Stack gap=\"3\">\n <AssetSinglePreview\n assetId={single}\n compact={compact}\n readOnly={readOnly}\n hasLibrary={assets !== null}\n fallbackTitle={source.titleOf({ collection: assetCollectionId, id: single })}\n selectButton={selectButton}\n onEdit={() => setEditing(true)}\n onRemove={() => onChange(applyAssetSelection(value, null, field.multiple))}\n />\n <div className=\"border-t border-border/60 pt-3\">\n <ObjectItemForm\n fields={subFields}\n value={assetSubfieldsOf(value)}\n readOnly={readOnly}\n idPrefix=\"asset-sub\"\n onChange={(next) => onChange(assetNode(single, next))}\n />\n </div>\n </Stack>\n </Card>\n ) : single ? (\n <AssetSinglePreview\n assetId={single}\n compact={compact}\n readOnly={readOnly}\n hasLibrary={assets !== null}\n fallbackTitle={source.titleOf({ collection: assetCollectionId, id: single })}\n selectButton={selectButton}\n onEdit={() => setEditing(true)}\n onRemove={() => onChange(applyAssetSelection(value, null, field.multiple))}\n />\n ) : (\n <Stack gap=\"2\">\n <EmptyAssetState compact={compact} canUpload={canUpload} onSelect={() => setOpen(true)} />\n <Row gap=\"2\" wrap align=\"center\">\n {selectButton}\n </Row>\n </Stack>\n )}\n\n {/* Image options (focal point + metadata) for a single selected asset, in a\n dialog rather than an inline disclosure (EC-301). Shared across every use. */}\n {!many && single && assets ? (\n <AssetOptionsDialog\n assetId={single}\n readOnly={readOnly}\n open={editing}\n onOpenChange={setEditing}\n />\n ) : null}\n\n <AssetPicker\n value={single}\n open={open}\n onOpenChange={setOpen}\n accept={assetAcceptAttr(field.accept)}\n onChange={applyPicked}\n />\n\n {rejected.length > 0 ? (\n <Row gap=\"2\" align=\"center\">\n <AlertTriangle className=\"size-4 shrink-0 text-warning\" />\n <Text size=\"xs\" tone=\"muted\">\n {rejected.length === 1\n ? translate('cms.asset.rejected.one', {\n name: rejected[0]!,\n types: describeAccept(field.accept),\n })\n : translate('cms.asset.rejected.other', {\n count: rejected.length,\n types: describeAccept(field.accept),\n })}\n </Text>\n </Row>\n ) : null}\n\n {dragging ? (\n <div className=\"pointer-events-none absolute inset-0 z-10 grid place-items-center rounded-lg border-2 border-dashed border-primary bg-background/85\">\n <Row gap=\"2\" align=\"center\">\n <Upload className=\"size-4 text-primary\" />\n <Text size=\"sm\" className=\"font-medium\">\n {translate('cms.asset.dropToUpload')}\n </Text>\n </Row>\n </div>\n ) : null}\n </Stack>\n )\n}\n\n/** Empty-state placeholder shown when an asset field has no selection (EC-301). */\nfunction EmptyAssetState({\n compact,\n canUpload,\n onSelect,\n}: {\n compact: boolean\n canUpload: boolean\n onSelect: () => void\n}) {\n return (\n <button\n type=\"button\"\n onClick={onSelect}\n className={cn(\n 'grid w-full place-items-center gap-1 rounded-lg border border-dashed border-border bg-muted/30 text-center text-muted-foreground transition-colors hover:bg-muted/60',\n compact ? 'aspect-video' : 'aspect-[5/2]',\n )}\n >\n <ImagePlus className=\"size-6\" aria-hidden />\n <Text size=\"xs\" tone=\"muted\">\n {canUpload ? translate('cms.asset.selectOrDrop') : translate('cms.asset.selectAsset')}\n </Text>\n </button>\n )\n}\n\n/** A large focal-aware preview of the selected asset (EC-301). */\nfunction AssetPreview({ assetId }: { assetId: string }) {\n const record = useAssetRecord(assetId)\n return <AssetThumb record={record} className=\"aspect-[4/3] w-full\" />\n}\n\n/** Filename + dimensions/type for the selected asset — the id is never shown (EC-301). */\nfunction AssetInfo({ assetId, fallbackTitle }: { assetId: string; fallbackTitle?: string }) {\n const record = useAssetRecord(assetId)\n const name = record?.title ?? record?.filename ?? fallbackTitle ?? assetId\n const dims = record?.width && record?.height ? `${record.width}×${record.height}` : null\n const kind = record?.contentType?.split('/')[1]?.toUpperCase()\n const meta = [dims, kind].filter(Boolean).join(' · ')\n return (\n <Stack gap=\"1\">\n <Text size=\"sm\" className=\"truncate font-medium\" title={name}>\n {name}\n </Text>\n {meta ? (\n <Text size=\"xs\" tone=\"subtle\">\n {meta}\n </Text>\n ) : null}\n </Stack>\n )\n}\n\n/**\n * The selected image's alt text shown in the field (EC-301) — what an editor most\n * wants at a glance; edited in the options dialog. Reads `meta.alt` with the legacy\n * `alt` column as fallback, matching delivery.\n */\nfunction AssetAltLine({ assetId }: { assetId: string }) {\n const record = useAssetRecord(assetId)\n const metaAlt = typeof record?.meta?.alt === 'string' ? record.meta.alt : undefined\n const alt = metaAlt ?? record?.alt\n return (\n <Row gap=\"1\" align=\"baseline\" className=\"min-w-0\">\n <Text size=\"xs\" tone=\"subtle\" className=\"shrink-0\">\n {translate('cms.asset.altLabel')}\n </Text>\n <Text\n size=\"xs\"\n tone={alt ? 'muted' : 'subtle'}\n className={cn('min-w-0 truncate', !alt && 'italic')}\n title={alt ?? undefined}\n >\n {alt || translate('cms.asset.notSet')}\n </Text>\n </Row>\n )\n}\n\n/** A single asset reference: focal-aware thumbnail + title, with an optional remove. */\nfunction AssetChip({\n assetId,\n collectionId,\n onRemove,\n}: {\n assetId: string\n collectionId: string\n onRemove?: () => void\n}) {\n const source = useCollectionView()\n const record = useAssetRecord(assetId)\n const title = source.titleOf({ collection: collectionId, id: assetId })\n return (\n <div className=\"group relative overflow-hidden rounded-lg border border-border bg-muted/40\">\n <AssetThumb record={record} alt={title} className=\"aspect-square w-full rounded-none border-0\" />\n <Row gap=\"1\" align=\"center\" className=\"px-2 py-1\">\n <Text size=\"xs\" tone=\"subtle\" className=\"min-w-0 flex-1 truncate\" title={title}>\n {title}\n </Text>\n {onRemove ? (\n <button\n type=\"button\"\n aria-label={translate('common.remove')}\n onClick={onRemove}\n className=\"text-muted-foreground hover:text-destructive\"\n >\n <X className=\"size-3.5\" />\n </button>\n ) : null}\n </Row>\n </div>\n )\n}\n\n/**\n * The selected single asset's preview row (EC-301): large focal-aware preview + info\n * column (filename/dimensions, Select/Edit/Remove, alt). Extracted so it renders either\n * bare (a plain asset field) or inside the grouping card when the field carries per-use\n * sub-fields — the asset + its fields then read as one unit.\n */\nfunction AssetSinglePreview({\n assetId,\n compact,\n readOnly,\n hasLibrary,\n fallbackTitle,\n selectButton,\n onEdit,\n onRemove,\n}: {\n assetId: string\n compact: boolean\n readOnly: boolean\n hasLibrary: boolean\n fallbackTitle?: string\n selectButton: React.ReactNode\n onEdit: () => void\n onRemove: () => void\n}) {\n return (\n <div className={compact ? 'flex flex-col gap-3' : 'flex gap-4'}>\n <div className={compact ? 'w-full' : 'w-1/2 shrink-0'}>\n <AssetPreview assetId={assetId} />\n </div>\n <Stack gap=\"2\" className={cn('min-w-0', !compact && 'flex-1')}>\n <AssetInfo assetId={assetId} fallbackTitle={fallbackTitle} />\n {!readOnly ? (\n <Row gap=\"2\" wrap align=\"center\">\n {selectButton}\n {hasLibrary ? (\n <Button variant=\"outline\" size=\"sm\" onClick={onEdit}>\n <SlidersHorizontal /> {translate('common.edit')}\n </Button>\n ) : null}\n <Button variant=\"ghost\" size=\"sm\" onClick={onRemove}>\n <Trash2 /> {translate('common.remove')}\n </Button>\n </Row>\n ) : null}\n <AssetAltLine assetId={assetId} />\n </Stack>\n </div>\n )\n}\n\n/**\n * One gallery placement when a `many` asset field declares per-use sub-fields (EC-301):\n * a thumbnail + filename + remove header over the placement's own sub-field form,\n * modelled on a repeater item card so a gallery's placements read as grouped units.\n */\nfunction AssetGalleryItem({\n assetId,\n collectionId,\n fields,\n value,\n onChangeSubs,\n onRemove,\n readOnly,\n idPrefix,\n}: {\n assetId: string\n collectionId: string\n fields: readonly FieldDef[]\n value: Record<string, unknown>\n onChangeSubs: (value: Record<string, unknown>) => void\n onRemove?: () => void\n readOnly: boolean\n idPrefix: string\n}) {\n const source = useCollectionView()\n const record = useAssetRecord(assetId)\n const title = source.titleOf({ collection: collectionId, id: assetId })\n return (\n <Card className=\"border-l-2 border-l-border p-0\">\n <Row gap=\"2\" align=\"center\" className=\"border-b border-border/60 bg-muted/30 px-2 py-1.5\">\n <AssetThumb record={record} alt={title} className=\"size-10 shrink-0\" />\n <Text size=\"sm\" className=\"min-w-0 flex-1 truncate font-medium\" title={title}>\n {title}\n </Text>\n {onRemove ? (\n <Button variant=\"ghost\" size=\"sm\" aria-label={translate('common.remove')} onClick={onRemove}>\n <Trash2 className=\"size-3.5\" />\n </Button>\n ) : null}\n </Row>\n <div className=\"px-3 pb-3\">\n <ObjectItemForm\n fields={fields}\n value={value}\n onChange={onChangeSubs}\n readOnly={readOnly}\n idPrefix={idPrefix}\n />\n </div>\n </Card>\n )\n}\n\n/**\n * The image options dialog (EC-301): the focal point (engine-special visual\n * control) + the asset's **schema-driven** metadata, rendered from the\n * `kind:'asset'` collection's `fields` through the same generic field engine\n * documents use — so a project declares asset metadata (alt, copyright, credit, …)\n * in code, not hardcoded here. Edits persist on the asset, shared across every use.\n * Metadata is buffered locally and committed once on close (one write); the focal\n * point commits immediately (it drives a live visual). No-ops without a library.\n */\nfunction AssetOptionsDialog({\n assetId,\n readOnly,\n open,\n onOpenChange,\n}: {\n assetId: string\n readOnly: boolean\n open: boolean\n onOpenChange: (open: boolean) => void\n}) {\n const source = useCollectionView()\n const assets = useOptionalAssets()\n const record = useAssetRecord(assetId)\n const metaFields = useMemo(() => {\n const assetCollectionId = source.assetCollectionId ?? 'asset'\n const collection = source.collections.find((c) => c.id === assetCollectionId)\n return collection ? assetMetaFieldsOf(collection) : []\n }, [source])\n\n // Local edit buffers, seeded once per open from the record (so an external edit\n // still wins between opens). Metadata is non-localized in v1 (the asset\n // resolution path is locale-agnostic) — localized asset metadata is a follow-up.\n const [meta, setMeta] = useState<Record<string, unknown>>({})\n const [focal, setFocal] = useState<{ x: number; y: number } | undefined>(undefined)\n const dirty = useRef(false)\n const seeded = useRef(false)\n useEffect(() => {\n if (!open) {\n seeded.current = false\n return\n }\n if (seeded.current || !record) return\n // Seed from the meta bag, but lift the legacy `alt` column in when meta lacks\n // it (EC-301 transition): a not-yet-migrated asset still shows its alt, and\n // saving writes it into meta — a lazy migration-on-edit, matching delivery's\n // `meta.alt ?? record.alt` read fallback.\n const seedMeta = { ...(record.alt !== undefined ? { alt: record.alt } : {}), ...(record.meta ?? {}) }\n setMeta(seedMeta)\n setFocal(record.focalPoint)\n dirty.current = false\n seeded.current = true\n }, [open, record])\n\n function handleOpenChange(next: boolean) {\n // Commit the buffered metadata once, on close (one write, not per keystroke).\n if (!next && !readOnly && assets && dirty.current) {\n void assets.updateMetadata(assetId, { meta: pruneMeta(meta) })\n dirty.current = false\n }\n onOpenChange(next)\n }\n\n function setField(name: string, value: unknown) {\n dirty.current = true\n setMeta((m) => ({ ...m, [name]: value }))\n }\n\n // The focal point drives a live crop preview → commit immediately (optimistic\n // marker), not deferred to close.\n function commitFocal(point: { x: number; y: number } | null) {\n if (readOnly || !assets) return\n setFocal(point ?? undefined)\n void assets.updateMetadata(assetId, { focalPoint: point })\n }\n\n return (\n <Dialog open={open} onOpenChange={handleOpenChange}>\n <DialogContent className=\"max-w-lg\">\n <DialogHeader>\n <DialogTitle>{translate('cms.asset.imageOptions.title')}</DialogTitle>\n <DialogDescription>\n {translate('cms.asset.imageOptions.description')}\n </DialogDescription>\n </DialogHeader>\n <Stack gap=\"3\">\n <Stack gap=\"2\">\n <Row align=\"center\">\n <Text size=\"sm\" className=\"font-medium\">\n {translate('cms.asset.focalPoint')}\n </Text>\n <Spacer />\n {focal && !readOnly ? (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => commitFocal(null)}>\n <RotateCcw /> {translate('cms.asset.resetToCentre')}\n </Button>\n ) : null}\n </Row>\n <FocalPointPicker\n record={record}\n point={focal}\n onChange={commitFocal}\n onPreview={setFocal}\n disabled={readOnly}\n />\n <Text size=\"xs\" tone=\"subtle\">\n {translate('cms.asset.focalPointHint')}\n </Text>\n <FocalCropPreviews record={record} point={focal} />\n </Stack>\n {metaFields.map((f) => (\n <Field key={f.name} label={fieldLabel(f)} hint={localizedLabel(f.form?.description) || undefined}>\n <FieldControl\n id={`asset-meta-${f.name}`}\n field={f}\n value={meta[f.name]}\n readOnly={readOnly}\n onChange={(v) => setField(f.name, v)}\n />\n </Field>\n ))}\n {metaFields.length === 0 ? (\n <Text size=\"sm\" tone=\"subtle\">\n No editable metadata fields are declared on the asset collection. Add fields (e.g.\n <Code tone=\"muted\"> alt</Code>, <Code tone=\"muted\">copyright</Code>) to it in code.\n </Text>\n ) : null}\n </Stack>\n </DialogContent>\n </Dialog>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"asset-field.js","sourceRoot":"","sources":["../../../src/components/cms/asset-field.tsx"],"names":[],"mappings":"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","sourcesContent":["import { AlertTriangle, ImagePlus, RotateCcw, SlidersHorizontal, Trash2, Upload, X } from 'lucide-react'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport type { AssetFieldDef, FieldDef } from '@elytracms/core/cms-core'\nimport {\n assetAcceptAttr,\n assetMatchesAccept,\n assetNode,\n assetSubfieldsOf,\n describeAccept,\n readAssetId,\n} from '@elytracms/core/cms-core'\nimport {\n Button,\n Card,\n Code,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n Field,\n Row,\n Spacer,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { applyAssetSelection, assetIdsOf, singleAssetIdOf } from '../assets/asset-selection'\nimport { useOptionalAssets } from '../assets/assets-store'\nimport { AssetThumb, FocalCropPreviews, FocalPointPicker, useAssetRecord } from '../assets/asset-thumb'\nimport { newlyAddedAssetIds } from '../assets/focal-point'\nimport { assetMetaFieldsOf, pruneMeta } from '../assets/asset-metadata'\nimport { fieldLabel } from './cms-labels'\nimport { useCollectionView } from '../collection-view/collection-view-context'\nimport { useFieldDensity } from './field-density'\nimport { AssetPicker } from './asset-picker'\nimport { FieldControl } from './field-control'\nimport { ObjectItemForm } from './object-array-field'\n\n/**\n * The asset-field family (EC-152 + EC-248 + EC-301) — split from\n * `field-control.tsx` (EC-330): the {@link AssetField} control plus its\n * preview/gallery/options sub-components. Per-use sub-fields and the options\n * dialog's metadata form render through the shared object item form /\n * `FieldControl`, so asset metadata stays schema-driven.\n */\n\n/**\n * The asset field (EC-152 + EC-248). Beyond picking from the library it now shows\n * a focal-point-aware preview, uploads directly from the field (reusing the EC-152\n * upload path — no parallel uploader), and edits the selected image's alt + focal\n * point inline (persisted on the asset document, where delivery reads them, EC-230).\n * With no `<AssetsProvider>` (fixtures/playground) it degrades to the labelled-badge\n * picker — no preview/upload — so it stays usable without a real library.\n */\nexport function AssetField({\n field,\n value,\n onChange,\n readOnly,\n}: {\n field: AssetFieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n}) {\n const source = useCollectionView()\n const assets = useOptionalAssets()\n const density = useFieldDensity()\n const compact = density === 'compact'\n const [open, setOpen] = useState(false)\n const [editing, setEditing] = useState(false)\n const [dragging, setDragging] = useState(false)\n // Names of files a drop/pick rejected against the field's `accept` (EC-301) — shown\n // as a transient inline issue, cleared on the next valid upload. Never a silent drop.\n const [rejected, setRejected] = useState<string[]>([])\n // Asset ids known when an upload-from-field started; the effect below diffs the\n // library against it to learn the new id (the `upload()` API returns nothing).\n const pendingRef = useRef<ReadonlySet<string> | null>(null)\n const assetCollectionId = source.assetCollectionId ?? 'asset'\n const many = field.multiple\n const ids = many ? assetIdsOf(value) : []\n const single = !many ? singleAssetIdOf(value) : null\n // EC-301 per-use sub-fields: with `field.fields` the value stores asset ENVELOPES\n // carrying the sub-field siblings (`{ type:'asset', id, caption }`); without them it\n // stays bare ids (self-typed on save). Picks/edits preserve a placement's sub-fields.\n const subFields = field.fields ?? []\n const hasSub = subFields.length > 0\n // The raw `many` entries (envelopes or bare ids), index-aligned with `ids`, so each\n // gallery item can read/write its own sub-fields.\n const entries = many && Array.isArray(value) ? (value as unknown[]) : []\n\n const libraryIds = useMemo(\n () => (assets?.status === 'ready' ? assets.assets.map((a) => a.id) : []),\n [assets],\n )\n\n // Auto-select an asset uploaded FROM this field once it lands in the library.\n useEffect(() => {\n if (!pendingRef.current) return\n const added = newlyAddedAssetIds(pendingRef.current, libraryIds)\n if (added.length === 0) return\n pendingRef.current = null\n if (many) {\n const current = Array.isArray(value) ? (value as unknown[]) : []\n onChange([...current, ...added.map((id) => (hasSub ? assetNode(id) : id))])\n } else {\n onChange(hasSub ? assetNode(added[0]!, assetSubfieldsOf(value)) : applyAssetSelection(value, added[0]!, false))\n }\n // Reacts to the library set growing (an upload landing), not to unrelated\n // value edits — `value`/`onChange` are read from the closure on purpose.\n }, [libraryIds, many, hasSub, onChange, value])\n\n function uploadFiles(files: File[]) {\n if (!assets || files.length === 0) return\n // EC-301: a drag-drop bypasses the file dialog's `accept` filter — re-check each\n // file against the field's `accept` and surface a visible issue for the rejects\n // instead of silently uploading the wrong kind.\n const ok: File[] = []\n const bad: string[] = []\n for (const file of files) {\n if (assetMatchesAccept({ contentType: file.type, filename: file.name }, field.accept)) ok.push(file)\n else bad.push(file.name)\n }\n setRejected(bad)\n if (ok.length === 0) return\n // Remember the library set so the effect above can learn the new id and\n // auto-select it once the upload lands (the upload API returns nothing).\n pendingRef.current = new Set(libraryIds)\n assets.upload(ok)\n }\n\n // Apply a picked asset id, preserving a placement's per-use sub-fields (EC-301).\n // Without sub-fields it stays the plain id-list behaviour (self-typed on save).\n function applyPicked(picked: string | null) {\n if (!hasSub) {\n onChange(applyAssetSelection(value, picked, field.multiple))\n return\n }\n if (many) {\n if (picked === null || entries.some((e) => readAssetId(e) === picked)) return\n onChange([...entries, assetNode(picked)])\n } else {\n onChange(picked ? assetNode(picked, assetSubfieldsOf(value)) : undefined)\n }\n }\n\n const canUpload = !readOnly && assets !== null\n\n // Drag-to-upload onto the field (EC-301): a depth counter keeps the overlay\n // steady as the pointer crosses child element boundaries. `dragenter`/`dragleave`\n // fire per descendant, so toggling on a bare `dragleave` blinks constantly; we\n // only hide the overlay when the counter returns to zero (the pointer has truly\n // left the field). The overlay is pointer-events-none, so it never fires its own\n // enter/leave to confuse the count.\n const dragDepth = useRef(0)\n function onDragEnter(e: React.DragEvent) {\n if (!canUpload) return\n e.preventDefault()\n dragDepth.current += 1\n if (!dragging) setDragging(true)\n }\n function onDragOver(e: React.DragEvent) {\n if (!canUpload) return\n e.preventDefault() // required so a drop is allowed\n }\n function onDragLeave() {\n if (!canUpload) return\n dragDepth.current -= 1\n if (dragDepth.current <= 0) {\n dragDepth.current = 0\n setDragging(false)\n }\n }\n function onDrop(e: React.DragEvent) {\n if (!canUpload) return\n e.preventDefault()\n dragDepth.current = 0\n setDragging(false)\n uploadFiles(Array.from(e.dataTransfer.files))\n }\n // While the picker is open the field is behind the modal, so any drop is happening\n // INSIDE the dialog — detach the field's drag handlers entirely so a drop there can\n // only be handled by the picker, never double-counted by the field too (EC-301). This\n // is the belt to the picker's stopPropagation braces: the field cannot react at all.\n const dragProps = canUpload && !open ? { onDragEnter, onDragOver, onDragLeave, onDrop } : {}\n\n const selectButton = !readOnly ? (\n <Button variant=\"outline\" size=\"sm\" onClick={() => setOpen(true)}>\n <ImagePlus /> {translate('common.select')}\n </Button>\n ) : null\n\n return (\n <Stack\n gap=\"2\"\n className={cn(\n 'relative rounded-lg transition-shadow',\n dragging && 'ring-2 ring-primary ring-offset-2 ring-offset-background',\n )}\n {...dragProps}\n >\n {many && hasSub ? (\n // Gallery with per-use sub-fields (EC-301): a card per placement — thumbnail +\n // its own sub-field form — reusing the object/repeater item rendering.\n <Stack gap=\"2\">\n {entries.length > 0 ? (\n <Stack gap=\"2\">\n {entries.map((entry, i) => {\n const aid = readAssetId(entry)\n if (!aid) return null\n return (\n <AssetGalleryItem\n key={`${aid}-${i}`}\n assetId={aid}\n collectionId={assetCollectionId}\n fields={subFields}\n value={assetSubfieldsOf(entry)}\n readOnly={readOnly}\n idPrefix={`asset-${i}`}\n onChangeSubs={(next) => {\n const nextEntries = entries.slice()\n nextEntries[i] = assetNode(aid, next)\n onChange(nextEntries)\n }}\n onRemove={readOnly ? undefined : () => onChange(entries.filter((_, j) => j !== i))}\n />\n )\n })}\n </Stack>\n ) : (\n <EmptyAssetState\n compact={compact}\n canUpload={canUpload}\n readOnly={readOnly}\n onSelect={() => setOpen(true)}\n />\n )}\n <Row gap=\"2\" wrap align=\"center\">\n {selectButton}\n </Row>\n </Stack>\n ) : many ? (\n // Gallery: a grid of focal-aware previews + the single Select entry.\n <Stack gap=\"2\">\n {ids.length > 0 ? (\n <div\n className={cn(\n 'grid gap-2',\n compact ? 'grid-cols-2' : 'grid-cols-3 sm:grid-cols-4',\n )}\n >\n {ids.map((aid, i) => (\n <AssetChip\n key={`${aid}-${i}`}\n assetId={aid}\n collectionId={assetCollectionId}\n onRemove={readOnly ? undefined : () => onChange(ids.filter((x) => x !== aid))}\n />\n ))}\n </div>\n ) : (\n <EmptyAssetState\n compact={compact}\n canUpload={canUpload}\n readOnly={readOnly}\n onSelect={() => setOpen(true)}\n />\n )}\n <Row gap=\"2\" wrap align=\"center\">\n {selectButton}\n </Row>\n </Stack>\n ) : single && hasSub ? (\n // With per-use sub-fields the asset + its fields read as ONE grouped unit\n // (like an object), so it's clear they belong together (EC-301 feedback).\n <Card className=\"border-l-2 border-l-border p-3\">\n <Stack gap=\"3\">\n <AssetSinglePreview\n assetId={single}\n compact={compact}\n readOnly={readOnly}\n hasLibrary={assets !== null}\n fallbackTitle={source.titleOf({ collection: assetCollectionId, id: single })}\n selectButton={selectButton}\n onEdit={() => setEditing(true)}\n onRemove={() => onChange(applyAssetSelection(value, null, field.multiple))}\n />\n <div className=\"border-t border-border/60 pt-3\">\n <ObjectItemForm\n fields={subFields}\n value={assetSubfieldsOf(value)}\n readOnly={readOnly}\n idPrefix=\"asset-sub\"\n onChange={(next) => onChange(assetNode(single, next))}\n />\n </div>\n </Stack>\n </Card>\n ) : single ? (\n <AssetSinglePreview\n assetId={single}\n compact={compact}\n readOnly={readOnly}\n hasLibrary={assets !== null}\n fallbackTitle={source.titleOf({ collection: assetCollectionId, id: single })}\n selectButton={selectButton}\n onEdit={() => setEditing(true)}\n onRemove={() => onChange(applyAssetSelection(value, null, field.multiple))}\n />\n ) : (\n <Stack gap=\"2\">\n <EmptyAssetState\n compact={compact}\n canUpload={canUpload}\n readOnly={readOnly}\n onSelect={() => setOpen(true)}\n />\n <Row gap=\"2\" wrap align=\"center\">\n {selectButton}\n </Row>\n </Stack>\n )}\n\n {/* Image options (focal point + metadata) for a single selected asset, in a\n dialog rather than an inline disclosure (EC-301). Shared across every use. */}\n {!many && single && assets ? (\n <AssetOptionsDialog\n assetId={single}\n readOnly={readOnly}\n open={editing}\n onOpenChange={setEditing}\n />\n ) : null}\n\n <AssetPicker\n value={single}\n open={open}\n onOpenChange={setOpen}\n accept={assetAcceptAttr(field.accept)}\n onChange={applyPicked}\n />\n\n {rejected.length > 0 ? (\n <Row gap=\"2\" align=\"center\">\n <AlertTriangle className=\"size-4 shrink-0 text-warning\" />\n <Text size=\"xs\" tone=\"muted\">\n {rejected.length === 1\n ? translate('cms.asset.rejected.one', {\n name: rejected[0]!,\n types: describeAccept(field.accept),\n })\n : translate('cms.asset.rejected.other', {\n count: rejected.length,\n types: describeAccept(field.accept),\n })}\n </Text>\n </Row>\n ) : null}\n\n {dragging ? (\n <div className=\"pointer-events-none absolute inset-0 z-10 grid place-items-center rounded-lg border-2 border-dashed border-primary bg-background/85\">\n <Row gap=\"2\" align=\"center\">\n <Upload className=\"size-4 text-primary\" />\n <Text size=\"sm\" className=\"font-medium\">\n {translate('cms.asset.dropToUpload')}\n </Text>\n </Row>\n </div>\n ) : null}\n </Stack>\n )\n}\n\n/**\n * Empty-state placeholder shown when an asset field has no selection (EC-301).\n *\n * EC-395: this was the one asset-field affordance that ignored `readOnly` — every\n * sibling control (`selectButton`, the per-item remove/change actions) already\n * degraded, but the empty-state tile stayed a clickable `<button>` that opened the\n * picker regardless. A locked, empty asset field must be as inert as a filled one:\n * read-only renders a plain (non-interactive, non-focusable) `div` with the same\n * frame — no `onClick`, no hover affordance — instead of a `<button>`.\n */\nfunction EmptyAssetState({\n compact,\n canUpload,\n readOnly,\n onSelect,\n}: {\n compact: boolean\n canUpload: boolean\n readOnly: boolean\n onSelect: () => void\n}) {\n const frame = cn(\n 'grid w-full place-items-center gap-1 rounded-lg border border-dashed border-border bg-muted/30 text-center text-muted-foreground',\n compact ? 'aspect-video' : 'aspect-[5/2]',\n )\n if (readOnly) {\n return <div className={frame}>{emptyAssetStateBody(canUpload)}</div>\n }\n return (\n <button type=\"button\" onClick={onSelect} className={cn(frame, 'transition-colors hover:bg-muted/60')}>\n {emptyAssetStateBody(canUpload)}\n </button>\n )\n}\n\nfunction emptyAssetStateBody(canUpload: boolean) {\n return (\n <>\n <ImagePlus className=\"size-6\" aria-hidden />\n <Text size=\"xs\" tone=\"muted\">\n {canUpload ? translate('cms.asset.selectOrDrop') : translate('cms.asset.selectAsset')}\n </Text>\n </>\n )\n}\n\n/** A large focal-aware preview of the selected asset (EC-301). */\nfunction AssetPreview({ assetId }: { assetId: string }) {\n const record = useAssetRecord(assetId)\n return <AssetThumb record={record} className=\"aspect-[4/3] w-full\" />\n}\n\n/** Filename + dimensions/type for the selected asset — the id is never shown (EC-301). */\nfunction AssetInfo({ assetId, fallbackTitle }: { assetId: string; fallbackTitle?: string }) {\n const record = useAssetRecord(assetId)\n const name = record?.title ?? record?.filename ?? fallbackTitle ?? assetId\n const dims = record?.width && record?.height ? `${record.width}×${record.height}` : null\n const kind = record?.contentType?.split('/')[1]?.toUpperCase()\n const meta = [dims, kind].filter(Boolean).join(' · ')\n return (\n <Stack gap=\"1\">\n <Text size=\"sm\" className=\"truncate font-medium\" title={name}>\n {name}\n </Text>\n {meta ? (\n <Text size=\"xs\" tone=\"subtle\">\n {meta}\n </Text>\n ) : null}\n </Stack>\n )\n}\n\n/**\n * The selected image's alt text shown in the field (EC-301) — what an editor most\n * wants at a glance; edited in the options dialog. Reads `meta.alt` with the legacy\n * `alt` column as fallback, matching delivery.\n */\nfunction AssetAltLine({ assetId }: { assetId: string }) {\n const record = useAssetRecord(assetId)\n const metaAlt = typeof record?.meta?.alt === 'string' ? record.meta.alt : undefined\n const alt = metaAlt ?? record?.alt\n return (\n <Row gap=\"1\" align=\"baseline\" className=\"min-w-0\">\n <Text size=\"xs\" tone=\"subtle\" className=\"shrink-0\">\n {translate('cms.asset.altLabel')}\n </Text>\n <Text\n size=\"xs\"\n tone={alt ? 'muted' : 'subtle'}\n className={cn('min-w-0 truncate', !alt && 'italic')}\n title={alt ?? undefined}\n >\n {alt || translate('cms.asset.notSet')}\n </Text>\n </Row>\n )\n}\n\n/** A single asset reference: focal-aware thumbnail + title, with an optional remove. */\nfunction AssetChip({\n assetId,\n collectionId,\n onRemove,\n}: {\n assetId: string\n collectionId: string\n onRemove?: () => void\n}) {\n const source = useCollectionView()\n const record = useAssetRecord(assetId)\n const title = source.titleOf({ collection: collectionId, id: assetId })\n return (\n <div className=\"group relative overflow-hidden rounded-lg border border-border bg-muted/40\">\n <AssetThumb record={record} alt={title} className=\"aspect-square w-full rounded-none border-0\" />\n <Row gap=\"1\" align=\"center\" className=\"px-2 py-1\">\n <Text size=\"xs\" tone=\"subtle\" className=\"min-w-0 flex-1 truncate\" title={title}>\n {title}\n </Text>\n {onRemove ? (\n <button\n type=\"button\"\n aria-label={translate('common.remove')}\n onClick={onRemove}\n className=\"text-muted-foreground hover:text-destructive\"\n >\n <X className=\"size-3.5\" />\n </button>\n ) : null}\n </Row>\n </div>\n )\n}\n\n/**\n * The selected single asset's preview row (EC-301): large focal-aware preview + info\n * column (filename/dimensions, Select/Edit/Remove, alt). Extracted so it renders either\n * bare (a plain asset field) or inside the grouping card when the field carries per-use\n * sub-fields — the asset + its fields then read as one unit.\n */\nfunction AssetSinglePreview({\n assetId,\n compact,\n readOnly,\n hasLibrary,\n fallbackTitle,\n selectButton,\n onEdit,\n onRemove,\n}: {\n assetId: string\n compact: boolean\n readOnly: boolean\n hasLibrary: boolean\n fallbackTitle?: string\n selectButton: React.ReactNode\n onEdit: () => void\n onRemove: () => void\n}) {\n return (\n <div className={compact ? 'flex flex-col gap-3' : 'flex gap-4'}>\n <div className={compact ? 'w-full' : 'w-1/2 shrink-0'}>\n <AssetPreview assetId={assetId} />\n </div>\n <Stack gap=\"2\" className={cn('min-w-0', !compact && 'flex-1')}>\n <AssetInfo assetId={assetId} fallbackTitle={fallbackTitle} />\n {!readOnly ? (\n <Row gap=\"2\" wrap align=\"center\">\n {selectButton}\n {hasLibrary ? (\n <Button variant=\"outline\" size=\"sm\" onClick={onEdit}>\n <SlidersHorizontal /> {translate('common.edit')}\n </Button>\n ) : null}\n <Button variant=\"ghost\" size=\"sm\" onClick={onRemove}>\n <Trash2 /> {translate('common.remove')}\n </Button>\n </Row>\n ) : null}\n <AssetAltLine assetId={assetId} />\n </Stack>\n </div>\n )\n}\n\n/**\n * One gallery placement when a `many` asset field declares per-use sub-fields (EC-301):\n * a thumbnail + filename + remove header over the placement's own sub-field form,\n * modelled on a repeater item card so a gallery's placements read as grouped units.\n */\nfunction AssetGalleryItem({\n assetId,\n collectionId,\n fields,\n value,\n onChangeSubs,\n onRemove,\n readOnly,\n idPrefix,\n}: {\n assetId: string\n collectionId: string\n fields: readonly FieldDef[]\n value: Record<string, unknown>\n onChangeSubs: (value: Record<string, unknown>) => void\n onRemove?: () => void\n readOnly: boolean\n idPrefix: string\n}) {\n const source = useCollectionView()\n const record = useAssetRecord(assetId)\n const title = source.titleOf({ collection: collectionId, id: assetId })\n return (\n <Card className=\"border-l-2 border-l-border p-0\">\n <Row gap=\"2\" align=\"center\" className=\"border-b border-border/60 bg-muted/30 px-2 py-1.5\">\n <AssetThumb record={record} alt={title} className=\"size-10 shrink-0\" />\n <Text size=\"sm\" className=\"min-w-0 flex-1 truncate font-medium\" title={title}>\n {title}\n </Text>\n {onRemove ? (\n <Button variant=\"ghost\" size=\"sm\" aria-label={translate('common.remove')} onClick={onRemove}>\n <Trash2 className=\"size-3.5\" />\n </Button>\n ) : null}\n </Row>\n <div className=\"px-3 pb-3\">\n <ObjectItemForm\n fields={fields}\n value={value}\n onChange={onChangeSubs}\n readOnly={readOnly}\n idPrefix={idPrefix}\n />\n </div>\n </Card>\n )\n}\n\n/**\n * The image options dialog (EC-301): the focal point (engine-special visual\n * control) + the asset's **schema-driven** metadata, rendered from the\n * `kind:'asset'` collection's `fields` through the same generic field engine\n * documents use — so a project declares asset metadata (alt, copyright, credit, …)\n * in code, not hardcoded here. Edits persist on the asset, shared across every use.\n * Metadata is buffered locally and committed once on close (one write); the focal\n * point commits immediately (it drives a live visual). No-ops without a library.\n */\nfunction AssetOptionsDialog({\n assetId,\n readOnly,\n open,\n onOpenChange,\n}: {\n assetId: string\n readOnly: boolean\n open: boolean\n onOpenChange: (open: boolean) => void\n}) {\n const source = useCollectionView()\n const assets = useOptionalAssets()\n const record = useAssetRecord(assetId)\n const metaFields = useMemo(() => {\n const assetCollectionId = source.assetCollectionId ?? 'asset'\n const collection = source.collections.find((c) => c.id === assetCollectionId)\n return collection ? assetMetaFieldsOf(collection) : []\n }, [source])\n\n // Local edit buffers, seeded once per open from the record (so an external edit\n // still wins between opens). Metadata is non-localized in v1 (the asset\n // resolution path is locale-agnostic) — localized asset metadata is a follow-up.\n const [meta, setMeta] = useState<Record<string, unknown>>({})\n const [focal, setFocal] = useState<{ x: number; y: number } | undefined>(undefined)\n const dirty = useRef(false)\n const seeded = useRef(false)\n useEffect(() => {\n if (!open) {\n seeded.current = false\n return\n }\n if (seeded.current || !record) return\n // Seed from the meta bag, but lift the legacy `alt` column in when meta lacks\n // it (EC-301 transition): a not-yet-migrated asset still shows its alt, and\n // saving writes it into meta — a lazy migration-on-edit, matching delivery's\n // `meta.alt ?? record.alt` read fallback.\n const seedMeta = { ...(record.alt !== undefined ? { alt: record.alt } : {}), ...(record.meta ?? {}) }\n setMeta(seedMeta)\n setFocal(record.focalPoint)\n dirty.current = false\n seeded.current = true\n }, [open, record])\n\n function handleOpenChange(next: boolean) {\n // Commit the buffered metadata once, on close (one write, not per keystroke).\n if (!next && !readOnly && assets && dirty.current) {\n void assets.updateMetadata(assetId, { meta: pruneMeta(meta) })\n dirty.current = false\n }\n onOpenChange(next)\n }\n\n function setField(name: string, value: unknown) {\n dirty.current = true\n setMeta((m) => ({ ...m, [name]: value }))\n }\n\n // The focal point drives a live crop preview → commit immediately (optimistic\n // marker), not deferred to close.\n function commitFocal(point: { x: number; y: number } | null) {\n if (readOnly || !assets) return\n setFocal(point ?? undefined)\n void assets.updateMetadata(assetId, { focalPoint: point })\n }\n\n return (\n <Dialog open={open} onOpenChange={handleOpenChange}>\n <DialogContent className=\"max-w-lg\">\n <DialogHeader>\n <DialogTitle>{translate('cms.asset.imageOptions.title')}</DialogTitle>\n <DialogDescription>\n {translate('cms.asset.imageOptions.description')}\n </DialogDescription>\n </DialogHeader>\n <Stack gap=\"3\">\n <Stack gap=\"2\">\n <Row align=\"center\">\n <Text size=\"sm\" className=\"font-medium\">\n {translate('cms.asset.focalPoint')}\n </Text>\n <Spacer />\n {focal && !readOnly ? (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => commitFocal(null)}>\n <RotateCcw /> {translate('cms.asset.resetToCentre')}\n </Button>\n ) : null}\n </Row>\n <FocalPointPicker\n record={record}\n point={focal}\n onChange={commitFocal}\n onPreview={setFocal}\n disabled={readOnly}\n />\n <Text size=\"xs\" tone=\"subtle\">\n {translate('cms.asset.focalPointHint')}\n </Text>\n <FocalCropPreviews record={record} point={focal} />\n </Stack>\n {metaFields.map((f) => (\n <Field key={f.name} label={fieldLabel(f)} hint={localizedLabel(f.form?.description) || undefined}>\n <FieldControl\n id={`asset-meta-${f.name}`}\n field={f}\n value={meta[f.name]}\n readOnly={readOnly}\n onChange={(v) => setField(f.name, v)}\n />\n </Field>\n ))}\n {metaFields.length === 0 ? (\n <Text size=\"sm\" tone=\"subtle\">\n No editable metadata fields are declared on the asset collection. Add fields (e.g.\n <Code tone=\"muted\"> alt</Code>, <Code tone=\"muted\">copyright</Code>) to it in code.\n </Text>\n ) : null}\n </Stack>\n </DialogContent>\n </Dialog>\n )\n}\n"]}
|
|
@@ -118,13 +118,19 @@ function AssetPickerDialog({ value, onOpenChange, onChange, accept, }) {
|
|
|
118
118
|
function AssetPickerCard({ asset, selected, onPick, }) {
|
|
119
119
|
return (_jsxs("button", { type: "button", onClick: onPick, className: cn('flex flex-col gap-1.5 rounded-lg border p-2 text-left transition-colors', selected ? 'border-primary bg-primary/5' : 'border-border hover:bg-muted'), "data-testid": "asset-picker-card", children: [_jsx(PickerThumbnail, { asset: asset }), _jsxs(Row, { gap: "1", children: [_jsx(Text, { size: "xs", className: "min-w-0 flex-1 truncate font-medium", children: asset.title ?? asset.filename }), selected ? _jsx(Check, { className: "size-3.5 text-primary" }) : null] }), _jsx(Text, { size: "xs", tone: asset.alt ? 'muted' : 'subtle', className: "truncate", children: asset.alt ?? translate('cms.asset.noAltText') })] }));
|
|
120
120
|
}
|
|
121
|
-
/** Image preview
|
|
121
|
+
/** Image preview: the EC-400 downscaled thumb when resolvable, else the full
|
|
122
|
+
* original; icon fallback for other types. In-memory/opaque locators
|
|
123
|
+
* (memory://, convex://) are not fetchable by <img>, so those degrade too. */
|
|
122
124
|
function PickerThumbnail({ asset }) {
|
|
123
125
|
const isImage = asset.contentType.startsWith('image/');
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
const thumbUrl = asset.thumb?.url;
|
|
127
|
+
const src = thumbUrl?.startsWith('http') === true
|
|
128
|
+
? thumbUrl
|
|
129
|
+
: asset.url?.startsWith('http') === true
|
|
130
|
+
? asset.url
|
|
131
|
+
: undefined;
|
|
132
|
+
if (isImage && src) {
|
|
133
|
+
return (_jsx("img", { src: src, alt: asset.alt ?? asset.filename, className: "aspect-video w-full rounded-md bg-muted object-cover" }));
|
|
128
134
|
}
|
|
129
135
|
return (_jsx("span", { className: "grid aspect-video w-full place-items-center rounded-md bg-muted text-muted-foreground", children: _jsx(ImageIcon, { className: "size-6" }) }));
|
|
130
136
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"asset-picker.js","sourceRoot":"","sources":["../../../src/components/cms/asset-picker.tsx"],"names":[],"mappings":"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","sourcesContent":["import { AlertTriangle, Check, ImageIcon, Maximize2, Minimize2, Search, Upload } from 'lucide-react'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n Input,\n Row,\n Spacer,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { filterAssets } from '../assets/asset-search'\nimport { AssetsProvider, useAssets, useOptionalAssets } from '../assets/assets-store'\nimport { newlyAddedAssetIds } from '../assets/focal-point'\nimport { useCollectionView } from '../collection-view/collection-view-context'\n\n/**\n * Pick an asset for a CMS `asset` field (brief §8.4, §8.6, EC-075 → EC-152).\n * Backed by the *real* asset library (the assets store over the operations\n * client) — the same records the Assets section lists — not by fixture\n * documents. Selecting stores the asset id on the document field. The dialog\n * is form-width with a maximize toggle and supports drag/click upload that\n * auto-selects the new asset (EC-301).\n */\nexport function AssetPicker({\n value,\n open,\n onOpenChange,\n onChange,\n accept,\n}: {\n value: string | null\n open: boolean\n onOpenChange: (open: boolean) => void\n onChange: (assetId: string | null) => void\n /** The file input's `accept` attribute for in-picker upload (field-derived). */\n accept?: string\n}) {\n const source = useCollectionView()\n const ambient = useOptionalAssets()\n // Mount nothing while closed: the self-provided assets store would\n // otherwise load the library eagerly for every asset field on the form.\n if (!open) return null\n if (ambient) {\n return (\n <AssetPickerDialog value={value} onOpenChange={onOpenChange} onChange={onChange} accept={accept} />\n )\n }\n if (source.projectId === undefined) {\n // No project scope (fixture-only CMS session): the library is\n // unreachable — say so instead of silently showing nothing.\n return (\n <Dialog open onOpenChange={onOpenChange}>\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle>{translate('cms.asset.pickTitle')}</DialogTitle>\n <DialogDescription>{translate('cms.asset.pickDescription')}</DialogDescription>\n </DialogHeader>\n <Row gap=\"2\" data-testid=\"asset-picker-no-project\">\n <AlertTriangle className=\"size-4 text-warning\" />\n <Text size=\"sm\" tone=\"muted\">\n {translate('cms.asset.noProject')}\n </Text>\n </Row>\n </DialogContent>\n </Dialog>\n )\n }\n return (\n <AssetsProvider projectId={source.projectId}>\n <AssetPickerDialog value={value} onOpenChange={onOpenChange} onChange={onChange} accept={accept} />\n </AssetsProvider>\n )\n}\n\n/** The open dialog over a mounted assets store. */\nfunction AssetPickerDialog({\n value,\n onOpenChange,\n onChange,\n accept,\n}: {\n value: string | null\n onOpenChange: (open: boolean) => void\n onChange: (assetId: string | null) => void\n accept?: string\n}) {\n const store = useAssets()\n // Fresh per open: the picker unmounts entirely while closed.\n const [query, setQuery] = useState('')\n const [maximized, setMaximized] = useState(false)\n const [dragging, setDragging] = useState(false)\n const fileInputRef = useRef<HTMLInputElement>(null)\n // Library ids when an upload started; the effect below diffs against it to\n // learn the new asset and select it (the `upload()` API returns nothing).\n const pendingRef = useRef<ReadonlySet<string> | null>(null)\n // Drag depth (EC-301): keeps the drop overlay steady as the pointer crosses\n // child boundaries — the same per-element dragenter/dragleave flicker the field\n // fixes. Only hide when the count returns to zero.\n const dragDepth = useRef(0)\n const assets = filterAssets(store.assets, query)\n const libraryIds = useMemo(() => store.assets.map((a) => a.id), [store.assets])\n\n // Auto-select an asset uploaded from within the picker, then close.\n useEffect(() => {\n if (!pendingRef.current) return\n const added = newlyAddedAssetIds(pendingRef.current, libraryIds)\n if (added.length === 0) return\n pendingRef.current = null\n onChange(added[0]!)\n onOpenChange(false)\n }, [libraryIds, onChange, onOpenChange])\n\n function handleUpload(files: File[]) {\n if (files.length === 0) return\n pendingRef.current = new Set(libraryIds)\n store.upload(files)\n }\n // Radix portals the dialog in the DOM, but React synthetic events still bubble up\n // the COMPONENT tree — and this picker is rendered as a child of the asset field,\n // which has its own drag-to-upload handlers. Without stopPropagation a drop inside\n // the dialog would fire BOTH the picker's and the field's `onDrop`, uploading the\n // file twice. Stop every DnD event here so the picker owns its drop entirely.\n function onDragEnter(e: React.DragEvent) {\n e.preventDefault()\n e.stopPropagation()\n dragDepth.current += 1\n if (!dragging) setDragging(true)\n }\n function onDragOver(e: React.DragEvent) {\n e.preventDefault()\n e.stopPropagation()\n }\n function onDragLeave(e: React.DragEvent) {\n e.stopPropagation()\n dragDepth.current -= 1\n if (dragDepth.current <= 0) {\n dragDepth.current = 0\n setDragging(false)\n }\n }\n function onDrop(e: React.DragEvent) {\n e.preventDefault()\n e.stopPropagation()\n dragDepth.current = 0\n setDragging(false)\n handleUpload(Array.from(e.dataTransfer.files))\n }\n\n return (\n <Dialog open onOpenChange={onOpenChange}>\n <DialogContent\n className={cn(maximized ? 'max-w-[95vw]' : 'max-w-2xl')}\n onDragEnter={onDragEnter}\n onDragOver={onDragOver}\n onDragLeave={onDragLeave}\n onDrop={onDrop}\n >\n <DialogHeader>\n <Row gap=\"2\" align=\"center\">\n <Stack gap=\"1\" className=\"min-w-0 flex-1\">\n <DialogTitle>{translate('cms.asset.pickTitle')}</DialogTitle>\n <DialogDescription>{translate('cms.asset.pickDescription')}</DialogDescription>\n </Stack>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n aria-label={maximized ? translate('cms.asset.restoreDialogSize') : translate('cms.asset.maximizeDialog')}\n onClick={() => setMaximized((v) => !v)}\n className=\"mr-7\"\n >\n {maximized ? <Minimize2 className=\"size-4\" /> : <Maximize2 className=\"size-4\" />}\n </Button>\n </Row>\n </DialogHeader>\n\n <Row gap=\"2\" align=\"center\">\n <span className=\"relative flex-1\">\n <Search className=\"pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n <Input\n autoFocus\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder={translate('cms.asset.searchPlaceholder')}\n className=\"pl-8\"\n data-testid=\"asset-picker-search\"\n />\n </span>\n <Button variant=\"outline\" size=\"sm\" onClick={() => fileInputRef.current?.click()}>\n <Upload /> {translate('common.upload')}\n </Button>\n <input\n ref={fileInputRef}\n type=\"file\"\n multiple\n accept={accept ?? 'image/*'}\n className=\"hidden\"\n onChange={(e) => {\n handleUpload(Array.from(e.target.files ?? []))\n e.target.value = ''\n }}\n />\n </Row>\n\n <div\n className={cn(\n 'grid gap-2 overflow-auto',\n maximized\n ? 'max-h-[78vh] grid-cols-4 lg:grid-cols-6'\n : 'max-h-[60vh] grid-cols-3 sm:grid-cols-4',\n )}\n data-testid=\"asset-picker-grid\"\n >\n {store.status === 'loading' ? (\n <Text size=\"sm\" tone=\"muted\" className=\"col-span-full p-3\">\n {translate('cms.asset.loading')}\n </Text>\n ) : store.status === 'error' ? (\n <Row gap=\"2\" className=\"col-span-full p-3\" data-testid=\"asset-picker-error\">\n <AlertTriangle className=\"size-4 text-destructive\" />\n <Text size=\"sm\" tone=\"muted\">\n {store.errorMessage ?? translate('cms.asset.loadError')}\n </Text>\n </Row>\n ) : assets.length === 0 ? (\n <Text size=\"sm\" tone=\"muted\" className=\"col-span-full p-3\">\n {query.trim()\n ? translate('cms.asset.noMatches')\n : translate('cms.asset.empty')}\n </Text>\n ) : (\n assets.map((asset) => (\n <AssetPickerCard\n key={asset.id}\n asset={asset}\n selected={asset.id === value}\n onPick={() => {\n onChange(asset.id)\n onOpenChange(false)\n }}\n />\n ))\n )}\n </div>\n\n <Row gap=\"2\">\n <Spacer />\n {value ? (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => {\n onChange(null)\n onOpenChange(false)\n }}\n >\n {translate('common.clear')}\n </Button>\n ) : null}\n </Row>\n\n {dragging ? (\n <div className=\"pointer-events-none absolute inset-0 z-10 grid place-items-center rounded-xl border-2 border-dashed border-primary bg-background/85\">\n <Row gap=\"2\" align=\"center\">\n <Upload className=\"size-5 text-primary\" />\n <Text className=\"font-medium\">{translate('cms.asset.dropToUpload')}</Text>\n </Row>\n </div>\n ) : null}\n </DialogContent>\n </Dialog>\n )\n}\n\n/** One selectable asset: thumbnail + title/filename + alt text. The internal\n * asset id is intentionally not shown — it's noise to editors (EC-301). */\nfunction AssetPickerCard({\n asset,\n selected,\n onPick,\n}: {\n asset: AssetRecord\n selected: boolean\n onPick: () => void\n}) {\n return (\n <button\n type=\"button\"\n onClick={onPick}\n className={cn(\n 'flex flex-col gap-1.5 rounded-lg border p-2 text-left transition-colors',\n selected ? 'border-primary bg-primary/5' : 'border-border hover:bg-muted',\n )}\n data-testid=\"asset-picker-card\"\n >\n <PickerThumbnail asset={asset} />\n <Row gap=\"1\">\n <Text size=\"xs\" className=\"min-w-0 flex-1 truncate font-medium\">\n {asset.title ?? asset.filename}\n </Text>\n {selected ? <Check className=\"size-3.5 text-primary\" /> : null}\n </Row>\n <Text size=\"xs\" tone={asset.alt ? 'muted' : 'subtle'} className=\"truncate\">\n {asset.alt ?? translate('cms.asset.noAltText')}\n </Text>\n </button>\n )\n}\n\n/** Image preview from the stored serve URL; icon fallback for other types. */\nfunction PickerThumbnail({ asset }: { asset: AssetRecord }) {\n const isImage = asset.contentType.startsWith('image/')\n // In-memory/opaque locators (memory://, convex://) are not fetchable by <img>.\n const resolvable = asset.url?.startsWith('http') ?? false\n if (isImage && asset.url && resolvable) {\n return (\n <img\n src={asset.url}\n alt={asset.alt ?? asset.filename}\n className=\"aspect-video w-full rounded-md bg-muted object-cover\"\n />\n )\n }\n return (\n <span className=\"grid aspect-video w-full place-items-center rounded-md bg-muted text-muted-foreground\">\n <ImageIcon className=\"size-6\" />\n </span>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"asset-picker.js","sourceRoot":"","sources":["../../../src/components/cms/asset-picker.tsx"],"names":[],"mappings":"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","sourcesContent":["import { AlertTriangle, Check, ImageIcon, Maximize2, Minimize2, Search, Upload } from 'lucide-react'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n Input,\n Row,\n Spacer,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { filterAssets } from '../assets/asset-search'\nimport { AssetsProvider, useAssets, useOptionalAssets } from '../assets/assets-store'\nimport { newlyAddedAssetIds } from '../assets/focal-point'\nimport { useCollectionView } from '../collection-view/collection-view-context'\n\n/**\n * Pick an asset for a CMS `asset` field (brief §8.4, §8.6, EC-075 → EC-152).\n * Backed by the *real* asset library (the assets store over the operations\n * client) — the same records the Assets section lists — not by fixture\n * documents. Selecting stores the asset id on the document field. The dialog\n * is form-width with a maximize toggle and supports drag/click upload that\n * auto-selects the new asset (EC-301).\n */\nexport function AssetPicker({\n value,\n open,\n onOpenChange,\n onChange,\n accept,\n}: {\n value: string | null\n open: boolean\n onOpenChange: (open: boolean) => void\n onChange: (assetId: string | null) => void\n /** The file input's `accept` attribute for in-picker upload (field-derived). */\n accept?: string\n}) {\n const source = useCollectionView()\n const ambient = useOptionalAssets()\n // Mount nothing while closed: the self-provided assets store would\n // otherwise load the library eagerly for every asset field on the form.\n if (!open) return null\n if (ambient) {\n return (\n <AssetPickerDialog value={value} onOpenChange={onOpenChange} onChange={onChange} accept={accept} />\n )\n }\n if (source.projectId === undefined) {\n // No project scope (fixture-only CMS session): the library is\n // unreachable — say so instead of silently showing nothing.\n return (\n <Dialog open onOpenChange={onOpenChange}>\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle>{translate('cms.asset.pickTitle')}</DialogTitle>\n <DialogDescription>{translate('cms.asset.pickDescription')}</DialogDescription>\n </DialogHeader>\n <Row gap=\"2\" data-testid=\"asset-picker-no-project\">\n <AlertTriangle className=\"size-4 text-warning\" />\n <Text size=\"sm\" tone=\"muted\">\n {translate('cms.asset.noProject')}\n </Text>\n </Row>\n </DialogContent>\n </Dialog>\n )\n }\n return (\n <AssetsProvider projectId={source.projectId}>\n <AssetPickerDialog value={value} onOpenChange={onOpenChange} onChange={onChange} accept={accept} />\n </AssetsProvider>\n )\n}\n\n/** The open dialog over a mounted assets store. */\nfunction AssetPickerDialog({\n value,\n onOpenChange,\n onChange,\n accept,\n}: {\n value: string | null\n onOpenChange: (open: boolean) => void\n onChange: (assetId: string | null) => void\n accept?: string\n}) {\n const store = useAssets()\n // Fresh per open: the picker unmounts entirely while closed.\n const [query, setQuery] = useState('')\n const [maximized, setMaximized] = useState(false)\n const [dragging, setDragging] = useState(false)\n const fileInputRef = useRef<HTMLInputElement>(null)\n // Library ids when an upload started; the effect below diffs against it to\n // learn the new asset and select it (the `upload()` API returns nothing).\n const pendingRef = useRef<ReadonlySet<string> | null>(null)\n // Drag depth (EC-301): keeps the drop overlay steady as the pointer crosses\n // child boundaries — the same per-element dragenter/dragleave flicker the field\n // fixes. Only hide when the count returns to zero.\n const dragDepth = useRef(0)\n const assets = filterAssets(store.assets, query)\n const libraryIds = useMemo(() => store.assets.map((a) => a.id), [store.assets])\n\n // Auto-select an asset uploaded from within the picker, then close.\n useEffect(() => {\n if (!pendingRef.current) return\n const added = newlyAddedAssetIds(pendingRef.current, libraryIds)\n if (added.length === 0) return\n pendingRef.current = null\n onChange(added[0]!)\n onOpenChange(false)\n }, [libraryIds, onChange, onOpenChange])\n\n function handleUpload(files: File[]) {\n if (files.length === 0) return\n pendingRef.current = new Set(libraryIds)\n store.upload(files)\n }\n // Radix portals the dialog in the DOM, but React synthetic events still bubble up\n // the COMPONENT tree — and this picker is rendered as a child of the asset field,\n // which has its own drag-to-upload handlers. Without stopPropagation a drop inside\n // the dialog would fire BOTH the picker's and the field's `onDrop`, uploading the\n // file twice. Stop every DnD event here so the picker owns its drop entirely.\n function onDragEnter(e: React.DragEvent) {\n e.preventDefault()\n e.stopPropagation()\n dragDepth.current += 1\n if (!dragging) setDragging(true)\n }\n function onDragOver(e: React.DragEvent) {\n e.preventDefault()\n e.stopPropagation()\n }\n function onDragLeave(e: React.DragEvent) {\n e.stopPropagation()\n dragDepth.current -= 1\n if (dragDepth.current <= 0) {\n dragDepth.current = 0\n setDragging(false)\n }\n }\n function onDrop(e: React.DragEvent) {\n e.preventDefault()\n e.stopPropagation()\n dragDepth.current = 0\n setDragging(false)\n handleUpload(Array.from(e.dataTransfer.files))\n }\n\n return (\n <Dialog open onOpenChange={onOpenChange}>\n <DialogContent\n className={cn(maximized ? 'max-w-[95vw]' : 'max-w-2xl')}\n onDragEnter={onDragEnter}\n onDragOver={onDragOver}\n onDragLeave={onDragLeave}\n onDrop={onDrop}\n >\n <DialogHeader>\n <Row gap=\"2\" align=\"center\">\n <Stack gap=\"1\" className=\"min-w-0 flex-1\">\n <DialogTitle>{translate('cms.asset.pickTitle')}</DialogTitle>\n <DialogDescription>{translate('cms.asset.pickDescription')}</DialogDescription>\n </Stack>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n aria-label={maximized ? translate('cms.asset.restoreDialogSize') : translate('cms.asset.maximizeDialog')}\n onClick={() => setMaximized((v) => !v)}\n className=\"mr-7\"\n >\n {maximized ? <Minimize2 className=\"size-4\" /> : <Maximize2 className=\"size-4\" />}\n </Button>\n </Row>\n </DialogHeader>\n\n <Row gap=\"2\" align=\"center\">\n <span className=\"relative flex-1\">\n <Search className=\"pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n <Input\n autoFocus\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder={translate('cms.asset.searchPlaceholder')}\n className=\"pl-8\"\n data-testid=\"asset-picker-search\"\n />\n </span>\n <Button variant=\"outline\" size=\"sm\" onClick={() => fileInputRef.current?.click()}>\n <Upload /> {translate('common.upload')}\n </Button>\n <input\n ref={fileInputRef}\n type=\"file\"\n multiple\n accept={accept ?? 'image/*'}\n className=\"hidden\"\n onChange={(e) => {\n handleUpload(Array.from(e.target.files ?? []))\n e.target.value = ''\n }}\n />\n </Row>\n\n <div\n className={cn(\n 'grid gap-2 overflow-auto',\n maximized\n ? 'max-h-[78vh] grid-cols-4 lg:grid-cols-6'\n : 'max-h-[60vh] grid-cols-3 sm:grid-cols-4',\n )}\n data-testid=\"asset-picker-grid\"\n >\n {store.status === 'loading' ? (\n <Text size=\"sm\" tone=\"muted\" className=\"col-span-full p-3\">\n {translate('cms.asset.loading')}\n </Text>\n ) : store.status === 'error' ? (\n <Row gap=\"2\" className=\"col-span-full p-3\" data-testid=\"asset-picker-error\">\n <AlertTriangle className=\"size-4 text-destructive\" />\n <Text size=\"sm\" tone=\"muted\">\n {store.errorMessage ?? translate('cms.asset.loadError')}\n </Text>\n </Row>\n ) : assets.length === 0 ? (\n <Text size=\"sm\" tone=\"muted\" className=\"col-span-full p-3\">\n {query.trim()\n ? translate('cms.asset.noMatches')\n : translate('cms.asset.empty')}\n </Text>\n ) : (\n assets.map((asset) => (\n <AssetPickerCard\n key={asset.id}\n asset={asset}\n selected={asset.id === value}\n onPick={() => {\n onChange(asset.id)\n onOpenChange(false)\n }}\n />\n ))\n )}\n </div>\n\n <Row gap=\"2\">\n <Spacer />\n {value ? (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => {\n onChange(null)\n onOpenChange(false)\n }}\n >\n {translate('common.clear')}\n </Button>\n ) : null}\n </Row>\n\n {dragging ? (\n <div className=\"pointer-events-none absolute inset-0 z-10 grid place-items-center rounded-xl border-2 border-dashed border-primary bg-background/85\">\n <Row gap=\"2\" align=\"center\">\n <Upload className=\"size-5 text-primary\" />\n <Text className=\"font-medium\">{translate('cms.asset.dropToUpload')}</Text>\n </Row>\n </div>\n ) : null}\n </DialogContent>\n </Dialog>\n )\n}\n\n/** One selectable asset: thumbnail + title/filename + alt text. The internal\n * asset id is intentionally not shown — it's noise to editors (EC-301). */\nfunction AssetPickerCard({\n asset,\n selected,\n onPick,\n}: {\n asset: AssetRecord\n selected: boolean\n onPick: () => void\n}) {\n return (\n <button\n type=\"button\"\n onClick={onPick}\n className={cn(\n 'flex flex-col gap-1.5 rounded-lg border p-2 text-left transition-colors',\n selected ? 'border-primary bg-primary/5' : 'border-border hover:bg-muted',\n )}\n data-testid=\"asset-picker-card\"\n >\n <PickerThumbnail asset={asset} />\n <Row gap=\"1\">\n <Text size=\"xs\" className=\"min-w-0 flex-1 truncate font-medium\">\n {asset.title ?? asset.filename}\n </Text>\n {selected ? <Check className=\"size-3.5 text-primary\" /> : null}\n </Row>\n <Text size=\"xs\" tone={asset.alt ? 'muted' : 'subtle'} className=\"truncate\">\n {asset.alt ?? translate('cms.asset.noAltText')}\n </Text>\n </button>\n )\n}\n\n/** Image preview: the EC-400 downscaled thumb when resolvable, else the full\n * original; icon fallback for other types. In-memory/opaque locators\n * (memory://, convex://) are not fetchable by <img>, so those degrade too. */\nfunction PickerThumbnail({ asset }: { asset: AssetRecord }) {\n const isImage = asset.contentType.startsWith('image/')\n const thumbUrl = asset.thumb?.url\n const src =\n thumbUrl?.startsWith('http') === true\n ? thumbUrl\n : asset.url?.startsWith('http') === true\n ? asset.url\n : undefined\n if (isImage && src) {\n return (\n <img\n src={src}\n alt={asset.alt ?? asset.filename}\n className=\"aspect-video w-full rounded-md bg-muted object-cover\"\n />\n )\n }\n return (\n <span className=\"grid aspect-video w-full place-items-center rounded-md bg-muted text-muted-foreground\">\n <ImageIcon className=\"size-6\" />\n </span>\n )\n}\n"]}
|
|
@@ -61,6 +61,22 @@ export interface BulkContext {
|
|
|
61
61
|
* when the action is `set-field`; unused otherwise.
|
|
62
62
|
*/
|
|
63
63
|
applySetField?(ref: DocumentRef, set: BulkSetField): void;
|
|
64
|
+
/**
|
|
65
|
+
* EC-425: the publish-time hidden-strip (EC-273 clearHiddenValues + EC-394
|
|
66
|
+
* clearHiddenProps, the SAME shared clean step the single-document `doPublish`
|
|
67
|
+
* runs) — returns the document a publish should snapshot. Identity-preserving:
|
|
68
|
+
* returns the input when nothing clears. Absent ⇒ publish the draft as stored
|
|
69
|
+
* (fixture harnesses without a clean point).
|
|
70
|
+
*/
|
|
71
|
+
cleanForPublish?(doc: CmsDocument): CmsDocument;
|
|
72
|
+
/**
|
|
73
|
+
* EC-425: land a cleaned draft (session + persistence) BEFORE its publish
|
|
74
|
+
* command dispatches, so the snapshot the publish pins — and the reference
|
|
75
|
+
* index derived from the stored draft (EC-286) — are clean. Awaited; only
|
|
76
|
+
* called when {@link cleanForPublish} actually changed the document
|
|
77
|
+
* (save-if-changed, mirroring `doPublish`).
|
|
78
|
+
*/
|
|
79
|
+
applyCleaned?(ref: DocumentRef, doc: CmsDocument): Promise<void> | void;
|
|
64
80
|
/**
|
|
65
81
|
* Dispatch the ONE operations command for an item. Absent in fixture-only
|
|
66
82
|
* sessions (no project scope) — items then mutate the session directly.
|
|
@@ -71,8 +87,11 @@ export interface BulkContext {
|
|
|
71
87
|
* Run one bulk action over a selection, item by item:
|
|
72
88
|
* 1. publish gates on validation — an invalid document fails with its issues
|
|
73
89
|
* and dispatches NOTHING (a set-field is a draft edit, EC-225 — never gated),
|
|
74
|
-
* 2.
|
|
75
|
-
*
|
|
76
|
-
*
|
|
90
|
+
* 2. publish joins the single-document clean point (EC-425): the hidden-strip
|
|
91
|
+
* runs per item and a changed draft is saved (awaited) BEFORE the publish
|
|
92
|
+
* command, so bulk-published snapshots go out as clean as editor publishes,
|
|
93
|
+
* 3. each remaining item dispatches exactly one operations command, awaited,
|
|
94
|
+
* 4. only succeeded items mutate the session,
|
|
95
|
+
* 5. the report carries one honest outcome per item, in selection order.
|
|
77
96
|
*/
|
|
78
97
|
export declare function runBulkAction(context: BulkContext, collectionId: string, ids: readonly string[], action: BulkAction, setField?: BulkSetField): Promise<BulkReport>;
|
|
@@ -21,9 +21,12 @@ const ACTION_STATE = {
|
|
|
21
21
|
* Run one bulk action over a selection, item by item:
|
|
22
22
|
* 1. publish gates on validation — an invalid document fails with its issues
|
|
23
23
|
* and dispatches NOTHING (a set-field is a draft edit, EC-225 — never gated),
|
|
24
|
-
* 2.
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* 2. publish joins the single-document clean point (EC-425): the hidden-strip
|
|
25
|
+
* runs per item and a changed draft is saved (awaited) BEFORE the publish
|
|
26
|
+
* command, so bulk-published snapshots go out as clean as editor publishes,
|
|
27
|
+
* 3. each remaining item dispatches exactly one operations command, awaited,
|
|
28
|
+
* 4. only succeeded items mutate the session,
|
|
29
|
+
* 5. the report carries one honest outcome per item, in selection order.
|
|
27
30
|
*/
|
|
28
31
|
export async function runBulkAction(context, collectionId, ids, action, setField) {
|
|
29
32
|
const results = [];
|
|
@@ -56,6 +59,27 @@ export async function runBulkAction(context, collectionId, ids, action, setField
|
|
|
56
59
|
});
|
|
57
60
|
continue;
|
|
58
61
|
}
|
|
62
|
+
// EC-425: the SAME clean-then-save sequence the single-document `doPublish`
|
|
63
|
+
// runs, per item — validation gated on the live draft above (matching the
|
|
64
|
+
// editor's pre-strip gate), then the hidden-strip; a changed draft lands
|
|
65
|
+
// (awaited) before the publish command so the pinned snapshot and the
|
|
66
|
+
// EC-286-derived reference index are clean. A failing save is an honest
|
|
67
|
+
// per-item failure and the publish never dispatches on the dirty draft.
|
|
68
|
+
if (context.cleanForPublish) {
|
|
69
|
+
const cleaned = context.cleanForPublish(doc);
|
|
70
|
+
if (cleaned !== doc) {
|
|
71
|
+
try {
|
|
72
|
+
await context.applyCleaned?.(ref, cleaned);
|
|
73
|
+
}
|
|
74
|
+
catch (cause) {
|
|
75
|
+
fail({
|
|
76
|
+
code: 'unknown',
|
|
77
|
+
message: cause instanceof Error ? cause.message : String(cause),
|
|
78
|
+
});
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
59
83
|
}
|
|
60
84
|
if (context.dispatch) {
|
|
61
85
|
let result;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bulk-actions.js","sourceRoot":"","sources":["../../../src/components/cms/bulk-actions.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AA8CjD,MAAM,UAAU,WAAW,CAAC,MAAkB;IAC5C,IAAI,SAAS,GAAG,CAAC,CAAA;IACjB,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,OAAO;QAAE,IAAI,MAAM,CAAC,EAAE;YAAE,SAAS,IAAI,CAAC,CAAA;IAClE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,SAAS,EAAE,CAAA;AACjE,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,eAAe,CAAC,KAAqB;IACnD,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3D,OAAO,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC/D,CAAC;IACD,OAAO,KAAK,CAAC,OAAO,CAAA;AACtB,CAAC;AAwBD,MAAM,YAAY,GAAsD;IACtE,OAAO,EAAE,WAAW;IACpB,SAAS,EAAE,OAAO;CACnB,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAAoB,EACpB,YAAoB,EACpB,GAAsB,EACtB,MAAkB,EAClB,QAAuB;IAEvB,MAAM,OAAO,GAAsB,EAAE,CAAA;IAErC,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;QACrB,MAAM,GAAG,GAAgB,EAAE,UAAU,EAAE,YAAY,EAAE,EAAE,EAAE,CAAA;QACzD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAClC,MAAM,IAAI,GAAG,CAAC,KAAqB,EAAE,EAAE,CACrC,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;QAEhF,MAAM,GAAG,GAAG,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;QAC7D,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,IAAI,CAAC;gBACH,IAAI,EAAE,WAAW;gBACjB,OAAO,EAAE,uCAAuC;gBAChD,MAAM,EAAE,cAAc;gBACtB,GAAG,EAAE,GAAG,YAAY,IAAI,EAAE,EAAE;aAC7B,CAAC,CAAA;YACF,SAAQ;QACV,CAAC;QAED,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAA;YAClF,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACtB,IAAI,CAAC;oBACH,IAAI,EAAE,YAAY;oBAClB,OAAO,EAAE,mCAAmC;oBAC5C,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;wBAC7B,IAAI,EAAE,KAAK,CAAC,IAAI;wBAChB,OAAO,EAAE,KAAK,CAAC,OAAO;wBACtB,IAAI,EAAE,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC;wBACrB,MAAM,EAAE,KAAK;qBACd,CAAC,CAAC;iBACJ,CAAC,CAAA;gBACF,SAAQ;YACV,CAAC;QACH,CAAC;QAED,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACrB,IAAI,MAAgC,CAAA;YACpC,IAAI,CAAC;gBACH,MAAM,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;YAC9C,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,uEAAuE;gBACvE,6DAA6D;gBAC7D,MAAM,GAAG;oBACP,EAAE,EAAE,KAAK;oBACT,KAAK,EAAE;wBACL,IAAI,EAAE,SAAS;wBACf,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;qBAChE;iBACF,CAAA;YACH,CAAC;YACD,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;gBACf,oEAAoE;gBACpE,gEAAgE;gBAChE,mEAAmE;gBACnE,yDAAyD;gBACzD,MAAM,gBAAgB,GAAG,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,WAAW,CAAA;gBACjF,IAAI,CAAC,gBAAgB,EAAE,CAAC;oBACtB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;oBAClB,SAAQ;gBACV,CAAC;YACH,CAAC;QACH,CAAC;QAED,IAAI,MAAM,KAAK,QAAQ;YAAE,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;aAC5C,IAAI,MAAM,KAAK,WAAW,EAAE,CAAC;YAChC,IAAI,QAAQ;gBAAE,OAAO,CAAC,aAAa,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;QACtD,CAAC;;YAAM,OAAO,CAAC,UAAU,CAAC,GAAG,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC,CAAA;QACpD,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;IACxC,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,CAAA;AAC/D,CAAC","sourcesContent":["import type {\n CmsDocument,\n CmsValidationIssue,\n DocumentRef,\n PublicationState,\n} from '@elytracms/core/cms-core'\nimport type { FilterScalar } from '@elytracms/core/content'\nimport type { OperationError, OperationResult } from '@elytracms/studio/lib/operations'\nimport { matchesRef } from './document-ref-utils'\n\n/**\n * Bulk actions over a table selection (EC-169) — the pure half.\n *\n * Each selected document gets its OWN operations command\n * (`documents.publish` / `documents.unpublish` / `documents.delete`, EC-224;\n * `documents.upsert` for a set-field, EC-247), awaited in selection order, and\n * its own per-item outcome. The report enumerates every failure with its\n * reason (validation gate, authorization denial, not-found, …) — a partial\n * failure is never silent and never collapsed into one toast. Session state is\n * only updated for items whose command succeeded.\n */\n\nexport type BulkAction = 'publish' | 'unpublish' | 'delete' | 'set-field'\n\n/**\n * The payload of a `set-field` bulk action (EC-247): assign one filterable\n * scalar field the same value across every selected document. The value is\n * folded into each document the locale-aware way (`applyCellValue`) by the\n * caller's `applySetField`/`dispatch`; this module only carries it.\n */\nexport interface BulkSetField {\n field: string\n value: FilterScalar\n}\n\n/** The outcome of one document's command within a bulk run. */\nexport interface BulkItemOutcome {\n ref: DocumentRef\n /** Display title at dispatch time (deleted docs still get named in the report). */\n title: string\n ok: boolean\n /** Human-readable failure reason — present exactly when `ok` is false. */\n reason?: string\n /** The structured error behind the reason (operation result / validation gate). */\n error?: OperationError\n}\n\nexport interface BulkReport {\n action: BulkAction\n /** Present for `set-field`: the field + value that was applied (report label). */\n setField?: BulkSetField\n results: BulkItemOutcome[]\n}\n\nexport function bulkSummary(report: BulkReport): { succeeded: number; failed: number } {\n let succeeded = 0\n for (const result of report.results) if (result.ok) succeeded += 1\n return { succeeded, failed: report.results.length - succeeded }\n}\n\n/** The failure reason a report row shows: validation issues beat the envelope. */\nexport function bulkErrorReason(error: OperationError): string {\n if (error.code === 'validation' && error.issues.length > 0) {\n return error.issues.map((issue) => issue.message).join(' · ')\n }\n return error.message\n}\n\nexport interface BulkContext {\n /** Current session documents (lookup + validation input). */\n documents: readonly CmsDocument[]\n titleOf(ref: DocumentRef): string\n /** Full document validation — gates publish: invalid documents never dispatch. */\n validate(doc: CmsDocument): CmsValidationIssue[]\n /** Apply one succeeded state transition to the session. */\n applyState(ref: DocumentRef, state: PublicationState): void\n /** Apply one succeeded delete to the session. */\n applyDelete(ref: DocumentRef): void\n /**\n * Apply one succeeded set-field to the session (locale-aware fold). Required\n * when the action is `set-field`; unused otherwise.\n */\n applySetField?(ref: DocumentRef, set: BulkSetField): void\n /**\n * Dispatch the ONE operations command for an item. Absent in fixture-only\n * sessions (no project scope) — items then mutate the session directly.\n */\n dispatch?: (action: BulkAction, ref: DocumentRef) => Promise<OperationResult<unknown>>\n}\n\nconst ACTION_STATE: Record<'publish' | 'unpublish', PublicationState> = {\n publish: 'published',\n unpublish: 'draft',\n}\n\n/**\n * Run one bulk action over a selection, item by item:\n * 1. publish gates on validation — an invalid document fails with its issues\n * and dispatches NOTHING (a set-field is a draft edit, EC-225 — never gated),\n * 2. each remaining item dispatches exactly one operations command, awaited,\n * 3. only succeeded items mutate the session,\n * 4. the report carries one honest outcome per item, in selection order.\n */\nexport async function runBulkAction(\n context: BulkContext,\n collectionId: string,\n ids: readonly string[],\n action: BulkAction,\n setField?: BulkSetField,\n): Promise<BulkReport> {\n const results: BulkItemOutcome[] = []\n\n for (const id of ids) {\n const ref: DocumentRef = { collection: collectionId, id }\n const title = context.titleOf(ref)\n const fail = (error: OperationError) =>\n results.push({ ref, title, ok: false, reason: bulkErrorReason(error), error })\n\n const doc = context.documents.find((d) => matchesRef(d, ref))\n if (!doc) {\n fail({\n code: 'not-found',\n message: 'Document is no longer in the session.',\n entity: 'cms-document',\n key: `${collectionId}:${id}`,\n })\n continue\n }\n\n if (action === 'publish') {\n const errors = context.validate(doc).filter((issue) => issue.severity === 'error')\n if (errors.length > 0) {\n fail({\n code: 'validation',\n message: 'Document is not valid to publish.',\n issues: errors.map((issue) => ({\n code: issue.code,\n message: issue.message,\n path: [...issue.path],\n domain: 'cms',\n })),\n })\n continue\n }\n }\n\n if (context.dispatch) {\n let result: OperationResult<unknown>\n try {\n result = await context.dispatch(action, ref)\n } catch (cause) {\n // Operations never throw by contract; a broken transport still becomes\n // a visible per-item failure instead of an aborted bulk run.\n result = {\n ok: false,\n error: {\n code: 'unknown',\n message: cause instanceof Error ? cause.message : String(cause),\n },\n }\n }\n if (!result.ok) {\n // Idempotent delete: the document was never persisted (e.g. a draft\n // whose baseline upsert was rejected) or another editor already\n // deleted it. The desired end state — gone — holds, so removing it\n // from the session is the honest outcome, not a failure.\n const idempotentDelete = action === 'delete' && result.error.code === 'not-found'\n if (!idempotentDelete) {\n fail(result.error)\n continue\n }\n }\n }\n\n if (action === 'delete') context.applyDelete(ref)\n else if (action === 'set-field') {\n if (setField) context.applySetField?.(ref, setField)\n } else context.applyState(ref, ACTION_STATE[action])\n results.push({ ref, title, ok: true })\n }\n\n return { action, ...(setField ? { setField } : {}), results }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"bulk-actions.js","sourceRoot":"","sources":["../../../src/components/cms/bulk-actions.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n CmsDocument,\n CmsValidationIssue,\n DocumentRef,\n PublicationState,\n} from '@elytracms/core/cms-core'\nimport type { FilterScalar } from '@elytracms/core/content'\nimport type { OperationError, OperationResult } from '@elytracms/studio/lib/operations'\nimport { matchesRef } from './document-ref-utils'\n\n/**\n * Bulk actions over a table selection (EC-169) — the pure half.\n *\n * Each selected document gets its OWN operations command\n * (`documents.publish` / `documents.unpublish` / `documents.delete`, EC-224;\n * `documents.upsert` for a set-field, EC-247), awaited in selection order, and\n * its own per-item outcome. The report enumerates every failure with its\n * reason (validation gate, authorization denial, not-found, …) — a partial\n * failure is never silent and never collapsed into one toast. Session state is\n * only updated for items whose command succeeded.\n */\n\nexport type BulkAction = 'publish' | 'unpublish' | 'delete' | 'set-field'\n\n/**\n * The payload of a `set-field` bulk action (EC-247): assign one filterable\n * scalar field the same value across every selected document. The value is\n * folded into each document the locale-aware way (`applyCellValue`) by the\n * caller's `applySetField`/`dispatch`; this module only carries it.\n */\nexport interface BulkSetField {\n field: string\n value: FilterScalar\n}\n\n/** The outcome of one document's command within a bulk run. */\nexport interface BulkItemOutcome {\n ref: DocumentRef\n /** Display title at dispatch time (deleted docs still get named in the report). */\n title: string\n ok: boolean\n /** Human-readable failure reason — present exactly when `ok` is false. */\n reason?: string\n /** The structured error behind the reason (operation result / validation gate). */\n error?: OperationError\n}\n\nexport interface BulkReport {\n action: BulkAction\n /** Present for `set-field`: the field + value that was applied (report label). */\n setField?: BulkSetField\n results: BulkItemOutcome[]\n}\n\nexport function bulkSummary(report: BulkReport): { succeeded: number; failed: number } {\n let succeeded = 0\n for (const result of report.results) if (result.ok) succeeded += 1\n return { succeeded, failed: report.results.length - succeeded }\n}\n\n/** The failure reason a report row shows: validation issues beat the envelope. */\nexport function bulkErrorReason(error: OperationError): string {\n if (error.code === 'validation' && error.issues.length > 0) {\n return error.issues.map((issue) => issue.message).join(' · ')\n }\n return error.message\n}\n\nexport interface BulkContext {\n /** Current session documents (lookup + validation input). */\n documents: readonly CmsDocument[]\n titleOf(ref: DocumentRef): string\n /** Full document validation — gates publish: invalid documents never dispatch. */\n validate(doc: CmsDocument): CmsValidationIssue[]\n /** Apply one succeeded state transition to the session. */\n applyState(ref: DocumentRef, state: PublicationState): void\n /** Apply one succeeded delete to the session. */\n applyDelete(ref: DocumentRef): void\n /**\n * Apply one succeeded set-field to the session (locale-aware fold). Required\n * when the action is `set-field`; unused otherwise.\n */\n applySetField?(ref: DocumentRef, set: BulkSetField): void\n /**\n * EC-425: the publish-time hidden-strip (EC-273 clearHiddenValues + EC-394\n * clearHiddenProps, the SAME shared clean step the single-document `doPublish`\n * runs) — returns the document a publish should snapshot. Identity-preserving:\n * returns the input when nothing clears. Absent ⇒ publish the draft as stored\n * (fixture harnesses without a clean point).\n */\n cleanForPublish?(doc: CmsDocument): CmsDocument\n /**\n * EC-425: land a cleaned draft (session + persistence) BEFORE its publish\n * command dispatches, so the snapshot the publish pins — and the reference\n * index derived from the stored draft (EC-286) — are clean. Awaited; only\n * called when {@link cleanForPublish} actually changed the document\n * (save-if-changed, mirroring `doPublish`).\n */\n applyCleaned?(ref: DocumentRef, doc: CmsDocument): Promise<void> | void\n /**\n * Dispatch the ONE operations command for an item. Absent in fixture-only\n * sessions (no project scope) — items then mutate the session directly.\n */\n dispatch?: (action: BulkAction, ref: DocumentRef) => Promise<OperationResult<unknown>>\n}\n\nconst ACTION_STATE: Record<'publish' | 'unpublish', PublicationState> = {\n publish: 'published',\n unpublish: 'draft',\n}\n\n/**\n * Run one bulk action over a selection, item by item:\n * 1. publish gates on validation — an invalid document fails with its issues\n * and dispatches NOTHING (a set-field is a draft edit, EC-225 — never gated),\n * 2. publish joins the single-document clean point (EC-425): the hidden-strip\n * runs per item and a changed draft is saved (awaited) BEFORE the publish\n * command, so bulk-published snapshots go out as clean as editor publishes,\n * 3. each remaining item dispatches exactly one operations command, awaited,\n * 4. only succeeded items mutate the session,\n * 5. the report carries one honest outcome per item, in selection order.\n */\nexport async function runBulkAction(\n context: BulkContext,\n collectionId: string,\n ids: readonly string[],\n action: BulkAction,\n setField?: BulkSetField,\n): Promise<BulkReport> {\n const results: BulkItemOutcome[] = []\n\n for (const id of ids) {\n const ref: DocumentRef = { collection: collectionId, id }\n const title = context.titleOf(ref)\n const fail = (error: OperationError) =>\n results.push({ ref, title, ok: false, reason: bulkErrorReason(error), error })\n\n const doc = context.documents.find((d) => matchesRef(d, ref))\n if (!doc) {\n fail({\n code: 'not-found',\n message: 'Document is no longer in the session.',\n entity: 'cms-document',\n key: `${collectionId}:${id}`,\n })\n continue\n }\n\n if (action === 'publish') {\n const errors = context.validate(doc).filter((issue) => issue.severity === 'error')\n if (errors.length > 0) {\n fail({\n code: 'validation',\n message: 'Document is not valid to publish.',\n issues: errors.map((issue) => ({\n code: issue.code,\n message: issue.message,\n path: [...issue.path],\n domain: 'cms',\n })),\n })\n continue\n }\n // EC-425: the SAME clean-then-save sequence the single-document `doPublish`\n // runs, per item — validation gated on the live draft above (matching the\n // editor's pre-strip gate), then the hidden-strip; a changed draft lands\n // (awaited) before the publish command so the pinned snapshot and the\n // EC-286-derived reference index are clean. A failing save is an honest\n // per-item failure and the publish never dispatches on the dirty draft.\n if (context.cleanForPublish) {\n const cleaned = context.cleanForPublish(doc)\n if (cleaned !== doc) {\n try {\n await context.applyCleaned?.(ref, cleaned)\n } catch (cause) {\n fail({\n code: 'unknown',\n message: cause instanceof Error ? cause.message : String(cause),\n })\n continue\n }\n }\n }\n }\n\n if (context.dispatch) {\n let result: OperationResult<unknown>\n try {\n result = await context.dispatch(action, ref)\n } catch (cause) {\n // Operations never throw by contract; a broken transport still becomes\n // a visible per-item failure instead of an aborted bulk run.\n result = {\n ok: false,\n error: {\n code: 'unknown',\n message: cause instanceof Error ? cause.message : String(cause),\n },\n }\n }\n if (!result.ok) {\n // Idempotent delete: the document was never persisted (e.g. a draft\n // whose baseline upsert was rejected) or another editor already\n // deleted it. The desired end state — gone — holds, so removing it\n // from the session is the honest outcome, not a failure.\n const idempotentDelete = action === 'delete' && result.error.code === 'not-found'\n if (!idempotentDelete) {\n fail(result.error)\n continue\n }\n }\n }\n\n if (action === 'delete') context.applyDelete(ref)\n else if (action === 'set-field') {\n if (setField) context.applySetField?.(ref, setField)\n } else context.applyState(ref, ACTION_STATE[action])\n results.push({ ref, title, ok: true })\n }\n\n return { action, ...(setField ? { setField } : {}), results }\n}\n"]}
|
|
@@ -14,6 +14,7 @@ import { useOptionalAssets } from '../assets/assets-store.js';
|
|
|
14
14
|
import { consumeStagedDocuments } from './document-staging.js';
|
|
15
15
|
import { applyCellValue, commitCellEdit } from './cell-edit.js';
|
|
16
16
|
import { runBulkAction } from './bulk-actions.js';
|
|
17
|
+
import { cleanHiddenForPublish } from './publish-clean.js';
|
|
17
18
|
const { Context: CmsContext, useRequired: useCms } = createRequiredContext('Cms');
|
|
18
19
|
function primaryField(collection) {
|
|
19
20
|
return titleFieldOf(collection)?.name ?? collection.fields[0]?.name ?? 'id';
|
|
@@ -558,6 +559,24 @@ export function CmsProvider({ children, projectId, initialCollections, initialDo
|
|
|
558
559
|
validateRichTextEmbeds,
|
|
559
560
|
assetExists,
|
|
560
561
|
}),
|
|
562
|
+
// EC-425: bulk publish joins the single-document clean point — the
|
|
563
|
+
// SAME shared hidden-strip `doPublish` runs (EC-273 field values +
|
|
564
|
+
// EC-394 component props, session registry), per item.
|
|
565
|
+
...(collection
|
|
566
|
+
? { cleanForPublish: (doc) => cleanHiddenForPublish(collection, doc) }
|
|
567
|
+
: {}),
|
|
568
|
+
// Land a cleaned draft exactly the way the editor's publish does:
|
|
569
|
+
// session apply + the persister's self-typing upsert (EC-277 manifest
|
|
570
|
+
// lens), flushed so the serial queue lands the upsert BEFORE the
|
|
571
|
+
// awaited publish command snapshots the stored draft.
|
|
572
|
+
applyCleaned: async (_ref, cleaned) => {
|
|
573
|
+
setDocuments((prev) => replaceDoc(prev, cleaned));
|
|
574
|
+
const persister = persisterRef.current;
|
|
575
|
+
if (persister && projectId !== undefined) {
|
|
576
|
+
persister.upsertDocument(cleaned);
|
|
577
|
+
await persister.flush();
|
|
578
|
+
}
|
|
579
|
+
},
|
|
561
580
|
// EC-224: a succeeded publish/unpublish moves the version pointer
|
|
562
581
|
// (`publishedKeys`) via setPublished — there is no separate flat
|
|
563
582
|
// `state` mirror to keep in sync (published-ness is derived from the
|