summaryrefslogtreecommitdiff
path: root/apps/web/src/core
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/core')
-rw-r--r--apps/web/src/core/index.ts6
-rw-r--r--apps/web/src/core/managers/audio-manager.ts14
-rw-r--r--apps/web/src/core/managers/clipboard-manager.ts2
-rw-r--r--apps/web/src/core/managers/commands.ts8
-rw-r--r--apps/web/src/core/managers/diagnostics-manager.ts2
-rw-r--r--apps/web/src/core/managers/media-manager.ts4
-rw-r--r--apps/web/src/core/managers/playback-manager.ts2
-rw-r--r--apps/web/src/core/managers/project-manager.ts24
-rw-r--r--apps/web/src/core/managers/renderer-manager.ts6
-rw-r--r--apps/web/src/core/managers/scenes-manager.ts640
-rw-r--r--apps/web/src/core/managers/selection-manager.ts6
-rw-r--r--apps/web/src/core/managers/timeline-manager.ts24
12 files changed, 369 insertions, 369 deletions
diff --git a/apps/web/src/core/index.ts b/apps/web/src/core/index.ts
index 483a26c1..3e55e1d6 100644
--- a/apps/web/src/core/index.ts
+++ b/apps/web/src/core/index.ts
@@ -10,9 +10,9 @@ import { AudioManager } from "./managers/audio-manager";
import { SelectionManager } from "./managers/selection-manager";
import { ClipboardManager } from "./managers/clipboard-manager";
import { DiagnosticsManager } from "./managers/diagnostics-manager";
-import { registerDefaultEffects } from "@/lib/effects";
-import { registerDefaultMasks } from "@/lib/masks";
-import { registerTranscriptionDiagnostics } from "@/lib/transcription/diagnostics";
+import { registerDefaultEffects } from "@/effects";
+import { registerDefaultMasks } from "@/masks";
+import { registerTranscriptionDiagnostics } from "@/transcription/diagnostics";
export class EditorCore {
private static instance: EditorCore | null = null;
diff --git a/apps/web/src/core/managers/audio-manager.ts b/apps/web/src/core/managers/audio-manager.ts
index 076b998d..545565be 100644
--- a/apps/web/src/core/managers/audio-manager.ts
+++ b/apps/web/src/core/managers/audio-manager.ts
@@ -1,21 +1,21 @@
import type { EditorCore } from "@/core";
-import { TICKS_PER_SECOND } from "@/lib/wasm";
+import { TICKS_PER_SECOND } from "@/wasm";
import {
clampRetimeRate,
shouldMaintainPitch,
-} from "@/lib/retime/rate";
-import type { AudioClipSource } from "@/lib/media/audio";
-import { createAudioContext, collectAudioClips } from "@/lib/media/audio";
+} from "@/retime/rate";
+import type { AudioClipSource } from "@/media/audio";
+import { createAudioContext, collectAudioClips } from "@/media/audio";
import {
buildAudioGainAutomation,
hasAnimatedVolume,
-} from "@/lib/timeline/audio-state";
-import { createAudioMasteringChain } from "@/lib/media/audio-mastering";
+} from "@/timeline/audio-state";
+import { createAudioMasteringChain } from "@/media/audio-mastering";
import {
getClipTimeAtSourceTime,
getSourceTimeAtClipTime,
renderRetimedBuffer,
-} from "@/lib/retime";
+} from "@/retime";
import {
ALL_FORMATS,
AudioBufferSink,
diff --git a/apps/web/src/core/managers/clipboard-manager.ts b/apps/web/src/core/managers/clipboard-manager.ts
index b19343df..8a2a51c9 100644
--- a/apps/web/src/core/managers/clipboard-manager.ts
+++ b/apps/web/src/core/managers/clipboard-manager.ts
@@ -5,7 +5,7 @@ import {
type ClipboardEntry,
type CopyContext,
type PasteContext,
-} from "@/lib/clipboard";
+} from "@/clipboard";
export class ClipboardManager {
private entry: ClipboardEntry | null = null;
diff --git a/apps/web/src/core/managers/commands.ts b/apps/web/src/core/managers/commands.ts
index 7cdd6373..9082db10 100644
--- a/apps/web/src/core/managers/commands.ts
+++ b/apps/web/src/core/managers/commands.ts
@@ -1,8 +1,8 @@
import type { EditorCore } from "@/core";
-import type { Command, CommandResult } from "@/lib/commands";
-import type { EditorSelectionSnapshot } from "@/lib/selection/editor-selection";
-import { applyRippleAdjustments, computeRippleAdjustments } from "@/lib/ripple";
-import type { SceneTracks } from "@/lib/timeline/types";
+import type { Command, CommandResult } from "@/commands";
+import type { EditorSelectionSnapshot } from "@/selection/editor-selection";
+import { applyRippleAdjustments, computeRippleAdjustments } from "@/ripple";
+import type { SceneTracks } from "@/timeline/types";
interface CommandHistoryEntry {
command: Command;
diff --git a/apps/web/src/core/managers/diagnostics-manager.ts b/apps/web/src/core/managers/diagnostics-manager.ts
index d022a350..0a32fab5 100644
--- a/apps/web/src/core/managers/diagnostics-manager.ts
+++ b/apps/web/src/core/managers/diagnostics-manager.ts
@@ -1,5 +1,5 @@
import type { EditorCore } from "@/core";
-import type { DiagnosticDefinition } from "@/lib/diagnostics/types";
+import type { DiagnosticDefinition } from "@/diagnostics/types";
interface DiagnosticRegistration extends DiagnosticDefinition {
check: (editor: EditorCore) => boolean;
diff --git a/apps/web/src/core/managers/media-manager.ts b/apps/web/src/core/managers/media-manager.ts
index e43340a7..553d1557 100644
--- a/apps/web/src/core/managers/media-manager.ts
+++ b/apps/web/src/core/managers/media-manager.ts
@@ -1,11 +1,11 @@
import type { EditorCore } from "@/core";
import { toast } from "sonner";
-import type { MediaAsset } from "@/lib/media/types";
+import type { MediaAsset } from "@/media/types";
import { storageService } from "@/services/storage/service";
import { generateUUID } from "@/utils/id";
import { videoCache } from "@/services/video-cache/service";
import { waveformCache } from "@/services/waveform-cache/service";
-import { BatchCommand, RemoveMediaAssetCommand } from "@/lib/commands";
+import { BatchCommand, RemoveMediaAssetCommand } from "@/commands";
export class MediaManager {
private assets: MediaAsset[] = [];
diff --git a/apps/web/src/core/managers/playback-manager.ts b/apps/web/src/core/managers/playback-manager.ts
index 341a0eae..1ea09826 100644
--- a/apps/web/src/core/managers/playback-manager.ts
+++ b/apps/web/src/core/managers/playback-manager.ts
@@ -1,5 +1,5 @@
import type { EditorCore } from "@/core";
-import { TICKS_PER_SECOND } from "@/lib/wasm";
+import { TICKS_PER_SECOND } from "@/wasm";
import { roundToFrame } from "opencut-wasm";
export class PlaybackManager {
diff --git a/apps/web/src/core/managers/project-manager.ts b/apps/web/src/core/managers/project-manager.ts
index 8555786d..b38fd50d 100644
--- a/apps/web/src/core/managers/project-manager.ts
+++ b/apps/web/src/core/managers/project-manager.ts
@@ -6,16 +6,16 @@ import type {
TProjectSortOption,
TProjectSettings,
TTimelineViewState,
-} from "@/lib/project/types";
-import type { ExportOptions, ExportResult, ExportState } from "@/lib/export";
+} from "@/project/types";
+import type { ExportOptions, ExportResult, ExportState } from "@/export";
import { storageService } from "@/services/storage/service";
import { toast } from "sonner";
import { generateUUID } from "@/utils/id";
-import { UpdateProjectSettingsCommand } from "@/lib/commands/project";
-import { DEFAULT_BACKGROUND_COLOR } from "@/lib/background/color";
-import { DEFAULT_CANVAS_SIZE } from "@/lib/canvas/sizes";
-import { DEFAULT_FPS } from "@/lib/fps/defaults";
-import { buildDefaultScene, getProjectDurationFromScenes } from "@/lib/scenes";
+import { UpdateProjectSettingsCommand } from "@/commands/project";
+import { DEFAULT_BACKGROUND_COLOR } from "@/background/color";
+import { DEFAULT_CANVAS_SIZE } from "@/canvas/sizes";
+import { DEFAULT_FPS } from "@/fps/defaults";
+import { buildDefaultScene, getProjectDurationFromScenes } from "@/timeline/scenes";
import { buildScene } from "@/services/renderer/scene-builder";
import { CanvasRenderer } from "@/services/renderer/canvas-renderer";
import {
@@ -24,11 +24,11 @@ import {
runStorageMigrations,
type MigrationProgress,
} from "@/services/storage/migrations";
-import { loadFonts } from "@/lib/fonts/google-fonts";
-import { DEFAULTS } from "@/lib/timeline/defaults";
-import { getElementFontFamilies } from "@/lib/timeline/element-utils";
-import { getRaisedProjectFpsForImportedMedia } from "@/lib/fps/utils";
-import type { MediaAsset } from "@/lib/media/types";
+import { loadFonts } from "@/fonts/google-fonts";
+import { DEFAULTS } from "@/timeline/defaults";
+import { getElementFontFamilies } from "@/timeline/element-utils";
+import { getRaisedProjectFpsForImportedMedia } from "@/fps/utils";
+import type { MediaAsset } from "@/media/types";
export interface MigrationState {
isMigrating: boolean;
diff --git a/apps/web/src/core/managers/renderer-manager.ts b/apps/web/src/core/managers/renderer-manager.ts
index 495292e6..7984655c 100644
--- a/apps/web/src/core/managers/renderer-manager.ts
+++ b/apps/web/src/core/managers/renderer-manager.ts
@@ -1,12 +1,12 @@
import type { EditorCore } from "@/core";
import type { RootNode } from "@/services/renderer/nodes/root-node";
-import type { ExportOptions, ExportResult } from "@/lib/export";
+import type { ExportOptions, ExportResult } from "@/export";
import { CanvasRenderer } from "@/services/renderer/canvas-renderer";
import { SceneExporter } from "@/services/renderer/scene-exporter";
import { buildScene } from "@/services/renderer/scene-builder";
-import { createTimelineAudioBuffer } from "@/lib/media/audio";
+import { createTimelineAudioBuffer } from "@/media/audio";
import { formatTimecode } from "opencut-wasm";
-import { frameRateToFloat } from "@/lib/fps/utils";
+import { frameRateToFloat } from "@/fps/utils";
import { downloadBlob } from "@/utils/browser";
type SnapshotResult =
diff --git a/apps/web/src/core/managers/scenes-manager.ts b/apps/web/src/core/managers/scenes-manager.ts
index 3508a066..13f251a1 100644
--- a/apps/web/src/core/managers/scenes-manager.ts
+++ b/apps/web/src/core/managers/scenes-manager.ts
@@ -1,320 +1,320 @@
-import type { EditorCore } from "@/core";
-import type { SceneTracks, TScene } from "@/lib/timeline";
-import { storageService } from "@/services/storage/service";
-import {
- getMainScene,
- ensureMainScene,
- canDeleteScene,
- findCurrentScene,
-} from "@/lib/scenes";
-import {
- getBookmarkAtTime,
- getFrameTime,
- isBookmarkAtTime,
-} from "@/lib/timeline/bookmarks";
-import {
- CreateSceneCommand,
- DeleteSceneCommand,
- MoveBookmarkCommand,
- RemoveBookmarkCommand,
- RenameSceneCommand,
- ToggleBookmarkCommand,
- UpdateBookmarkCommand,
-} from "@/lib/commands/scene";
-
-export class ScenesManager {
- private active: TScene | null = null;
- private list: TScene[] = [];
- private listeners = new Set<() => void>();
-
- constructor(private editor: EditorCore) {}
-
- async createScene({
- name,
- isMain = false,
- }: {
- name: string;
- isMain: boolean;
- }): Promise<string> {
- if (!this.editor.project.getActive()) {
- throw new Error("No active project");
- }
-
- const command = new CreateSceneCommand(name, isMain);
- this.editor.command.execute({ command });
- return command.getSceneId();
- }
-
- async deleteScene({ sceneId }: { sceneId: string }): Promise<void> {
- const sceneToDelete = this.list.find((s) => s.id === sceneId);
-
- if (!sceneToDelete) {
- throw new Error("Scene not found");
- }
-
- const { canDelete, reason } = canDeleteScene({ scene: sceneToDelete });
- if (!canDelete) {
- throw new Error(reason);
- }
-
- if (!this.editor.project.getActive()) {
- throw new Error("No active project");
- }
-
- const command = new DeleteSceneCommand(sceneId);
- this.editor.command.execute({ command });
- }
-
- async renameScene({
- sceneId,
- name,
- }: {
- sceneId: string;
- name: string;
- }): Promise<void> {
- if (!this.editor.project.getActive()) {
- throw new Error("No active project");
- }
-
- const command = new RenameSceneCommand(sceneId, name);
- this.editor.command.execute({ command });
- }
-
- async switchToScene({ sceneId }: { sceneId: string }): Promise<void> {
- const targetScene = this.list.find((s) => s.id === sceneId);
-
- if (!targetScene) {
- throw new Error("Scene not found");
- }
-
- const activeProject = this.editor.project.getActive();
-
- if (activeProject) {
- const updatedProject = {
- ...activeProject,
- currentSceneId: sceneId,
- metadata: {
- ...activeProject.metadata,
- updatedAt: new Date(),
- },
- };
-
- this.editor.project.setActiveProject({ project: updatedProject });
- }
-
- this.active = targetScene;
- this.notify();
- }
-
- async toggleBookmark({ time }: { time: number }): Promise<void> {
- const command = new ToggleBookmarkCommand(time);
- this.editor.command.execute({ command });
- }
-
- isBookmarked({ time }: { time: number }): boolean {
- const activeScene = this.getActiveScene();
- const activeProject = this.editor.project.getActive();
-
- if (!activeScene || !this.active || !activeProject) return false;
-
- const frameTime = getFrameTime({
- time,
- fps: activeProject.settings.fps,
- });
-
- return isBookmarkAtTime({ bookmarks: activeScene.bookmarks, frameTime });
- }
-
- async removeBookmark({ time }: { time: number }): Promise<void> {
- const command = new RemoveBookmarkCommand(time);
- this.editor.command.execute({ command });
- }
-
- async updateBookmark({
- time,
- updates,
- }: {
- time: number;
- updates: Partial<{ note: string; color: string; duration: number }>;
- }): Promise<void> {
- const command = new UpdateBookmarkCommand(time, updates);
- this.editor.command.execute({ command });
- }
-
- async moveBookmark({
- fromTime,
- toTime,
- }: {
- fromTime: number;
- toTime: number;
- }): Promise<void> {
- const command = new MoveBookmarkCommand(fromTime, toTime);
- this.editor.command.execute({ command });
- }
-
- getBookmarkAtTime({ time }: { time: number }) {
- const activeScene = this.active;
- const activeProject = this.editor.project.getActive();
-
- if (!activeScene || !activeProject) return null;
-
- const frameTime = getFrameTime({
- time,
- fps: activeProject.settings.fps,
- });
-
- return getBookmarkAtTime({
- bookmarks: activeScene.bookmarks,
- frameTime,
- });
- }
-
- async loadProjectScenes({ projectId }: { projectId: string }): Promise<void> {
- try {
- const result = await storageService.loadProject({ id: projectId });
- if (result?.project.scenes) {
- const ensuredScenes = result.project.scenes ?? [];
- const currentScene = findCurrentScene({
- scenes: ensuredScenes,
- currentSceneId: result.project.currentSceneId,
- });
-
- this.list = ensuredScenes;
- this.active = currentScene;
- this.notify();
- }
- } catch (error) {
- console.error("Failed to load project scenes:", error);
- this.list = [];
- this.active = null;
- this.notify();
- }
- }
-
- initializeScenes({
- scenes,
- currentSceneId,
- }: {
- scenes: TScene[];
- currentSceneId?: string;
- }): void {
- const ensuredScenes = ensureMainScene({ scenes });
- const currentScene = currentSceneId
- ? ensuredScenes.find((s) => s.id === currentSceneId)
- : null;
-
- const fallbackScene = getMainScene({ scenes: ensuredScenes });
-
- this.list = ensuredScenes;
- this.active = currentScene || fallbackScene;
- this.notify();
-
- const hasAddedMainScene = ensuredScenes.length > scenes.length;
- if (hasAddedMainScene) {
- const activeProject = this.editor.project.getActive();
-
- if (activeProject) {
- const updatedProject = {
- ...activeProject,
- scenes: ensuredScenes,
- metadata: {
- ...activeProject.metadata,
- updatedAt: new Date(),
- },
- };
-
- this.editor.project.setActiveProject({ project: updatedProject });
- this.editor.save.markDirty({ force: true });
- }
- }
- }
-
- clearScenes(): void {
- this.list = [];
- this.active = null;
- this.notify();
- }
-
- getActiveScene(): TScene {
- if (!this.active) {
- throw new Error("No active scene.");
- }
- return this.active;
- }
-
- getActiveSceneOrNull(): TScene | null {
- return this.active;
- }
-
- getScenes(): TScene[] {
- return this.list;
- }
-
- setScenes({
- scenes,
- activeSceneId,
- }: {
- scenes: TScene[];
- activeSceneId?: string;
- }): void {
- this.list = scenes;
- const nextActiveSceneId = activeSceneId ?? this.active?.id ?? null;
- this.active = nextActiveSceneId
- ? (scenes.find((scene) => scene.id === nextActiveSceneId) ?? null)
- : null;
- this.notify();
-
- const activeProject = this.editor.project.getActive();
- if (activeProject) {
- const updatedProject = {
- ...activeProject,
- scenes,
- metadata: {
- ...activeProject.metadata,
- updatedAt: new Date(),
- },
- };
- this.editor.project.setActiveProject({ project: updatedProject });
- }
- }
-
- subscribe(listener: () => void): () => void {
- this.listeners.add(listener);
- return () => this.listeners.delete(listener);
- }
-
- private notify(): void {
- this.listeners.forEach((fn) => {
- fn();
- });
- }
-
- updateSceneTracks({ tracks }: { tracks: SceneTracks }): void {
- if (!this.active) return;
-
- const updatedScene: TScene = {
- ...this.active,
- tracks,
- updatedAt: new Date(),
- };
-
- this.list = this.list.map((s) =>
- s.id === this.active?.id ? updatedScene : s,
- );
- this.active = updatedScene;
- this.notify();
-
- const activeProject = this.editor.project.getActive();
- if (activeProject) {
- const updatedProject = {
- ...activeProject,
- scenes: this.list,
- metadata: {
- ...activeProject.metadata,
- updatedAt: new Date(),
- },
- };
- this.editor.project.setActiveProject({ project: updatedProject });
- }
- }
-}
+import type { EditorCore } from "@/core";
+import type { SceneTracks, TScene } from "@/timeline";
+import { storageService } from "@/services/storage/service";
+import {
+ getMainScene,
+ ensureMainScene,
+ canDeleteScene,
+ findCurrentScene,
+} from "@/timeline/scenes";
+import {
+ getBookmarkAtTime,
+ getFrameTime,
+ isBookmarkAtTime,
+} from "@/timeline/bookmarks/index";
+import {
+ CreateSceneCommand,
+ DeleteSceneCommand,
+ MoveBookmarkCommand,
+ RemoveBookmarkCommand,
+ RenameSceneCommand,
+ ToggleBookmarkCommand,
+ UpdateBookmarkCommand,
+} from "@/commands/scene";
+
+export class ScenesManager {
+ private active: TScene | null = null;
+ private list: TScene[] = [];
+ private listeners = new Set<() => void>();
+
+ constructor(private editor: EditorCore) {}
+
+ async createScene({
+ name,
+ isMain = false,
+ }: {
+ name: string;
+ isMain: boolean;
+ }): Promise<string> {
+ if (!this.editor.project.getActive()) {
+ throw new Error("No active project");
+ }
+
+ const command = new CreateSceneCommand(name, isMain);
+ this.editor.command.execute({ command });
+ return command.getSceneId();
+ }
+
+ async deleteScene({ sceneId }: { sceneId: string }): Promise<void> {
+ const sceneToDelete = this.list.find((s) => s.id === sceneId);
+
+ if (!sceneToDelete) {
+ throw new Error("Scene not found");
+ }
+
+ const { canDelete, reason } = canDeleteScene({ scene: sceneToDelete });
+ if (!canDelete) {
+ throw new Error(reason);
+ }
+
+ if (!this.editor.project.getActive()) {
+ throw new Error("No active project");
+ }
+
+ const command = new DeleteSceneCommand(sceneId);
+ this.editor.command.execute({ command });
+ }
+
+ async renameScene({
+ sceneId,
+ name,
+ }: {
+ sceneId: string;
+ name: string;
+ }): Promise<void> {
+ if (!this.editor.project.getActive()) {
+ throw new Error("No active project");
+ }
+
+ const command = new RenameSceneCommand(sceneId, name);
+ this.editor.command.execute({ command });
+ }
+
+ async switchToScene({ sceneId }: { sceneId: string }): Promise<void> {
+ const targetScene = this.list.find((s) => s.id === sceneId);
+
+ if (!targetScene) {
+ throw new Error("Scene not found");
+ }
+
+ const activeProject = this.editor.project.getActive();
+
+ if (activeProject) {
+ const updatedProject = {
+ ...activeProject,
+ currentSceneId: sceneId,
+ metadata: {
+ ...activeProject.metadata,
+ updatedAt: new Date(),
+ },
+ };
+
+ this.editor.project.setActiveProject({ project: updatedProject });
+ }
+
+ this.active = targetScene;
+ this.notify();
+ }
+
+ async toggleBookmark({ time }: { time: number }): Promise<void> {
+ const command = new ToggleBookmarkCommand(time);
+ this.editor.command.execute({ command });
+ }
+
+ isBookmarked({ time }: { time: number }): boolean {
+ const activeScene = this.getActiveScene();
+ const activeProject = this.editor.project.getActive();
+
+ if (!activeScene || !this.active || !activeProject) return false;
+
+ const frameTime = getFrameTime({
+ time,
+ fps: activeProject.settings.fps,
+ });
+
+ return isBookmarkAtTime({ bookmarks: activeScene.bookmarks, frameTime });
+ }
+
+ async removeBookmark({ time }: { time: number }): Promise<void> {
+ const command = new RemoveBookmarkCommand(time);
+ this.editor.command.execute({ command });
+ }
+
+ async updateBookmark({
+ time,
+ updates,
+ }: {
+ time: number;
+ updates: Partial<{ note: string; color: string; duration: number }>;
+ }): Promise<void> {
+ const command = new UpdateBookmarkCommand(time, updates);
+ this.editor.command.execute({ command });
+ }
+
+ async moveBookmark({
+ fromTime,
+ toTime,
+ }: {
+ fromTime: number;
+ toTime: number;
+ }): Promise<void> {
+ const command = new MoveBookmarkCommand(fromTime, toTime);
+ this.editor.command.execute({ command });
+ }
+
+ getBookmarkAtTime({ time }: { time: number }) {
+ const activeScene = this.active;
+ const activeProject = this.editor.project.getActive();
+
+ if (!activeScene || !activeProject) return null;
+
+ const frameTime = getFrameTime({
+ time,
+ fps: activeProject.settings.fps,
+ });
+
+ return getBookmarkAtTime({
+ bookmarks: activeScene.bookmarks,
+ frameTime,
+ });
+ }
+
+ async loadProjectScenes({ projectId }: { projectId: string }): Promise<void> {
+ try {
+ const result = await storageService.loadProject({ id: projectId });
+ if (result?.project.scenes) {
+ const ensuredScenes = result.project.scenes ?? [];
+ const currentScene = findCurrentScene({
+ scenes: ensuredScenes,
+ currentSceneId: result.project.currentSceneId,
+ });
+
+ this.list = ensuredScenes;
+ this.active = currentScene;
+ this.notify();
+ }
+ } catch (error) {
+ console.error("Failed to load project scenes:", error);
+ this.list = [];
+ this.active = null;
+ this.notify();
+ }
+ }
+
+ initializeScenes({
+ scenes,
+ currentSceneId,
+ }: {
+ scenes: TScene[];
+ currentSceneId?: string;
+ }): void {
+ const ensuredScenes = ensureMainScene({ scenes });
+ const currentScene = currentSceneId
+ ? ensuredScenes.find((s) => s.id === currentSceneId)
+ : null;
+
+ const fallbackScene = getMainScene({ scenes: ensuredScenes });
+
+ this.list = ensuredScenes;
+ this.active = currentScene || fallbackScene;
+ this.notify();
+
+ const hasAddedMainScene = ensuredScenes.length > scenes.length;
+ if (hasAddedMainScene) {
+ const activeProject = this.editor.project.getActive();
+
+ if (activeProject) {
+ const updatedProject = {
+ ...activeProject,
+ scenes: ensuredScenes,
+ metadata: {
+ ...activeProject.metadata,
+ updatedAt: new Date(),
+ },
+ };
+
+ this.editor.project.setActiveProject({ project: updatedProject });
+ this.editor.save.markDirty({ force: true });
+ }
+ }
+ }
+
+ clearScenes(): void {
+ this.list = [];
+ this.active = null;
+ this.notify();
+ }
+
+ getActiveScene(): TScene {
+ if (!this.active) {
+ throw new Error("No active scene.");
+ }
+ return this.active;
+ }
+
+ getActiveSceneOrNull(): TScene | null {
+ return this.active;
+ }
+
+ getScenes(): TScene[] {
+ return this.list;
+ }
+
+ setScenes({
+ scenes,
+ activeSceneId,
+ }: {
+ scenes: TScene[];
+ activeSceneId?: string;
+ }): void {
+ this.list = scenes;
+ const nextActiveSceneId = activeSceneId ?? this.active?.id ?? null;
+ this.active = nextActiveSceneId
+ ? (scenes.find((scene) => scene.id === nextActiveSceneId) ?? null)
+ : null;
+ this.notify();
+
+ const activeProject = this.editor.project.getActive();
+ if (activeProject) {
+ const updatedProject = {
+ ...activeProject,
+ scenes,
+ metadata: {
+ ...activeProject.metadata,
+ updatedAt: new Date(),
+ },
+ };
+ this.editor.project.setActiveProject({ project: updatedProject });
+ }
+ }
+
+ subscribe(listener: () => void): () => void {
+ this.listeners.add(listener);
+ return () => this.listeners.delete(listener);
+ }
+
+ private notify(): void {
+ this.listeners.forEach((fn) => {
+ fn();
+ });
+ }
+
+ updateSceneTracks({ tracks }: { tracks: SceneTracks }): void {
+ if (!this.active) return;
+
+ const updatedScene: TScene = {
+ ...this.active,
+ tracks,
+ updatedAt: new Date(),
+ };
+
+ this.list = this.list.map((s) =>
+ s.id === this.active?.id ? updatedScene : s,
+ );
+ this.active = updatedScene;
+ this.notify();
+
+ const activeProject = this.editor.project.getActive();
+ if (activeProject) {
+ const updatedProject = {
+ ...activeProject,
+ scenes: this.list,
+ metadata: {
+ ...activeProject.metadata,
+ updatedAt: new Date(),
+ },
+ };
+ this.editor.project.setActiveProject({ project: updatedProject });
+ }
+ }
+}
diff --git a/apps/web/src/core/managers/selection-manager.ts b/apps/web/src/core/managers/selection-manager.ts
index 5da4fa60..cfc46ccc 100644
--- a/apps/web/src/core/managers/selection-manager.ts
+++ b/apps/web/src/core/managers/selection-manager.ts
@@ -1,12 +1,12 @@
import type { EditorCore } from "@/core";
-import type { SelectedKeyframeRef } from "@/lib/animation/types";
+import type { SelectedKeyframeRef } from "@/animation/types";
import type {
EditorSelectionKind,
EditorSelectionPatch,
EditorSelectionSnapshot,
SelectedMaskPointSelection,
-} from "@/lib/selection/editor-selection";
-import type { ElementRef } from "@/lib/timeline/types";
+} from "@/selection/editor-selection";
+import type { ElementRef } from "@/timeline/types";
export class SelectionManager {
private selectedElements: ElementRef[] = [];
diff --git a/apps/web/src/core/managers/timeline-manager.ts b/apps/web/src/core/managers/timeline-manager.ts
index c58128fb..dbc3332b 100644
--- a/apps/web/src/core/managers/timeline-manager.ts
+++ b/apps/web/src/core/managers/timeline-manager.ts
@@ -1,33 +1,33 @@
import type { EditorCore } from "@/core";
-import type { ElementBounds } from "@/lib/preview/element-bounds";
-import type { ParamValues } from "@/lib/params";
+import type { ElementBounds } from "@/preview/element-bounds";
+import type { ParamValues } from "@/params";
import type {
SceneTracks,
TrackType,
TimelineTrack,
TimelineElement,
RetimeConfig,
-} from "@/lib/timeline";
-import { calculateTotalDuration } from "@/lib/timeline";
-import { findTrackInSceneTracks } from "@/lib/timeline/track-element-update";
+} from "@/timeline";
+import { calculateTotalDuration } from "@/timeline";
+import { findTrackInSceneTracks } from "@/timeline/track-element-update";
import {
canElementBeHidden,
canElementHaveAudio,
-} from "@/lib/timeline/element-utils";
+} from "@/timeline/element-utils";
import type {
AnimationPath,
AnimationInterpolation,
AnimationValue,
AnimationValueForPath,
ScalarCurveKeyframePatch,
-} from "@/lib/animation/types";
+} from "@/animation/types";
import {
getElementLocalTime,
resolveAnimationTarget,
resolveAnimationPathValueAtTime,
-} from "@/lib/animation";
+} from "@/animation";
import { lastFrameTime } from "opencut-wasm";
-import { BatchCommand } from "@/lib/commands";
+import { BatchCommand } from "@/commands";
import {
AddTrackCommand,
RemoveTrackCommand,
@@ -56,12 +56,12 @@ import {
UpsertEffectParamKeyframeCommand,
RemoveEffectParamKeyframeCommand,
ToggleSourceAudioSeparationCommand,
-} from "@/lib/commands/timeline";
-import type { InsertElementParams } from "@/lib/commands/timeline/element/insert-element";
+} from "@/commands/timeline";
+import type { InsertElementParams } from "@/commands/timeline/element/insert-element";
import type {
PlannedElementMove,
PlannedTrackCreation,
-} from "@/lib/timeline/group-move";
+} from "@/timeline/group-move";
export class TimelineManager {
private listeners = new Set<() => void>();