UNPKG

kinetic-slider

Version:

A WebGL-powered kinetic slider component using PIXI.js

532 lines (528 loc) 20.4 kB
'use strict'; var react = require('react'); require('pixi-filters'); require('../managers/ShaderResourceManager.cjs'); require('../filters/advancedBloomFilter.cjs'); require('pixi.js'); require('../filters/blurFilter.cjs'); require('../filters/colorMatrixFilter.cjs'); require('../filters/dropShadowFilter.cjs'); require('gsap'); require('../filters/tiltShiftFilter.cjs'); var FilterFactory = require('../filters/FilterFactory.cjs'); var RenderScheduler = require('../managers/RenderScheduler.cjs'); var UpdateTypes = require('../managers/UpdateTypes.cjs'); const isDevelopment = typeof undefined !== "undefined" && true ? false : false; const FILTER_COORDINATION_EVENT = "kinetic-slider:filter-update"; const useFilters = ({ pixi, props, resourceManager }) => { if (isDevelopment) { console.log("[useFilters] Hook called with:", { hasApp: !!pixi.app.current, hasStage: !!pixi.app.current?.stage, slidesCount: pixi.slides.current?.length, textContainersCount: pixi.textContainers.current?.length, hasResourceManager: !!resourceManager, imageFilters: props.imageFilters, textFilters: props.textFilters }); } const filterMapRef = react.useRef({}); const originalConfigsRef = react.useRef({ image: [], text: [] }); const filtersInitializedRef = react.useRef(false); const filtersActiveRef = react.useRef(false); react.useRef(null); react.useRef(true); const applyFiltersToTargetRef = react.useRef(null); const applyFiltersToObjectsRef = react.useRef(null); react.useRef({ pendingFilters: [], pendingObjects: [] }); const batchQueueRef = react.useRef([]); const batchConfigRef = react.useRef({ bufferMs: 16, // One frame at 60fps - will be adjusted dynamically maxBatchSize: 10 // Will be adjusted dynamically }); const batchTimeoutRef = react.useRef(null); const performanceMetricsRef = react.useRef({ lastProcessTime: 0, averageProcessTime: 0, updateCount: 0, bufferAdjustmentCounter: 0, totalProcessTime: 0 }); react.useRef(/* @__PURE__ */ new Map()); const processBatchQueueRef = react.useRef(() => { const start = performance.now(); const queue = [...batchQueueRef.current]; batchQueueRef.current = []; if (queue.length === 0) return; if (isDevelopment) { console.log(`[useFilters] Processing batch of ${queue.length} filter updates`); } const updates = {}; const priorityOrder = { "low": 0, "normal": 1, "high": 2, "critical": 3 }; queue.sort((a, b) => { return priorityOrder[a.priority] - priorityOrder[b.priority]; }); for (const update of queue) { if (updates[update.filterId] && priorityOrder[updates[update.filterId].priority] >= priorityOrder[update.priority]) { continue; } updates[update.filterId] = update; } Object.values(updates).forEach((update) => { const { filterId, changes } = update; const [targetId, filterIndex] = filterId.split("-"); if (!filterMapRef.current[targetId]) { if (isDevelopment) { console.warn(`[useFilters] Filter target not found: ${targetId}`); } return; } const filterData = filterMapRef.current[targetId].filters[Number(filterIndex)]; if (!filterData) { if (isDevelopment) { console.warn(`[useFilters] Filter not found: ${filterId}`); } return; } if (changes.intensity !== void 0) { filterData.updateIntensity(changes.intensity); if (isDevelopment) { console.log(`[useFilters] Set filter ${filterId} to ${changes.enabled ? "active" : "inactive"} with intensity ${changes.intensity}`); } } if (changes.enabled !== void 0) { filterData.instance.enabled = changes.enabled; } }); const end = performance.now(); const processingTime = end - start; const metrics = performanceMetricsRef.current; metrics.lastProcessTime = processingTime; metrics.totalProcessTime += processingTime; metrics.updateCount += queue.length; metrics.averageProcessTime = metrics.totalProcessTime / metrics.updateCount; metrics.bufferAdjustmentCounter++; if (metrics.bufferAdjustmentCounter >= 10) { metrics.bufferAdjustmentCounter = 0; if (metrics.averageProcessTime > 4) { batchConfigRef.current.bufferMs = Math.min(50, batchConfigRef.current.bufferMs + 4); if (isDevelopment) { console.log(`[useFilters] Increased batch buffer to ${batchConfigRef.current.bufferMs}ms due to slow processing`); } } else if (metrics.averageProcessTime < 2 && batchConfigRef.current.bufferMs > 16) { batchConfigRef.current.bufferMs = Math.max(16, batchConfigRef.current.bufferMs - 2); if (isDevelopment) { console.log(`[useFilters] Decreased batch buffer to ${batchConfigRef.current.bufferMs}ms due to fast processing`); } } } if (pixi.app.current) { pixi.app.current.render(); } }); const scheduleNextBatchRef = react.useRef(() => { if (batchTimeoutRef.current !== null) { window.clearTimeout(batchTimeoutRef.current); batchTimeoutRef.current = null; } if (batchQueueRef.current.length > 0) { if (batchQueueRef.current.some((update) => update.priority === "critical")) { processBatchQueueRef.current(); return; } const highPriorityCount = batchQueueRef.current.filter((update) => update.priority === "high").length; if (highPriorityCount > 0 && (highPriorityCount >= 3 || batchQueueRef.current.length >= batchConfigRef.current.maxBatchSize)) { processBatchQueueRef.current(); return; } batchTimeoutRef.current = window.setTimeout(() => { processBatchQueueRef.current(); batchTimeoutRef.current = null; }, batchConfigRef.current.bufferMs); } }); const processFilterConfigs = (filterConfig) => { if (!filterConfig) { return []; } const configs = Array.isArray(filterConfig) ? filterConfig : [filterConfig]; return configs.filter((config) => { if (!config || !config.type) { if (isDevelopment) { console.warn("Invalid filter config - missing type:", config); } return false; } return true; }).map((config) => { return { type: config.type, // Set default values only if not defined in config enabled: config.enabled ?? true, // Default to enabled if not explicitly disabled intensity: config.intensity ?? 1, // Default intensity 1 if not defined ...config // Keep all other properties from config (might override defaults) }; }); }; const adaptFilterConfig = (config) => { const { type, enabled = true, intensity = 1, options = {}, ...rest } = config; const filterProperties = { ...rest, ...options }; return { type, enabled: enabled !== false, intensity: intensity || 1, ...filterProperties }; }; const applyFiltersToObjects = react.useCallback(async (targets, configs, baseId) => { if (!configs || configs.length === 0 || !targets || targets.length === 0) { if (isDevelopment) { console.log(`No filter configs or targets provided for ${baseId}`); } return; } if (!applyFiltersToTargetRef.current) { console.error("[useFilters] applyFiltersToTargetRef.current is not defined"); return; } const processedConfigs = configs.map((c) => adaptFilterConfig(c)); const applyPromises = targets.map((target, index) => { const targetId = `${baseId}-${index}`; return applyFiltersToTargetRef.current(target, processedConfigs, targetId); }); await Promise.all(applyPromises); }, []); react.useEffect(() => { applyFiltersToObjectsRef.current = applyFiltersToObjects; }, [applyFiltersToObjects]); const updateFilterIntensities = react.useCallback((active, force = false) => { if (!filtersInitializedRef.current) { if (isDevelopment) { console.log("[useFilters] Filters not initialized, skipping intensity update"); } return; } if (isDevelopment) { console.log(`[useFilters] Setting filter intensity to ${active ? "active" : "inactive"}`); } const batchUpdates = []; Object.entries(filterMapRef.current).forEach(([targetId, filterData]) => { try { const typedFilterData = filterData; if (!typedFilterData.target) return; const { target, filters } = typedFilterData; if (!target || !filters || filters.length === 0) return; if (!force && filters.some((f) => f.instance.enabled === active)) return; filters.forEach((f) => { f.instance.enabled = active; if (active) { if (f.initialIntensity !== void 0) { f.updateIntensity(f.initialIntensity); } } else { f.updateIntensity(0); } }); if (isDevelopment) { console.log(`[useFilters] Set filter ${targetId} to ${active ? "active" : "inactive"} with intensity ${active ? "initial" : 0}`); } batchUpdates.push({ filterId: targetId, changes: { enabled: active }, timestamp: performance.now(), priority: force ? "critical" : "normal" }); } catch (error) { if (isDevelopment) { console.error(`[useFilters] Error updating filter ${targetId}:`, error); } } }); if (batchUpdates.length > 0) { if (isDevelopment) { console.log(`[useFilters] Processing batch of ${batchUpdates.length} filter updates`); } batchQueueRef.current.push(...batchUpdates); if (force) { processBatchQueueRef.current(); } else { scheduleNextBatchRef.current(); } } filtersActiveRef.current = active; }, [filtersInitializedRef.current]); const resetAllFilters = react.useCallback(() => { if (!filtersInitializedRef.current) { if (isDevelopment) { console.log("[useFilters] Filters not initialized, skipping reset"); } return; } if (isDevelopment) { console.log("[useFilters] Resetting all filters to default state"); } const updates = []; const now = performance.now(); Object.entries(filterMapRef.current).forEach(([targetId, filterData]) => { const typedFilterData = filterData; typedFilterData.filters.forEach((filter, index) => { const filterId = `${targetId}-${index}`; updates.push({ filterId, changes: { intensity: filter.initialIntensity, enabled: true }, timestamp: now, priority: "normal" }); if (isDevelopment) { console.log(`[useFilters] Reset filter ${filterId} to default state`); } }); }); batchQueueRef.current.push(...updates); scheduleNextBatchRef.current(); Object.entries(filterMapRef.current).forEach(([_, filterData]) => { const typedFilterData = filterData; typedFilterData.filters.forEach((filter) => { filter.reset(); }); }); }, []); const activateFilterEffects = react.useCallback(() => { try { if (filtersInitializedRef.current) { updateFilterIntensities(true, true); return; } if (isDevelopment) { console.log("[useFilters] Filters not initialized, initializing now before activation"); } if (!pixi.app.current || !pixi.slides.current || !pixi.slides.current.length) { console.log("[useFilters] Missing required objects for filter initialization"); return; } const imageFilterConfigs = processFilterConfigs(props.imageFilters || []); const textFilterConfigs = processFilterConfigs(props.textFilters || []); originalConfigsRef.current = { image: [...imageFilterConfigs], text: [...textFilterConfigs] }; console.log(`[useFilters] Initializing filters: ${imageFilterConfigs.length} image filters, ${textFilterConfigs.length} text filters`); if (pixi.slides.current && pixi.slides.current.length) { pixi.slides.current.forEach((slide, index) => { if (!slide) return; const slideName = `slide-${index}`; console.log(`[useFilters] Creating filters for ${slideName}`); filterMapRef.current[slideName] = { target: slide, filters: [] }; imageFilterConfigs.forEach(async (config, filterIndex) => { try { const filterResult = await FilterFactory.FilterFactory.createFilterAsync(config); const filter = filterResult.filter; filter.enabled = true; if (!slide.filters) { slide.filters = [filter]; } else if (Array.isArray(slide.filters)) { slide.filters.push(filter); } filterMapRef.current[slideName].filters.push({ instance: filter, updateIntensity: (intensity) => { console.log(`[useFilters] Updated ${slideName} filter ${filterIndex} intensity to ${intensity}`); filterResult.updateIntensity(intensity); }, reset: () => { console.log(`[useFilters] Reset ${slideName} filter ${filterIndex}`); filterResult.reset(); }, initialIntensity: config.intensity || 1 }); console.log(`[useFilters] Created filter for ${slideName} with type ${config.type}`); } catch (err) { console.error(`[useFilters] Error creating filter for ${slideName}:`, err); } }); }); } if (pixi.textContainers.current && pixi.textContainers.current.length) { pixi.textContainers.current.forEach((container, index) => { if (!container) return; const containerName = `text-container-${index}`; console.log(`[useFilters] Creating filters for ${containerName}`); filterMapRef.current[containerName] = { target: container, filters: [] }; textFilterConfigs.forEach(async (config, filterIndex) => { try { const filterResult = await FilterFactory.FilterFactory.createFilterAsync(config); const filter = filterResult.filter; filter.enabled = true; if (!container.filters) { container.filters = [filter]; } else if (Array.isArray(container.filters)) { container.filters.push(filter); } filterMapRef.current[containerName].filters.push({ instance: filter, updateIntensity: (intensity) => { console.log(`[useFilters] Updated ${containerName} filter ${filterIndex} intensity to ${intensity}`); filterResult.updateIntensity(intensity); }, reset: () => { console.log(`[useFilters] Reset ${containerName} filter ${filterIndex}`); filterResult.reset(); }, initialIntensity: config.intensity || 1 }); console.log(`[useFilters] Created filter for ${containerName} with type ${config.type}`); } catch (err) { console.error(`[useFilters] Error creating filter for ${containerName}:`, err); } }); }); } filtersInitializedRef.current = true; filtersActiveRef.current = true; console.log("[useFilters] Filters initialized and activated"); } catch (error) { if (isDevelopment) { console.error("Error activating filter effects:", error); } } }, [pixi.app, pixi.slides, pixi.textContainers, props.imageFilters, props.textFilters, updateFilterIntensities]); const handleFilterCoordinationEvent = react.useCallback((event) => { console.log("[useFilters] Event handler implemented"); }, []); react.useEffect(() => { window.addEventListener(FILTER_COORDINATION_EVENT, handleFilterCoordinationEvent); return () => { window.removeEventListener(FILTER_COORDINATION_EVENT, handleFilterCoordinationEvent); }; }, [handleFilterCoordinationEvent]); applyFiltersToTargetRef.current = async (target, configs, id) => { console.warn(`[useFilters] Dummy applyFiltersToTarget called for ${id} - real implementation not yet available`); return Promise.resolve(); }; react.useEffect(() => { const scheduler = RenderScheduler.RenderScheduler.getInstance(); const scheduledProcessBatch = () => { processBatchQueueRef.current(); }; const scheduleBatchProcessing = () => { scheduler.scheduleTypedUpdate( "useFilters", UpdateTypes.UpdateType.FILTER_UPDATE, scheduledProcessBatch ); }; scheduleNextBatchRef.current = () => { if (batchQueueRef.current.some((update) => update.priority === "critical")) { if (batchTimeoutRef.current !== null) { window.clearTimeout(batchTimeoutRef.current); batchTimeoutRef.current = null; } batchTimeoutRef.current = window.setTimeout(() => { processBatchQueueRef.current(); batchTimeoutRef.current = null; }, 0); return; } if (batchQueueRef.current.some((update) => update.priority === "high")) { scheduleBatchProcessing(); return; } if (batchTimeoutRef.current !== null) { window.clearTimeout(batchTimeoutRef.current); batchTimeoutRef.current = null; } batchTimeoutRef.current = window.setTimeout(() => { scheduleBatchProcessing(); batchTimeoutRef.current = null; }, batchConfigRef.current.bufferMs); }; return () => { if (batchTimeoutRef.current !== null) { window.clearTimeout(batchTimeoutRef.current); batchTimeoutRef.current = null; } scheduler.cancelTypedUpdate("useFilters", UpdateTypes.UpdateType.FILTER_UPDATE); }; }, []); react.useEffect(() => { return () => { batchQueueRef.current = []; if (batchTimeoutRef.current !== null) { window.clearTimeout(batchTimeoutRef.current); batchTimeoutRef.current = null; } if (filtersInitializedRef.current) { Object.entries(filterMapRef.current).forEach(([_, filterData]) => { const typedFilterData = filterData; typedFilterData.filters.forEach((filter) => { try { filter.instance.enabled = false; filter.reset(); } catch (err) { if (isDevelopment) { console.error("[useFilters] Error cleaning up filter:", err); } } }); }); } }; }, []); react.useEffect(() => { if (typeof window === "undefined") return; const handleFilterCoordinationEvent2 = (event) => { const customEvent = event; const { type, intensity, priority } = customEvent.detail; if (isDevelopment) { console.log(`[useFilters] Received filter coordination event: ${type} = ${intensity}`); } if (type === "background-displacement" || type === "cursor-displacement") { if (intensity === 0) { updateFilterIntensities(false, priority === "critical"); } else { updateFilterIntensities(true, priority === "critical"); } } }; window.addEventListener(FILTER_COORDINATION_EVENT, handleFilterCoordinationEvent2); return () => { window.removeEventListener(FILTER_COORDINATION_EVENT, handleFilterCoordinationEvent2); }; }, [updateFilterIntensities]); return { updateFilterIntensities, resetAllFilters, activateFilterEffects, isInitialized: filtersInitializedRef.current, isActive: filtersActiveRef.current, setFiltersActive: (active) => { filtersActiveRef.current = active; } }; }; exports.useFilters = useFilters; //# sourceMappingURL=useFilters.cjs.map