@elytracms/studio 0.0.24 → 0.0.26
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 +49 -7
- package/convex/delivery.ts +14 -16
- package/convex/references.ts +38 -2
- package/convex/schema.ts +6 -1
- package/dist/components/assets/asset-library.js +5 -2
- package/dist/components/assets/asset-library.js.map +1 -1
- package/dist/components/assets/assets-store.js +45 -30
- package/dist/components/assets/assets-store.js.map +1 -1
- package/dist/components/auth-context.d.ts +4 -2
- package/dist/components/auth-context.js +19 -19
- package/dist/components/auth-context.js.map +1 -1
- package/dist/components/cms/cms-store.js +5 -4
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/components/cms/collection-table.js +10 -3
- package/dist/components/cms/collection-table.js.map +1 -1
- package/dist/components/cms/detail-editor.js +36 -9
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/field-control.d.ts +2 -1
- package/dist/components/cms/field-control.js +166 -74
- package/dist/components/cms/field-control.js.map +1 -1
- package/dist/components/cms/versions-pane.js +28 -16
- package/dist/components/cms/versions-pane.js.map +1 -1
- package/dist/components/project-session.d.ts +11 -0
- package/dist/components/project-session.js.map +1 -1
- package/dist/components/routes/routes-shell.js +22 -22
- package/dist/components/routes/routes-shell.js.map +1 -1
- package/dist/components/studio-context.js +5 -1
- package/dist/components/studio-context.js.map +1 -1
- package/dist/lib/assets/delete-asset.js +7 -3
- package/dist/lib/assets/delete-asset.js.map +1 -1
- package/dist/lib/cms/published-documents.d.ts +6 -6
- package/dist/lib/cms/published-documents.js +21 -58
- package/dist/lib/cms/published-documents.js.map +1 -1
- package/dist/lib/cms-views/view-model.d.ts +7 -3
- package/dist/lib/cms-views/view-model.js +11 -5
- package/dist/lib/cms-views/view-model.js.map +1 -1
- package/dist/lib/data/collection-content.js +7 -5
- package/dist/lib/data/collection-content.js.map +1 -1
- package/dist/lib/data/data-store.js +34 -24
- package/dist/lib/data/data-store.js.map +1 -1
- package/dist/lib/persistence/convex-adapter.js +22 -0
- package/dist/lib/persistence/convex-adapter.js.map +1 -1
- package/dist/lib/persistence/index.js +6 -2
- package/dist/lib/persistence/index.js.map +1 -1
- package/dist/lib/query/index.d.ts +6 -0
- package/dist/lib/query/index.js +6 -0
- package/dist/lib/query/index.js.map +1 -0
- package/dist/lib/query/operations-query.d.ts +85 -0
- package/dist/lib/query/operations-query.js +124 -0
- package/dist/lib/query/operations-query.js.map +1 -0
- package/dist/lib/references/reference-model.js +7 -1
- package/dist/lib/references/reference-model.js.map +1 -1
- package/dist/lib/references/references-store.d.ts +5 -10
- package/dist/lib/references/references-store.js +61 -77
- package/dist/lib/references/references-store.js.map +1 -1
- package/dist/ui/collapsible-section.d.ts +20 -0
- package/dist/ui/collapsible-section.js +18 -0
- package/dist/ui/collapsible-section.js.map +1 -0
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +1 -0
- package/dist/ui/index.js.map +1 -1
- package/package.json +3 -2
package/convex/cms.ts
CHANGED
|
@@ -98,11 +98,15 @@ export const saveDocumentVersion = mutation({
|
|
|
98
98
|
},
|
|
99
99
|
handler: async (ctx, args) => {
|
|
100
100
|
await requireRole(ctx, 'editor') // EC-150
|
|
101
|
-
|
|
101
|
+
// EC-302: versions are appended monotonically, so the latest-created row holds
|
|
102
|
+
// the highest version. Read just that one (index-ordered, newest first) instead
|
|
103
|
+
// of collecting the whole — unbounded — history to compute `max+1`.
|
|
104
|
+
const latest = await ctx.db
|
|
102
105
|
.query('documentVersions')
|
|
103
106
|
.withIndex('by_doc', (q) => q.eq('collection', args.collection).eq('docId', args.docId))
|
|
104
|
-
.
|
|
105
|
-
|
|
107
|
+
.order('desc')
|
|
108
|
+
.first()
|
|
109
|
+
const version = (latest?.version ?? 0) + 1
|
|
106
110
|
const id = await ctx.db.insert('documentVersions', { ...args, version })
|
|
107
111
|
return ctx.db.get(id)
|
|
108
112
|
},
|
|
@@ -125,11 +129,49 @@ export const getDocumentVersion = query({
|
|
|
125
129
|
args: { collection: v.string(), docId: v.string(), version: v.number() },
|
|
126
130
|
handler: async (ctx, args) => {
|
|
127
131
|
await requireRole(ctx, 'viewer') // EC-150
|
|
128
|
-
|
|
132
|
+
// EC-307: address the row directly via `by_doc_version` instead of collecting
|
|
133
|
+
// the whole — unbounded — per-document history and finding in JS. `version` is
|
|
134
|
+
// unique per (collection, docId), so `.unique()` both fetches and asserts that.
|
|
135
|
+
return ctx.db
|
|
129
136
|
.query('documentVersions')
|
|
130
|
-
.withIndex('
|
|
131
|
-
|
|
132
|
-
|
|
137
|
+
.withIndex('by_doc_version', (q) =>
|
|
138
|
+
q.eq('collection', args.collection).eq('docId', args.docId).eq('version', args.version),
|
|
139
|
+
)
|
|
140
|
+
.unique()
|
|
141
|
+
},
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* The pinned published snapshot of every currently-published document (EC-307) —
|
|
146
|
+
* one bulk read collapsing the per-document `listVersions` fan-out the studio's
|
|
147
|
+
* published pool used to issue (`published-documents.ts`). Reads each published
|
|
148
|
+
* document's pinned version row directly via `by_doc_version`. Returns the
|
|
149
|
+
* `CmsDocument` snapshots (the live perspective); unpublished documents are absent.
|
|
150
|
+
*/
|
|
151
|
+
export const listPublishedDocuments = query({
|
|
152
|
+
args: { collection: v.optional(v.string()) },
|
|
153
|
+
handler: async (ctx, args) => {
|
|
154
|
+
await requireRole(ctx, 'viewer') // EC-150
|
|
155
|
+
const collection = args.collection
|
|
156
|
+
const docs = collection
|
|
157
|
+
? await ctx.db
|
|
158
|
+
.query('cmsDocuments')
|
|
159
|
+
.withIndex('by_collection', (q) => q.eq('collection', collection))
|
|
160
|
+
.collect()
|
|
161
|
+
: await ctx.db.query('cmsDocuments').collect()
|
|
162
|
+
const snapshots: unknown[] = []
|
|
163
|
+
for (const doc of docs) {
|
|
164
|
+
const version = doc.publishedVersion
|
|
165
|
+
if (version === undefined) continue
|
|
166
|
+
const pinned = await ctx.db
|
|
167
|
+
.query('documentVersions')
|
|
168
|
+
.withIndex('by_doc_version', (q) =>
|
|
169
|
+
q.eq('collection', doc.collection).eq('docId', doc.docId).eq('version', version),
|
|
170
|
+
)
|
|
171
|
+
.unique()
|
|
172
|
+
if (pinned) snapshots.push(pinned.snapshot)
|
|
173
|
+
}
|
|
174
|
+
return snapshots
|
|
133
175
|
},
|
|
134
176
|
})
|
|
135
177
|
|
package/convex/delivery.ts
CHANGED
|
@@ -136,25 +136,23 @@ export async function loadSnapshot(
|
|
|
136
136
|
if (perspective === 'draft') {
|
|
137
137
|
documents = documentRows.map((row) => row.document as unknown)
|
|
138
138
|
} else {
|
|
139
|
-
const versionsByKey = new Map<string, Map<number, unknown>>()
|
|
140
|
-
for (const row of await ctx.db.query('documentVersions').collect()) {
|
|
141
|
-
const key = `${row.collection}:${row.docId}`
|
|
142
|
-
let byVersion = versionsByKey.get(key)
|
|
143
|
-
if (!byVersion) {
|
|
144
|
-
byVersion = new Map()
|
|
145
|
-
versionsByKey.set(key, byVersion)
|
|
146
|
-
}
|
|
147
|
-
byVersion.set(row.version, row.snapshot as unknown)
|
|
148
|
-
}
|
|
149
139
|
documents = []
|
|
150
140
|
for (const row of documentRows) {
|
|
151
141
|
if (row.publishedVersion === undefined) continue
|
|
152
|
-
//
|
|
153
|
-
// (
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
142
|
+
// EC-308: read THIS document's pinned version directly via `by_doc_version`
|
|
143
|
+
// (EC-307) instead of collecting the entire append-only `documentVersions`
|
|
144
|
+
// table and joining in memory — the published snapshot now scales with the
|
|
145
|
+
// number of published docs, not with total edit history. Serve the pinned
|
|
146
|
+
// recorded version, NOT the live draft; a dangling pin (`.unique()` → null)
|
|
147
|
+
// is dropped, never falling back to the draft.
|
|
148
|
+
const version = row.publishedVersion
|
|
149
|
+
const pinned = await ctx.db
|
|
150
|
+
.query('documentVersions')
|
|
151
|
+
.withIndex('by_doc_version', (q) =>
|
|
152
|
+
q.eq('collection', row.collection).eq('docId', row.docId).eq('version', version),
|
|
153
|
+
)
|
|
154
|
+
.unique()
|
|
155
|
+
if (pinned) documents.push(pinned.snapshot as unknown)
|
|
158
156
|
}
|
|
159
157
|
}
|
|
160
158
|
|
package/convex/references.ts
CHANGED
|
@@ -187,11 +187,47 @@ export const inbound = query({
|
|
|
187
187
|
},
|
|
188
188
|
})
|
|
189
189
|
|
|
190
|
+
export const inboundMany = query({
|
|
191
|
+
args: {
|
|
192
|
+
targets: v.array(v.object({ targetType: targetTypeValidator, targetId: v.string() })),
|
|
193
|
+
variant: v.optional(variantValidator),
|
|
194
|
+
},
|
|
195
|
+
handler: async (ctx, args) => {
|
|
196
|
+
await requireRole(ctx, 'viewer') // EC-150
|
|
197
|
+
// EC-308: inbound entries for many targets in ONE round-trip — loop the
|
|
198
|
+
// `by_target` index server-side instead of the client issuing one `inbound`
|
|
199
|
+
// query per target (the bulk delete/unpublish pre-flight fan-out). One result
|
|
200
|
+
// list per target, in `targets` order.
|
|
201
|
+
const groups = []
|
|
202
|
+
for (const target of args.targets) {
|
|
203
|
+
const rows = await ctx.db
|
|
204
|
+
.query('referenceEntries')
|
|
205
|
+
.withIndex('by_target', (q) => {
|
|
206
|
+
const scoped = q.eq('targetType', target.targetType).eq('targetId', target.targetId)
|
|
207
|
+
return args.variant ? scoped.eq('variant', args.variant) : scoped
|
|
208
|
+
})
|
|
209
|
+
.collect()
|
|
210
|
+
groups.push(rows)
|
|
211
|
+
}
|
|
212
|
+
return groups
|
|
213
|
+
},
|
|
214
|
+
})
|
|
215
|
+
|
|
190
216
|
export const list = query({
|
|
191
217
|
args: { variant: v.optional(variantValidator) },
|
|
192
218
|
handler: async (ctx, args) => {
|
|
193
219
|
await requireRole(ctx, 'viewer') // EC-150
|
|
194
|
-
|
|
195
|
-
|
|
220
|
+
// EC-302: `by_source` leads with `variant`, so a variant filter is an index
|
|
221
|
+
// prefix — push it into the query instead of collecting the whole table and
|
|
222
|
+
// filtering in JS. Callers treat the result as an unordered set, so the shift
|
|
223
|
+
// from insertion order to index order is immaterial.
|
|
224
|
+
const variant = args.variant
|
|
225
|
+
if (variant !== undefined) {
|
|
226
|
+
return ctx.db
|
|
227
|
+
.query('referenceEntries')
|
|
228
|
+
.withIndex('by_source', (q) => q.eq('variant', variant))
|
|
229
|
+
.collect()
|
|
230
|
+
}
|
|
231
|
+
return ctx.db.query('referenceEntries').collect()
|
|
196
232
|
},
|
|
197
233
|
})
|
package/convex/schema.ts
CHANGED
|
@@ -108,7 +108,12 @@ export default defineSchema({
|
|
|
108
108
|
snapshot: v.any(),
|
|
109
109
|
label: v.optional(v.string()),
|
|
110
110
|
createdAt: v.string(),
|
|
111
|
-
})
|
|
111
|
+
})
|
|
112
|
+
.index('by_doc', ['collection', 'docId'])
|
|
113
|
+
// EC-307: address one recorded version directly (the hot restore/publish-pool
|
|
114
|
+
// path) instead of collecting a document's whole — unbounded — history and
|
|
115
|
+
// finding in JS. `version` is unique per (collection, docId), so a lookup is `.unique()`.
|
|
116
|
+
.index('by_doc_version', ['collection', 'docId', 'version']),
|
|
112
117
|
|
|
113
118
|
/** Asset metadata; bytes live in Convex/file storage behind the blob adapter (EC-044). */
|
|
114
119
|
assets: defineTable({
|
|
@@ -170,8 +170,11 @@ function DeleteAssetButton({ asset }) {
|
|
|
170
170
|
const confirm = async () => {
|
|
171
171
|
setBusy(true);
|
|
172
172
|
setError(null);
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
// The dialog has already enumerated usages (or shown there are none) and the user
|
|
174
|
+
// is confirming deletion either way — so confirm unconditionally, letting
|
|
175
|
+
// deleteAsset skip its redundant inbound re-check (EC-301 perf). Weak-by-default:
|
|
176
|
+
// a still-referenced asset deletes anyway, dangling into a visible issue.
|
|
177
|
+
const result = await store.remove(asset.id, { confirmInUse: true });
|
|
175
178
|
setBusy(false);
|
|
176
179
|
if (result.outcome === 'deleted' || result.outcome === 'not-found') {
|
|
177
180
|
setOpen(false);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"asset-library.js","sourceRoot":"","sources":["../../../src/components/assets/asset-library.tsx"],"names":[],"mappings":";AAAA,OAAO,EACL,aAAa,EACb,SAAS,EACT,KAAK,EACL,MAAM,EACN,SAAS,EACT,MAAM,EACN,MAAM,EACN,MAAM,EACN,CAAC,GACF,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAInD,OAAO,EAAE,yBAAyB,EAAE,MAAM,kCAAkC,CAAA;AAE5E,OAAO,EAAE,EAAE,EAAE,MAAM,6BAA6B,CAAA;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAChE,OAAO,EACL,KAAK,EACL,MAAM,EACN,IAAI,EACJ,MAAM,EACN,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,KAAK,EACL,IAAI,EACJ,OAAO,EACP,KAAK,EACL,KAAK,EACL,SAAS,EACT,WAAW,EACX,GAAG,EACH,MAAM,EACN,KAAK,EACL,IAAI,GACL,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAG1C;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAAC,EAC3B,SAAS,EACT,YAAY,GAIb;IACC,MAAM,KAAK,GAAG,SAAS,EAAE,CAAA;IACzB,MAAM,YAAY,GAAG,MAAM,CAA0B,IAAI,CAAC,CAAA;IAC1D,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC/C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAA;IAEtC,MAAM,MAAM,GAAG,CAAC,KAAoC,EAAE,EAAE;QACtD,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK;YAAE,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;QAC7D,KAAK,CAAC,MAAM,CAAC,KAAK,GAAG,EAAE,CAAA;IACzB,CAAC,CAAA;IAED,MAAM,MAAM,GAAG,CAAC,KAAgB,EAAE,EAAE;QAClC,KAAK,CAAC,cAAc,EAAE,CAAA;QACtB,WAAW,CAAC,KAAK,CAAC,CAAA;QAClB,IAAI,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;YAAE,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAA;IACtF,CAAC,CAAA;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;QAC7B,OAAO,CACL,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,SAAS,EAAC,KAAK,iBAAa,cAAc,aACvD,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,aACV,KAAC,aAAa,IAAC,SAAS,EAAC,yBAAyB,GAAG,EACrD,KAAC,IAAI,IAAC,MAAM,EAAC,UAAU,sCAA6B,IAChD,EACN,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,KAAK,CAAC,YAAY,IAAI,yCAAyC,GAC3D,EACP,KAAC,GAAG,cACF,MAAC,MAAM,IAAC,OAAO,EAAC,SAAS,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,KAAK,CAAC,OAAO,EAAE,aACrE,KAAC,SAAS,KAAG,cACN,GACL,IACA,CACT,CAAA;IACH,CAAC;IAED,OAAO,CACL,MAAC,KAAK,IACJ,GAAG,EAAC,GAAG,EACP,SAAS,EAAE,EAAE,CACX,mBAAmB,EACnB,QAAQ,IAAI,+DAA+D,CAC5E,EACD,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE;YACpB,KAAK,CAAC,cAAc,EAAE,CAAA;YACtB,WAAW,CAAC,IAAI,CAAC,CAAA;QACnB,CAAC,EACD,WAAW,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,EACrC,MAAM,EAAE,MAAM,iBACF,eAAe,aAE3B,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,aACV,KAAC,OAAO,IAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAC,IAAI,uBAElB,EACV,KAAC,KAAK,IAAC,OAAO,EAAC,SAAS,YAAE,KAAK,CAAC,MAAM,CAAC,MAAM,GAAS,EACtD,KAAC,MAAM,KAAG,EACV,KAAC,gBAAgB,IAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,GAAI,EACtD,gBACE,GAAG,EAAE,YAAY,EACjB,IAAI,EAAC,MAAM,EACX,QAAQ,QACR,SAAS,EAAC,QAAQ,EAClB,QAAQ,EAAE,MAAM,iBACJ,kBAAkB,GAC9B,EACF,MAAC,MAAM,IAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,KAAK,EAAE,aAC5D,KAAC,MAAM,KAAG,eACH,IACL,EAEN,KAAC,QAAQ,IAAC,OAAO,EAAE,QAAQ,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,GAAI,EAE3D,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAC1B,MAAC,KAAK,mBAAa,cAAc,aAC/B,KAAC,WAAW,cACV,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,MAAM,EAAC,UAAU,EAAC,IAAI,EAAC,OAAO,wBAEvC,GACK,EACd,KAAC,SAAS,IAAC,SAAS,EAAC,4BAA4B,YAC9C,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAC5B,KAAC,SAAS,IAER,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,EAC1C,SAAS,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,IAHzC,KAAK,CAAC,EAAE,CAIb,CACH,CAAC,GACQ,IACN,CACT,CAAC,CAAC,CAAC,IAAI,EAEP,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,CAC5B,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,qCAErB,CACR,CAAC,CAAC,CAAC,CACF,KAAC,SAAS,IACR,MAAM,EAAE,YAAY,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,EACzC,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EACjC,SAAS,EAAE,SAAS,EACpB,YAAY,EAAE,YAAY,GAC1B,CACH,IACK,CACT,CAAA;AACH,CAAC;AAED,mEAAmE;AACnE,SAAS,gBAAgB,CAAC,EACxB,KAAK,EACL,QAAQ,GAIT;IACC,OAAO,CACL,gBAAM,SAAS,EAAC,UAAU,aACxB,KAAC,MAAM,IAAC,SAAS,EAAC,+FAA+F,GAAG,EACpH,KAAC,KAAK,IACJ,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EACzC,WAAW,EAAC,qBAAgB,gBACjB,eAAe,EAC1B,SAAS,EAAC,WAAW,iBACT,cAAc,GAC1B,IACG,CACR,CAAA;AACH,CAAC;AAED,8DAA8D;AAC9D,SAAS,QAAQ,CAAC,EAAE,OAAO,EAAwB;IACjD,OAAO,CACL,MAAC,KAAK,IACJ,GAAG,EAAC,GAAG,EACP,KAAK,EAAC,QAAQ,EACd,SAAS,EAAE,EAAE,CACX,+DAA+D,EAC/D,CAAC,OAAO,IAAI,gBAAgB,CAC7B,aAED,KAAC,SAAS,IAAC,SAAS,EAAC,8BAA8B,GAAG,EACtD,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,yEAErB,IACD,CACT,CAAA;AACH,CAAC;AAED,MAAM,WAAW,GAAyC;IACxD,IAAI,EAAE,QAAQ;IACd,SAAS,EAAE,WAAW;IACtB,SAAS,EAAE,WAAW;IACtB,UAAU,EAAE,iBAAiB;IAC7B,IAAI,EAAE,MAAM;IACZ,MAAM,EAAE,QAAQ;CACjB,CAAA;AAED,mFAAmF;AACnF,SAAS,SAAS,CAAC,EACjB,KAAK,EACL,OAAO,EACP,SAAS,GAKV;IACC,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,CAAA;IACvB,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,KAAK,QAAQ,CAAA;IACvC,OAAO,CACL,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,SAAS,EAAC,WAAW,iBAAa,YAAY,aAC3D,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,aACV,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,MAAM,EAAC,QAAQ,EAAC,SAAS,EAAC,yBAAyB,YAChE,KAAK,CAAC,QAAQ,GACV,EACP,MAAC,KAAK,IAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,aAC3E,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,EACxB,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,cAAc,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IACjD,EACP,MAAM,CAAC,CAAC,CAAC,CACR,8BACG,KAAK,CAAC,OAAO,EAAE,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,CACpC,MAAC,MAAM,IAAC,OAAO,EAAC,SAAS,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,OAAO,iBAAc,cAAc,aAC9E,KAAC,SAAS,KAAG,cACN,CACV,CAAC,CAAC,CAAC,IAAI,EACR,KAAC,MAAM,IAAC,OAAO,EAAC,OAAO,EAAC,IAAI,EAAC,SAAS,EAAC,OAAO,EAAE,SAAS,gBAAa,gBAAgB,YACpF,KAAC,CAAC,KAAG,GACE,IACR,CACJ,CAAC,CAAC,CAAC,IAAI,IACJ,EACL,MAAM,CAAC,CAAC,CAAC,CACR,MAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,iBAAa,cAAc,aACpD,KAAK,CAAC,OAAO,EAAE,KAAK,QAAI,KAAK,CAAC,OAAO,EAAE,OAAO,IAC1C,CACR,CAAC,CAAC,CAAC,CACF,KAAC,WAAW,IAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,GAAI,CACvC,IACK,CACT,CAAA;AACH,CAAC;AAED,kDAAkD;AAClD,SAAS,WAAW,CAAC,EAAE,KAAK,EAAqB;IAC/C,OAAO,CACL,eAAM,SAAS,EAAC,0DAA0D,YACxE,eACE,SAAS,EAAC,yDAAyD,EACnE,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,EAAE,GAC/C,GACG,CACR,CAAA;AACH,CAAC;AAED,SAAS,SAAS,CAAC,EACjB,MAAM,EACN,QAAQ,EACR,SAAS,EACT,YAAY,GAMb;IACC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,yEAAyE;QACzE,8DAA8D;QAC9D,OAAO,QAAQ,CAAC,CAAC,CAAC,CAChB,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,iBAAa,oBAAoB,6CAEtD,CACR,CAAC,CAAC,CAAC,IAAI,CAAA;IACV,CAAC;IACD,OAAO,CACL,KAAC,IAAI,IAAC,YAAY,EAAC,OAAO,EAAC,GAAG,EAAC,GAAG,iBAAa,YAAY,YACxD,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CACrB,KAAC,SAAS,IAER,KAAK,EAAE,KAAK,EACZ,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,KAAK,CAAC,EAAE,KAAK,YAAY,IAH7B,KAAK,CAAC,EAAE,CAIb,CACH,CAAC,GACG,CACR,CAAA;AACH,CAAC;AAED,SAAS,WAAW,CAAC,QAAgB;IACnC,IAAI,QAAQ,GAAG,IAAI;QAAE,OAAO,GAAG,QAAQ,IAAI,CAAA;IAC3C,IAAI,QAAQ,GAAG,IAAI,GAAG,IAAI;QAAE,OAAO,GAAG,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAA;IACvE,OAAO,GAAG,CAAC,QAAQ,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAA;AACtD,CAAC;AAED,SAAS,SAAS,CAAC,EACjB,KAAK,EACL,SAAS,EACT,OAAO,GAMR;IACC,MAAM,OAAO,GAAG,MAAM,CAAwB,IAAI,CAAC,CAAA;IACnD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,OAAO;YAAE,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAA;IACvF,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAA;IACb,OAAO,CACL,cAAK,GAAG,EAAE,OAAO,YACf,MAAC,KAAK,IACJ,SAAS,EAAE,EAAE,CAAC,iBAAiB,EAAE,OAAO,IAAI,wBAAwB,CAAC,iBACzD,YAAY,aAExB,KAAC,cAAc,IAAC,KAAK,EAAE,KAAK,GAAI,EAChC,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,SAAS,EAAC,KAAK,aAC5B,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,MAAM,EAAC,QAAQ,EAAC,SAAS,EAAC,UAAU,EAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,YACvE,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,QAAQ,GACzB,EACN,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CACb,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,UAAU,YAC9C,KAAK,CAAC,QAAQ,GACV,CACR,CAAC,CAAC,CAAC,IAAI,EACR,KAAC,IAAI,IACH,IAAI,EAAC,IAAI,EACT,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,EACpC,SAAS,EAAC,UAAU,iBACR,WAAW,YAEtB,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,aAAa,GAC3C,EACP,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,IAAI,mBACf,KAAC,IAAI,IAAC,IAAI,EAAC,OAAO,YAAE,KAAK,CAAC,WAAW,GAAQ,EAC5C,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAC7B,MAAC,IAAI,IAAC,IAAI,EAAC,OAAO,aACf,KAAK,CAAC,KAAK,YAAG,KAAK,CAAC,MAAM,IACtB,CACR,CAAC,CAAC,CAAC,IAAI,EACR,KAAC,IAAI,IAAC,IAAI,EAAC,OAAO,YAAE,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAQ,IACnD,EACN,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,aACV,KAAC,IAAI,IAAC,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,yBAAyB,YACnD,KAAK,CAAC,EAAE,GACJ,EACP,KAAC,qBAAqB,IAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,GAAI,EAChF,KAAC,eAAe,IAAC,KAAK,EAAE,KAAK,GAAI,EACjC,KAAC,iBAAiB,IAAC,KAAK,EAAE,KAAK,GAAI,IAC/B,IACA,IACF,GACJ,CACP,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,qBAAqB,CAAC,EAC7B,KAAK,EACL,SAAS,EACT,QAAQ,GAKT;IACC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACvC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,QAAQ;YAAE,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7B,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;IACd,OAAO,CACL,8BACE,KAAC,MAAM,IACL,OAAO,EAAC,OAAO,EACf,IAAI,EAAC,SAAS,gBACF,iBAAiB,KAAK,CAAC,QAAQ,EAAE,EAC7C,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,iBAChB,kBAAkB,YAE9B,KAAC,KAAK,KAAG,GACF,EACT,KAAC,MAAM,IAAC,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,YACvC,MAAC,aAAa,IAAC,SAAS,EAAC,UAAU,aACjC,MAAC,YAAY,eACX,MAAC,WAAW,kCAAY,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,QAAQ,IAAe,EACpE,KAAC,iBAAiB,iGAEE,IACP,EACd,IAAI,CAAC,CAAC,CAAC,CACN,KAAC,eAAe,IAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,GAAI,CACnF,CAAC,CAAC,CAAC,IAAI,IACM,GACT,IACR,CACJ,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,eAAe,CAAC,EAAE,KAAK,EAA0B;IACxD,MAAM,KAAK,GAAG,SAAS,EAAE,CAAA;IACzB,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACvC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAA;IAClC,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAA;IACtC,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IACvD,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAEvC,MAAM,UAAU,GAAG,GAAG,EAAE;QACtB,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,EAAE,CAAC,CAAA;QACvB,QAAQ,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,CAAA;QAC3B,QAAQ,CAAC,IAAI,CAAC,CAAA;QACd,OAAO,CAAC,IAAI,CAAC,CAAA;IACf,CAAC,CAAA;IAED,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE;QACtB,OAAO,CAAC,IAAI,CAAC,CAAA;QACb,QAAQ,CAAC,IAAI,CAAC,CAAA;QACd,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAA;QACnE,OAAO,CAAC,KAAK,CAAC,CAAA;QACd,IAAI,MAAM,CAAC,EAAE;YAAE,OAAO,CAAC,KAAK,CAAC,CAAA;;YACxB,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IAC/B,CAAC,CAAA;IAED,OAAO,CACL,8BACE,KAAC,MAAM,IACL,OAAO,EAAC,OAAO,EACf,IAAI,EAAC,SAAS,gBACF,QAAQ,KAAK,CAAC,QAAQ,EAAE,EACpC,OAAO,EAAE,UAAU,iBACP,YAAY,YAExB,KAAC,MAAM,KAAG,GACH,EACT,KAAC,MAAM,IAAC,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,YACvC,MAAC,aAAa,IAAC,SAAS,EAAC,UAAU,aACjC,MAAC,YAAY,eACX,MAAC,WAAW,wBAAO,KAAK,CAAC,QAAQ,IAAe,EAChD,KAAC,iBAAiB,yHAGE,IACP,EACf,KAAC,KAAK,IAAC,KAAK,EAAC,UAAU,EAAC,OAAO,EAAE,aAAa,KAAK,CAAC,EAAE,EAAE,YACtD,KAAC,KAAK,IACJ,EAAE,EAAE,aAAa,KAAK,CAAC,EAAE,EAAE,EAC3B,KAAK,EAAE,GAAG,EACV,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EACvC,WAAW,EAAC,0BAAqB,iBACrB,gBAAgB,GAC5B,GACI,EACR,KAAC,KAAK,IAAC,KAAK,EAAC,OAAO,EAAC,OAAO,EAAE,eAAe,KAAK,CAAC,EAAE,EAAE,YACrD,KAAC,KAAK,IACJ,EAAE,EAAE,eAAe,KAAK,CAAC,EAAE,EAAE,EAC7B,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EACzC,WAAW,EAAE,KAAK,CAAC,QAAQ,iBACf,kBAAkB,GAC9B,GACI,EACP,KAAK,CAAC,CAAC,CAAC,CACP,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,SAAS,EAAC,kBAAkB,iBAAa,kBAAkB,YACxE,KAAK,GACD,CACR,CAAC,CAAC,CAAC,IAAI,EACR,MAAC,YAAY,eACX,KAAC,MAAM,IAAC,OAAO,EAAC,OAAO,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,IAAI,uBAEtE,EACT,KAAC,MAAM,IACL,IAAI,EAAC,IAAI,EACT,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,EAC1B,QAAQ,EAAE,IAAI,iBACF,iBAAiB,qBAGtB,IACI,IACD,GACT,IACR,CACJ,CAAA;AACH,CAAC;AAED,8EAA8E;AAC9E,SAAS,cAAc,CAAC,EAAE,KAAK,EAA0B;IACvD,MAAM,OAAO,GAAG,KAAK,CAAC,WAAW,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAA;IACtD,+EAA+E;IAC/E,MAAM,UAAU,GAAG,KAAK,CAAC,GAAG,EAAE,UAAU,CAAC,MAAM,CAAC,IAAI,KAAK,CAAA;IACzD,IAAI,OAAO,IAAI,KAAK,CAAC,GAAG,IAAI,UAAU,EAAE,CAAC;QACvC,OAAO,CACL,cACE,GAAG,EAAE,KAAK,CAAC,GAAG,EACd,GAAG,EAAE,KAAK,CAAC,GAAG,IAAI,KAAK,CAAC,QAAQ,EAChC,SAAS,EAAC,2CAA2C,GACrD,CACH,CAAA;IACH,CAAC;IACD,OAAO,CACL,eAAM,SAAS,EAAC,4EAA4E,YAC1F,KAAC,SAAS,IAAC,SAAS,EAAC,QAAQ,GAAG,GAC3B,CACR,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,iBAAiB,CAAC,EAAE,KAAK,EAA0B;IAC1D,MAAM,KAAK,GAAG,SAAS,EAAE,CAAA;IACzB,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACvC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAA8B,IAAI,CAAC,CAAA;IACvE,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IACvD,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAEvC,MAAM,UAAU,GAAG,KAAK,IAAI,EAAE;QAC5B,OAAO,CAAC,IAAI,CAAC,CAAA;QACb,QAAQ,CAAC,IAAI,CAAC,CAAA;QACd,SAAS,CAAC,IAAI,CAAC,CAAA;QACf,IAAI,CAAC;YACH,SAAS,CAAC,MAAM,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAA;QAC3C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,QAAQ,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;QAClE,CAAC;IACH,CAAC,CAAA;IAED,MAAM,OAAO,GAAG,KAAK,IAAI,EAAE;QACzB,OAAO,CAAC,IAAI,CAAC,CAAA;QACb,QAAQ,CAAC,IAAI,CAAC,CAAA;QACd,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,CAAA;QACvC,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,CAAA;QACpE,OAAO,CAAC,KAAK,CAAC,CAAA;QACd,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,IAAI,MAAM,CAAC,OAAO,KAAK,WAAW,EAAE,CAAC;YACnE,OAAO,CAAC,KAAK,CAAC,CAAA;QAChB,CAAC;aAAM,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YACxC,4EAA4E;YAC5E,QAAQ,CAAC,8BAA8B,MAAM,CAAC,UAAU,+BAA+B,CAAC,CAAA;QAC1F,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QAC1B,CAAC;IACH,CAAC,CAAA;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,yBAAyB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAChE,MAAM,UAAU,GAAG,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,CAAC,CAAA;IAC7C,OAAO,CACL,8BACE,KAAC,MAAM,IACL,OAAO,EAAC,OAAO,EACf,IAAI,EAAC,SAAS,gBACF,UAAU,KAAK,CAAC,QAAQ,EAAE,EACtC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,UAAU,EAAE,iBACpB,cAAc,YAE1B,KAAC,MAAM,KAAG,GACH,EACT,KAAC,MAAM,IAAC,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,YACvC,MAAC,aAAa,IAAC,SAAS,EAAC,uCAAuC,aAC9D,MAAC,YAAY,eACX,MAAC,WAAW,0BAAS,KAAK,CAAC,QAAQ,SAAgB,EACnD,KAAC,iBAAiB,2FAEE,IACP,EACd,MAAM,KAAK,IAAI,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,CACnC,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,0CAErB,CACR,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,CACnB,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,iBAAa,qBAAqB,aAC9C,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,aACV,KAAC,aAAa,IAAC,SAAS,EAAC,qBAAqB,GAAG,EACjD,MAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,MAAM,EAAC,UAAU,2BACpB,UAAU,YAAQ,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,IACnD,IACH,EACN,KAAC,mBAAmB,IAClB,SAAS,EAAE,KAAK,CAAC,SAAS,EAC1B,MAAM,EAAE,MAAO,CAAC,MAAM,EACtB,UAAU,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,GAChC,EACF,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,2GAGrB,IACD,CACT,CAAC,CAAC,CAAC,CACF,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,qDAErB,CACR,EACA,KAAK,CAAC,CAAC,CAAC,CACP,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,SAAS,EAAC,kBAAkB,iBAAa,oBAAoB,YAC1E,KAAK,GACD,CACR,CAAC,CAAC,CAAC,IAAI,EACR,MAAC,YAAY,eACX,KAAC,MAAM,IAAC,OAAO,EAAC,OAAO,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,IAAI,uBAEtE,EACT,KAAC,MAAM,IACL,OAAO,EAAC,aAAa,EACrB,IAAI,EAAC,IAAI,EACT,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,OAAO,EAAE,EAC7B,QAAQ,EAAE,IAAI,IAAI,MAAM,KAAK,IAAI,iBACrB,sBAAsB,YAEjC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,QAAQ,GACrC,IACI,IACD,GACT,IACR,CACJ,CAAA;AACH,CAAC","sourcesContent":["import {\n AlertTriangle,\n ImageIcon,\n Link2,\n Pencil,\n RotateCcw,\n Search,\n Trash2,\n Upload,\n X,\n} from 'lucide-react'\nimport { useEffect, useRef, useState } from 'react'\nimport type { ChangeEvent, DragEvent } from 'react'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport type { UploadState } from '@elytracms/studio/lib/assets'\nimport { buildAssetReferenceImpact } from '@elytracms/studio/lib/references'\nimport type { ReferenceEntryView } from '@elytracms/studio/lib/references'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { ReferenceImpactRows } from '../cms/reference-confirm'\nimport { ReferencesPanel } from '../references/references-panel'\nimport {\n Badge,\n Button,\n Code,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Field,\n Grid,\n Heading,\n Input,\n Panel,\n PanelBody,\n PanelHeader,\n Row,\n Spacer,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { filterAssets } from './asset-search'\nimport { useAssets } from './assets-store'\nimport type { UploadEntry } from './assets-store'\n\n/**\n * Asset library surface (EC-151, EC-152): file picker + drag-and-drop uploads\n * with visible progress/failure/retry, a searchable grid of stored assets\n * (filename/alt/title), metadata editing (alt, title — persisted through the\n * operations asset facade), and reference-respecting deletion (usage count +\n * block-or-confirm). All storage access goes through the persistence adapter\n * via the assets store. Each card answers \"where is this asset used?\" via the\n * shared references panel (EC-172); `focusAssetId` (the `?asset=` deep-link\n * param) scrolls that card into view and opens its references dialog.\n */\nexport function AssetLibrary({\n projectId,\n focusAssetId,\n}: {\n projectId: string\n focusAssetId?: string\n}) {\n const store = useAssets()\n const fileInputRef = useRef<HTMLInputElement | null>(null)\n const [dragging, setDragging] = useState(false)\n const [query, setQuery] = useState('')\n\n const onPick = (event: ChangeEvent<HTMLInputElement>) => {\n if (event.target.files) store.upload([...event.target.files])\n event.target.value = ''\n }\n\n const onDrop = (event: DragEvent) => {\n event.preventDefault()\n setDragging(false)\n if (event.dataTransfer.files.length > 0) store.upload([...event.dataTransfer.files])\n }\n\n if (store.status === 'error') {\n return (\n <Stack gap=\"2\" className=\"p-6\" data-testid=\"assets-error\">\n <Row gap=\"2\">\n <AlertTriangle className=\"size-4 text-destructive\" />\n <Text weight=\"semibold\">Assets failed to load</Text>\n </Row>\n <Text size=\"sm\" tone=\"muted\">\n {store.errorMessage ?? 'The persistence backend is unreachable.'}\n </Text>\n <Row>\n <Button variant=\"outline\" size=\"sm\" onClick={() => void store.refresh()}>\n <RotateCcw /> Retry\n </Button>\n </Row>\n </Stack>\n )\n }\n\n return (\n <Stack\n gap=\"4\"\n className={cn(\n 'flex-1 p-4 sm:p-6',\n dragging && 'outline-2 outline-dashed outline-primary/60 -outline-offset-4',\n )}\n onDragOver={(event) => {\n event.preventDefault()\n setDragging(true)\n }}\n onDragLeave={() => setDragging(false)}\n onDrop={onDrop}\n data-testid=\"asset-library\"\n >\n <Row gap=\"2\">\n <Heading level={2} size=\"h2\">\n Assets\n </Heading>\n <Badge variant=\"outline\">{store.assets.length}</Badge>\n <Spacer />\n <AssetSearchInput value={query} onChange={setQuery} />\n <input\n ref={fileInputRef}\n type=\"file\"\n multiple\n className=\"hidden\"\n onChange={onPick}\n data-testid=\"asset-file-input\"\n />\n <Button size=\"sm\" onClick={() => fileInputRef.current?.click()}>\n <Upload /> Upload\n </Button>\n </Row>\n\n <DropHint visible={dragging || store.assets.length === 0} />\n\n {store.uploads.length > 0 ? (\n <Panel data-testid=\"upload-queue\">\n <PanelHeader>\n <Text size=\"xs\" weight=\"semibold\" tone=\"muted\">\n Uploads\n </Text>\n </PanelHeader>\n <PanelBody className=\"divide-y divide-border p-0\">\n {store.uploads.map((entry) => (\n <UploadRow\n key={entry.id}\n entry={entry}\n onRetry={() => store.retryUpload(entry.id)}\n onDismiss={() => store.dismissUpload(entry.id)}\n />\n ))}\n </PanelBody>\n </Panel>\n ) : null}\n\n {store.status === 'loading' ? (\n <Text size=\"sm\" tone=\"muted\">\n Loading assets…\n </Text>\n ) : (\n <AssetGrid\n assets={filterAssets(store.assets, query)}\n filtered={query.trim().length > 0}\n projectId={projectId}\n focusAssetId={focusAssetId}\n />\n )}\n </Stack>\n )\n}\n\n/** Named search primitive over filename / alt / title (EC-152). */\nfunction AssetSearchInput({\n value,\n onChange,\n}: {\n value: string\n onChange: (value: string) => void\n}) {\n return (\n <span className=\"relative\">\n <Search className=\"pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n <Input\n value={value}\n onChange={(e) => onChange(e.target.value)}\n placeholder=\"Search assets…\"\n aria-label=\"Search assets\"\n className=\"w-56 pl-8\"\n data-testid=\"asset-search\"\n />\n </span>\n )\n}\n\n/** Persistent drop affordance; doubles as the empty state. */\nfunction DropHint({ visible }: { visible: boolean }) {\n return (\n <Stack\n gap=\"1\"\n align=\"center\"\n className={cn(\n 'rounded-lg border border-dashed border-border p-6 text-center',\n !visible && 'hidden sm:flex',\n )}\n >\n <ImageIcon className=\"size-5 text-muted-foreground\" />\n <Text size=\"sm\" tone=\"muted\">\n Drop files anywhere to upload, or use the Upload button.\n </Text>\n </Stack>\n )\n}\n\nconst PHASE_LABEL: Record<UploadState['phase'], string> = {\n idle: 'queued',\n preparing: 'preparing',\n uploading: 'uploading',\n confirming: 'saving metadata',\n done: 'done',\n failed: 'failed',\n}\n\n/** One in-flight upload: progress while transferring, explicit failure + retry. */\nfunction UploadRow({\n entry,\n onRetry,\n onDismiss,\n}: {\n entry: UploadEntry\n onRetry: () => void\n onDismiss: () => void\n}) {\n const { state } = entry\n const failed = state.phase === 'failed'\n return (\n <Stack gap=\"1\" className=\"px-3 py-2\" data-testid=\"upload-row\">\n <Row gap=\"2\">\n <Text size=\"sm\" weight=\"medium\" className=\"min-w-0 flex-1 truncate\">\n {entry.filename}\n </Text>\n <Badge variant={failed ? 'error' : state.phase === 'done' ? 'success' : 'info'}>\n {PHASE_LABEL[state.phase]}\n {state.attempt > 1 ? ` · attempt ${state.attempt}` : ''}\n </Badge>\n {failed ? (\n <>\n {state.failure?.retryable !== false ? (\n <Button variant=\"outline\" size=\"sm\" onClick={onRetry} data-testid=\"upload-retry\">\n <RotateCcw /> Retry\n </Button>\n ) : null}\n <Button variant=\"ghost\" size=\"icon-sm\" onClick={onDismiss} aria-label=\"Dismiss upload\">\n <X />\n </Button>\n </>\n ) : null}\n </Row>\n {failed ? (\n <Text size=\"xs\" tone=\"muted\" data-testid=\"upload-error\">\n {state.failure?.stage}: {state.failure?.message}\n </Text>\n ) : (\n <ProgressBar value={state.progress} />\n )}\n </Stack>\n )\n}\n\n/** Named progress primitive for byte transfer. */\nfunction ProgressBar({ value }: { value: number }) {\n return (\n <span className=\"block h-1.5 w-full overflow-hidden rounded-full bg-muted\">\n <span\n className=\"block h-full rounded-full bg-primary transition-[width]\"\n style={{ width: `${Math.round(value * 100)}%` }}\n />\n </span>\n )\n}\n\nfunction AssetGrid({\n assets,\n filtered,\n projectId,\n focusAssetId,\n}: {\n assets: readonly AssetRecord[]\n filtered: boolean\n projectId: string\n focusAssetId?: string\n}) {\n if (assets.length === 0) {\n // The unfiltered empty state is the DropHint above; the *filtered* empty\n // state must say so explicitly (deterministic, reproducible).\n return filtered ? (\n <Text size=\"sm\" tone=\"muted\" data-testid=\"asset-search-empty\">\n No assets match your search.\n </Text>\n ) : null\n }\n return (\n <Grid minItemWidth=\"220px\" gap=\"3\" data-testid=\"asset-grid\">\n {assets.map((asset) => (\n <AssetCard\n key={asset.id}\n asset={asset}\n projectId={projectId}\n focused={asset.id === focusAssetId}\n />\n ))}\n </Grid>\n )\n}\n\nfunction formatBytes(byteSize: number): string {\n if (byteSize < 1024) return `${byteSize} B`\n if (byteSize < 1024 * 1024) return `${(byteSize / 1024).toFixed(1)} KB`\n return `${(byteSize / (1024 * 1024)).toFixed(1)} MB`\n}\n\nfunction AssetCard({\n asset,\n projectId,\n focused,\n}: {\n asset: AssetRecord\n projectId: string\n /** Deep-link target (`?asset=`): scroll into view + open the references dialog. */\n focused?: boolean\n}) {\n const cardRef = useRef<HTMLDivElement | null>(null)\n useEffect(() => {\n if (focused) cardRef.current?.scrollIntoView({ block: 'center', behavior: 'smooth' })\n }, [focused])\n return (\n <div ref={cardRef}>\n <Panel\n className={cn('overflow-hidden', focused && 'ring-2 ring-primary/70')}\n data-testid=\"asset-card\"\n >\n <AssetThumbnail asset={asset} />\n <Stack gap=\"1\" className=\"p-3\">\n <Text size=\"sm\" weight=\"medium\" className=\"truncate\" title={asset.filename}>\n {asset.title ?? asset.filename}\n </Text>\n {asset.title ? (\n <Text size=\"xs\" tone=\"muted\" className=\"truncate\">\n {asset.filename}\n </Text>\n ) : null}\n <Text\n size=\"xs\"\n tone={asset.alt ? 'muted' : 'subtle'}\n className=\"truncate\"\n data-testid=\"asset-alt\"\n >\n {asset.alt ? `Alt: ${asset.alt}` : 'No alt text'}\n </Text>\n <Row gap=\"1\" wrap>\n <Code tone=\"muted\">{asset.contentType}</Code>\n {asset.width && asset.height ? (\n <Code tone=\"muted\">\n {asset.width}×{asset.height}\n </Code>\n ) : null}\n <Code tone=\"muted\">{formatBytes(asset.byteSize)}</Code>\n </Row>\n <Row gap=\"1\">\n <Code tone=\"muted\" className=\"min-w-0 flex-1 truncate\">\n {asset.id}\n </Code>\n <AssetReferencesButton asset={asset} projectId={projectId} autoOpen={focused} />\n <EditAssetButton asset={asset} />\n <DeleteAssetButton asset={asset} />\n </Row>\n </Stack>\n </Panel>\n </div>\n )\n}\n\n/**\n * \"Where is this asset used?\" (EC-172): the shared references panel, inbound\n * over the EC-155 index, hosted in a dialog per asset card.\n */\nfunction AssetReferencesButton({\n asset,\n projectId,\n autoOpen,\n}: {\n asset: AssetRecord\n projectId: string\n autoOpen?: boolean\n}) {\n const [open, setOpen] = useState(false)\n useEffect(() => {\n if (autoOpen) setOpen(true)\n }, [autoOpen])\n return (\n <>\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={`References of ${asset.filename}`}\n onClick={() => setOpen(true)}\n data-testid=\"asset-references\"\n >\n <Link2 />\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"max-w-lg\">\n <DialogHeader>\n <DialogTitle>Used by — {asset.title ?? asset.filename}</DialogTitle>\n <DialogDescription>\n Everything referencing this asset, with deep links to the exact field or node.\n </DialogDescription>\n </DialogHeader>\n {open ? (\n <ReferencesPanel projectId={projectId} target={{ type: 'asset', id: asset.id }} />\n ) : null}\n </DialogContent>\n </Dialog>\n </>\n )\n}\n\n/**\n * Metadata editing (EC-152): alt + title, persisted through the operations\n * asset facade (`assets.upsert`). Failures render in the dialog — never a\n * silent loss.\n */\nfunction EditAssetButton({ asset }: { asset: AssetRecord }) {\n const store = useAssets()\n const [open, setOpen] = useState(false)\n const [alt, setAlt] = useState('')\n const [title, setTitle] = useState('')\n const [error, setError] = useState<string | null>(null)\n const [busy, setBusy] = useState(false)\n\n const openDialog = () => {\n setAlt(asset.alt ?? '')\n setTitle(asset.title ?? '')\n setError(null)\n setOpen(true)\n }\n\n const save = async () => {\n setBusy(true)\n setError(null)\n const result = await store.updateMetadata(asset.id, { alt, title })\n setBusy(false)\n if (result.ok) setOpen(false)\n else setError(result.message)\n }\n\n return (\n <>\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={`Edit ${asset.filename}`}\n onClick={openDialog}\n data-testid=\"asset-edit\"\n >\n <Pencil />\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle>Edit {asset.filename}</DialogTitle>\n <DialogDescription>\n Alt text flows through the delivery shape into rendered markup; the title is the\n library display name.\n </DialogDescription>\n </DialogHeader>\n <Field label=\"Alt text\" htmlFor={`asset-alt-${asset.id}`}>\n <Input\n id={`asset-alt-${asset.id}`}\n value={alt}\n onChange={(e) => setAlt(e.target.value)}\n placeholder=\"Describe the image…\"\n data-testid=\"asset-edit-alt\"\n />\n </Field>\n <Field label=\"Title\" htmlFor={`asset-title-${asset.id}`}>\n <Input\n id={`asset-title-${asset.id}`}\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n placeholder={asset.filename}\n data-testid=\"asset-edit-title\"\n />\n </Field>\n {error ? (\n <Text size=\"sm\" className=\"text-destructive\" data-testid=\"asset-edit-error\">\n {error}\n </Text>\n ) : null}\n <DialogFooter>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => setOpen(false)} disabled={busy}>\n Cancel\n </Button>\n <Button\n size=\"sm\"\n onClick={() => void save()}\n disabled={busy}\n data-testid=\"asset-edit-save\"\n >\n Save\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </>\n )\n}\n\n/** Image preview from the stored serve URL; icon fallback for other types. */\nfunction AssetThumbnail({ asset }: { asset: AssetRecord }) {\n const isImage = asset.contentType.startsWith('image/')\n // In-memory/opaque locators (memory://, convex://) are not fetchable by <img>.\n const resolvable = asset.url?.startsWith('http') ?? false\n if (isImage && asset.url && resolvable) {\n return (\n <img\n src={asset.url}\n alt={asset.alt ?? asset.filename}\n className=\"aspect-video w-full bg-muted object-cover\"\n />\n )\n }\n return (\n <span className=\"grid aspect-video w-full place-items-center bg-muted text-muted-foreground\">\n <ImageIcon className=\"size-6\" />\n </span>\n )\n}\n\n/**\n * Reference-respecting delete (EC-151, EC-202): the dialog enumerates the exact\n * inbound references — index-accurate (EC-155), with deep links to the field or\n * node — using the shared EC-173 impact rows. Deleting a referenced asset is\n * weak-by-default (allowed, with an explicit \"delete anyway\"): every reference\n * dangles into a visible validation issue rather than silently breaking.\n */\nfunction DeleteAssetButton({ asset }: { asset: AssetRecord }) {\n const store = useAssets()\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 store.usagesOf(asset.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 const inUse = (usages?.length ?? 0) > 0\n const result = await store.remove(asset.id, { confirmInUse: inUse })\n setBusy(false)\n if (result.outcome === 'deleted' || result.outcome === 'not-found') {\n setOpen(false)\n } else if (result.outcome === 'blocked') {\n // A reference appeared between the pre-flight read and confirm (rare race).\n setError(`Asset is now referenced in ${result.usageCount} place(s) — reopen to review.`)\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=\"ghost\"\n size=\"icon-sm\"\n aria-label={`Delete ${asset.filename}`}\n onClick={() => void openDialog()}\n data-testid=\"asset-delete\"\n >\n <Trash2 />\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"max-h-[80vh] max-w-md overflow-y-auto\">\n <DialogHeader>\n <DialogTitle>Delete {asset.filename}?</DialogTitle>\n <DialogDescription>\n Removes the stored bytes and the metadata record. This cannot be undone.\n </DialogDescription>\n </DialogHeader>\n {usages === null && error === null ? (\n <Text size=\"sm\" tone=\"muted\">\n Checking references…\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 In use in {usageCount} place{usageCount === 1 ? '' : 's'}\n </Text>\n </Row>\n <ReferenceImpactRows\n projectId={store.projectId}\n broken={impact!.broken}\n onNavigate={() => setOpen(false)}\n />\n <Text size=\"xs\" tone=\"muted\">\n Deleting will leave these references broken — they will surface as validation\n issues.\n </Text>\n </Stack>\n ) : (\n <Text size=\"sm\" tone=\"muted\">\n No references found. Safe to delete.\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 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 ? 'Delete anyway' : 'Delete'}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"asset-library.js","sourceRoot":"","sources":["../../../src/components/assets/asset-library.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n AlertTriangle,\n ImageIcon,\n Link2,\n Pencil,\n RotateCcw,\n Search,\n Trash2,\n Upload,\n X,\n} from 'lucide-react'\nimport { useEffect, useRef, useState } from 'react'\nimport type { ChangeEvent, DragEvent } from 'react'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport type { UploadState } from '@elytracms/studio/lib/assets'\nimport { buildAssetReferenceImpact } from '@elytracms/studio/lib/references'\nimport type { ReferenceEntryView } from '@elytracms/studio/lib/references'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { ReferenceImpactRows } from '../cms/reference-confirm'\nimport { ReferencesPanel } from '../references/references-panel'\nimport {\n Badge,\n Button,\n Code,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Field,\n Grid,\n Heading,\n Input,\n Panel,\n PanelBody,\n PanelHeader,\n Row,\n Spacer,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { filterAssets } from './asset-search'\nimport { useAssets } from './assets-store'\nimport type { UploadEntry } from './assets-store'\n\n/**\n * Asset library surface (EC-151, EC-152): file picker + drag-and-drop uploads\n * with visible progress/failure/retry, a searchable grid of stored assets\n * (filename/alt/title), metadata editing (alt, title — persisted through the\n * operations asset facade), and reference-respecting deletion (usage count +\n * block-or-confirm). All storage access goes through the persistence adapter\n * via the assets store. Each card answers \"where is this asset used?\" via the\n * shared references panel (EC-172); `focusAssetId` (the `?asset=` deep-link\n * param) scrolls that card into view and opens its references dialog.\n */\nexport function AssetLibrary({\n projectId,\n focusAssetId,\n}: {\n projectId: string\n focusAssetId?: string\n}) {\n const store = useAssets()\n const fileInputRef = useRef<HTMLInputElement | null>(null)\n const [dragging, setDragging] = useState(false)\n const [query, setQuery] = useState('')\n\n const onPick = (event: ChangeEvent<HTMLInputElement>) => {\n if (event.target.files) store.upload([...event.target.files])\n event.target.value = ''\n }\n\n const onDrop = (event: DragEvent) => {\n event.preventDefault()\n setDragging(false)\n if (event.dataTransfer.files.length > 0) store.upload([...event.dataTransfer.files])\n }\n\n if (store.status === 'error') {\n return (\n <Stack gap=\"2\" className=\"p-6\" data-testid=\"assets-error\">\n <Row gap=\"2\">\n <AlertTriangle className=\"size-4 text-destructive\" />\n <Text weight=\"semibold\">Assets failed to load</Text>\n </Row>\n <Text size=\"sm\" tone=\"muted\">\n {store.errorMessage ?? 'The persistence backend is unreachable.'}\n </Text>\n <Row>\n <Button variant=\"outline\" size=\"sm\" onClick={() => void store.refresh()}>\n <RotateCcw /> Retry\n </Button>\n </Row>\n </Stack>\n )\n }\n\n return (\n <Stack\n gap=\"4\"\n className={cn(\n 'flex-1 p-4 sm:p-6',\n dragging && 'outline-2 outline-dashed outline-primary/60 -outline-offset-4',\n )}\n onDragOver={(event) => {\n event.preventDefault()\n setDragging(true)\n }}\n onDragLeave={() => setDragging(false)}\n onDrop={onDrop}\n data-testid=\"asset-library\"\n >\n <Row gap=\"2\">\n <Heading level={2} size=\"h2\">\n Assets\n </Heading>\n <Badge variant=\"outline\">{store.assets.length}</Badge>\n <Spacer />\n <AssetSearchInput value={query} onChange={setQuery} />\n <input\n ref={fileInputRef}\n type=\"file\"\n multiple\n className=\"hidden\"\n onChange={onPick}\n data-testid=\"asset-file-input\"\n />\n <Button size=\"sm\" onClick={() => fileInputRef.current?.click()}>\n <Upload /> Upload\n </Button>\n </Row>\n\n <DropHint visible={dragging || store.assets.length === 0} />\n\n {store.uploads.length > 0 ? (\n <Panel data-testid=\"upload-queue\">\n <PanelHeader>\n <Text size=\"xs\" weight=\"semibold\" tone=\"muted\">\n Uploads\n </Text>\n </PanelHeader>\n <PanelBody className=\"divide-y divide-border p-0\">\n {store.uploads.map((entry) => (\n <UploadRow\n key={entry.id}\n entry={entry}\n onRetry={() => store.retryUpload(entry.id)}\n onDismiss={() => store.dismissUpload(entry.id)}\n />\n ))}\n </PanelBody>\n </Panel>\n ) : null}\n\n {store.status === 'loading' ? (\n <Text size=\"sm\" tone=\"muted\">\n Loading assets…\n </Text>\n ) : (\n <AssetGrid\n assets={filterAssets(store.assets, query)}\n filtered={query.trim().length > 0}\n projectId={projectId}\n focusAssetId={focusAssetId}\n />\n )}\n </Stack>\n )\n}\n\n/** Named search primitive over filename / alt / title (EC-152). */\nfunction AssetSearchInput({\n value,\n onChange,\n}: {\n value: string\n onChange: (value: string) => void\n}) {\n return (\n <span className=\"relative\">\n <Search className=\"pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n <Input\n value={value}\n onChange={(e) => onChange(e.target.value)}\n placeholder=\"Search assets…\"\n aria-label=\"Search assets\"\n className=\"w-56 pl-8\"\n data-testid=\"asset-search\"\n />\n </span>\n )\n}\n\n/** Persistent drop affordance; doubles as the empty state. */\nfunction DropHint({ visible }: { visible: boolean }) {\n return (\n <Stack\n gap=\"1\"\n align=\"center\"\n className={cn(\n 'rounded-lg border border-dashed border-border p-6 text-center',\n !visible && 'hidden sm:flex',\n )}\n >\n <ImageIcon className=\"size-5 text-muted-foreground\" />\n <Text size=\"sm\" tone=\"muted\">\n Drop files anywhere to upload, or use the Upload button.\n </Text>\n </Stack>\n )\n}\n\nconst PHASE_LABEL: Record<UploadState['phase'], string> = {\n idle: 'queued',\n preparing: 'preparing',\n uploading: 'uploading',\n confirming: 'saving metadata',\n done: 'done',\n failed: 'failed',\n}\n\n/** One in-flight upload: progress while transferring, explicit failure + retry. */\nfunction UploadRow({\n entry,\n onRetry,\n onDismiss,\n}: {\n entry: UploadEntry\n onRetry: () => void\n onDismiss: () => void\n}) {\n const { state } = entry\n const failed = state.phase === 'failed'\n return (\n <Stack gap=\"1\" className=\"px-3 py-2\" data-testid=\"upload-row\">\n <Row gap=\"2\">\n <Text size=\"sm\" weight=\"medium\" className=\"min-w-0 flex-1 truncate\">\n {entry.filename}\n </Text>\n <Badge variant={failed ? 'error' : state.phase === 'done' ? 'success' : 'info'}>\n {PHASE_LABEL[state.phase]}\n {state.attempt > 1 ? ` · attempt ${state.attempt}` : ''}\n </Badge>\n {failed ? (\n <>\n {state.failure?.retryable !== false ? (\n <Button variant=\"outline\" size=\"sm\" onClick={onRetry} data-testid=\"upload-retry\">\n <RotateCcw /> Retry\n </Button>\n ) : null}\n <Button variant=\"ghost\" size=\"icon-sm\" onClick={onDismiss} aria-label=\"Dismiss upload\">\n <X />\n </Button>\n </>\n ) : null}\n </Row>\n {failed ? (\n <Text size=\"xs\" tone=\"muted\" data-testid=\"upload-error\">\n {state.failure?.stage}: {state.failure?.message}\n </Text>\n ) : (\n <ProgressBar value={state.progress} />\n )}\n </Stack>\n )\n}\n\n/** Named progress primitive for byte transfer. */\nfunction ProgressBar({ value }: { value: number }) {\n return (\n <span className=\"block h-1.5 w-full overflow-hidden rounded-full bg-muted\">\n <span\n className=\"block h-full rounded-full bg-primary transition-[width]\"\n style={{ width: `${Math.round(value * 100)}%` }}\n />\n </span>\n )\n}\n\nfunction AssetGrid({\n assets,\n filtered,\n projectId,\n focusAssetId,\n}: {\n assets: readonly AssetRecord[]\n filtered: boolean\n projectId: string\n focusAssetId?: string\n}) {\n if (assets.length === 0) {\n // The unfiltered empty state is the DropHint above; the *filtered* empty\n // state must say so explicitly (deterministic, reproducible).\n return filtered ? (\n <Text size=\"sm\" tone=\"muted\" data-testid=\"asset-search-empty\">\n No assets match your search.\n </Text>\n ) : null\n }\n return (\n <Grid minItemWidth=\"220px\" gap=\"3\" data-testid=\"asset-grid\">\n {assets.map((asset) => (\n <AssetCard\n key={asset.id}\n asset={asset}\n projectId={projectId}\n focused={asset.id === focusAssetId}\n />\n ))}\n </Grid>\n )\n}\n\nfunction formatBytes(byteSize: number): string {\n if (byteSize < 1024) return `${byteSize} B`\n if (byteSize < 1024 * 1024) return `${(byteSize / 1024).toFixed(1)} KB`\n return `${(byteSize / (1024 * 1024)).toFixed(1)} MB`\n}\n\nfunction AssetCard({\n asset,\n projectId,\n focused,\n}: {\n asset: AssetRecord\n projectId: string\n /** Deep-link target (`?asset=`): scroll into view + open the references dialog. */\n focused?: boolean\n}) {\n const cardRef = useRef<HTMLDivElement | null>(null)\n useEffect(() => {\n if (focused) cardRef.current?.scrollIntoView({ block: 'center', behavior: 'smooth' })\n }, [focused])\n return (\n <div ref={cardRef}>\n <Panel\n className={cn('overflow-hidden', focused && 'ring-2 ring-primary/70')}\n data-testid=\"asset-card\"\n >\n <AssetThumbnail asset={asset} />\n <Stack gap=\"1\" className=\"p-3\">\n <Text size=\"sm\" weight=\"medium\" className=\"truncate\" title={asset.filename}>\n {asset.title ?? asset.filename}\n </Text>\n {asset.title ? (\n <Text size=\"xs\" tone=\"muted\" className=\"truncate\">\n {asset.filename}\n </Text>\n ) : null}\n <Text\n size=\"xs\"\n tone={asset.alt ? 'muted' : 'subtle'}\n className=\"truncate\"\n data-testid=\"asset-alt\"\n >\n {asset.alt ? `Alt: ${asset.alt}` : 'No alt text'}\n </Text>\n <Row gap=\"1\" wrap>\n <Code tone=\"muted\">{asset.contentType}</Code>\n {asset.width && asset.height ? (\n <Code tone=\"muted\">\n {asset.width}×{asset.height}\n </Code>\n ) : null}\n <Code tone=\"muted\">{formatBytes(asset.byteSize)}</Code>\n </Row>\n <Row gap=\"1\">\n <Code tone=\"muted\" className=\"min-w-0 flex-1 truncate\">\n {asset.id}\n </Code>\n <AssetReferencesButton asset={asset} projectId={projectId} autoOpen={focused} />\n <EditAssetButton asset={asset} />\n <DeleteAssetButton asset={asset} />\n </Row>\n </Stack>\n </Panel>\n </div>\n )\n}\n\n/**\n * \"Where is this asset used?\" (EC-172): the shared references panel, inbound\n * over the EC-155 index, hosted in a dialog per asset card.\n */\nfunction AssetReferencesButton({\n asset,\n projectId,\n autoOpen,\n}: {\n asset: AssetRecord\n projectId: string\n autoOpen?: boolean\n}) {\n const [open, setOpen] = useState(false)\n useEffect(() => {\n if (autoOpen) setOpen(true)\n }, [autoOpen])\n return (\n <>\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={`References of ${asset.filename}`}\n onClick={() => setOpen(true)}\n data-testid=\"asset-references\"\n >\n <Link2 />\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"max-w-lg\">\n <DialogHeader>\n <DialogTitle>Used by — {asset.title ?? asset.filename}</DialogTitle>\n <DialogDescription>\n Everything referencing this asset, with deep links to the exact field or node.\n </DialogDescription>\n </DialogHeader>\n {open ? (\n <ReferencesPanel projectId={projectId} target={{ type: 'asset', id: asset.id }} />\n ) : null}\n </DialogContent>\n </Dialog>\n </>\n )\n}\n\n/**\n * Metadata editing (EC-152): alt + title, persisted through the operations\n * asset facade (`assets.upsert`). Failures render in the dialog — never a\n * silent loss.\n */\nfunction EditAssetButton({ asset }: { asset: AssetRecord }) {\n const store = useAssets()\n const [open, setOpen] = useState(false)\n const [alt, setAlt] = useState('')\n const [title, setTitle] = useState('')\n const [error, setError] = useState<string | null>(null)\n const [busy, setBusy] = useState(false)\n\n const openDialog = () => {\n setAlt(asset.alt ?? '')\n setTitle(asset.title ?? '')\n setError(null)\n setOpen(true)\n }\n\n const save = async () => {\n setBusy(true)\n setError(null)\n const result = await store.updateMetadata(asset.id, { alt, title })\n setBusy(false)\n if (result.ok) setOpen(false)\n else setError(result.message)\n }\n\n return (\n <>\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={`Edit ${asset.filename}`}\n onClick={openDialog}\n data-testid=\"asset-edit\"\n >\n <Pencil />\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle>Edit {asset.filename}</DialogTitle>\n <DialogDescription>\n Alt text flows through the delivery shape into rendered markup; the title is the\n library display name.\n </DialogDescription>\n </DialogHeader>\n <Field label=\"Alt text\" htmlFor={`asset-alt-${asset.id}`}>\n <Input\n id={`asset-alt-${asset.id}`}\n value={alt}\n onChange={(e) => setAlt(e.target.value)}\n placeholder=\"Describe the image…\"\n data-testid=\"asset-edit-alt\"\n />\n </Field>\n <Field label=\"Title\" htmlFor={`asset-title-${asset.id}`}>\n <Input\n id={`asset-title-${asset.id}`}\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n placeholder={asset.filename}\n data-testid=\"asset-edit-title\"\n />\n </Field>\n {error ? (\n <Text size=\"sm\" className=\"text-destructive\" data-testid=\"asset-edit-error\">\n {error}\n </Text>\n ) : null}\n <DialogFooter>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => setOpen(false)} disabled={busy}>\n Cancel\n </Button>\n <Button\n size=\"sm\"\n onClick={() => void save()}\n disabled={busy}\n data-testid=\"asset-edit-save\"\n >\n Save\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </>\n )\n}\n\n/** Image preview from the stored serve URL; icon fallback for other types. */\nfunction AssetThumbnail({ asset }: { asset: AssetRecord }) {\n const isImage = asset.contentType.startsWith('image/')\n // In-memory/opaque locators (memory://, convex://) are not fetchable by <img>.\n const resolvable = asset.url?.startsWith('http') ?? false\n if (isImage && asset.url && resolvable) {\n return (\n <img\n src={asset.url}\n alt={asset.alt ?? asset.filename}\n className=\"aspect-video w-full bg-muted object-cover\"\n />\n )\n }\n return (\n <span className=\"grid aspect-video w-full place-items-center bg-muted text-muted-foreground\">\n <ImageIcon className=\"size-6\" />\n </span>\n )\n}\n\n/**\n * Reference-respecting delete (EC-151, EC-202): the dialog enumerates the exact\n * inbound references — index-accurate (EC-155), with deep links to the field or\n * node — using the shared EC-173 impact rows. Deleting a referenced asset is\n * weak-by-default (allowed, with an explicit \"delete anyway\"): every reference\n * dangles into a visible validation issue rather than silently breaking.\n */\nfunction DeleteAssetButton({ asset }: { asset: AssetRecord }) {\n const store = useAssets()\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 store.usagesOf(asset.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 user\n // is confirming deletion either way — so confirm unconditionally, letting\n // deleteAsset skip its redundant inbound re-check (EC-301 perf). Weak-by-default:\n // a still-referenced asset deletes anyway, dangling into a visible issue.\n const result = await store.remove(asset.id, { confirmInUse: true })\n setBusy(false)\n if (result.outcome === 'deleted' || result.outcome === 'not-found') {\n setOpen(false)\n } else if (result.outcome === 'blocked') {\n // A reference appeared between the pre-flight read and confirm (rare race).\n setError(`Asset is now referenced in ${result.usageCount} place(s) — reopen to review.`)\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=\"ghost\"\n size=\"icon-sm\"\n aria-label={`Delete ${asset.filename}`}\n onClick={() => void openDialog()}\n data-testid=\"asset-delete\"\n >\n <Trash2 />\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"max-h-[80vh] max-w-md overflow-y-auto\">\n <DialogHeader>\n <DialogTitle>Delete {asset.filename}?</DialogTitle>\n <DialogDescription>\n Removes the stored bytes and the metadata record. This cannot be undone.\n </DialogDescription>\n </DialogHeader>\n {usages === null && error === null ? (\n <Text size=\"sm\" tone=\"muted\">\n Checking references…\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 In use in {usageCount} place{usageCount === 1 ? '' : 's'}\n </Text>\n </Row>\n <ReferenceImpactRows\n projectId={store.projectId}\n broken={impact!.broken}\n onNavigate={() => setOpen(false)}\n />\n <Text size=\"xs\" tone=\"muted\">\n Deleting will leave these references broken — they will surface as validation\n issues.\n </Text>\n </Stack>\n ) : (\n <Text size=\"sm\" tone=\"muted\">\n No references found. Safe to delete.\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 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 ? 'Delete anyway' : 'Delete'}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </>\n )\n}\n"]}
|
|
@@ -1,43 +1,54 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useCallback, useContext,
|
|
2
|
+
import { createContext, useCallback, useContext, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import { deleteAsset, runUpload, xhrUploadTransport } from '@elytracms/studio/lib/assets';
|
|
4
4
|
import { loadInboundAssetReferenceViews } from '@elytracms/studio/lib/references';
|
|
5
5
|
import { getStudioOperations, operationsAssetRepository } from '@elytracms/studio/lib/operations';
|
|
6
|
+
import { useOperationCache, useOperationQuery } from '@elytracms/studio/lib/query';
|
|
6
7
|
import { updateAssetMetadata } from './asset-metadata';
|
|
7
8
|
const AssetsContext = createContext(null);
|
|
9
|
+
const EMPTY_ASSETS = [];
|
|
10
|
+
/** Cache key for a project's asset library (shared by the query and its patches). */
|
|
11
|
+
function assetsQueryKey(projectId) {
|
|
12
|
+
return ['assets', projectId];
|
|
13
|
+
}
|
|
14
|
+
/** Load a project's asset library (pure over the operations client). A list
|
|
15
|
+
* failure throws so the query surfaces it as an error status. */
|
|
16
|
+
async function loadAssets(operations, projectId) {
|
|
17
|
+
const result = await operations.assets.list({ projectId });
|
|
18
|
+
if (!result.ok)
|
|
19
|
+
throw new Error(result.error.message);
|
|
20
|
+
return [...result.value];
|
|
21
|
+
}
|
|
8
22
|
export function AssetsProvider({ projectId, children, }) {
|
|
9
23
|
const uploadFilesRef = useRef(new Map());
|
|
10
24
|
const uploadSeq = useRef(0);
|
|
11
|
-
const [status, setStatus] = useState('loading');
|
|
12
|
-
const [errorMessage, setErrorMessage] = useState(undefined);
|
|
13
|
-
const [assets, setAssets] = useState([]);
|
|
14
25
|
const [uploads, setUploads] = useState([]);
|
|
15
26
|
/** The shared studio operations bundle + the metadata-write facade over it. */
|
|
16
27
|
const studio = useCallback(async () => {
|
|
17
28
|
const bundle = await getStudioOperations();
|
|
18
29
|
return { ...bundle, assetRepo: operationsAssetRepository(bundle.operations) };
|
|
19
30
|
}, []);
|
|
31
|
+
// The library list, read through the one operations-backed query cache
|
|
32
|
+
// (EC-308/AD-17). No `invalidateOn`: this provider is the sole writer of its
|
|
33
|
+
// project's assets, so its own mutations patch the cache in place (the EC-301
|
|
34
|
+
// rule — a metadata or focal-point edit must never trigger a full re-list).
|
|
35
|
+
const query = useOperationQuery({
|
|
36
|
+
queryKey: assetsQueryKey(projectId),
|
|
37
|
+
loader: (operations) => loadAssets(operations, projectId),
|
|
38
|
+
});
|
|
39
|
+
const cache = useOperationCache();
|
|
40
|
+
const refetch = query.refetch;
|
|
41
|
+
const assets = query.data ?? EMPTY_ASSETS;
|
|
42
|
+
const status = query.isPending
|
|
43
|
+
? 'loading'
|
|
44
|
+
: query.isError
|
|
45
|
+
? 'error'
|
|
46
|
+
: 'ready';
|
|
47
|
+
const errorMessage = query.isError && query.error instanceof Error ? query.error.message : undefined;
|
|
48
|
+
/** Re-list from the backend (the public escape hatch + post-upload reconcile). */
|
|
20
49
|
const refresh = useCallback(async () => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const result = await operations.assets.list({ projectId });
|
|
24
|
-
if (!result.ok) {
|
|
25
|
-
setStatus('error');
|
|
26
|
-
setErrorMessage(result.error.message);
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
setAssets(result.value);
|
|
30
|
-
setStatus('ready');
|
|
31
|
-
setErrorMessage(undefined);
|
|
32
|
-
}
|
|
33
|
-
catch (error) {
|
|
34
|
-
setStatus('error');
|
|
35
|
-
setErrorMessage(error instanceof Error ? error.message : String(error));
|
|
36
|
-
}
|
|
37
|
-
}, [studio, projectId]);
|
|
38
|
-
useEffect(() => {
|
|
39
|
-
void refresh();
|
|
40
|
-
}, [refresh]);
|
|
50
|
+
await refetch();
|
|
51
|
+
}, [refetch]);
|
|
41
52
|
const setUploadState = useCallback((uploadId, state) => {
|
|
42
53
|
setUploads((prev) => prev.map((u) => (u.id === uploadId ? { ...u, state } : u)));
|
|
43
54
|
}, []);
|
|
@@ -94,25 +105,29 @@ export function AssetsProvider({ projectId, children, }) {
|
|
|
94
105
|
// facade); blob byte deletion stays on the adapter; the usage gate reads
|
|
95
106
|
// the EC-155 reference index (`adapter.references`, EC-202).
|
|
96
107
|
const result = await deleteAsset({ assets: assetRepo, blobs: adapter.blobs, references: adapter.references }, projectId, assetId, options);
|
|
97
|
-
|
|
98
|
-
|
|
108
|
+
// Drop the one removed record from the cache rather than re-listing the whole
|
|
109
|
+
// library (EC-301 perf, mirrors updateMetadata) — a delete must not trigger a full
|
|
110
|
+
// `assets.list` round-trip on top of the deletion itself.
|
|
111
|
+
if (result.outcome === 'deleted' || result.outcome === 'not-found') {
|
|
112
|
+
cache.patch(assetsQueryKey(projectId), (prev) => (prev ?? []).filter((a) => a.id !== assetId));
|
|
113
|
+
}
|
|
99
114
|
return result;
|
|
100
|
-
}, [studio, projectId,
|
|
115
|
+
}, [studio, projectId, cache]);
|
|
101
116
|
const updateMetadata = useCallback(async (assetId, patch) => {
|
|
102
117
|
// Metadata writes go through the `assets.upsert` command via the
|
|
103
118
|
// operations facade (EC-141) — never straight at a repository.
|
|
104
119
|
const { assetRepo } = await studio();
|
|
105
120
|
const result = await updateAssetMetadata({ assets: assetRepo }, projectId, assetId, patch);
|
|
106
121
|
if (result.ok) {
|
|
107
|
-
// Patch the one updated record into
|
|
122
|
+
// Patch the one updated record into the cache rather than re-listing the
|
|
108
123
|
// whole library (EC-301). A focal-point or alt edit must not trigger a full
|
|
109
124
|
// `assets.list` round-trip — that re-list (plus the marker only moving once
|
|
110
125
|
// it returned) was the focal-point picker's lag and query storm.
|
|
111
126
|
const updated = result.asset;
|
|
112
|
-
|
|
127
|
+
cache.patch(assetsQueryKey(projectId), (prev) => (prev ?? []).map((a) => (a.id === assetId ? updated : a)));
|
|
113
128
|
}
|
|
114
129
|
return result;
|
|
115
|
-
}, [studio, projectId]);
|
|
130
|
+
}, [studio, projectId, cache]);
|
|
116
131
|
const store = useMemo(() => ({
|
|
117
132
|
projectId,
|
|
118
133
|
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) {\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
|
+
{"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, 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 { OperationsClient, StudioOperations } from '@elytracms/studio/lib/operations'\nimport { useOperationCache, useOperationQuery } from '@elytracms/studio/lib/query'\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\nconst EMPTY_ASSETS: readonly AssetRecord[] = []\n\n/** Cache key for a project's asset library (shared by the query and its patches). */\nfunction assetsQueryKey(projectId: string): readonly [string, string] {\n return ['assets', projectId]\n}\n\n/** Load a project's asset library (pure over the operations client). A list\n * failure throws so the query surfaces it as an error status. */\nasync function loadAssets(operations: OperationsClient, projectId: string): Promise<AssetRecord[]> {\n const result = await operations.assets.list({ projectId })\n if (!result.ok) throw new Error(result.error.message)\n return [...result.value]\n}\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 [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 // The library list, read through the one operations-backed query cache\n // (EC-308/AD-17). No `invalidateOn`: this provider is the sole writer of its\n // project's assets, so its own mutations patch the cache in place (the EC-301\n // rule — a metadata or focal-point edit must never trigger a full re-list).\n const query = useOperationQuery<AssetRecord[]>({\n queryKey: assetsQueryKey(projectId),\n loader: (operations) => loadAssets(operations, projectId),\n })\n const cache = useOperationCache()\n const refetch = query.refetch\n const assets = query.data ?? EMPTY_ASSETS\n const status: AssetsStore['status'] = query.isPending\n ? 'loading'\n : query.isError\n ? 'error'\n : 'ready'\n const errorMessage =\n query.isError && query.error instanceof Error ? query.error.message : undefined\n\n /** Re-list from the backend (the public escape hatch + post-upload reconcile). */\n const refresh = useCallback(async () => {\n await refetch()\n }, [refetch])\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 // Drop the one removed record from the cache rather than re-listing the whole\n // library (EC-301 perf, mirrors updateMetadata) — a delete must not trigger a full\n // `assets.list` round-trip on top of the deletion itself.\n if (result.outcome === 'deleted' || result.outcome === 'not-found') {\n cache.patch<AssetRecord[]>(assetsQueryKey(projectId), (prev) =>\n (prev ?? []).filter((a) => a.id !== assetId),\n )\n }\n return result\n },\n [studio, projectId, cache],\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 the cache 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 cache.patch<AssetRecord[]>(assetsQueryKey(projectId), (prev) =>\n (prev ?? []).map((a) => (a.id === assetId ? updated : a)),\n )\n }\n return result\n },\n [studio, projectId, cache],\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"]}
|
|
@@ -32,7 +32,9 @@ export declare function useAuth(): AuthStore;
|
|
|
32
32
|
export declare function useOptionalAuth(): AuthStore | null;
|
|
33
33
|
/**
|
|
34
34
|
* The signed-in user's role on a project (for the shell's role badge).
|
|
35
|
-
*
|
|
36
|
-
*
|
|
35
|
+
* Read through the one operations-backed query cache (EC-308/AD-17) via the
|
|
36
|
+
* `members.find` query — no longer reaching past the spine into
|
|
37
|
+
* `adapter.members` — and re-read when membership changes. `null` while loading
|
|
38
|
+
* or when the user is not a member.
|
|
37
39
|
*/
|
|
38
40
|
export declare function useProjectRole(projectId: string | null): ProjectRole | null;
|