Update
This commit is contained in:
@@ -5,12 +5,7 @@ import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sh
|
|||||||
import useLocalSettingsStore from '@/stores/localSettingsStore';
|
import useLocalSettingsStore from '@/stores/localSettingsStore';
|
||||||
import { SettingsIcon } from 'lucide-react';
|
import { SettingsIcon } from 'lucide-react';
|
||||||
|
|
||||||
interface EditNavSidebarProps {
|
export default function EditNavSidebar({ isOpen, onClose }) {
|
||||||
isOpen: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function EditNavSidebar({ isOpen, onClose }: EditNavSidebarProps) {
|
|
||||||
const { getSetting, setSetting } = useLocalSettingsStore();
|
const { getSetting, setSetting } = useLocalSettingsStore();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -7,12 +7,7 @@ import useMediaStore from '@/stores/MediaStore';
|
|||||||
import { Edit3 } from 'lucide-react';
|
import { Edit3 } from 'lucide-react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
interface EditSidebarProps {
|
export default function EditSidebar({ isOpen, onClose }) {
|
||||||
isOpen: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function EditSidebar({ isOpen, onClose }: EditSidebarProps) {
|
|
||||||
const {
|
const {
|
||||||
memes,
|
memes,
|
||||||
backgrounds,
|
backgrounds,
|
||||||
@@ -42,7 +37,7 @@ export default function EditSidebar({ isOpen, onClose }: EditSidebarProps) {
|
|||||||
}
|
}
|
||||||
}, [isOpen, activeTab, memes.length, backgrounds.length, isFetchingMemes, isFetchingBackgrounds]);
|
}, [isOpen, activeTab, memes.length, backgrounds.length, isFetchingMemes, isFetchingBackgrounds]);
|
||||||
|
|
||||||
const handleTabChange = (value: string) => {
|
const handleTabChange = (value) => {
|
||||||
setActiveTab(value);
|
setActiveTab(value);
|
||||||
if (value === 'memes' && memes.length === 0 && !isFetchingMemes) {
|
if (value === 'memes' && memes.length === 0 && !isFetchingMemes) {
|
||||||
fetchMemes();
|
fetchMemes();
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { useEffect, useLayoutEffect, useState } from 'react';
|
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||||
import { LAYOUT_CONSTANTS, calculateResponsiveScale } from '../utils/layout-constants';
|
import { LAYOUT_CONSTANTS, calculateResponsiveScale } from '../utils/layout-constants';
|
||||||
import VideoEditor from './canvas/video-editor';
|
import VideoEditor from './canvas/video-editor';
|
||||||
|
|
||||||
const useResponsiveCanvas = (maxWidth: number = 350) => {
|
const useResponsiveCanvas = (maxWidth = 350) => {
|
||||||
const [scale, setScale] = useState(() => calculateResponsiveScale(maxWidth));
|
const [scale, setScale] = useState(() => calculateResponsiveScale(maxWidth));
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
@@ -22,14 +22,14 @@ const useResponsiveCanvas = (maxWidth: number = 350) => {
|
|||||||
window.addEventListener('orientationchange', handleResize);
|
window.addEventListener('orientationchange', handleResize);
|
||||||
|
|
||||||
// ResizeObserver for more reliable detection
|
// ResizeObserver for more reliable detection
|
||||||
let resizeObserver: ResizeObserver | undefined;
|
let resizeObserver;
|
||||||
if (window.ResizeObserver) {
|
if (window.ResizeObserver) {
|
||||||
resizeObserver = new ResizeObserver(handleResize);
|
resizeObserver = new ResizeObserver(handleResize);
|
||||||
resizeObserver.observe(document.body);
|
resizeObserver.observe(document.body);
|
||||||
}
|
}
|
||||||
|
|
||||||
// MutationObserver for dev tools detection
|
// MutationObserver for dev tools detection
|
||||||
let mutationObserver: MutationObserver | undefined;
|
let mutationObserver;
|
||||||
if (window.MutationObserver) {
|
if (window.MutationObserver) {
|
||||||
mutationObserver = new MutationObserver(() => {
|
mutationObserver = new MutationObserver(() => {
|
||||||
setTimeout(handleResize, 50);
|
setTimeout(handleResize, 50);
|
||||||
@@ -51,11 +51,7 @@ const useResponsiveCanvas = (maxWidth: number = 350) => {
|
|||||||
return scale;
|
return scale;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface EditorCanvasProps {
|
const EditorCanvas = ({ maxWidth = 350 }) => {
|
||||||
maxWidth?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EditorCanvas: React.FC<EditorCanvasProps> = ({ maxWidth = 350 }) => {
|
|
||||||
const scale = useResponsiveCanvas(maxWidth);
|
const scale = useResponsiveCanvas(maxWidth);
|
||||||
const displayWidth = LAYOUT_CONSTANTS.CANVAS_WIDTH * scale;
|
const displayWidth = LAYOUT_CONSTANTS.CANVAS_WIDTH * scale;
|
||||||
const displayHeight = LAYOUT_CONSTANTS.CANVAS_HEIGHT * scale;
|
const displayHeight = LAYOUT_CONSTANTS.CANVAS_HEIGHT * scale;
|
||||||
67
resources/js/modules/editor/utils/layout-constants.js
Normal file
67
resources/js/modules/editor/utils/layout-constants.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
// Shared layout constants for the editor
|
||||||
|
export const LAYOUT_CONSTANTS = {
|
||||||
|
// Fixed element heights
|
||||||
|
HEADER_HEIGHT: 64, // Header with padding
|
||||||
|
CONTROLS_HEIGHT: 48, // Button controls height
|
||||||
|
|
||||||
|
// Canvas dimensions
|
||||||
|
CANVAS_WIDTH: 720,
|
||||||
|
CANVAS_HEIGHT: 1280,
|
||||||
|
CANVAS_ASPECT_RATIO: 720 / 1280, // 9:16 aspect ratio
|
||||||
|
|
||||||
|
// Spacing and padding
|
||||||
|
MIN_PADDING: 16, // Minimum padding around elements
|
||||||
|
CONTAINER_SPACING: 16, // Space between header, canvas, and controls (space-y-4 = 16px)
|
||||||
|
CONTAINER_VERTICAL_PADDING: 24, // py-6 = 24px top + 24px bottom
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate optimal maxWidth based on viewport dimensions
|
||||||
|
export const calculateOptimalMaxWidth = () => {
|
||||||
|
const viewportWidth = window.innerWidth;
|
||||||
|
const viewportHeight = window.innerHeight;
|
||||||
|
|
||||||
|
// Calculate total used height for fixed elements and spacing
|
||||||
|
const usedHeight =
|
||||||
|
LAYOUT_CONSTANTS.HEADER_HEIGHT +
|
||||||
|
LAYOUT_CONSTANTS.CONTROLS_HEIGHT +
|
||||||
|
(LAYOUT_CONSTANTS.CONTAINER_SPACING * 2) + // Space between elements
|
||||||
|
LAYOUT_CONSTANTS.CONTAINER_VERTICAL_PADDING + // py-6 padding
|
||||||
|
(LAYOUT_CONSTANTS.MIN_PADDING * 2); // Additional safety padding
|
||||||
|
|
||||||
|
const availableHeight = viewportHeight - usedHeight;
|
||||||
|
const availableWidth = viewportWidth - (LAYOUT_CONSTANTS.MIN_PADDING * 2);
|
||||||
|
|
||||||
|
// Calculate maxWidth based on both width and height constraints
|
||||||
|
const maxWidthFromHeight = availableHeight * LAYOUT_CONSTANTS.CANVAS_ASPECT_RATIO;
|
||||||
|
const maxWidthFromWidth = availableWidth;
|
||||||
|
|
||||||
|
// Use the smaller constraint to ensure everything fits
|
||||||
|
const optimalMaxWidth = Math.min(maxWidthFromHeight, maxWidthFromWidth);
|
||||||
|
|
||||||
|
// Ensure minimum viable size (but allow smaller if viewport is tiny)
|
||||||
|
const minViableWidth = 280;
|
||||||
|
|
||||||
|
return Math.max(optimalMaxWidth, Math.min(minViableWidth, availableWidth));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate responsive width based on optimal maxWidth
|
||||||
|
export const calculateResponsiveWidth = () => {
|
||||||
|
const optimalMaxWidth = calculateOptimalMaxWidth();
|
||||||
|
const viewportWidth = window.innerWidth;
|
||||||
|
const padding = LAYOUT_CONSTANTS.MIN_PADDING * 2;
|
||||||
|
const availableWidth = viewportWidth - padding;
|
||||||
|
|
||||||
|
return Math.min(availableWidth, optimalMaxWidth);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate responsive scale for canvas
|
||||||
|
export const calculateResponsiveScale = (maxWidth) => {
|
||||||
|
const viewportWidth = window.innerWidth;
|
||||||
|
const padding = LAYOUT_CONSTANTS.MIN_PADDING * 2;
|
||||||
|
const availableWidth = viewportWidth - padding;
|
||||||
|
const constrainedWidth = Math.min(availableWidth, maxWidth);
|
||||||
|
const scale = constrainedWidth / LAYOUT_CONSTANTS.CANVAS_WIDTH;
|
||||||
|
|
||||||
|
// Cap at 100% to avoid upscaling
|
||||||
|
return Math.min(scale, 1);
|
||||||
|
};
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
// Shared layout constants for the editor
|
|
||||||
export const LAYOUT_CONSTANTS = {
|
|
||||||
// Fixed element heights
|
|
||||||
HEADER_HEIGHT: 64, // Header with padding
|
|
||||||
CONTROLS_HEIGHT: 48, // Button controls height
|
|
||||||
|
|
||||||
// Canvas dimensions
|
|
||||||
CANVAS_WIDTH: 720,
|
|
||||||
CANVAS_HEIGHT: 1280,
|
|
||||||
CANVAS_ASPECT_RATIO: 720 / 1280, // 9:16 aspect ratio
|
|
||||||
|
|
||||||
// Spacing and padding
|
|
||||||
MIN_PADDING: 16, // Minimum padding around elements
|
|
||||||
CONTAINER_SPACING: 16, // Space between header, canvas, and controls (space-y-4 = 16px)
|
|
||||||
CONTAINER_VERTICAL_PADDING: 24, // py-6 = 24px top + 24px bottom
|
|
||||||
};
|
|
||||||
|
|
||||||
// Calculate optimal maxWidth based on viewport dimensions
|
|
||||||
export const calculateOptimalMaxWidth = (): number => {
|
|
||||||
const viewportWidth = window.innerWidth;
|
|
||||||
const viewportHeight = window.innerHeight;
|
|
||||||
|
|
||||||
// Calculate total used height for fixed elements and spacing
|
|
||||||
const usedHeight =
|
|
||||||
LAYOUT_CONSTANTS.HEADER_HEIGHT +
|
|
||||||
LAYOUT_CONSTANTS.CONTROLS_HEIGHT +
|
|
||||||
(LAYOUT_CONSTANTS.CONTAINER_SPACING * 2) + // Space between elements
|
|
||||||
LAYOUT_CONSTANTS.CONTAINER_VERTICAL_PADDING + // py-6 padding
|
|
||||||
(LAYOUT_CONSTANTS.MIN_PADDING * 2); // Additional safety padding
|
|
||||||
|
|
||||||
const availableHeight = viewportHeight - usedHeight;
|
|
||||||
const availableWidth = viewportWidth - (LAYOUT_CONSTANTS.MIN_PADDING * 2);
|
|
||||||
|
|
||||||
// Calculate maxWidth based on both width and height constraints
|
|
||||||
const maxWidthFromHeight = availableHeight * LAYOUT_CONSTANTS.CANVAS_ASPECT_RATIO;
|
|
||||||
const maxWidthFromWidth = availableWidth;
|
|
||||||
|
|
||||||
// Use the smaller constraint to ensure everything fits
|
|
||||||
const optimalMaxWidth = Math.min(maxWidthFromHeight, maxWidthFromWidth);
|
|
||||||
|
|
||||||
// Ensure minimum viable size (but allow smaller if viewport is tiny)
|
|
||||||
const minViableWidth = 280;
|
|
||||||
|
|
||||||
return Math.max(optimalMaxWidth, Math.min(minViableWidth, availableWidth));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Calculate responsive width based on optimal maxWidth
|
|
||||||
export const calculateResponsiveWidth = (): number => {
|
|
||||||
const optimalMaxWidth = calculateOptimalMaxWidth();
|
|
||||||
const viewportWidth = window.innerWidth;
|
|
||||||
const padding = LAYOUT_CONSTANTS.MIN_PADDING * 2;
|
|
||||||
const availableWidth = viewportWidth - padding;
|
|
||||||
|
|
||||||
return Math.min(availableWidth, optimalMaxWidth);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Calculate responsive scale for canvas
|
|
||||||
export const calculateResponsiveScale = (maxWidth: number): number => {
|
|
||||||
const viewportWidth = window.innerWidth;
|
|
||||||
const padding = LAYOUT_CONSTANTS.MIN_PADDING * 2;
|
|
||||||
const availableWidth = viewportWidth - padding;
|
|
||||||
const constrainedWidth = Math.min(availableWidth, maxWidth);
|
|
||||||
const scale = constrainedWidth / LAYOUT_CONSTANTS.CANVAS_WIDTH;
|
|
||||||
|
|
||||||
// Cap at 100% to avoid upscaling
|
|
||||||
return Math.min(scale, 1);
|
|
||||||
};
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import Editor from '@/modules/editor/editor';
|
import Editor from '@/modules/editor/editor.jsx';
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
83
resources/js/stores/localSettingsStore.js
Normal file
83
resources/js/stores/localSettingsStore.js
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import { mountStoreDevtool } from 'simple-zustand-devtools';
|
||||||
|
import { create } from 'zustand';
|
||||||
|
import { persist } from 'zustand/middleware';
|
||||||
|
|
||||||
|
// Immutable default settings
|
||||||
|
const defaultSettings = {
|
||||||
|
genAlphaSlang: false,
|
||||||
|
// Add more settings here
|
||||||
|
};
|
||||||
|
|
||||||
|
const useLocalSettingsStore = create(
|
||||||
|
persist(
|
||||||
|
(set, get) => ({
|
||||||
|
settings: { ...defaultSettings }, // clone to avoid shared reference
|
||||||
|
|
||||||
|
// Get a setting by key
|
||||||
|
getSetting: (key) => {
|
||||||
|
const currentSettings = get().settings;
|
||||||
|
return key in currentSettings ? currentSettings[key] : (defaultSettings[key] ?? null);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Set or update a specific setting
|
||||||
|
setSetting: (key, value) => {
|
||||||
|
console.log(`Updating setting ${key} to`, value); // <-- Debug log
|
||||||
|
set((state) => ({
|
||||||
|
settings: {
|
||||||
|
...state.settings,
|
||||||
|
[key]: value,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
// Reset all settings to default
|
||||||
|
resetSettings: () => {
|
||||||
|
set({ settings: { ...defaultSettings } }); // create new object reference
|
||||||
|
},
|
||||||
|
|
||||||
|
// Reset a specific setting to its default
|
||||||
|
resetSetting: (key) => {
|
||||||
|
set((state) => ({
|
||||||
|
settings: {
|
||||||
|
...state.settings,
|
||||||
|
[key]: defaultSettings[key],
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
// Merge default settings with current ones (useful on load)
|
||||||
|
initializeSettings: () => {
|
||||||
|
set((state) => ({
|
||||||
|
settings: {
|
||||||
|
...defaultSettings,
|
||||||
|
...state.settings,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: 'local-settings-storage',
|
||||||
|
// Ensure only the 'settings' key is stored
|
||||||
|
partialize: (state) => ({ settings: state.settings }),
|
||||||
|
// Explicit localStorage usage (for compatibility)
|
||||||
|
storage: {
|
||||||
|
getItem: (name) => {
|
||||||
|
const stored = localStorage.getItem(name);
|
||||||
|
return stored ? JSON.parse(stored) : null;
|
||||||
|
},
|
||||||
|
setItem: (name, value) => {
|
||||||
|
localStorage.setItem(name, JSON.stringify(value));
|
||||||
|
},
|
||||||
|
removeItem: (name) => {
|
||||||
|
localStorage.removeItem(name);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (import.meta.env.APP_ENV === 'local') {
|
||||||
|
mountStoreDevtool('LocalSettingsStore', useLocalSettingsStore);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useLocalSettingsStore;
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
import { create } from 'zustand';
|
|
||||||
import { persist } from 'zustand/middleware';
|
|
||||||
|
|
||||||
// Immutable default settings
|
|
||||||
const defaultSettings = {
|
|
||||||
genAlphaSlang: false,
|
|
||||||
// Add more settings here
|
|
||||||
};
|
|
||||||
|
|
||||||
const useLocalSettingsStore = create(
|
|
||||||
persist(
|
|
||||||
(set, get) => ({
|
|
||||||
settings: { ...defaultSettings }, // clone to avoid shared reference
|
|
||||||
|
|
||||||
// Get a setting by key
|
|
||||||
getSetting: (key) => {
|
|
||||||
const currentSettings = get().settings;
|
|
||||||
return key in currentSettings ? currentSettings[key] : defaultSettings[key] ?? null;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Set or update a specific setting
|
|
||||||
setSetting: (key, value) => {
|
|
||||||
console.log(`Updating setting ${key} to`, value); // <-- Debug log
|
|
||||||
set((state) => ({
|
|
||||||
settings: {
|
|
||||||
...state.settings,
|
|
||||||
[key]: value,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
// Reset all settings to default
|
|
||||||
resetSettings: () => {
|
|
||||||
set({ settings: { ...defaultSettings } }); // create new object reference
|
|
||||||
},
|
|
||||||
|
|
||||||
// Reset a specific setting to its default
|
|
||||||
resetSetting: (key) => {
|
|
||||||
set((state) => ({
|
|
||||||
settings: {
|
|
||||||
...state.settings,
|
|
||||||
[key]: defaultSettings[key],
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
// Merge default settings with current ones (useful on load)
|
|
||||||
initializeSettings: () => {
|
|
||||||
set((state) => ({
|
|
||||||
settings: {
|
|
||||||
...defaultSettings,
|
|
||||||
...state.settings,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
name: 'local-settings-storage',
|
|
||||||
// Ensure only the 'settings' key is stored
|
|
||||||
partialize: (state) => ({ settings: state.settings }),
|
|
||||||
// Explicit localStorage usage (for compatibility)
|
|
||||||
storage: {
|
|
||||||
getItem: (name) => {
|
|
||||||
const stored = localStorage.getItem(name);
|
|
||||||
return stored ? JSON.parse(stored) : null;
|
|
||||||
},
|
|
||||||
setItem: (name, value) => {
|
|
||||||
localStorage.setItem(name, JSON.stringify(value));
|
|
||||||
},
|
|
||||||
removeItem: (name) => {
|
|
||||||
localStorage.removeItem(name);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (import.meta.env.APP_ENV === "local") {
|
|
||||||
mountStoreDevtool("LocalSettingsStore", useLocalSettingsStore);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default useLocalSettingsStore;
|
|
||||||
Reference in New Issue
Block a user