@burdenoff/microfe-theculturepost 2026.721.3 → 2026.726.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/components/AddCitationDialog.js +1 -1
- package/dist/theculturepost/components/AudioRecorderControl.d.ts +28 -0
- package/dist/theculturepost/components/AudioRecorderControl.d.ts.map +1 -0
- package/dist/theculturepost/components/AudioRecorderControl.js +116 -0
- package/dist/theculturepost/components/AudioRecorderControl.js.map +1 -0
- package/dist/theculturepost/components/CitationList.js +1 -1
- package/dist/theculturepost/components/CreateArchiveItemDialog.js +77 -77
- package/dist/theculturepost/components/CreateArchiveItemDialog.js.map +1 -1
- package/dist/theculturepost/components/CreateCollectionDialog.js +1 -1
- package/dist/theculturepost/components/CreateStoryDialog.js +1 -1
- package/dist/theculturepost/components/CreateTopicDialog.js +1 -1
- package/dist/theculturepost/components/OpenDiscussionDialog.js +1 -1
- package/dist/theculturepost/components/PendingOralHistoryPanel.d.ts +10 -0
- package/dist/theculturepost/components/PendingOralHistoryPanel.d.ts.map +1 -0
- package/dist/theculturepost/components/PendingOralHistoryPanel.js +67 -0
- package/dist/theculturepost/components/PendingOralHistoryPanel.js.map +1 -0
- package/dist/theculturepost/components/RecordOralHistoryDialog.d.ts.map +1 -1
- package/dist/theculturepost/components/RecordOralHistoryDialog.js +276 -174
- package/dist/theculturepost/components/RecordOralHistoryDialog.js.map +1 -1
- package/dist/theculturepost/components/ReplyThread.js +1 -1
- package/dist/theculturepost/components/StoryCard.js +1 -1
- package/dist/theculturepost/components/index.js +1 -1
- package/dist/theculturepost/hooks/index.d.ts +1 -0
- package/dist/theculturepost/hooks/index.d.ts.map +1 -1
- package/dist/theculturepost/hooks/index.js +1 -0
- package/dist/theculturepost/hooks/usePendingOralHistorySync.d.ts +10 -0
- package/dist/theculturepost/hooks/usePendingOralHistorySync.d.ts.map +1 -0
- package/dist/theculturepost/hooks/usePendingOralHistorySync.js +59 -0
- package/dist/theculturepost/hooks/usePendingOralHistorySync.js.map +1 -0
- package/dist/theculturepost/hooks/useTcpApi.js +1 -1
- package/dist/theculturepost/hooks/useTcpApi.js.map +1 -1
- package/dist/theculturepost/lib/offlineOralHistoryQueue.d.ts +54 -0
- package/dist/theculturepost/lib/offlineOralHistoryQueue.d.ts.map +1 -0
- package/dist/theculturepost/lib/offlineOralHistoryQueue.js +68 -0
- package/dist/theculturepost/lib/offlineOralHistoryQueue.js.map +1 -0
- package/dist/theculturepost/pages/archive/ArchiveItemDetailPage.js +1 -1
- package/dist/theculturepost/pages/archive/ArchivePage.d.ts.map +1 -1
- package/dist/theculturepost/pages/archive/ArchivePage.js +99 -95
- package/dist/theculturepost/pages/archive/ArchivePage.js.map +1 -1
- package/dist/theculturepost/pages/discussions/DiscussionDetailPage.js +1 -1
- package/dist/theculturepost/pages/stories/StoryDetailPage.js +1 -1
- package/dist/theculturepost/pages/topics/TopicDetailPage.js +1 -1
- package/dist/theculturepost/types/index.d.ts +8 -0
- package/dist/theculturepost/types/index.d.ts.map +1 -1
- package/dist/theculturepost/utils/nativeBridge.js +10 -7
- package/dist/theculturepost/utils/nativeBridge.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { FILE_FOLDERS as e } from "../hooks/useFiles.js";
|
|
2
2
|
import { formatEnumLabel as t } from "../utils/formatters.js";
|
|
3
3
|
import { useTcpAddCitation as n } from "../hooks/useTcpApi.js";
|
|
4
|
-
import "../hooks/index.js";
|
|
5
4
|
import { describeError as r, notify as i } from "../lib/toast.js";
|
|
5
|
+
import "../hooks/index.js";
|
|
6
6
|
import { FileUploadField as a } from "./FileUploadField.js";
|
|
7
7
|
import { sourceTypeIcon as o } from "./CitationList.js";
|
|
8
8
|
import { createElement as s, useState as c } from "react";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AudioRecorderControl — in-app microphone recording for oral-history capture.
|
|
3
|
+
* @module theculturepost/components
|
|
4
|
+
*
|
|
5
|
+
* Uses the standard `navigator.mediaDevices.getUserMedia` + `MediaRecorder`
|
|
6
|
+
* Web APIs, which work identically inside the Capacitor WebView (once the
|
|
7
|
+
* host app declares `RECORD_AUDIO` / `NSMicrophoneUsageDescription`) and in a
|
|
8
|
+
* desktop browser — no native Capacitor plugin is required for audio-only
|
|
9
|
+
* capture. This is what makes "record now" possible on-device for field
|
|
10
|
+
* oral-history collection (BOFF-4128), as an addition to the existing
|
|
11
|
+
* upload-an-existing-file flow in {@link MediaDropzone}.
|
|
12
|
+
*/
|
|
13
|
+
export interface RecordedAudio {
|
|
14
|
+
blob: Blob;
|
|
15
|
+
mimeType: string;
|
|
16
|
+
durationSecs: number;
|
|
17
|
+
/** An object URL for local preview — caller must revoke it when done. */
|
|
18
|
+
previewUrl: string;
|
|
19
|
+
}
|
|
20
|
+
interface AudioRecorderControlProps {
|
|
21
|
+
value: RecordedAudio | null;
|
|
22
|
+
onRecorded: (audio: RecordedAudio) => void;
|
|
23
|
+
onClear: () => void;
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export declare function AudioRecorderControl({ value, onRecorded, onClear, disabled }: AudioRecorderControlProps): import("react/jsx-runtime").JSX.Element;
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=AudioRecorderControl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AudioRecorderControl.d.ts","sourceRoot":"","sources":["../../../src/theculturepost/components/AudioRecorderControl.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAQH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,yBAAyB;IACjC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC3C,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAmBD,wBAAgB,oBAAoB,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,yBAAyB,2CAkIvG"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { isNativePlatform as e } from "../utils/nativeBridge.js";
|
|
2
|
+
import { AudioPlayer as t } from "./AudioPlayer.js";
|
|
3
|
+
import { useEffect as n, useRef as r, useState as i } from "react";
|
|
4
|
+
import { AlertCircle as a, Mic as o, Square as s, Trash2 as c } from "lucide-react";
|
|
5
|
+
import { Fragment as l, jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
6
|
+
import { Button as f } from "@burdenoff/fe-libs/ui";
|
|
7
|
+
//#region src/theculturepost/components/AudioRecorderControl.tsx
|
|
8
|
+
function p() {
|
|
9
|
+
for (let e of [
|
|
10
|
+
"audio/webm;codecs=opus",
|
|
11
|
+
"audio/webm",
|
|
12
|
+
"audio/mp4",
|
|
13
|
+
"audio/aac"
|
|
14
|
+
]) if (typeof MediaRecorder < "u" && MediaRecorder.isTypeSupported?.(e)) return e;
|
|
15
|
+
return "";
|
|
16
|
+
}
|
|
17
|
+
function m(e) {
|
|
18
|
+
let t = Math.floor(e / 60), n = Math.floor(e % 60);
|
|
19
|
+
return `${t}:${String(n).padStart(2, "0")}`;
|
|
20
|
+
}
|
|
21
|
+
function h({ value: h, onRecorded: g, onClear: _, disabled: v }) {
|
|
22
|
+
let [y, b] = i(!1), [x, S] = i(0), [C, w] = i(null), T = r(null), E = r([]), D = r(null), O = r(0), k = r(null);
|
|
23
|
+
n(() => () => {
|
|
24
|
+
k.current && clearInterval(k.current), D.current?.getTracks().forEach((e) => e.stop());
|
|
25
|
+
}, []);
|
|
26
|
+
let A = () => {
|
|
27
|
+
k.current &&= (clearInterval(k.current), null);
|
|
28
|
+
};
|
|
29
|
+
return h ? /* @__PURE__ */ d("div", {
|
|
30
|
+
className: "flex flex-col gap-2",
|
|
31
|
+
children: [/* @__PURE__ */ u(t, {
|
|
32
|
+
src: h.previewUrl,
|
|
33
|
+
seed: "local-recording",
|
|
34
|
+
durationSecs: h.durationSecs
|
|
35
|
+
}), /* @__PURE__ */ d("button", {
|
|
36
|
+
type: "button",
|
|
37
|
+
onClick: () => {
|
|
38
|
+
h && URL.revokeObjectURL(h.previewUrl), _(), S(0);
|
|
39
|
+
},
|
|
40
|
+
disabled: v,
|
|
41
|
+
className: "inline-flex w-fit items-center gap-1 text-sm text-text-tertiary hover:text-text-primary disabled:opacity-60",
|
|
42
|
+
children: [/* @__PURE__ */ u(c, { className: "h-3.5 w-3.5" }), " Discard and record again"]
|
|
43
|
+
})]
|
|
44
|
+
}) : /* @__PURE__ */ d("div", {
|
|
45
|
+
className: "flex flex-col items-center gap-3 rounded-xl border border-dashed border-border-seam bg-bg-surface px-4 py-6 text-center",
|
|
46
|
+
children: [y ? /* @__PURE__ */ d(l, { children: [/* @__PURE__ */ d("div", {
|
|
47
|
+
className: "flex items-center gap-2 text-status-error-text",
|
|
48
|
+
children: [/* @__PURE__ */ d("span", {
|
|
49
|
+
className: "relative flex h-3 w-3",
|
|
50
|
+
children: [/* @__PURE__ */ u("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-status-error-text opacity-75" }), /* @__PURE__ */ u("span", { className: "relative inline-flex h-3 w-3 rounded-full bg-status-error-text" })]
|
|
51
|
+
}), /* @__PURE__ */ u("span", {
|
|
52
|
+
className: "font-mono text-lg tabular-nums",
|
|
53
|
+
children: m(x)
|
|
54
|
+
})]
|
|
55
|
+
}), /* @__PURE__ */ d(f, {
|
|
56
|
+
type: "button",
|
|
57
|
+
variant: "outline",
|
|
58
|
+
onClick: () => {
|
|
59
|
+
T.current?.stop(), b(!1);
|
|
60
|
+
},
|
|
61
|
+
children: [/* @__PURE__ */ u(s, { className: "mr-1.5 h-4 w-4" }), " Stop recording"]
|
|
62
|
+
})] }) : /* @__PURE__ */ d(l, { children: [
|
|
63
|
+
/* @__PURE__ */ u(o, {
|
|
64
|
+
className: "h-6 w-6 text-text-tertiary",
|
|
65
|
+
"aria-hidden": "true"
|
|
66
|
+
}),
|
|
67
|
+
/* @__PURE__ */ u("p", {
|
|
68
|
+
className: "text-sm text-text-secondary",
|
|
69
|
+
children: e() ? "Record the testimony directly on this device." : "Record using this device’s microphone."
|
|
70
|
+
}),
|
|
71
|
+
/* @__PURE__ */ d(f, {
|
|
72
|
+
type: "button",
|
|
73
|
+
onClick: async () => {
|
|
74
|
+
if (w(null), typeof navigator > "u" || !navigator.mediaDevices?.getUserMedia) {
|
|
75
|
+
w("Microphone recording is not supported in this browser.");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
let e = await navigator.mediaDevices.getUserMedia({ audio: !0 });
|
|
80
|
+
D.current = e;
|
|
81
|
+
let t = p(), n = t ? new MediaRecorder(e, { mimeType: t }) : new MediaRecorder(e);
|
|
82
|
+
E.current = [], n.ondataavailable = (e) => {
|
|
83
|
+
e.data.size > 0 && E.current.push(e.data);
|
|
84
|
+
}, n.onstop = () => {
|
|
85
|
+
A(), e.getTracks().forEach((e) => e.stop());
|
|
86
|
+
let r = n.mimeType || t || "audio/webm", i = new Blob(E.current, { type: r });
|
|
87
|
+
g({
|
|
88
|
+
blob: i,
|
|
89
|
+
mimeType: r,
|
|
90
|
+
durationSecs: Math.max(1, Math.round((Date.now() - O.current) / 1e3)),
|
|
91
|
+
previewUrl: URL.createObjectURL(i)
|
|
92
|
+
});
|
|
93
|
+
}, T.current = n, O.current = Date.now(), S(0), n.start(), b(!0), k.current = setInterval(() => {
|
|
94
|
+
S(Math.round((Date.now() - O.current) / 1e3));
|
|
95
|
+
}, 500);
|
|
96
|
+
} catch {
|
|
97
|
+
w("Microphone access was denied or is unavailable. Check app permissions and try again.");
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
disabled: v,
|
|
101
|
+
children: [/* @__PURE__ */ u(o, { className: "mr-1.5 h-4 w-4" }), " Start recording"]
|
|
102
|
+
})
|
|
103
|
+
] }), C && /* @__PURE__ */ d("p", {
|
|
104
|
+
className: "flex items-center gap-1.5 text-xs text-status-error-text",
|
|
105
|
+
children: [
|
|
106
|
+
/* @__PURE__ */ u(a, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
107
|
+
" ",
|
|
108
|
+
C
|
|
109
|
+
]
|
|
110
|
+
})]
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
export { h as AudioRecorderControl };
|
|
115
|
+
|
|
116
|
+
//# sourceMappingURL=AudioRecorderControl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AudioRecorderControl.js","names":[],"sources":["../../../src/theculturepost/components/AudioRecorderControl.tsx"],"sourcesContent":["/**\n * AudioRecorderControl — in-app microphone recording for oral-history capture.\n * @module theculturepost/components\n *\n * Uses the standard `navigator.mediaDevices.getUserMedia` + `MediaRecorder`\n * Web APIs, which work identically inside the Capacitor WebView (once the\n * host app declares `RECORD_AUDIO` / `NSMicrophoneUsageDescription`) and in a\n * desktop browser — no native Capacitor plugin is required for audio-only\n * capture. This is what makes \"record now\" possible on-device for field\n * oral-history collection (BOFF-4128), as an addition to the existing\n * upload-an-existing-file flow in {@link MediaDropzone}.\n */\n\nimport { useEffect, useRef, useState } from 'react';\nimport { Mic, Square, Trash2, AlertCircle } from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { AudioPlayer } from './AudioPlayer';\nimport { isNativePlatform } from '../utils/nativeBridge';\n\nexport interface RecordedAudio {\n blob: Blob;\n mimeType: string;\n durationSecs: number;\n /** An object URL for local preview — caller must revoke it when done. */\n previewUrl: string;\n}\n\ninterface AudioRecorderControlProps {\n value: RecordedAudio | null;\n onRecorded: (audio: RecordedAudio) => void;\n onClear: () => void;\n disabled?: boolean;\n}\n\n/** Picks the first MIME type the platform's MediaRecorder actually supports. */\nfunction pickSupportedMimeType(): string {\n const candidates = ['audio/webm;codecs=opus', 'audio/webm', 'audio/mp4', 'audio/aac'];\n for (const candidate of candidates) {\n if (typeof MediaRecorder !== 'undefined' && MediaRecorder.isTypeSupported?.(candidate)) {\n return candidate;\n }\n }\n return '';\n}\n\nfunction formatElapsed(seconds: number): string {\n const m = Math.floor(seconds / 60);\n const s = Math.floor(seconds % 60);\n return `${m}:${String(s).padStart(2, '0')}`;\n}\n\nexport function AudioRecorderControl({ value, onRecorded, onClear, disabled }: AudioRecorderControlProps) {\n const [recording, setRecording] = useState(false);\n const [elapsed, setElapsed] = useState(0);\n const [error, setError] = useState<string | null>(null);\n\n const mediaRecorderRef = useRef<MediaRecorder | null>(null);\n const chunksRef = useRef<Blob[]>([]);\n const streamRef = useRef<MediaStream | null>(null);\n const startedAtRef = useRef<number>(0);\n const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n useEffect(\n () => () => {\n // Unmount safety: stop any in-flight recording and release the mic.\n if (timerRef.current) clearInterval(timerRef.current);\n streamRef.current?.getTracks().forEach((track) => track.stop());\n },\n []\n );\n\n const stopTimer = () => {\n if (timerRef.current) {\n clearInterval(timerRef.current);\n timerRef.current = null;\n }\n };\n\n const start = async () => {\n setError(null);\n if (typeof navigator === 'undefined' || !navigator.mediaDevices?.getUserMedia) {\n setError('Microphone recording is not supported in this browser.');\n return;\n }\n try {\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n streamRef.current = stream;\n const mimeType = pickSupportedMimeType();\n const recorder = mimeType\n ? new MediaRecorder(stream, { mimeType })\n : new MediaRecorder(stream);\n chunksRef.current = [];\n\n recorder.ondataavailable = (event) => {\n if (event.data.size > 0) chunksRef.current.push(event.data);\n };\n\n recorder.onstop = () => {\n stopTimer();\n stream.getTracks().forEach((track) => track.stop());\n const finalMimeType = recorder.mimeType || mimeType || 'audio/webm';\n const blob = new Blob(chunksRef.current, { type: finalMimeType });\n const durationSecs = Math.max(1, Math.round((Date.now() - startedAtRef.current) / 1000));\n onRecorded({ blob, mimeType: finalMimeType, durationSecs, previewUrl: URL.createObjectURL(blob) });\n };\n\n mediaRecorderRef.current = recorder;\n startedAtRef.current = Date.now();\n setElapsed(0);\n recorder.start();\n setRecording(true);\n timerRef.current = setInterval(() => {\n setElapsed(Math.round((Date.now() - startedAtRef.current) / 1000));\n }, 500);\n } catch {\n setError('Microphone access was denied or is unavailable. Check app permissions and try again.');\n }\n };\n\n const stop = () => {\n mediaRecorderRef.current?.stop();\n setRecording(false);\n };\n\n const discard = () => {\n if (value) URL.revokeObjectURL(value.previewUrl);\n onClear();\n setElapsed(0);\n };\n\n if (value) {\n return (\n <div className=\"flex flex-col gap-2\">\n <AudioPlayer src={value.previewUrl} seed=\"local-recording\" durationSecs={value.durationSecs} />\n <button\n type=\"button\"\n onClick={discard}\n disabled={disabled}\n className=\"inline-flex w-fit items-center gap-1 text-sm text-text-tertiary hover:text-text-primary disabled:opacity-60\"\n >\n <Trash2 className=\"h-3.5 w-3.5\" /> Discard and record again\n </button>\n </div>\n );\n }\n\n return (\n <div className=\"flex flex-col items-center gap-3 rounded-xl border border-dashed border-border-seam bg-bg-surface px-4 py-6 text-center\">\n {recording ? (\n <>\n <div className=\"flex items-center gap-2 text-status-error-text\">\n <span className=\"relative flex h-3 w-3\">\n <span className=\"absolute inline-flex h-full w-full animate-ping rounded-full bg-status-error-text opacity-75\" />\n <span className=\"relative inline-flex h-3 w-3 rounded-full bg-status-error-text\" />\n </span>\n <span className=\"font-mono text-lg tabular-nums\">{formatElapsed(elapsed)}</span>\n </div>\n <Button type=\"button\" variant=\"outline\" onClick={stop}>\n <Square className=\"mr-1.5 h-4 w-4\" /> Stop recording\n </Button>\n </>\n ) : (\n <>\n <Mic className=\"h-6 w-6 text-text-tertiary\" aria-hidden=\"true\" />\n <p className=\"text-sm text-text-secondary\">\n {isNativePlatform()\n ? 'Record the testimony directly on this device.'\n : 'Record using this device’s microphone.'}\n </p>\n <Button type=\"button\" onClick={start} disabled={disabled}>\n <Mic className=\"mr-1.5 h-4 w-4\" /> Start recording\n </Button>\n </>\n )}\n {error && (\n <p className=\"flex items-center gap-1.5 text-xs text-status-error-text\">\n <AlertCircle className=\"h-3.5 w-3.5 shrink-0\" /> {error}\n </p>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -3,8 +3,8 @@ import { ErrorView as t, LoadingView as n } from "./StateViews.js";
|
|
|
3
3
|
import { formatDate as r, formatEnumLabel as i } from "../utils/formatters.js";
|
|
4
4
|
import { StatusBadge as a } from "./StatusBadge.js";
|
|
5
5
|
import { useTcpCitations as o, useTcpVerifyCitation as s } from "../hooks/useTcpApi.js";
|
|
6
|
-
import "../hooks/index.js";
|
|
7
6
|
import { describeError as c, notify as l } from "../lib/toast.js";
|
|
7
|
+
import "../hooks/index.js";
|
|
8
8
|
import { EmptyState as u } from "./EmptyState.js";
|
|
9
9
|
import { createElement as d } from "react";
|
|
10
10
|
import { AlertCircle as f, BookMarked as p, BookOpen as m, Box as h, CheckCircle2 as g, ExternalLink as _, FileText as v, Globe as y, GraduationCap as b, HelpCircle as x, Landmark as S, Mic as C, Newspaper as w, Quote as T, ShieldCheck as E, XCircle as D } from "lucide-react";
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { FILE_FOLDERS as e } from "../hooks/useFiles.js";
|
|
2
2
|
import { formatEnumLabel as t } from "../utils/formatters.js";
|
|
3
|
-
import { useTcpCollections as n, useTcpCreateArchiveItem as
|
|
3
|
+
import { useTcpCollections as n, useTcpCreateArchiveItem as ee } from "../hooks/useTcpApi.js";
|
|
4
|
+
import { describeError as te, notify as ne } from "../lib/toast.js";
|
|
4
5
|
import "../hooks/index.js";
|
|
5
|
-
import {
|
|
6
|
+
import { TagInput as r } from "./TagInput.js";
|
|
6
7
|
import { Counter as i, MediaDropzone as a } from "./RecordOralHistoryDialog.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { Button as u, Input as d, Label as f, ResponsiveDialog as ne, ResponsiveDialogContent as re, ResponsiveDialogDescription as ie, ResponsiveDialogFooter as ae, ResponsiveDialogHeader as p, ResponsiveDialogTitle as m, Select as h, SelectContent as g, SelectItem as _, SelectTrigger as v, SelectValue as y, Textarea as b } from "@burdenoff/fe-libs/ui";
|
|
8
|
+
import { useState as o } from "react";
|
|
9
|
+
import { jsx as s, jsxs as c } from "react/jsx-runtime";
|
|
10
|
+
import { Button as l, Input as u, Label as d, ResponsiveDialog as re, ResponsiveDialogContent as ie, ResponsiveDialogDescription as ae, ResponsiveDialogFooter as f, ResponsiveDialogHeader as p, ResponsiveDialogTitle as oe, Select as m, SelectContent as h, SelectItem as g, SelectTrigger as _, SelectValue as v, Textarea as y } from "@burdenoff/fe-libs/ui";
|
|
11
11
|
//#region src/theculturepost/components/CreateArchiveItemDialog.tsx
|
|
12
|
-
var
|
|
12
|
+
var se = [
|
|
13
13
|
"DOCUMENT",
|
|
14
14
|
"MANUSCRIPT",
|
|
15
15
|
"PHOTOGRAPH",
|
|
@@ -17,38 +17,38 @@ var x = [
|
|
|
17
17
|
"VIDEO",
|
|
18
18
|
"MAP",
|
|
19
19
|
"OBJECT"
|
|
20
|
-
],
|
|
20
|
+
], ce = [
|
|
21
21
|
"PUBLIC",
|
|
22
22
|
"COMMUNITY",
|
|
23
23
|
"RESTRICTED",
|
|
24
24
|
"EMBARGOED"
|
|
25
|
-
],
|
|
25
|
+
], b = {
|
|
26
26
|
PUBLIC: "Visible to everyone.",
|
|
27
27
|
COMMUNITY: "Available to community members.",
|
|
28
28
|
RESTRICTED: "Visible to selected groups.",
|
|
29
29
|
EMBARGOED: "Hidden until a future date."
|
|
30
|
-
},
|
|
30
|
+
}, x = 160, S = 600, C = 600, le = 160, ue = (e) => {
|
|
31
31
|
if (!e) return null;
|
|
32
32
|
let t = new Date(e);
|
|
33
33
|
return Number.isNaN(t.getTime()) ? null : t.toISOString();
|
|
34
34
|
};
|
|
35
|
-
function
|
|
36
|
-
let
|
|
37
|
-
j(""), N(""), F("DOCUMENT"), L("PUBLIC"), z(
|
|
35
|
+
function w({ open: w, onOpenChange: T, collectionId: E, onCreated: D }) {
|
|
36
|
+
let O = ee(), k = n({ limit: 100 }), [A, j] = o(""), [M, N] = o(""), [P, F] = o("DOCUMENT"), [I, L] = o("PUBLIC"), [R, z] = o(E ?? ""), [B, V] = o(null), [H, U] = o(""), [W, G] = o(""), [K, q] = o([]), [J, Y] = o([]), [X, Z] = o(""), [Q, $] = o(null), de = () => {
|
|
37
|
+
j(""), N(""), F("DOCUMENT"), L("PUBLIC"), z(E ?? ""), V(null), U(""), G(""), q([]), Y([]), Z(""), $(null);
|
|
38
38
|
};
|
|
39
|
-
return /* @__PURE__ */
|
|
40
|
-
open:
|
|
41
|
-
onOpenChange:
|
|
42
|
-
children: /* @__PURE__ */
|
|
39
|
+
return /* @__PURE__ */ s(re, {
|
|
40
|
+
open: w,
|
|
41
|
+
onOpenChange: T,
|
|
42
|
+
children: /* @__PURE__ */ c(ie, {
|
|
43
43
|
className: "sm:max-w-2xl",
|
|
44
44
|
children: [
|
|
45
|
-
/* @__PURE__ */
|
|
46
|
-
/* @__PURE__ */
|
|
45
|
+
/* @__PURE__ */ c(p, { children: [/* @__PURE__ */ s(oe, { children: "Add an artifact" }), /* @__PURE__ */ s(ae, { children: "Catalogue a manuscript, photograph, recording, map, object or document with its provenance." })] }),
|
|
46
|
+
/* @__PURE__ */ c("div", {
|
|
47
47
|
className: "flex max-h-[64vh] flex-col gap-4 overflow-y-auto py-2",
|
|
48
48
|
children: [
|
|
49
|
-
/* @__PURE__ */
|
|
49
|
+
/* @__PURE__ */ c("div", {
|
|
50
50
|
className: "grid gap-1.5",
|
|
51
|
-
children: [/* @__PURE__ */
|
|
51
|
+
children: [/* @__PURE__ */ s(d, { children: "File" }), /* @__PURE__ */ s(a, {
|
|
52
52
|
folder: e.artifacts,
|
|
53
53
|
accept: "image/*,audio/*,video/*,application/pdf",
|
|
54
54
|
promptLabel: "Drag and drop the artifact file here",
|
|
@@ -58,111 +58,111 @@ function T({ open: T, onOpenChange: E, collectionId: D, onCreated: O }) {
|
|
|
58
58
|
onClear: () => V(null)
|
|
59
59
|
})]
|
|
60
60
|
}),
|
|
61
|
-
/* @__PURE__ */
|
|
61
|
+
/* @__PURE__ */ c("div", {
|
|
62
62
|
className: "grid gap-1.5",
|
|
63
63
|
children: [
|
|
64
|
-
/* @__PURE__ */
|
|
64
|
+
/* @__PURE__ */ c(d, {
|
|
65
65
|
htmlFor: "item-title",
|
|
66
|
-
children: ["Title ", /* @__PURE__ */
|
|
66
|
+
children: ["Title ", /* @__PURE__ */ s("span", {
|
|
67
67
|
className: "text-status-error-text",
|
|
68
68
|
children: "*"
|
|
69
69
|
})]
|
|
70
70
|
}),
|
|
71
|
-
/* @__PURE__ */
|
|
71
|
+
/* @__PURE__ */ s(u, {
|
|
72
72
|
id: "item-title",
|
|
73
73
|
value: A,
|
|
74
|
-
maxLength:
|
|
74
|
+
maxLength: x,
|
|
75
75
|
onChange: (e) => j(e.target.value),
|
|
76
76
|
placeholder: "e.g. Palm-leaf manuscript, 18th c."
|
|
77
77
|
}),
|
|
78
|
-
/* @__PURE__ */
|
|
78
|
+
/* @__PURE__ */ s(i, {
|
|
79
79
|
length: A.length,
|
|
80
|
-
max:
|
|
80
|
+
max: x
|
|
81
81
|
})
|
|
82
82
|
]
|
|
83
83
|
}),
|
|
84
|
-
/* @__PURE__ */
|
|
84
|
+
/* @__PURE__ */ c("div", {
|
|
85
85
|
className: "grid gap-3 sm:grid-cols-2",
|
|
86
|
-
children: [/* @__PURE__ */
|
|
86
|
+
children: [/* @__PURE__ */ c("div", {
|
|
87
87
|
className: "grid gap-1.5",
|
|
88
|
-
children: [/* @__PURE__ */
|
|
88
|
+
children: [/* @__PURE__ */ s(d, { children: "Kind" }), /* @__PURE__ */ c(m, {
|
|
89
89
|
value: P,
|
|
90
90
|
onValueChange: (e) => F(e),
|
|
91
|
-
children: [/* @__PURE__ */
|
|
91
|
+
children: [/* @__PURE__ */ s(_, { children: /* @__PURE__ */ s(v, {}) }), /* @__PURE__ */ s(h, { children: se.map((e) => /* @__PURE__ */ s(g, {
|
|
92
92
|
value: e,
|
|
93
93
|
children: t(e)
|
|
94
94
|
}, e)) })]
|
|
95
95
|
})]
|
|
96
|
-
}), /* @__PURE__ */
|
|
96
|
+
}), /* @__PURE__ */ c("div", {
|
|
97
97
|
className: "grid gap-1.5",
|
|
98
98
|
children: [
|
|
99
|
-
/* @__PURE__ */
|
|
100
|
-
/* @__PURE__ */
|
|
99
|
+
/* @__PURE__ */ s(d, { children: "Access level" }),
|
|
100
|
+
/* @__PURE__ */ c(m, {
|
|
101
101
|
value: I,
|
|
102
102
|
onValueChange: (e) => L(e),
|
|
103
|
-
children: [/* @__PURE__ */
|
|
103
|
+
children: [/* @__PURE__ */ s(_, { children: /* @__PURE__ */ s(v, {}) }), /* @__PURE__ */ s(h, { children: ce.map((e) => /* @__PURE__ */ s(g, {
|
|
104
104
|
value: e,
|
|
105
105
|
children: t(e)
|
|
106
106
|
}, e)) })]
|
|
107
107
|
}),
|
|
108
|
-
/* @__PURE__ */
|
|
108
|
+
/* @__PURE__ */ s("p", {
|
|
109
109
|
className: "text-xs text-text-tertiary",
|
|
110
|
-
children:
|
|
110
|
+
children: b[I]
|
|
111
111
|
})
|
|
112
112
|
]
|
|
113
113
|
})]
|
|
114
114
|
}),
|
|
115
|
-
!
|
|
115
|
+
!E && /* @__PURE__ */ c("div", {
|
|
116
116
|
className: "grid gap-1.5",
|
|
117
|
-
children: [/* @__PURE__ */
|
|
117
|
+
children: [/* @__PURE__ */ s(d, { children: "Collection" }), /* @__PURE__ */ c(m, {
|
|
118
118
|
value: R,
|
|
119
119
|
onValueChange: z,
|
|
120
|
-
children: [/* @__PURE__ */
|
|
120
|
+
children: [/* @__PURE__ */ s(_, { children: /* @__PURE__ */ s(v, { placeholder: "Add to a collection (optional)" }) }), /* @__PURE__ */ s(h, { children: (k.data ?? []).map((e) => /* @__PURE__ */ s(g, {
|
|
121
121
|
value: e.id,
|
|
122
122
|
children: e.name
|
|
123
123
|
}, e.id)) })]
|
|
124
124
|
})]
|
|
125
125
|
}),
|
|
126
|
-
/* @__PURE__ */
|
|
126
|
+
/* @__PURE__ */ c("div", {
|
|
127
127
|
className: "grid gap-1.5",
|
|
128
128
|
children: [
|
|
129
|
-
/* @__PURE__ */
|
|
129
|
+
/* @__PURE__ */ s(d, {
|
|
130
130
|
htmlFor: "item-desc",
|
|
131
131
|
children: "Description"
|
|
132
132
|
}),
|
|
133
|
-
/* @__PURE__ */
|
|
133
|
+
/* @__PURE__ */ s(y, {
|
|
134
134
|
id: "item-desc",
|
|
135
135
|
value: M,
|
|
136
|
-
maxLength:
|
|
136
|
+
maxLength: S,
|
|
137
137
|
onChange: (e) => N(e.target.value),
|
|
138
138
|
rows: 2
|
|
139
139
|
}),
|
|
140
|
-
/* @__PURE__ */
|
|
140
|
+
/* @__PURE__ */ s(i, {
|
|
141
141
|
length: M.length,
|
|
142
|
-
max:
|
|
142
|
+
max: S
|
|
143
143
|
})
|
|
144
144
|
]
|
|
145
145
|
}),
|
|
146
|
-
/* @__PURE__ */
|
|
146
|
+
/* @__PURE__ */ c("div", {
|
|
147
147
|
className: "grid gap-3 sm:grid-cols-2",
|
|
148
|
-
children: [/* @__PURE__ */
|
|
148
|
+
children: [/* @__PURE__ */ c("div", {
|
|
149
149
|
className: "grid gap-1.5",
|
|
150
|
-
children: [/* @__PURE__ */
|
|
150
|
+
children: [/* @__PURE__ */ s(d, {
|
|
151
151
|
htmlFor: "item-origin",
|
|
152
152
|
children: "Origin"
|
|
153
|
-
}), /* @__PURE__ */
|
|
153
|
+
}), /* @__PURE__ */ s(u, {
|
|
154
154
|
id: "item-origin",
|
|
155
155
|
value: W,
|
|
156
|
-
maxLength:
|
|
156
|
+
maxLength: le,
|
|
157
157
|
onChange: (e) => G(e.target.value),
|
|
158
158
|
placeholder: "Place of origin"
|
|
159
159
|
})]
|
|
160
|
-
}), /* @__PURE__ */
|
|
160
|
+
}), /* @__PURE__ */ c("div", {
|
|
161
161
|
className: "grid gap-1.5",
|
|
162
|
-
children: [/* @__PURE__ */
|
|
162
|
+
children: [/* @__PURE__ */ s(d, {
|
|
163
163
|
htmlFor: "item-dated",
|
|
164
164
|
children: "Dated"
|
|
165
|
-
}), /* @__PURE__ */
|
|
165
|
+
}), /* @__PURE__ */ s(u, {
|
|
166
166
|
id: "item-dated",
|
|
167
167
|
type: "date",
|
|
168
168
|
value: X,
|
|
@@ -170,33 +170,33 @@ function T({ open: T, onOpenChange: E, collectionId: D, onCreated: O }) {
|
|
|
170
170
|
})]
|
|
171
171
|
})]
|
|
172
172
|
}),
|
|
173
|
-
/* @__PURE__ */
|
|
173
|
+
/* @__PURE__ */ c("div", {
|
|
174
174
|
className: "grid gap-1.5",
|
|
175
175
|
children: [
|
|
176
|
-
/* @__PURE__ */
|
|
176
|
+
/* @__PURE__ */ s(d, {
|
|
177
177
|
htmlFor: "item-provenance",
|
|
178
178
|
children: "Provenance"
|
|
179
179
|
}),
|
|
180
|
-
/* @__PURE__ */
|
|
180
|
+
/* @__PURE__ */ s(y, {
|
|
181
181
|
id: "item-provenance",
|
|
182
182
|
value: H,
|
|
183
|
-
maxLength:
|
|
183
|
+
maxLength: C,
|
|
184
184
|
onChange: (e) => U(e.target.value),
|
|
185
185
|
placeholder: "Chain of custody / source history",
|
|
186
186
|
rows: 2
|
|
187
187
|
}),
|
|
188
|
-
/* @__PURE__ */
|
|
188
|
+
/* @__PURE__ */ s(i, {
|
|
189
189
|
length: H.length,
|
|
190
|
-
max:
|
|
190
|
+
max: C
|
|
191
191
|
})
|
|
192
192
|
]
|
|
193
193
|
}),
|
|
194
|
-
/* @__PURE__ */
|
|
194
|
+
/* @__PURE__ */ c("div", {
|
|
195
195
|
className: "grid gap-1.5",
|
|
196
|
-
children: [/* @__PURE__ */
|
|
196
|
+
children: [/* @__PURE__ */ s(d, {
|
|
197
197
|
htmlFor: "item-langs",
|
|
198
198
|
children: "Languages"
|
|
199
|
-
}), /* @__PURE__ */
|
|
199
|
+
}), /* @__PURE__ */ s(r, {
|
|
200
200
|
id: "item-langs",
|
|
201
201
|
value: K,
|
|
202
202
|
onChange: q,
|
|
@@ -204,36 +204,36 @@ function T({ open: T, onOpenChange: E, collectionId: D, onCreated: O }) {
|
|
|
204
204
|
preserveCase: !0
|
|
205
205
|
})]
|
|
206
206
|
}),
|
|
207
|
-
/* @__PURE__ */
|
|
207
|
+
/* @__PURE__ */ c("div", {
|
|
208
208
|
className: "grid gap-1.5",
|
|
209
|
-
children: [/* @__PURE__ */
|
|
209
|
+
children: [/* @__PURE__ */ s(d, {
|
|
210
210
|
htmlFor: "item-tags",
|
|
211
211
|
children: "Tags"
|
|
212
|
-
}), /* @__PURE__ */
|
|
212
|
+
}), /* @__PURE__ */ s(r, {
|
|
213
213
|
id: "item-tags",
|
|
214
214
|
value: J,
|
|
215
215
|
onChange: Y,
|
|
216
216
|
placeholder: "Add tags…"
|
|
217
217
|
})]
|
|
218
218
|
}),
|
|
219
|
-
Q && /* @__PURE__ */
|
|
219
|
+
Q && /* @__PURE__ */ s("p", {
|
|
220
220
|
className: "text-sm text-status-error-text",
|
|
221
221
|
children: Q
|
|
222
222
|
})
|
|
223
223
|
]
|
|
224
224
|
}),
|
|
225
|
-
/* @__PURE__ */
|
|
225
|
+
/* @__PURE__ */ c(f, { children: [/* @__PURE__ */ s(l, {
|
|
226
226
|
variant: "outline",
|
|
227
|
-
onClick: () =>
|
|
227
|
+
onClick: () => T(!1),
|
|
228
228
|
children: "Cancel"
|
|
229
|
-
}), /* @__PURE__ */
|
|
229
|
+
}), /* @__PURE__ */ s(l, {
|
|
230
230
|
onClick: async () => {
|
|
231
231
|
if (!A.trim()) {
|
|
232
232
|
$("A title is required.");
|
|
233
233
|
return;
|
|
234
234
|
}
|
|
235
235
|
try {
|
|
236
|
-
let e = await
|
|
236
|
+
let e = await O.mutateAsync({
|
|
237
237
|
title: A.trim(),
|
|
238
238
|
description: M.trim() || null,
|
|
239
239
|
kind: P,
|
|
@@ -244,14 +244,14 @@ function T({ open: T, onOpenChange: E, collectionId: D, onCreated: O }) {
|
|
|
244
244
|
origin: W.trim() || null,
|
|
245
245
|
languages: K.length ? K : null,
|
|
246
246
|
tags: J.length ? J : null,
|
|
247
|
-
datedAt:
|
|
247
|
+
datedAt: ue(X)
|
|
248
248
|
});
|
|
249
|
-
|
|
249
|
+
ne.success("Artifact added", e.title), T(!1), de(), D?.(e);
|
|
250
250
|
} catch (e) {
|
|
251
|
-
$(
|
|
251
|
+
$(te(e));
|
|
252
252
|
}
|
|
253
253
|
},
|
|
254
|
-
disabled:
|
|
254
|
+
disabled: O.isPending,
|
|
255
255
|
children: "Add artifact"
|
|
256
256
|
})] })
|
|
257
257
|
]
|
|
@@ -259,6 +259,6 @@ function T({ open: T, onOpenChange: E, collectionId: D, onCreated: O }) {
|
|
|
259
259
|
});
|
|
260
260
|
}
|
|
261
261
|
//#endregion
|
|
262
|
-
export {
|
|
262
|
+
export { w as CreateArchiveItemDialog };
|
|
263
263
|
|
|
264
264
|
//# sourceMappingURL=CreateArchiveItemDialog.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CreateArchiveItemDialog.js","names":[],"sources":["../../../src/theculturepost/components/CreateArchiveItemDialog.tsx"],"sourcesContent":["/**\n * CreateArchiveItemDialog — add an artifact to the archive.\n * @module theculturepost/components\n *\n * Writes `tcpCreateArchiveItem`. The binary is uploaded to wspace-files-svc via\n * the shared {@link MediaDropzone} (drag-drop) and only its id is stored here.\n * The schema exposes a SINGLE `file` per item — never a gallery. Kind + access\n * level are chosen up front.\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 { TagInput } from './TagInput';\nimport { MediaDropzone, Counter, type UploadedRef } from './RecordOralHistoryDialog';\nimport { useTcpCreateArchiveItem, useTcpCollections, FILE_FOLDERS } from '../hooks';\nimport { formatEnumLabel } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { TcpArchiveItem, TcpArchiveItemKind, TcpAccessLevel } from '../types';\n\nconst KINDS: TcpArchiveItemKind[] = [\n 'DOCUMENT',\n 'MANUSCRIPT',\n 'PHOTOGRAPH',\n 'AUDIO',\n 'VIDEO',\n 'MAP',\n 'OBJECT',\n];\n\nconst ACCESS_LEVELS: TcpAccessLevel[] = ['PUBLIC', 'COMMUNITY', '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 DESCRIPTION_MAX = 600;\nconst PROVENANCE_MAX = 600;\nconst ORIGIN_MAX = 160;\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 CreateArchiveItemDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n /** Pre-select a collection (e.g. from a collection detail page). */\n collectionId?: string;\n onCreated?: (item: TcpArchiveItem) => void;\n}\n\nexport function CreateArchiveItemDialog({\n open,\n onOpenChange,\n collectionId,\n onCreated,\n}: CreateArchiveItemDialogProps) {\n const createItem = useTcpCreateArchiveItem();\n const collectionsQuery = useTcpCollections({ limit: 100 });\n\n const [title, setTitle] = useState('');\n const [description, setDescription] = useState('');\n const [kind, setKind] = useState<TcpArchiveItemKind>('DOCUMENT');\n const [accessLevel, setAccessLevel] = useState<TcpAccessLevel>('PUBLIC');\n const [pickedCollection, setPickedCollection] = useState(collectionId ?? '');\n const [file, setFile] = useState<UploadedRef | null>(null);\n const [provenance, setProvenance] = useState('');\n const [origin, setOrigin] = useState('');\n const [languages, setLanguages] = useState<string[]>([]);\n const [tags, setTags] = useState<string[]>([]);\n const [datedAt, setDatedAt] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n const reset = () => {\n setTitle('');\n setDescription('');\n setKind('DOCUMENT');\n setAccessLevel('PUBLIC');\n setPickedCollection(collectionId ?? '');\n setFile(null);\n setProvenance('');\n setOrigin('');\n setLanguages([]);\n setTags([]);\n setDatedAt('');\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 createItem.mutateAsync({\n title: title.trim(),\n description: description.trim() || null,\n kind,\n accessLevel,\n collectionId: pickedCollection || null,\n fileId: file?.id ?? null,\n provenance: provenance.trim() || null,\n origin: origin.trim() || null,\n languages: languages.length ? languages : null,\n tags: tags.length ? tags : null,\n datedAt: dateToIso(datedAt),\n });\n notify.success('Artifact added', 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>Add an artifact</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Catalogue a manuscript, photograph, recording, map, object or document with its\n provenance.\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>File</Label>\n <MediaDropzone\n folder={FILE_FOLDERS.artifacts}\n accept=\"image/*,audio/*,video/*,application/pdf\"\n promptLabel=\"Drag and drop the artifact file here\"\n hint=\"Image, audio, video or PDF, up to 25 MB. One file per artifact.\"\n value={file}\n onUploaded={(ref) => setFile(ref)}\n onClear={() => setFile(null)}\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"item-title\">\n Title <span className=\"text-status-error-text\">*</span>\n </Label>\n <Input\n id=\"item-title\"\n value={title}\n maxLength={TITLE_MAX}\n onChange={(e) => setTitle(e.target.value)}\n placeholder=\"e.g. Palm-leaf manuscript, 18th c.\"\n />\n <Counter length={title.length} max={TITLE_MAX} />\n </div>\n\n <div className=\"grid gap-3 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label>Kind</Label>\n <Select value={kind} onValueChange={(v) => setKind(v as TcpArchiveItemKind)}>\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {KINDS.map((k) => (\n <SelectItem key={k} value={k}>\n {formatEnumLabel(k)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\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>\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=\"item-desc\">Description</Label>\n <Textarea\n id=\"item-desc\"\n value={description}\n maxLength={DESCRIPTION_MAX}\n onChange={(e) => setDescription(e.target.value)}\n rows={2}\n />\n <Counter length={description.length} max={DESCRIPTION_MAX} />\n </div>\n\n <div className=\"grid gap-3 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"item-origin\">Origin</Label>\n <Input\n id=\"item-origin\"\n value={origin}\n maxLength={ORIGIN_MAX}\n onChange={(e) => setOrigin(e.target.value)}\n placeholder=\"Place of origin\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"item-dated\">Dated</Label>\n <Input\n id=\"item-dated\"\n type=\"date\"\n value={datedAt}\n onChange={(e) => setDatedAt(e.target.value)}\n />\n </div>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"item-provenance\">Provenance</Label>\n <Textarea\n id=\"item-provenance\"\n value={provenance}\n maxLength={PROVENANCE_MAX}\n onChange={(e) => setProvenance(e.target.value)}\n placeholder=\"Chain of custody / source history\"\n rows={2}\n />\n <Counter length={provenance.length} max={PROVENANCE_MAX} />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"item-langs\">Languages</Label>\n <TagInput\n id=\"item-langs\"\n value={languages}\n onChange={setLanguages}\n placeholder=\"e.g. Tamil, Sanskrit…\"\n preserveCase\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"item-tags\">Tags</Label>\n <TagInput id=\"item-tags\" value={tags} onChange={setTags} placeholder=\"Add tags…\" />\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={createItem.isPending}>\n Add artifact\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CreateArchiveItemDialog.js","names":[],"sources":["../../../src/theculturepost/components/CreateArchiveItemDialog.tsx"],"sourcesContent":["/**\n * CreateArchiveItemDialog — add an artifact to the archive.\n * @module theculturepost/components\n *\n * Writes `tcpCreateArchiveItem`. The binary is uploaded to wspace-files-svc via\n * the shared {@link MediaDropzone} (drag-drop) and only its id is stored here.\n * The schema exposes a SINGLE `file` per item — never a gallery. Kind + access\n * level are chosen up front.\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 { TagInput } from './TagInput';\nimport { MediaDropzone, Counter, type UploadedRef } from './RecordOralHistoryDialog';\nimport { useTcpCreateArchiveItem, useTcpCollections, FILE_FOLDERS } from '../hooks';\nimport { formatEnumLabel } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { TcpArchiveItem, TcpArchiveItemKind, TcpAccessLevel } from '../types';\n\nconst KINDS: TcpArchiveItemKind[] = [\n 'DOCUMENT',\n 'MANUSCRIPT',\n 'PHOTOGRAPH',\n 'AUDIO',\n 'VIDEO',\n 'MAP',\n 'OBJECT',\n];\n\nconst ACCESS_LEVELS: TcpAccessLevel[] = ['PUBLIC', 'COMMUNITY', '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 DESCRIPTION_MAX = 600;\nconst PROVENANCE_MAX = 600;\nconst ORIGIN_MAX = 160;\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 CreateArchiveItemDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n /** Pre-select a collection (e.g. from a collection detail page). */\n collectionId?: string;\n onCreated?: (item: TcpArchiveItem) => void;\n}\n\nexport function CreateArchiveItemDialog({\n open,\n onOpenChange,\n collectionId,\n onCreated,\n}: CreateArchiveItemDialogProps) {\n const createItem = useTcpCreateArchiveItem();\n const collectionsQuery = useTcpCollections({ limit: 100 });\n\n const [title, setTitle] = useState('');\n const [description, setDescription] = useState('');\n const [kind, setKind] = useState<TcpArchiveItemKind>('DOCUMENT');\n const [accessLevel, setAccessLevel] = useState<TcpAccessLevel>('PUBLIC');\n const [pickedCollection, setPickedCollection] = useState(collectionId ?? '');\n const [file, setFile] = useState<UploadedRef | null>(null);\n const [provenance, setProvenance] = useState('');\n const [origin, setOrigin] = useState('');\n const [languages, setLanguages] = useState<string[]>([]);\n const [tags, setTags] = useState<string[]>([]);\n const [datedAt, setDatedAt] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n const reset = () => {\n setTitle('');\n setDescription('');\n setKind('DOCUMENT');\n setAccessLevel('PUBLIC');\n setPickedCollection(collectionId ?? '');\n setFile(null);\n setProvenance('');\n setOrigin('');\n setLanguages([]);\n setTags([]);\n setDatedAt('');\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 createItem.mutateAsync({\n title: title.trim(),\n description: description.trim() || null,\n kind,\n accessLevel,\n collectionId: pickedCollection || null,\n fileId: file?.id ?? null,\n provenance: provenance.trim() || null,\n origin: origin.trim() || null,\n languages: languages.length ? languages : null,\n tags: tags.length ? tags : null,\n datedAt: dateToIso(datedAt),\n });\n notify.success('Artifact added', 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>Add an artifact</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Catalogue a manuscript, photograph, recording, map, object or document with its\n provenance.\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>File</Label>\n <MediaDropzone\n folder={FILE_FOLDERS.artifacts}\n accept=\"image/*,audio/*,video/*,application/pdf\"\n promptLabel=\"Drag and drop the artifact file here\"\n hint=\"Image, audio, video or PDF, up to 25 MB. One file per artifact.\"\n value={file}\n onUploaded={(ref) => setFile(ref)}\n onClear={() => setFile(null)}\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"item-title\">\n Title <span className=\"text-status-error-text\">*</span>\n </Label>\n <Input\n id=\"item-title\"\n value={title}\n maxLength={TITLE_MAX}\n onChange={(e) => setTitle(e.target.value)}\n placeholder=\"e.g. Palm-leaf manuscript, 18th c.\"\n />\n <Counter length={title.length} max={TITLE_MAX} />\n </div>\n\n <div className=\"grid gap-3 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label>Kind</Label>\n <Select value={kind} onValueChange={(v) => setKind(v as TcpArchiveItemKind)}>\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {KINDS.map((k) => (\n <SelectItem key={k} value={k}>\n {formatEnumLabel(k)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\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>\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=\"item-desc\">Description</Label>\n <Textarea\n id=\"item-desc\"\n value={description}\n maxLength={DESCRIPTION_MAX}\n onChange={(e) => setDescription(e.target.value)}\n rows={2}\n />\n <Counter length={description.length} max={DESCRIPTION_MAX} />\n </div>\n\n <div className=\"grid gap-3 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"item-origin\">Origin</Label>\n <Input\n id=\"item-origin\"\n value={origin}\n maxLength={ORIGIN_MAX}\n onChange={(e) => setOrigin(e.target.value)}\n placeholder=\"Place of origin\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"item-dated\">Dated</Label>\n <Input\n id=\"item-dated\"\n type=\"date\"\n value={datedAt}\n onChange={(e) => setDatedAt(e.target.value)}\n />\n </div>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"item-provenance\">Provenance</Label>\n <Textarea\n id=\"item-provenance\"\n value={provenance}\n maxLength={PROVENANCE_MAX}\n onChange={(e) => setProvenance(e.target.value)}\n placeholder=\"Chain of custody / source history\"\n rows={2}\n />\n <Counter length={provenance.length} max={PROVENANCE_MAX} />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"item-langs\">Languages</Label>\n <TagInput\n id=\"item-langs\"\n value={languages}\n onChange={setLanguages}\n placeholder=\"e.g. Tamil, Sanskrit…\"\n preserveCase\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"item-tags\">Tags</Label>\n <TagInput id=\"item-tags\" value={tags} onChange={setTags} placeholder=\"Add tags…\" />\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={createItem.isPending}>\n Add artifact\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { FILE_FOLDERS as e } from "../hooks/useFiles.js";
|
|
2
2
|
import { useTcpCreateCollection as t } from "../hooks/useTcpApi.js";
|
|
3
|
-
import "../hooks/index.js";
|
|
4
3
|
import { describeError as n, notify as r } from "../lib/toast.js";
|
|
4
|
+
import "../hooks/index.js";
|
|
5
5
|
import { slugify as i } from "../utils/slug.js";
|
|
6
6
|
import { Counter as a, MediaDropzone as o } from "./RecordOralHistoryDialog.js";
|
|
7
7
|
import { useState as s } from "react";
|