import { GetRoomCameraWidgetManager, IRoomCameraWidgetEffect, IRoomCameraWidgetSelectedEffect, NitroLogger, NitroTexture, RoomCameraWidgetSelectedEffect } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { FaSave, FaSearchMinus, FaSearchPlus, FaTrash } from 'react-icons/fa'; import { CameraEditorTabs, CameraPicture, CameraPictureThumbnail, LocalizeText } from '../../../../api'; import { Button, Column, Flex, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, Slider, Text } from '../../../../common'; import { CameraWidgetEffectListView } from './effect-list'; export interface CameraWidgetEditorViewProps { picture: CameraPicture; availableEffects: IRoomCameraWidgetEffect[]; myLevel: number; onClose: () => void; onCancel: () => void; onCheckout: (pictureUrl: string) => void; } const TABS: string[] = [ CameraEditorTabs.COLORMATRIX, CameraEditorTabs.COMPOSITE ]; export const CameraWidgetEditorView: FC = props => { const { picture = null, availableEffects = null, myLevel = 1, onClose = null, onCancel = null, onCheckout = null } = props; const [ currentTab, setCurrentTab ] = useState(TABS[0]); const [ selectedEffectName, setSelectedEffectName ] = useState(null); const [ selectedEffects, setSelectedEffects ] = useState([]); const [ effectsThumbnails, setEffectsThumbnails ] = useState([]); const [ isZoomed, setIsZoomed ] = useState(false); const [ currentPictureUrl, setCurrentPictureUrl ] = useState(picture?.imageUrl ?? ''); const [ stableTexture, setStableTexture ] = useState(null); const debounceTimerRef = useRef>(null); const requestIdRef = useRef(0); useEffect(() => { const img = new Image(); img.onload = () => setStableTexture(NitroTexture.from(img)); img.src = picture.imageUrl; }, [ picture ]); const getColorMatrixEffects = useMemo(() => { return availableEffects.filter(effect => effect.colorMatrix); }, [ availableEffects ]); const getCompositeEffects = useMemo(() => { return availableEffects.filter(effect => effect.texture); }, [ availableEffects ]); const getEffectList = useCallback(() => { return currentTab === CameraEditorTabs.COLORMATRIX ? getColorMatrixEffects : getCompositeEffects; }, [ currentTab, getColorMatrixEffects, getCompositeEffects ]); const getSelectedEffectIndex = useCallback((name: string) => { if (!name || !name.length || !selectedEffects || !selectedEffects.length) return -1; return selectedEffects.findIndex(effect => effect.effect.name === name); }, [ selectedEffects ]); const getCurrentEffectIndex = useMemo(() => { return getSelectedEffectIndex(selectedEffectName); }, [ selectedEffectName, getSelectedEffectIndex ]); const getCurrentEffect = useMemo(() => { if (!selectedEffectName) return null; return selectedEffects[getCurrentEffectIndex] || null; }, [ selectedEffectName, getCurrentEffectIndex, selectedEffects ]); const setSelectedEffectAlpha = useCallback((alpha: number) => { const index = getCurrentEffectIndex; if (index === -1) return; setSelectedEffects(prevValue => { const clone = [ ...prevValue ]; const currentEffect = clone[index]; clone[index] = new RoomCameraWidgetSelectedEffect(currentEffect.effect, alpha); return clone; }); }, [ getCurrentEffectIndex ]); const processAction = useCallback((type: string, effectName: string = null) => { switch (type) { case 'close': onClose(); return; case 'cancel': onCancel(); return; case 'checkout': onCheckout(currentPictureUrl); return; case 'change_tab': setCurrentTab(String(effectName)); return; case 'select_effect': { const existingIndex = getSelectedEffectIndex(effectName); if (existingIndex >= 0) return; const effect = availableEffects.find(effect => effect.name === effectName); if (!effect) return; setSelectedEffects(prevValue => [ ...prevValue, new RoomCameraWidgetSelectedEffect(effect, 1) ]); setSelectedEffectName(effect.name); return; } case 'remove_effect': { const existingIndex = getSelectedEffectIndex(effectName); if (existingIndex === -1) return; setSelectedEffects(prevValue => { const clone = [ ...prevValue ]; clone.splice(existingIndex, 1); return clone; }); if (selectedEffectName === effectName) setSelectedEffectName(null); return; } case 'clear_effects': setSelectedEffectName(null); setSelectedEffects([]); return; case 'download': { if(!currentPictureUrl || !currentPictureUrl.startsWith('data:image/')) return; const link = document.createElement('a'); link.href = currentPictureUrl; link.download = 'camera_photo.png'; document.body.appendChild(link); link.click(); document.body.removeChild(link); return; } case 'zoom': setIsZoomed(prev => !prev); return; } }, [ availableEffects, selectedEffectName, currentPictureUrl, getSelectedEffectIndex, onCancel, onCheckout, onClose ]); useEffect(() => { if(!stableTexture) return; const processThumbnails = async () => { const renderedEffects = await Promise.all( availableEffects.map(effect => GetRoomCameraWidgetManager().applyEffects(stableTexture, [ new RoomCameraWidgetSelectedEffect(effect, 1) ], false) ) ); setEffectsThumbnails(renderedEffects.map((image, index) => new CameraPictureThumbnail(availableEffects[index].name, image.src))); }; processThumbnails(); }, [ stableTexture, availableEffects ]); useEffect(() => { if(!stableTexture) return; if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current); debounceTimerRef.current = setTimeout(() => { const id = ++requestIdRef.current; GetRoomCameraWidgetManager() .applyEffects(stableTexture, selectedEffects, false) .then(imageElement => { if (id !== requestIdRef.current) return; setCurrentPictureUrl(imageElement.src); }) .catch(error => NitroLogger.error('Failed to apply effects to picture', error)); }, 50); return () => { if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current); }; }, [ stableTexture, selectedEffects ]); return ( processAction('close') } /> { TABS.map(tab => ( processAction('change_tab', tab) }> )) }
{ currentPictureUrl && }
{ selectedEffectName && ( { LocalizeText('camera.effect.name.' + selectedEffectName) } setSelectedEffectAlpha(event / 100) } renderThumb={ ({ key, ...props }, state) =>
{ state.valueNow }
} />
) }
); };