refactor: move canvas size to be a project-level setting instead of editor-level
This commit is contained in:
parent
f0dd30ebfc
commit
b0329f1372
|
|
@ -57,14 +57,18 @@ function ProjectSettingsTabs() {
|
|||
}
|
||||
|
||||
function ProjectInfoView() {
|
||||
const { activeProject, updateProjectFps } = useProjectStore();
|
||||
const { canvasPresets, setCanvasSize } = useEditorStore();
|
||||
const { activeProject, updateProjectFps, updateCanvasSize } =
|
||||
useProjectStore();
|
||||
const { canvasPresets } = useEditorStore();
|
||||
const { getDisplayName } = useAspectRatio();
|
||||
|
||||
const handleAspectRatioChange = (value: string) => {
|
||||
const preset = canvasPresets.find((p) => p.name === value);
|
||||
if (preset) {
|
||||
setCanvasSize({ width: preset.width, height: preset.height });
|
||||
updateCanvasSize(
|
||||
{ width: preset.width, height: preset.height },
|
||||
"preset"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
import { CanvasSize } from "@/types/editor";
|
||||
|
||||
const DEFAULT_CANVAS_PRESETS = [
|
||||
{ name: "16:9", width: 1920, height: 1080 },
|
||||
{ name: "9:16", width: 1080, height: 1920 },
|
||||
{ name: "1:1", width: 1080, height: 1080 },
|
||||
{ name: "4:3", width: 1440, height: 1080 },
|
||||
];
|
||||
|
||||
/**
|
||||
* Helper function to find the best matching canvas preset for an aspect ratio
|
||||
* @param aspectRatio The target aspect ratio to match
|
||||
* @returns The best matching canvas size
|
||||
*/
|
||||
export function findBestCanvasPreset(aspectRatio: number): CanvasSize {
|
||||
// Calculate aspect ratio for each preset and find the closest match
|
||||
let bestMatch = DEFAULT_CANVAS_PRESETS[0]; // Default to 16:9 HD
|
||||
let smallestDifference = Math.abs(
|
||||
aspectRatio - bestMatch.width / bestMatch.height
|
||||
);
|
||||
|
||||
for (const preset of DEFAULT_CANVAS_PRESETS) {
|
||||
const presetAspectRatio = preset.width / preset.height;
|
||||
const difference = Math.abs(aspectRatio - presetAspectRatio);
|
||||
|
||||
if (difference < smallestDifference) {
|
||||
smallestDifference = difference;
|
||||
bestMatch = preset;
|
||||
}
|
||||
}
|
||||
|
||||
// If the difference is still significant (> 0.1), create a custom size
|
||||
// based on the media aspect ratio with a reasonable resolution
|
||||
const bestAspectRatio = bestMatch.width / bestMatch.height;
|
||||
if (Math.abs(aspectRatio - bestAspectRatio) > 0.1) {
|
||||
// Create custom dimensions based on the aspect ratio
|
||||
if (aspectRatio > 1) {
|
||||
// Landscape - use 1920 width
|
||||
return { width: 1920, height: Math.round(1920 / aspectRatio) };
|
||||
}
|
||||
// Portrait or square - use 1080 height
|
||||
return { width: Math.round(1080 * aspectRatio), height: 1080 };
|
||||
}
|
||||
|
||||
return { width: bestMatch.width, height: bestMatch.height };
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { create } from "zustand";
|
||||
import { CanvasSize, CanvasPreset } from "@/types/editor";
|
||||
import { persist } from "zustand/middleware";
|
||||
import { CanvasPreset } from "@/types/editor";
|
||||
|
||||
type CanvasMode = "preset" | "original" | "custom";
|
||||
export type PlatformLayout = "tiktok";
|
||||
|
||||
export const PLATFORM_LAYOUTS: Record<PlatformLayout, string> = {
|
||||
|
|
@ -17,9 +17,7 @@ interface EditorState {
|
|||
isInitializing: boolean;
|
||||
isPanelsReady: boolean;
|
||||
|
||||
// Canvas/Project settings
|
||||
canvasSize: CanvasSize;
|
||||
canvasMode: CanvasMode;
|
||||
// Editor UI settings
|
||||
canvasPresets: CanvasPreset[];
|
||||
layoutGuide: LayoutGuideSettings;
|
||||
|
||||
|
|
@ -27,9 +25,6 @@ interface EditorState {
|
|||
setInitializing: (loading: boolean) => void;
|
||||
setPanelsReady: (ready: boolean) => void;
|
||||
initializeApp: () => Promise<void>;
|
||||
setCanvasSize: (size: CanvasSize) => void;
|
||||
setCanvasSizeToOriginal: (aspectRatio: number) => void;
|
||||
setCanvasSizeFromAspectRatio: (aspectRatio: number) => void;
|
||||
setLayoutGuide: (settings: Partial<LayoutGuideSettings>) => void;
|
||||
toggleLayoutGuide: (platform: PlatformLayout) => void;
|
||||
}
|
||||
|
|
@ -41,96 +36,56 @@ const DEFAULT_CANVAS_PRESETS: CanvasPreset[] = [
|
|||
{ name: "4:3", width: 1440, height: 1080 },
|
||||
];
|
||||
|
||||
// Helper function to find the best matching canvas preset for an aspect ratio
|
||||
const findBestCanvasPreset = (aspectRatio: number): CanvasSize => {
|
||||
// Calculate aspect ratio for each preset and find the closest match
|
||||
let bestMatch = DEFAULT_CANVAS_PRESETS[0]; // Default to 16:9 HD
|
||||
let smallestDifference = Math.abs(
|
||||
aspectRatio - bestMatch.width / bestMatch.height
|
||||
);
|
||||
|
||||
for (const preset of DEFAULT_CANVAS_PRESETS) {
|
||||
const presetAspectRatio = preset.width / preset.height;
|
||||
const difference = Math.abs(aspectRatio - presetAspectRatio);
|
||||
|
||||
if (difference < smallestDifference) {
|
||||
smallestDifference = difference;
|
||||
bestMatch = preset;
|
||||
}
|
||||
}
|
||||
|
||||
// If the difference is still significant (> 0.1), create a custom size
|
||||
// based on the media aspect ratio with a reasonable resolution
|
||||
const bestAspectRatio = bestMatch.width / bestMatch.height;
|
||||
if (Math.abs(aspectRatio - bestAspectRatio) > 0.1) {
|
||||
// Create custom dimensions based on the aspect ratio
|
||||
if (aspectRatio > 1) {
|
||||
// Landscape - use 1920 width
|
||||
return { width: 1920, height: Math.round(1920 / aspectRatio) };
|
||||
}
|
||||
// Portrait or square - use 1080 height
|
||||
return { width: Math.round(1080 * aspectRatio), height: 1080 };
|
||||
}
|
||||
|
||||
return { width: bestMatch.width, height: bestMatch.height };
|
||||
};
|
||||
|
||||
export const useEditorStore = create<EditorState>((set, get) => ({
|
||||
// Initial states
|
||||
isInitializing: true,
|
||||
isPanelsReady: false,
|
||||
canvasSize: { width: 1920, height: 1080 }, // Default 16:9 HD
|
||||
canvasMode: "preset" as CanvasMode,
|
||||
canvasPresets: DEFAULT_CANVAS_PRESETS,
|
||||
layoutGuide: {
|
||||
platform: null,
|
||||
},
|
||||
|
||||
// Actions
|
||||
setInitializing: (loading) => {
|
||||
set({ isInitializing: loading });
|
||||
},
|
||||
|
||||
setPanelsReady: (ready) => {
|
||||
set({ isPanelsReady: ready });
|
||||
},
|
||||
|
||||
initializeApp: async () => {
|
||||
console.log("Initializing video editor...");
|
||||
set({ isInitializing: true, isPanelsReady: false });
|
||||
|
||||
set({ isPanelsReady: true, isInitializing: false });
|
||||
console.log("Video editor ready");
|
||||
},
|
||||
|
||||
setCanvasSize: (size) => {
|
||||
set({ canvasSize: size, canvasMode: "preset" });
|
||||
},
|
||||
|
||||
setCanvasSizeToOriginal: (aspectRatio) => {
|
||||
const newCanvasSize = findBestCanvasPreset(aspectRatio);
|
||||
set({ canvasSize: newCanvasSize, canvasMode: "original" });
|
||||
},
|
||||
|
||||
setCanvasSizeFromAspectRatio: (aspectRatio) => {
|
||||
const newCanvasSize = findBestCanvasPreset(aspectRatio);
|
||||
set({ canvasSize: newCanvasSize, canvasMode: "custom" });
|
||||
},
|
||||
|
||||
setLayoutGuide: (settings) => {
|
||||
set((state) => ({
|
||||
export const useEditorStore = create<EditorState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
// Initial states
|
||||
isInitializing: true,
|
||||
isPanelsReady: false,
|
||||
canvasPresets: DEFAULT_CANVAS_PRESETS,
|
||||
layoutGuide: {
|
||||
...state.layoutGuide,
|
||||
...settings,
|
||||
platform: null,
|
||||
},
|
||||
}));
|
||||
},
|
||||
|
||||
toggleLayoutGuide: (platform) => {
|
||||
set((state) => ({
|
||||
layoutGuide: {
|
||||
platform: state.layoutGuide.platform === platform ? null : platform,
|
||||
// Actions
|
||||
setInitializing: (loading) => {
|
||||
set({ isInitializing: loading });
|
||||
},
|
||||
}));
|
||||
},
|
||||
}));
|
||||
|
||||
setPanelsReady: (ready) => {
|
||||
set({ isPanelsReady: ready });
|
||||
},
|
||||
|
||||
initializeApp: async () => {
|
||||
console.log("Initializing video editor...");
|
||||
set({ isInitializing: true, isPanelsReady: false });
|
||||
|
||||
set({ isPanelsReady: true, isInitializing: false });
|
||||
console.log("Video editor ready");
|
||||
},
|
||||
|
||||
setLayoutGuide: (settings) => {
|
||||
set((state) => ({
|
||||
layoutGuide: {
|
||||
...state.layoutGuide,
|
||||
...settings,
|
||||
},
|
||||
}));
|
||||
},
|
||||
|
||||
toggleLayoutGuide: (platform) => {
|
||||
set((state) => ({
|
||||
layoutGuide: {
|
||||
platform: state.layoutGuide.platform === platform ? null : platform,
|
||||
},
|
||||
}));
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: "editor-settings",
|
||||
partialize: (state) => ({
|
||||
layoutGuide: state.layoutGuide,
|
||||
}),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,22 @@ import { toast } from "sonner";
|
|||
import { useMediaStore } from "./media-store";
|
||||
import { useTimelineStore } from "./timeline-store";
|
||||
import { generateUUID } from "@/lib/utils";
|
||||
import { CanvasSize, CanvasMode } from "@/types/editor";
|
||||
|
||||
const DEFAULT_PROJECT: TProject = {
|
||||
id: generateUUID(),
|
||||
name: "Untitled",
|
||||
thumbnail: "",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
backgroundColor: "#000000",
|
||||
backgroundType: "color",
|
||||
blurIntensity: 8,
|
||||
bookmarks: [],
|
||||
fps: 30,
|
||||
canvasSize: { width: 1920, height: 1080 },
|
||||
canvasMode: "preset",
|
||||
};
|
||||
|
||||
interface ProjectStore {
|
||||
activeProject: TProject | null;
|
||||
|
|
@ -28,6 +44,7 @@ interface ProjectStore {
|
|||
options?: { backgroundColor?: string; blurIntensity?: number }
|
||||
) => Promise<void>;
|
||||
updateProjectFps: (fps: number) => Promise<void>;
|
||||
updateCanvasSize: (size: CanvasSize, mode: CanvasMode) => Promise<void>;
|
||||
|
||||
// Bookmark methods
|
||||
toggleBookmark: (time: number) => Promise<void>;
|
||||
|
|
@ -144,18 +161,7 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
|
|||
},
|
||||
|
||||
createNewProject: async (name: string) => {
|
||||
const newProject: TProject = {
|
||||
id: generateUUID(),
|
||||
name,
|
||||
thumbnail: "",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
backgroundColor: "#000000",
|
||||
backgroundType: "color",
|
||||
blurIntensity: 8,
|
||||
bookmarks: [],
|
||||
fps: 30,
|
||||
};
|
||||
const newProject: TProject = { ...DEFAULT_PROJECT, name };
|
||||
|
||||
set({ activeProject: newProject });
|
||||
|
||||
|
|
@ -428,6 +434,29 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
|
|||
}
|
||||
},
|
||||
|
||||
updateCanvasSize: async (size: CanvasSize, mode: CanvasMode) => {
|
||||
const { activeProject } = get();
|
||||
if (!activeProject) return;
|
||||
|
||||
const updatedProject = {
|
||||
...activeProject,
|
||||
canvasSize: size,
|
||||
canvasMode: mode,
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
try {
|
||||
await storageService.saveProject(updatedProject);
|
||||
set({ activeProject: updatedProject });
|
||||
await get().loadAllProjects(); // Refresh the list
|
||||
} catch (error) {
|
||||
console.error("Failed to update canvas size:", error);
|
||||
toast.error("Failed to update canvas size", {
|
||||
description: "Please try again",
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
getFilteredAndSortedProjects: (searchQuery: string, sortOption: string) => {
|
||||
const { savedProjects } = get();
|
||||
|
||||
|
|
|
|||
|
|
@ -10,17 +10,16 @@ import {
|
|||
ensureMainTrack,
|
||||
validateElementTrackCompatibility,
|
||||
} from "@/types/timeline";
|
||||
import { useEditorStore } from "./editor-store";
|
||||
import {
|
||||
useMediaStore,
|
||||
getMediaAspectRatio,
|
||||
type MediaItem,
|
||||
} from "./media-store";
|
||||
import { findBestCanvasPreset } from "@/lib/editor-utils";
|
||||
import { storageService } from "@/lib/storage/storage-service";
|
||||
import { useProjectStore } from "./project-store";
|
||||
import { generateUUID } from "@/lib/utils";
|
||||
import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants";
|
||||
import { toast } from "sonner";
|
||||
import { checkElementOverlaps, resolveElementOverlaps } from "@/lib/timeline";
|
||||
|
||||
// Helper function to manage element naming with suffixes
|
||||
|
|
@ -533,9 +532,10 @@ export const useTimelineStore = create<TimelineStore>((set, get) => {
|
|||
mediaItem &&
|
||||
(mediaItem.type === "image" || mediaItem.type === "video")
|
||||
) {
|
||||
const editorStore = useEditorStore.getState();
|
||||
editorStore.setCanvasSizeFromAspectRatio(
|
||||
getMediaAspectRatio(mediaItem)
|
||||
const projectStore = useProjectStore.getState();
|
||||
projectStore.updateCanvasSize(
|
||||
findBestCanvasPreset(getMediaAspectRatio(mediaItem)),
|
||||
"original"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
export type BackgroundType = "blur" | "mirror" | "color";
|
||||
|
||||
export type CanvasMode = "preset" | "original" | "custom";
|
||||
|
||||
export interface CanvasSize {
|
||||
width: number;
|
||||
height: number;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { CanvasSize } from "./editor";
|
||||
|
||||
export interface TProject {
|
||||
id: string;
|
||||
name: string;
|
||||
|
|
@ -10,4 +12,12 @@ export interface TProject {
|
|||
blurIntensity?: number; // in pixels (4, 8, 18)
|
||||
fps?: number;
|
||||
bookmarks?: number[];
|
||||
canvasSize: CanvasSize;
|
||||
/**
|
||||
* Tracks how the canvas size was set:
|
||||
* - "preset": User selected a standard preset (e.g. 16:9, 9:16)
|
||||
* - "original": Using the first media item's dimensions
|
||||
* - "custom": User set a custom aspect ratio or dimensions
|
||||
*/
|
||||
canvasMode: "preset" | "original" | "custom";
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue