ui video selector

This commit is contained in:
2026-02-06 09:41:50 -03:00
parent daabd15c19
commit 2cf6c89fbb
7 changed files with 613 additions and 47 deletions

View File

@@ -0,0 +1,143 @@
import { useState, useEffect } from "react";
import { getPresets, getJobs, createJob, cancelJob } from "./api";
import type { MediaAsset, TranscodePreset, TranscodeJob } from "./types";
interface JobPanelProps {
asset: MediaAsset;
trimStart: number;
trimEnd: number;
}
export default function JobPanel({ asset, trimStart, trimEnd }: JobPanelProps) {
const [presets, setPresets] = useState<TranscodePreset[]>([]);
const [jobs, setJobs] = useState<TranscodeJob[]>([]);
const [selectedPresetId, setSelectedPresetId] = useState<string>("");
const [submitting, setSubmitting] = useState(false);
// Load presets on mount
useEffect(() => {
getPresets().then(setPresets).catch(console.error);
}, []);
// Poll jobs for this asset
useEffect(() => {
let active = true;
const fetchJobs = () => {
getJobs()
.then((allJobs) => {
if (active) {
setJobs(
allJobs.filter((j) => j.source_asset_id === asset.id),
);
}
})
.catch(console.error);
};
fetchJobs();
const interval = setInterval(fetchJobs, 3000);
return () => {
active = false;
clearInterval(interval);
};
}, [asset.id]);
const hasTrim = trimStart > 0 || (asset.duration != null && trimEnd < asset.duration);
const hasPreset = selectedPresetId !== "";
const canSubmit = hasTrim || hasPreset;
const buttonLabel = hasPreset
? "Transcode"
: hasTrim
? "Trim (Copy)"
: "Select trim or preset";
async function handleSubmit() {
setSubmitting(true);
try {
await createJob({
source_asset_id: asset.id,
preset_id: selectedPresetId || null,
trim_start: hasTrim ? trimStart : null,
trim_end: hasTrim ? trimEnd : null,
});
// Refresh jobs immediately
const allJobs = await getJobs();
setJobs(allJobs.filter((j) => j.source_asset_id === asset.id));
} catch (e) {
alert(e instanceof Error ? e.message : "Failed to create job");
} finally {
setSubmitting(false);
}
}
async function handleCancel(jobId: string) {
try {
await cancelJob(jobId);
const allJobs = await getJobs();
setJobs(allJobs.filter((j) => j.source_asset_id === asset.id));
} catch (e) {
console.error("Cancel failed:", e);
}
}
return (
<div className="job-panel">
<div className="job-controls">
<select
value={selectedPresetId}
onChange={(e) => setSelectedPresetId(e.target.value)}
className="preset-select"
>
<option value="">No preset (trim only)</option>
{presets.map((p) => (
<option key={p.id} value={p.id}>
{p.name}
</option>
))}
</select>
<button
onClick={handleSubmit}
disabled={!canSubmit || submitting}
className="enqueue-button"
>
{submitting ? "Submitting..." : buttonLabel}
</button>
</div>
{jobs.length > 0 && (
<div className="job-list">
<h3>Jobs</h3>
{jobs.map((job) => (
<div key={job.id} className="job-item">
<div className="job-item-header">
<span className="job-filename">{job.output_filename}</span>
<span className={`job-status ${job.status}`}>{job.status}</span>
</div>
{job.status === "processing" && (
<div className="job-progress-bar">
<div
className="job-progress-fill"
style={{ width: `${job.progress}%` }}
/>
</div>
)}
{(job.status === "pending" || job.status === "processing") && (
<button
className="job-cancel"
onClick={() => handleCancel(job.id)}
>
Cancel
</button>
)}
{job.status === "failed" && job.error_message && (
<div className="job-error">{job.error_message}</div>
)}
</div>
))}
</div>
)}
</div>
);
}