- Add canvas-based mask drawing tools (brush, eraser, rect, lasso) - Add undo/redo history support for mask editing - Integrate inpainting UI into preview player - Add backend API endpoint for inpainting requests - Add MediaService.inpaint method with ComfyUI workflow support - Add Flux inpainting workflows for selfhost and RunningHub 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
162 lines
4.0 KiB
TypeScript
162 lines
4.0 KiB
TypeScript
import { create } from 'zustand'
|
|
|
|
// Inpainting types
|
|
export type InpaintingTool = 'brush' | 'eraser' | 'rect' | 'lasso'
|
|
|
|
export interface StoryboardFrame {
|
|
id: string
|
|
index: number
|
|
narration: string
|
|
imagePrompt: string
|
|
imagePath?: string
|
|
audioPath?: string
|
|
duration: number
|
|
order: number
|
|
}
|
|
|
|
export interface Storyboard {
|
|
id: string
|
|
title: string
|
|
frames: StoryboardFrame[]
|
|
totalDuration: number
|
|
}
|
|
|
|
interface EditorState {
|
|
storyboard: Storyboard | null
|
|
selectedFrameId: string | null
|
|
isPlaying: boolean
|
|
currentTime: number
|
|
|
|
// Inpainting state
|
|
isInpaintingMode: boolean
|
|
inpaintingTool: InpaintingTool
|
|
brushSize: number
|
|
currentMask: string | null
|
|
inpaintPrompt: string
|
|
|
|
// Actions
|
|
setStoryboard: (storyboard: Storyboard) => void
|
|
selectFrame: (frameId: string | null) => void
|
|
setSelectedFrameId: (frameId: string | null) => void // Alias for selectFrame
|
|
reorderFrames: (fromIndex: number, toIndex: number) => void
|
|
updateFrameDuration: (frameId: string, duration: number) => void
|
|
updateFrame: (frameId: string, updates: Partial<StoryboardFrame>) => void
|
|
setPlaying: (playing: boolean) => void
|
|
setCurrentTime: (time: number | ((prev: number) => number)) => void
|
|
|
|
// Inpainting actions
|
|
setInpaintingMode: (mode: boolean) => void
|
|
setInpaintingTool: (tool: InpaintingTool) => void
|
|
setBrushSize: (size: number) => void
|
|
setCurrentMask: (mask: string | null) => void
|
|
setInpaintPrompt: (prompt: string) => void
|
|
resetInpainting: () => void
|
|
}
|
|
|
|
export const useEditorStore = create<EditorState>((set, get) => ({
|
|
storyboard: null,
|
|
selectedFrameId: null,
|
|
isPlaying: false,
|
|
currentTime: 0,
|
|
|
|
// Inpainting initial state
|
|
isInpaintingMode: false,
|
|
inpaintingTool: 'brush' as InpaintingTool,
|
|
brushSize: 20,
|
|
currentMask: null,
|
|
inpaintPrompt: '',
|
|
|
|
setStoryboard: (storyboard) => set({ storyboard }),
|
|
|
|
selectFrame: (frameId) => set({ selectedFrameId: frameId }),
|
|
|
|
setSelectedFrameId: (frameId) => set({ selectedFrameId: frameId }),
|
|
|
|
reorderFrames: (fromIndex, toIndex) => {
|
|
const { storyboard } = get()
|
|
if (!storyboard) return
|
|
|
|
const frames = [...storyboard.frames]
|
|
const [removed] = frames.splice(fromIndex, 1)
|
|
frames.splice(toIndex, 0, removed)
|
|
|
|
// Update order values
|
|
const reorderedFrames = frames.map((frame, idx) => ({
|
|
...frame,
|
|
order: idx,
|
|
}))
|
|
|
|
set({
|
|
storyboard: {
|
|
...storyboard,
|
|
frames: reorderedFrames,
|
|
},
|
|
})
|
|
},
|
|
|
|
updateFrameDuration: (frameId, duration) => {
|
|
const { storyboard } = get()
|
|
if (!storyboard) return
|
|
|
|
const frames = storyboard.frames.map((frame) =>
|
|
frame.id === frameId ? { ...frame, duration } : frame
|
|
)
|
|
|
|
const totalDuration = frames.reduce((sum, f) => sum + f.duration, 0)
|
|
|
|
set({
|
|
storyboard: {
|
|
...storyboard,
|
|
frames,
|
|
totalDuration,
|
|
},
|
|
})
|
|
},
|
|
|
|
updateFrame: (frameId, updates) => {
|
|
const { storyboard } = get()
|
|
if (!storyboard) return
|
|
|
|
const frames = storyboard.frames.map((frame) =>
|
|
frame.id === frameId ? { ...frame, ...updates } : frame
|
|
)
|
|
|
|
const totalDuration = frames.reduce((sum, f) => sum + f.duration, 0)
|
|
|
|
set({
|
|
storyboard: {
|
|
...storyboard,
|
|
frames,
|
|
totalDuration,
|
|
},
|
|
})
|
|
},
|
|
|
|
setPlaying: (playing) => set({ isPlaying: playing }),
|
|
|
|
setCurrentTime: (time) => {
|
|
if (typeof time === 'function') {
|
|
const { currentTime } = get()
|
|
set({ currentTime: time(currentTime) })
|
|
} else {
|
|
set({ currentTime: time })
|
|
}
|
|
},
|
|
|
|
// Inpainting actions
|
|
setInpaintingMode: (mode) => set({ isInpaintingMode: mode }),
|
|
setInpaintingTool: (tool) => set({ inpaintingTool: tool }),
|
|
setBrushSize: (size) => set({ brushSize: size }),
|
|
setCurrentMask: (mask) => set({ currentMask: mask }),
|
|
setInpaintPrompt: (prompt) => set({ inpaintPrompt: prompt }),
|
|
resetInpainting: () => set({
|
|
isInpaintingMode: false,
|
|
inpaintingTool: 'brush',
|
|
brushSize: 20,
|
|
currentMask: null,
|
|
inpaintPrompt: '',
|
|
}),
|
|
}))
|
|
|
|
|