@declarion/react 0.1.91 → 0.1.93
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/dist-lib/{DraggableChildrenTable-BNjHMCeC.js → DraggableChildrenTable-IfspXSFA.js} +41 -41
- package/dist-lib/{DraggableChildrenTable-BNjHMCeC.js.map → DraggableChildrenTable-IfspXSFA.js.map} +1 -1
- package/dist-lib/Grid-C69-4exi.js +405 -0
- package/dist-lib/Grid-C69-4exi.js.map +1 -0
- package/dist-lib/api/data.d.ts +1 -0
- package/dist-lib/api/events.d.ts +14 -0
- package/dist-lib/components/detail-layout/AssociatedBlock.d.ts +4 -1
- package/dist-lib/components/file-widgets/helpers.d.ts +2 -0
- package/dist-lib/components/file-widgets/useEmbedFileSrc.d.ts +42 -0
- package/dist-lib/declarion-react.css +1 -1
- package/dist-lib/embed/EmbedAuthBootstrap.d.ts +8 -0
- package/dist-lib/embed/EmbedContext.d.ts +50 -0
- package/dist-lib/embed/EmbedDirtyReporter.d.ts +10 -0
- package/dist-lib/embed/EmbedLocationReporter.d.ts +8 -0
- package/dist-lib/embed/EmbedResizeReporter.d.ts +6 -0
- package/dist-lib/embed/EmbedUnsavedGuard.d.ts +7 -0
- package/dist-lib/embed/embed-dirty.d.ts +24 -0
- package/dist-lib/embed/embed-nav-guard.d.ts +20 -0
- package/dist-lib/embed/file-src.d.ts +63 -0
- package/dist-lib/embed/handshake.d.ts +57 -0
- package/dist-lib/embed/index.d.ts +15 -0
- package/dist-lib/embed/params.d.ts +75 -0
- package/dist-lib/embed/protocol.d.ts +138 -0
- package/dist-lib/embed/screen-location.d.ts +20 -0
- package/dist-lib/embed/theme.d.ts +34 -0
- package/dist-lib/embed/token.d.ts +57 -0
- package/dist-lib/embed/useEmbedNavigate.d.ts +27 -0
- package/dist-lib/index.js +4678 -4261
- package/dist-lib/index.js.map +1 -1
- package/dist-lib/{useUserLocale-DyXRSWj9.js → useUserLocale-GBIXbEE6.js} +1656 -1659
- package/dist-lib/useUserLocale-GBIXbEE6.js.map +1 -0
- package/dist-lib/value-DFpIiSZ-.js +739 -0
- package/dist-lib/value-DFpIiSZ-.js.map +1 -0
- package/package.json +1 -1
- package/dist-lib/Grid-BJAiAeHo.js +0 -405
- package/dist-lib/Grid-BJAiAeHo.js.map +0 -1
- package/dist-lib/useUserLocale-DyXRSWj9.js.map +0 -1
- package/dist-lib/value-Bhy_PJo2.js +0 -535
- package/dist-lib/value-Bhy_PJo2.js.map +0 -1
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import { _ as e, b as t, d as n, f as r, i, o as a, t as o, v as s, x as c, y as l } from "./value-DFpIiSZ-.js";
|
|
2
|
+
import { useCallback as u, useEffect as d, useMemo as f, useRef as p, useState as m } from "react";
|
|
3
|
+
import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
4
|
+
import { DndContext as v, PointerSensor as y, closestCenter as b, useSensor as x, useSensors as S } from "@dnd-kit/core";
|
|
5
|
+
import { SortableContext as C, arrayMove as ee, rectSortingStrategy as te, useSortable as w } from "@dnd-kit/sortable";
|
|
6
|
+
import { CSS as T } from "@dnd-kit/utilities";
|
|
7
|
+
//#region src/components/file-widgets/Grid.tsx
|
|
8
|
+
function E(e, t) {
|
|
9
|
+
return n(e, t);
|
|
10
|
+
}
|
|
11
|
+
function D(n) {
|
|
12
|
+
let { field: r, fieldName: i, value: l, onChange: h, mode: w, disabled: T } = n, E = o(l), [D, k] = m([]), [A, j] = m(/* @__PURE__ */ new Set()), [M, N] = m(null), { register: P, unregister: F, isDraft: I } = a(), L = p(/* @__PURE__ */ new Set()), R = p([]);
|
|
13
|
+
R.current = D;
|
|
14
|
+
let z = p(E);
|
|
15
|
+
z.current = E, d(() => () => {
|
|
16
|
+
R.current.forEach((e) => e.abort());
|
|
17
|
+
}, []);
|
|
18
|
+
let B = S(x(y, { activationConstraint: { distance: 5 } })), V = r.display?.placeholder || i, H = (r.accepts ?? ["image/*"]).join(","), U = r.display?.reorderable !== !1, W = r.display?.listable_thumbnail, G = u(async (a) => {
|
|
19
|
+
let o = c(a, r.accepts, r.max_size_bytes), l = `tile-${Math.random().toString(36).slice(2)}-${Date.now()}`, u = new AbortController();
|
|
20
|
+
if (o) {
|
|
21
|
+
k((e) => [...e, {
|
|
22
|
+
localId: l,
|
|
23
|
+
filename: a.name,
|
|
24
|
+
progress: null,
|
|
25
|
+
error: o,
|
|
26
|
+
abort: () => {}
|
|
27
|
+
}]);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
k((e) => [...e, {
|
|
31
|
+
localId: l,
|
|
32
|
+
filename: a.name,
|
|
33
|
+
progress: {
|
|
34
|
+
loaded: 0,
|
|
35
|
+
total: a.size,
|
|
36
|
+
pct: 0
|
|
37
|
+
},
|
|
38
|
+
error: null,
|
|
39
|
+
abort: () => u.abort()
|
|
40
|
+
}]);
|
|
41
|
+
try {
|
|
42
|
+
let e = await t({
|
|
43
|
+
file: a,
|
|
44
|
+
entityCode: n.entityCode,
|
|
45
|
+
fieldCode: i,
|
|
46
|
+
signal: u.signal,
|
|
47
|
+
onProgress: (e) => k((t) => t.map((t) => t.localId === l ? {
|
|
48
|
+
...t,
|
|
49
|
+
progress: e
|
|
50
|
+
} : t))
|
|
51
|
+
});
|
|
52
|
+
if (L.current.delete(l)) {
|
|
53
|
+
s(e.id).catch(() => {}), F(e.id);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
P(e.id, () => s(e.id));
|
|
57
|
+
let r = {
|
|
58
|
+
id: e.id,
|
|
59
|
+
filename: e.filename,
|
|
60
|
+
content_type: e.content_type,
|
|
61
|
+
size_bytes: e.size_bytes,
|
|
62
|
+
sha256: e.sha256,
|
|
63
|
+
width: e.width,
|
|
64
|
+
height: e.height,
|
|
65
|
+
url: e.url,
|
|
66
|
+
metadata: e.metadata
|
|
67
|
+
}, o = [...z.current, r];
|
|
68
|
+
z.current = o, h?.(o), k((e) => e.filter((e) => e.localId !== l));
|
|
69
|
+
} catch (t) {
|
|
70
|
+
if (t instanceof e && t.code === "ABORTED") {
|
|
71
|
+
k((e) => e.filter((e) => e.localId !== l));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
let n = t instanceof e ? {
|
|
75
|
+
code: t.code,
|
|
76
|
+
message: t.message
|
|
77
|
+
} : {
|
|
78
|
+
code: "UNKNOWN",
|
|
79
|
+
message: t instanceof Error ? t.message : "Upload failed"
|
|
80
|
+
};
|
|
81
|
+
k((e) => e.map((e) => e.localId === l ? {
|
|
82
|
+
...e,
|
|
83
|
+
error: n,
|
|
84
|
+
progress: null,
|
|
85
|
+
abort: () => {}
|
|
86
|
+
} : e));
|
|
87
|
+
}
|
|
88
|
+
}, [
|
|
89
|
+
r.accepts,
|
|
90
|
+
r.max_size_bytes,
|
|
91
|
+
i,
|
|
92
|
+
h,
|
|
93
|
+
n.entityCode,
|
|
94
|
+
P,
|
|
95
|
+
F
|
|
96
|
+
]), K = u((e) => {
|
|
97
|
+
Array.from(e).forEach((e) => void G(e));
|
|
98
|
+
}, [G]), q = p(null), J = u(() => {
|
|
99
|
+
T || q.current?.click();
|
|
100
|
+
}, [T]), ie = u((e) => {
|
|
101
|
+
T || (e.preventDefault(), e.dataTransfer.dropEffect = "copy");
|
|
102
|
+
}, [T]), ae = u((e) => {
|
|
103
|
+
if (T) return;
|
|
104
|
+
e.preventDefault();
|
|
105
|
+
let t = e.dataTransfer.files;
|
|
106
|
+
t && t.length > 0 && K(t);
|
|
107
|
+
}, [T, K]), oe = u((e) => {
|
|
108
|
+
let t = e.target.files;
|
|
109
|
+
e.target.value = "", t && t.length > 0 && K(t);
|
|
110
|
+
}, [K]), se = u((e) => {
|
|
111
|
+
I(e) && (s(e).catch(() => {}), F(e));
|
|
112
|
+
let t = E.filter((t) => t.id !== e);
|
|
113
|
+
h?.(t), j((t) => {
|
|
114
|
+
let n = new Set(t);
|
|
115
|
+
return n.delete(e), n;
|
|
116
|
+
});
|
|
117
|
+
}, [
|
|
118
|
+
I,
|
|
119
|
+
E,
|
|
120
|
+
h,
|
|
121
|
+
F
|
|
122
|
+
]), ce = u(() => {
|
|
123
|
+
A.size !== 0 && (Array.from(A).forEach((e) => {
|
|
124
|
+
I(e) && (s(e).catch(() => {}), F(e));
|
|
125
|
+
}), h?.(E.filter((e) => !A.has(e.id))), j(/* @__PURE__ */ new Set()));
|
|
126
|
+
}, [
|
|
127
|
+
I,
|
|
128
|
+
E,
|
|
129
|
+
A,
|
|
130
|
+
h,
|
|
131
|
+
F
|
|
132
|
+
]), le = u((e) => {
|
|
133
|
+
let { active: t, over: n } = e;
|
|
134
|
+
if (!n || t.id === n.id) return;
|
|
135
|
+
let r = E.findIndex((e) => e.id === t.id), i = E.findIndex((e) => e.id === n.id);
|
|
136
|
+
r < 0 || i < 0 || h?.(ee(E, r, i));
|
|
137
|
+
}, [E, h]), ue = u((e) => {
|
|
138
|
+
j((t) => {
|
|
139
|
+
let n = new Set(t);
|
|
140
|
+
return n.has(e) ? n.delete(e) : n.add(e), n;
|
|
141
|
+
});
|
|
142
|
+
}, []), Y = M == null ? null : E[M], X = u(() => {
|
|
143
|
+
N((e) => e == null ? e : Math.min(E.length - 1, e + 1));
|
|
144
|
+
}, [E.length]), Z = u(() => {
|
|
145
|
+
N((e) => e == null ? e : Math.max(0, e - 1));
|
|
146
|
+
}, []), Q = u(() => N(null), []);
|
|
147
|
+
d(() => {
|
|
148
|
+
if (M == null) return;
|
|
149
|
+
let e = (e) => {
|
|
150
|
+
e.key === "Escape" ? Q() : e.key === "ArrowRight" ? X() : e.key === "ArrowLeft" && Z();
|
|
151
|
+
};
|
|
152
|
+
return window.addEventListener("keydown", e), () => window.removeEventListener("keydown", e);
|
|
153
|
+
}, [
|
|
154
|
+
M,
|
|
155
|
+
Q,
|
|
156
|
+
X,
|
|
157
|
+
Z
|
|
158
|
+
]);
|
|
159
|
+
let de = u((e) => {
|
|
160
|
+
T || (e.key === "Enter" || e.key === " ") && e.target === e.currentTarget && (e.preventDefault(), J());
|
|
161
|
+
}, [T, J]), $ = w === "display", fe = f(() => E.map((e) => e.id), [E]);
|
|
162
|
+
return /* @__PURE__ */ _("div", {
|
|
163
|
+
"data-testid": `file-widget-${i}`,
|
|
164
|
+
className: "flex flex-col gap-2",
|
|
165
|
+
children: [
|
|
166
|
+
!$ && A.size > 0 && /* @__PURE__ */ _("div", {
|
|
167
|
+
className: "flex items-center gap-2 rounded-md border border-border bg-surface-2 p-2 text-sm",
|
|
168
|
+
role: "toolbar",
|
|
169
|
+
"aria-label": "Bulk actions",
|
|
170
|
+
children: [
|
|
171
|
+
/* @__PURE__ */ _("span", {
|
|
172
|
+
className: "text-foreground",
|
|
173
|
+
children: [A.size, " selected"]
|
|
174
|
+
}),
|
|
175
|
+
/* @__PURE__ */ g("button", {
|
|
176
|
+
type: "button",
|
|
177
|
+
className: "rounded-sm border border-border bg-card px-2 py-1 text-xs text-destructive hover:bg-surface-2",
|
|
178
|
+
onClick: ce,
|
|
179
|
+
"data-testid": "bulk-delete",
|
|
180
|
+
children: "Delete"
|
|
181
|
+
}),
|
|
182
|
+
/* @__PURE__ */ g("button", {
|
|
183
|
+
type: "button",
|
|
184
|
+
className: "rounded-sm border border-border bg-card px-2 py-1 text-xs text-foreground hover:bg-surface-2",
|
|
185
|
+
onClick: () => j(/* @__PURE__ */ new Set()),
|
|
186
|
+
children: "Clear"
|
|
187
|
+
})
|
|
188
|
+
]
|
|
189
|
+
}),
|
|
190
|
+
/* @__PURE__ */ g(v, {
|
|
191
|
+
sensors: B,
|
|
192
|
+
collisionDetection: b,
|
|
193
|
+
onDragEnd: le,
|
|
194
|
+
children: /* @__PURE__ */ g(C, {
|
|
195
|
+
items: fe,
|
|
196
|
+
strategy: te,
|
|
197
|
+
children: /* @__PURE__ */ _("div", {
|
|
198
|
+
className: "grid grid-cols-[repeat(auto-fill,minmax(140px,1fr))] gap-3 rounded-md border border-dashed border-border bg-surface p-3 outline-none focus-visible:outline-2 focus-visible:outline-accent",
|
|
199
|
+
role: "grid",
|
|
200
|
+
"aria-label": V,
|
|
201
|
+
tabIndex: $ || T ? -1 : 0,
|
|
202
|
+
onDragOver: ie,
|
|
203
|
+
onDrop: ae,
|
|
204
|
+
onKeyDown: de,
|
|
205
|
+
"data-testid": "grid-container",
|
|
206
|
+
children: [
|
|
207
|
+
E.map((e, t) => /* @__PURE__ */ g(O, {
|
|
208
|
+
file: e,
|
|
209
|
+
selected: A.has(e.id),
|
|
210
|
+
disabled: T || $,
|
|
211
|
+
reorderable: U && !$,
|
|
212
|
+
preferredThumbnail: W,
|
|
213
|
+
onSelect: () => ue(e.id),
|
|
214
|
+
onRemove: () => se(e.id),
|
|
215
|
+
onPreview: () => N(t)
|
|
216
|
+
}, e.id)),
|
|
217
|
+
D.map((e) => /* @__PURE__ */ g(ne, {
|
|
218
|
+
tile: e,
|
|
219
|
+
onCancel: () => {
|
|
220
|
+
e.abort(), L.current.add(e.localId), k((t) => t.filter((t) => t.localId !== e.localId));
|
|
221
|
+
},
|
|
222
|
+
onDismiss: () => k((t) => t.filter((t) => t.localId !== e.localId))
|
|
223
|
+
}, e.localId)),
|
|
224
|
+
E.length === 0 && D.length === 0 && /* @__PURE__ */ g("div", {
|
|
225
|
+
className: "col-span-full flex h-24 cursor-pointer items-center justify-center text-sm text-muted-foreground",
|
|
226
|
+
onClick: J,
|
|
227
|
+
"data-testid": "grid-empty",
|
|
228
|
+
children: r.display?.placeholder || "Drop files here or click to browse"
|
|
229
|
+
})
|
|
230
|
+
]
|
|
231
|
+
})
|
|
232
|
+
})
|
|
233
|
+
}),
|
|
234
|
+
!$ && /* @__PURE__ */ g("input", {
|
|
235
|
+
ref: q,
|
|
236
|
+
type: "file",
|
|
237
|
+
accept: H,
|
|
238
|
+
multiple: !0,
|
|
239
|
+
onChange: oe,
|
|
240
|
+
style: { display: "none" },
|
|
241
|
+
"data-testid": `file-input-${i}`
|
|
242
|
+
}),
|
|
243
|
+
Y && /* @__PURE__ */ g(re, {
|
|
244
|
+
file: Y,
|
|
245
|
+
hasPrev: M > 0,
|
|
246
|
+
hasNext: M < E.length - 1,
|
|
247
|
+
onPrev: Z,
|
|
248
|
+
onNext: X,
|
|
249
|
+
onClose: Q
|
|
250
|
+
})
|
|
251
|
+
]
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
function O({ file: e, selected: t, disabled: n, reorderable: a, preferredThumbnail: o, onSelect: s, onRemove: c, onPreview: l }) {
|
|
255
|
+
let { attributes: u, listeners: d, setNodeRef: f, transform: p, transition: m, isDragging: v } = w({
|
|
256
|
+
id: e.id,
|
|
257
|
+
disabled: !a
|
|
258
|
+
}), y = {
|
|
259
|
+
transform: T.Transform.toString(p),
|
|
260
|
+
transition: m,
|
|
261
|
+
opacity: v ? .5 : 1
|
|
262
|
+
}, b = i(e.id, r(e, o), E(e, o));
|
|
263
|
+
return /* @__PURE__ */ _("div", {
|
|
264
|
+
ref: f,
|
|
265
|
+
style: y,
|
|
266
|
+
className: `group relative aspect-square overflow-hidden rounded-md border ${t ? "border-accent" : "border-border"} bg-surface-2`,
|
|
267
|
+
"data-testid": `grid-tile-${e.id}`,
|
|
268
|
+
"data-selected": t || void 0,
|
|
269
|
+
children: [b ? /* @__PURE__ */ g("img", {
|
|
270
|
+
src: b,
|
|
271
|
+
alt: e.filename,
|
|
272
|
+
className: "h-full w-full cursor-pointer object-cover",
|
|
273
|
+
loading: "lazy",
|
|
274
|
+
onClick: l
|
|
275
|
+
}) : /* @__PURE__ */ g("div", {
|
|
276
|
+
className: "flex h-full w-full cursor-pointer items-center justify-center bg-surface-3 text-xs text-muted-foreground",
|
|
277
|
+
onClick: l,
|
|
278
|
+
children: e.filename
|
|
279
|
+
}), !n && /* @__PURE__ */ _(h, { children: [
|
|
280
|
+
/* @__PURE__ */ g("input", {
|
|
281
|
+
type: "checkbox",
|
|
282
|
+
className: "absolute left-1 top-1 h-4 w-4 cursor-pointer",
|
|
283
|
+
checked: t,
|
|
284
|
+
onChange: s,
|
|
285
|
+
"aria-label": `Select ${e.filename}`,
|
|
286
|
+
"data-testid": `select-${e.id}`,
|
|
287
|
+
onClick: (e) => e.stopPropagation()
|
|
288
|
+
}),
|
|
289
|
+
a && /* @__PURE__ */ g("button", {
|
|
290
|
+
type: "button",
|
|
291
|
+
className: "absolute right-1 top-1 cursor-grab rounded-sm border border-border bg-card px-1 text-xs",
|
|
292
|
+
"aria-label": `Drag to reorder ${e.filename}`,
|
|
293
|
+
"data-testid": `drag-${e.id}`,
|
|
294
|
+
...u,
|
|
295
|
+
...d,
|
|
296
|
+
children: "⋮⋮"
|
|
297
|
+
}),
|
|
298
|
+
/* @__PURE__ */ _("div", {
|
|
299
|
+
className: "absolute inset-x-0 bottom-0 flex items-center justify-end gap-1 bg-black/50 p-1 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100",
|
|
300
|
+
children: [/* @__PURE__ */ g("button", {
|
|
301
|
+
type: "button",
|
|
302
|
+
className: "rounded-sm border border-border bg-card px-2 py-0.5 text-xs",
|
|
303
|
+
onClick: l,
|
|
304
|
+
children: "View"
|
|
305
|
+
}), /* @__PURE__ */ g("button", {
|
|
306
|
+
type: "button",
|
|
307
|
+
className: "rounded-sm border border-border bg-card px-2 py-0.5 text-xs text-destructive",
|
|
308
|
+
onClick: c,
|
|
309
|
+
"data-testid": `remove-${e.id}`,
|
|
310
|
+
children: "Delete"
|
|
311
|
+
})]
|
|
312
|
+
})
|
|
313
|
+
] })]
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
function ne({ tile: e, onCancel: t, onDismiss: n }) {
|
|
317
|
+
return /* @__PURE__ */ _("div", {
|
|
318
|
+
className: `relative flex aspect-square flex-col items-center justify-center rounded-md border ${e.error ? "border-destructive" : "border-border"} bg-surface-2 p-2 text-center text-xs`,
|
|
319
|
+
"data-testid": `pending-tile-${e.localId}`,
|
|
320
|
+
children: [
|
|
321
|
+
/* @__PURE__ */ g("span", {
|
|
322
|
+
className: "truncate",
|
|
323
|
+
title: e.filename,
|
|
324
|
+
children: e.filename
|
|
325
|
+
}),
|
|
326
|
+
e.progress && !e.error && /* @__PURE__ */ _("span", {
|
|
327
|
+
className: "mt-1 text-muted-foreground",
|
|
328
|
+
role: "progressbar",
|
|
329
|
+
"aria-valuenow": e.progress.pct,
|
|
330
|
+
"aria-valuemin": 0,
|
|
331
|
+
"aria-valuemax": 100,
|
|
332
|
+
children: [e.progress.pct, "%"]
|
|
333
|
+
}),
|
|
334
|
+
e.error && /* @__PURE__ */ g("div", {
|
|
335
|
+
role: "alert",
|
|
336
|
+
className: "mt-1 text-destructive",
|
|
337
|
+
children: e.error.message
|
|
338
|
+
}),
|
|
339
|
+
/* @__PURE__ */ g("button", {
|
|
340
|
+
type: "button",
|
|
341
|
+
className: "mt-1 rounded-sm border border-border bg-card px-2 py-0.5",
|
|
342
|
+
onClick: e.error ? n : t,
|
|
343
|
+
children: e.error ? "Dismiss" : "Cancel"
|
|
344
|
+
})
|
|
345
|
+
]
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
function re({ file: e, hasPrev: t, hasNext: n, onPrev: r, onNext: a, onClose: o }) {
|
|
349
|
+
let s = i(e.id, "preview", e.presigned_url ?? e.url ?? E(e));
|
|
350
|
+
return /* @__PURE__ */ _("div", {
|
|
351
|
+
className: "fixed inset-0 z-50 flex items-center justify-center bg-black/80",
|
|
352
|
+
role: "dialog",
|
|
353
|
+
"aria-modal": "true",
|
|
354
|
+
"aria-label": e.filename,
|
|
355
|
+
"data-testid": "lightbox",
|
|
356
|
+
onClick: o,
|
|
357
|
+
children: [
|
|
358
|
+
/* @__PURE__ */ g("button", {
|
|
359
|
+
type: "button",
|
|
360
|
+
className: "absolute right-4 top-4 rounded-sm border border-border bg-card px-3 py-1 text-foreground",
|
|
361
|
+
onClick: (e) => {
|
|
362
|
+
e.stopPropagation(), o();
|
|
363
|
+
},
|
|
364
|
+
"data-testid": "lightbox-close",
|
|
365
|
+
children: "Close"
|
|
366
|
+
}),
|
|
367
|
+
t && /* @__PURE__ */ g("button", {
|
|
368
|
+
type: "button",
|
|
369
|
+
className: "absolute left-4 rounded-sm border border-border bg-card px-3 py-2 text-foreground",
|
|
370
|
+
onClick: (e) => {
|
|
371
|
+
e.stopPropagation(), r();
|
|
372
|
+
},
|
|
373
|
+
"data-testid": "lightbox-prev",
|
|
374
|
+
children: "‹"
|
|
375
|
+
}),
|
|
376
|
+
n && /* @__PURE__ */ g("button", {
|
|
377
|
+
type: "button",
|
|
378
|
+
className: "absolute right-4 bottom-4 rounded-sm border border-border bg-card px-3 py-2 text-foreground",
|
|
379
|
+
onClick: (e) => {
|
|
380
|
+
e.stopPropagation(), a();
|
|
381
|
+
},
|
|
382
|
+
"data-testid": "lightbox-next",
|
|
383
|
+
children: "›"
|
|
384
|
+
}),
|
|
385
|
+
s ? /* @__PURE__ */ g("img", {
|
|
386
|
+
src: s,
|
|
387
|
+
alt: e.filename,
|
|
388
|
+
className: "max-h-[90vh] max-w-[90vw] object-contain",
|
|
389
|
+
onClick: (e) => e.stopPropagation()
|
|
390
|
+
}) : /* @__PURE__ */ _("div", {
|
|
391
|
+
className: "rounded-md bg-card p-4 text-foreground",
|
|
392
|
+
children: [
|
|
393
|
+
e.filename,
|
|
394
|
+
" (",
|
|
395
|
+
l(e.size_bytes),
|
|
396
|
+
")"
|
|
397
|
+
]
|
|
398
|
+
})
|
|
399
|
+
]
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
//#endregion
|
|
403
|
+
export { D as Grid };
|
|
404
|
+
|
|
405
|
+
//# sourceMappingURL=Grid-C69-4exi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Grid-C69-4exi.js","names":[],"sources":["../src/components/file-widgets/Grid.tsx"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type { CSSProperties, DragEvent, KeyboardEvent } from \"react\";\nimport {\n DndContext,\n closestCenter,\n PointerSensor,\n useSensor,\n useSensors,\n type DragEndEvent,\n} from \"@dnd-kit/core\";\nimport {\n SortableContext,\n rectSortingStrategy,\n useSortable,\n arrayMove,\n} from \"@dnd-kit/sortable\";\nimport { CSS } from \"@dnd-kit/utilities\";\nimport {\n uploadFile,\n discardFile,\n validateClientSide,\n formatBytes,\n UploadError,\n} from \"./upload\";\nimport { useDraftRegistration } from \"./draft-scope\";\nimport { preferredListThumbnailURL, preferredThumbnailVariant } from \"./helpers\";\nimport { useEmbedFileSrc } from \"./useEmbedFileSrc\";\nimport { fileArrayFromValue } from \"./value\";\nimport type {\n FileWidgetProps,\n HydratedFile,\n UploadProgress,\n} from \"./types\";\n\n// Grid widget: thumbnail grid for `mode: array` files. Per einstein §6.4.\n//\n// Behaviors:\n// - drag-drop multi-upload (each file gets its own progress tile)\n// - per-item hover overlay (View / Metadata / Delete)\n// - drag-handle reorder via @dnd-kit\n// - multi-select toolbar with Bulk Delete\n// - click → lightbox with prev/next + keyboard nav\n// - IntersectionObserver lazy thumbnail loading\n// - per-tile thumbnail prefers the schema-selected list derivation, then\n// falls back to built-in thumbs or the primary file URL\n//\n// Value contract: array of HydratedFile. onChange emits the full new array\n// (callers persist it via the entity's $files payload on save).\n\ninterface InFlightTile {\n // Local-only tiles representing uploads in progress. Once finished they\n // are appended to the value array and removed from this list.\n localId: string;\n filename: string;\n progress: UploadProgress | null;\n error: { code: string; message: string } | null;\n abort: () => void;\n}\n\nfunction thumbUrl(f: HydratedFile, preferredDerivationKey?: string): string | undefined {\n return preferredListThumbnailURL(f, preferredDerivationKey);\n}\n\nexport function Grid(props: FileWidgetProps) {\n const { field, fieldName, value, onChange, mode, disabled } = props;\n const items = fileArrayFromValue(value);\n const [tiles, setTiles] = useState<InFlightTile[]>([]);\n const [selected, setSelected] = useState<Set<string>>(new Set());\n const [lightbox, setLightbox] = useState<number | null>(null);\n const { register, unregister, isDraft } = useDraftRegistration();\n const cancelledUploadsRef = useRef<Set<string>>(new Set());\n const tilesRef = useRef<InFlightTile[]>([]);\n tilesRef.current = tiles;\n // valueRef snapshots the current value array so parallel uploads append\n // against the most recent state, not the closure they were created with.\n const valueRef = useRef<HydratedFile[]>(items);\n valueRef.current = items;\n\n // Cleanup all in-flight uploads on unmount.\n useEffect(() => {\n return () => {\n tilesRef.current.forEach((t) => t.abort());\n };\n }, []);\n\n const sensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),\n );\n\n const ariaLabel = field.display?.placeholder || fieldName;\n const accept = (field.accepts ?? [\"image/*\"]).join(\",\");\n const reorderable = field.display?.reorderable !== false;\n const preferredThumbnail = field.display?.listable_thumbnail;\n\n const startUpload = useCallback(\n async (raw: File) => {\n const v = validateClientSide(raw, field.accepts, field.max_size_bytes);\n const localId = `tile-${Math.random().toString(36).slice(2)}-${Date.now()}`;\n const ctrl = new AbortController();\n\n if (v) {\n setTiles((prev) => [\n ...prev,\n {\n localId,\n filename: raw.name,\n progress: null,\n error: v,\n abort: () => {},\n },\n ]);\n return;\n }\n\n setTiles((prev) => [\n ...prev,\n {\n localId,\n filename: raw.name,\n progress: { loaded: 0, total: raw.size, pct: 0 },\n error: null,\n abort: () => ctrl.abort(),\n },\n ]);\n\n try {\n const result = await uploadFile({\n file: raw,\n entityCode: props.entityCode,\n fieldCode: fieldName,\n signal: ctrl.signal,\n onProgress: (p) =>\n setTiles((prev) =>\n prev.map((t) =>\n t.localId === localId ? { ...t, progress: p } : t,\n ),\n ),\n });\n if (cancelledUploadsRef.current.delete(localId)) {\n void discardFile(result.id).catch(() => {});\n unregister(result.id);\n return;\n }\n register(result.id, () => discardFile(result.id));\n const next: HydratedFile = {\n id: result.id,\n filename: result.filename,\n content_type: result.content_type,\n size_bytes: result.size_bytes,\n sha256: result.sha256,\n width: result.width,\n height: result.height,\n url: result.url,\n metadata: result.metadata,\n };\n const merged = [...valueRef.current, next];\n valueRef.current = merged;\n onChange?.(merged);\n setTiles((prev) => prev.filter((t) => t.localId !== localId));\n } catch (err) {\n if (err instanceof UploadError && err.code === \"ABORTED\") {\n setTiles((prev) => prev.filter((t) => t.localId !== localId));\n return;\n }\n const e =\n err instanceof UploadError\n ? { code: err.code, message: err.message }\n : {\n code: \"UNKNOWN\",\n message: err instanceof Error ? err.message : \"Upload failed\",\n };\n setTiles((prev) =>\n prev.map((t) =>\n t.localId === localId\n ? { ...t, error: e, progress: null, abort: () => {} }\n : t,\n ),\n );\n }\n },\n [field.accepts, field.max_size_bytes, fieldName, onChange, props.entityCode, register, unregister],\n );\n\n const handleFiles = useCallback(\n (files: FileList | File[]) => {\n const arr = Array.from(files);\n arr.forEach((f) => void startUpload(f));\n },\n [startUpload],\n );\n\n const fileInputRef = useRef<HTMLInputElement | null>(null);\n const onPick = useCallback(() => {\n if (disabled) return;\n fileInputRef.current?.click();\n }, [disabled]);\n\n const onDragOver = useCallback(\n (e: DragEvent<HTMLDivElement>) => {\n if (disabled) return;\n e.preventDefault();\n e.dataTransfer.dropEffect = \"copy\";\n },\n [disabled],\n );\n\n const onDrop = useCallback(\n (e: DragEvent<HTMLDivElement>) => {\n if (disabled) return;\n e.preventDefault();\n const files = e.dataTransfer.files;\n if (files && files.length > 0) handleFiles(files);\n },\n [disabled, handleFiles],\n );\n\n const onChangeFile = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const files = e.target.files;\n e.target.value = \"\";\n if (files && files.length > 0) handleFiles(files);\n },\n [handleFiles],\n );\n\n const handleRemove = useCallback(\n (id: string) => {\n if (isDraft(id)) {\n void discardFile(id).catch(() => {});\n unregister(id);\n }\n const next = items.filter((f) => f.id !== id);\n onChange?.(next);\n setSelected((prev) => {\n const n = new Set(prev);\n n.delete(id);\n return n;\n });\n },\n [isDraft, items, onChange, unregister],\n );\n\n const handleBulkDelete = useCallback(() => {\n if (selected.size === 0) return;\n const ids = Array.from(selected);\n ids.forEach((id) => {\n if (isDraft(id)) {\n void discardFile(id).catch(() => {});\n unregister(id);\n }\n });\n onChange?.(items.filter((f) => !selected.has(f.id)));\n setSelected(new Set());\n }, [isDraft, items, selected, onChange, unregister]);\n\n const handleDragEnd = useCallback(\n (event: DragEndEvent) => {\n const { active, over } = event;\n if (!over || active.id === over.id) return;\n const oldIdx = items.findIndex((f) => f.id === active.id);\n const newIdx = items.findIndex((f) => f.id === over.id);\n if (oldIdx < 0 || newIdx < 0) return;\n onChange?.(arrayMove(items, oldIdx, newIdx));\n },\n [items, onChange],\n );\n\n const toggleSelect = useCallback((id: string) => {\n setSelected((prev) => {\n const n = new Set(prev);\n if (n.has(id)) n.delete(id);\n else n.add(id);\n return n;\n });\n }, []);\n\n const lightboxFile = lightbox != null ? items[lightbox] : null;\n const lightboxNext = useCallback(() => {\n setLightbox((cur) =>\n cur == null ? cur : Math.min(items.length - 1, cur + 1),\n );\n }, [items.length]);\n const lightboxPrev = useCallback(() => {\n setLightbox((cur) => (cur == null ? cur : Math.max(0, cur - 1)));\n }, []);\n const lightboxClose = useCallback(() => setLightbox(null), []);\n\n useEffect(() => {\n if (lightbox == null) return;\n const handler = (e: globalThis.KeyboardEvent) => {\n if (e.key === \"Escape\") lightboxClose();\n else if (e.key === \"ArrowRight\") lightboxNext();\n else if (e.key === \"ArrowLeft\") lightboxPrev();\n };\n window.addEventListener(\"keydown\", handler);\n return () => window.removeEventListener(\"keydown\", handler);\n }, [lightbox, lightboxClose, lightboxNext, lightboxPrev]);\n\n const onGridKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n if (disabled) return;\n if (e.key === \"Enter\" || e.key === \" \") {\n if (e.target === e.currentTarget) {\n e.preventDefault();\n onPick();\n }\n }\n },\n [disabled, onPick],\n );\n\n const isDisplay = mode === \"display\";\n\n const sortableIds = useMemo(() => items.map((f) => f.id), [items]);\n\n return (\n <div data-testid={`file-widget-${fieldName}`} className=\"flex flex-col gap-2\">\n {!isDisplay && selected.size > 0 && (\n <div\n className=\"flex items-center gap-2 rounded-md border border-border bg-surface-2 p-2 text-sm\"\n role=\"toolbar\"\n aria-label=\"Bulk actions\"\n >\n <span className=\"text-foreground\">{selected.size} selected</span>\n <button\n type=\"button\"\n className=\"rounded-sm border border-border bg-card px-2 py-1 text-xs text-destructive hover:bg-surface-2\"\n onClick={handleBulkDelete}\n data-testid=\"bulk-delete\"\n >\n Delete\n </button>\n <button\n type=\"button\"\n className=\"rounded-sm border border-border bg-card px-2 py-1 text-xs text-foreground hover:bg-surface-2\"\n onClick={() => setSelected(new Set())}\n >\n Clear\n </button>\n </div>\n )}\n\n <DndContext\n sensors={sensors}\n collisionDetection={closestCenter}\n onDragEnd={handleDragEnd}\n >\n <SortableContext items={sortableIds} strategy={rectSortingStrategy}>\n <div\n className=\"grid grid-cols-[repeat(auto-fill,minmax(140px,1fr))] gap-3 rounded-md border border-dashed border-border bg-surface p-3 outline-none focus-visible:outline-2 focus-visible:outline-accent\"\n role=\"grid\"\n aria-label={ariaLabel}\n tabIndex={isDisplay || disabled ? -1 : 0}\n onDragOver={onDragOver}\n onDrop={onDrop}\n onKeyDown={onGridKeyDown}\n data-testid=\"grid-container\"\n >\n {items.map((f, idx) => (\n <SortableTile\n key={f.id}\n file={f}\n selected={selected.has(f.id)}\n disabled={disabled || isDisplay}\n reorderable={reorderable && !isDisplay}\n preferredThumbnail={preferredThumbnail}\n onSelect={() => toggleSelect(f.id)}\n onRemove={() => handleRemove(f.id)}\n onPreview={() => setLightbox(idx)}\n />\n ))}\n {tiles.map((t) => (\n <PendingTile\n key={t.localId}\n tile={t}\n onCancel={() => {\n t.abort();\n cancelledUploadsRef.current.add(t.localId);\n setTiles((prev) =>\n prev.filter((x) => x.localId !== t.localId),\n );\n }}\n onDismiss={() =>\n setTiles((prev) =>\n prev.filter((x) => x.localId !== t.localId),\n )\n }\n />\n ))}\n {items.length === 0 && tiles.length === 0 && (\n <div\n className=\"col-span-full flex h-24 cursor-pointer items-center justify-center text-sm text-muted-foreground\"\n onClick={onPick}\n data-testid=\"grid-empty\"\n >\n {field.display?.placeholder || \"Drop files here or click to browse\"}\n </div>\n )}\n </div>\n </SortableContext>\n </DndContext>\n\n {!isDisplay && (\n <input\n ref={fileInputRef}\n type=\"file\"\n accept={accept}\n multiple\n onChange={onChangeFile}\n style={{ display: \"none\" }}\n data-testid={`file-input-${fieldName}`}\n />\n )}\n\n {lightboxFile && (\n <Lightbox\n file={lightboxFile}\n hasPrev={lightbox! > 0}\n hasNext={lightbox! < items.length - 1}\n onPrev={lightboxPrev}\n onNext={lightboxNext}\n onClose={lightboxClose}\n />\n )}\n </div>\n );\n}\n\nfunction SortableTile({\n file,\n selected,\n disabled,\n reorderable,\n preferredThumbnail,\n onSelect,\n onRemove,\n onPreview,\n}: {\n file: HydratedFile;\n selected: boolean;\n disabled?: boolean;\n reorderable: boolean;\n preferredThumbnail?: string;\n onSelect: () => void;\n onRemove: () => void;\n onPreview: () => void;\n}) {\n const {\n attributes,\n listeners,\n setNodeRef,\n transform,\n transition,\n isDragging,\n } = useSortable({ id: file.id, disabled: !reorderable });\n const style: CSSProperties = {\n transform: CSS.Transform.toString(transform),\n transition,\n opacity: isDragging ? 0.5 : 1,\n };\n // Cookie mode: `thumbUrl` is the static thumbnail URL, returned unchanged.\n // Embed mode: resolved through the Bearer-authenticated file URL endpoint.\n const url = useEmbedFileSrc(\n file.id,\n preferredThumbnailVariant(file, preferredThumbnail),\n thumbUrl(file, preferredThumbnail),\n );\n return (\n <div\n ref={setNodeRef}\n style={style}\n className={`group relative aspect-square overflow-hidden rounded-md border ${\n selected ? \"border-accent\" : \"border-border\"\n } bg-surface-2`}\n data-testid={`grid-tile-${file.id}`}\n data-selected={selected || undefined}\n >\n {/* Lazy thumb: native loading=\"lazy\" approximates IntersectionObserver\n and is what the rest of the SDK uses (see ImageWidget). */}\n {url ? (\n <img\n src={url}\n alt={file.filename}\n className=\"h-full w-full cursor-pointer object-cover\"\n loading=\"lazy\"\n onClick={onPreview}\n />\n ) : (\n <div\n className=\"flex h-full w-full cursor-pointer items-center justify-center bg-surface-3 text-xs text-muted-foreground\"\n onClick={onPreview}\n >\n {file.filename}\n </div>\n )}\n {!disabled && (\n <>\n <input\n type=\"checkbox\"\n className=\"absolute left-1 top-1 h-4 w-4 cursor-pointer\"\n checked={selected}\n onChange={onSelect}\n aria-label={`Select ${file.filename}`}\n data-testid={`select-${file.id}`}\n onClick={(e) => e.stopPropagation()}\n />\n {reorderable && (\n <button\n type=\"button\"\n className=\"absolute right-1 top-1 cursor-grab rounded-sm border border-border bg-card px-1 text-xs\"\n aria-label={`Drag to reorder ${file.filename}`}\n data-testid={`drag-${file.id}`}\n {...attributes}\n {...listeners}\n >\n ⋮⋮\n </button>\n )}\n <div className=\"absolute inset-x-0 bottom-0 flex items-center justify-end gap-1 bg-black/50 p-1 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100\">\n <button\n type=\"button\"\n className=\"rounded-sm border border-border bg-card px-2 py-0.5 text-xs\"\n onClick={onPreview}\n >\n View\n </button>\n <button\n type=\"button\"\n className=\"rounded-sm border border-border bg-card px-2 py-0.5 text-xs text-destructive\"\n onClick={onRemove}\n data-testid={`remove-${file.id}`}\n >\n Delete\n </button>\n </div>\n </>\n )}\n </div>\n );\n}\n\nfunction PendingTile({\n tile,\n onCancel,\n onDismiss,\n}: {\n tile: InFlightTile;\n onCancel: () => void;\n onDismiss: () => void;\n}) {\n return (\n <div\n className={`relative flex aspect-square flex-col items-center justify-center rounded-md border ${\n tile.error ? \"border-destructive\" : \"border-border\"\n } bg-surface-2 p-2 text-center text-xs`}\n data-testid={`pending-tile-${tile.localId}`}\n >\n <span className=\"truncate\" title={tile.filename}>\n {tile.filename}\n </span>\n {tile.progress && !tile.error && (\n <span\n className=\"mt-1 text-muted-foreground\"\n role=\"progressbar\"\n aria-valuenow={tile.progress.pct}\n aria-valuemin={0}\n aria-valuemax={100}\n >\n {tile.progress.pct}%\n </span>\n )}\n {tile.error && (\n <div role=\"alert\" className=\"mt-1 text-destructive\">\n {tile.error.message}\n </div>\n )}\n <button\n type=\"button\"\n className=\"mt-1 rounded-sm border border-border bg-card px-2 py-0.5\"\n onClick={tile.error ? onDismiss : onCancel}\n >\n {tile.error ? \"Dismiss\" : \"Cancel\"}\n </button>\n </div>\n );\n}\n\nfunction Lightbox({\n file,\n hasPrev,\n hasNext,\n onPrev,\n onNext,\n onClose,\n}: {\n file: HydratedFile;\n hasPrev: boolean;\n hasNext: boolean;\n onPrev: () => void;\n onNext: () => void;\n onClose: () => void;\n}) {\n // The lightbox shows the full image. Cookie mode: the static primary URL.\n // Embed mode: the primary file resolved through the embed file URL endpoint.\n const url = useEmbedFileSrc(\n file.id,\n \"preview\",\n file.presigned_url ?? file.url ?? thumbUrl(file),\n );\n return (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/80\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={file.filename}\n data-testid=\"lightbox\"\n onClick={onClose}\n >\n <button\n type=\"button\"\n className=\"absolute right-4 top-4 rounded-sm border border-border bg-card px-3 py-1 text-foreground\"\n onClick={(e) => {\n e.stopPropagation();\n onClose();\n }}\n data-testid=\"lightbox-close\"\n >\n Close\n </button>\n {hasPrev && (\n <button\n type=\"button\"\n className=\"absolute left-4 rounded-sm border border-border bg-card px-3 py-2 text-foreground\"\n onClick={(e) => {\n e.stopPropagation();\n onPrev();\n }}\n data-testid=\"lightbox-prev\"\n >\n ‹\n </button>\n )}\n {hasNext && (\n <button\n type=\"button\"\n className=\"absolute right-4 bottom-4 rounded-sm border border-border bg-card px-3 py-2 text-foreground\"\n onClick={(e) => {\n e.stopPropagation();\n onNext();\n }}\n data-testid=\"lightbox-next\"\n >\n ›\n </button>\n )}\n {url ? (\n <img\n src={url}\n alt={file.filename}\n className=\"max-h-[90vh] max-w-[90vw] object-contain\"\n onClick={(e) => e.stopPropagation()}\n />\n ) : (\n <div className=\"rounded-md bg-card p-4 text-foreground\">\n {file.filename} ({formatBytes(file.size_bytes)})\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
package/dist-lib/api/data.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export declare function getEntity(entity: string, pkValues: PkValues | string):
|
|
|
8
8
|
export declare function createEntity(entity: string, data: Record<string, unknown>): Promise<DataResponse>;
|
|
9
9
|
export declare function updateEntity(entity: string, pkValues: PkValues | string, data: Record<string, unknown>): Promise<DataResponse>;
|
|
10
10
|
export declare function deleteEntity(entity: string, pkValues: PkValues | string): Promise<void>;
|
|
11
|
+
export declare function encodeObjectId(pkValues: PkValues): string;
|
|
11
12
|
export declare function restoreEntity(entity: string, pkValues: PkValues | string): Promise<void>;
|
|
12
13
|
/** Result of POST /api/data/{entity}/export. The server streams a CSV
|
|
13
14
|
* blob; the client gets the bytes plus the server-resolved filename
|
package/dist-lib/api/events.d.ts
CHANGED
|
@@ -42,5 +42,19 @@ export declare class SSEAuthError extends Error {
|
|
|
42
42
|
export interface StreamEventsOptions {
|
|
43
43
|
signal?: AbortSignal;
|
|
44
44
|
lastEventID?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Embed token mode: the scoped embed JWT. When set, it is sent as
|
|
47
|
+
* `Authorization: Bearer <token>` and the request omits cookies (see
|
|
48
|
+
* `credentials` below). Outside embed mode this is undefined and the
|
|
49
|
+
* cookie path runs unchanged.
|
|
50
|
+
*/
|
|
51
|
+
bearerToken?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Cookie policy for the SSE fetch. Defaults to `"include"` (the cookie
|
|
54
|
+
* path). Embed mode passes `"omit"`: the iframe is same-origin to
|
|
55
|
+
* Declarion and the auth middleware reads the cookie before the Bearer
|
|
56
|
+
* header, so a sent cookie would bypass the scoped embed token.
|
|
57
|
+
*/
|
|
58
|
+
credentials?: RequestCredentials;
|
|
45
59
|
}
|
|
46
60
|
export declare function streamEvents(options?: StreamEventsOptions): AsyncGenerator<SSEEvent>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LayoutNode, Action } from "../../types/schema";
|
|
1
|
+
import type { LayoutNode, Entity, Action } from "../../types/schema";
|
|
2
2
|
import type { LayoutContext } from "./LayoutRenderer";
|
|
3
3
|
interface AssociatedBlockProps {
|
|
4
4
|
node: LayoutNode;
|
|
@@ -12,5 +12,8 @@ export declare function resolveAssociatedToolbarActions(actions: Record<string,
|
|
|
12
12
|
code: string;
|
|
13
13
|
action: Action;
|
|
14
14
|
}>;
|
|
15
|
+
export declare function resolveAssociatedForeignKey(entities: Record<string, Entity>, parentEntityCode: string, assocCode: string): string | null;
|
|
16
|
+
export declare function associatedEditableColumns(entity: Entity, columns: string[], foreignKey: string): Set<string>;
|
|
17
|
+
export declare function associatedDraftEditableColumns(entity: Entity, columns: string[], foreignKey: string): Set<string>;
|
|
15
18
|
export declare function AssociatedBlock({ node, ctx }: AssociatedBlockProps): import("react/jsx-runtime").JSX.Element | null;
|
|
16
19
|
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { EmbedFileVariant } from "../../embed/file-src";
|
|
1
2
|
import type { HydratedFile } from "./types";
|
|
2
3
|
export interface FocalPoint {
|
|
3
4
|
x: number;
|
|
@@ -8,6 +9,7 @@ export declare function canPreviewFile(file: HydratedFile): boolean;
|
|
|
8
9
|
export declare function previewFileURL(file: HydratedFile): string | undefined;
|
|
9
10
|
export declare function downloadFileURL(file: HydratedFile): string | undefined;
|
|
10
11
|
export declare function preferredListThumbnailURL(file: HydratedFile | null, preferredDerivationKey?: string): string | undefined;
|
|
12
|
+
export declare function preferredThumbnailVariant(file: HydratedFile | null, preferredDerivationKey?: string): EmbedFileVariant;
|
|
11
13
|
export declare function readFocalPoint(file: HydratedFile | null): FocalPoint | null;
|
|
12
14
|
export declare function focalPointObjectPosition(point: FocalPoint | null): string | undefined;
|
|
13
15
|
export declare function withFocalPoint(file: HydratedFile, point: FocalPoint): HydratedFile;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type EmbedFileVariant } from "../../embed/file-src";
|
|
2
|
+
/**
|
|
3
|
+
* Reactively resolve a file `src` for a native `<img>` element.
|
|
4
|
+
*
|
|
5
|
+
* Outside embed token mode the hook returns `staticUrl` unchanged and does no
|
|
6
|
+
* work - the cookie carries auth and the browser loads the URL directly, so
|
|
7
|
+
* behavior is identical to passing `staticUrl` straight to `<img src>`.
|
|
8
|
+
*
|
|
9
|
+
* In embed token mode the hook resolves through `resolveEmbedFileSrc`:
|
|
10
|
+
* - a presigned (non-proxy) URL is used directly;
|
|
11
|
+
* - a proxy-backed file is fetched as bytes and rendered from a `blob:` URL.
|
|
12
|
+
* The hook returns `undefined` until the async resolution settles, so the
|
|
13
|
+
* widget shows its placeholder rather than a broken image during the fetch.
|
|
14
|
+
*
|
|
15
|
+
* `fileId` may be undefined when the widget is empty; the hook then returns
|
|
16
|
+
* `staticUrl` (also typically undefined) and does nothing.
|
|
17
|
+
*
|
|
18
|
+
* A `blob:` object URL is revoked when `fileId` / `variant` changes and on
|
|
19
|
+
* unmount. A `DerivationPendingError` (the derived endpoint returned 202)
|
|
20
|
+
* resolves to `undefined` so the caller can fall back to the primary file.
|
|
21
|
+
*/
|
|
22
|
+
export declare function useEmbedFileSrc(fileId: string | undefined, variant: EmbedFileVariant, staticUrl: string | undefined): string | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* An imperative opener for the download / preview buttons.
|
|
25
|
+
*
|
|
26
|
+
* `window.open(url)` cannot attach an `Authorization` header, so in embed
|
|
27
|
+
* token mode a click must FIRST resolve the file to a presigned or `blob:`
|
|
28
|
+
* URL and THEN open that. Outside embed mode the returned function opens the
|
|
29
|
+
* static URL directly, unchanged from before.
|
|
30
|
+
*
|
|
31
|
+
* The returned `open(fileId, variant, staticUrl)` function:
|
|
32
|
+
* - non-embed mode: `window.open(staticUrl)` synchronously, as before;
|
|
33
|
+
* - embed mode: resolves via `resolveEmbedFileSrc`, then `window.open` the
|
|
34
|
+
* result. A `blob:` URL opened this way is revoked on unmount (it must
|
|
35
|
+
* outlive the click so the new tab can load it; revoking immediately would
|
|
36
|
+
* break the open).
|
|
37
|
+
*
|
|
38
|
+
* Returns `{ open }`. `open` is a no-op when `fileId` / `staticUrl` is absent.
|
|
39
|
+
*/
|
|
40
|
+
export declare function useEmbedFileOpener(): {
|
|
41
|
+
open: (fileId: string | undefined, variant: EmbedFileVariant, staticUrl: string | undefined) => void;
|
|
42
|
+
};
|