🆙 Camera Fix

This commit is contained in:
duckietm
2026-03-18 09:17:04 +01:00
parent 2973c18ead
commit 9a6638219d
@@ -1,8 +1,9 @@
import { GetRoomCameraWidgetManager, IRoomCameraWidgetEffect, IRoomCameraWidgetSelectedEffect, NitroLogger, RoomCameraWidgetSelectedEffect } from '@nitrots/nitro-renderer'; import { GetRoomCameraWidgetManager, IRoomCameraWidgetEffect, IRoomCameraWidgetSelectedEffect, NitroLogger, RoomCameraWidgetSelectedEffect, TextureUtils } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Texture } from 'pixi.js';
import { FaSave, FaSearchMinus, FaSearchPlus, FaTrash } from 'react-icons/fa'; import { FaSave, FaSearchMinus, FaSearchPlus, FaTrash } from 'react-icons/fa';
import { CameraEditorTabs, CameraPicture, CameraPictureThumbnail, LocalizeText } from '../../../../api'; import { CameraEditorTabs, CameraPicture, CameraPictureThumbnail, LocalizeText } from '../../../../api';
import { Button, Column, Flex, Grid, LayoutImage, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, Slider, Text } from '../../../../common'; import { Button, Column, Flex, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, Slider, Text } from '../../../../common';
import { CameraWidgetEffectListView } from './effect-list'; import { CameraWidgetEffectListView } from './effect-list';
export interface CameraWidgetEditorViewProps { export interface CameraWidgetEditorViewProps {
@@ -23,10 +24,18 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
const [ selectedEffects, setSelectedEffects ] = useState<IRoomCameraWidgetSelectedEffect[]>([]); const [ selectedEffects, setSelectedEffects ] = useState<IRoomCameraWidgetSelectedEffect[]>([]);
const [ effectsThumbnails, setEffectsThumbnails ] = useState<CameraPictureThumbnail[]>([]); const [ effectsThumbnails, setEffectsThumbnails ] = useState<CameraPictureThumbnail[]>([]);
const [ isZoomed, setIsZoomed ] = useState(false); const [ isZoomed, setIsZoomed ] = useState(false);
const [ currentPictureUrl, setCurrentPictureUrl ] = useState<string>(''); const [ currentPictureUrl, setCurrentPictureUrl ] = useState<string>(picture?.imageUrl ?? '');
const [ stableTexture, setStableTexture ] = useState<Texture>(null);
const debounceTimerRef = useRef<ReturnType<typeof setTimeout>>(null); const debounceTimerRef = useRef<ReturnType<typeof setTimeout>>(null);
const requestIdRef = useRef<number>(0); const requestIdRef = useRef<number>(0);
useEffect(() =>
{
const img = new Image();
img.onload = () => setStableTexture(Texture.from(img));
img.src = picture.imageUrl;
}, [ picture ]);
const getColorMatrixEffects = useMemo(() => { const getColorMatrixEffects = useMemo(() => {
return availableEffects.filter(effect => effect.colorMatrix); return availableEffects.filter(effect => effect.colorMatrix);
}, [ availableEffects ]); }, [ availableEffects ]);
@@ -108,12 +117,14 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
setSelectedEffects([]); setSelectedEffects([]);
return; return;
case 'download': { case 'download': {
(async () => { if(!currentPictureUrl || !currentPictureUrl.startsWith('data:image/')) return;
const image = new Image();
image.src = currentPictureUrl; const link = document.createElement('a');
const newWindow = window.open(''); link.href = currentPictureUrl;
newWindow.document.write(image.outerHTML); link.download = 'camera_photo.png';
})(); document.body.appendChild(link);
link.click();
document.body.removeChild(link);
return; return;
} }
case 'zoom': case 'zoom':
@@ -123,25 +134,29 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
}, [ availableEffects, selectedEffectName, currentPictureUrl, getSelectedEffectIndex, onCancel, onCheckout, onClose ]); }, [ availableEffects, selectedEffectName, currentPictureUrl, getSelectedEffectIndex, onCancel, onCheckout, onClose ]);
useEffect(() => { useEffect(() => {
if(!stableTexture) return;
const processThumbnails = async () => { const processThumbnails = async () => {
const renderedEffects = await Promise.all( const renderedEffects = await Promise.all(
availableEffects.map(effect => availableEffects.map(effect =>
GetRoomCameraWidgetManager().applyEffects(picture.texture, [ new RoomCameraWidgetSelectedEffect(effect, 1) ], false) GetRoomCameraWidgetManager().applyEffects(stableTexture, [ new RoomCameraWidgetSelectedEffect(effect, 1) ], false)
) )
); );
setEffectsThumbnails(renderedEffects.map((image, index) => new CameraPictureThumbnail(availableEffects[index].name, image.src))); setEffectsThumbnails(renderedEffects.map((image, index) => new CameraPictureThumbnail(availableEffects[index].name, image.src)));
}; };
processThumbnails(); processThumbnails();
}, [ picture, availableEffects ]); }, [ stableTexture, availableEffects ]);
useEffect(() => { useEffect(() => {
if(!stableTexture) return;
if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current); if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
debounceTimerRef.current = setTimeout(() => { debounceTimerRef.current = setTimeout(() => {
const id = ++requestIdRef.current; const id = ++requestIdRef.current;
GetRoomCameraWidgetManager() GetRoomCameraWidgetManager()
.applyEffects(picture.texture, selectedEffects, false) .applyEffects(stableTexture, selectedEffects, false)
.then(imageElement => { .then(imageElement => {
if (id !== requestIdRef.current) return; if (id !== requestIdRef.current) return;
setCurrentPictureUrl(imageElement.src); setCurrentPictureUrl(imageElement.src);
@@ -152,10 +167,10 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
return () => { return () => {
if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current); if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
}; };
}, [ picture, selectedEffects ]); }, [ stableTexture, selectedEffects ]);
return ( return (
<NitroCardView className="w-[620px] h-[500px]"> <NitroCardView className="w-[600px] h-[500px]">
<NitroCardHeaderView headerText={ LocalizeText('camera.editor.button.text') } onCloseClick={ event => processAction('close') } /> <NitroCardHeaderView headerText={ LocalizeText('camera.editor.button.text') } onCloseClick={ event => processAction('close') } />
<NitroCardTabsView> <NitroCardTabsView>
{ TABS.map(tab => ( { TABS.map(tab => (
@@ -177,16 +192,14 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
</Column> </Column>
<Column justifyContent="between" overflow="hidden" size={ 7 }> <Column justifyContent="between" overflow="hidden" size={ 7 }>
<Column center> <Column center>
<LayoutImage <div className="w-[325px] h-[325px] overflow-hidden">
style={{ { currentPictureUrl && <img
width: '320px', alt=""
height: '320px', src={ currentPictureUrl }
backgroundImage: `url(${currentPictureUrl})`, className="w-[325px] h-[325px] [image-rendering:pixelated]"
backgroundPosition: isZoomed ? 'center' : 'top left', style={ isZoomed ? { transform: 'scale(2)', transformOrigin: 'center' } : undefined }
backgroundSize: isZoomed ? 'contain' : 'auto', // Zoom only affects display /> }
backgroundRepeat: 'no-repeat' </div>
}}
/>
{ selectedEffectName && ( { selectedEffectName && (
<Column center fullWidth gap={ 1 }> <Column center fullWidth gap={ 1 }>
<Text>{ LocalizeText('camera.effect.name.' + selectedEffectName) }</Text> <Text>{ LocalizeText('camera.effect.name.' + selectedEffectName) }</Text>