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/managers/audio-manager.ts12
-rw-r--r--apps/web/src/core/managers/commands.ts5
-rw-r--r--apps/web/src/core/managers/project-manager.ts5
-rw-r--r--apps/web/src/core/managers/scenes-manager.ts47
-rw-r--r--apps/web/src/core/managers/timeline-manager.ts53
5 files changed, 115 insertions, 7 deletions
diff --git a/apps/web/src/core/managers/audio-manager.ts b/apps/web/src/core/managers/audio-manager.ts
index 3a83be48..457e49d6 100644
--- a/apps/web/src/core/managers/audio-manager.ts
+++ b/apps/web/src/core/managers/audio-manager.ts
@@ -125,7 +125,8 @@ export class AudioManager {
private getPlaybackTime(): number {
if (!this.audioContext) return this.playbackStartTime;
- const elapsed = this.audioContext.currentTime - this.playbackStartContextTime;
+ const elapsed =
+ this.audioContext.currentTime - this.playbackStartContextTime;
return this.playbackStartTime + elapsed;
}
@@ -176,7 +177,11 @@ export class AudioManager {
if (clip.startTime > windowEnd) continue;
this.activeClipIds.add(clip.id);
- void this.runClipIterator({ clip, startTime: currentTime, sessionId: this.playbackSessionId });
+ void this.runClipIterator({
+ clip,
+ startTime: currentTime,
+ sessionId: this.playbackSessionId,
+ });
}
}
@@ -239,8 +244,7 @@ export class AudioManager {
node.connect(this.masterGain ?? audioContext.destination);
const startTimestamp =
- this.playbackStartContextTime +
- (timelineTime - this.playbackStartTime);
+ this.playbackStartContextTime + (timelineTime - this.playbackStartTime);
if (startTimestamp >= audioContext.currentTime) {
node.start(startTimestamp);
diff --git a/apps/web/src/core/managers/commands.ts b/apps/web/src/core/managers/commands.ts
index d996a30e..46ee643a 100644
--- a/apps/web/src/core/managers/commands.ts
+++ b/apps/web/src/core/managers/commands.ts
@@ -11,6 +11,11 @@ export class CommandManager {
return command;
}
+ push({ command }: { command: Command }): void {
+ this.history.push(command);
+ this.redoStack = [];
+ }
+
undo(): void {
if (this.history.length === 0) return;
const command = this.history.pop();
diff --git a/apps/web/src/core/managers/project-manager.ts b/apps/web/src/core/managers/project-manager.ts
index c7497ef0..2608b8c0 100644
--- a/apps/web/src/core/managers/project-manager.ts
+++ b/apps/web/src/core/managers/project-manager.ts
@@ -27,6 +27,8 @@ import {
type MigrationProgress,
} from "@/services/storage/migrations";
import { DEFAULT_TIMELINE_VIEW_STATE } from "@/constants/timeline-constants";
+import { loadFonts } from "@/lib/fonts/google-fonts";
+import { collectFontFamilies } from "@/lib/timeline/element-utils";
export interface MigrationState {
isMigrating: boolean;
@@ -146,6 +148,9 @@ export class ProjectManager {
await this.editor.media.loadProjectMedia({ projectId: id });
+ const allTracks = (project.scenes ?? []).flatMap((scene) => scene.tracks);
+ await loadFonts({ families: collectFontFamilies({ tracks: allTracks }) });
+
if (!project.metadata.thumbnail) {
const didUpdateThumbnail = await this.updateThumbnailFromTimeline();
if (didUpdateThumbnail) {
diff --git a/apps/web/src/core/managers/scenes-manager.ts b/apps/web/src/core/managers/scenes-manager.ts
index 8c761f9b..c844f448 100644
--- a/apps/web/src/core/managers/scenes-manager.ts
+++ b/apps/web/src/core/managers/scenes-manager.ts
@@ -7,14 +7,20 @@ import {
canDeleteScene,
findCurrentScene,
} from "@/lib/scenes";
-import { getFrameTime, isBookmarkAtTime } from "@/lib/timeline/bookmarks";
+import {
+ getBookmarkAtTime,
+ getFrameTime,
+ isBookmarkAtTime,
+} from "@/lib/timeline/bookmarks";
import { ensureMainTrack } from "@/lib/timeline/track-utils";
import {
CreateSceneCommand,
DeleteSceneCommand,
+ MoveBookmarkCommand,
RemoveBookmarkCommand,
RenameSceneCommand,
ToggleBookmarkCommand,
+ UpdateBookmarkCommand,
} from "@/lib/commands/scene";
export class ScenesManager {
@@ -125,6 +131,45 @@ export class ScenesManager {
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 });
diff --git a/apps/web/src/core/managers/timeline-manager.ts b/apps/web/src/core/managers/timeline-manager.ts
index ad9f7792..c8ac5a79 100644
--- a/apps/web/src/core/managers/timeline-manager.ts
+++ b/apps/web/src/core/managers/timeline-manager.ts
@@ -23,12 +23,14 @@ import {
PasteCommand,
UpdateElementStartTimeCommand,
MoveElementCommand,
+ TracksSnapshotCommand,
} from "@/lib/commands/timeline";
-import { BatchCommand } from "@/lib/commands";
+import { BatchCommand, PreviewTracker } from "@/lib/commands";
import type { InsertElementParams } from "@/lib/commands/timeline/element/insert-element";
export class TimelineManager {
private listeners = new Set<() => void>();
+ private previewTracker = new PreviewTracker<TimelineTrack[]>();
constructor(private editor: EditorCore) {}
@@ -214,7 +216,8 @@ export class TimelineManager {
({ trackId, elementId, updates: elementUpdates }) =>
new UpdateElementCommand(trackId, elementId, elementUpdates),
);
- const command = commands.length === 1 ? commands[0] : new BatchCommand(commands);
+ const command =
+ commands.length === 1 ? commands[0] : new BatchCommand(commands);
if (pushHistory) {
this.editor.command.execute({ command });
} else {
@@ -222,6 +225,52 @@ export class TimelineManager {
}
}
+ isPreviewActive(): boolean {
+ return this.previewTracker.isActive();
+ }
+
+ previewElements({
+ updates,
+ }: {
+ updates: Array<{
+ trackId: string;
+ elementId: string;
+ updates: Partial<Record<string, unknown>>;
+ }>;
+ }): void {
+ const tracks = this.getTracks();
+ this.previewTracker.begin({ state: tracks });
+
+ let updatedTracks = tracks;
+ for (const { trackId, elementId, updates: elementUpdates } of updates) {
+ updatedTracks = updatedTracks.map((track) => {
+ if (track.id !== trackId) return track;
+ const newElements = track.elements.map((element) =>
+ element.id === elementId
+ ? { ...element, ...elementUpdates }
+ : element,
+ );
+ return { ...track, elements: newElements } as TimelineTrack;
+ });
+ }
+ this.updateTracks(updatedTracks);
+ }
+
+ commitPreview(): void {
+ const snapshot = this.previewTracker.end();
+ if (snapshot === null) return;
+ const currentTracks = this.getTracks();
+ const command = new TracksSnapshotCommand(snapshot, currentTracks);
+ this.editor.command.push({ command });
+ }
+
+ discardPreview(): void {
+ const snapshot = this.previewTracker.end();
+ if (snapshot !== null) {
+ this.updateTracks(snapshot);
+ }
+ }
+
duplicateElements({
elements,
}: {