From 7caf41e17c4ea411d128e57529789171fcf2ca94 Mon Sep 17 00:00:00 2001 From: twotalesanimation <80506065+twotalesanimation@users.noreply.github.com> Date: Fri, 7 Aug 2026 07:34:10 +0200 Subject: [PATCH] Slates --- VFXReviewConnector.jsx | 46 ++--- actions/shots.ts | 34 ++++ .../projects/[id]/shots/[shotId]/page.tsx | 20 +- components/shots/ShotSlateTab.tsx | 190 ++++++++++++++++++ lib/render-pipeline/exports.ts | 31 +-- .../migration.sql | 5 + prisma/schema.prisma | 5 + types/index.ts | 5 + 8 files changed, 282 insertions(+), 54 deletions(-) create mode 100644 components/shots/ShotSlateTab.tsx create mode 100644 prisma/migrations/20260807000000_shot_slate_fields/migration.sql diff --git a/VFXReviewConnector.jsx b/VFXReviewConnector.jsx index 852b778..1857a8c 100644 --- a/VFXReviewConnector.jsx +++ b/VFXReviewConnector.jsx @@ -23,7 +23,6 @@ var HANDLE_FRAMES = 8; lastActionText: null, pipelineStatusText: null, urgentCheckbox: null, - vfxScopeField: null, submissionNoteField: null }; @@ -2452,9 +2451,6 @@ var HANDLE_FRAMES = 8; if (pipelineState.prefilledShotCode === shotCode) { return; } - if (uiState.vfxScopeField) { - uiState.vfxScopeField.text = (exportInfo && exportInfo.vfxScope) ? exportInfo.vfxScope : ""; - } if (uiState.submissionNoteField) { uiState.submissionNoteField.text = (exportInfo && exportInfo.submissionNote) ? exportInfo.submissionNote : ""; } @@ -2534,13 +2530,12 @@ var HANDLE_FRAMES = 8; if (urgent) { body.priority = 20; } - // Sent every time so edits stick; omitting them would make the server - // inherit the previous submission's values instead. - if (uiState.vfxScopeField) { - body.vfxScope = uiState.vfxScopeField.text; - } + // submissionNote is a per-export override; if blank the server falls back to the shot's slate default. if (uiState.submissionNoteField) { - body.submissionNote = uiState.submissionNoteField.text; + var note = uiState.submissionNoteField.text; + if (note && note.trim()) { + body.submissionNote = note; + } } return postJSON(BASE_URL + "/api/ext/exports", body); } @@ -2671,10 +2666,6 @@ var HANDLE_FRAMES = 8; var shotBuilderGroup; var shotBuilderButton; var pipelinePanel; - var slateScopeGroup; - var slateScopeLabel; - var slateNoteGroup; - var slateNoteLabel; var pipelineRow1; var pipelineRow2; var queueExportButton; @@ -2753,24 +2744,6 @@ var HANDLE_FRAMES = 8; uiState.pipelineStatusText = pipelinePanel.add("statictext", undefined, "Export status: not checked"); - slateScopeGroup = pipelinePanel.add("group"); - slateScopeGroup.orientation = "row"; - slateScopeGroup.alignChildren = ["fill", "center"]; - slateScopeGroup.spacing = 6; - slateScopeLabel = slateScopeGroup.add("statictext", undefined, "VFX Scope:"); - slateScopeLabel.preferredSize = [90, 20]; - uiState.vfxScopeField = slateScopeGroup.add("edittext", undefined, ""); - uiState.vfxScopeField.preferredSize = [220, 22]; - - slateNoteGroup = pipelinePanel.add("group"); - slateNoteGroup.orientation = "row"; - slateNoteGroup.alignChildren = ["fill", "top"]; - slateNoteGroup.spacing = 6; - slateNoteLabel = slateNoteGroup.add("statictext", undefined, "Submission Note:"); - slateNoteLabel.preferredSize = [90, 20]; - uiState.submissionNoteField = slateNoteGroup.add("edittext", undefined, "", { multiline: true }); - uiState.submissionNoteField.preferredSize = [220, 48]; - pipelineRow1 = pipelinePanel.add("group"); pipelineRow1.orientation = "row"; pipelineRow1.alignChildren = ["fill", "center"]; @@ -2778,6 +2751,15 @@ var HANDLE_FRAMES = 8; queueExportButton = pipelineRow1.add("button", undefined, "Queue Export"); uiState.urgentCheckbox = pipelineRow1.add("checkbox", undefined, "Urgent"); + var slateNoteGroup = pipelinePanel.add("group"); + slateNoteGroup.orientation = "row"; + slateNoteGroup.alignChildren = ["fill", "top"]; + slateNoteGroup.spacing = 6; + var slateNoteLabel = slateNoteGroup.add("statictext", undefined, "Sub. Note:"); + slateNoteLabel.preferredSize = [70, 20]; + uiState.submissionNoteField = slateNoteGroup.add("edittext", undefined, "", { multiline: false }); + uiState.submissionNoteField.preferredSize = [240, 22]; + pipelineRow2 = pipelinePanel.add("group"); pipelineRow2.orientation = "row"; pipelineRow2.alignChildren = ["fill", "center"]; diff --git a/actions/shots.ts b/actions/shots.ts index 796b487..6f05876 100644 --- a/actions/shots.ts +++ b/actions/shots.ts @@ -489,6 +489,40 @@ export async function updateShotNotes(shotId: string, notes: string) { return { success: true }; } +// ── Update Shot Slate Fields ────────────────────────────────────────────────── + +export async function updateShotSlate( + shotId: string, + data: { + slateType?: string | null; + slateDescription?: string | null; + slateVfxScope?: string | null; + slateSubmissionNote?: string | null; + } +) { + const session = await auth(); + if (!session?.user) throw new Error("Unauthorized"); + if (!["ADMIN", "PRODUCER", "SUPERVISOR"].includes(session.user.role)) { + throw new Error("Insufficient permissions"); + } + + const shot = await db.shot.findUnique({ where: { id: shotId }, select: { projectId: true } }); + if (!shot) throw new Error("Shot not found"); + + await db.shot.update({ + where: { id: shotId }, + data: { + slateType: data.slateType !== undefined ? (data.slateType?.trim() || null) : undefined, + slateDescription: data.slateDescription !== undefined ? (data.slateDescription?.trim() || null) : undefined, + slateVfxScope: data.slateVfxScope !== undefined ? (data.slateVfxScope?.trim() || null) : undefined, + slateSubmissionNote: data.slateSubmissionNote !== undefined ? (data.slateSubmissionNote?.trim() || null) : undefined, + }, + }); + + revalidatePath(`/projects/${shot.projectId}`); + return { success: true }; +} + // ── Toggle Key Shot ─────────────────────────────────────────────────────────── export async function toggleKeyShot(shotId: string, isKeyShot: boolean) { diff --git a/app/(dashboard)/projects/[id]/shots/[shotId]/page.tsx b/app/(dashboard)/projects/[id]/shots/[shotId]/page.tsx index 2688ed6..404534a 100644 --- a/app/(dashboard)/projects/[id]/shots/[shotId]/page.tsx +++ b/app/(dashboard)/projects/[id]/shots/[shotId]/page.tsx @@ -30,11 +30,13 @@ import { FileVideo, Pencil, Check, + LayoutList, } from "lucide-react"; import { Input } from "@/components/ui/input"; import type { ShotWithDetails } from "@/types"; import { ShotSettingsTab } from "@/components/shots/ShotSettingsTab"; import { ShotExportsTab } from "@/components/shots/ShotExportsTab"; +import { ShotSlateTab } from "@/components/shots/ShotSlateTab"; import { FootageViewer } from "@/components/shots/FootageViewer"; import { HighResUploadDialog } from "@/components/shots/HighResUploadDialog"; import { duplicateShot, internallyApproveShot, shareWithClient, unshareFromClient, unapproveShot, updateShotVersion } from "@/actions/shots"; @@ -90,7 +92,7 @@ export default function ShotDetailPage() { const [isDuplicating, setIsDuplicating] = useState(false); const [isActioning, setIsActioning] = useState(false); const [highResDialogOpen, setHighResDialogOpen] = useState(false); - const [activeTab, setActiveTab] = useState<"tasks" | "reviews" | "footage" | "exports" | "settings">("tasks"); + const [activeTab, setActiveTab] = useState<"tasks" | "slate" | "reviews" | "footage" | "exports" | "settings">("tasks"); const [editingVersion, setEditingVersion] = useState(false); const [versionInput, setVersionInput] = useState(""); const [savingVersion, setSavingVersion] = useState(false); @@ -520,6 +522,18 @@ export default function ShotDetailPage() { Tasks +