summaryrefslogtreecommitdiff
path: root/apps/web/src/constants
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/constants')
-rw-r--r--apps/web/src/constants/actions.ts300
-rw-r--r--apps/web/src/constants/font-constants.ts158
-rw-r--r--apps/web/src/constants/timeline-constants.ts110
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;
}