mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
push
This commit is contained in:
@@ -4,6 +4,7 @@ import { SceneManager } from "./managers/scene-manager";
|
||||
import { ProjectManager } from "./managers/project-manager";
|
||||
import { MediaManager } from "./managers/media-manager";
|
||||
import { RendererManager } from "./managers/renderer-manager";
|
||||
import { CommandManager } from "./managers/commands";
|
||||
import { buildScene } from "@/services/renderer/scene-builder";
|
||||
import { SceneExporter } from "@/services/renderer/scene-exporter";
|
||||
import type { ExportOptions } from "@/types/export";
|
||||
@@ -12,6 +13,7 @@ import { DEFAULT_FPS } from "@/constants/editor-constants";
|
||||
export class EditorCore {
|
||||
private static instance: EditorCore | null = null;
|
||||
|
||||
public readonly command: CommandManager;
|
||||
public readonly playback: PlaybackManager;
|
||||
public readonly timeline: TimelineManager;
|
||||
public readonly scene: SceneManager;
|
||||
@@ -20,6 +22,7 @@ export class EditorCore {
|
||||
public readonly renderer: RendererManager;
|
||||
|
||||
private constructor() {
|
||||
this.command = new CommandManager();
|
||||
this.playback = new PlaybackManager(this);
|
||||
this.timeline = new TimelineManager(this);
|
||||
this.scene = new SceneManager(this);
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Command } from "@/lib/commands";
|
||||
|
||||
export class CommandManager {
|
||||
private history: Command[] = [];
|
||||
private redoStack: Command[] = [];
|
||||
|
||||
execute({ command }: { command: Command }): Command {
|
||||
command.execute();
|
||||
this.history.push(command);
|
||||
this.redoStack = [];
|
||||
return command;
|
||||
}
|
||||
|
||||
undo(): void {
|
||||
if (this.history.length === 0) return;
|
||||
const command = this.history.pop();
|
||||
command?.undo();
|
||||
if (command) {
|
||||
this.redoStack.push(command);
|
||||
}
|
||||
}
|
||||
|
||||
redo(): void {
|
||||
if (this.redoStack.length === 0) return;
|
||||
const command = this.redoStack.pop();
|
||||
command?.redo();
|
||||
if (command) {
|
||||
this.history.push(command);
|
||||
}
|
||||
}
|
||||
|
||||
canUndo(): boolean {
|
||||
return this.history.length > 0;
|
||||
}
|
||||
|
||||
canRedo(): boolean {
|
||||
return this.redoStack.length > 0;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.history = [];
|
||||
this.redoStack = [];
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
import type { EditorCore } from "@/core";
|
||||
import type { MediaFile } from "@/types/media";
|
||||
import type { MediaFile } from "@/types/assets";
|
||||
import { storageService } from "@/lib/storage/storage-service";
|
||||
import { generateUUID } from "@/lib/utils";
|
||||
import { videoCache } from "@/lib/video-cache";
|
||||
import { generateThumbnail } from "@/lib/media-processing-utils";
|
||||
|
||||
export class MediaManager {
|
||||
private mediaFiles: MediaFile[] = [];
|
||||
private isLoading = false;
|
||||
public mediaFiles: MediaFile[] = [];
|
||||
public isLoading = false;
|
||||
private listeners = new Set<() => void>();
|
||||
|
||||
constructor(private editor: EditorCore) {}
|
||||
@@ -88,32 +87,7 @@ export class MediaManager {
|
||||
|
||||
try {
|
||||
const mediaItems = await storageService.loadAllMediaFiles({ projectId });
|
||||
|
||||
const updatedMediaItems = await Promise.all(
|
||||
mediaItems.map(async (item) => {
|
||||
if (item.type === "video" && item.file) {
|
||||
try {
|
||||
const thumbnailUrl = await generateThumbnail({
|
||||
videoFile: item.file,
|
||||
timeInSeconds: 1,
|
||||
});
|
||||
return {
|
||||
...item,
|
||||
thumbnailUrl,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Failed to regenerate thumbnail for video ${item.id}:`,
|
||||
error,
|
||||
);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}),
|
||||
);
|
||||
|
||||
this.mediaFiles = updatedMediaItems;
|
||||
this.mediaFiles = mediaItems;
|
||||
this.notify();
|
||||
} catch (error) {
|
||||
console.error("Failed to load media items:", error);
|
||||
|
||||
@@ -2,12 +2,12 @@ import type { EditorCore } from "@/core";
|
||||
import { DEFAULT_FPS } from "@/constants/editor-constants";
|
||||
|
||||
export class PlaybackManager {
|
||||
private isPlaying = false;
|
||||
private currentTime = 0;
|
||||
private volume = 1;
|
||||
private muted = false;
|
||||
private previousVolume = 1;
|
||||
private speed = 1.0;
|
||||
public isPlaying = false;
|
||||
public currentTime = 0;
|
||||
public volume = 1;
|
||||
public muted = false;
|
||||
public previousVolume = 1;
|
||||
public speed = 1.0;
|
||||
private listeners = new Set<() => void>();
|
||||
private playbackTimer: number | null = null;
|
||||
private lastUpdate = 0;
|
||||
|
||||
@@ -4,14 +4,19 @@ import type { TCanvasSize } from "@/types/editor";
|
||||
import { storageService } from "@/lib/storage/storage-service";
|
||||
import { toast } from "sonner";
|
||||
import { generateUUID } from "@/lib/utils";
|
||||
import { DEFAULT_FPS, DEFAULT_CANVAS_SIZE, DEFAULT_BLUR_INTENSITY } from "@/constants/editor-constants";
|
||||
import {
|
||||
DEFAULT_FPS,
|
||||
DEFAULT_CANVAS_SIZE,
|
||||
DEFAULT_BLUR_INTENSITY,
|
||||
} from "@/constants/editor-constants";
|
||||
import { buildDefaultScene } from "@/lib/scene-utils";
|
||||
import { generateThumbnail } from "@/lib/media-processing-utils";
|
||||
|
||||
export class ProjectManager {
|
||||
private activeProject: TProject | null = null;
|
||||
private savedProjects: TProject[] = [];
|
||||
private isLoading = true;
|
||||
private isInitialized = false;
|
||||
public activeProject: TProject | null = null;
|
||||
public savedProjects: TProject[] = [];
|
||||
public isLoading = true;
|
||||
public isInitialized = false;
|
||||
private invalidProjectIds = new Set<string>();
|
||||
private listeners = new Set<() => void>();
|
||||
|
||||
@@ -257,6 +262,70 @@ export class ProjectManager {
|
||||
}
|
||||
}
|
||||
|
||||
async updateProjectThumbnail({
|
||||
thumbnail,
|
||||
}: {
|
||||
thumbnail: string;
|
||||
}): Promise<void> {
|
||||
if (!this.activeProject) return;
|
||||
|
||||
const updatedProject = {
|
||||
...this.activeProject,
|
||||
thumbnail,
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
try {
|
||||
await storageService.saveProject({ project: updatedProject });
|
||||
this.activeProject = updatedProject;
|
||||
this.notify();
|
||||
await this.loadAllProjects();
|
||||
} catch (error) {
|
||||
console.error("Failed to update project thumbnail:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async prepareExit(): Promise<void> {
|
||||
if (!this.activeProject) return;
|
||||
|
||||
try {
|
||||
const tracks = this.editor.timeline.getTracks();
|
||||
const mediaFiles = this.editor.media.getMediaFiles();
|
||||
|
||||
const firstElement = tracks
|
||||
.flatMap((track) => track.elements)
|
||||
.sort((a, b) => a.startTime - b.startTime)[0];
|
||||
|
||||
if (
|
||||
firstElement &&
|
||||
(firstElement.type === "video" || firstElement.type === "image")
|
||||
) {
|
||||
const mediaFile = mediaFiles.find(
|
||||
(item) => item.id === firstElement.mediaId,
|
||||
);
|
||||
|
||||
if (mediaFile) {
|
||||
let thumbnailDataUrl: string | undefined;
|
||||
|
||||
if (mediaFile.type === "video" && mediaFile.file) {
|
||||
thumbnailDataUrl = await generateThumbnail({
|
||||
videoFile: mediaFile.file,
|
||||
timeInSeconds: 1,
|
||||
});
|
||||
} else if (mediaFile.type === "image" && mediaFile.url) {
|
||||
thumbnailDataUrl = mediaFile.thumbnailUrl || mediaFile.url;
|
||||
}
|
||||
|
||||
if (thumbnailDataUrl && !thumbnailDataUrl.startsWith("blob:")) {
|
||||
await this.updateProjectThumbnail({ thumbnail: thumbnailDataUrl });
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to generate project thumbnail on exit:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async updateProjectBackground({
|
||||
backgroundColor,
|
||||
}: {
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { EditorCore } from "@/core";
|
||||
import type { RootNode } from "@/services/renderer/nodes/root-node";
|
||||
import type { ExportOptions, ExportResult } from "@/types/export";
|
||||
import type { TimelineTrack } from "@/types/timeline";
|
||||
import type { MediaFile } from "@/types/media";
|
||||
import type { MediaFile } from "@/types/assets";
|
||||
import { SceneExporter } from "@/services/renderer/scene-exporter";
|
||||
import { buildScene } from "@/services/renderer/scene-builder";
|
||||
import { DEFAULT_FPS, DEFAULT_CANVAS_SIZE } from "@/constants/editor-constants";
|
||||
@@ -23,7 +23,7 @@ interface AudioElement {
|
||||
}
|
||||
|
||||
export class RendererManager {
|
||||
private renderTree: RootNode | null = null;
|
||||
public renderTree: RootNode | null = null;
|
||||
private listeners = new Set<() => void>();
|
||||
|
||||
constructor(private editor: EditorCore) {}
|
||||
@@ -166,14 +166,17 @@ export class RendererManager {
|
||||
if (track.muted) continue;
|
||||
|
||||
for (const element of track.elements) {
|
||||
if (element.type !== "media") continue;
|
||||
if (element.type !== "audio") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const mediaElement = element;
|
||||
const mediaItem = mediaMap.get(mediaElement.mediaId);
|
||||
if (!mediaItem || mediaItem.type !== "audio") continue;
|
||||
const mediaItem = mediaMap.get(element.mediaId);
|
||||
if (!mediaItem || mediaItem.type !== "audio") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const visibleDuration =
|
||||
mediaElement.duration - mediaElement.trimStart - mediaElement.trimEnd;
|
||||
element.duration - element.trimStart - element.trimEnd;
|
||||
if (visibleDuration <= 0) continue;
|
||||
|
||||
try {
|
||||
@@ -184,11 +187,11 @@ export class RendererManager {
|
||||
|
||||
audioElements.push({
|
||||
buffer: audioBuffer,
|
||||
startTime: mediaElement.startTime,
|
||||
duration: mediaElement.duration,
|
||||
trimStart: mediaElement.trimStart,
|
||||
trimEnd: mediaElement.trimEnd,
|
||||
muted: mediaElement.muted || track.muted || false,
|
||||
startTime: element.startTime,
|
||||
duration: element.duration,
|
||||
trimStart: element.trimStart,
|
||||
trimEnd: element.trimEnd,
|
||||
muted: element.muted || track.muted || false,
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn(`Failed to decode audio file ${mediaItem.name}:`, error);
|
||||
|
||||
@@ -21,8 +21,8 @@ import {
|
||||
} from "@/lib/timeline/bookmark-utils";
|
||||
|
||||
export class SceneManager {
|
||||
private currentScene: TScene | null = null;
|
||||
private scenes: TScene[] = [];
|
||||
public currentScene: TScene | null = null;
|
||||
public scenes: TScene[] = [];
|
||||
private listeners = new Set<() => void>();
|
||||
|
||||
constructor(private editor: EditorCore) {}
|
||||
|
||||
@@ -4,31 +4,30 @@ import type {
|
||||
CreateTimelineElement,
|
||||
TimelineTrack,
|
||||
TextElement,
|
||||
DragData,
|
||||
TimelineElement,
|
||||
} from "@/types/timeline";
|
||||
import type { MediaFile } from "@/types/media";
|
||||
import { calculateTotalDuration } from "@/lib/timeline/calculation-utils";
|
||||
import { calculateTotalDuration } from "@/lib/timeline";
|
||||
import { storageService } from "@/lib/storage/storage-service";
|
||||
import {
|
||||
AddTrackCommand,
|
||||
RemoveTrackCommand,
|
||||
ToggleTrackMuteCommand,
|
||||
AddElementToTrackCommand,
|
||||
UpdateElementTrimCommand,
|
||||
UpdateElementDurationCommand,
|
||||
DeleteElementsCommand,
|
||||
DuplicateElementsCommand,
|
||||
ToggleElementsHiddenCommand,
|
||||
ToggleElementsMutedCommand,
|
||||
UpdateTextElementCommand,
|
||||
SplitElementsCommand,
|
||||
PasteCommand,
|
||||
UpdateElementStartTimeCommand,
|
||||
MoveElementCommand,
|
||||
} from "@/lib/commands/timeline";
|
||||
|
||||
export class TimelineManager {
|
||||
private _tracks: TimelineTrack[] = [];
|
||||
private history: TimelineTrack[][] = [];
|
||||
private redoStack: TimelineTrack[][] = [];
|
||||
private clipboard: {
|
||||
items: Array<{ trackType: TrackType; element: CreateTimelineElement }>;
|
||||
} | null = null;
|
||||
private tracks: TimelineTrack[] = [];
|
||||
private snappingEnabled = true;
|
||||
private rippleEditingEnabled = false;
|
||||
private selectedElements: { trackId: string; elementId: string }[] = [];
|
||||
private dragState = {
|
||||
isDragging: false,
|
||||
elementId: null as string | null,
|
||||
trackId: null as string | null,
|
||||
startMouseX: 0,
|
||||
startElementTime: 0,
|
||||
clickOffsetTime: 0,
|
||||
currentTime: 0,
|
||||
};
|
||||
public sortedTracks: TimelineTrack[] = [];
|
||||
private listeners = new Set<() => void>();
|
||||
|
||||
constructor(private editor: EditorCore) {
|
||||
@@ -36,110 +35,18 @@ export class TimelineManager {
|
||||
}
|
||||
|
||||
private initializeTracks(): void {
|
||||
this._tracks = [];
|
||||
this.tracks = [];
|
||||
this.sortedTracks = [];
|
||||
}
|
||||
|
||||
getSortedTracks(): TimelineTrack[] {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
toggleSnapping(): void {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
toggleRippleEditing(): void {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
selectElement({
|
||||
trackId,
|
||||
elementId,
|
||||
multi = false,
|
||||
}: {
|
||||
trackId: string;
|
||||
elementId: string;
|
||||
multi?: boolean;
|
||||
}): void {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
deselectElement({
|
||||
trackId,
|
||||
elementId,
|
||||
}: {
|
||||
trackId: string;
|
||||
elementId: string;
|
||||
}): void {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
clearSelectedElements(): void {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
setSelectedElements({
|
||||
elements,
|
||||
}: {
|
||||
elements: { trackId: string; elementId: string }[];
|
||||
}): void {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
setDragState({
|
||||
dragState,
|
||||
}: {
|
||||
dragState: Partial<{
|
||||
isDragging: boolean;
|
||||
elementId: string | null;
|
||||
trackId: string | null;
|
||||
startMouseX: number;
|
||||
startElementTime: number;
|
||||
clickOffsetTime: number;
|
||||
currentTime: number;
|
||||
}>;
|
||||
}): void {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
startDrag({
|
||||
elementId,
|
||||
trackId,
|
||||
startMouseX,
|
||||
startElementTime,
|
||||
clickOffsetTime,
|
||||
}: {
|
||||
elementId: string;
|
||||
trackId: string;
|
||||
startMouseX: number;
|
||||
startElementTime: number;
|
||||
clickOffsetTime: number;
|
||||
}): void {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
updateDragTime({ currentTime }: { currentTime: number }): void {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
endDrag(): void {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
addTrack({ type }: { type: TrackType }): string {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
insertTrackAt({ type, index }: { type: TrackType; index: number }): string {
|
||||
throw new Error("Not implemented");
|
||||
addTrack({ type, index }: { type: TrackType; index?: number }): string {
|
||||
const command = new AddTrackCommand(type, index);
|
||||
this.editor.command.execute({ command });
|
||||
return command.getTrackId();
|
||||
}
|
||||
|
||||
removeTrack({ trackId }: { trackId: string }): void {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
removeTrackWithRipple({ trackId }: { trackId: string }): void {
|
||||
throw new Error("Not implemented");
|
||||
const command = new RemoveTrackCommand(trackId);
|
||||
this.editor.command.execute({ command });
|
||||
}
|
||||
|
||||
addElementToTrack({
|
||||
@@ -149,7 +56,8 @@ export class TimelineManager {
|
||||
trackId: string;
|
||||
element: CreateTimelineElement;
|
||||
}): void {
|
||||
throw new Error("Not implemented");
|
||||
const command = new AddElementToTrackCommand(trackId, element);
|
||||
this.editor.command.execute({ command });
|
||||
}
|
||||
|
||||
updateElementTrim({
|
||||
@@ -165,7 +73,17 @@ export class TimelineManager {
|
||||
trimEnd: number;
|
||||
pushHistory?: boolean;
|
||||
}): void {
|
||||
throw new Error("Not implemented");
|
||||
const command = new UpdateElementTrimCommand(
|
||||
trackId,
|
||||
elementId,
|
||||
trimStart,
|
||||
trimEnd,
|
||||
);
|
||||
if (pushHistory) {
|
||||
this.editor.command.execute({ command });
|
||||
} else {
|
||||
command.execute();
|
||||
}
|
||||
}
|
||||
|
||||
updateElementDuration({
|
||||
@@ -179,97 +97,99 @@ export class TimelineManager {
|
||||
duration: number;
|
||||
pushHistory?: boolean;
|
||||
}): void {
|
||||
throw new Error("Not implemented");
|
||||
const command = new UpdateElementDurationCommand(
|
||||
trackId,
|
||||
elementId,
|
||||
duration,
|
||||
);
|
||||
if (pushHistory) {
|
||||
this.editor.command.execute({ command });
|
||||
} else {
|
||||
command.execute();
|
||||
}
|
||||
}
|
||||
|
||||
updateElementStartTime({
|
||||
trackId,
|
||||
elementId,
|
||||
elements,
|
||||
startTime,
|
||||
pushHistory = true,
|
||||
}: {
|
||||
trackId: string;
|
||||
elementId: string;
|
||||
elements: { trackId: string; elementId: string }[];
|
||||
startTime: number;
|
||||
pushHistory?: boolean;
|
||||
}): void {
|
||||
throw new Error("Not implemented");
|
||||
const command = new UpdateElementStartTimeCommand(elements, startTime);
|
||||
this.editor.command.execute({ command });
|
||||
}
|
||||
|
||||
toggleTrackMute({ trackId }: { trackId: string }): void {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
splitAndKeepLeft({
|
||||
trackId,
|
||||
elementId,
|
||||
splitTime,
|
||||
}: {
|
||||
trackId: string;
|
||||
elementId: string;
|
||||
splitTime: number;
|
||||
}): void {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
splitAndKeepRight({
|
||||
trackId,
|
||||
elementId,
|
||||
splitTime,
|
||||
}: {
|
||||
trackId: string;
|
||||
elementId: string;
|
||||
splitTime: number;
|
||||
}): void {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
updateElementStartTimeWithRipple({
|
||||
trackId,
|
||||
moveElement({
|
||||
sourceTrackId,
|
||||
targetTrackId,
|
||||
elementId,
|
||||
newStartTime,
|
||||
}: {
|
||||
trackId: string;
|
||||
sourceTrackId: string;
|
||||
targetTrackId: string;
|
||||
elementId: string;
|
||||
newStartTime: number;
|
||||
}): void {
|
||||
throw new Error("Not implemented");
|
||||
const command = new MoveElementCommand(
|
||||
sourceTrackId,
|
||||
targetTrackId,
|
||||
elementId,
|
||||
newStartTime,
|
||||
);
|
||||
this.editor.command.execute({ command });
|
||||
}
|
||||
|
||||
removeElementFromTrackWithRipple({
|
||||
trackId,
|
||||
elementId,
|
||||
pushHistory = true,
|
||||
toggleTrackMute({ trackId }: { trackId: string }): void {
|
||||
const command = new ToggleTrackMuteCommand(trackId);
|
||||
this.editor.command.execute({ command });
|
||||
}
|
||||
|
||||
splitElements({
|
||||
elements,
|
||||
splitTime,
|
||||
retainSide = "both",
|
||||
}: {
|
||||
trackId: string;
|
||||
elementId: string;
|
||||
pushHistory?: boolean;
|
||||
elements: { trackId: string; elementId: string }[];
|
||||
splitTime: number;
|
||||
retainSide?: "both" | "left" | "right";
|
||||
}): void {
|
||||
throw new Error("Not implemented");
|
||||
const command = new SplitElementsCommand(elements, splitTime, retainSide);
|
||||
this.editor.command.execute({ command });
|
||||
}
|
||||
|
||||
getTotalDuration(): number {
|
||||
return calculateTotalDuration({ tracks: this._tracks });
|
||||
return calculateTotalDuration({ tracks: this.sortedTracks });
|
||||
}
|
||||
|
||||
getProjectThumbnail({
|
||||
projectId,
|
||||
getTrackById({ trackId }: { trackId: string }): TimelineTrack | null {
|
||||
return this.sortedTracks.find((track) => track.id === trackId) ?? null;
|
||||
}
|
||||
|
||||
getElementsWithTracks({
|
||||
elements,
|
||||
}: {
|
||||
projectId: string;
|
||||
}): Promise<string | null> {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
elements:
|
||||
| { trackId: string; elementId: string }[]
|
||||
| { trackId: string; elementId: string };
|
||||
}):
|
||||
| Array<{ track: TimelineTrack; element: TimelineElement }>
|
||||
| { track: TimelineTrack; element: TimelineElement }
|
||||
| null {
|
||||
const normalized = Array.isArray(elements) ? elements : [elements];
|
||||
const result: Array<{ track: TimelineTrack; element: TimelineElement }> =
|
||||
[];
|
||||
|
||||
undo(): void {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
for (const { trackId, elementId } of normalized) {
|
||||
const track = this.getTrackById({ trackId });
|
||||
const element = track?.elements.find((el) => el.id === elementId);
|
||||
|
||||
redo(): void {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
if (track && element) {
|
||||
result.push({ track, element });
|
||||
}
|
||||
}
|
||||
|
||||
pushHistory(): void {
|
||||
throw new Error("Not implemented");
|
||||
return Array.isArray(elements) ? result : (result[0] ?? null);
|
||||
}
|
||||
|
||||
async loadProjectTimeline({
|
||||
@@ -279,7 +199,19 @@ export class TimelineManager {
|
||||
projectId: string;
|
||||
sceneId?: string;
|
||||
}): Promise<void> {
|
||||
throw new Error("Not implemented");
|
||||
try {
|
||||
const tracks = await storageService.loadTimeline({
|
||||
projectId,
|
||||
sceneId,
|
||||
});
|
||||
|
||||
if (tracks) {
|
||||
this.updateTracks(tracks);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load timeline:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async saveProjectTimeline({
|
||||
@@ -289,91 +221,34 @@ export class TimelineManager {
|
||||
projectId: string;
|
||||
sceneId?: string;
|
||||
}): Promise<void> {
|
||||
throw new Error("Not implemented");
|
||||
try {
|
||||
await storageService.saveTimeline({
|
||||
projectId,
|
||||
tracks: this.sortedTracks,
|
||||
sceneId,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to save timeline:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
clearTimeline(): void {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
copySelected(): void {
|
||||
throw new Error("Not implemented");
|
||||
this.updateTracks([]);
|
||||
}
|
||||
|
||||
pasteAtTime({ time }: { time: number }): void {
|
||||
throw new Error("Not implemented");
|
||||
const command = new PasteCommand(time);
|
||||
this.editor.command.execute({ command });
|
||||
}
|
||||
|
||||
deleteSelected({
|
||||
trackId,
|
||||
elementId,
|
||||
deleteElements({
|
||||
elements,
|
||||
}: {
|
||||
trackId?: string;
|
||||
elementId?: string;
|
||||
elements: { trackId: string; elementId: string }[];
|
||||
}): void {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
splitSelected({
|
||||
splitTime,
|
||||
trackId,
|
||||
elementId,
|
||||
}: {
|
||||
splitTime: number;
|
||||
trackId?: string;
|
||||
elementId?: string;
|
||||
}): void {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
toggleSelectedHidden({
|
||||
trackId,
|
||||
elementId,
|
||||
}: {
|
||||
trackId?: string;
|
||||
elementId?: string;
|
||||
}): void {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
toggleSelectedMuted({
|
||||
trackId,
|
||||
elementId,
|
||||
}: {
|
||||
trackId?: string;
|
||||
elementId?: string;
|
||||
}): void {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
duplicateElement({
|
||||
trackId,
|
||||
elementId,
|
||||
}: {
|
||||
trackId: string;
|
||||
elementId: string;
|
||||
}): void {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
revealElementInMedia({ elementId }: { elementId: string }): void {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
getContextMenuState({
|
||||
trackId,
|
||||
elementId,
|
||||
}: {
|
||||
trackId: string;
|
||||
elementId: string;
|
||||
}): {
|
||||
isMultipleSelected: boolean;
|
||||
isCurrentElementSelected: boolean;
|
||||
hasAudioElements: boolean;
|
||||
canSplitSelected: boolean;
|
||||
currentTime: number;
|
||||
} {
|
||||
throw new Error("Not implemented");
|
||||
const command = new DeleteElementsCommand(elements);
|
||||
this.editor.command.execute({ command });
|
||||
}
|
||||
|
||||
updateTextElement({
|
||||
@@ -395,14 +270,38 @@ export class TimelineManager {
|
||||
| "fontWeight"
|
||||
| "fontStyle"
|
||||
| "textDecoration"
|
||||
| "x"
|
||||
| "y"
|
||||
| "rotation"
|
||||
| "opacity"
|
||||
>
|
||||
>;
|
||||
}): void {
|
||||
throw new Error("Not implemented");
|
||||
const command = new UpdateTextElementCommand(trackId, elementId, updates);
|
||||
this.editor.command.execute({ command });
|
||||
}
|
||||
|
||||
duplicateElements({
|
||||
elements,
|
||||
}: {
|
||||
elements: { trackId: string; elementId: string }[];
|
||||
}): void {
|
||||
const command = new DuplicateElementsCommand({ elements });
|
||||
this.editor.command.execute({ command });
|
||||
}
|
||||
|
||||
toggleElementsHidden({
|
||||
elements,
|
||||
}: {
|
||||
elements: { trackId: string; elementId: string }[];
|
||||
}): void {
|
||||
const command = new ToggleElementsHiddenCommand(elements);
|
||||
this.editor.command.execute({ command });
|
||||
}
|
||||
|
||||
toggleElementsMuted({
|
||||
elements,
|
||||
}: {
|
||||
elements: { trackId: string; elementId: string }[];
|
||||
}): void {
|
||||
const command = new ToggleElementsMutedCommand(elements);
|
||||
this.editor.command.execute({ command });
|
||||
}
|
||||
|
||||
checkElementOverlap({
|
||||
@@ -419,50 +318,8 @@ export class TimelineManager {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
findOrCreateTrack({ trackType }: { trackType: TrackType }): string {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
addElementAtTime({
|
||||
item,
|
||||
currentTime = 0,
|
||||
}: {
|
||||
item: MediaFile | TextElement;
|
||||
currentTime?: number;
|
||||
}): boolean {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
addElementToNewTrack({
|
||||
item,
|
||||
}: {
|
||||
item: MediaFile | TextElement | DragData;
|
||||
}): boolean {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
getTracks(): TimelineTrack[] {
|
||||
return this.tracks;
|
||||
}
|
||||
|
||||
getTracksWithMain(): TimelineTrack[] {
|
||||
return this._tracks;
|
||||
}
|
||||
|
||||
getSelectedElements(): { trackId: string; elementId: string }[] {
|
||||
return this.selectedElements;
|
||||
}
|
||||
|
||||
getDragState(): typeof this.dragState {
|
||||
return this.dragState;
|
||||
}
|
||||
|
||||
getSnappingEnabled(): boolean {
|
||||
return this.snappingEnabled;
|
||||
}
|
||||
|
||||
getRippleEditingEnabled(): boolean {
|
||||
return this.rippleEditingEnabled;
|
||||
return this.sortedTracks;
|
||||
}
|
||||
|
||||
subscribe(listener: () => void): () => void {
|
||||
@@ -474,9 +331,8 @@ export class TimelineManager {
|
||||
this.listeners.forEach((fn) => fn());
|
||||
}
|
||||
|
||||
private updateTracks(newTracks: TimelineTrack[]): void {
|
||||
this._tracks = newTracks;
|
||||
this.tracks = newTracks; // TODO: Implement proper sorting with main track
|
||||
updateTracks(newTracks: TimelineTrack[]): void {
|
||||
this.sortedTracks = newTracks;
|
||||
this.notify();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user