176 lines
5.2 KiB
TypeScript
176 lines
5.2 KiB
TypeScript
/**
|
|
* useTransitionSettings Hook
|
|
*
|
|
* Resolves transition settings by cascading through three levels:
|
|
* Element → Project → Global (fallback)
|
|
*
|
|
* Video transitions always take precedence over CSS-based transitions.
|
|
*/
|
|
|
|
import { useMemo } from 'react';
|
|
import type {
|
|
GlobalTransitionDefaults,
|
|
ProjectTransitionSettings,
|
|
ElementTransitionSettings,
|
|
ResolvedTransitionSettings,
|
|
TransitionType,
|
|
EasingFunction,
|
|
DEFAULT_TRANSITION_SETTINGS,
|
|
} from '../types/transition';
|
|
|
|
export interface UseTransitionSettingsParams {
|
|
/** Global defaults (from global_transition_defaults table) */
|
|
globalDefaults: GlobalTransitionDefaults | null;
|
|
/** Project-level settings (from project_transition_settings table, environment-aware) */
|
|
projectSettings?: ProjectTransitionSettings | null;
|
|
/** Element-level settings (from ui_schema_json) */
|
|
elementSettings?: ElementTransitionSettings | null;
|
|
}
|
|
|
|
/**
|
|
* Default values used when no settings are available at any level
|
|
*/
|
|
const FALLBACK_DEFAULTS = {
|
|
type: 'fade' as TransitionType,
|
|
durationMs: 700,
|
|
easing: 'ease-in-out' as EasingFunction,
|
|
overlayColor: '#000000',
|
|
};
|
|
|
|
/**
|
|
* Hook to resolve transition settings with cascade:
|
|
* Element → Project → Global → Fallback defaults
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* // Project settings are fetched from project_transition_settings store
|
|
* // and converted using entityToProjectSettings() helper
|
|
* const transitionSettings = useTransitionSettings({
|
|
* globalDefaults,
|
|
* projectSettings, // from projectTransitionSettingsSlice (environment-aware)
|
|
* elementSettings: element?.transitionSettings,
|
|
* });
|
|
*
|
|
* // Use resolved settings
|
|
* if (transitionSettings.type === 'video') {
|
|
* // Play video transition
|
|
* } else {
|
|
* // Apply CSS transition with transitionSettings.durationMs and transitionSettings.easing
|
|
* }
|
|
* ```
|
|
*/
|
|
export function useTransitionSettings({
|
|
globalDefaults,
|
|
projectSettings,
|
|
elementSettings,
|
|
}: UseTransitionSettingsParams): ResolvedTransitionSettings {
|
|
return useMemo(() => {
|
|
// Video transitions always take precedence
|
|
if (elementSettings?.transitionVideoUrl) {
|
|
return {
|
|
type: 'video' as TransitionType,
|
|
durationMs:
|
|
elementSettings.transitionDurationMs ?? FALLBACK_DEFAULTS.durationMs,
|
|
easing: elementSettings.transitionEasing ?? FALLBACK_DEFAULTS.easing,
|
|
overlayColor:
|
|
elementSettings.transitionOverlayColor ??
|
|
projectSettings?.overlayColor ??
|
|
globalDefaults?.overlay_color ??
|
|
FALLBACK_DEFAULTS.overlayColor,
|
|
videoUrl: elementSettings.transitionVideoUrl,
|
|
reverseVideoUrl: elementSettings.reverseVideoUrl,
|
|
};
|
|
}
|
|
|
|
// Cascade: Element → Project → Global → Fallback
|
|
const type: TransitionType =
|
|
elementSettings?.transitionType ??
|
|
projectSettings?.transitionType ??
|
|
globalDefaults?.transition_type ??
|
|
FALLBACK_DEFAULTS.type;
|
|
|
|
const durationMs: number =
|
|
elementSettings?.transitionDurationMs ??
|
|
projectSettings?.durationMs ??
|
|
globalDefaults?.duration_ms ??
|
|
FALLBACK_DEFAULTS.durationMs;
|
|
|
|
const easing: EasingFunction =
|
|
elementSettings?.transitionEasing ??
|
|
projectSettings?.easing ??
|
|
globalDefaults?.easing ??
|
|
FALLBACK_DEFAULTS.easing;
|
|
|
|
const overlayColor: string =
|
|
elementSettings?.transitionOverlayColor ??
|
|
projectSettings?.overlayColor ??
|
|
globalDefaults?.overlay_color ??
|
|
FALLBACK_DEFAULTS.overlayColor;
|
|
|
|
return {
|
|
type,
|
|
durationMs,
|
|
easing,
|
|
overlayColor,
|
|
};
|
|
}, [globalDefaults, projectSettings, elementSettings]);
|
|
}
|
|
|
|
/**
|
|
* Resolve transition settings without React hook (for non-component contexts)
|
|
*/
|
|
export function resolveTransitionSettings({
|
|
globalDefaults,
|
|
projectSettings,
|
|
elementSettings,
|
|
}: UseTransitionSettingsParams): ResolvedTransitionSettings {
|
|
// Video transitions always take precedence
|
|
if (elementSettings?.transitionVideoUrl) {
|
|
return {
|
|
type: 'video' as TransitionType,
|
|
durationMs:
|
|
elementSettings.transitionDurationMs ?? FALLBACK_DEFAULTS.durationMs,
|
|
easing: elementSettings.transitionEasing ?? FALLBACK_DEFAULTS.easing,
|
|
overlayColor:
|
|
elementSettings.transitionOverlayColor ??
|
|
projectSettings?.overlayColor ??
|
|
globalDefaults?.overlay_color ??
|
|
FALLBACK_DEFAULTS.overlayColor,
|
|
videoUrl: elementSettings.transitionVideoUrl,
|
|
reverseVideoUrl: elementSettings.reverseVideoUrl,
|
|
};
|
|
}
|
|
|
|
// Cascade: Element → Project → Global → Fallback
|
|
const type: TransitionType =
|
|
elementSettings?.transitionType ??
|
|
projectSettings?.transitionType ??
|
|
globalDefaults?.transition_type ??
|
|
FALLBACK_DEFAULTS.type;
|
|
|
|
const durationMs: number =
|
|
elementSettings?.transitionDurationMs ??
|
|
projectSettings?.durationMs ??
|
|
globalDefaults?.duration_ms ??
|
|
FALLBACK_DEFAULTS.durationMs;
|
|
|
|
const easing: EasingFunction =
|
|
elementSettings?.transitionEasing ??
|
|
projectSettings?.easing ??
|
|
globalDefaults?.easing ??
|
|
FALLBACK_DEFAULTS.easing;
|
|
|
|
const overlayColor: string =
|
|
elementSettings?.transitionOverlayColor ??
|
|
projectSettings?.overlayColor ??
|
|
globalDefaults?.overlay_color ??
|
|
FALLBACK_DEFAULTS.overlayColor;
|
|
|
|
return {
|
|
type,
|
|
durationMs,
|
|
easing,
|
|
overlayColor,
|
|
};
|
|
}
|