diff --git a/src/lib/ai/ai-operations.svelte.ts b/src/lib/ai/ai-operations.svelte.ts index 7a33663..d4c66d4 100644 --- a/src/lib/ai/ai-operations.svelte.ts +++ b/src/lib/ai/ai-operations.svelte.ts @@ -4,7 +4,7 @@ * Handles progressive tool execution with layer ID tracking across tool calls. * Refactored to use shared 'mutations.ts' logic. */ -import { projectStore } from '$lib/stores/project.svelte'; +import type { ProjectStore } from '$lib/stores/project.svelte'; import type { CreateLayerInput, CreateLayerOutput, @@ -48,9 +48,9 @@ export function resetLayerTracking() { // Context Helper // ============================================ -function getContext(): MutationContext { +function getContext(projectStore: ProjectStore): MutationContext { return { - project: projectStore.project, + project: projectStore.state, layerIdMap: layerIdMap, layerCreationIndex: layerCreationIndex }; @@ -63,8 +63,11 @@ function getContext(): MutationContext { /** * Execute create_layer tool */ -export function executeCreateLayer(input: CreateLayerInput): CreateLayerOutput { - const ctx = getContext(); +export function executeCreateLayer( + projectStore: ProjectStore, + input: CreateLayerInput +): CreateLayerOutput { + const ctx = getContext(projectStore); const result = mutateCreateLayer(ctx, input); // Update local index tracker @@ -86,26 +89,36 @@ export function executeCreateLayer(input: CreateLayerInput): CreateLayerOutput { /** * Execute animate_layer tool */ -export function executeAnimateLayer(input: AnimateLayerInput): AnimateLayerOutput { - return mutateAnimateLayer(getContext(), input); +export function executeAnimateLayer( + projectStore: ProjectStore, + input: AnimateLayerInput +): AnimateLayerOutput { + return mutateAnimateLayer(getContext(projectStore), input); } /** * Execute edit_layer tool */ -export function executeEditLayer(input: EditLayerInput): EditLayerOutput { - return mutateEditLayer(getContext(), input); +export function executeEditLayer( + projectStore: ProjectStore, + input: EditLayerInput +): EditLayerOutput { + return mutateEditLayer(getContext(projectStore), input); } /** * Execute remove_layer tool */ -export function executeRemoveLayer(input: RemoveLayerInput): RemoveLayerOutput { - const result = mutateRemoveLayer(getContext(), input); +export function executeRemoveLayer( + projectStore: ProjectStore, + input: RemoveLayerInput +): RemoveLayerOutput { + const result = mutateRemoveLayer(getContext(projectStore), input); if (result.success) { if ( projectStore.selectedLayerId && - getContext().project.layers.find((l) => l.id === projectStore.selectedLayerId) === undefined + getContext(projectStore).project.layers.find((l) => l.id === projectStore.selectedLayerId) === + undefined ) { projectStore.selectedLayerId = null; } @@ -116,6 +129,9 @@ export function executeRemoveLayer(input: RemoveLayerInput): RemoveLayerOutput { /** * Execute configure_project tool */ -export function executeConfigureProject(input: ConfigureProjectInput): ConfigureProjectOutput { - return mutateConfigureProject(getContext(), input); +export function executeConfigureProject( + projectStore: ProjectStore, + input: ConfigureProjectInput +): ConfigureProjectOutput { + return mutateConfigureProject(getContext(projectStore), input); } diff --git a/src/lib/components/ai/ai-chat.svelte b/src/lib/components/ai/ai-chat.svelte index ef62d65..77be9bd 100644 --- a/src/lib/components/ai/ai-chat.svelte +++ b/src/lib/components/ai/ai-chat.svelte @@ -1,5 +1,5 @@ @@ -187,7 +190,7 @@ style:left="-10000px" style:top="-10000px" style:background-color="rgba(127, 127, 127, 0.6)" - style:clip-path={`polygon(evenodd, 0 0, 0 20000px, 20000px 20000px, 20000px 0, 0 0, ${10000 - projectStore.project.width / 2}px ${10000 - projectStore.project.height / 2}px, ${10000 - projectStore.project.width / 2}px ${10000 + projectStore.project.height / 2}px, ${10000 + projectStore.project.width / 2}px ${10000 + projectStore.project.height / 2}px, ${10000 + projectStore.project.width / 2}px ${10000 - projectStore.project.height / 2}px, ${10000 - projectStore.project.width / 2}px ${10000 - projectStore.project.height / 2}px)`} + style:clip-path={`polygon(evenodd, 0 0, 0 20000px, 20000px 20000px, 20000px 0, 0 0, ${10000 - projectStore.state.width / 2}px ${10000 - projectStore.state.height / 2}px, ${10000 - projectStore.state.width / 2}px ${10000 + projectStore.state.height / 2}px, ${10000 + projectStore.state.width / 2}px ${10000 + projectStore.state.height / 2}px, ${10000 + projectStore.state.width / 2}px ${10000 - projectStore.state.height / 2}px, ${10000 - projectStore.state.width / 2}px ${10000 - projectStore.state.height / 2}px)`} style:pointer-events="none" > {/if} @@ -197,17 +200,17 @@ bind:this={projectViewport} class="project-viewport" class:project-viewport-recording={isRecording} - style:width="{projectStore.project.width}px" - style:height="{projectStore.project.height}px" - style:left={isRecording ? 'auto' : `-${projectStore.project.width / 2}px`} - style:top={isRecording ? 'auto' : `-${projectStore.project.height / 2}px`} + style:width="{projectStore.state.width}px" + style:height="{projectStore.state.height}px" + style:left={isRecording ? 'auto' : `-${projectStore.state.width / 2}px`} + style:top={isRecording ? 'auto' : `-${projectStore.state.height / 2}px`} style:transform={isRecording ? `scale(${recordingScale})` : undefined} style:perspective="1000px" style:perspective-origin="center center" style:transform-style="preserve-3d" style:isolation="isolate" - style:background-color={getBackgroundColor(projectStore.project.background)} - style:background-image={getBackgroundImage(projectStore.project.background)} + style:background-color={getBackgroundColor(projectStore.state.background)} + style:background-image={getBackgroundImage(projectStore.state.background)} style:cursor={projectStore.isRecording ? 'none' : undefined} > @@ -217,9 +220,9 @@ style:pointer-events={projectStore.isRecording ? 'none' : undefined} > import { Button } from '$lib/components/ui/button'; import { Play, Pause, SkipBack } from '@lucide/svelte'; - import { projectStore } from '$lib/stores/project.svelte'; + import { getEditorState } from '$lib/contexts/editor.svelte'; + + const editorState = $derived(getEditorState()); + const projectStore = $derived(editorState.project); function togglePlayback() { if (projectStore.isPlaying) { diff --git a/src/lib/components/editor/editor-layout.svelte b/src/lib/components/editor/editor-layout.svelte index da2d84d..0131f3c 100644 --- a/src/lib/components/editor/editor-layout.svelte +++ b/src/lib/components/editor/editor-layout.svelte @@ -7,7 +7,7 @@ import Panel from './panels/panel.svelte'; import KeyboardHandler from './keyboard-handler.svelte'; import { ResizableHandle, ResizablePane, ResizablePaneGroup } from '$lib/components/ui/resizable'; - import { projectStore } from '$lib/stores/project.svelte'; + import { getEditorState } from '$lib/contexts/editor.svelte'; import { Layers, Settings, Clock, Sparkles } from '@lucide/svelte'; import AiChat from '$lib/components/ai/ai-chat.svelte'; import ModelSelector from '$lib/components/ai/model-selector.svelte'; @@ -15,6 +15,9 @@ import AddLayer from './panels/add-layer.svelte'; import { IsMobile } from '$lib/hooks/is-mobile.svelte'; + const editorState = $derived(getEditorState()); + const projectStore = $derived(editorState.project); + interface Props { projectId?: string | null; isOwner?: boolean; @@ -102,7 +105,7 @@ - {projectStore.currentTime.toFixed(2)}s / {projectStore.project.duration}s + {projectStore.currentTime.toFixed(2)}s / {projectStore.state.duration}s {/snippet} @@ -164,7 +167,7 @@ {#snippet content()} @@ -208,7 +211,7 @@ {/snippet} {#snippet actionsSnippet()} - {projectStore.currentTime.toFixed(2)}s / {projectStore.project.duration}s + {projectStore.currentTime.toFixed(2)}s / {projectStore.state.duration}s {/snippet} diff --git a/src/lib/components/editor/export-dialog.svelte b/src/lib/components/editor/export-dialog.svelte index bfb7a05..df7f466 100644 --- a/src/lib/components/editor/export-dialog.svelte +++ b/src/lib/components/editor/export-dialog.svelte @@ -11,10 +11,13 @@ import { Label } from '$lib/components/ui/label'; import { Input } from '$lib/components/ui/input'; import { Progress } from '$lib/components/ui/progress'; - import { projectStore } from '$lib/stores/project.svelte'; + import { getEditorState } from '$lib/contexts/editor.svelte'; import { VideoCapture } from '$lib/utils/video-capture'; import { Loader2, AlertCircle, Monitor, Server } from '@lucide/svelte'; + const editorState = $derived(getEditorState()); + const projectStore = $derived(editorState.project); + interface Props { open: boolean; onOpenChange: (open: boolean) => void; @@ -43,9 +46,9 @@ let exportSettings = $derived({ format: 'webm', - fps: projectStore.project.fps, - width: projectStore.project.width, - height: projectStore.project.height + fps: projectStore.state.fps, + width: projectStore.state.width, + height: projectStore.state.height }); let exportMode = $derived(projectId ? 'server' : 'browser'); @@ -123,7 +126,7 @@ // In a streaming response, we have to read it as a blob if we want to trigger a download window const blob = await response.blob(); - const filename = `${projectStore.project.name || 'video'}.mp4`; + const filename = `${projectStore.state.name || 'video'}.mp4`; const url = URL.createObjectURL(blob); const a = document.createElement('a'); @@ -209,7 +212,7 @@ width: exportSettings.width, height: exportSettings.height, fps: exportSettings.fps, - duration: projectStore.project.duration, + duration: projectStore.state.duration, onReadyToRecord: () => { // Start playback synchronized with recording projectStore.play(); @@ -233,7 +236,7 @@ try { // Download the video - const filename = `${projectStore.project.name || 'video'}.mp4`; + const filename = `${projectStore.state.name || 'video'}.mp4`; VideoCapture.downloadBlob(blob, filename); // Close dialog after successful export diff --git a/src/lib/components/editor/keyboard-handler.svelte b/src/lib/components/editor/keyboard-handler.svelte index 76c3375..36a1a63 100644 --- a/src/lib/components/editor/keyboard-handler.svelte +++ b/src/lib/components/editor/keyboard-handler.svelte @@ -1,8 +1,11 @@ - + Login required @@ -42,11 +35,9 @@
- +
free AI credits included
diff --git a/src/lib/components/editor/panels/add-layer.svelte b/src/lib/components/editor/panels/add-layer.svelte index 3a0bf8f..f97eac5 100644 --- a/src/lib/components/editor/panels/add-layer.svelte +++ b/src/lib/components/editor/panels/add-layer.svelte @@ -1,5 +1,5 @@ diff --git a/src/routes/(app)/p/[id]/+page.svelte b/src/routes/(app)/p/[id]/+page.svelte index fbd4749..5c38407 100644 --- a/src/routes/(app)/p/[id]/+page.svelte +++ b/src/routes/(app)/p/[id]/+page.svelte @@ -2,25 +2,27 @@ import SeoHead from '$lib/components/seo-head.svelte'; import JsonLd from '$lib/components/json-ld.svelte'; import EditorLayout from '$lib/components/editor/editor-layout.svelte'; - import { projectStore } from '$lib/stores/project.svelte'; import { PUBLIC_BASE_URL } from '$env/static/public'; import type { PageData } from './$types'; + import { getEditorState } from '$lib/contexts/editor.svelte'; let { data }: { data: PageData } = $props(); + const editorState = $derived(getEditorState()); + const baseUrl = PUBLIC_BASE_URL; - const projectName = $derived(data.project.data.name || 'Untitled Project'); + const projectName = $derived(editorState.project.state.name || 'Untitled Project'); const projectDescription = $derived( `${projectName} - Animated video created with DevMotion. Design with manual controls or use AI-powered suggestions.` ); const projectUrl = $derived(`${baseUrl}/p/${data.project.id}`); const ogImage = $derived(`${baseUrl}/p/${data.project.id}/og.png`); - // Use $effect to react to project changes when switching between projects + // Load project data when route changes $effect(() => { const projectData = data.project.data; - projectStore.loadProject({ + editorState.project.loadProject({ id: data.project.id, name: projectData.name, width: projectData.width, @@ -31,7 +33,9 @@ layers: projectData.layers }); - projectStore.setDbContext(data.project.id, data.isOwner, data.canEdit, data.project.isPublic); + editorState.setDbContext(data.project.id, data.isOwner, data.canEdit, data.project.isPublic); + + editorState.isMcp = data.project.isMcp; });