@elytracms/studio 0.0.20 → 0.0.22
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 +2 -0
- package/convex/content.ts +6 -0
- package/convex/delivery.ts +5 -0
- package/convex/schema.ts +2 -0
- package/dist/components/assets/asset-metadata.d.ts +7 -0
- package/dist/components/assets/asset-metadata.js +3 -0
- package/dist/components/assets/asset-metadata.js.map +1 -1
- package/dist/components/assets/assets-store.js +9 -3
- package/dist/components/assets/assets-store.js.map +1 -1
- package/dist/components/builder/canvas.d.ts +7 -1
- package/dist/components/builder/canvas.js +63 -6
- package/dist/components/builder/canvas.js.map +1 -1
- package/dist/components/builder/composition-field-editor.js +1 -1
- package/dist/components/builder/composition-field-editor.js.map +1 -1
- package/dist/components/builder/property-inspector.js +2 -1
- package/dist/components/builder/property-inspector.js.map +1 -1
- package/dist/components/builder/reuse-context.js +21 -8
- package/dist/components/builder/reuse-context.js.map +1 -1
- package/dist/components/builder/reuse.d.ts +14 -1
- package/dist/components/builder/reuse.js +33 -1
- package/dist/components/builder/reuse.js.map +1 -1
- package/dist/components/cms/asset-picker.d.ts +6 -2
- package/dist/components/cms/asset-picker.js +56 -12
- package/dist/components/cms/asset-picker.js.map +1 -1
- package/dist/components/cms/detail-editor.js +78 -7
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/field-control.js +125 -32
- package/dist/components/cms/field-control.js.map +1 -1
- package/dist/components/cms/field-density.d.ts +14 -0
- package/dist/components/cms/field-density.js +10 -0
- package/dist/components/cms/field-density.js.map +1 -0
- package/dist/lib/data/collection-content.d.ts +12 -3
- package/dist/lib/data/collection-content.js +20 -17
- package/dist/lib/data/collection-content.js.map +1 -1
- package/dist/lib/persistence/convex-adapter.js +2 -0
- package/dist/lib/persistence/convex-adapter.js.map +1 -1
- package/package.json +2 -2
package/convex/assets.ts
CHANGED
|
@@ -26,6 +26,8 @@ export const upsert = mutation({
|
|
|
26
26
|
storageKey: v.string(),
|
|
27
27
|
url: v.optional(v.string()),
|
|
28
28
|
focalPoint: v.optional(v.object({ x: v.number(), y: v.number() })),
|
|
29
|
+
/** Schema-driven asset metadata bag (EC-301): alt, copyright, credit, …. */
|
|
30
|
+
meta: v.optional(v.any()),
|
|
29
31
|
createdAt: v.string(),
|
|
30
32
|
updatedAt: v.string(),
|
|
31
33
|
},
|
package/convex/content.ts
CHANGED
|
@@ -209,6 +209,12 @@ export async function createContent(
|
|
|
209
209
|
...(row.width !== undefined ? { width: row.width } : {}),
|
|
210
210
|
...(row.height !== undefined ? { height: row.height } : {}),
|
|
211
211
|
...(row.alt !== undefined ? { alt: row.alt } : {}),
|
|
212
|
+
// EC-301: focalPoint (EC-230) and the schema-driven metadata bag must reach
|
|
213
|
+
// delivery — both were dropped here, so a studio-set hotspot/metadata never
|
|
214
|
+
// made it into the LIVE snapshot (only the studio canvas, which reads records
|
|
215
|
+
// directly, showed them).
|
|
216
|
+
...(row.focalPoint !== undefined ? { focalPoint: row.focalPoint } : {}),
|
|
217
|
+
...(row.meta !== undefined ? { meta: row.meta } : {}),
|
|
212
218
|
storageKey: row.storageKey,
|
|
213
219
|
...(url !== undefined ? { url } : {}),
|
|
214
220
|
createdAt: row.createdAt,
|
package/convex/delivery.ts
CHANGED
|
@@ -178,6 +178,11 @@ export async function loadSnapshot(
|
|
|
178
178
|
...(row.width !== undefined ? { width: row.width } : {}),
|
|
179
179
|
...(row.height !== undefined ? { height: row.height } : {}),
|
|
180
180
|
...(row.alt !== undefined ? { alt: row.alt } : {}),
|
|
181
|
+
// EC-301: thread focalPoint (EC-230) + the schema-driven metadata bag into
|
|
182
|
+
// the delivery snapshot — both were omitted here, dropping a studio-set
|
|
183
|
+
// hotspot/metadata on the LIVE path.
|
|
184
|
+
...(row.focalPoint !== undefined ? { focalPoint: row.focalPoint } : {}),
|
|
185
|
+
...(row.meta !== undefined ? { meta: row.meta } : {}),
|
|
181
186
|
storageKey: row.storageKey,
|
|
182
187
|
...(url !== undefined ? { url } : {}),
|
|
183
188
|
createdAt: row.createdAt,
|
package/convex/schema.ts
CHANGED
|
@@ -126,6 +126,8 @@ export default defineSchema({
|
|
|
126
126
|
url: v.optional(v.string()),
|
|
127
127
|
/** Normalized image focal point `{ x, y }` in 0–1 (EC-230); CSS object-position. */
|
|
128
128
|
focalPoint: v.optional(v.object({ x: v.number(), y: v.number() })),
|
|
129
|
+
/** Schema-driven asset metadata bag (EC-301): alt, copyright, credit, …. */
|
|
130
|
+
meta: v.optional(v.any()),
|
|
129
131
|
createdAt: v.string(),
|
|
130
132
|
updatedAt: v.string(),
|
|
131
133
|
}).index('by_asset', ['assetId']),
|
|
@@ -23,6 +23,13 @@ export interface AssetMetadataPatch {
|
|
|
23
23
|
x: number;
|
|
24
24
|
y: number;
|
|
25
25
|
} | null;
|
|
26
|
+
/**
|
|
27
|
+
* Schema-driven asset metadata bag (EC-301): alt, copyright, credit, … keyed by
|
|
28
|
+
* the asset collection's `fields`. A present object **replaces** the whole bag
|
|
29
|
+
* (the image options dialog holds the complete edited meta); an absent key
|
|
30
|
+
* leaves the current bag untouched.
|
|
31
|
+
*/
|
|
32
|
+
meta?: Record<string, unknown>;
|
|
26
33
|
}
|
|
27
34
|
/** The full upsert input that re-states `current` with the patch applied. */
|
|
28
35
|
export declare function assetMetadataUpsertInput(current: AssetRecord, patch: AssetMetadataPatch): NewAssetInput;
|
|
@@ -11,6 +11,8 @@ export function assetMetadataUpsertInput(current, patch) {
|
|
|
11
11
|
const title = mergeField(patch.title, 'title' in patch, current.title);
|
|
12
12
|
// present `{x,y}` sets, present `null` clears, absent keeps the current point.
|
|
13
13
|
const focalPoint = 'focalPoint' in patch ? (patch.focalPoint ?? undefined) : current.focalPoint;
|
|
14
|
+
// present object replaces the whole bag; absent keeps the current one (EC-301).
|
|
15
|
+
const meta = 'meta' in patch ? patch.meta : current.meta;
|
|
14
16
|
return {
|
|
15
17
|
id: current.id,
|
|
16
18
|
filename: current.filename,
|
|
@@ -23,6 +25,7 @@ export function assetMetadataUpsertInput(current, patch) {
|
|
|
23
25
|
...(alt !== undefined ? { alt } : {}),
|
|
24
26
|
...(title !== undefined ? { title } : {}),
|
|
25
27
|
...(focalPoint !== undefined ? { focalPoint } : {}),
|
|
28
|
+
...(meta !== undefined ? { meta } : {}),
|
|
26
29
|
};
|
|
27
30
|
}
|
|
28
31
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"asset-metadata.js","sourceRoot":"","sources":["../../../src/components/assets/asset-metadata.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"asset-metadata.js","sourceRoot":"","sources":["../../../src/components/assets/asset-metadata.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssetRecord, AssetRepository, NewAssetInput } from '@elytracms/core/persistence'\n\n/**\n * Asset metadata editing (EC-152). A patch is applied through the\n * {@link AssetRepository} interface — in the studio that is the EC-141\n * operations facade, so every edit runs the `assets.upsert` command\n * (validate → authorize → mutate → event). Pure orchestration; injectable\n * for tests.\n */\n\nexport interface AssetMetadataPatch {\n /**\n * New alt text. A present key overwrites; a trimmed-empty string clears\n * the field. An absent key leaves the current value untouched.\n */\n alt?: string\n /** New display title; same present/empty/absent semantics as `alt`. */\n title?: string\n /**\n * New focal point (EC-248), the normalized 0–1 hotspot threaded to delivery's\n * `object-position` (EC-230). A present `{x,y}` sets it, `null` clears it, an\n * absent key leaves the current value untouched.\n */\n focalPoint?: { x: number; y: number } | null\n /**\n * Schema-driven asset metadata bag (EC-301): alt, copyright, credit, … keyed by\n * the asset collection's `fields`. A present object **replaces** the whole bag\n * (the image options dialog holds the complete edited meta); an absent key\n * leaves the current bag untouched.\n */\n meta?: Record<string, unknown>\n}\n\n/** Merge one patch field: present+empty clears, present+value sets, absent keeps. */\nfunction mergeField(\n patchValue: string | undefined,\n patchHasKey: boolean,\n currentValue: string | undefined,\n): string | undefined {\n if (!patchHasKey) return currentValue\n const trimmed = patchValue?.trim() ?? ''\n return trimmed.length > 0 ? trimmed : undefined\n}\n\n/** The full upsert input that re-states `current` with the patch applied. */\nexport function assetMetadataUpsertInput(\n current: AssetRecord,\n patch: AssetMetadataPatch,\n): NewAssetInput {\n const alt = mergeField(patch.alt, 'alt' in patch, current.alt)\n const title = mergeField(patch.title, 'title' in patch, current.title)\n // present `{x,y}` sets, present `null` clears, absent keeps the current point.\n const focalPoint = 'focalPoint' in patch ? (patch.focalPoint ?? undefined) : current.focalPoint\n // present object replaces the whole bag; absent keeps the current one (EC-301).\n const meta = 'meta' in patch ? patch.meta : current.meta\n return {\n id: current.id,\n filename: current.filename,\n contentType: current.contentType,\n byteSize: current.byteSize,\n storageKey: current.storageKey,\n ...(current.width !== undefined ? { width: current.width } : {}),\n ...(current.height !== undefined ? { height: current.height } : {}),\n ...(current.url !== undefined ? { url: current.url } : {}),\n ...(alt !== undefined ? { alt } : {}),\n ...(title !== undefined ? { title } : {}),\n ...(focalPoint !== undefined ? { focalPoint } : {}),\n ...(meta !== undefined ? { meta } : {}),\n }\n}\n\nexport type UpdateAssetMetadataResult =\n | { ok: true; asset: AssetRecord }\n | { ok: false; message: string }\n\n/**\n * Load the current record, apply the patch, and persist it via the\n * repository (the operations facade in the studio). Failures come back as a\n * structured result — never a throw the UI has to guess about.\n */\nexport async function updateAssetMetadata(\n deps: { assets: AssetRepository },\n projectId: string,\n assetId: string,\n patch: AssetMetadataPatch,\n): Promise<UpdateAssetMetadataResult> {\n try {\n const current = await deps.assets.getAsset(projectId, assetId)\n const asset = await deps.assets.upsertAsset(projectId, assetMetadataUpsertInput(current, patch))\n return { ok: true, asset }\n } catch (cause) {\n return { ok: false, message: cause instanceof Error ? cause.message : String(cause) }\n }\n}\n"]}
|
|
@@ -103,10 +103,16 @@ export function AssetsProvider({ projectId, children, }) {
|
|
|
103
103
|
// operations facade (EC-141) — never straight at a repository.
|
|
104
104
|
const { assetRepo } = await studio();
|
|
105
105
|
const result = await updateAssetMetadata({ assets: assetRepo }, projectId, assetId, patch);
|
|
106
|
-
if (result.ok)
|
|
107
|
-
|
|
106
|
+
if (result.ok) {
|
|
107
|
+
// Patch the one updated record into local state rather than re-listing the
|
|
108
|
+
// whole library (EC-301). A focal-point or alt edit must not trigger a full
|
|
109
|
+
// `assets.list` round-trip — that re-list (plus the marker only moving once
|
|
110
|
+
// it returned) was the focal-point picker's lag and query storm.
|
|
111
|
+
const updated = result.asset;
|
|
112
|
+
setAssets((prev) => prev.map((a) => (a.id === assetId ? updated : a)));
|
|
113
|
+
}
|
|
108
114
|
return result;
|
|
109
|
-
}, [studio, projectId
|
|
115
|
+
}, [studio, projectId]);
|
|
110
116
|
const store = useMemo(() => ({
|
|
111
117
|
projectId,
|
|
112
118
|
status,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assets-store.js","sourceRoot":"","sources":["../../../src/components/assets/assets-store.tsx"],"names":[],"mappings":"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","sourcesContent":["import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport type { AssetRecord, AssetRepository } from '@elytracms/core/persistence'\nimport { deleteAsset, runUpload, xhrUploadTransport } from '@elytracms/studio/lib/assets'\nimport type { DeleteAssetResult, UploadState, UploadableFile } from '@elytracms/studio/lib/assets'\nimport { loadInboundAssetReferenceViews } from '@elytracms/studio/lib/references'\nimport type { ReferenceEntryView } from '@elytracms/studio/lib/references'\nimport { getStudioOperations, operationsAssetRepository } from '@elytracms/studio/lib/operations'\nimport type { StudioOperations } from '@elytracms/studio/lib/operations'\nimport { updateAssetMetadata } from './asset-metadata'\nimport type { AssetMetadataPatch, UpdateAssetMetadataResult } from './asset-metadata'\n\n/**\n * Asset library store (EC-151, EC-141). Asset *metadata* mutations go through\n * the shared operations client (`assets.*` commands — validate → authorize →\n * mutate → event); blob *bytes* ride on `adapter.blobs`, which is transport,\n * not domain. The UI works identically against the in-memory seed and a live\n * Convex deployment.\n */\n\nexport interface UploadEntry {\n id: string\n filename: string\n state: UploadState\n}\n\nexport interface AssetsStore {\n /** The active project (e.g. for reference deep links, EC-202). */\n projectId: string\n status: 'loading' | 'ready' | 'error'\n errorMessage?: string\n assets: readonly AssetRecord[]\n uploads: readonly UploadEntry[]\n upload(files: Iterable<UploadableFile>): void\n retryUpload(uploadId: string): void\n dismissUpload(uploadId: string): void\n /**\n * Index-accurate inbound references of an asset (EC-202): the \"used by\"\n * pre-flight of the block-or-confirm delete dialog, read from the EC-155\n * reference index (not the retired string scan).\n */\n usagesOf(assetId: string): Promise<ReferenceEntryView[]>\n remove(assetId: string, options?: { confirmInUse?: boolean }): Promise<DeleteAssetResult>\n /** Edit alt/title (EC-152); persists through the operations asset facade. */\n updateMetadata(assetId: string, patch: AssetMetadataPatch): Promise<UpdateAssetMetadataResult>\n refresh(): Promise<void>\n}\n\nconst AssetsContext = createContext<AssetsStore | null>(null)\n\nexport function AssetsProvider({\n projectId,\n children,\n}: {\n projectId: string\n children: ReactNode\n}) {\n const uploadFilesRef = useRef(new Map<string, UploadableFile>())\n const uploadSeq = useRef(0)\n const [status, setStatus] = useState<AssetsStore['status']>('loading')\n const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined)\n const [assets, setAssets] = useState<readonly AssetRecord[]>([])\n const [uploads, setUploads] = useState<readonly UploadEntry[]>([])\n\n /** The shared studio operations bundle + the metadata-write facade over it. */\n const studio = useCallback(async (): Promise<\n StudioOperations & { assetRepo: AssetRepository }\n > => {\n const bundle = await getStudioOperations()\n return { ...bundle, assetRepo: operationsAssetRepository(bundle.operations) }\n }, [])\n\n const refresh = useCallback(async () => {\n try {\n const { operations } = await studio()\n const result = await operations.assets.list({ projectId })\n if (!result.ok) {\n setStatus('error')\n setErrorMessage(result.error.message)\n return\n }\n setAssets(result.value)\n setStatus('ready')\n setErrorMessage(undefined)\n } catch (error) {\n setStatus('error')\n setErrorMessage(error instanceof Error ? error.message : String(error))\n }\n }, [studio, projectId])\n\n useEffect(() => {\n void refresh()\n }, [refresh])\n\n const setUploadState = useCallback((uploadId: string, state: UploadState) => {\n setUploads((prev) => prev.map((u) => (u.id === uploadId ? { ...u, state } : u)))\n }, [])\n\n const runEntry = useCallback(\n async (uploadId: string, file: UploadableFile, previous?: UploadState) => {\n const { adapter, assetRepo } = await studio()\n const result = await runUpload(\n {\n // Bytes ride on blob storage (transport); the metadata record is\n // confirmed through the `assets.upsert` operation command.\n blobs: adapter.blobs,\n assets: assetRepo,\n ...(typeof XMLHttpRequest !== 'undefined' ? { transport: xhrUploadTransport } : {}),\n },\n {\n projectId,\n file,\n onState: (state) => setUploadState(uploadId, state),\n },\n previous,\n )\n if (result.ok) {\n // Completed uploads leave the queue; the asset appears in the library.\n uploadFilesRef.current.delete(uploadId)\n setUploads((prev) => prev.filter((u) => u.id !== uploadId))\n await refresh()\n }\n },\n [studio, projectId, refresh, setUploadState],\n )\n\n const upload = useCallback(\n (files: Iterable<UploadableFile>) => {\n for (const file of files) {\n uploadSeq.current += 1\n const uploadId = `upload-${uploadSeq.current}`\n uploadFilesRef.current.set(uploadId, file)\n setUploads((prev) => [\n ...prev,\n { id: uploadId, filename: file.name, state: { phase: 'idle', progress: 0, attempt: 0 } },\n ])\n void runEntry(uploadId, file)\n }\n },\n [runEntry],\n )\n\n const retryUpload = useCallback(\n (uploadId: string) => {\n const file = uploadFilesRef.current.get(uploadId)\n const entry = uploads.find((u) => u.id === uploadId)\n if (!file || !entry || entry.state.phase !== 'failed') return\n void runEntry(uploadId, file, entry.state)\n },\n [runEntry, uploads],\n )\n\n const dismissUpload = useCallback((uploadId: string) => {\n uploadFilesRef.current.delete(uploadId)\n setUploads((prev) => prev.filter((u) => u.id !== uploadId))\n }, [])\n\n const usagesOf = useCallback(\n // Index-accurate (EC-202): inbound references from the EC-155 index via the\n // operations queries — the same machinery the References panel uses.\n async (assetId: string) => loadInboundAssetReferenceViews(projectId, assetId),\n [projectId],\n )\n\n const remove = useCallback(\n async (assetId: string, options?: { confirmInUse?: boolean }) => {\n const { adapter, assetRepo } = await studio()\n // Metadata removal goes through the `assets.remove` command (via the\n // facade); blob byte deletion stays on the adapter; the usage gate reads\n // the EC-155 reference index (`adapter.references`, EC-202).\n const result = await deleteAsset(\n { assets: assetRepo, blobs: adapter.blobs, references: adapter.references },\n projectId,\n assetId,\n options,\n )\n if (result.outcome === 'deleted' || result.outcome === 'not-found') await refresh()\n return result\n },\n [studio, projectId, refresh],\n )\n\n const updateMetadata = useCallback(\n async (assetId: string, patch: AssetMetadataPatch) => {\n // Metadata writes go through the `assets.upsert` command via the\n // operations facade (EC-141) — never straight at a repository.\n const { assetRepo } = await studio()\n const result = await updateAssetMetadata({ assets: assetRepo }, projectId, assetId, patch)\n if (result.ok) await refresh()\n return result\n },\n [studio, projectId, refresh],\n )\n\n const store = useMemo<AssetsStore>(\n () => ({\n projectId,\n status,\n ...(errorMessage !== undefined ? { errorMessage } : {}),\n assets,\n uploads,\n upload,\n retryUpload,\n dismissUpload,\n usagesOf,\n remove,\n updateMetadata,\n refresh,\n }),\n [\n projectId,\n status,\n errorMessage,\n assets,\n uploads,\n upload,\n retryUpload,\n dismissUpload,\n usagesOf,\n remove,\n updateMetadata,\n refresh,\n ],\n )\n\n return <AssetsContext.Provider value={store}>{children}</AssetsContext.Provider>\n}\n\nexport function useAssets(): AssetsStore {\n const store = useContext(AssetsContext)\n if (!store) throw new Error('useAssets must be used within an <AssetsProvider>.')\n return store\n}\n\n/**\n * The assets store when an `<AssetsProvider>` is mounted above, else `null`.\n * Lets surfaces outside the Assets section (the CMS asset picker, EC-152)\n * reuse an ambient provider or mount their own.\n */\nexport function useOptionalAssets(): AssetsStore | null {\n return useContext(AssetsContext)\n}\n"]}
|
|
1
|
+
{"version":3,"file":"assets-store.js","sourceRoot":"","sources":["../../../src/components/assets/assets-store.tsx"],"names":[],"mappings":"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","sourcesContent":["import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport type { AssetRecord, AssetRepository } from '@elytracms/core/persistence'\nimport { deleteAsset, runUpload, xhrUploadTransport } from '@elytracms/studio/lib/assets'\nimport type { DeleteAssetResult, UploadState, UploadableFile } from '@elytracms/studio/lib/assets'\nimport { loadInboundAssetReferenceViews } from '@elytracms/studio/lib/references'\nimport type { ReferenceEntryView } from '@elytracms/studio/lib/references'\nimport { getStudioOperations, operationsAssetRepository } from '@elytracms/studio/lib/operations'\nimport type { StudioOperations } from '@elytracms/studio/lib/operations'\nimport { updateAssetMetadata } from './asset-metadata'\nimport type { AssetMetadataPatch, UpdateAssetMetadataResult } from './asset-metadata'\n\n/**\n * Asset library store (EC-151, EC-141). Asset *metadata* mutations go through\n * the shared operations client (`assets.*` commands — validate → authorize →\n * mutate → event); blob *bytes* ride on `adapter.blobs`, which is transport,\n * not domain. The UI works identically against the in-memory seed and a live\n * Convex deployment.\n */\n\nexport interface UploadEntry {\n id: string\n filename: string\n state: UploadState\n}\n\nexport interface AssetsStore {\n /** The active project (e.g. for reference deep links, EC-202). */\n projectId: string\n status: 'loading' | 'ready' | 'error'\n errorMessage?: string\n assets: readonly AssetRecord[]\n uploads: readonly UploadEntry[]\n upload(files: Iterable<UploadableFile>): void\n retryUpload(uploadId: string): void\n dismissUpload(uploadId: string): void\n /**\n * Index-accurate inbound references of an asset (EC-202): the \"used by\"\n * pre-flight of the block-or-confirm delete dialog, read from the EC-155\n * reference index (not the retired string scan).\n */\n usagesOf(assetId: string): Promise<ReferenceEntryView[]>\n remove(assetId: string, options?: { confirmInUse?: boolean }): Promise<DeleteAssetResult>\n /** Edit alt/title (EC-152); persists through the operations asset facade. */\n updateMetadata(assetId: string, patch: AssetMetadataPatch): Promise<UpdateAssetMetadataResult>\n refresh(): Promise<void>\n}\n\nconst AssetsContext = createContext<AssetsStore | null>(null)\n\nexport function AssetsProvider({\n projectId,\n children,\n}: {\n projectId: string\n children: ReactNode\n}) {\n const uploadFilesRef = useRef(new Map<string, UploadableFile>())\n const uploadSeq = useRef(0)\n const [status, setStatus] = useState<AssetsStore['status']>('loading')\n const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined)\n const [assets, setAssets] = useState<readonly AssetRecord[]>([])\n const [uploads, setUploads] = useState<readonly UploadEntry[]>([])\n\n /** The shared studio operations bundle + the metadata-write facade over it. */\n const studio = useCallback(async (): Promise<\n StudioOperations & { assetRepo: AssetRepository }\n > => {\n const bundle = await getStudioOperations()\n return { ...bundle, assetRepo: operationsAssetRepository(bundle.operations) }\n }, [])\n\n const refresh = useCallback(async () => {\n try {\n const { operations } = await studio()\n const result = await operations.assets.list({ projectId })\n if (!result.ok) {\n setStatus('error')\n setErrorMessage(result.error.message)\n return\n }\n setAssets(result.value)\n setStatus('ready')\n setErrorMessage(undefined)\n } catch (error) {\n setStatus('error')\n setErrorMessage(error instanceof Error ? error.message : String(error))\n }\n }, [studio, projectId])\n\n useEffect(() => {\n void refresh()\n }, [refresh])\n\n const setUploadState = useCallback((uploadId: string, state: UploadState) => {\n setUploads((prev) => prev.map((u) => (u.id === uploadId ? { ...u, state } : u)))\n }, [])\n\n const runEntry = useCallback(\n async (uploadId: string, file: UploadableFile, previous?: UploadState) => {\n const { adapter, assetRepo } = await studio()\n const result = await runUpload(\n {\n // Bytes ride on blob storage (transport); the metadata record is\n // confirmed through the `assets.upsert` operation command.\n blobs: adapter.blobs,\n assets: assetRepo,\n ...(typeof XMLHttpRequest !== 'undefined' ? { transport: xhrUploadTransport } : {}),\n },\n {\n projectId,\n file,\n onState: (state) => setUploadState(uploadId, state),\n },\n previous,\n )\n if (result.ok) {\n // Completed uploads leave the queue; the asset appears in the library.\n uploadFilesRef.current.delete(uploadId)\n setUploads((prev) => prev.filter((u) => u.id !== uploadId))\n await refresh()\n }\n },\n [studio, projectId, refresh, setUploadState],\n )\n\n const upload = useCallback(\n (files: Iterable<UploadableFile>) => {\n for (const file of files) {\n uploadSeq.current += 1\n const uploadId = `upload-${uploadSeq.current}`\n uploadFilesRef.current.set(uploadId, file)\n setUploads((prev) => [\n ...prev,\n { id: uploadId, filename: file.name, state: { phase: 'idle', progress: 0, attempt: 0 } },\n ])\n void runEntry(uploadId, file)\n }\n },\n [runEntry],\n )\n\n const retryUpload = useCallback(\n (uploadId: string) => {\n const file = uploadFilesRef.current.get(uploadId)\n const entry = uploads.find((u) => u.id === uploadId)\n if (!file || !entry || entry.state.phase !== 'failed') return\n void runEntry(uploadId, file, entry.state)\n },\n [runEntry, uploads],\n )\n\n const dismissUpload = useCallback((uploadId: string) => {\n uploadFilesRef.current.delete(uploadId)\n setUploads((prev) => prev.filter((u) => u.id !== uploadId))\n }, [])\n\n const usagesOf = useCallback(\n // Index-accurate (EC-202): inbound references from the EC-155 index via the\n // operations queries — the same machinery the References panel uses.\n async (assetId: string) => loadInboundAssetReferenceViews(projectId, assetId),\n [projectId],\n )\n\n const remove = useCallback(\n async (assetId: string, options?: { confirmInUse?: boolean }) => {\n const { adapter, assetRepo } = await studio()\n // Metadata removal goes through the `assets.remove` command (via the\n // facade); blob byte deletion stays on the adapter; the usage gate reads\n // the EC-155 reference index (`adapter.references`, EC-202).\n const result = await deleteAsset(\n { assets: assetRepo, blobs: adapter.blobs, references: adapter.references },\n projectId,\n assetId,\n options,\n )\n if (result.outcome === 'deleted' || result.outcome === 'not-found') await refresh()\n return result\n },\n [studio, projectId, refresh],\n )\n\n const updateMetadata = useCallback(\n async (assetId: string, patch: AssetMetadataPatch) => {\n // Metadata writes go through the `assets.upsert` command via the\n // operations facade (EC-141) — never straight at a repository.\n const { assetRepo } = await studio()\n const result = await updateAssetMetadata({ assets: assetRepo }, projectId, assetId, patch)\n if (result.ok) {\n // Patch the one updated record into local state rather than re-listing the\n // whole library (EC-301). A focal-point or alt edit must not trigger a full\n // `assets.list` round-trip — that re-list (plus the marker only moving once\n // it returned) was the focal-point picker's lag and query storm.\n const updated = result.asset\n setAssets((prev) => prev.map((a) => (a.id === assetId ? updated : a)))\n }\n return result\n },\n [studio, projectId],\n )\n\n const store = useMemo<AssetsStore>(\n () => ({\n projectId,\n status,\n ...(errorMessage !== undefined ? { errorMessage } : {}),\n assets,\n uploads,\n upload,\n retryUpload,\n dismissUpload,\n usagesOf,\n remove,\n updateMetadata,\n refresh,\n }),\n [\n projectId,\n status,\n errorMessage,\n assets,\n uploads,\n upload,\n retryUpload,\n dismissUpload,\n usagesOf,\n remove,\n updateMetadata,\n refresh,\n ],\n )\n\n return <AssetsContext.Provider value={store}>{children}</AssetsContext.Provider>\n}\n\nexport function useAssets(): AssetsStore {\n const store = useContext(AssetsContext)\n if (!store) throw new Error('useAssets must be used within an <AssetsProvider>.')\n return store\n}\n\n/**\n * The assets store when an `<AssetsProvider>` is mounted above, else `null`.\n * Lets surfaces outside the Assets section (the CMS asset picker, EC-152)\n * reuse an ambient provider or mount their own.\n */\nexport function useOptionalAssets(): AssetsStore | null {\n return useContext(AssetsContext)\n}\n"]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CanvasPerspective } from '@elytracms/studio/lib/data';
|
|
1
2
|
/**
|
|
2
3
|
* Iframe-isolated page preview (brief §8.2, EC-061, EC-064). The graph renders
|
|
3
4
|
* into a same-origin iframe via a React portal — true style isolation + true
|
|
@@ -5,4 +6,9 @@
|
|
|
5
6
|
* select and hover/selection draw outlines. Native listeners bridge the iframe
|
|
6
7
|
* boundary (React's synthetic events don't cross frames).
|
|
7
8
|
*/
|
|
8
|
-
export declare function BuilderCanvas(
|
|
9
|
+
export declare function BuilderCanvas({ perspective, }?: {
|
|
10
|
+
/** Resolve references against this perspective (EC-300/AD-16). The editable Draft
|
|
11
|
+
* view passes `'draft'` (a just-made reusable shows immediately); the read-only
|
|
12
|
+
* Published view passes `'published'` (honest preview of what delivery serves). */
|
|
13
|
+
perspective?: CanvasPerspective;
|
|
14
|
+
}): import("react").JSX.Element;
|
|
@@ -14,6 +14,8 @@ import { cmsBindingFallback, useStudioLocale } from '@elytracms/studio/lib/local
|
|
|
14
14
|
import { getInstantiatedKit } from '@elytracms/studio/lib/starters/kit-session';
|
|
15
15
|
import { getCanvasStylesheetUrl } from './canvas-stylesheet';
|
|
16
16
|
import { useBuilder } from './builder-store';
|
|
17
|
+
import { useReuse } from './reuse-context';
|
|
18
|
+
import { reuseEmbedRef } from './reuse';
|
|
17
19
|
import { useCanvasViewports, viewportWidth } from './canvas-breakpoints';
|
|
18
20
|
import { ClipboardNoticeOverlay, attachClipboardListeners, useStudioClipboard, } from './clipboard-actions';
|
|
19
21
|
import { createInlineEditSession, createInlineRichTextSession, findInlineTextHost, resolveInlineEditableProp, resolveInlineRichTextProp, } from './inline-edit';
|
|
@@ -108,7 +110,7 @@ function collectIds(node, out = new Set()) {
|
|
|
108
110
|
* select and hover/selection draw outlines. Native listeners bridge the iframe
|
|
109
111
|
* boundary (React's synthetic events don't cross frames).
|
|
110
112
|
*/
|
|
111
|
-
export function BuilderCanvas() {
|
|
113
|
+
export function BuilderCanvas({ perspective = 'draft', } = {}) {
|
|
112
114
|
const builder = useBuilder();
|
|
113
115
|
const { dispatch } = builder;
|
|
114
116
|
const studio = getStudioRegistry();
|
|
@@ -640,24 +642,44 @@ export function BuilderCanvas() {
|
|
|
640
642
|
// the value actually came from. Same decision function as the inspector's
|
|
641
643
|
// Content tab (`cmsBindingFallback`), so chip and label can never disagree.
|
|
642
644
|
const { locale: activeLocale } = useStudioLocale();
|
|
645
|
+
// EC-300: the reuse capability identifies a transcluding embed + its relation prop,
|
|
646
|
+
// so the canvas can detect a HARD reference whose target doesn't resolve and show a
|
|
647
|
+
// placeholder instead of an empty section. Null-safe (no provider / no capability).
|
|
648
|
+
const reuse = useReuse();
|
|
643
649
|
// EC-254: populate a block's `relation` prop (the editor's document pick — a
|
|
644
650
|
// ProductGrid's products, a Reusable's element) to delivery shape from the
|
|
645
651
|
// active content session, in the canvas's active locale, so the canvas previews
|
|
646
652
|
// picked documents exactly as the host renders them. The studio analog of the
|
|
647
653
|
// host's baked relations map. A missing/invisible target → null → the block's
|
|
648
654
|
// own empty state.
|
|
649
|
-
const resolveRelation = useCallback((ref) => resolveCanvasRelation(ref, activeLocale), [activeLocale]);
|
|
655
|
+
const resolveRelation = useCallback((ref) => resolveCanvasRelation(ref, activeLocale, perspective), [activeLocale, perspective]);
|
|
650
656
|
// EC-255: resolve a listing block's query (a ProductGrid keyed to a category) to
|
|
651
657
|
// its documents from the active content session, in the canvas's active locale, so
|
|
652
658
|
// the canvas previews a dynamic grid exactly as the host renders it. The studio
|
|
653
659
|
// analog of the host's baked listings map; an empty/invalid query → [] → the
|
|
654
660
|
// block's own empty state.
|
|
655
|
-
const resolveListing = useCallback((query) => resolveCanvasListing(query, activeLocale), [activeLocale]);
|
|
661
|
+
const resolveListing = useCallback((query) => resolveCanvasListing(query, activeLocale, perspective), [activeLocale, perspective]);
|
|
656
662
|
// EC-291: resolve a rich-text internal link (`{type:'reference'}`) to its target's
|
|
657
663
|
// URL path from the active content session, in the canvas's active locale — the
|
|
658
664
|
// studio analog of the host's baked links map. A missing/unpublished/unroutable
|
|
659
665
|
// target → null → the rich-text renderer shows the visible `unresolved-ref` issue.
|
|
660
|
-
const resolveLink = useCallback((ref) => resolveCanvasLink(ref, activeLocale), [activeLocale]);
|
|
666
|
+
const resolveLink = useCallback((ref) => resolveCanvasLink(ref, activeLocale, perspective), [activeLocale, perspective]);
|
|
667
|
+
// EC-300/AD-16: a transcluding embed whose target doesn't resolve in the active
|
|
668
|
+
// perspective renders a placeholder, never an empty section. `unpublished` = the
|
|
669
|
+
// target exists as a draft but isn't live (Published view of a draft-only reusable);
|
|
670
|
+
// `missing` = no resolvable target at all (deleted). `null` = resolves fine, or the
|
|
671
|
+
// node isn't an embed / has no target picked yet (its own empty state owns that).
|
|
672
|
+
const unresolvedReuse = useCallback((node) => {
|
|
673
|
+
const cap = reuse?.capability;
|
|
674
|
+
if (!cap)
|
|
675
|
+
return null;
|
|
676
|
+
const ref = reuseEmbedRef(node, cap);
|
|
677
|
+
if (!ref)
|
|
678
|
+
return null;
|
|
679
|
+
if (resolveCanvasRelation(ref, activeLocale, perspective))
|
|
680
|
+
return null;
|
|
681
|
+
return resolveCanvasRelation(ref, activeLocale, 'draft') ? 'unpublished' : 'missing';
|
|
682
|
+
}, [reuse, activeLocale, perspective]);
|
|
661
683
|
const localeFallbackOf = useCallback((node) => {
|
|
662
684
|
for (const value of Object.values(node.props ?? {})) {
|
|
663
685
|
if (value.kind !== 'binding')
|
|
@@ -676,8 +698,16 @@ export function BuilderCanvas() {
|
|
|
676
698
|
resolveRelation,
|
|
677
699
|
resolveListing,
|
|
678
700
|
resolveLink,
|
|
679
|
-
decorateNode: (node, rendered) =>
|
|
680
|
-
|
|
701
|
+
decorateNode: (node, rendered) => {
|
|
702
|
+
if (!pageNodeIds.has(node.id))
|
|
703
|
+
return rendered;
|
|
704
|
+
// An unresolved hard reference shows the placeholder in place of the empty
|
|
705
|
+
// transclusion; the node stays selectable, so its inspector relation chip
|
|
706
|
+
// (EC-296) deep-links to the reusable to edit/publish it.
|
|
707
|
+
const unresolved = unresolvedReuse(node);
|
|
708
|
+
return (_jsx("div", { "data-ec-node-id": node.id, className: "ec-editable", "data-selected": node.id === builder.selectedId || undefined, "data-hovered": node.id === builder.hoveredId || undefined, "data-ec-locale-fallback": localeFallbackOf(node), children: unresolved ? _jsx(UnresolvedReusablePlaceholder, { status: unresolved }) : rendered }, `${node.id}#${richEditEpochs[node.id] ?? 0}`));
|
|
709
|
+
},
|
|
710
|
+
}), [studio, resolveBinding, resolveAsset, resolveRelation, resolveListing, resolveLink, unresolvedReuse, pageNodeIds, builder.selectedId, builder.hoveredId, localeFallbackOf, richEditEpochs]);
|
|
681
711
|
// Tokenized spacing handles (EC-163): a selected Stack/Grid with 2+ children
|
|
682
712
|
// gets draggable gap handles between them, portaled into the iframe.
|
|
683
713
|
const spacingTarget = doc && hasSpacingHandles(builder.selectedNode) && pageNodeIds.has(builder.selectedNode.id)
|
|
@@ -694,4 +724,31 @@ export function BuilderCanvas() {
|
|
|
694
724
|
renderPageComposition(activeRoot, ctx), doc.body)
|
|
695
725
|
: null, spacingTarget && doc ? (_jsx(SpacingHandles, { doc: doc, node: spacingTarget }, spacingTarget.id)) : null, richEditHost && doc ? _jsx(RichTextToolbar, { doc: doc, host: richEditHost }) : null] })] }));
|
|
696
726
|
}
|
|
727
|
+
/**
|
|
728
|
+
* In-canvas placeholder for a transcluding embed whose target doesn't resolve
|
|
729
|
+
* (EC-300/AD-16) — shown in place of the empty section so a hard reference never
|
|
730
|
+
* silently vanishes. Rendered INTO the iframe (portal), which carries only the
|
|
731
|
+
* project's stylesheet, so it uses inline styles rather than studio UI classes.
|
|
732
|
+
* The node stays selectable; the inspector's `element` relation chip (EC-296)
|
|
733
|
+
* deep-links to the reusable to edit or publish it.
|
|
734
|
+
*/
|
|
735
|
+
function UnresolvedReusablePlaceholder({ status }) {
|
|
736
|
+
const title = status === 'unpublished'
|
|
737
|
+
? 'This reusable element isn’t published yet'
|
|
738
|
+
: 'This reusable element is missing';
|
|
739
|
+
const hint = status === 'unpublished'
|
|
740
|
+
? 'Publish it to show it here and on live pages — or open it from the Properties panel to keep editing.'
|
|
741
|
+
: 'Open the embed’s “Element” in the Properties panel to fix the reference.';
|
|
742
|
+
return (_jsxs("div", { style: {
|
|
743
|
+
boxSizing: 'border-box',
|
|
744
|
+
border: '1px dashed rgba(120,120,120,0.5)',
|
|
745
|
+
borderRadius: 8,
|
|
746
|
+
padding: '24px 28px',
|
|
747
|
+
margin: '12px 16px',
|
|
748
|
+
background: 'rgba(127,127,127,0.06)',
|
|
749
|
+
color: 'rgba(90,90,90,0.95)',
|
|
750
|
+
font: '14px/1.5 system-ui, -apple-system, sans-serif',
|
|
751
|
+
textAlign: 'center',
|
|
752
|
+
}, children: [_jsxs("div", { style: { fontWeight: 600 }, children: ["\u26A0 ", title] }), _jsx("div", { style: { marginTop: 6, fontSize: 13, opacity: 0.85 }, children: hint })] }));
|
|
753
|
+
}
|
|
697
754
|
//# sourceMappingURL=canvas.js.map
|