add an unlink buttton to unlink timestamps in the segments table

Add progress bar for uploading
This commit is contained in:
2026-06-03 00:09:47 +02:00
parent 40ab840a71
commit da898ef6f8
3 changed files with 363 additions and 15 deletions
+63 -12
View File
@@ -38,6 +38,8 @@ import {
CallSplit,
ContentCopy,
Delete,
Link,
LinkOff,
MyLocation,
Settings,
SkipNext,
@@ -133,8 +135,22 @@ export default function App() {
const [job, setJob] = useState<Job | null>(null);
const [outputs, setOutputs] = useState<string[]>([]);
const [isUploading, setIsUploading] = useState(false);
const [uploadProgress, setUploadProgress] = useState(0);
const [error, setError] = useState<string | null>(null);
type SegmentRow = {
kind: "intro" | "core" | "outro";
label: string;
start: number;
end: number;
startEditable: boolean;
endEditable: boolean;
coreIndex?: number;
};
const [isLinked, setIsLinked] = useState(true);
const [customSegmentRows, setCustomSegmentRows] = useState<SegmentRow[]>([]);
const videoRef = useRef<HTMLVideoElement | null>(null);
useEffect(() => {
@@ -340,16 +356,6 @@ export default function App() {
return Array.from(markMap.values()).sort((a, b) => a.value - b.value);
}, [markers, project, duration]);
type SegmentRow = {
kind: "intro" | "core" | "outro";
label: string;
start: number;
end: number;
startEditable: boolean;
endEditable: boolean;
coreIndex?: number;
};
const introBoundary = useMemo(() => {
if (!project) return 0;
const maxDuration = duration || project.intro_seconds;
@@ -394,7 +400,7 @@ export default function App() {
});
}, [duration, coreBoundaries, frameStep]);
const segmentRows = useMemo(() => {
const derivedSegmentRows = useMemo(() => {
if (!duration || !project || coreBoundaries.length < 2) return [] as SegmentRow[];
const introEnd = Math.max(0, introBoundary - frameStep);
const outroStart = clamp(alignedOutroBoundary, 0, duration);
@@ -430,6 +436,8 @@ export default function App() {
return rows;
}, [duration, project, coreBoundaries, introBoundary, alignedOutroBoundary, lastFrameTime, frameStep, segments]);
const segmentRows = isLinked ? derivedSegmentRows : customSegmentRows;
useEffect(() => {
if (!segmentRows.length) {
setSegmentDrafts([]);
@@ -602,9 +610,12 @@ export default function App() {
if (videoFiles.length === 0) return;
setError(null);
setIsUploading(true);
setUploadProgress(0);
try {
const uploadedItems = await uploadVideos(videoFiles);
const uploadedItems = await uploadVideos(videoFiles, (index, total, progress) => {
setUploadProgress((index + progress) / total);
});
const uploaded = uploadedItems[0];
await refreshVideos();
if (uploaded) {
@@ -616,6 +627,7 @@ export default function App() {
setOutputs([]);
setJob(null);
setOutputPrefix(uploaded.filename.replace(/\.[^.]+$/, ""));
setIsLinked(true);
}
setVideoFiles([]);
setPreviewUrl("");
@@ -639,6 +651,7 @@ export default function App() {
setOutputPrefix(item.filename.replace(/\.[^.]+$/, ""));
setVideoFiles([]);
setPreviewUrl("");
setIsLinked(true);
try {
const response = await listMarkers(item.id);
setMarkers(normalizeMarkers(response.markers ?? []));
@@ -799,6 +812,17 @@ export default function App() {
}
const snapped = snapToFrame(parsed);
if (!isLinked) {
setCustomSegmentRows((prev) => {
const next = [...prev];
next[rowIndex] = { ...next[rowIndex], [field]: snapped };
return next;
});
setPendingSegmentEditPersist(true);
return;
}
let boundaryIndex = -1;
let boundaryValue = snapped;
@@ -854,6 +878,12 @@ export default function App() {
const handleDeleteSegment = async (row: SegmentRow) => {
setError(null);
if (!isLinked) {
setCustomSegmentRows((prev) => prev.filter((r) => r !== row));
setPendingSegmentEditPersist(true);
return;
}
if (row.kind === "intro") {
await saveProject({ intro_seconds: 0 });
setPendingSegmentEditPersist(true);
@@ -1014,6 +1044,14 @@ export default function App() {
Project settings
</Button>
</Box>
{isUploading && (
<Box sx={{ mt: 1 }}>
<LinearProgress variant="determinate" value={uploadProgress * 100} />
<Typography variant="body2" color="text.secondary" sx={{ mt: 0.5 }}>
Uploading... {Math.round(uploadProgress * 100)}%
</Typography>
</Box>
)}
<Box>
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 1 }}>
<Typography variant="subtitle2">Recent uploads</Typography>
@@ -1355,6 +1393,19 @@ export default function App() {
<Button variant="outlined" onClick={handleSaveMarkers} disabled={!video || markers.length === 0}>
Save markers
</Button>
<Button
variant="outlined"
startIcon={isLinked ? <LinkOff /> : <Link />}
onClick={() => {
if (isLinked) {
setCustomSegmentRows(derivedSegmentRows);
}
setIsLinked(!isLinked);
}}
disabled={!duration}
>
{isLinked ? "Unlink timestamps" : "Link timestamps"}
</Button>
</Stack>
</Grid>
<Grid item xs={12}>