@burdenoff/microfe-theculturepost 2026.704.2 → 2026.705.1
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/theculturepost/TheCulturePostRoot.js +63 -45
- package/dist/theculturepost/TheCulturePostRoot.js.map +1 -1
- package/dist/theculturepost/TheCulturePostRoutes.js +17 -9
- package/dist/theculturepost/TheCulturePostRoutes.js.map +1 -1
- package/dist/theculturepost/components/AddCitationDialog.js +131 -102
- package/dist/theculturepost/components/AddCitationDialog.js.map +1 -1
- package/dist/theculturepost/components/ArchiveItemCard.js +56 -43
- package/dist/theculturepost/components/ArchiveItemCard.js.map +1 -1
- package/dist/theculturepost/components/AudioPlayer.js +74 -0
- package/dist/theculturepost/components/AudioPlayer.js.map +1 -0
- package/dist/theculturepost/components/CitationList.js +215 -105
- package/dist/theculturepost/components/CitationList.js.map +1 -1
- package/dist/theculturepost/components/CollectionCard.js +32 -32
- package/dist/theculturepost/components/CollectionCard.js.map +1 -1
- package/dist/theculturepost/components/CreateArchiveItemDialog.js +133 -93
- package/dist/theculturepost/components/CreateArchiveItemDialog.js.map +1 -1
- package/dist/theculturepost/components/CreateCollectionDialog.js +91 -70
- package/dist/theculturepost/components/CreateCollectionDialog.js.map +1 -1
- package/dist/theculturepost/components/CreateStoryDialog.js +311 -132
- package/dist/theculturepost/components/CreateStoryDialog.js.map +1 -1
- package/dist/theculturepost/components/DecorativeCover.js +78 -0
- package/dist/theculturepost/components/DecorativeCover.js.map +1 -0
- package/dist/theculturepost/components/DecorativeDivider.js +45 -0
- package/dist/theculturepost/components/DecorativeDivider.js.map +1 -0
- package/dist/theculturepost/components/DiscussionCard.js +75 -60
- package/dist/theculturepost/components/DiscussionCard.js.map +1 -1
- package/dist/theculturepost/components/HistoryTimeline.js +115 -0
- package/dist/theculturepost/components/HistoryTimeline.js.map +1 -0
- package/dist/theculturepost/components/LifecycleStepper.js +33 -0
- package/dist/theculturepost/components/LifecycleStepper.js.map +1 -0
- package/dist/theculturepost/components/OpenDiscussionDialog.js +66 -56
- package/dist/theculturepost/components/OpenDiscussionDialog.js.map +1 -1
- package/dist/theculturepost/components/OralHistoryCard.js +6 -73
- package/dist/theculturepost/components/OverlayBadge.js +22 -0
- package/dist/theculturepost/components/OverlayBadge.js.map +1 -0
- package/dist/theculturepost/components/PageHero.js +62 -0
- package/dist/theculturepost/components/PageHero.js.map +1 -0
- package/dist/theculturepost/components/RecordOralHistoryDialog.js +306 -131
- package/dist/theculturepost/components/RecordOralHistoryDialog.js.map +1 -1
- package/dist/theculturepost/components/ReplyThread.js +268 -139
- package/dist/theculturepost/components/ReplyThread.js.map +1 -1
- package/dist/theculturepost/components/StatusBadge.js +9 -9
- package/dist/theculturepost/components/StatusBadge.js.map +1 -1
- package/dist/theculturepost/components/StoryCard.js +54 -49
- package/dist/theculturepost/components/StoryCard.js.map +1 -1
- package/dist/theculturepost/components/TopicCard.js +60 -41
- package/dist/theculturepost/components/TopicCard.js.map +1 -1
- package/dist/theculturepost/components/Waveform.js +31 -0
- package/dist/theculturepost/components/Waveform.js.map +1 -0
- package/dist/theculturepost/components/categoryMeta.js +20 -0
- package/dist/theculturepost/components/categoryMeta.js.map +1 -0
- package/dist/theculturepost/components/index.js +41 -0
- package/dist/theculturepost/components/viz/BarChart.js +53 -0
- package/dist/theculturepost/components/viz/BarChart.js.map +1 -0
- package/dist/theculturepost/components/viz/Donut.js +58 -0
- package/dist/theculturepost/components/viz/Donut.js.map +1 -0
- package/dist/theculturepost/components/viz/StatTile.js +35 -0
- package/dist/theculturepost/components/viz/StatTile.js.map +1 -0
- package/dist/theculturepost/components/viz/index.js +3 -0
- package/dist/theculturepost/pages/HomePage.js +247 -124
- package/dist/theculturepost/pages/HomePage.js.map +1 -1
- package/dist/theculturepost/pages/archive/ArchiveItemDetailPage.js +319 -127
- package/dist/theculturepost/pages/archive/ArchiveItemDetailPage.js.map +1 -1
- package/dist/theculturepost/pages/archive/ArchivePage.js +265 -180
- package/dist/theculturepost/pages/archive/ArchivePage.js.map +1 -1
- package/dist/theculturepost/pages/archive/CollectionDetailPage.js +158 -143
- package/dist/theculturepost/pages/archive/CollectionDetailPage.js.map +1 -1
- package/dist/theculturepost/pages/discussions/DiscussionDetailPage.js +155 -93
- package/dist/theculturepost/pages/discussions/DiscussionDetailPage.js.map +1 -1
- package/dist/theculturepost/pages/discussions/DiscussionsPage.js +16 -16
- package/dist/theculturepost/pages/discussions/DiscussionsPage.js.map +1 -1
- package/dist/theculturepost/pages/governance/GovernancePage.js +293 -0
- package/dist/theculturepost/pages/governance/GovernancePage.js.map +1 -0
- package/dist/theculturepost/pages/stories/StoriesPage.js +18 -18
- package/dist/theculturepost/pages/stories/StoriesPage.js.map +1 -1
- package/dist/theculturepost/pages/stories/StoryDetailPage.js +603 -285
- package/dist/theculturepost/pages/stories/StoryDetailPage.js.map +1 -1
- package/dist/theculturepost/pages/timeline/TimelinePage.js +162 -0
- package/dist/theculturepost/pages/timeline/TimelinePage.js.map +1 -0
- package/dist/theculturepost/pages/topics/TopicDetailPage.js +239 -163
- package/dist/theculturepost/pages/topics/TopicDetailPage.js.map +1 -1
- package/dist/theculturepost/pages/topics/TopicsPage.js +76 -74
- package/dist/theculturepost/pages/topics/TopicsPage.js.map +1 -1
- package/dist/theculturepost/styles/injectDisplayFont.js +11 -0
- package/dist/theculturepost/styles/injectDisplayFont.js.map +1 -0
- package/dist/theculturepost/utils/formatters.js +6 -1
- package/dist/theculturepost/utils/formatters.js.map +1 -1
- package/package.json +1 -1
- package/dist/theculturepost/components/OralHistoryCard.js.map +0 -1
|
@@ -1,204 +1,379 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { cn as e } from "../utils/cn.js";
|
|
2
|
+
import { FILE_FOLDERS as t, uploadImageToFiles as n } from "../hooks/useFiles.js";
|
|
3
|
+
import { formatEnumLabel as r } from "../utils/formatters.js";
|
|
4
|
+
import { useTcpCollections as i, useTcpRecordOralHistory as a } from "../hooks/useTcpApi.js";
|
|
4
5
|
import "../hooks/index.js";
|
|
5
|
-
import { describeError as
|
|
6
|
-
import {
|
|
7
|
-
import { useState as
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
6
|
+
import { describeError as o, notify as s } from "../lib/toast.js";
|
|
7
|
+
import { AudioPlayer as c } from "./AudioPlayer.js";
|
|
8
|
+
import { useRef as l, useState as u } from "react";
|
|
9
|
+
import { FileCheck2 as d, Loader2 as f, UploadCloud as p, X as m } from "lucide-react";
|
|
10
|
+
import { jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
11
|
+
import { Button as _, Input as v, Label as y, ResponsiveDialog as b, ResponsiveDialogContent as x, ResponsiveDialogDescription as S, ResponsiveDialogFooter as C, ResponsiveDialogHeader as w, ResponsiveDialogTitle as T, Select as E, SelectContent as D, SelectItem as O, SelectTrigger as k, SelectValue as A, Textarea as j } from "@burdenoff/fe-libs/ui";
|
|
10
12
|
//#region src/theculturepost/components/RecordOralHistoryDialog.tsx
|
|
11
|
-
|
|
13
|
+
function M({ folder: t, accept: r, hint: i, promptLabel: a, value: _, onUploaded: v, onClear: y, maxMb: b = 25, disabled: x = !1 }) {
|
|
14
|
+
let S = l(null), [C, w] = u(!1), [T, E] = u(!1), [D, O] = u(null), [k, A] = u(_);
|
|
15
|
+
k !== _ && (A(_), _ || O(null));
|
|
16
|
+
let j = b * 1024 * 1024, M = async (e) => {
|
|
17
|
+
if (!(!e || x)) {
|
|
18
|
+
if (e.size > j) {
|
|
19
|
+
s.error("File too large", `Please choose a file under ${b} MB.`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
E(!0);
|
|
23
|
+
try {
|
|
24
|
+
let r = await n(e, t), i = e.type.startsWith("image/"), a = e.type.startsWith("audio/") || e.type.startsWith("video/");
|
|
25
|
+
O(i ? "image" : a ? "audio" : "file"), v({
|
|
26
|
+
id: r.id,
|
|
27
|
+
url: r.url,
|
|
28
|
+
name: r.name
|
|
29
|
+
}, e), s.success("File uploaded", r.name);
|
|
30
|
+
} catch (e) {
|
|
31
|
+
s.error("Upload failed", o(e));
|
|
32
|
+
} finally {
|
|
33
|
+
E(!1);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}, N = /* @__PURE__ */ h("input", {
|
|
37
|
+
ref: S,
|
|
38
|
+
type: "file",
|
|
39
|
+
accept: r,
|
|
40
|
+
className: "sr-only",
|
|
41
|
+
disabled: x || T,
|
|
42
|
+
onChange: (e) => {
|
|
43
|
+
let t = e.target.files?.[0];
|
|
44
|
+
e.target.value = "", M(t);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
return _ ? /* @__PURE__ */ g("div", {
|
|
48
|
+
className: "flex flex-col gap-2",
|
|
49
|
+
children: [
|
|
50
|
+
D === "image" ? /* @__PURE__ */ h("div", {
|
|
51
|
+
className: "overflow-hidden rounded-xl border border-border-seam",
|
|
52
|
+
children: /* @__PURE__ */ h("img", {
|
|
53
|
+
src: _.url,
|
|
54
|
+
alt: _.name,
|
|
55
|
+
className: "max-h-48 w-full object-cover"
|
|
56
|
+
})
|
|
57
|
+
}) : D === "audio" ? /* @__PURE__ */ h(c, {
|
|
58
|
+
src: _.url,
|
|
59
|
+
seed: _.id
|
|
60
|
+
}) : /* @__PURE__ */ g("div", {
|
|
61
|
+
className: "flex items-center gap-2 rounded-xl border border-border-seam bg-bg-surface px-3 py-2 text-sm",
|
|
62
|
+
children: [/* @__PURE__ */ h(d, { className: "h-4 w-4 shrink-0 text-status-success-text" }), /* @__PURE__ */ h("span", {
|
|
63
|
+
className: "truncate text-text-primary",
|
|
64
|
+
children: _.name
|
|
65
|
+
})]
|
|
66
|
+
}),
|
|
67
|
+
/* @__PURE__ */ g("div", {
|
|
68
|
+
className: "flex items-center gap-4",
|
|
69
|
+
children: [/* @__PURE__ */ h("button", {
|
|
70
|
+
type: "button",
|
|
71
|
+
onClick: () => S.current?.click(),
|
|
72
|
+
disabled: x || T,
|
|
73
|
+
className: "text-sm font-medium text-accent hover:underline disabled:opacity-60",
|
|
74
|
+
children: T ? "Uploading…" : "Replace"
|
|
75
|
+
}), /* @__PURE__ */ g("button", {
|
|
76
|
+
type: "button",
|
|
77
|
+
onClick: y,
|
|
78
|
+
className: "inline-flex items-center gap-1 text-sm text-text-tertiary hover:text-text-primary",
|
|
79
|
+
children: [/* @__PURE__ */ h(m, { className: "h-3.5 w-3.5" }), " Remove"]
|
|
80
|
+
})]
|
|
81
|
+
}),
|
|
82
|
+
N
|
|
83
|
+
]
|
|
84
|
+
}) : /* @__PURE__ */ g("div", {
|
|
85
|
+
onDragOver: (e) => {
|
|
86
|
+
e.preventDefault(), x || w(!0);
|
|
87
|
+
},
|
|
88
|
+
onDragLeave: () => w(!1),
|
|
89
|
+
onDrop: (e) => {
|
|
90
|
+
e.preventDefault(), w(!1), M(e.dataTransfer.files?.[0]);
|
|
91
|
+
},
|
|
92
|
+
className: e("flex flex-col items-center justify-center gap-2 rounded-xl border border-dashed px-4 py-6 text-center transition-colors", C ? "border-accent bg-[var(--color-accent-soft)]" : "border-border-seam bg-bg-surface", (x || T) && "opacity-60"),
|
|
93
|
+
children: [
|
|
94
|
+
N,
|
|
95
|
+
T ? /* @__PURE__ */ h(f, {
|
|
96
|
+
className: "h-6 w-6 animate-spin text-text-tertiary",
|
|
97
|
+
"aria-hidden": "true"
|
|
98
|
+
}) : /* @__PURE__ */ h(p, {
|
|
99
|
+
className: "h-6 w-6 text-text-tertiary",
|
|
100
|
+
"aria-hidden": "true"
|
|
101
|
+
}),
|
|
102
|
+
/* @__PURE__ */ h("p", {
|
|
103
|
+
className: "text-sm text-text-secondary",
|
|
104
|
+
children: T ? "Uploading…" : a
|
|
105
|
+
}),
|
|
106
|
+
!T && /* @__PURE__ */ g("button", {
|
|
107
|
+
type: "button",
|
|
108
|
+
onClick: () => S.current?.click(),
|
|
109
|
+
disabled: x,
|
|
110
|
+
className: "inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-60",
|
|
111
|
+
children: [/* @__PURE__ */ h(p, { className: "h-4 w-4" }), " Browse files"]
|
|
112
|
+
}),
|
|
113
|
+
/* @__PURE__ */ h("p", {
|
|
114
|
+
className: "text-xs text-text-tertiary",
|
|
115
|
+
children: i
|
|
116
|
+
})
|
|
117
|
+
]
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
var N = [
|
|
12
121
|
"COMMUNITY",
|
|
13
122
|
"PUBLIC",
|
|
14
123
|
"RESTRICTED",
|
|
15
124
|
"EMBARGOED"
|
|
16
|
-
],
|
|
125
|
+
], ee = {
|
|
126
|
+
PUBLIC: "Visible to everyone.",
|
|
127
|
+
COMMUNITY: "Available to community members.",
|
|
128
|
+
RESTRICTED: "Visible to selected groups.",
|
|
129
|
+
EMBARGOED: "Hidden until a future date."
|
|
130
|
+
}, te = 160, ne = 120, re = 60, ie = 300, P = 5e3, ae = (e) => {
|
|
17
131
|
if (!e) return null;
|
|
18
132
|
let t = new Date(e);
|
|
19
133
|
return Number.isNaN(t.getTime()) ? null : t.toISOString();
|
|
20
134
|
};
|
|
21
|
-
function
|
|
22
|
-
let
|
|
23
|
-
|
|
135
|
+
function F({ open: e, onOpenChange: n, collectionId: c, onCreated: l }) {
|
|
136
|
+
let d = a(), f = i({ limit: 100 }), [p, m] = u(""), [F, L] = u(""), [R, z] = u(""), [B, V] = u(""), [H, U] = u(""), [W, G] = u("COMMUNITY"), [K, q] = u(c ?? ""), [J, Y] = u(null), [X, Z] = u(""), [oe, se] = u(""), [Q, $] = u(null), ce = () => {
|
|
137
|
+
m(""), L(""), z(""), V(""), U(""), G("COMMUNITY"), q(c ?? ""), Y(null), Z(""), se(""), $(null);
|
|
138
|
+
}, le = (e) => {
|
|
139
|
+
try {
|
|
140
|
+
let t = new Audio();
|
|
141
|
+
t.preload = "metadata", t.onloadedmetadata = () => {
|
|
142
|
+
Number.isFinite(t.duration) && t.duration > 0 && Z((e) => e || String(Math.round(t.duration)));
|
|
143
|
+
}, t.src = e;
|
|
144
|
+
} catch {}
|
|
24
145
|
};
|
|
25
|
-
return /* @__PURE__ */
|
|
26
|
-
open:
|
|
27
|
-
onOpenChange:
|
|
28
|
-
children: /* @__PURE__ */
|
|
29
|
-
className: "sm:max-w-
|
|
146
|
+
return /* @__PURE__ */ h(b, {
|
|
147
|
+
open: e,
|
|
148
|
+
onOpenChange: n,
|
|
149
|
+
children: /* @__PURE__ */ g(x, {
|
|
150
|
+
className: "sm:max-w-2xl",
|
|
30
151
|
children: [
|
|
31
|
-
/* @__PURE__ */
|
|
32
|
-
/* @__PURE__ */
|
|
33
|
-
className: "flex max-h-[
|
|
152
|
+
/* @__PURE__ */ g(w, { children: [/* @__PURE__ */ h(T, { children: "Record an oral history" }), /* @__PURE__ */ h(S, { children: "Preserve a first-person account. Attach the recording and a transcript for the archive." })] }),
|
|
153
|
+
/* @__PURE__ */ g("div", {
|
|
154
|
+
className: "flex max-h-[64vh] flex-col gap-4 overflow-y-auto py-2",
|
|
34
155
|
children: [
|
|
35
|
-
/* @__PURE__ */
|
|
156
|
+
/* @__PURE__ */ g("div", {
|
|
36
157
|
className: "grid gap-1.5",
|
|
37
|
-
children: [/* @__PURE__ */
|
|
38
|
-
folder:
|
|
158
|
+
children: [/* @__PURE__ */ h(y, { children: "Recording" }), /* @__PURE__ */ h(M, {
|
|
159
|
+
folder: t.recordings,
|
|
39
160
|
accept: "audio/*,video/*",
|
|
40
|
-
|
|
41
|
-
hint: "Audio or video
|
|
42
|
-
|
|
43
|
-
onUploaded:
|
|
44
|
-
|
|
161
|
+
promptLabel: "Drag and drop an audio or video file here",
|
|
162
|
+
hint: "Audio or video, up to 25 MB.",
|
|
163
|
+
value: J,
|
|
164
|
+
onUploaded: (e) => {
|
|
165
|
+
Y(e), le(e.url);
|
|
166
|
+
},
|
|
167
|
+
onClear: () => Y(null)
|
|
45
168
|
})]
|
|
46
169
|
}),
|
|
47
|
-
/* @__PURE__ */
|
|
48
|
-
className: "grid gap-
|
|
49
|
-
children: [/* @__PURE__ */
|
|
50
|
-
|
|
51
|
-
children:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
170
|
+
/* @__PURE__ */ g("div", {
|
|
171
|
+
className: "grid gap-3 sm:grid-cols-2",
|
|
172
|
+
children: [/* @__PURE__ */ g("div", {
|
|
173
|
+
className: "grid gap-1.5",
|
|
174
|
+
children: [
|
|
175
|
+
/* @__PURE__ */ g(y, {
|
|
176
|
+
htmlFor: "oral-title",
|
|
177
|
+
children: ["Title ", /* @__PURE__ */ h("span", {
|
|
178
|
+
className: "text-status-error-text",
|
|
179
|
+
children: "*"
|
|
180
|
+
})]
|
|
181
|
+
}),
|
|
182
|
+
/* @__PURE__ */ h(v, {
|
|
183
|
+
id: "oral-title",
|
|
184
|
+
value: p,
|
|
185
|
+
maxLength: te,
|
|
186
|
+
onChange: (e) => m(e.target.value),
|
|
187
|
+
placeholder: "e.g. A weaver remembers the looms"
|
|
188
|
+
}),
|
|
189
|
+
/* @__PURE__ */ h(I, {
|
|
190
|
+
length: p.length,
|
|
191
|
+
max: te
|
|
192
|
+
})
|
|
193
|
+
]
|
|
194
|
+
}), /* @__PURE__ */ g("div", {
|
|
62
195
|
className: "grid gap-1.5",
|
|
63
|
-
children: [/* @__PURE__ */
|
|
196
|
+
children: [/* @__PURE__ */ h(y, {
|
|
64
197
|
htmlFor: "oral-narrator",
|
|
65
198
|
children: "Narrator"
|
|
66
|
-
}), /* @__PURE__ */
|
|
199
|
+
}), /* @__PURE__ */ h(v, {
|
|
67
200
|
id: "oral-narrator",
|
|
68
|
-
value: R,
|
|
69
|
-
onChange: (e) => z(e.target.value),
|
|
70
|
-
placeholder: "Full name"
|
|
71
|
-
})]
|
|
72
|
-
}), /* @__PURE__ */ l("div", {
|
|
73
|
-
className: "grid gap-1.5",
|
|
74
|
-
children: [/* @__PURE__ */ c(f, {
|
|
75
|
-
htmlFor: "oral-language",
|
|
76
|
-
children: "Language"
|
|
77
|
-
}), /* @__PURE__ */ c(d, {
|
|
78
|
-
id: "oral-language",
|
|
79
201
|
value: B,
|
|
202
|
+
maxLength: ne,
|
|
80
203
|
onChange: (e) => V(e.target.value),
|
|
81
|
-
placeholder: "
|
|
204
|
+
placeholder: "Full name"
|
|
82
205
|
})]
|
|
83
206
|
})]
|
|
84
207
|
}),
|
|
85
|
-
/* @__PURE__ */
|
|
86
|
-
className: "grid grid-cols-2
|
|
87
|
-
children: [/* @__PURE__ */
|
|
208
|
+
/* @__PURE__ */ g("div", {
|
|
209
|
+
className: "grid gap-3 sm:grid-cols-2",
|
|
210
|
+
children: [/* @__PURE__ */ g("div", {
|
|
88
211
|
className: "grid gap-1.5",
|
|
89
|
-
children: [/* @__PURE__ */
|
|
212
|
+
children: [/* @__PURE__ */ h(y, {
|
|
213
|
+
htmlFor: "oral-language",
|
|
214
|
+
children: "Language"
|
|
215
|
+
}), /* @__PURE__ */ h(v, {
|
|
216
|
+
id: "oral-language",
|
|
90
217
|
value: H,
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
children: t(e)
|
|
95
|
-
}, e)) })]
|
|
218
|
+
maxLength: re,
|
|
219
|
+
onChange: (e) => U(e.target.value),
|
|
220
|
+
placeholder: "e.g. Tamil"
|
|
96
221
|
})]
|
|
97
|
-
}), /* @__PURE__ */
|
|
222
|
+
}), /* @__PURE__ */ g("div", {
|
|
98
223
|
className: "grid gap-1.5",
|
|
99
|
-
children: [/* @__PURE__ */
|
|
224
|
+
children: [/* @__PURE__ */ h(y, {
|
|
100
225
|
htmlFor: "oral-duration",
|
|
101
226
|
children: "Duration (seconds)"
|
|
102
|
-
}), /* @__PURE__ */
|
|
227
|
+
}), /* @__PURE__ */ h(v, {
|
|
103
228
|
id: "oral-duration",
|
|
104
229
|
type: "number",
|
|
105
230
|
min: 0,
|
|
106
|
-
value:
|
|
107
|
-
onChange: (e) =>
|
|
108
|
-
placeholder: "
|
|
231
|
+
value: X,
|
|
232
|
+
onChange: (e) => Z(e.target.value),
|
|
233
|
+
placeholder: "Auto-filled from the recording"
|
|
109
234
|
})]
|
|
110
235
|
})]
|
|
111
236
|
}),
|
|
112
|
-
|
|
237
|
+
/* @__PURE__ */ g("div", {
|
|
238
|
+
className: "grid gap-3 sm:grid-cols-2",
|
|
239
|
+
children: [/* @__PURE__ */ g("div", {
|
|
240
|
+
className: "grid gap-1.5",
|
|
241
|
+
children: [/* @__PURE__ */ h(y, {
|
|
242
|
+
htmlFor: "oral-recorded",
|
|
243
|
+
children: "Recorded on"
|
|
244
|
+
}), /* @__PURE__ */ h(v, {
|
|
245
|
+
id: "oral-recorded",
|
|
246
|
+
type: "date",
|
|
247
|
+
value: oe,
|
|
248
|
+
onChange: (e) => se(e.target.value)
|
|
249
|
+
})]
|
|
250
|
+
}), /* @__PURE__ */ g("div", {
|
|
251
|
+
className: "grid gap-1.5",
|
|
252
|
+
children: [
|
|
253
|
+
/* @__PURE__ */ h(y, { children: "Access level" }),
|
|
254
|
+
/* @__PURE__ */ g(E, {
|
|
255
|
+
value: W,
|
|
256
|
+
onValueChange: (e) => G(e),
|
|
257
|
+
children: [/* @__PURE__ */ h(k, {
|
|
258
|
+
"aria-label": "Access level",
|
|
259
|
+
children: /* @__PURE__ */ h(A, {})
|
|
260
|
+
}), /* @__PURE__ */ h(D, { children: N.map((e) => /* @__PURE__ */ h(O, {
|
|
261
|
+
value: e,
|
|
262
|
+
children: r(e)
|
|
263
|
+
}, e)) })]
|
|
264
|
+
}),
|
|
265
|
+
/* @__PURE__ */ h("p", {
|
|
266
|
+
className: "text-xs text-text-tertiary",
|
|
267
|
+
children: ee[W]
|
|
268
|
+
})
|
|
269
|
+
]
|
|
270
|
+
})]
|
|
271
|
+
}),
|
|
272
|
+
!c && /* @__PURE__ */ g("div", {
|
|
113
273
|
className: "grid gap-1.5",
|
|
114
|
-
children: [/* @__PURE__ */
|
|
115
|
-
value:
|
|
116
|
-
onValueChange:
|
|
117
|
-
children: [/* @__PURE__ */
|
|
274
|
+
children: [/* @__PURE__ */ h(y, { children: "Collection" }), /* @__PURE__ */ g(E, {
|
|
275
|
+
value: K,
|
|
276
|
+
onValueChange: q,
|
|
277
|
+
children: [/* @__PURE__ */ h(k, { children: /* @__PURE__ */ h(A, { placeholder: "Add to a collection (optional)" }) }), /* @__PURE__ */ h(D, { children: (f.data ?? []).map((e) => /* @__PURE__ */ h(O, {
|
|
118
278
|
value: e.id,
|
|
119
279
|
children: e.name
|
|
120
280
|
}, e.id)) })]
|
|
121
281
|
})]
|
|
122
282
|
}),
|
|
123
|
-
/* @__PURE__ */
|
|
283
|
+
/* @__PURE__ */ g("div", {
|
|
124
284
|
className: "grid gap-1.5",
|
|
125
|
-
children: [
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
285
|
+
children: [
|
|
286
|
+
/* @__PURE__ */ h(y, {
|
|
287
|
+
htmlFor: "oral-summary",
|
|
288
|
+
children: "Summary"
|
|
289
|
+
}),
|
|
290
|
+
/* @__PURE__ */ h(j, {
|
|
291
|
+
id: "oral-summary",
|
|
292
|
+
value: F,
|
|
293
|
+
maxLength: ie,
|
|
294
|
+
onChange: (e) => L(e.target.value),
|
|
295
|
+
placeholder: "A short description of what this account covers.",
|
|
296
|
+
rows: 3
|
|
297
|
+
}),
|
|
298
|
+
/* @__PURE__ */ h(I, {
|
|
299
|
+
length: F.length,
|
|
300
|
+
max: ie
|
|
301
|
+
})
|
|
302
|
+
]
|
|
134
303
|
}),
|
|
135
|
-
/* @__PURE__ */
|
|
304
|
+
/* @__PURE__ */ g("div", {
|
|
136
305
|
className: "grid gap-1.5",
|
|
137
|
-
children: [
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
onChange: (e) => L(e.target.value),
|
|
156
|
-
placeholder: "The transcribed account (line breaks are preserved).",
|
|
157
|
-
rows: 4
|
|
158
|
-
})]
|
|
306
|
+
children: [
|
|
307
|
+
/* @__PURE__ */ h(y, {
|
|
308
|
+
htmlFor: "oral-transcript",
|
|
309
|
+
children: "Transcript"
|
|
310
|
+
}),
|
|
311
|
+
/* @__PURE__ */ h(j, {
|
|
312
|
+
id: "oral-transcript",
|
|
313
|
+
value: R,
|
|
314
|
+
maxLength: P,
|
|
315
|
+
onChange: (e) => z(e.target.value),
|
|
316
|
+
placeholder: "The transcribed account (line breaks are preserved).",
|
|
317
|
+
rows: 5
|
|
318
|
+
}),
|
|
319
|
+
/* @__PURE__ */ h(I, {
|
|
320
|
+
length: R.length,
|
|
321
|
+
max: P
|
|
322
|
+
})
|
|
323
|
+
]
|
|
159
324
|
}),
|
|
160
|
-
Q && /* @__PURE__ */
|
|
325
|
+
Q && /* @__PURE__ */ h("p", {
|
|
161
326
|
className: "text-sm text-status-error-text",
|
|
162
327
|
children: Q
|
|
163
328
|
})
|
|
164
329
|
]
|
|
165
330
|
}),
|
|
166
|
-
/* @__PURE__ */
|
|
331
|
+
/* @__PURE__ */ g(C, { children: [/* @__PURE__ */ h(_, {
|
|
167
332
|
variant: "outline",
|
|
168
|
-
onClick: () =>
|
|
333
|
+
onClick: () => n(!1),
|
|
169
334
|
children: "Cancel"
|
|
170
|
-
}), /* @__PURE__ */
|
|
335
|
+
}), /* @__PURE__ */ h(_, {
|
|
171
336
|
onClick: async () => {
|
|
172
|
-
if (!
|
|
337
|
+
if (!p.trim()) {
|
|
173
338
|
$("A title is required.");
|
|
174
339
|
return;
|
|
175
340
|
}
|
|
176
341
|
try {
|
|
177
|
-
let e = await
|
|
178
|
-
title:
|
|
179
|
-
summary:
|
|
180
|
-
transcript:
|
|
181
|
-
narratorName:
|
|
182
|
-
language:
|
|
183
|
-
accessLevel:
|
|
184
|
-
collectionId:
|
|
185
|
-
recordingFileId:
|
|
186
|
-
durationSecs:
|
|
187
|
-
recordedAt:
|
|
342
|
+
let e = await d.mutateAsync({
|
|
343
|
+
title: p.trim(),
|
|
344
|
+
summary: F.trim() || null,
|
|
345
|
+
transcript: R.trim() || null,
|
|
346
|
+
narratorName: B.trim() || null,
|
|
347
|
+
language: H.trim() || null,
|
|
348
|
+
accessLevel: W,
|
|
349
|
+
collectionId: K || null,
|
|
350
|
+
recordingFileId: J?.id ?? null,
|
|
351
|
+
durationSecs: X ? Number(X) : null,
|
|
352
|
+
recordedAt: ae(oe)
|
|
188
353
|
});
|
|
189
|
-
|
|
354
|
+
s.success("Oral history recorded", e.title), n(!1), ce(), l?.(e);
|
|
190
355
|
} catch (e) {
|
|
191
|
-
$(
|
|
356
|
+
$(o(e));
|
|
192
357
|
}
|
|
193
358
|
},
|
|
194
|
-
disabled:
|
|
359
|
+
disabled: d.isPending,
|
|
195
360
|
children: "Record oral history"
|
|
196
361
|
})] })
|
|
197
362
|
]
|
|
198
363
|
})
|
|
199
364
|
});
|
|
200
365
|
}
|
|
366
|
+
function I({ length: e, max: t }) {
|
|
367
|
+
return /* @__PURE__ */ g("p", {
|
|
368
|
+
className: "text-right text-xs tabular-nums text-text-tertiary",
|
|
369
|
+
children: [
|
|
370
|
+
e,
|
|
371
|
+
" / ",
|
|
372
|
+
t
|
|
373
|
+
]
|
|
374
|
+
});
|
|
375
|
+
}
|
|
201
376
|
//#endregion
|
|
202
|
-
export {
|
|
377
|
+
export { I as Counter, M as MediaDropzone, F as RecordOralHistoryDialog };
|
|
203
378
|
|
|
204
379
|
//# sourceMappingURL=RecordOralHistoryDialog.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RecordOralHistoryDialog.js","names":[],"sources":["../../../src/theculturepost/components/RecordOralHistoryDialog.tsx"],"sourcesContent":["/**\n * RecordOralHistoryDialog — capture an oral history recording.\n * @module theculturepost/components\n *\n * Writes `tcpRecordOralHistory`. The recording (audio/video) is uploaded to\n * wspace-files-svc; `narratorName` is a plain string stored directly.\n */\n\nimport { useState } from 'react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { FileUploadField } from './FileUploadField';\nimport { useTcpRecordOralHistory, useTcpCollections, FILE_FOLDERS } from '../hooks';\nimport { formatEnumLabel } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { TcpOralHistory, TcpAccessLevel } from '../types';\n\nconst ACCESS_LEVELS: TcpAccessLevel[] = ['COMMUNITY', 'PUBLIC', 'RESTRICTED', 'EMBARGOED'];\n\nconst dateToIso = (value: string): string | null => {\n if (!value) return null;\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? null : d.toISOString();\n};\n\ninterface RecordOralHistoryDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n /** Pre-select a collection. */\n collectionId?: string;\n onCreated?: (oralHistory: TcpOralHistory) => void;\n}\n\nexport function RecordOralHistoryDialog({\n open,\n onOpenChange,\n collectionId,\n onCreated,\n}: RecordOralHistoryDialogProps) {\n const record = useTcpRecordOralHistory();\n const collectionsQuery = useTcpCollections({ limit: 100 });\n\n const [title, setTitle] = useState('');\n const [summary, setSummary] = useState('');\n const [transcript, setTranscript] = useState('');\n const [narratorName, setNarratorName] = useState('');\n const [language, setLanguage] = useState('');\n const [accessLevel, setAccessLevel] = useState<TcpAccessLevel>('COMMUNITY');\n const [pickedCollection, setPickedCollection] = useState(collectionId ?? '');\n const [recordingFileId, setRecordingFileId] = useState<string | null>(null);\n const [durationSecs, setDurationSecs] = useState('');\n const [recordedAt, setRecordedAt] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n const reset = () => {\n setTitle('');\n setSummary('');\n setTranscript('');\n setNarratorName('');\n setLanguage('');\n setAccessLevel('COMMUNITY');\n setPickedCollection(collectionId ?? '');\n setRecordingFileId(null);\n setDurationSecs('');\n setRecordedAt('');\n setError(null);\n };\n\n const submit = async () => {\n if (!title.trim()) {\n setError('A title is required.');\n return;\n }\n try {\n const created = await record.mutateAsync({\n title: title.trim(),\n summary: summary.trim() || null,\n transcript: transcript.trim() || null,\n narratorName: narratorName.trim() || null,\n language: language.trim() || null,\n accessLevel,\n collectionId: pickedCollection || null,\n recordingFileId,\n durationSecs: durationSecs ? Number(durationSecs) : null,\n recordedAt: dateToIso(recordedAt),\n });\n notify.success('Oral history recorded', created.title);\n onOpenChange(false);\n reset();\n onCreated?.(created);\n } catch (err) {\n setError(describeError(err));\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"sm:max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Record an oral history</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Preserve a first-person account. Attach the recording and a transcript for the archive.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"flex max-h-[62vh] flex-col gap-4 overflow-y-auto py-2\">\n <div className=\"grid gap-1.5\">\n <Label>Recording</Label>\n <FileUploadField\n folder={FILE_FOLDERS.recordings}\n accept=\"audio/*,video/*\"\n label=\"Upload recording\"\n hint=\"Audio or video. Up to 25 MB.\"\n fileId={recordingFileId}\n onUploaded={setRecordingFileId}\n onCleared={() => setRecordingFileId(null)}\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"oral-title\">Title</Label>\n <Input\n id=\"oral-title\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n placeholder=\"e.g. A weaver remembers the looms\"\n />\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"oral-narrator\">Narrator</Label>\n <Input\n id=\"oral-narrator\"\n value={narratorName}\n onChange={(e) => setNarratorName(e.target.value)}\n placeholder=\"Full name\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"oral-language\">Language</Label>\n <Input\n id=\"oral-language\"\n value={language}\n onChange={(e) => setLanguage(e.target.value)}\n placeholder=\"e.g. Tamil\"\n />\n </div>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"grid gap-1.5\">\n <Label>Access level</Label>\n <Select\n value={accessLevel}\n onValueChange={(v) => setAccessLevel(v as TcpAccessLevel)}\n >\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {ACCESS_LEVELS.map((a) => (\n <SelectItem key={a} value={a}>\n {formatEnumLabel(a)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"oral-duration\">Duration (seconds)</Label>\n <Input\n id=\"oral-duration\"\n type=\"number\"\n min={0}\n value={durationSecs}\n onChange={(e) => setDurationSecs(e.target.value)}\n placeholder=\"Optional\"\n />\n </div>\n </div>\n {!collectionId && (\n <div className=\"grid gap-1.5\">\n <Label>Collection</Label>\n <Select value={pickedCollection} onValueChange={setPickedCollection}>\n <SelectTrigger>\n <SelectValue placeholder=\"Add to a collection (optional)\" />\n </SelectTrigger>\n <SelectContent>\n {(collectionsQuery.data ?? []).map((collection) => (\n <SelectItem key={collection.id} value={collection.id}>\n {collection.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n )}\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"oral-recorded\">Recorded on</Label>\n <Input\n id=\"oral-recorded\"\n type=\"date\"\n value={recordedAt}\n onChange={(e) => setRecordedAt(e.target.value)}\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"oral-summary\">Summary</Label>\n <Textarea\n id=\"oral-summary\"\n value={summary}\n onChange={(e) => setSummary(e.target.value)}\n rows={2}\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"oral-transcript\">Transcript</Label>\n <Textarea\n id=\"oral-transcript\"\n value={transcript}\n onChange={(e) => setTranscript(e.target.value)}\n placeholder=\"The transcribed account (line breaks are preserved).\"\n rows={4}\n />\n </div>\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => onOpenChange(false)}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={record.isPending}>\n Record oral history\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"RecordOralHistoryDialog.js","names":[],"sources":["../../../src/theculturepost/components/RecordOralHistoryDialog.tsx"],"sourcesContent":["/**\n * RecordOralHistoryDialog — capture an oral history recording.\n * @module theculturepost/components\n *\n * Writes `tcpRecordOralHistory`. The recording (audio/video) is uploaded to\n * wspace-files-svc via the shared {@link MediaDropzone} (drag-drop or browse),\n * and previewed inline with the foundation {@link AudioPlayer}. `narratorName`\n * is a plain string stored directly — the schema has NO narrator portrait/age,\n * so none is ever rendered. All `TcpCreateOralHistoryInput` fields are captured.\n */\n\nimport { useRef, useState } from 'react';\nimport { UploadCloud, Loader2, FileCheck2, X } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { AudioPlayer } from './AudioPlayer';\nimport { cn } from '../utils/cn';\nimport {\n uploadImageToFiles,\n useTcpRecordOralHistory,\n useTcpCollections,\n FILE_FOLDERS,\n} from '../hooks';\nimport { formatEnumLabel } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { TcpOralHistory, TcpAccessLevel } from '../types';\n\n// ---------------------------------------------------------------------------\n// MediaDropzone — a shared drag-drop uploader (reused by the archive dialogs).\n// ---------------------------------------------------------------------------\n\n/** A file uploaded to wspace-files-svc: id (persisted) + presigned url + name. */\nexport interface UploadedRef {\n id: string;\n url: string;\n name: string;\n}\n\ninterface MediaDropzoneProps {\n /** Destination folder in wspace-files-svc (see FILE_FOLDERS). */\n folder: string;\n /** HTML accept attribute (e.g. 'audio/*,video/*'). */\n accept: string;\n /** Muted helper line under the drop prompt. */\n hint: string;\n /** Large prompt shown in the empty dropzone. */\n promptLabel: string;\n /** Current uploaded file (controlled so the parent can reset on submit). */\n value: UploadedRef | null;\n /** Called after a successful upload; `file` is provided for metadata probes. */\n onUploaded: (ref: UploadedRef, file: File) => void;\n onClear: () => void;\n /** Max upload size in MB. */\n maxMb?: number;\n disabled?: boolean;\n}\n\n/**\n * A token-only drag-and-drop file uploader with an inline preview: images show\n * a thumbnail, audio/video show the {@link AudioPlayer}, everything else shows a\n * filename chip. Uploads go straight to wspace-files-svc — only the id is kept.\n */\nexport function MediaDropzone({\n folder,\n accept,\n hint,\n promptLabel,\n value,\n onUploaded,\n onClear,\n maxMb = 25,\n disabled = false,\n}: MediaDropzoneProps) {\n const inputRef = useRef<HTMLInputElement | null>(null);\n const [dragging, setDragging] = useState(false);\n const [uploading, setUploading] = useState(false);\n const [previewType, setPreviewType] = useState<'image' | 'audio' | 'file' | null>(null);\n\n // Keep the preview kind in sync when the parent clears the value (e.g. on reset).\n const [trackedValue, setTrackedValue] = useState(value);\n if (trackedValue !== value) {\n setTrackedValue(value);\n if (!value) setPreviewType(null);\n }\n\n const maxBytes = maxMb * 1024 * 1024;\n\n const ingest = async (file: File | undefined) => {\n if (!file || disabled) return;\n if (file.size > maxBytes) {\n notify.error('File too large', `Please choose a file under ${maxMb} MB.`);\n return;\n }\n setUploading(true);\n try {\n const uploaded = await uploadImageToFiles(file, folder);\n const isImage = file.type.startsWith('image/');\n const isAv = file.type.startsWith('audio/') || file.type.startsWith('video/');\n setPreviewType(isImage ? 'image' : isAv ? 'audio' : 'file');\n onUploaded({ id: uploaded.id, url: uploaded.url, name: uploaded.name }, file);\n notify.success('File uploaded', uploaded.name);\n } catch (err) {\n notify.error('Upload failed', describeError(err));\n } finally {\n setUploading(false);\n }\n };\n\n const hiddenInput = (\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n className=\"sr-only\"\n disabled={disabled || uploading}\n onChange={(e) => {\n const file = e.target.files?.[0];\n e.target.value = '';\n void ingest(file);\n }}\n />\n );\n\n if (value) {\n return (\n <div className=\"flex flex-col gap-2\">\n {previewType === 'image' ? (\n <div className=\"overflow-hidden rounded-xl border border-border-seam\">\n <img src={value.url} alt={value.name} className=\"max-h-48 w-full object-cover\" />\n </div>\n ) : previewType === 'audio' ? (\n <AudioPlayer src={value.url} seed={value.id} />\n ) : (\n <div className=\"flex items-center gap-2 rounded-xl border border-border-seam bg-bg-surface px-3 py-2 text-sm\">\n <FileCheck2 className=\"h-4 w-4 shrink-0 text-status-success-text\" />\n <span className=\"truncate text-text-primary\">{value.name}</span>\n </div>\n )}\n <div className=\"flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => inputRef.current?.click()}\n disabled={disabled || uploading}\n className=\"text-sm font-medium text-accent hover:underline disabled:opacity-60\"\n >\n {uploading ? 'Uploading…' : 'Replace'}\n </button>\n <button\n type=\"button\"\n onClick={onClear}\n className=\"inline-flex items-center gap-1 text-sm text-text-tertiary hover:text-text-primary\"\n >\n <X className=\"h-3.5 w-3.5\" /> Remove\n </button>\n </div>\n {hiddenInput}\n </div>\n );\n }\n\n return (\n <div\n onDragOver={(e) => {\n e.preventDefault();\n if (!disabled) setDragging(true);\n }}\n onDragLeave={() => setDragging(false)}\n onDrop={(e) => {\n e.preventDefault();\n setDragging(false);\n void ingest(e.dataTransfer.files?.[0]);\n }}\n className={cn(\n 'flex flex-col items-center justify-center gap-2 rounded-xl border border-dashed px-4 py-6 text-center transition-colors',\n dragging\n ? 'border-accent bg-[var(--color-accent-soft)]'\n : 'border-border-seam bg-bg-surface',\n (disabled || uploading) && 'opacity-60'\n )}\n >\n {hiddenInput}\n {uploading ? (\n <Loader2 className=\"h-6 w-6 animate-spin text-text-tertiary\" aria-hidden=\"true\" />\n ) : (\n <UploadCloud className=\"h-6 w-6 text-text-tertiary\" aria-hidden=\"true\" />\n )}\n <p className=\"text-sm text-text-secondary\">{uploading ? 'Uploading…' : promptLabel}</p>\n {!uploading && (\n <button\n type=\"button\"\n onClick={() => inputRef.current?.click()}\n disabled={disabled}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-60\"\n >\n <UploadCloud className=\"h-4 w-4\" /> Browse files\n </button>\n )}\n <p className=\"text-xs text-text-tertiary\">{hint}</p>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Access levels + copy\n// ---------------------------------------------------------------------------\n\nconst ACCESS_LEVELS: TcpAccessLevel[] = ['COMMUNITY', 'PUBLIC', 'RESTRICTED', 'EMBARGOED'];\n\nconst ACCESS_HELP: Record<TcpAccessLevel, string> = {\n PUBLIC: 'Visible to everyone.',\n COMMUNITY: 'Available to community members.',\n RESTRICTED: 'Visible to selected groups.',\n EMBARGOED: 'Hidden until a future date.',\n};\n\nconst TITLE_MAX = 160;\nconst NARRATOR_MAX = 120;\nconst LANGUAGE_MAX = 60;\nconst SUMMARY_MAX = 300;\nconst TRANSCRIPT_MAX = 5000;\n\nconst dateToIso = (value: string): string | null => {\n if (!value) return null;\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? null : d.toISOString();\n};\n\ninterface RecordOralHistoryDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n /** Pre-select a collection. */\n collectionId?: string;\n onCreated?: (oralHistory: TcpOralHistory) => void;\n}\n\nexport function RecordOralHistoryDialog({\n open,\n onOpenChange,\n collectionId,\n onCreated,\n}: RecordOralHistoryDialogProps) {\n const record = useTcpRecordOralHistory();\n const collectionsQuery = useTcpCollections({ limit: 100 });\n\n const [title, setTitle] = useState('');\n const [summary, setSummary] = useState('');\n const [transcript, setTranscript] = useState('');\n const [narratorName, setNarratorName] = useState('');\n const [language, setLanguage] = useState('');\n const [accessLevel, setAccessLevel] = useState<TcpAccessLevel>('COMMUNITY');\n const [pickedCollection, setPickedCollection] = useState(collectionId ?? '');\n const [recording, setRecording] = useState<UploadedRef | null>(null);\n const [durationSecs, setDurationSecs] = useState('');\n const [recordedAt, setRecordedAt] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n const reset = () => {\n setTitle('');\n setSummary('');\n setTranscript('');\n setNarratorName('');\n setLanguage('');\n setAccessLevel('COMMUNITY');\n setPickedCollection(collectionId ?? '');\n setRecording(null);\n setDurationSecs('');\n setRecordedAt('');\n setError(null);\n };\n\n /** Best-effort: read the recording's real duration to pre-fill the field. */\n const probeDuration = (url: string) => {\n try {\n const probe = new Audio();\n probe.preload = 'metadata';\n probe.onloadedmetadata = () => {\n if (Number.isFinite(probe.duration) && probe.duration > 0) {\n setDurationSecs((prev) => (prev ? prev : String(Math.round(probe.duration))));\n }\n };\n probe.src = url;\n } catch {\n // Non-fatal — the field stays manually editable.\n }\n };\n\n const submit = async () => {\n if (!title.trim()) {\n setError('A title is required.');\n return;\n }\n try {\n const created = await record.mutateAsync({\n title: title.trim(),\n summary: summary.trim() || null,\n transcript: transcript.trim() || null,\n narratorName: narratorName.trim() || null,\n language: language.trim() || null,\n accessLevel,\n collectionId: pickedCollection || null,\n recordingFileId: recording?.id ?? null,\n durationSecs: durationSecs ? Number(durationSecs) : null,\n recordedAt: dateToIso(recordedAt),\n });\n notify.success('Oral history recorded', created.title);\n onOpenChange(false);\n reset();\n onCreated?.(created);\n } catch (err) {\n setError(describeError(err));\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"sm:max-w-2xl\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Record an oral history</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Preserve a first-person account. Attach the recording and a transcript for the archive.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"flex max-h-[64vh] flex-col gap-4 overflow-y-auto py-2\">\n <div className=\"grid gap-1.5\">\n <Label>Recording</Label>\n <MediaDropzone\n folder={FILE_FOLDERS.recordings}\n accept=\"audio/*,video/*\"\n promptLabel=\"Drag and drop an audio or video file here\"\n hint=\"Audio or video, up to 25 MB.\"\n value={recording}\n onUploaded={(ref) => {\n setRecording(ref);\n probeDuration(ref.url);\n }}\n onClear={() => setRecording(null)}\n />\n </div>\n\n <div className=\"grid gap-3 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"oral-title\">\n Title <span className=\"text-status-error-text\">*</span>\n </Label>\n <Input\n id=\"oral-title\"\n value={title}\n maxLength={TITLE_MAX}\n onChange={(e) => setTitle(e.target.value)}\n placeholder=\"e.g. A weaver remembers the looms\"\n />\n <Counter length={title.length} max={TITLE_MAX} />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"oral-narrator\">Narrator</Label>\n <Input\n id=\"oral-narrator\"\n value={narratorName}\n maxLength={NARRATOR_MAX}\n onChange={(e) => setNarratorName(e.target.value)}\n placeholder=\"Full name\"\n />\n </div>\n </div>\n\n <div className=\"grid gap-3 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"oral-language\">Language</Label>\n <Input\n id=\"oral-language\"\n value={language}\n maxLength={LANGUAGE_MAX}\n onChange={(e) => setLanguage(e.target.value)}\n placeholder=\"e.g. Tamil\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"oral-duration\">Duration (seconds)</Label>\n <Input\n id=\"oral-duration\"\n type=\"number\"\n min={0}\n value={durationSecs}\n onChange={(e) => setDurationSecs(e.target.value)}\n placeholder=\"Auto-filled from the recording\"\n />\n </div>\n </div>\n\n <div className=\"grid gap-3 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"oral-recorded\">Recorded on</Label>\n <Input\n id=\"oral-recorded\"\n type=\"date\"\n value={recordedAt}\n onChange={(e) => setRecordedAt(e.target.value)}\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label>Access level</Label>\n <Select\n value={accessLevel}\n onValueChange={(v) => setAccessLevel(v as TcpAccessLevel)}\n >\n <SelectTrigger aria-label=\"Access level\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {ACCESS_LEVELS.map((a) => (\n <SelectItem key={a} value={a}>\n {formatEnumLabel(a)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <p className=\"text-xs text-text-tertiary\">{ACCESS_HELP[accessLevel]}</p>\n </div>\n </div>\n\n {!collectionId && (\n <div className=\"grid gap-1.5\">\n <Label>Collection</Label>\n <Select value={pickedCollection} onValueChange={setPickedCollection}>\n <SelectTrigger>\n <SelectValue placeholder=\"Add to a collection (optional)\" />\n </SelectTrigger>\n <SelectContent>\n {(collectionsQuery.data ?? []).map((collection) => (\n <SelectItem key={collection.id} value={collection.id}>\n {collection.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n )}\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"oral-summary\">Summary</Label>\n <Textarea\n id=\"oral-summary\"\n value={summary}\n maxLength={SUMMARY_MAX}\n onChange={(e) => setSummary(e.target.value)}\n placeholder=\"A short description of what this account covers.\"\n rows={3}\n />\n <Counter length={summary.length} max={SUMMARY_MAX} />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"oral-transcript\">Transcript</Label>\n <Textarea\n id=\"oral-transcript\"\n value={transcript}\n maxLength={TRANSCRIPT_MAX}\n onChange={(e) => setTranscript(e.target.value)}\n placeholder=\"The transcribed account (line breaks are preserved).\"\n rows={5}\n />\n <Counter length={transcript.length} max={TRANSCRIPT_MAX} />\n </div>\n\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => onOpenChange(false)}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={record.isPending}>\n Record oral history\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\n/** A right-aligned character counter shown under a text field. */\nexport function Counter({ length, max }: { length: number; max: number }) {\n return (\n <p className=\"text-right text-xs tabular-nums text-text-tertiary\">\n {length} / {max}\n </p>\n );\n}\n"],"mappings":"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"}
|