@elytracms/studio 0.0.95 → 0.0.97
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/cms.ts +272 -16
- package/convex/delivery-folded-drift.test.ts +104 -0
- package/convex/delivery.ts +24 -8
- package/convex/depth-codec-lookalike.test.ts +192 -0
- package/convex/fixture-accounts.test.ts +305 -0
- package/convex/fixtureAccounts.ts +298 -0
- package/convex/incarnation.test.ts +149 -0
- package/convex/ingest.test.ts +145 -3
- package/convex/ingest.ts +123 -17
- package/convex/receipt-races.test.ts +184 -0
- package/convex/records-zod-copy.test.ts +76 -0
- package/convex/schema.ts +20 -1
- package/convex/strict-delete.test.ts +129 -0
- package/convex/strict-reference-race.test.ts +139 -0
- package/dist/components/ai/ai-chat-panel.js +15 -2
- package/dist/components/ai/ai-chat-panel.js.map +1 -1
- package/dist/components/ai/translate-document-action.d.ts +6 -0
- package/dist/components/ai/translate-document-action.js.map +1 -1
- package/dist/components/ai/translate-document-dialog.d.ts +1 -1
- package/dist/components/ai/translate-document-dialog.js +5 -5
- package/dist/components/ai/translate-document-dialog.js.map +1 -1
- package/dist/components/assets/asset-detail.js +5 -2
- package/dist/components/assets/asset-detail.js.map +1 -1
- package/dist/components/assets/asset-locale.d.ts +7 -0
- package/dist/components/assets/asset-locale.js +16 -2
- package/dist/components/assets/asset-locale.js.map +1 -1
- package/dist/components/assets/asset-meta-drift-notice.d.ts +13 -0
- package/dist/components/assets/asset-meta-drift-notice.js +22 -0
- package/dist/components/assets/asset-meta-drift-notice.js.map +1 -0
- package/dist/components/assets/asset-meta-label.d.ts +2 -1
- package/dist/components/assets/asset-meta-label.js +4 -2
- package/dist/components/assets/asset-meta-label.js.map +1 -1
- package/dist/components/assets/asset-metadata.d.ts +33 -13
- package/dist/components/assets/asset-metadata.js +46 -22
- package/dist/components/assets/asset-metadata.js.map +1 -1
- package/dist/components/assets/asset-search.d.ts +6 -2
- package/dist/components/assets/asset-search.js +15 -5
- package/dist/components/assets/asset-search.js.map +1 -1
- package/dist/components/assets/assets-collection-view-source.d.ts +5 -3
- package/dist/components/assets/assets-collection-view-source.js +7 -5
- package/dist/components/assets/assets-collection-view-source.js.map +1 -1
- package/dist/components/builder/canvas-rebase.d.ts +36 -12
- package/dist/components/builder/canvas-rebase.js +13 -3
- package/dist/components/builder/canvas-rebase.js.map +1 -1
- package/dist/components/builder/canvas-render-context.js +5 -4
- package/dist/components/builder/canvas-render-context.js.map +1 -1
- package/dist/components/builder/composition-field-editor.d.ts +12 -6
- package/dist/components/builder/composition-field-editor.js +6 -5
- package/dist/components/builder/composition-field-editor.js.map +1 -1
- package/dist/components/cms/asset-picker-core.d.ts +2 -2
- package/dist/components/cms/asset-picker-core.js +2 -2
- package/dist/components/cms/asset-picker-core.js.map +1 -1
- package/dist/components/cms/asset-picker.js +3 -1
- package/dist/components/cms/asset-picker.js.map +1 -1
- package/dist/components/cms/canvas-commit.d.ts +6 -0
- package/dist/components/cms/canvas-commit.js +5 -1
- package/dist/components/cms/canvas-commit.js.map +1 -1
- package/dist/components/cms/cms-store.d.ts +24 -8
- package/dist/components/cms/cms-store.js +21 -14
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/components/cms/collection-table.js +4 -11
- package/dist/components/cms/collection-table.js.map +1 -1
- package/dist/components/cms/detail-editor.js +2 -2
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/draft-commit.d.ts +16 -3
- package/dist/components/cms/draft-commit.js +3 -2
- package/dist/components/cms/draft-commit.js.map +1 -1
- package/dist/components/cms/use-document-draft.d.ts +107 -10
- package/dist/components/cms/use-document-draft.js +157 -42
- package/dist/components/cms/use-document-draft.js.map +1 -1
- package/dist/components/collection-view/cms-collection-view-source.js +6 -4
- package/dist/components/collection-view/cms-collection-view-source.js.map +1 -1
- package/dist/components/components/transfer-dialog.js +6 -4
- package/dist/components/components/transfer-dialog.js.map +1 -1
- package/dist/lib/ai/chat-state.d.ts +7 -0
- package/dist/lib/ai/chat-state.js.map +1 -1
- package/dist/lib/ai/tools.d.ts +8 -0
- package/dist/lib/ai/tools.js +4 -0
- package/dist/lib/ai/tools.js.map +1 -1
- package/dist/lib/cms-views/view-model.d.ts +15 -8
- package/dist/lib/cms-views/view-model.js +19 -5
- package/dist/lib/cms-views/view-model.js.map +1 -1
- package/dist/lib/collection-view/__fixtures__/document-writes.js +1 -0
- package/dist/lib/collection-view/__fixtures__/document-writes.js.map +1 -1
- package/dist/lib/collection-view/collection-view-source.d.ts +18 -7
- package/dist/lib/collection-view/collection-view-source.js.map +1 -1
- package/dist/lib/data/collection-content.js +5 -0
- package/dist/lib/data/collection-content.js.map +1 -1
- package/dist/lib/engine-version.d.ts +1 -1
- package/dist/lib/engine-version.js +1 -1
- package/dist/lib/engine-version.js.map +1 -1
- package/dist/lib/i18n/messages.de.js +15 -1
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +13 -0
- package/dist/lib/i18n/messages.en.js +16 -1
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/operations/cms-persistence.d.ts +119 -35
- package/dist/lib/operations/cms-persistence.js +441 -91
- package/dist/lib/operations/cms-persistence.js.map +1 -1
- package/dist/lib/persistence/convex-adapter.js +80 -8
- package/dist/lib/persistence/convex-adapter.js.map +1 -1
- package/dist/lib/persona/labels.js +2 -0
- package/dist/lib/persona/labels.js.map +1 -1
- package/package.json +4 -3
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useMemo, useRef, useState } from 'react';
|
|
3
3
|
import { AlertTriangle, Link2, RotateCcw, Trash2 } from 'lucide-react';
|
|
4
|
+
import { assetMetaIsLocalized } from '@elytracms/core/persistence';
|
|
4
5
|
import { Link, STUDIO_PROJECT_ROUTE, useNavigate } from '@elytracms/studio/router';
|
|
5
6
|
import { localizedLabel, translate } from '../../lib/i18n/index.js';
|
|
6
7
|
import { LocaleSwitcher } from '../../lib/locale/index.js';
|
|
@@ -15,6 +16,7 @@ import { FocalCropPreviews, FocalPointPicker, useAssetRecord } from './asset-thu
|
|
|
15
16
|
import { assetMetaDraft, assetMetaDraftPatch, assetMetaFieldValue, assetMetaFieldsOf, formatBytes, setAssetMetaField, } from './asset-metadata.js';
|
|
16
17
|
import { useAssetReadLocale } from './asset-locale.js';
|
|
17
18
|
import { AssetMetaFieldLabel } from './asset-meta-label.js';
|
|
19
|
+
import { AssetMetaDriftNotice } from './asset-meta-drift-notice.js';
|
|
18
20
|
import { useAssets } from './assets-store.js';
|
|
19
21
|
import { ReplaceFileButton } from './replace-file.js';
|
|
20
22
|
/**
|
|
@@ -82,7 +84,8 @@ function AssetDetailBody({ projectId, record, collection, }) {
|
|
|
82
84
|
// base a save is diffed against, so only the keys (and locales) this editor changed are
|
|
83
85
|
// written and another session's change to any other survives.
|
|
84
86
|
const opened = useRef(draft);
|
|
85
|
-
|
|
87
|
+
// EC-659: "localized" is the read locale's lens — the rule the form reads and writes by.
|
|
88
|
+
const localizedFields = metaFields.some((f) => assetMetaIsLocalized(at, f.name));
|
|
86
89
|
const [focal, setFocal] = useState(record.focalPoint);
|
|
87
90
|
const [dirty, setDirty] = useState(false);
|
|
88
91
|
const [saving, setSaving] = useState(false);
|
|
@@ -120,7 +123,7 @@ function AssetDetailBody({ projectId, record, collection, }) {
|
|
|
120
123
|
setFocal(point ?? undefined);
|
|
121
124
|
void store.updateMetadata(record.id, { focalPoint: point });
|
|
122
125
|
}
|
|
123
|
-
return (_jsxs(Stack, { gap: "4", className: "mx-auto max-w-3xl p-4 pb-10 sm:p-6", children: [_jsx(AssetDetailHeader, { projectId: projectId, record: record, replace: _jsx(ReplaceFileButton, { record: record, disabled: dirty, disabledHint: translate('cms.asset.replaceSaveFirst'), onReplaced: onReplaced }) }), !record.url ? (_jsx(Text, { size: "sm", className: "text-destructive", role: "alert", "data-testid": "asset-detail-no-url", children: translate('assets.detail.noFileUrl') })) : null, _jsxs(Panel, { children: [_jsx(PanelHeader, { children: _jsxs(Row, { align: "center", className: "w-full", children: [_jsx(Text, { size: "xs", weight: "semibold", tone: "muted", children: translate('cms.asset.focalPoint') }), _jsx(Spacer, {}), focal ? (_jsxs(Button, { variant: "ghost", size: "sm", onClick: () => commitFocal(null), children: [_jsx(RotateCcw, {}), " ", translate('cms.asset.resetToCentre')] })) : null] }) }), _jsx(PanelBody, { children: _jsxs(Stack, { gap: "2", children: [_jsx(FocalPointPicker, { record: record, point: focal, onChange: commitFocal, onPreview: setFocal }), replaced ? (_jsx(Text, { size: "xs", className: "font-medium", "data-testid": "asset-replaced-check-focal", children: translate('cms.asset.replacedCheckFocal') })) : null, _jsx(Text, { size: "xs", tone: "subtle", children: translate('cms.asset.focalPointHint') }), _jsx(FocalCropPreviews, { record: record, point: focal })] }) })] }), _jsxs(Panel, { "data-testid": "asset-detail-meta", children: [_jsx(PanelHeader, { children: _jsxs(Row, { align: "center", className: "w-full", children: [_jsx(Text, { size: "xs", weight: "semibold", tone: "muted", children: translate('assets.detail.metadataHeading') }), _jsx(Spacer, {}), localizedFields ? _jsx(LocaleSwitcher, {}) : null] }) }), _jsx(PanelBody, { children: _jsxs(Stack, { gap: "3", children: [metaFields.map((f) => (_jsx(Field, { label: _jsx(AssetMetaFieldLabel, { field: f, at: at }), hint: localizedLabel(f.form?.description) || undefined, children: _jsx(FieldControl, { id: `asset-detail-${f.name}`, field: f, value: assetMetaFieldValue(draft, f, at), readOnly: false, onChange: (v) => setField(f, v) }) }, f.name))), metaFields.length === 0 ? (_jsx(Text, { size: "sm", tone: "subtle", children: translate('cms.asset.noMetaFields', { code1: 'alt', code2: 'copyright' }) })) : null, saveError ? (_jsx(Text, { size: "sm", className: "text-destructive", "data-testid": "asset-detail-save-error", children: saveError })) : null, metaFields.length > 0 ? (_jsxs(Row, { gap: "2", children: [_jsx(Spacer, {}), _jsx(Button, { size: "sm", onClick: () => void save(), disabled: !dirty || saving, "data-testid": "asset-detail-save", children: saving ? translate('common.saving') : translate('common.save') })] })) : null] }) })] })] }));
|
|
126
|
+
return (_jsxs(Stack, { gap: "4", className: "mx-auto max-w-3xl p-4 pb-10 sm:p-6", children: [_jsx(AssetDetailHeader, { projectId: projectId, record: record, replace: _jsx(ReplaceFileButton, { record: record, disabled: dirty, disabledHint: translate('cms.asset.replaceSaveFirst'), onReplaced: onReplaced }) }), !record.url ? (_jsx(Text, { size: "sm", className: "text-destructive", role: "alert", "data-testid": "asset-detail-no-url", children: translate('assets.detail.noFileUrl') })) : null, _jsxs(Panel, { children: [_jsx(PanelHeader, { children: _jsxs(Row, { align: "center", className: "w-full", children: [_jsx(Text, { size: "xs", weight: "semibold", tone: "muted", children: translate('cms.asset.focalPoint') }), _jsx(Spacer, {}), focal ? (_jsxs(Button, { variant: "ghost", size: "sm", onClick: () => commitFocal(null), children: [_jsx(RotateCcw, {}), " ", translate('cms.asset.resetToCentre')] })) : null] }) }), _jsx(PanelBody, { children: _jsxs(Stack, { gap: "2", children: [_jsx(FocalPointPicker, { record: record, point: focal, onChange: commitFocal, onPreview: setFocal }), replaced ? (_jsx(Text, { size: "xs", className: "font-medium", "data-testid": "asset-replaced-check-focal", children: translate('cms.asset.replacedCheckFocal') })) : null, _jsx(Text, { size: "xs", tone: "subtle", children: translate('cms.asset.focalPointHint') }), _jsx(FocalCropPreviews, { record: record, point: focal })] }) })] }), _jsxs(Panel, { "data-testid": "asset-detail-meta", children: [_jsx(PanelHeader, { children: _jsxs(Row, { align: "center", className: "w-full", children: [_jsx(Text, { size: "xs", weight: "semibold", tone: "muted", children: translate('assets.detail.metadataHeading') }), _jsx(Spacer, {}), localizedFields ? _jsx(LocaleSwitcher, {}) : null] }) }), _jsx(PanelBody, { children: _jsxs(Stack, { gap: "3", children: [_jsx(AssetMetaDriftNotice, { record: record, at: at }), metaFields.map((f) => (_jsx(Field, { label: _jsx(AssetMetaFieldLabel, { field: f, at: at }), hint: localizedLabel(f.form?.description) || undefined, children: _jsx(FieldControl, { id: `asset-detail-${f.name}`, field: f, value: assetMetaFieldValue(draft, f, at), readOnly: false, onChange: (v) => setField(f, v) }) }, f.name))), metaFields.length === 0 ? (_jsx(Text, { size: "sm", tone: "subtle", children: translate('cms.asset.noMetaFields', { code1: 'alt', code2: 'copyright' }) })) : null, saveError ? (_jsx(Text, { size: "sm", className: "text-destructive", "data-testid": "asset-detail-save-error", children: saveError })) : null, metaFields.length > 0 ? (_jsxs(Row, { gap: "2", children: [_jsx(Spacer, {}), _jsx(Button, { size: "sm", onClick: () => void save(), disabled: !dirty || saving, "data-testid": "asset-detail-save", children: saving ? translate('common.saving') : translate('common.save') })] })) : null] }) })] })] }));
|
|
124
127
|
}
|
|
125
128
|
/** Filename/title heading + system info (content type, dimensions, size) + actions. */
|
|
126
129
|
function AssetDetailHeader({ projectId, record, replace, }) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"asset-detail.js","sourceRoot":"","sources":["../../../src/components/assets/asset-detail.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { AlertTriangle, Link2, RotateCcw, Trash2 } from 'lucide-react'\nimport type { CollectionDef, FieldDef } from '@elytracms/core/cms-core'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport { Link, STUDIO_PROJECT_ROUTE, useNavigate } from '@elytracms/studio/router'\nimport { localizedLabel, translate } from '../../lib/i18n'\nimport { LocaleSwitcher } from '../../lib/locale'\nimport { buildAssetReferenceImpact, loadInboundAssetReferenceViews } from '../../lib/references'\nimport type { ReferenceEntryView } from '../../lib/references'\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Field,\n Heading,\n Panel,\n PanelBody,\n PanelHeader,\n Row,\n Spacer,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { FieldControl } from '../cms/field-control'\nimport { ReferenceImpactRows } from '../cms/reference-confirm'\nimport { ReferencesPanel } from '../references/references-panel'\nimport { useSetBreadcrumb } from '../breadcrumb-context'\nimport { useCollectionView } from '../collection-view/collection-view-context'\nimport { FocalCropPreviews, FocalPointPicker, useAssetRecord } from './asset-thumb'\nimport type { FocalPoint } from './focal-point'\nimport {\n assetMetaDraft,\n assetMetaDraftPatch,\n assetMetaFieldValue,\n assetMetaFieldsOf,\n formatBytes,\n setAssetMetaField,\n} from './asset-metadata'\nimport type { AssetMetaDraft } from './asset-metadata'\nimport { useAssetReadLocale } from './asset-locale'\nimport { AssetMetaFieldLabel } from './asset-meta-label'\nimport { useAssets } from './assets-store'\nimport { ReplaceFileButton } from './replace-file'\n\n/**\n * The assets rail's DETAIL surface (EC-319). Pragmatic call: assets don't fit\n * the portable `PortableDetailEditor` shell cleanly — there's no dirty/Save/\n * Publish document model here (an asset record isn't a `cmsDocument`), and the\n * focal-point picker is a bespoke drag interaction over the image, not a\n * scalar field value a generic form row can host without contortions. So this\n * is a bespoke surface (mirrors the CMS document editor's shape: a header +\n * panels), reached via the SAME rail-owned `detailPath`/`listPath` seam every\n * other rail uses (AD-19) — only the body differs.\n *\n * Unifies what were two divergent asset-edit UIs before this task: the plain\n * alt/title dialog on the old asset library card, and the richer schema-driven\n * + focal-point \"image options\" dialog opened from an `asset` field elsewhere\n * (`field-control.tsx`). This page reuses the LATTER (richer) content, so\n * editing an asset here is never a downgrade from editing it via a field.\n *\n * Deletion keeps the EC-202 reference-index safe-delete path (`assets-store.tsx`\n * → `deleteAsset`, keyed on `{ type: 'asset' }`). Since EC-433 the universal\n * table's row/bulk delete runs the SAME asset-shaped reference check (the\n * source's `referenceTarget`) and the same confirmed store delete — the two\n * surfaces share one delete semantics, not two divergent ones.\n */\nexport function AssetDetail({ projectId, assetId }: { projectId: string; assetId: string }) {\n const source = useCollectionView()\n const store = useAssets()\n const record = useAssetRecord(assetId)\n const collection = source.schema\n\n const collectionLabel = localizedLabel(collection.form?.label, collection.id)\n useSetBreadcrumb(\n useMemo(() => {\n if (source.listPath === undefined) return [{ label: collectionLabel }]\n const title = record?.title ?? record?.filename ?? assetId\n return [\n { label: collectionLabel, to: STUDIO_PROJECT_ROUTE, params: { projectId, _splat: source.listPath } },\n { label: title },\n ]\n }, [collectionLabel, source.listPath, record, projectId, assetId]),\n )\n\n if (record === undefined) {\n if (store.status === 'loading') {\n return (\n <Stack className=\"h-full place-items-center p-8\" align=\"center\" justify=\"center\">\n <Text size=\"sm\" tone=\"muted\">\n {translate('assets.loading')}\n </Text>\n </Stack>\n )\n }\n return (\n <div className=\"grid h-full place-items-center p-8\">\n <Stack gap=\"3\" align=\"center\" className=\"text-center\">\n <Heading level={2} size=\"h2\">\n {translate('cms.editor.notFoundTitle')}\n </Heading>\n {source.listPath !== undefined ? (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: source.listPath }}\n className=\"text-sm text-primary hover:underline\"\n >\n {translate('cms.editor.backToList', { label: collectionLabel })}\n </Link>\n ) : null}\n </Stack>\n </div>\n )\n }\n\n // Remount per asset (the surface's own key, set by the caller) resets every\n // local buffer below cleanly when navigating from one asset's detail to\n // another — no stale-state carryover to guard against by hand.\n return <AssetDetailBody projectId={projectId} record={record} collection={source.schema} />\n}\n\nfunction AssetDetailBody({\n projectId,\n record,\n collection,\n}: {\n projectId: string\n record: AssetRecord\n collection: CollectionDef\n}) {\n const store = useAssets()\n const metaFields = useMemo(() => assetMetaFieldsOf(collection), [collection])\n\n // Metadata buffers locally and commits explicitly (Save) — an asset record has\n // no dirty/Save document model of its own to piggyback on. The focal point\n // commits immediately (it drives a live visual), matching the field's options\n // dialog (`field-control.tsx`).\n // EC-587: the draft the editor starts from has a legacy `alt` column lifted in, so a\n // not-yet-migrated asset shows its alt and the save moves it to `meta.alt`.\n // EC-632: the draft is the document shape (shared bag + per-locale buckets); the form\n // shows it in the studio's locale, and switching locale only changes that view.\n const at = useAssetReadLocale()\n const [draft, setDraft] = useState<AssetMetaDraft>(() => assetMetaDraft(record))\n // EC-634: the draft as this editor OPENED it (or as of its last successful save) — the\n // base a save is diffed against, so only the keys (and locales) this editor changed are\n // written and another session's change to any other survives.\n const opened = useRef(draft)\n const localizedFields = metaFields.some((f) => f.localized === true)\n const [focal, setFocal] = useState<FocalPoint | undefined>(record.focalPoint)\n const [dirty, setDirty] = useState(false)\n const [saving, setSaving] = useState(false)\n const [saveError, setSaveError] = useState<string | null>(null)\n // EC-568: after a file replace the focal point is KEPT (the ticket's recommendation),\n // but it was chosen on the old pixels — say so beside the picker.\n const [replaced, setReplaced] = useState(false)\n\n function onReplaced(asset: AssetRecord) {\n // The new file may have brought metadata: start the buffer from the stored record.\n const seed = assetMetaDraft(asset)\n opened.current = seed\n setDraft(seed)\n setFocal(asset.focalPoint)\n setDirty(false)\n setReplaced(true)\n }\n\n function setField(field: FieldDef, value: unknown) {\n setDirty(true)\n setDraft((d) => setAssetMetaField(d, field, at, value))\n }\n\n async function save() {\n setSaving(true)\n setSaveError(null)\n const saved = draft\n const result = await store.updateMetadata(record.id, assetMetaDraftPatch(opened.current, saved))\n setSaving(false)\n if (result.ok) {\n opened.current = saved\n setDirty(false)\n } else setSaveError(result.message)\n }\n\n function commitFocal(point: FocalPoint | null) {\n setFocal(point ?? undefined)\n void store.updateMetadata(record.id, { focalPoint: point })\n }\n\n return (\n <Stack gap=\"4\" className=\"mx-auto max-w-3xl p-4 pb-10 sm:p-6\">\n <AssetDetailHeader\n projectId={projectId}\n record={record}\n replace={\n <ReplaceFileButton\n record={record}\n disabled={dirty}\n disabledHint={translate('cms.asset.replaceSaveFirst')}\n onReplaced={onReplaced}\n />\n }\n />\n\n {/* EC-633: a record with no file URL is no resolved asset — every use renders the\n empty state; say so where it is fixed (Replace file). */}\n {!record.url ? (\n <Text size=\"sm\" className=\"text-destructive\" role=\"alert\" data-testid=\"asset-detail-no-url\">\n {translate('assets.detail.noFileUrl')}\n </Text>\n ) : null}\n\n <Panel>\n <PanelHeader>\n <Row align=\"center\" className=\"w-full\">\n <Text size=\"xs\" weight=\"semibold\" tone=\"muted\">\n {translate('cms.asset.focalPoint')}\n </Text>\n <Spacer />\n {focal ? (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => commitFocal(null)}>\n <RotateCcw /> {translate('cms.asset.resetToCentre')}\n </Button>\n ) : null}\n </Row>\n </PanelHeader>\n <PanelBody>\n <Stack gap=\"2\">\n <FocalPointPicker record={record} point={focal} onChange={commitFocal} onPreview={setFocal} />\n {replaced ? (\n <Text size=\"xs\" className=\"font-medium\" data-testid=\"asset-replaced-check-focal\">\n {translate('cms.asset.replacedCheckFocal')}\n </Text>\n ) : null}\n <Text size=\"xs\" tone=\"subtle\">\n {translate('cms.asset.focalPointHint')}\n </Text>\n <FocalCropPreviews record={record} point={focal} />\n </Stack>\n </PanelBody>\n </Panel>\n\n <Panel data-testid=\"asset-detail-meta\">\n <PanelHeader>\n <Row align=\"center\" className=\"w-full\">\n <Text size=\"xs\" weight=\"semibold\" tone=\"muted\">\n {translate('assets.detail.metadataHeading')}\n </Text>\n <Spacer />\n {/* EC-632: a localized field edits per locale — the studio's one switcher. */}\n {localizedFields ? <LocaleSwitcher /> : null}\n </Row>\n </PanelHeader>\n <PanelBody>\n <Stack gap=\"3\">\n {metaFields.map((f: FieldDef) => (\n <Field\n key={f.name}\n label={<AssetMetaFieldLabel field={f} at={at} />}\n hint={localizedLabel(f.form?.description) || undefined}\n >\n <FieldControl\n id={`asset-detail-${f.name}`}\n field={f}\n value={assetMetaFieldValue(draft, f, at)}\n readOnly={false}\n onChange={(v) => setField(f, v)}\n />\n </Field>\n ))}\n {metaFields.length === 0 ? (\n <Text size=\"sm\" tone=\"subtle\">\n {translate('cms.asset.noMetaFields', { code1: 'alt', code2: 'copyright' })}\n </Text>\n ) : null}\n {saveError ? (\n <Text size=\"sm\" className=\"text-destructive\" data-testid=\"asset-detail-save-error\">\n {saveError}\n </Text>\n ) : null}\n {metaFields.length > 0 ? (\n <Row gap=\"2\">\n <Spacer />\n <Button\n size=\"sm\"\n onClick={() => void save()}\n disabled={!dirty || saving}\n data-testid=\"asset-detail-save\"\n >\n {saving ? translate('common.saving') : translate('common.save')}\n </Button>\n </Row>\n ) : null}\n </Stack>\n </PanelBody>\n </Panel>\n </Stack>\n )\n}\n\n/** Filename/title heading + system info (content type, dimensions, size) + actions. */\nfunction AssetDetailHeader({\n projectId,\n record,\n replace,\n}: {\n projectId: string\n record: AssetRecord\n replace?: ReactNode\n}) {\n const dims = record.width && record.height ? `${record.width}×${record.height}` : null\n const info = [record.contentType, dims, formatBytes(record.byteSize)].filter(Boolean).join(' · ')\n return (\n <Row gap=\"3\" align=\"start\" wrap>\n <Stack gap=\"1\" className=\"min-w-0 flex-1\">\n <Heading level={2} size=\"h2\" className=\"truncate\" title={record.title ?? record.filename}>\n {record.title ?? record.filename}\n </Heading>\n {record.title ? (\n <Text size=\"sm\" tone=\"muted\" className=\"truncate\">\n {record.filename}\n </Text>\n ) : null}\n <Text size=\"xs\" tone=\"subtle\">\n {info}\n </Text>\n </Stack>\n <Row gap=\"2\" wrap>\n {replace}\n <AssetReferencesButton projectId={projectId} record={record} />\n <AssetDeleteButton projectId={projectId} record={record} />\n </Row>\n </Row>\n )\n}\n\n/** \"Where is this asset used?\" (EC-172): the shared references panel, EC-155-backed. */\nfunction AssetReferencesButton({ projectId, record }: { projectId: string; record: AssetRecord }) {\n const [open, setOpen] = useState(false)\n return (\n <>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setOpen(true)}\n data-testid=\"asset-detail-references\"\n >\n <Link2 /> {translate('assets.references.buttonLabel')}\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"max-w-lg\">\n <DialogHeader>\n <DialogTitle>\n {translate('assets.references.title', { name: record.title ?? record.filename })}\n </DialogTitle>\n <DialogDescription>{translate('assets.references.description')}</DialogDescription>\n </DialogHeader>\n {open ? (\n <ReferencesPanel projectId={projectId} target={{ type: 'asset', id: record.id }} />\n ) : null}\n </DialogContent>\n </Dialog>\n </>\n )\n}\n\n/**\n * Reference-respecting delete (EC-151, EC-202): enumerates the exact inbound\n * references — index-accurate, with deep links to the field/node — before\n * removing. Deleting a referenced asset is weak-by-default (allowed with an\n * explicit \"delete anyway\"); every reference dangles into a visible validation\n * issue rather than silently breaking. On success, returns to the list.\n */\nfunction AssetDeleteButton({ projectId, record }: { projectId: string; record: AssetRecord }) {\n const source = useCollectionView()\n const store = useAssets()\n const navigate = useNavigate()\n const [open, setOpen] = useState(false)\n const [usages, setUsages] = useState<ReferenceEntryView[] | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [busy, setBusy] = useState(false)\n\n const openDialog = async () => {\n setOpen(true)\n setError(null)\n setUsages(null)\n try {\n setUsages(await loadInboundAssetReferenceViews(projectId, record.id))\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause))\n }\n }\n\n const confirm = async () => {\n setBusy(true)\n setError(null)\n // The dialog has already enumerated usages (or shown there are none) and the\n // user is confirming either way — confirm unconditionally, skipping the\n // redundant inbound re-check (EC-301 perf). Weak-by-default: a still-\n // referenced asset deletes anyway, dangling into a visible issue.\n const result = await store.remove(record.id, { confirmInUse: true })\n setBusy(false)\n if (result.outcome === 'deleted' || result.outcome === 'not-found') {\n setOpen(false)\n if (source.listPath !== undefined) {\n void navigate({ to: STUDIO_PROJECT_ROUTE, params: { projectId, _splat: source.listPath } })\n }\n } else if (result.outcome === 'blocked') {\n setError(\n translate(\n result.usageCount === 1 ? 'assets.delete.raceError.one' : 'assets.delete.raceError.other',\n { count: result.usageCount },\n ),\n )\n } else {\n setError(result.message)\n }\n }\n\n const impact = usages ? buildAssetReferenceImpact(usages) : null\n const usageCount = impact?.broken.length ?? 0\n return (\n <>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => void openDialog()}\n data-testid=\"asset-detail-delete\"\n >\n <Trash2 /> {translate('common.delete')}\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"max-h-[80vh] max-w-md overflow-y-auto\">\n <DialogHeader>\n <DialogTitle>{translate('assets.delete.title', { filename: record.filename })}</DialogTitle>\n <DialogDescription>{translate('assets.delete.description')}</DialogDescription>\n </DialogHeader>\n {usages === null && error === null ? (\n <Text size=\"sm\" tone=\"muted\">\n {translate('assets.delete.checking')}\n </Text>\n ) : usageCount > 0 ? (\n <Stack gap=\"2\" data-testid=\"asset-usage-warning\">\n <Row gap=\"2\">\n <AlertTriangle className=\"size-4 text-warning\" />\n <Text size=\"sm\" weight=\"semibold\">\n {translate(usageCount === 1 ? 'assets.delete.inUse.one' : 'assets.delete.inUse.other', {\n count: usageCount,\n })}\n </Text>\n </Row>\n <ReferenceImpactRows\n projectId={projectId}\n broken={impact!.broken}\n onNavigate={() => setOpen(false)}\n />\n <Text size=\"xs\" tone=\"muted\">\n {translate('assets.delete.brokenWarning')}\n </Text>\n </Stack>\n ) : (\n <Text size=\"sm\" tone=\"muted\">\n {translate('assets.delete.noReferences')}\n </Text>\n )}\n {error ? (\n <Text size=\"sm\" className=\"text-destructive\" data-testid=\"asset-delete-error\">\n {error}\n </Text>\n ) : null}\n <DialogFooter>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => setOpen(false)} disabled={busy}>\n {translate('common.cancel')}\n </Button>\n <Button\n variant=\"destructive\"\n size=\"sm\"\n onClick={() => void confirm()}\n disabled={busy || usages === null}\n data-testid=\"asset-delete-confirm\"\n >\n {usageCount > 0 ? translate('assets.delete.confirmAnyway') : translate('common.delete')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"asset-detail.js","sourceRoot":"","sources":["../../../src/components/assets/asset-detail.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { AlertTriangle, Link2, RotateCcw, Trash2 } from 'lucide-react'\nimport type { CollectionDef, FieldDef } from '@elytracms/core/cms-core'\nimport { assetMetaIsLocalized } from '@elytracms/core/persistence'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport { Link, STUDIO_PROJECT_ROUTE, useNavigate } from '@elytracms/studio/router'\nimport { localizedLabel, translate } from '../../lib/i18n'\nimport { LocaleSwitcher } from '../../lib/locale'\nimport { buildAssetReferenceImpact, loadInboundAssetReferenceViews } from '../../lib/references'\nimport type { ReferenceEntryView } from '../../lib/references'\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Field,\n Heading,\n Panel,\n PanelBody,\n PanelHeader,\n Row,\n Spacer,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { FieldControl } from '../cms/field-control'\nimport { ReferenceImpactRows } from '../cms/reference-confirm'\nimport { ReferencesPanel } from '../references/references-panel'\nimport { useSetBreadcrumb } from '../breadcrumb-context'\nimport { useCollectionView } from '../collection-view/collection-view-context'\nimport { FocalCropPreviews, FocalPointPicker, useAssetRecord } from './asset-thumb'\nimport type { FocalPoint } from './focal-point'\nimport {\n assetMetaDraft,\n assetMetaDraftPatch,\n assetMetaFieldValue,\n assetMetaFieldsOf,\n formatBytes,\n setAssetMetaField,\n} from './asset-metadata'\nimport type { AssetMetaDraft } from './asset-metadata'\nimport { useAssetReadLocale } from './asset-locale'\nimport { AssetMetaFieldLabel } from './asset-meta-label'\nimport { AssetMetaDriftNotice } from './asset-meta-drift-notice'\nimport { useAssets } from './assets-store'\nimport { ReplaceFileButton } from './replace-file'\n\n/**\n * The assets rail's DETAIL surface (EC-319). Pragmatic call: assets don't fit\n * the portable `PortableDetailEditor` shell cleanly — there's no dirty/Save/\n * Publish document model here (an asset record isn't a `cmsDocument`), and the\n * focal-point picker is a bespoke drag interaction over the image, not a\n * scalar field value a generic form row can host without contortions. So this\n * is a bespoke surface (mirrors the CMS document editor's shape: a header +\n * panels), reached via the SAME rail-owned `detailPath`/`listPath` seam every\n * other rail uses (AD-19) — only the body differs.\n *\n * Unifies what were two divergent asset-edit UIs before this task: the plain\n * alt/title dialog on the old asset library card, and the richer schema-driven\n * + focal-point \"image options\" dialog opened from an `asset` field elsewhere\n * (`field-control.tsx`). This page reuses the LATTER (richer) content, so\n * editing an asset here is never a downgrade from editing it via a field.\n *\n * Deletion keeps the EC-202 reference-index safe-delete path (`assets-store.tsx`\n * → `deleteAsset`, keyed on `{ type: 'asset' }`). Since EC-433 the universal\n * table's row/bulk delete runs the SAME asset-shaped reference check (the\n * source's `referenceTarget`) and the same confirmed store delete — the two\n * surfaces share one delete semantics, not two divergent ones.\n */\nexport function AssetDetail({ projectId, assetId }: { projectId: string; assetId: string }) {\n const source = useCollectionView()\n const store = useAssets()\n const record = useAssetRecord(assetId)\n const collection = source.schema\n\n const collectionLabel = localizedLabel(collection.form?.label, collection.id)\n useSetBreadcrumb(\n useMemo(() => {\n if (source.listPath === undefined) return [{ label: collectionLabel }]\n const title = record?.title ?? record?.filename ?? assetId\n return [\n { label: collectionLabel, to: STUDIO_PROJECT_ROUTE, params: { projectId, _splat: source.listPath } },\n { label: title },\n ]\n }, [collectionLabel, source.listPath, record, projectId, assetId]),\n )\n\n if (record === undefined) {\n if (store.status === 'loading') {\n return (\n <Stack className=\"h-full place-items-center p-8\" align=\"center\" justify=\"center\">\n <Text size=\"sm\" tone=\"muted\">\n {translate('assets.loading')}\n </Text>\n </Stack>\n )\n }\n return (\n <div className=\"grid h-full place-items-center p-8\">\n <Stack gap=\"3\" align=\"center\" className=\"text-center\">\n <Heading level={2} size=\"h2\">\n {translate('cms.editor.notFoundTitle')}\n </Heading>\n {source.listPath !== undefined ? (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: source.listPath }}\n className=\"text-sm text-primary hover:underline\"\n >\n {translate('cms.editor.backToList', { label: collectionLabel })}\n </Link>\n ) : null}\n </Stack>\n </div>\n )\n }\n\n // Remount per asset (the surface's own key, set by the caller) resets every\n // local buffer below cleanly when navigating from one asset's detail to\n // another — no stale-state carryover to guard against by hand.\n return <AssetDetailBody projectId={projectId} record={record} collection={source.schema} />\n}\n\nfunction AssetDetailBody({\n projectId,\n record,\n collection,\n}: {\n projectId: string\n record: AssetRecord\n collection: CollectionDef\n}) {\n const store = useAssets()\n const metaFields = useMemo(() => assetMetaFieldsOf(collection), [collection])\n\n // Metadata buffers locally and commits explicitly (Save) — an asset record has\n // no dirty/Save document model of its own to piggyback on. The focal point\n // commits immediately (it drives a live visual), matching the field's options\n // dialog (`field-control.tsx`).\n // EC-587: the draft the editor starts from has a legacy `alt` column lifted in, so a\n // not-yet-migrated asset shows its alt and the save moves it to `meta.alt`.\n // EC-632: the draft is the document shape (shared bag + per-locale buckets); the form\n // shows it in the studio's locale, and switching locale only changes that view.\n const at = useAssetReadLocale()\n const [draft, setDraft] = useState<AssetMetaDraft>(() => assetMetaDraft(record))\n // EC-634: the draft as this editor OPENED it (or as of its last successful save) — the\n // base a save is diffed against, so only the keys (and locales) this editor changed are\n // written and another session's change to any other survives.\n const opened = useRef(draft)\n // EC-659: \"localized\" is the read locale's lens — the rule the form reads and writes by.\n const localizedFields = metaFields.some((f) => assetMetaIsLocalized(at, f.name))\n const [focal, setFocal] = useState<FocalPoint | undefined>(record.focalPoint)\n const [dirty, setDirty] = useState(false)\n const [saving, setSaving] = useState(false)\n const [saveError, setSaveError] = useState<string | null>(null)\n // EC-568: after a file replace the focal point is KEPT (the ticket's recommendation),\n // but it was chosen on the old pixels — say so beside the picker.\n const [replaced, setReplaced] = useState(false)\n\n function onReplaced(asset: AssetRecord) {\n // The new file may have brought metadata: start the buffer from the stored record.\n const seed = assetMetaDraft(asset)\n opened.current = seed\n setDraft(seed)\n setFocal(asset.focalPoint)\n setDirty(false)\n setReplaced(true)\n }\n\n function setField(field: FieldDef, value: unknown) {\n setDirty(true)\n setDraft((d) => setAssetMetaField(d, field, at, value))\n }\n\n async function save() {\n setSaving(true)\n setSaveError(null)\n const saved = draft\n const result = await store.updateMetadata(record.id, assetMetaDraftPatch(opened.current, saved))\n setSaving(false)\n if (result.ok) {\n opened.current = saved\n setDirty(false)\n } else setSaveError(result.message)\n }\n\n function commitFocal(point: FocalPoint | null) {\n setFocal(point ?? undefined)\n void store.updateMetadata(record.id, { focalPoint: point })\n }\n\n return (\n <Stack gap=\"4\" className=\"mx-auto max-w-3xl p-4 pb-10 sm:p-6\">\n <AssetDetailHeader\n projectId={projectId}\n record={record}\n replace={\n <ReplaceFileButton\n record={record}\n disabled={dirty}\n disabledHint={translate('cms.asset.replaceSaveFirst')}\n onReplaced={onReplaced}\n />\n }\n />\n\n {/* EC-633: a record with no file URL is no resolved asset — every use renders the\n empty state; say so where it is fixed (Replace file). */}\n {!record.url ? (\n <Text size=\"sm\" className=\"text-destructive\" role=\"alert\" data-testid=\"asset-detail-no-url\">\n {translate('assets.detail.noFileUrl')}\n </Text>\n ) : null}\n\n <Panel>\n <PanelHeader>\n <Row align=\"center\" className=\"w-full\">\n <Text size=\"xs\" weight=\"semibold\" tone=\"muted\">\n {translate('cms.asset.focalPoint')}\n </Text>\n <Spacer />\n {focal ? (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => commitFocal(null)}>\n <RotateCcw /> {translate('cms.asset.resetToCentre')}\n </Button>\n ) : null}\n </Row>\n </PanelHeader>\n <PanelBody>\n <Stack gap=\"2\">\n <FocalPointPicker record={record} point={focal} onChange={commitFocal} onPreview={setFocal} />\n {replaced ? (\n <Text size=\"xs\" className=\"font-medium\" data-testid=\"asset-replaced-check-focal\">\n {translate('cms.asset.replacedCheckFocal')}\n </Text>\n ) : null}\n <Text size=\"xs\" tone=\"subtle\">\n {translate('cms.asset.focalPointHint')}\n </Text>\n <FocalCropPreviews record={record} point={focal} />\n </Stack>\n </PanelBody>\n </Panel>\n\n <Panel data-testid=\"asset-detail-meta\">\n <PanelHeader>\n <Row align=\"center\" className=\"w-full\">\n <Text size=\"xs\" weight=\"semibold\" tone=\"muted\">\n {translate('assets.detail.metadataHeading')}\n </Text>\n <Spacer />\n {/* EC-632: a localized field edits per locale — the studio's one switcher. */}\n {localizedFields ? <LocaleSwitcher /> : null}\n </Row>\n </PanelHeader>\n <PanelBody>\n <Stack gap=\"3\">\n {/* EC-659: stored translations the asset collection no longer reads — named here. */}\n <AssetMetaDriftNotice record={record} at={at} />\n {metaFields.map((f: FieldDef) => (\n <Field\n key={f.name}\n label={<AssetMetaFieldLabel field={f} at={at} />}\n hint={localizedLabel(f.form?.description) || undefined}\n >\n <FieldControl\n id={`asset-detail-${f.name}`}\n field={f}\n value={assetMetaFieldValue(draft, f, at)}\n readOnly={false}\n onChange={(v) => setField(f, v)}\n />\n </Field>\n ))}\n {metaFields.length === 0 ? (\n <Text size=\"sm\" tone=\"subtle\">\n {translate('cms.asset.noMetaFields', { code1: 'alt', code2: 'copyright' })}\n </Text>\n ) : null}\n {saveError ? (\n <Text size=\"sm\" className=\"text-destructive\" data-testid=\"asset-detail-save-error\">\n {saveError}\n </Text>\n ) : null}\n {metaFields.length > 0 ? (\n <Row gap=\"2\">\n <Spacer />\n <Button\n size=\"sm\"\n onClick={() => void save()}\n disabled={!dirty || saving}\n data-testid=\"asset-detail-save\"\n >\n {saving ? translate('common.saving') : translate('common.save')}\n </Button>\n </Row>\n ) : null}\n </Stack>\n </PanelBody>\n </Panel>\n </Stack>\n )\n}\n\n/** Filename/title heading + system info (content type, dimensions, size) + actions. */\nfunction AssetDetailHeader({\n projectId,\n record,\n replace,\n}: {\n projectId: string\n record: AssetRecord\n replace?: ReactNode\n}) {\n const dims = record.width && record.height ? `${record.width}×${record.height}` : null\n const info = [record.contentType, dims, formatBytes(record.byteSize)].filter(Boolean).join(' · ')\n return (\n <Row gap=\"3\" align=\"start\" wrap>\n <Stack gap=\"1\" className=\"min-w-0 flex-1\">\n <Heading level={2} size=\"h2\" className=\"truncate\" title={record.title ?? record.filename}>\n {record.title ?? record.filename}\n </Heading>\n {record.title ? (\n <Text size=\"sm\" tone=\"muted\" className=\"truncate\">\n {record.filename}\n </Text>\n ) : null}\n <Text size=\"xs\" tone=\"subtle\">\n {info}\n </Text>\n </Stack>\n <Row gap=\"2\" wrap>\n {replace}\n <AssetReferencesButton projectId={projectId} record={record} />\n <AssetDeleteButton projectId={projectId} record={record} />\n </Row>\n </Row>\n )\n}\n\n/** \"Where is this asset used?\" (EC-172): the shared references panel, EC-155-backed. */\nfunction AssetReferencesButton({ projectId, record }: { projectId: string; record: AssetRecord }) {\n const [open, setOpen] = useState(false)\n return (\n <>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setOpen(true)}\n data-testid=\"asset-detail-references\"\n >\n <Link2 /> {translate('assets.references.buttonLabel')}\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"max-w-lg\">\n <DialogHeader>\n <DialogTitle>\n {translate('assets.references.title', { name: record.title ?? record.filename })}\n </DialogTitle>\n <DialogDescription>{translate('assets.references.description')}</DialogDescription>\n </DialogHeader>\n {open ? (\n <ReferencesPanel projectId={projectId} target={{ type: 'asset', id: record.id }} />\n ) : null}\n </DialogContent>\n </Dialog>\n </>\n )\n}\n\n/**\n * Reference-respecting delete (EC-151, EC-202): enumerates the exact inbound\n * references — index-accurate, with deep links to the field/node — before\n * removing. Deleting a referenced asset is weak-by-default (allowed with an\n * explicit \"delete anyway\"); every reference dangles into a visible validation\n * issue rather than silently breaking. On success, returns to the list.\n */\nfunction AssetDeleteButton({ projectId, record }: { projectId: string; record: AssetRecord }) {\n const source = useCollectionView()\n const store = useAssets()\n const navigate = useNavigate()\n const [open, setOpen] = useState(false)\n const [usages, setUsages] = useState<ReferenceEntryView[] | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [busy, setBusy] = useState(false)\n\n const openDialog = async () => {\n setOpen(true)\n setError(null)\n setUsages(null)\n try {\n setUsages(await loadInboundAssetReferenceViews(projectId, record.id))\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause))\n }\n }\n\n const confirm = async () => {\n setBusy(true)\n setError(null)\n // The dialog has already enumerated usages (or shown there are none) and the\n // user is confirming either way — confirm unconditionally, skipping the\n // redundant inbound re-check (EC-301 perf). Weak-by-default: a still-\n // referenced asset deletes anyway, dangling into a visible issue.\n const result = await store.remove(record.id, { confirmInUse: true })\n setBusy(false)\n if (result.outcome === 'deleted' || result.outcome === 'not-found') {\n setOpen(false)\n if (source.listPath !== undefined) {\n void navigate({ to: STUDIO_PROJECT_ROUTE, params: { projectId, _splat: source.listPath } })\n }\n } else if (result.outcome === 'blocked') {\n setError(\n translate(\n result.usageCount === 1 ? 'assets.delete.raceError.one' : 'assets.delete.raceError.other',\n { count: result.usageCount },\n ),\n )\n } else {\n setError(result.message)\n }\n }\n\n const impact = usages ? buildAssetReferenceImpact(usages) : null\n const usageCount = impact?.broken.length ?? 0\n return (\n <>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => void openDialog()}\n data-testid=\"asset-detail-delete\"\n >\n <Trash2 /> {translate('common.delete')}\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"max-h-[80vh] max-w-md overflow-y-auto\">\n <DialogHeader>\n <DialogTitle>{translate('assets.delete.title', { filename: record.filename })}</DialogTitle>\n <DialogDescription>{translate('assets.delete.description')}</DialogDescription>\n </DialogHeader>\n {usages === null && error === null ? (\n <Text size=\"sm\" tone=\"muted\">\n {translate('assets.delete.checking')}\n </Text>\n ) : usageCount > 0 ? (\n <Stack gap=\"2\" data-testid=\"asset-usage-warning\">\n <Row gap=\"2\">\n <AlertTriangle className=\"size-4 text-warning\" />\n <Text size=\"sm\" weight=\"semibold\">\n {translate(usageCount === 1 ? 'assets.delete.inUse.one' : 'assets.delete.inUse.other', {\n count: usageCount,\n })}\n </Text>\n </Row>\n <ReferenceImpactRows\n projectId={projectId}\n broken={impact!.broken}\n onNavigate={() => setOpen(false)}\n />\n <Text size=\"xs\" tone=\"muted\">\n {translate('assets.delete.brokenWarning')}\n </Text>\n </Stack>\n ) : (\n <Text size=\"sm\" tone=\"muted\">\n {translate('assets.delete.noReferences')}\n </Text>\n )}\n {error ? (\n <Text size=\"sm\" className=\"text-destructive\" data-testid=\"asset-delete-error\">\n {error}\n </Text>\n ) : null}\n <DialogFooter>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => setOpen(false)} disabled={busy}>\n {translate('common.cancel')}\n </Button>\n <Button\n variant=\"destructive\"\n size=\"sm\"\n onClick={() => void confirm()}\n disabled={busy || usages === null}\n data-testid=\"asset-delete-confirm\"\n >\n {usageCount > 0 ? translate('assets.delete.confirmAnyway') : translate('common.delete')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </>\n )\n}\n"]}
|
|
@@ -5,5 +5,12 @@ import type { AssetReadLocale } from '@elytracms/core/persistence';
|
|
|
5
5
|
* locale config — so an asset's localized alt reads in the same locale as the document
|
|
6
6
|
* around it. `null` before a project's locale config is installed (EC-479): then only the
|
|
7
7
|
* shared metadata exists to read.
|
|
8
|
+
*
|
|
9
|
+
* EC-659: read through the project's asset collection as it is declared NOW (the active
|
|
10
|
+
* config project's collections — the ones the CMS session and delivery hold), so the
|
|
11
|
+
* studio's displays, its metadata form and the canvas apply a stored translation exactly
|
|
12
|
+
* where delivery does: for a field the schema marks `localized`, and nowhere else. The ONE
|
|
13
|
+
* studio construction of an asset read locale — the canvas, the picker, the thumbnails, the
|
|
14
|
+
* asset library and its rail all take theirs from here.
|
|
8
15
|
*/
|
|
9
16
|
export declare function useAssetReadLocale(): AssetReadLocale | null;
|
|
@@ -1,16 +1,30 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
|
-
import { assetReadLocale } from '@elytracms/core/persistence';
|
|
2
|
+
import { assetMetaLens, assetReadLocale } from '@elytracms/core/persistence';
|
|
3
3
|
import { useStudioLocale } from '../../lib/locale/index.js';
|
|
4
|
+
import { resolveDeploymentCollections } from '../../lib/workspace/index.js';
|
|
5
|
+
/** One identity for "no config project" — the workspace answers a fresh `[]` each call. */
|
|
6
|
+
const NO_COLLECTIONS = [];
|
|
4
7
|
/**
|
|
5
8
|
* The locale the studio reads and edits an asset's metadata in (EC-632): the studio's
|
|
6
9
|
* ambient locale (the one the document editor and the canvas show) over the project's
|
|
7
10
|
* locale config — so an asset's localized alt reads in the same locale as the document
|
|
8
11
|
* around it. `null` before a project's locale config is installed (EC-479): then only the
|
|
9
12
|
* shared metadata exists to read.
|
|
13
|
+
*
|
|
14
|
+
* EC-659: read through the project's asset collection as it is declared NOW (the active
|
|
15
|
+
* config project's collections — the ones the CMS session and delivery hold), so the
|
|
16
|
+
* studio's displays, its metadata form and the canvas apply a stored translation exactly
|
|
17
|
+
* where delivery does: for a field the schema marks `localized`, and nowhere else. The ONE
|
|
18
|
+
* studio construction of an asset read locale — the canvas, the picker, the thumbnails, the
|
|
19
|
+
* asset library and its rail all take theirs from here.
|
|
10
20
|
*/
|
|
11
21
|
export function useAssetReadLocale() {
|
|
12
22
|
const { locale, config } = useStudioLocale();
|
|
13
23
|
const defaultLocale = config?.default;
|
|
14
|
-
|
|
24
|
+
const declared = resolveDeploymentCollections();
|
|
25
|
+
const collections = declared.length > 0 ? declared : NO_COLLECTIONS;
|
|
26
|
+
return useMemo(() => defaultLocale === undefined
|
|
27
|
+
? null
|
|
28
|
+
: assetReadLocale({ default: defaultLocale }, assetMetaLens(collections), locale), [defaultLocale, locale, collections]);
|
|
15
29
|
}
|
|
16
30
|
//# sourceMappingURL=asset-locale.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"asset-locale.js","sourceRoot":"","sources":["../../../src/components/assets/asset-locale.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"asset-locale.js","sourceRoot":"","sources":["../../../src/components/assets/asset-locale.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAE/B,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAE5E,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAA;AAElE,2FAA2F;AAC3F,MAAM,cAAc,GAA6B,EAAE,CAAA;AAEnD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,eAAe,EAAE,CAAA;IAC5C,MAAM,aAAa,GAAG,MAAM,EAAE,OAAO,CAAA;IACrC,MAAM,QAAQ,GAAG,4BAA4B,EAAE,CAAA;IAC/C,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,cAAc,CAAA;IACnE,OAAO,OAAO,CACZ,GAAG,EAAE,CACH,aAAa,KAAK,SAAS;QACzB,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,eAAe,CAAC,EAAE,OAAO,EAAE,aAAa,EAAE,EAAE,aAAa,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC,EACrF,CAAC,aAAa,EAAE,MAAM,EAAE,WAAW,CAAC,CACrC,CAAA;AACH,CAAC","sourcesContent":["import { useMemo } from 'react'\nimport type { CollectionDef } from '@elytracms/core/cms-core'\nimport { assetMetaLens, assetReadLocale } from '@elytracms/core/persistence'\nimport type { AssetReadLocale } from '@elytracms/core/persistence'\nimport { useStudioLocale } from '../../lib/locale'\nimport { resolveDeploymentCollections } from '../../lib/workspace'\n\n/** One identity for \"no config project\" — the workspace answers a fresh `[]` each call. */\nconst NO_COLLECTIONS: readonly CollectionDef[] = []\n\n/**\n * The locale the studio reads and edits an asset's metadata in (EC-632): the studio's\n * ambient locale (the one the document editor and the canvas show) over the project's\n * locale config — so an asset's localized alt reads in the same locale as the document\n * around it. `null` before a project's locale config is installed (EC-479): then only the\n * shared metadata exists to read.\n *\n * EC-659: read through the project's asset collection as it is declared NOW (the active\n * config project's collections — the ones the CMS session and delivery hold), so the\n * studio's displays, its metadata form and the canvas apply a stored translation exactly\n * where delivery does: for a field the schema marks `localized`, and nowhere else. The ONE\n * studio construction of an asset read locale — the canvas, the picker, the thumbnails, the\n * asset library and its rail all take theirs from here.\n */\nexport function useAssetReadLocale(): AssetReadLocale | null {\n const { locale, config } = useStudioLocale()\n const defaultLocale = config?.default\n const declared = resolveDeploymentCollections()\n const collections = declared.length > 0 ? declared : NO_COLLECTIONS\n return useMemo(\n () =>\n defaultLocale === undefined\n ? null\n : assetReadLocale({ default: defaultLocale }, assetMetaLens(collections), locale),\n [defaultLocale, locale, collections],\n )\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AssetReadLocale, AssetRecord } from '@elytracms/core/persistence';
|
|
2
|
+
/**
|
|
3
|
+
* EC-659 — the studio's face of `asset-meta-drift`: the stored translations of this asset
|
|
4
|
+
* that its asset collection no longer reads (a field un-localized, or no longer declared).
|
|
5
|
+
* Delivery serves the shared value and names the residue on its issues; this names it
|
|
6
|
+
* where an editor can fix it — saving a field that is no longer translatable removes its
|
|
7
|
+
* old translations (`setAssetMetaField`). Nothing when there is no drift, or before a
|
|
8
|
+
* locale config (and with it the lens) is installed.
|
|
9
|
+
*/
|
|
10
|
+
export declare function AssetMetaDriftNotice({ record, at }: {
|
|
11
|
+
record: AssetRecord;
|
|
12
|
+
at: AssetReadLocale | null;
|
|
13
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { assetMetaDrift } from '@elytracms/core/persistence';
|
|
3
|
+
import { Stack, Text } from '@elytracms/studio/ui';
|
|
4
|
+
import { translate } from '../../lib/i18n/index.js';
|
|
5
|
+
import { assetMetaDriftSummary } from './asset-metadata.js';
|
|
6
|
+
/**
|
|
7
|
+
* EC-659 — the studio's face of `asset-meta-drift`: the stored translations of this asset
|
|
8
|
+
* that its asset collection no longer reads (a field un-localized, or no longer declared).
|
|
9
|
+
* Delivery serves the shared value and names the residue on its issues; this names it
|
|
10
|
+
* where an editor can fix it — saving a field that is no longer translatable removes its
|
|
11
|
+
* old translations (`setAssetMetaField`). Nothing when there is no drift, or before a
|
|
12
|
+
* locale config (and with it the lens) is installed.
|
|
13
|
+
*/
|
|
14
|
+
export function AssetMetaDriftNotice({ record, at }) {
|
|
15
|
+
if (at === null)
|
|
16
|
+
return null;
|
|
17
|
+
const lines = assetMetaDriftSummary(assetMetaDrift(record, at.lens));
|
|
18
|
+
if (lines.length === 0)
|
|
19
|
+
return null;
|
|
20
|
+
return (_jsx(Stack, { gap: "1", role: "status", "data-testid": "asset-detail-meta-drift", children: lines.map((line) => (_jsx(Text, { size: "sm", tone: "muted", "data-ec-asset-meta-drift": line.reason, children: translate(line.reason === 'not-localized' ? 'assets.detail.metaDriftNotLocalized' : 'assets.detail.metaDriftUndeclared', { fields: line.fields }) }, line.reason))) }));
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=asset-meta-drift-notice.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset-meta-drift-notice.js","sourceRoot":"","sources":["../../../src/components/assets/asset-meta-drift-notice.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAE5D,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAA;AAExD;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,EAAE,MAAM,EAAE,EAAE,EAAuD;IACtG,IAAI,EAAE,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA;IAC5B,MAAM,KAAK,GAAG,qBAAqB,CAAC,cAAc,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAA;IACpE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACnC,OAAO,CACL,KAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,IAAI,EAAC,QAAQ,iBAAa,yBAAyB,YAC/D,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CACnB,KAAC,IAAI,IAAmB,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,8BAA2B,IAAI,CAAC,MAAM,YACjF,SAAS,CACR,IAAI,CAAC,MAAM,KAAK,eAAe,CAAC,CAAC,CAAC,qCAAqC,CAAC,CAAC,CAAC,mCAAmC,EAC7G,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CACxB,IAJQ,IAAI,CAAC,MAAM,CAKf,CACR,CAAC,GACI,CACT,CAAA;AACH,CAAC","sourcesContent":["import { assetMetaDrift } from '@elytracms/core/persistence'\nimport type { AssetReadLocale, AssetRecord } from '@elytracms/core/persistence'\nimport { Stack, Text } from '@elytracms/studio/ui'\nimport { translate } from '../../lib/i18n'\nimport { assetMetaDriftSummary } from './asset-metadata'\n\n/**\n * EC-659 — the studio's face of `asset-meta-drift`: the stored translations of this asset\n * that its asset collection no longer reads (a field un-localized, or no longer declared).\n * Delivery serves the shared value and names the residue on its issues; this names it\n * where an editor can fix it — saving a field that is no longer translatable removes its\n * old translations (`setAssetMetaField`). Nothing when there is no drift, or before a\n * locale config (and with it the lens) is installed.\n */\nexport function AssetMetaDriftNotice({ record, at }: { record: AssetRecord; at: AssetReadLocale | null }) {\n if (at === null) return null\n const lines = assetMetaDriftSummary(assetMetaDrift(record, at.lens))\n if (lines.length === 0) return null\n return (\n <Stack gap=\"1\" role=\"status\" data-testid=\"asset-detail-meta-drift\">\n {lines.map((line) => (\n <Text key={line.reason} size=\"sm\" tone=\"muted\" data-ec-asset-meta-drift={line.reason}>\n {translate(\n line.reason === 'not-localized' ? 'assets.detail.metaDriftNotLocalized' : 'assets.detail.metaDriftUndeclared',\n { fields: line.fields },\n )}\n </Text>\n ))}\n </Stack>\n )\n}\n"]}
|
|
@@ -4,7 +4,8 @@ import type { AssetReadLocale } from '@elytracms/core/persistence';
|
|
|
4
4
|
* The label of one asset metadata field (EC-632): the field's label, and — for a
|
|
5
5
|
* `localized: true` field — the locale it is edited in, as the document editor labels a
|
|
6
6
|
* localized field (`Title en`). One component, so the image options dialog and the asset
|
|
7
|
-
* library's detail say the same thing.
|
|
7
|
+
* library's detail say the same thing. EC-659: "localized" is `at`'s lens — the rule the
|
|
8
|
+
* field's value is read and written by.
|
|
8
9
|
*/
|
|
9
10
|
export declare function AssetMetaFieldLabel({ field, at }: {
|
|
10
11
|
field: FieldDef;
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { assetMetaIsLocalized } from '@elytracms/core/persistence';
|
|
2
3
|
import { Badge, Row } from '@elytracms/studio/ui';
|
|
3
4
|
import { fieldLabel } from '../cms/cms-labels.js';
|
|
4
5
|
/**
|
|
5
6
|
* The label of one asset metadata field (EC-632): the field's label, and — for a
|
|
6
7
|
* `localized: true` field — the locale it is edited in, as the document editor labels a
|
|
7
8
|
* localized field (`Title en`). One component, so the image options dialog and the asset
|
|
8
|
-
* library's detail say the same thing.
|
|
9
|
+
* library's detail say the same thing. EC-659: "localized" is `at`'s lens — the rule the
|
|
10
|
+
* field's value is read and written by.
|
|
9
11
|
*/
|
|
10
12
|
export function AssetMetaFieldLabel({ field, at }) {
|
|
11
|
-
if (
|
|
13
|
+
if (at === null || !assetMetaIsLocalized(at, field.name))
|
|
12
14
|
return _jsx(_Fragment, { children: fieldLabel(field) });
|
|
13
15
|
return (_jsxs(Row, { gap: "2", children: [_jsx("span", { children: fieldLabel(field) }), _jsx(Badge, { variant: "outline", "data-ec-asset-meta-locale": at.locale, children: at.locale })] }));
|
|
14
16
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"asset-meta-label.js","sourceRoot":"","sources":["../../../src/components/assets/asset-meta-label.tsx"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"asset-meta-label.js","sourceRoot":"","sources":["../../../src/components/assets/asset-meta-label.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAA;AAElE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAA;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAE9C;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,EAAE,KAAK,EAAE,EAAE,EAAmD;IAChG,IAAI,EAAE,KAAK,IAAI,IAAI,CAAC,oBAAoB,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC;QAAE,OAAO,4BAAG,UAAU,CAAC,KAAK,CAAC,GAAI,CAAA;IACzF,OAAO,CACL,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,aACV,yBAAO,UAAU,CAAC,KAAK,CAAC,GAAQ,EAChC,KAAC,KAAK,IAAC,OAAO,EAAC,SAAS,+BAA4B,EAAE,CAAC,MAAM,YAC1D,EAAE,CAAC,MAAM,GACJ,IACJ,CACP,CAAA;AACH,CAAC","sourcesContent":["import type { FieldDef } from '@elytracms/core/cms-core'\nimport { assetMetaIsLocalized } from '@elytracms/core/persistence'\nimport type { AssetReadLocale } from '@elytracms/core/persistence'\nimport { Badge, Row } from '@elytracms/studio/ui'\nimport { fieldLabel } from '../cms/cms-labels'\n\n/**\n * The label of one asset metadata field (EC-632): the field's label, and — for a\n * `localized: true` field — the locale it is edited in, as the document editor labels a\n * localized field (`Title en`). One component, so the image options dialog and the asset\n * library's detail say the same thing. EC-659: \"localized\" is `at`'s lens — the rule the\n * field's value is read and written by.\n */\nexport function AssetMetaFieldLabel({ field, at }: { field: FieldDef; at: AssetReadLocale | null }) {\n if (at === null || !assetMetaIsLocalized(at, field.name)) return <>{fieldLabel(field)}</>\n return (\n <Row gap=\"2\">\n <span>{fieldLabel(field)}</span>\n <Badge variant=\"outline\" data-ec-asset-meta-locale={at.locale}>\n {at.locale}\n </Badge>\n </Row>\n )\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CollectionDef, FieldDef } from '@elytracms/core/cms-core';
|
|
2
|
-
import type { AssetReadLocale, AssetRecord, AssetRepository, AssetWriteInput } from '@elytracms/core/persistence';
|
|
2
|
+
import type { AssetMetaDrift, AssetReadLocale, AssetRecord, AssetRepository, AssetWriteInput } from '@elytracms/core/persistence';
|
|
3
3
|
/**
|
|
4
4
|
* Asset metadata editing (EC-152). A patch is applied through the
|
|
5
5
|
* {@link AssetRepository} interface — in the studio that is the EC-141
|
|
@@ -71,8 +71,14 @@ export interface AssetMetaDraft {
|
|
|
71
71
|
readonly meta: Readonly<Record<string, unknown>>;
|
|
72
72
|
readonly localizedMeta: Readonly<Record<string, Readonly<Record<string, unknown>>>>;
|
|
73
73
|
}
|
|
74
|
-
/**
|
|
75
|
-
|
|
74
|
+
/**
|
|
75
|
+
* The fields of the asset collection an editor edits by — only the name matters here.
|
|
76
|
+
* EC-659: whether a field is edited per locale is the read locale's LENS (`at.lens`, the
|
|
77
|
+
* project's asset collection as declared now — the one delivery reads by), never the
|
|
78
|
+
* form's own copy of the field: a mount's rail override cannot make the editor write a
|
|
79
|
+
* translation delivery would not read.
|
|
80
|
+
*/
|
|
81
|
+
type MetaField = Pick<FieldDef, 'name'>;
|
|
76
82
|
/**
|
|
77
83
|
* The draft an editor opens (EC-587 + EC-632): the stored bags, with a legacy `alt` column
|
|
78
84
|
* lifted into the shared one (core `liftLegacyAlt`), so a not-yet-migrated asset shows the
|
|
@@ -80,22 +86,27 @@ type MetaField = Pick<FieldDef, 'name' | 'localized'>;
|
|
|
80
86
|
*/
|
|
81
87
|
export declare function assetMetaDraft(record: Pick<AssetRecord, 'alt' | 'meta' | 'localizedMeta'>): AssetMetaDraft;
|
|
82
88
|
/**
|
|
83
|
-
* What the form shows for one field (EC-632)
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* value
|
|
89
|
+
* What the form shows for one field (EC-632) — read by THE positional rule delivery's view
|
|
90
|
+
* reads by (core `assetMetaValueAt`, EC-659), with the editor's fallback (`'own'`): a field
|
|
91
|
+
* the lens localizes shows the editor's locale's own value (an untranslated locale shows
|
|
92
|
+
* empty — it falls back to the default locale on delivery); the default locale also shows a
|
|
93
|
+
* value written before the field was localized (the shared bag — delivery's last-resort
|
|
94
|
+
* fallback, so it IS that locale's value). Any other field the shared value — a translation
|
|
95
|
+
* left from a former localization is drift, shown by no reader. Without a locale config,
|
|
96
|
+
* the shared value.
|
|
88
97
|
*/
|
|
89
98
|
export declare function assetMetaFieldValue(draft: AssetMetaDraft, field: MetaField, at: AssetReadLocale | null): unknown;
|
|
90
99
|
/**
|
|
91
|
-
* Write one field of the draft (EC-632) — `undefined` removes the value
|
|
92
|
-
*
|
|
100
|
+
* Write one field of the draft (EC-632) — `undefined` removes the value. Where it goes is
|
|
101
|
+
* the lens's answer (EC-659, `assetMetaIsLocalized` — the rule every reader reads by):
|
|
102
|
+
* - a field the lens localizes goes into the editor's locale's bucket; in the DEFAULT
|
|
93
103
|
* locale a value written before the field was localized leaves the shared bag — the
|
|
94
104
|
* default bucket is its home now, so a clear there really clears it;
|
|
95
105
|
* - any other field goes into the shared bag, and a value a former localization left in a
|
|
96
|
-
* bucket is removed (one home per key —
|
|
97
|
-
*
|
|
98
|
-
*
|
|
106
|
+
* bucket is removed (one home per key — the stale value is drift, read by nobody; this
|
|
107
|
+
* edit is how an editor clears it);
|
|
108
|
+
* - without a locale config (`at` null) there is no lens: the shared bag, and every
|
|
109
|
+
* bucket left alone — a translation is never removed on a guess.
|
|
99
110
|
*/
|
|
100
111
|
export declare function setAssetMetaField(draft: AssetMetaDraft, field: MetaField, at: AssetReadLocale | null, value: unknown): AssetMetaDraft;
|
|
101
112
|
/**
|
|
@@ -106,6 +117,15 @@ export declare function setAssetMetaField(draft: AssetMetaDraft, field: MetaFiel
|
|
|
106
117
|
* dialog, the asset library's detail, the assets rail) sends this instead of its buffer.
|
|
107
118
|
*/
|
|
108
119
|
export declare function assetMetaDraftPatch(opened: AssetMetaDraft, edited: AssetMetaDraft): Pick<AssetMetadataPatch, 'metaPatch' | 'localizedMetaPatch'>;
|
|
120
|
+
/**
|
|
121
|
+
* The asset detail's drift notice, as data (EC-659): the stored translations the lens does
|
|
122
|
+
* not read (core `assetMetaDrift`), grouped by reason, each field listed with the locales
|
|
123
|
+
* holding one — `alt (de, fr)`. Empty when there is none. Pure.
|
|
124
|
+
*/
|
|
125
|
+
export declare function assetMetaDriftSummary(drift: readonly AssetMetaDrift[]): Array<{
|
|
126
|
+
reason: AssetMetaDrift['reason'];
|
|
127
|
+
fields: string;
|
|
128
|
+
}>;
|
|
109
129
|
/** The form's flat view of a draft in one locale — `{ [field]: value }` for the given fields. */
|
|
110
130
|
export declare function assetMetaFormValues(draft: AssetMetaDraft, fields: readonly MetaField[], at: AssetReadLocale | null): Record<string, unknown>;
|
|
111
131
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { liftLegacyAlt, metaPatchBetween } from '@elytracms/core/persistence';
|
|
1
|
+
import { assetMetaIsLocalized, assetMetaValueAt, liftLegacyAlt, metaPatchBetween } from '@elytracms/core/persistence';
|
|
2
2
|
/**
|
|
3
3
|
* Asset metadata editing (EC-152). A patch is applied through the
|
|
4
4
|
* {@link AssetRepository} interface — in the studio that is the EC-141
|
|
@@ -56,30 +56,30 @@ export function assetMetaDraft(record) {
|
|
|
56
56
|
}
|
|
57
57
|
const hasOwn = (bag, key) => bag !== undefined && Object.prototype.hasOwnProperty.call(bag, key);
|
|
58
58
|
/**
|
|
59
|
-
* What the form shows for one field (EC-632)
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* value
|
|
59
|
+
* What the form shows for one field (EC-632) — read by THE positional rule delivery's view
|
|
60
|
+
* reads by (core `assetMetaValueAt`, EC-659), with the editor's fallback (`'own'`): a field
|
|
61
|
+
* the lens localizes shows the editor's locale's own value (an untranslated locale shows
|
|
62
|
+
* empty — it falls back to the default locale on delivery); the default locale also shows a
|
|
63
|
+
* value written before the field was localized (the shared bag — delivery's last-resort
|
|
64
|
+
* fallback, so it IS that locale's value). Any other field the shared value — a translation
|
|
65
|
+
* left from a former localization is drift, shown by no reader. Without a locale config,
|
|
66
|
+
* the shared value.
|
|
64
67
|
*/
|
|
65
68
|
export function assetMetaFieldValue(draft, field, at) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (hasOwn(bucket, field.name))
|
|
69
|
-
return bucket[field.name];
|
|
70
|
-
return at.locale === at.default ? draft.meta[field.name] : undefined;
|
|
71
|
-
}
|
|
72
|
-
return draft.meta[field.name];
|
|
69
|
+
const read = assetMetaValueAt(draft, field.name, at, 'own');
|
|
70
|
+
return read.found ? read.value : undefined;
|
|
73
71
|
}
|
|
74
72
|
/**
|
|
75
|
-
* Write one field of the draft (EC-632) — `undefined` removes the value
|
|
76
|
-
*
|
|
73
|
+
* Write one field of the draft (EC-632) — `undefined` removes the value. Where it goes is
|
|
74
|
+
* the lens's answer (EC-659, `assetMetaIsLocalized` — the rule every reader reads by):
|
|
75
|
+
* - a field the lens localizes goes into the editor's locale's bucket; in the DEFAULT
|
|
77
76
|
* locale a value written before the field was localized leaves the shared bag — the
|
|
78
77
|
* default bucket is its home now, so a clear there really clears it;
|
|
79
78
|
* - any other field goes into the shared bag, and a value a former localization left in a
|
|
80
|
-
* bucket is removed (one home per key —
|
|
81
|
-
*
|
|
82
|
-
*
|
|
79
|
+
* bucket is removed (one home per key — the stale value is drift, read by nobody; this
|
|
80
|
+
* edit is how an editor clears it);
|
|
81
|
+
* - without a locale config (`at` null) there is no lens: the shared bag, and every
|
|
82
|
+
* bucket left alone — a translation is never removed on a guess.
|
|
83
83
|
*/
|
|
84
84
|
export function setAssetMetaField(draft, field, at, value) {
|
|
85
85
|
const name = field.name;
|
|
@@ -96,10 +96,10 @@ export function setAssetMetaField(draft, field, at, value) {
|
|
|
96
96
|
delete next[name];
|
|
97
97
|
return next;
|
|
98
98
|
};
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
// No locale config installed yet (no lens): the shared bag, the buckets left untouched.
|
|
100
|
+
if (at === null)
|
|
101
|
+
return { meta: withValue(draft.meta), localizedMeta: draft.localizedMeta };
|
|
102
|
+
if (assetMetaIsLocalized(at, name)) {
|
|
103
103
|
const bucket = withValue(draft.localizedMeta[at.locale]);
|
|
104
104
|
const localizedMeta = { ...draft.localizedMeta, [at.locale]: bucket };
|
|
105
105
|
const meta = at.locale === at.default && hasOwn(draft.meta, name) ? without(draft.meta) : draft.meta;
|
|
@@ -131,6 +131,30 @@ export function assetMetaDraftPatch(opened, edited) {
|
|
|
131
131
|
...(Object.keys(localizedMetaPatch).length > 0 ? { localizedMetaPatch } : {}),
|
|
132
132
|
};
|
|
133
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* The asset detail's drift notice, as data (EC-659): the stored translations the lens does
|
|
136
|
+
* not read (core `assetMetaDrift`), grouped by reason, each field listed with the locales
|
|
137
|
+
* holding one — `alt (de, fr)`. Empty when there is none. Pure.
|
|
138
|
+
*/
|
|
139
|
+
export function assetMetaDriftSummary(drift) {
|
|
140
|
+
const lines = [];
|
|
141
|
+
for (const reason of ['not-localized', 'undeclared']) {
|
|
142
|
+
const byField = new Map();
|
|
143
|
+
for (const entry of drift) {
|
|
144
|
+
if (entry.reason !== reason)
|
|
145
|
+
continue;
|
|
146
|
+
byField.set(entry.field, [...(byField.get(entry.field) ?? []), entry.locale]);
|
|
147
|
+
}
|
|
148
|
+
if (byField.size === 0)
|
|
149
|
+
continue;
|
|
150
|
+
const fields = [...byField.entries()]
|
|
151
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
152
|
+
.map(([field, locales]) => `${field} (${locales.join(', ')})`)
|
|
153
|
+
.join(', ');
|
|
154
|
+
lines.push({ reason, fields });
|
|
155
|
+
}
|
|
156
|
+
return lines;
|
|
157
|
+
}
|
|
134
158
|
/** The form's flat view of a draft in one locale — `{ [field]: value }` for the given fields. */
|
|
135
159
|
export function assetMetaFormValues(draft, fields, at) {
|
|
136
160
|
return Object.fromEntries(fields.map((field) => [field.name, assetMetaFieldValue(draft, field, at)]));
|