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;
});