diff options
Diffstat (limited to 'apps/web/src/constants')
| -rw-r--r-- | apps/web/src/constants/actions.ts | 300 | ||||
| -rw-r--r-- | apps/web/src/constants/font-constants.ts | 158 | ||||
| -rw-r--r-- | apps/web/src/constants/timeline-constants.ts | 110 |
3 files changed, 284 insertions, 284 deletions
diff --git a/apps/web/src/constants/actions.ts b/apps/web/src/constants/actions.ts index 5f1883f1..644bb0ec 100644 --- a/apps/web/src/constants/actions.ts +++ b/apps/web/src/constants/actions.ts @@ -3,49 +3,49 @@ */ import { - useEffect, - useRef, - useState, - useCallback, - MutableRefObject, + useEffect, + useRef, + useState, + useCallback, + MutableRefObject, } from "react"; // Simple event emitter for action changes class ActionEmitter { - private listeners: Array<(actions: Action[]) => void> = []; + private listeners: Array<(actions: Action[]) => void> = []; - subscribe(listener: (actions: Action[]) => void) { - this.listeners.push(listener); - return () => { - this.listeners = this.listeners.filter((l) => l !== listener); - }; - } + subscribe(listener: (actions: Action[]) => void) { + this.listeners.push(listener); + return () => { + this.listeners = this.listeners.filter((l) => l !== listener); + }; + } - emit(actions: Action[]) { - this.listeners.forEach((listener) => listener(actions)); - } + emit(actions: Action[]) { + this.listeners.forEach((listener) => listener(actions)); + } } const actionEmitter = new ActionEmitter(); export type Action = - | "toggle-play" // Toggle play/pause state - | "stop-playback" // Stop playback - | "seek-forward" // Seek forward in playback - | "seek-backward" // Seek backward in playback - | "frame-step-forward" // Step forward by one frame - | "frame-step-backward" // Step backward by one frame - | "jump-forward" // Jump forward by 5 seconds - | "jump-backward" // Jump backward by 5 seconds - | "goto-start" // Go to timeline start - | "goto-end" // Go to timeline end - | "split-element" // Split element at current time - | "delete-selected" // Delete selected elements - | "select-all" // Select all elements - | "duplicate-selected" // Duplicate selected element - | "toggle-snapping" // Toggle snapping - | "undo" // Undo last action - | "redo"; // Redo last undone action + | "toggle-play" // Toggle play/pause state + | "stop-playback" // Stop playback + | "seek-forward" // Seek forward in playback + | "seek-backward" // Seek backward in playback + | "frame-step-forward" // Step forward by one frame + | "frame-step-backward" // Step backward by one frame + | "jump-forward" // Jump forward by 5 seconds + | "jump-backward" // Jump backward by 5 seconds + | "goto-start" // Go to timeline start + | "goto-end" // Go to timeline end + | "split-element" // Split element at current time + | "delete-selected" // Delete selected elements + | "select-all" // Select all elements + | "duplicate-selected" // Duplicate selected element + | "toggle-snapping" // Toggle snapping + | "undo" // Undo last action + | "redo"; // Redo last undone action /** * Defines the arguments, if present for a given type that is required to be passed on @@ -59,14 +59,14 @@ export type Action = * will know if you got something wrong if there is a type error in this file */ type ActionArgsMap = { - "seek-forward": { seconds: number } | undefined; // Args needed for seeking forward (default: 1) - "seek-backward": { seconds: number } | undefined; // Args needed for seeking backward (default: 1) - "jump-forward": { seconds: number } | undefined; // Args needed for jumping forward (default: 5) - "jump-backward": { seconds: number } | undefined; // Args needed for jumping backward (default: 5) + "seek-forward": { seconds: number } | undefined; // Args needed for seeking forward (default: 1) + "seek-backward": { seconds: number } | undefined; // Args needed for seeking backward (default: 1) + "jump-forward": { seconds: number } | undefined; // Args needed for jumping forward (default: 5) + "jump-backward": { seconds: number } | undefined; // Args needed for jumping backward (default: 5) }; type KeysWithValueUndefined<T> = { - [K in keyof T]: undefined extends T[K] ? K : never; + [K in keyof T]: undefined extends T[K] ? K : never; }[keyof T]; /** @@ -79,8 +79,8 @@ export type ActionWithArgs = keyof ActionArgsMap; */ export type ActionWithOptionalArgs = - | ActionWithNoArgs - | KeysWithValueUndefined<ActionArgsMap>; + | ActionWithNoArgs + | KeysWithValueUndefined<ActionArgsMap>; /** * Actions which do not require arguments for their invocation @@ -91,18 +91,18 @@ export type ActionWithNoArgs = Exclude<Action, ActionWithArgs>; * Resolves the argument type for a given Action */ type ArgOfHoppAction<A extends Action> = A extends ActionWithArgs - ? ActionArgsMap[A] - : undefined; + ? ActionArgsMap[A] + : undefined; /** * Resolves the action function for a given Action, used by action handler function defs */ type ActionFunc<A extends Action> = A extends ActionWithArgs - ? (arg: ArgOfHoppAction<A>, trigger?: InvocationTriggers) => void - : (_?: undefined, trigger?: InvocationTriggers) => void; + ? (arg: ArgOfHoppAction<A>, trigger?: InvocationTriggers) => void + : (_?: undefined, trigger?: InvocationTriggers) => void; type BoundActionList = { - [A in Action]?: Array<ActionFunc<A>>; + [A in Action]?: Array<ActionFunc<A>>; }; const boundActions: BoundActionList = {}; @@ -110,34 +110,34 @@ const boundActions: BoundActionList = {}; let currentActiveActions: Action[] = []; function updateActiveActions() { - const newActions = Object.keys(boundActions) as Action[]; - currentActiveActions = newActions; - actionEmitter.emit(newActions); + const newActions = Object.keys(boundActions) as Action[]; + currentActiveActions = newActions; + actionEmitter.emit(newActions); } export function bindAction<A extends Action>( - action: A, - handler: ActionFunc<A> + action: A, + handler: ActionFunc<A>, ) { - if (boundActions[action]) { - boundActions[action]?.push(handler); - } else { - // 'any' assertion because TypeScript doesn't seem to be able to figure out the links. - boundActions[action] = [handler] as any; - } + if (boundActions[action]) { + boundActions[action]?.push(handler); + } else { + // 'any' assertion because TypeScript doesn't seem to be able to figure out the links. + boundActions[action] = [handler] as any; + } - updateActiveActions(); + updateActiveActions(); } export type InvocationTriggers = "keypress" | "mouseclick"; type InvokeActionFunc = { - ( - action: ActionWithOptionalArgs, - args?: undefined, - trigger?: InvocationTriggers - ): void; - <A extends ActionWithArgs>(action: A, args: ActionArgsMap[A]): void; + ( + action: ActionWithOptionalArgs, + args?: undefined, + trigger?: InvocationTriggers, + ): void; + <A extends ActionWithArgs>(action: A, args: ActionArgsMap[A]): void; }; /** @@ -148,27 +148,27 @@ type InvokeActionFunc = { * @param trigger Optionally supply the trigger that invoked the action (keypress/mouseclick) */ export const invokeAction: InvokeActionFunc = <A extends Action>( - action: A, - args?: ArgOfHoppAction<A>, - trigger?: InvocationTriggers + action: A, + args?: ArgOfHoppAction<A>, + trigger?: InvocationTriggers, ) => { - boundActions[action]?.forEach((handler) => (handler as any)(args, trigger)); + boundActions[action]?.forEach((handler) => (handler as any)(args, trigger)); }; export function unbindAction<A extends Action>( - action: A, - handler: ActionFunc<A> + action: A, + handler: ActionFunc<A>, ) { - // 'any' assertion because TypeScript doesn't seem to be able to figure out the links. - boundActions[action] = boundActions[action]?.filter( - (x) => x !== handler - ) as any; + // 'any' assertion because TypeScript doesn't seem to be able to figure out the links. + boundActions[action] = boundActions[action]?.filter( + (x) => x !== handler, + ) as any; - if (boundActions[action]?.length === 0) { - delete boundActions[action]; - } + if (boundActions[action]?.length === 0) { + delete boundActions[action]; + } - updateActiveActions(); + updateActiveActions(); } /** @@ -177,7 +177,7 @@ export function unbindAction<A extends Action>( * @param action The action to check */ export function isActionBound(action: Action): boolean { - return !!boundActions[action]; + return !!boundActions[action]; } /** @@ -189,65 +189,65 @@ export function isActionBound(action: Action): boolean { * @param isActive A ref that indicates whether the action is active */ export function useActionHandler<A extends Action>( - action: A, - handler: ActionFunc<A>, - isActive: MutableRefObject<boolean> | boolean | undefined = undefined + action: A, + handler: ActionFunc<A>, + isActive: MutableRefObject<boolean> | boolean | undefined = undefined, ) { - const handlerRef = useRef(handler); - const [isBound, setIsBound] = useState(false); + const handlerRef = useRef(handler); + const [isBound, setIsBound] = useState(false); - // Update handler ref when handler changes - useEffect(() => { - handlerRef.current = handler; - }, [handler]); + // Update handler ref when handler changes + useEffect(() => { + handlerRef.current = handler; + }, [handler]); - // Create a stable handler wrapper - const stableHandler = useCallback( - (args: any, trigger?: InvocationTriggers) => { - (handlerRef.current as any)(args, trigger); - }, - [] - ) as ActionFunc<A>; + // Create a stable handler wrapper + const stableHandler = useCallback( + (args: any, trigger?: InvocationTriggers) => { + (handlerRef.current as any)(args, trigger); + }, + [], + ) as ActionFunc<A>; - useEffect(() => { - const shouldBind = - isActive === undefined || - (typeof isActive === "boolean" ? isActive : isActive.current); + useEffect(() => { + const shouldBind = + isActive === undefined || + (typeof isActive === "boolean" ? isActive : isActive.current); - if (shouldBind && !isBound) { - bindAction(action, stableHandler); - setIsBound(true); - } else if (!shouldBind && isBound) { - unbindAction(action, stableHandler); - setIsBound(false); - } + if (shouldBind && !isBound) { + bindAction(action, stableHandler); + setIsBound(true); + } else if (!shouldBind && isBound) { + unbindAction(action, stableHandler); + setIsBound(false); + } - return () => { - if (isBound) { - unbindAction(action, stableHandler); - setIsBound(false); - } - }; - }, [action, stableHandler, isActive, isBound]); + return () => { + if (isBound) { + unbindAction(action, stableHandler); + setIsBound(false); + } + }; + }, [action, stableHandler, isActive, isBound]); - // Handle ref-based isActive changes - useEffect(() => { - if (isActive && typeof isActive === "object" && "current" in isActive) { - // Poll for ref changes - const interval = setInterval(() => { - const shouldBind = isActive.current; - if (shouldBind !== isBound) { - if (shouldBind) { - bindAction(action, stableHandler); - } else { - unbindAction(action, stableHandler); - } - setIsBound(shouldBind); - } - }, 100); - return () => clearInterval(interval); - } - }, [action, stableHandler, isActive, isBound]); + // Handle ref-based isActive changes + useEffect(() => { + if (isActive && typeof isActive === "object" && "current" in isActive) { + // Poll for ref changes + const interval = setInterval(() => { + const shouldBind = isActive.current; + if (shouldBind !== isBound) { + if (shouldBind) { + bindAction(action, stableHandler); + } else { + unbindAction(action, stableHandler); + } + setIsBound(shouldBind); + } + }, 100); + return () => clearInterval(interval); + } + }, [action, stableHandler, isActive, isBound]); } /** @@ -255,18 +255,18 @@ export function useActionHandler<A extends Action>( * and re-renders when the list changes */ export function useActiveActions(): Action[] { - const [activeActions, setActiveActions] = useState<Action[]>([]); + const [activeActions, setActiveActions] = useState<Action[]>([]); - useEffect(() => { - // Set initial value - setActiveActions(currentActiveActions); + useEffect(() => { + // Set initial value + setActiveActions(currentActiveActions); - // Subscribe to changes - const unsubscribe = actionEmitter.subscribe(setActiveActions); - return unsubscribe; - }, []); + // Subscribe to changes + const unsubscribe = actionEmitter.subscribe(setActiveActions); + return unsubscribe; + }, []); - return activeActions; + return activeActions; } /** @@ -274,20 +274,20 @@ export function useActiveActions(): Action[] { * and re-renders when the binding state changes */ export function useIsActionBound(action: Action): boolean { - const [isBound, setIsBound] = useState(() => isActionBound(action)); + const [isBound, setIsBound] = useState(() => isActionBound(action)); - useEffect(() => { - const updateBoundState = () => { - setIsBound(isActionBound(action)); - }; + useEffect(() => { + const updateBoundState = () => { + setIsBound(isActionBound(action)); + }; - // Set initial value - updateBoundState(); + // Set initial value + updateBoundState(); - // Subscribe to changes - const unsubscribe = actionEmitter.subscribe(updateBoundState); - return unsubscribe; - }, [action]); + // Subscribe to changes + const unsubscribe = actionEmitter.subscribe(updateBoundState); + return unsubscribe; + }, [action]); - return isBound; + return isBound; } diff --git a/apps/web/src/constants/font-constants.ts b/apps/web/src/constants/font-constants.ts index bfddc182..46f84848 100644 --- a/apps/web/src/constants/font-constants.ts +++ b/apps/web/src/constants/font-constants.ts @@ -1,79 +1,79 @@ -export interface FontOption {
- value: string;
- label: string;
- category: "system" | "google" | "custom";
- weights?: number[];
- hasClassName?: boolean;
-}
-
-export const FONT_OPTIONS: FontOption[] = [
- // System fonts (always available)
- { value: "Arial", label: "Arial", category: "system", hasClassName: false },
- {
- value: "Helvetica",
- label: "Helvetica",
- category: "system",
- hasClassName: false,
- },
- {
- value: "Times New Roman",
- label: "Times New Roman",
- category: "system",
- hasClassName: false,
- },
- {
- value: "Georgia",
- label: "Georgia",
- category: "system",
- hasClassName: false,
- },
-
- // Google Fonts (loaded in layout.tsx)
- {
- value: "Inter",
- label: "Inter",
- category: "google",
- weights: [400, 700],
- hasClassName: true,
- },
- {
- value: "Roboto",
- label: "Roboto",
- category: "google",
- weights: [400, 700],
- hasClassName: true,
- },
- {
- value: "Open Sans",
- label: "Open Sans",
- category: "google",
- hasClassName: true,
- },
- {
- value: "Playfair Display",
- label: "Playfair Display",
- category: "google",
- hasClassName: true,
- },
- {
- value: "Comic Neue",
- label: "Comic Neue",
- category: "google",
- hasClassName: false,
- },
-] as const;
-
-export const DEFAULT_FONT = "Arial";
-
-// Type-safe font family union
-export type FontFamily = (typeof FONT_OPTIONS)[number]["value"];
-
-// Helper functions
-export const getFontByValue = (value: string): FontOption | undefined =>
- FONT_OPTIONS.find((font) => font.value === value);
-
-export const getGoogleFonts = (): FontOption[] =>
- FONT_OPTIONS.filter((font) => font.category === "google");
-
-export const getSystemFonts = (): FontOption[] =>
- FONT_OPTIONS.filter((font) => font.category === "system");
+export interface FontOption { + value: string; + label: string; + category: "system" | "google" | "custom"; + weights?: number[]; + hasClassName?: boolean; +} + +export const FONT_OPTIONS: FontOption[] = [ + // System fonts (always available) + { value: "Arial", label: "Arial", category: "system", hasClassName: false }, + { + value: "Helvetica", + label: "Helvetica", + category: "system", + hasClassName: false, + }, + { + value: "Times New Roman", + label: "Times New Roman", + category: "system", + hasClassName: false, + }, + { + value: "Georgia", + label: "Georgia", + category: "system", + hasClassName: false, + }, + + // Google Fonts (loaded in layout.tsx) + { + value: "Inter", + label: "Inter", + category: "google", + weights: [400, 700], + hasClassName: true, + }, + { + value: "Roboto", + label: "Roboto", + category: "google", + weights: [400, 700], + hasClassName: true, + }, + { + value: "Open Sans", + label: "Open Sans", + category: "google", + hasClassName: true, + }, + { + value: "Playfair Display", + label: "Playfair Display", + category: "google", + hasClassName: true, + }, + { + value: "Comic Neue", + label: "Comic Neue", + category: "google", + hasClassName: false, + }, +] as const; + +export const DEFAULT_FONT = "Arial"; + +// Type-safe font family union +export type FontFamily = (typeof FONT_OPTIONS)[number]["value"]; + +// Helper functions +export const getFontByValue = (value: string): FontOption | undefined => + FONT_OPTIONS.find((font) => font.value === value); + +export const getGoogleFonts = (): FontOption[] => + FONT_OPTIONS.filter((font) => font.category === "google"); + +export const getSystemFonts = (): FontOption[] => + FONT_OPTIONS.filter((font) => font.category === "system"); diff --git a/apps/web/src/constants/timeline-constants.ts b/apps/web/src/constants/timeline-constants.ts index 8c76a2e8..f670a319 100644 --- a/apps/web/src/constants/timeline-constants.ts +++ b/apps/web/src/constants/timeline-constants.ts @@ -2,106 +2,106 @@ import type { TrackType } from "@/types/timeline"; // Track color definitions export const TRACK_COLORS: Record< - TrackType, - { solid: string; background: string; border: string } + TrackType, + { solid: string; background: string; border: string } > = { - media: { - solid: "bg-blue-500", - background: "bg-blue-500/20", - border: "border-white/80", - }, - text: { - solid: "bg-[#9C4937]", - background: "bg-[#9C4937]", - border: "border-white/80", - }, - audio: { - solid: "bg-green-500", - background: "bg-green-500/20", - border: "border-white/80", - }, + media: { + solid: "bg-blue-500", + background: "bg-blue-500/20", + border: "border-white/80", + }, + text: { + solid: "bg-[#9C4937]", + background: "bg-[#9C4937]", + border: "border-white/80", + }, + audio: { + solid: "bg-green-500", + background: "bg-green-500/20", + border: "border-white/80", + }, } as const; // Utility functions export function getTrackColors(type: TrackType) { - return TRACK_COLORS[type]; + return TRACK_COLORS[type]; } export function getTrackElementClasses(type: TrackType) { - const colors = getTrackColors(type); - return `${colors.background} ${colors.border}`; + const colors = getTrackColors(type); + return `${colors.background} ${colors.border}`; } // Track height definitions export const TRACK_HEIGHTS: Record<TrackType, number> = { - media: 65, - text: 25, - audio: 50, + media: 65, + text: 25, + audio: 50, } as const; // Utility function for track heights export function getTrackHeight(type: TrackType): number { - return TRACK_HEIGHTS[type]; + return TRACK_HEIGHTS[type]; } // Calculate cumulative height up to (but not including) a track index export function getCumulativeHeightBefore( - tracks: Array<{ type: TrackType }>, - trackIndex: number + tracks: Array<{ type: TrackType }>, + trackIndex: number, ): number { - const GAP = 4; // 4px gap between tracks (equivalent to Tailwind's gap-1) - return tracks - .slice(0, trackIndex) - .reduce((sum, track) => sum + getTrackHeight(track.type) + GAP, 0); + const GAP = 4; // 4px gap between tracks (equivalent to Tailwind's gap-1) + return tracks + .slice(0, trackIndex) + .reduce((sum, track) => sum + getTrackHeight(track.type) + GAP, 0); } // Calculate total height of all tracks export function getTotalTracksHeight( - tracks: Array<{ type: TrackType }> + tracks: Array<{ type: TrackType }>, ): number { - const GAP = 4; // 4px gap between tracks (equivalent to Tailwind's gap-1) - const tracksHeight = tracks.reduce( - (sum, track) => sum + getTrackHeight(track.type), - 0 - ); - const gapsHeight = Math.max(0, tracks.length - 1) * GAP; // n-1 gaps for n tracks - return tracksHeight + gapsHeight; + const GAP = 4; // 4px gap between tracks (equivalent to Tailwind's gap-1) + const tracksHeight = tracks.reduce( + (sum, track) => sum + getTrackHeight(track.type), + 0, + ); + const gapsHeight = Math.max(0, tracks.length - 1) * GAP; // n-1 gaps for n tracks + return tracksHeight + gapsHeight; } // Other timeline constants export const TIMELINE_CONSTANTS = { - ELEMENT_MIN_WIDTH: 80, - PIXELS_PER_SECOND: 50, - TRACK_HEIGHT: 60, // Default fallback - DEFAULT_TEXT_DURATION: 5, - DEFAULT_IMAGE_DURATION: 5, - ZOOM_LEVELS: [0.25, 0.5, 1, 1.5, 2, 3, 4], + ELEMENT_MIN_WIDTH: 80, + PIXELS_PER_SECOND: 50, + TRACK_HEIGHT: 60, // Default fallback + DEFAULT_TEXT_DURATION: 5, + DEFAULT_IMAGE_DURATION: 5, + ZOOM_LEVELS: [0.25, 0.5, 1, 1.5, 2, 3, 4], } as const; // FPS presets for project settings export const FPS_PRESETS = [ - { value: "24", label: "24 fps (Film)" }, - { value: "25", label: "25 fps (PAL)" }, - { value: "30", label: "30 fps (NTSC)" }, - { value: "60", label: "60 fps (High)" }, - { value: "120", label: "120 fps (Slow-mo)" }, + { value: "24", label: "24 fps (Film)" }, + { value: "25", label: "25 fps (PAL)" }, + { value: "30", label: "30 fps (NTSC)" }, + { value: "60", label: "60 fps (High)" }, + { value: "120", label: "120 fps (Slow-mo)" }, ] as const; // Frame snapping utilities export function timeToFrame(time: number, fps: number): number { - return Math.round(time * fps); + return Math.round(time * fps); } export function frameToTime(frame: number, fps: number): number { - return frame / fps; + return frame / fps; } export function snapTimeToFrame(time: number, fps: number): number { - if (fps <= 0) return time; // Fallback for invalid FPS - const frame = timeToFrame(time, fps); - return frameToTime(frame, fps); + if (fps <= 0) return time; // Fallback for invalid FPS + const frame = timeToFrame(time, fps); + return frameToTime(frame, fps); } export function getFrameDuration(fps: number): number { - return 1 / fps; + return 1 / fps; } |
