fuck malware, fuck you

This commit is contained in:
Maze Winther
2026-01-18 05:44:25 +01:00
parent 0934db2aba
commit f18e9f4b4e
59 changed files with 1850 additions and 2100 deletions
+4
View File
@@ -5,6 +5,7 @@ import { ProjectManager } from "./managers/project-manager";
import { MediaManager } from "./managers/media-manager";
import { RendererManager } from "./managers/renderer-manager";
import { CommandManager } from "./managers/commands";
import { SaveManager } from "./managers/save-manager";
import { buildScene } from "@/services/renderer/scene-builder";
import { SceneExporter } from "@/services/renderer/scene-exporter";
import type { ExportOptions } from "@/types/export";
@@ -19,6 +20,7 @@ export class EditorCore {
public readonly project: ProjectManager;
public readonly media: MediaManager;
public readonly renderer: RendererManager;
public readonly save: SaveManager;
private constructor() {
this.command = new CommandManager();
@@ -28,6 +30,8 @@ export class EditorCore {
this.project = new ProjectManager(this);
this.media = new MediaManager(this);
this.renderer = new RendererManager(this);
this.save = new SaveManager(this);
this.save.start();
}
static getInstance(): EditorCore {
+15 -24
View File
@@ -8,6 +8,7 @@ import type { TimelineElement } from "@/types/timeline";
import { storageService } from "@/lib/storage/storage-service";
import { toast } from "sonner";
import { generateUUID } from "@/lib/utils";
import { UpdateProjectSettingsCommand } from "@/lib/commands/project";
import {
DEFAULT_FPS,
DEFAULT_CANVAS_SIZE,
@@ -130,6 +131,7 @@ export class ProjectManager {
this.notify();
}
this.editor.save.pause();
await this.ensureStorageMigrations();
this.editor.media.clearAllAssets();
this.editor.scenes.clearScenes();
@@ -159,6 +161,7 @@ export class ProjectManager {
} finally {
this.isLoading = false;
this.notify();
this.editor.save.resume();
}
}
@@ -339,27 +342,20 @@ export class ProjectManager {
async updateSettings({
settings,
pushHistory = true,
}: {
settings: Partial<TProjectSettings>;
pushHistory?: boolean;
}): Promise<void> {
if (!this.active) return;
const updatedProject: TProject = {
...this.active,
settings: { ...this.active.settings, ...settings },
metadata: { ...this.active.metadata, updatedAt: new Date() },
};
try {
await storageService.saveProject({ project: updatedProject });
this.active = updatedProject;
this.notify();
} catch (error) {
console.error("Failed to update settings:", error);
toast.error("Failed to update settings", {
description: "Please try again",
});
const command = new UpdateProjectSettingsCommand(settings);
if (pushHistory) {
this.editor.command.execute({ command });
return;
}
command.execute();
}
async updateThumbnail({ thumbnail }: { thumbnail: string }): Promise<void> {
@@ -369,15 +365,10 @@ export class ProjectManager {
...this.active,
metadata: { ...this.active.metadata, thumbnail, updatedAt: new Date() },
};
try {
await storageService.saveProject({ project: updatedProject });
this.active = updatedProject;
this.notify();
this.updateMetadata(updatedProject);
} catch (error) {
console.error("Failed to update thumbnail:", error);
}
this.active = updatedProject;
this.notify();
this.updateMetadata(updatedProject);
this.editor.save.markDirty();
}
async prepareExit(): Promise<void> {
+105
View File
@@ -0,0 +1,105 @@
import type { EditorCore } from "@/core";
type SaveManagerOptions = {
debounceMs?: number;
};
export class SaveManager {
private debounceMs: number;
private isPaused = false;
private isSaving = false;
private hasPendingSave = false;
private saveTimer: ReturnType<typeof setTimeout> | null = null;
private unsubscribeHandlers: Array<() => void> = [];
constructor(
private editor: EditorCore,
{ debounceMs = 800 }: SaveManagerOptions = {},
) {
this.debounceMs = debounceMs;
}
start(): void {
if (this.unsubscribeHandlers.length > 0) return;
this.unsubscribeHandlers = [
this.editor.scenes.subscribe(() => {
this.markDirty();
}),
this.editor.timeline.subscribe(() => {
this.markDirty();
}),
];
}
stop(): void {
for (const unsubscribe of this.unsubscribeHandlers) {
unsubscribe();
}
this.unsubscribeHandlers = [];
this.clearTimer();
}
pause(): void {
this.isPaused = true;
}
resume(): void {
this.isPaused = false;
if (this.hasPendingSave) {
this.queueSave();
}
}
markDirty(
{ force = false }: { force?: boolean } = {},
): void {
if (this.isPaused && !force) return;
this.hasPendingSave = true;
this.queueSave();
}
async flush(): Promise<void> {
this.hasPendingSave = true;
await this.saveNow();
}
private queueSave(): void {
if (this.isSaving) return;
if (this.saveTimer) {
clearTimeout(this.saveTimer);
}
this.saveTimer = setTimeout(() => {
void this.saveNow();
}, this.debounceMs);
}
private async saveNow(): Promise<void> {
if (this.isSaving) return;
if (!this.hasPendingSave) return;
const activeProject = this.editor.project.getActiveOrNull();
if (!activeProject) return;
if (this.editor.project.getIsLoading()) return;
if (this.editor.project.getMigrationState().isMigrating) return;
this.isSaving = true;
this.hasPendingSave = false;
this.clearTimer();
try {
await this.editor.project.saveCurrentProject();
} finally {
this.isSaving = false;
if (this.hasPendingSave) {
this.queueSave();
}
}
}
private clearTimer(): void {
if (!this.saveTimer) return;
clearTimeout(this.saveTimer);
this.saveTimer = null;
}
}
+32 -165
View File
@@ -1,23 +1,24 @@
import type { EditorCore } from "@/core";
import type { TScene } from "@/types/timeline";
import type { TimelineTrack, TScene } from "@/types/timeline";
import { storageService } from "@/lib/storage/storage-service";
import { toast } from "sonner";
import {
updateSceneInArray,
getMainScene,
ensureMainScene,
buildDefaultScene,
canDeleteScene,
getFallbackSceneAfterDelete,
findCurrentScene,
} from "@/lib/scene-utils";
import {
getFrameTime,
toggleBookmarkInArray,
removeBookmarkFromArray,
isBookmarkAtTime,
} from "@/lib/timeline/bookmark-utils";
import { ensureMainTrack } from "@/lib/timeline/track-utils";
import {
CreateSceneCommand,
DeleteSceneCommand,
RemoveBookmarkCommand,
RenameSceneCommand,
ToggleBookmarkCommand,
} from "@/lib/commands/scene";
export class ScenesManager {
private active: TScene | null = null;
@@ -33,16 +34,13 @@ export class ScenesManager {
name: string;
isMain: boolean;
}): Promise<string> {
const newScene = buildDefaultScene({ name, isMain });
const updatedScenes = [...this.list, newScene];
try {
await this.updateProjectWithScenes({ updatedScenes });
return newScene.id;
} catch (error) {
console.error("Failed to create scene:", error);
throw error;
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> {
@@ -57,23 +55,12 @@ export class ScenesManager {
throw new Error(reason);
}
const updatedScenes = this.list.filter((s) => s.id !== sceneId);
const newCurrentScene = getFallbackSceneAfterDelete({
scenes: updatedScenes,
deletedSceneId: sceneId,
currentSceneId: this.active?.id || null,
});
try {
await this.updateProjectWithScenes({
updatedScenes,
updatedSceneId: newCurrentScene?.id,
});
} catch (error) {
console.error("Failed to delete scene:", error);
throw error;
if (!this.editor.project.getActive()) {
throw new Error("No active project");
}
const command = new DeleteSceneCommand(sceneId);
this.editor.command.execute({ command });
}
async renameScene({
@@ -83,21 +70,12 @@ export class ScenesManager {
sceneId: string;
name: string;
}): Promise<void> {
const updatedScenes = updateSceneInArray({
scenes: this.list,
sceneId,
updates: { name, updatedAt: new Date() },
});
try {
await this.updateProjectWithScenes({
updatedScenes,
updatedSceneId: sceneId,
});
} catch (error) {
console.error("Failed to rename scene:", error);
throw error;
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> {
@@ -119,7 +97,6 @@ export class ScenesManager {
},
};
await storageService.saveProject({ project: updatedProject });
this.editor.project.setActiveProject({ project: updatedProject });
}
@@ -128,39 +105,8 @@ export class ScenesManager {
}
async toggleBookmark({ time }: { time: number }): Promise<void> {
const activeScene = this.getActiveScene();
if (!activeScene || !this.active) return;
const activeProject = this.editor.project.getActive();
if (!activeProject) return;
const frameTime = getFrameTime({
time,
fps: activeProject.settings.fps,
});
const updatedBookmarks = toggleBookmarkInArray({
bookmarks: activeScene.bookmarks,
frameTime,
});
const updatedScenes = updateSceneInArray({
scenes: this.list,
sceneId: activeScene.id,
updates: { bookmarks: updatedBookmarks },
});
try {
await this.updateProjectWithScenes({
updatedScenes,
updatedSceneId: activeScene.id,
});
} catch (error) {
console.error("Failed to update scene bookmarks:", error);
toast.error("Failed to update bookmarks", {
description: "Please try again",
});
}
const command = new ToggleBookmarkCommand(time);
this.editor.command.execute({ command });
}
isBookmarked({ time }: { time: number }): boolean {
@@ -178,43 +124,8 @@ export class ScenesManager {
}
async removeBookmark({ time }: { time: number }): Promise<void> {
const activeScene = this.getActiveScene();
if (!activeScene || !this.active) return;
const activeProject = this.editor.project.getActive();
if (!activeProject) return;
const frameTime = getFrameTime({
time,
fps: activeProject.settings.fps,
});
const updatedBookmarks = removeBookmarkFromArray({
bookmarks: activeScene.bookmarks,
frameTime,
});
if (updatedBookmarks.length === activeScene.bookmarks.length) {
return;
}
const updatedScenes = updateSceneInArray({
scenes: this.list,
sceneId: activeScene.id,
updates: { bookmarks: updatedBookmarks },
});
try {
await this.updateProjectWithScenes({
updatedScenes,
updatedSceneId: activeScene.id,
});
} catch (error) {
console.error("Failed to update scene bookmarks:", error);
toast.error("Failed to remove bookmark", {
description: "Please try again",
});
}
const command = new RemoveBookmarkCommand(time);
this.editor.command.execute({ command });
}
async loadProjectScenes({ projectId }: { projectId: string }): Promise<void> {
@@ -245,8 +156,8 @@ export class ScenesManager {
updatedAt: new Date(),
},
};
await storageService.saveProject({ project: updatedProject });
this.editor.project.setActiveProject({ project: updatedProject });
this.editor.save.markDirty({ force: true });
}
}
}
@@ -292,17 +203,8 @@ export class ScenesManager {
},
};
storageService
.saveProject({ project: updatedProject })
.then(() => {
this.editor.project.setActiveProject({ project: updatedProject });
})
.catch((error) => {
console.error(
"Failed to save project with background scene:",
error,
);
});
this.editor.project.setActiveProject({ project: updatedProject });
this.editor.save.markDirty({ force: true });
}
}
}
@@ -347,9 +249,6 @@ export class ScenesManager {
updatedAt: new Date(),
},
};
storageService.saveProject({ project: updatedProject }).catch((error) => {
console.error("Failed to persist scenes:", error);
});
this.editor.project.setActiveProject({ project: updatedProject });
}
}
@@ -366,7 +265,7 @@ export class ScenesManager {
updateSceneTracks({
tracks,
}: {
tracks: import("@/types/timeline").TimelineTrack[];
tracks: TimelineTrack[];
}): void {
if (!this.active) return;
@@ -421,36 +320,4 @@ export class ScenesManager {
return { scenes: ensuredScenes, hasAddedMainTrack };
}
private async updateProjectWithScenes({
updatedScenes,
updatedSceneId,
}: {
updatedScenes: TScene[];
updatedSceneId?: string;
}): Promise<void> {
const activeProject = this.editor.project.getActive();
if (!activeProject) {
throw new Error("No active project");
}
const updatedScene = updatedSceneId
? updatedScenes.find((s) => s.id === updatedSceneId)
: this.active;
const updatedProject = {
...activeProject,
scenes: updatedScenes,
metadata: {
...activeProject.metadata,
updatedAt: new Date(),
},
};
await storageService.saveProject({ project: updatedProject });
this.editor.project.setActiveProject({ project: updatedProject });
this.list = updatedScenes;
this.active = updatedScene || null;
this.notify();
}
}
+9 -17
View File
@@ -12,6 +12,7 @@ import {
AddTrackCommand,
RemoveTrackCommand,
ToggleTrackMuteCommand,
ToggleTrackVisibilityCommand,
AddElementToTrackCommand,
UpdateElementTrimCommand,
UpdateElementDurationCommand,
@@ -54,20 +55,17 @@ export class TimelineManager {
}
updateElementTrim({
trackId,
elementId,
trimStart,
trimEnd,
pushHistory = true,
}: {
trackId: string;
elementId: string;
trimStart: number;
trimEnd: number;
pushHistory?: boolean;
}): void {
const command = new UpdateElementTrimCommand(
trackId,
elementId,
trimStart,
trimEnd,
@@ -118,17 +116,20 @@ export class TimelineManager {
targetTrackId,
elementId,
newStartTime,
createTrack,
}: {
sourceTrackId: string;
targetTrackId: string;
elementId: string;
newStartTime: number;
createTrack?: { type: TrackType; index: number };
}): void {
const command = new MoveElementCommand(
sourceTrackId,
targetTrackId,
elementId,
newStartTime,
createTrack,
);
this.editor.command.execute({ command });
}
@@ -138,6 +139,11 @@ export class TimelineManager {
this.editor.command.execute({ command });
}
toggleTrackVisibility({ trackId }: { trackId: string }): void {
const command = new ToggleTrackVisibilityCommand(trackId);
this.editor.command.execute({ command });
}
splitElements({
elements,
splitTime,
@@ -257,20 +263,6 @@ export class TimelineManager {
this.editor.command.execute({ command });
}
checkElementOverlap({
trackId,
startTime,
duration,
excludeElementId,
}: {
trackId: string;
startTime: number;
duration: number;
excludeElementId?: string;
}): boolean {
throw new Error("Not implemented");
}
getTracks(): TimelineTrack[] {
return this.editor.scenes.getActiveScene()?.tracks ?? [];
}