Camera updates

This commit is contained in:
Bill 2021-08-05 02:26:43 -04:00
parent 351a446483
commit 28539e6923
17 changed files with 214 additions and 214 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 830 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1000 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 935 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 959 B

View File

@ -476,13 +476,13 @@
&.icon-camera-colormatrix { &.icon-camera-colormatrix {
background: url('../images/icons/camera-colormatrix.png'); background: url('../images/icons/camera-colormatrix.png');
width: 32px; width: 32px;
height: 14px; height: 21px;
} }
&.icon-camera-composite { &.icon-camera-composite {
background: url('../images/icons/camera-composite.png'); background: url('../images/icons/camera-composite.png');
width: 32px; width: 32px;
height: 14px; height: 21px;
} }
&.icon-user-profile { &.icon-user-profile {

View File

@ -1,30 +1,41 @@
.nitro-camera-capture { .nitro-camera-capture {
.nitro-close { position: relative;
top: 7px;
right: 9px; .header-close {
top: 8px;
right: 8px;
border-radius: $border-radius;
box-shadow: 0 0 0 1.5px $white;
border: 2px solid #921911;
background: repeating-linear-gradient(rgba(245,80,65,1), rgba(245,80,65,1) 50%, rgba(194,48,39,1) 50%, rgba(194,48,39,1) 100%);
cursor: pointer;
line-height: 1;
padding: 1px 3px;
&:hover {
filter: brightness(1.2);
}
&:active {
filter: brightness(0.8);
}
}
.camera-area {
position: absolute;
top: 37px;
left: 10px;
width: 320px;
height: 320px;
} }
.camera-canvas { .camera-canvas {
position: relative;
width: 340px; width: 340px;
height: 462px; height: 462px;
background-image: url('../../../../assets/images/room-widgets/camera-widget/camera-spritesheet.png'); background-image: url('../../../../assets/images/room-widgets/camera-widget/camera-spritesheet.png');
background-position: -1px -1px;
.camera-frame { z-index: 2;
position: absolute;
width: 320px;
height: 320px;
margin-top: 37px;
margin-left: 11.4px;
&.bg {
background: black;
}
.camera-frame-preview-actions {
background: rgba(0, 0, 0, .5);
}
}
.camera-button { .camera-button {
width: 94px; width: 94px;
@ -33,14 +44,26 @@
margin-top: 362px; margin-top: 362px;
background-image: url('../../../../assets/images/room-widgets/camera-widget/camera-spritesheet.png'); background-image: url('../../../../assets/images/room-widgets/camera-widget/camera-spritesheet.png');
background-position: -340px 0px; background-position: -343px -321px;
&:hover { &:hover {
background-position: -340px -94px; background-position: -535px -321px;
} }
&:active { &:active {
background-position: -340px -188px; background-position: -439px -321px;
}
}
.camera-view-finder {
background-image: url('../../../../assets/images/room-widgets/camera-widget/camera-spritesheet.png');
background-position: -343px -1px;
}
.camera-frame {
.camera-frame-preview-actions {
background: rgba(0, 0, 0, .5);
} }
} }
} }

View File

@ -1,48 +1,50 @@
import { RoomWidgetCameraConfigurationComposer, RoomWidgetCameraConfigurationEvent } from 'nitro-renderer'; import { InitCameraMessageEvent, IRoomCameraWidgetEffect, RequestCameraConfigurationComposer } from 'nitro-renderer';
import { RoomCameraWidgetManagerEvent } from 'nitro-renderer/src/nitro/camera/events/RoomCameraWidgetManagerEvent'; import { RoomCameraWidgetManagerEvent } from 'nitro-renderer/src/nitro/camera/events/RoomCameraWidgetManagerEvent';
import { IRoomCameraWidgetSelectedEffect } from 'nitro-renderer/src/nitro/camera/IRoomCameraWidgetSelectedEffect'; import { IRoomCameraWidgetSelectedEffect } from 'nitro-renderer/src/nitro/camera/IRoomCameraWidgetSelectedEffect';
import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { FC, useCallback, useEffect, useState } from 'react';
import { GetRoomCameraWidgetManager } from '../../../../api'; import { GetRoomCameraWidgetManager } from '../../../../api';
import { RoomWidgetCameraEvent } from '../../../../events/room-widgets/camera/RoomWidgetCameraEvent'; import { RoomWidgetCameraEvent } from '../../../../events/room-widgets/camera/RoomWidgetCameraEvent';
import { useCameraEvent } from '../../../../hooks/events/nitro/camera/camera-event'; import { useCameraEvent } from '../../../../hooks/events/nitro/camera/camera-event';
import { useUiEvent } from '../../../../hooks/events/ui/ui-event'; import { useUiEvent } from '../../../../hooks/events/ui/ui-event';
import { CreateMessageHook, SendMessageHook } from '../../../../hooks/messages/message-event'; import { CreateMessageHook, SendMessageHook } from '../../../../hooks/messages/message-event';
import { CameraPicture } from './common/CameraPicture';
import { CameraWidgetContextProvider } from './context/CameraWidgetContext'; import { CameraWidgetContextProvider } from './context/CameraWidgetContext';
import { CameraWidgetCaptureView } from './views/capture/CameraWidgetCaptureView'; import { CameraWidgetCaptureView } from './views/capture/CameraWidgetCaptureView';
import { CameraWidgetCheckoutView } from './views/checkout/CameraWidgetCheckoutView'; import { CameraWidgetCheckoutView } from './views/checkout/CameraWidgetCheckoutView';
import { CameraWidgetEditorView } from './views/editor/CameraWidgetEditorView'; import { CameraWidgetEditorView } from './views/editor/CameraWidgetEditorView';
const MODE_NONE: number = 0;
const MODE_CAPTURE: number = 1;
const MODE_EDITOR: number = 2;
const MODE_CHECKOUT: number = 3;
export const CameraWidgetView: FC<{}> = props => export const CameraWidgetView: FC<{}> = props =>
{ {
const [ effectsReady, setEffectsReady ] = useState(false); const [ mode, setMode ] = useState<number>(MODE_NONE);
const [ availableEffects, setAvailableEffects ] = useState<IRoomCameraWidgetEffect[]>([]);
const [ isCaptureVisible, setIsCaptureVisible ] = useState(false); const [ cameraRoll, setCameraRoll ] = useState<CameraPicture[]>([]);
const [ isEditorVisible, setIsEditorVisible ] = useState(false);
const [ isCheckoutVisible, setIsCheckoutVisible ] = useState(false);
const [ myLevel, setMyLevel ] = useState(10);
const [ cameraRoll, setCameraRoll ] = useState<HTMLImageElement[]>([]);
const [ selectedPictureIndex, setSelectedPictureIndex ] = useState(-1); const [ selectedPictureIndex, setSelectedPictureIndex ] = useState(-1);
const [ selectedEffects, setSelectedEffects ] = useState<IRoomCameraWidgetSelectedEffect[]>([]); const [ selectedEffects, setSelectedEffects ] = useState<IRoomCameraWidgetSelectedEffect[]>([]);
const [ isZoomed, setIsZoomed ] = useState(false); const [ isZoomed, setIsZoomed ] = useState(false);
const [ price, setPrice ] = useState<{credits: Number, points: Number, pointsType: number}>(null); const [ myLevel, setMyLevel ] = useState(10);
const [ price, setPrice ] = useState<{ credits: number, duckets: number, publishDucketPrice: number }>(null);
const onNitroEvent = useCallback((event: RoomWidgetCameraEvent) => const onNitroEvent = useCallback((event: RoomWidgetCameraEvent) =>
{ {
switch(event.type) switch(event.type)
{ {
case RoomWidgetCameraEvent.SHOW_CAMERA: case RoomWidgetCameraEvent.SHOW_CAMERA:
setIsCaptureVisible(true); setMode(MODE_CAPTURE);
return; return;
case RoomWidgetCameraEvent.HIDE_CAMERA: case RoomWidgetCameraEvent.HIDE_CAMERA:
setIsCaptureVisible(false); setMode(MODE_NONE);
setIsEditorVisible(false);
setIsCheckoutVisible(false);
return; return;
case RoomWidgetCameraEvent.TOGGLE_CAMERA: case RoomWidgetCameraEvent.TOGGLE_CAMERA:
setIsCaptureVisible(value => !value); setMode(prevValue =>
setIsEditorVisible(false); {
setIsCheckoutVisible(false); if(!prevValue) return MODE_CAPTURE;
else return MODE_NONE;
});
return; return;
} }
}, []); }, []);
@ -51,78 +53,68 @@ export const CameraWidgetView: FC<{}> = props =>
useUiEvent(RoomWidgetCameraEvent.HIDE_CAMERA, onNitroEvent); useUiEvent(RoomWidgetCameraEvent.HIDE_CAMERA, onNitroEvent);
useUiEvent(RoomWidgetCameraEvent.TOGGLE_CAMERA, onNitroEvent); useUiEvent(RoomWidgetCameraEvent.TOGGLE_CAMERA, onNitroEvent);
useEffect(() =>
{
if(price) return;
SendMessageHook(new RoomWidgetCameraConfigurationComposer());
}, [ price ]);
const onCameraConfigurationEvent = useCallback((event: RoomWidgetCameraConfigurationEvent) =>
{
const parser = event.getParser();
setPrice({ credits: parser.credits, points: parser.points, pointsType: parser.pointsType });
}, []);
CreateMessageHook(RoomWidgetCameraConfigurationEvent, onCameraConfigurationEvent);
const availableEffects = useMemo(() =>
{
if(!effectsReady) return null;
return Array.from(GetRoomCameraWidgetManager().effects.values());
}, [ effectsReady ]);
const onRoomCameraWidgetManagerEvent = useCallback((event: RoomCameraWidgetManagerEvent) => const onRoomCameraWidgetManagerEvent = useCallback((event: RoomCameraWidgetManagerEvent) =>
{ {
setEffectsReady(true); setAvailableEffects(Array.from(GetRoomCameraWidgetManager().effects.values()))
}, []); }, []);
useCameraEvent(RoomCameraWidgetManagerEvent.INITIALIZED, onRoomCameraWidgetManagerEvent); useCameraEvent(RoomCameraWidgetManagerEvent.INITIALIZED, onRoomCameraWidgetManagerEvent);
const onCameraConfigurationEvent = useCallback((event: InitCameraMessageEvent) =>
{
const parser = event.getParser();
setPrice({ credits: parser.creditPrice, duckets: parser.ducketPrice, publishDucketPrice: parser.publishDucketPrice });
}, []);
CreateMessageHook(InitCameraMessageEvent, onCameraConfigurationEvent);
useEffect(() => useEffect(() =>
{ {
if(!GetRoomCameraWidgetManager().isLoaded) if(!GetRoomCameraWidgetManager().isLoaded)
{ {
GetRoomCameraWidgetManager().init(); GetRoomCameraWidgetManager().init();
SendMessageHook(new RequestCameraConfigurationComposer());
return; return;
} }
setEffectsReady(true);
}, []); }, []);
const processAction = useCallback((type: string, value: any = null) => const processAction = useCallback((type: string) =>
{ {
switch(type) switch(type)
{ {
case 'close': case 'close':
setIsCaptureVisible(false); setMode(MODE_NONE);
setIsEditorVisible(false);
setIsCheckoutVisible(false);
return; return;
case 'capture_edit': case 'edit':
setIsCaptureVisible(false); setMode(MODE_EDITOR);
setIsEditorVisible(true); return;
case 'delete':
setCameraRoll(prevValue =>
{
const clone = [ ...prevValue ];
clone.splice(selectedPictureIndex, 1);
return clone;
});
return; return;
case 'editor_cancel': case 'editor_cancel':
setIsCaptureVisible(true); setMode(MODE_CAPTURE);
setIsEditorVisible(false);
setIsCheckoutVisible(false);
return; return;
case 'editor_checkout': case 'checkout':
setIsEditorVisible(false); setMode(MODE_CHECKOUT);
setIsCheckoutVisible(true);
return; return;
} }
}, []); }, [ selectedPictureIndex ]);
return ( return (
<CameraWidgetContextProvider value={ { cameraRoll, setCameraRoll, selectedPictureIndex, setSelectedPictureIndex, selectedEffects, setSelectedEffects, isZoomed, setIsZoomed } }> <CameraWidgetContextProvider value={ { cameraRoll, setCameraRoll, selectedPictureIndex, setSelectedPictureIndex, selectedEffects, setSelectedEffects, isZoomed, setIsZoomed } }>
{ isCaptureVisible && <CameraWidgetCaptureView onCloseClick={ () => processAction('close') } onEditClick={ () => processAction('capture_edit') } /> } { (mode === MODE_CAPTURE) && <CameraWidgetCaptureView onClose={ () => processAction('close') } onEdit={ () => processAction('edit') } onDelete={ () => processAction('delete') } /> }
{ isEditorVisible && <CameraWidgetEditorView myLevel={ myLevel } onCloseClick={ () => processAction('close') } onCancelClick={ () => processAction('editor_cancel') } onCheckoutClick={ () => processAction('editor_checkout') } availableEffects={ availableEffects } /> } { (mode === MODE_EDITOR) && <CameraWidgetEditorView myLevel={ myLevel } onClose={ () => processAction('close') } onCancel={ () => processAction('editor_cancel') } onCheckout={ () => processAction('checkout') } availableEffects={ availableEffects } /> }
{ isCheckoutVisible && <CameraWidgetCheckoutView onCloseClick={ () => processAction('close') } onCancelClick={ () => processAction('editor_cancel') } price={ price }></CameraWidgetCheckoutView> } { (mode === MODE_CHECKOUT) && <CameraWidgetCheckoutView onCloseClick={ () => processAction('close') } onCancelClick={ () => processAction('editor_cancel') } price={ price }></CameraWidgetCheckoutView> }
</CameraWidgetContextProvider> </CameraWidgetContextProvider>
); );
} }

View File

@ -0,0 +1,8 @@
import { NitroTexture } from 'nitro-renderer';
export class CameraPicture
{
constructor(
public texture: NitroTexture,
public imageUrl: string) {}
}

View File

@ -1,10 +1,11 @@
import { IRoomCameraWidgetSelectedEffect } from 'nitro-renderer/src/nitro/camera/IRoomCameraWidgetSelectedEffect'; import { IRoomCameraWidgetSelectedEffect } from 'nitro-renderer/src/nitro/camera/IRoomCameraWidgetSelectedEffect';
import { ProviderProps } from 'react'; import { ProviderProps } from 'react';
import { CameraPicture } from '../common/CameraPicture';
export interface ICameraWidgetContext export interface ICameraWidgetContext
{ {
cameraRoll: HTMLImageElement[], cameraRoll: CameraPicture[],
setCameraRoll: (cameraRoll: HTMLImageElement[]) => void, setCameraRoll: (cameraRoll: CameraPicture[]) => void,
selectedPictureIndex: number, selectedPictureIndex: number,
setSelectedPictureIndex: (index: number) => void, setSelectedPictureIndex: (index: number) => void,
selectedEffects: IRoomCameraWidgetSelectedEffect[], selectedEffects: IRoomCameraWidgetSelectedEffect[],

View File

@ -1,103 +1,81 @@
import classNames from 'classnames'; import { NitroRectangle, TextureUtils } from 'nitro-renderer';
import { NitroRectangle } from 'nitro-renderer';
import { FC, useCallback, useRef } from 'react'; import { FC, useCallback, useRef } from 'react';
import { GetRoomEngine, GetRoomSession } from '../../../../../../api'; import { GetRoomEngine, GetRoomSession } from '../../../../../../api';
import { DraggableWindow } from '../../../../../../layout/draggable-window/DraggableWindow'; import { DraggableWindow } from '../../../../../../layout/draggable-window/DraggableWindow';
import { LocalizeText } from '../../../../../../utils/LocalizeText'; import { LocalizeText } from '../../../../../../utils/LocalizeText';
import { CameraPicture } from '../../common/CameraPicture';
import { useCameraWidgetContext } from '../../context/CameraWidgetContext'; import { useCameraWidgetContext } from '../../context/CameraWidgetContext';
import { CameraWidgetCaptureViewProps } from './CameraWidgetCaptureView.types'; import { CameraWidgetCaptureViewProps } from './CameraWidgetCaptureView.types';
const CAMERA_ROLL_LIMIT: number = 5;
export const CameraWidgetCaptureView: FC<CameraWidgetCaptureViewProps> = props => export const CameraWidgetCaptureView: FC<CameraWidgetCaptureViewProps> = props =>
{ {
const { onCloseClick = null, onEditClick = null } = props; const { onClose = null, onEdit = null, onDelete = null } = props;
const { cameraRoll = null, setCameraRoll = null, selectedPictureIndex = -1, setSelectedPictureIndex = null } = useCameraWidgetContext();
const elementRef = useRef<HTMLDivElement>();
const CAMERA_ROLL_LIMIT: number = 5; const selectedPicture = ((selectedPictureIndex > -1) ? cameraRoll[selectedPictureIndex] : null);
const cameraFrameRef = useRef<HTMLDivElement>();
const cameraWidgetContext = useCameraWidgetContext(); const getCameraBounds = useCallback(() =>
{
if(!elementRef || !elementRef.current) return null;
const frameBounds = elementRef.current.getBoundingClientRect();
return new NitroRectangle(Math.floor(frameBounds.x), Math.floor(frameBounds.y), Math.floor(frameBounds.width), Math.floor(frameBounds.height));
}, []);
const takePicture = useCallback(() => const takePicture = useCallback(() =>
{ {
if(cameraWidgetContext.selectedPictureIndex > -1) if(selectedPictureIndex > -1)
{ {
cameraWidgetContext.setSelectedPictureIndex(-1); setSelectedPictureIndex(-1);
return; return;
} }
const frameBounds = cameraFrameRef.current.getBoundingClientRect(); const texture = GetRoomEngine().createTextureFromRoom(GetRoomSession().roomId, 1, getCameraBounds());
if(!frameBounds) return; const clone = [ ...cameraRoll ];
const rectangle = new NitroRectangle(Math.floor(frameBounds.x), Math.floor(frameBounds.y), Math.floor(frameBounds.width), Math.floor(frameBounds.height)); if(clone.length >= CAMERA_ROLL_LIMIT)
const texture = GetRoomEngine().createTextureFromRoom(GetRoomSession().roomId, 1, rectangle);
if(cameraWidgetContext.cameraRoll.length + 1 === CAMERA_ROLL_LIMIT)
{ {
alert(LocalizeText('camera.full.body')); alert(LocalizeText('camera.full.body'));
clone.pop();
} }
let remainingRoll = cameraWidgetContext.cameraRoll; clone.push(new CameraPicture(texture, TextureUtils.generateImageUrl(texture)));
if(cameraWidgetContext.cameraRoll.length === CAMERA_ROLL_LIMIT) setCameraRoll(clone);
{ }, [ cameraRoll, selectedPictureIndex, getCameraBounds, setCameraRoll, setSelectedPictureIndex ]);
remainingRoll = remainingRoll.slice(0, CAMERA_ROLL_LIMIT - 1);
}
//cameraWidgetContext.setCameraRoll([ ...remainingRoll, image ]);
}, [ cameraWidgetContext ]);
const processAction = useCallback((type: string, value: string | number = null) =>
{
switch(type)
{
case 'take_picture':
takePicture();
return;
case 'preview_picture':
cameraWidgetContext.setSelectedPictureIndex(Number(value));
return;
case 'discard_picture':
cameraWidgetContext.setSelectedPictureIndex(-1);
const clone = Array.from(cameraWidgetContext.cameraRoll);
clone.splice(cameraWidgetContext.selectedPictureIndex, 1);
cameraWidgetContext.setCameraRoll(clone);
return;
case 'edit_picture':
onEditClick();
return;
case 'close':
onCloseClick();
return;
}
}, [ takePicture, cameraWidgetContext, onEditClick, onCloseClick ]);
return ( return (
<DraggableWindow handle=".nitro-camera-capture"> <DraggableWindow>
<div className="nitro-camera-capture d-flex flex-column justify-content-center align-items-center"> <div className="d-flex flex-column justify-content-center align-items-center nitro-camera-capture">
<div className="camera-canvas"> { selectedPicture && <img alt="" className="camera-area" src={ selectedPicture.imageUrl } /> }
<div className="position-absolute header-close" onClick={ event => processAction('close') }> <div className="camera-canvas drag-handler">
<div className="position-absolute header-close" onClick={ onClose }>
<i className="fas fa-times" /> <i className="fas fa-times" />
</div> </div>
<div ref={ cameraFrameRef } className={'camera-frame ' + classNames({'bg': cameraWidgetContext.selectedPictureIndex > -1}) }> { !selectedPicture && <div ref={ elementRef } className="camera-area camera-view-finder" /> }
{ cameraWidgetContext.selectedPictureIndex > -1 && <div> { selectedPicture &&
<img alt="" src={ cameraWidgetContext.cameraRoll[cameraWidgetContext.selectedPictureIndex].src } /> <div className="camera-area camera-frame">
<div className="camera-frame-preview-actions w-100 position-absolute bottom-0 py-2 text-center"> <div className="camera-frame-preview-actions w-100 position-absolute bottom-0 py-2 text-center">
<button className="btn btn-success me-3" title={ LocalizeText('camera.editor.button.tooltip') } onClick={ event => processAction('edit_picture') }>{ LocalizeText('camera.editor.button.text') }</button> <button className="btn btn-success me-3" title={ LocalizeText('camera.editor.button.tooltip') } onClick={ onEdit }>{ LocalizeText('camera.editor.button.text') }</button>
<button className="btn btn-danger" onClick={ event => processAction('discard_picture') }>{ LocalizeText('camera.delete.button.text') }</button> <button className="btn btn-danger" onClick={ onDelete }>{ LocalizeText('camera.delete.button.text') }</button>
</div> </div>
</div> } </div> }
</div>
<div className="d-flex justify-content-center"> <div className="d-flex justify-content-center">
<div className="camera-button" title={ LocalizeText('camera.take.photo.button.tooltip') } onClick={ takePicture }></div> <div className="camera-button" title={ LocalizeText('camera.take.photo.button.tooltip') } onClick={ takePicture } />
</div> </div>
</div> </div>
{ cameraWidgetContext.cameraRoll.length > 0 && <div className="camera-roll d-flex justify-content-center py-2"> { (cameraRoll.length > 0) &&
{ cameraWidgetContext.cameraRoll.map((picture, index) => <div className="camera-roll d-flex justify-content-center py-2">
{ cameraRoll.map((picture, index) =>
{ {
return <img alt="" key={ index } className={ (index < cameraWidgetContext.cameraRoll.length - 1 ? 'me-2' : '') } src={ picture.src } onClick={ event => processAction('preview_picture', index) } />; return <img alt="" key={ index } className={ (index < (cameraRoll.length - 1) ? 'me-2' : '') } src={ picture.imageUrl } onClick={ event => setSelectedPictureIndex(index) } />;
}) } }) }
</div> } </div> }
</div> </div>

View File

@ -1,5 +1,6 @@
export interface CameraWidgetCaptureViewProps export interface CameraWidgetCaptureViewProps
{ {
onCloseClick: () => void; onClose: () => void;
onEditClick: () => void; onEdit: () => void;
onDelete: () => void;
} }

View File

@ -1,5 +1,5 @@
/* eslint-disable jsx-a11y/anchor-is-valid */ /* eslint-disable jsx-a11y/anchor-is-valid */
import { RoomWidgetCameraPublishComposer, RoomWidgetCameraPublishedEvent, RoomWidgetCameraPurchaseComposer, RoomWidgetCameraPurchaseSuccessfulEvent } from 'nitro-renderer'; import { CameraPublishStatusMessageEvent, CameraPurchaseOKMessageEvent, PublishPhotoMessageComposer, PurchasePhotoMessageComposer } from 'nitro-renderer';
import { FC, useCallback, useState } from 'react'; import { FC, useCallback, useState } from 'react';
import { GetRoomCameraWidgetManager } from '../../../../../../api/nitro/camera/GetRoomCameraWidgetManager'; import { GetRoomCameraWidgetManager } from '../../../../../../api/nitro/camera/GetRoomCameraWidgetManager';
import { CreateMessageHook, SendMessageHook } from '../../../../../../hooks/messages/message-event'; import { CreateMessageHook, SendMessageHook } from '../../../../../../hooks/messages/message-event';
@ -12,36 +12,35 @@ import { CameraWidgetCheckoutViewProps } from './CameraWidgetCheckoutView.types'
export const CameraWidgetCheckoutView: FC<CameraWidgetCheckoutViewProps> = props => export const CameraWidgetCheckoutView: FC<CameraWidgetCheckoutViewProps> = props =>
{ {
const { onCloseClick = null, onCancelClick = null, price = null } = props; const { onCloseClick = null, onCancelClick = null, price = null } = props;
const cameraWidgetContext = useCameraWidgetContext();
const [ picturesBought, setPicturesBought ] = useState(0); const [ picturesBought, setPicturesBought ] = useState(0);
const [ wasPicturePublished, setWasPicturePublished ] = useState(false); const [ wasPicturePublished, setWasPicturePublished ] = useState(false);
const [ isWaiting, setIsWaiting ] = useState(false); const [ isWaiting, setIsWaiting ] = useState(false);
const [ publishCooldown, setPublishCooldown ] = useState(0); const [ publishCooldown, setPublishCooldown ] = useState(0);
const { cameraRoll = null, selectedPictureIndex = -1, selectedEffects = null, isZoomed = false } = useCameraWidgetContext();
const onCameraPurchaseSuccessfulEvent = useCallback((event: RoomWidgetCameraPurchaseSuccessfulEvent) => const onCameraPurchaseOKMessageEvent = useCallback((event: CameraPurchaseOKMessageEvent) =>
{ {
setPicturesBought(value => value + 1); setPicturesBought(value => value + 1);
setIsWaiting(false); setIsWaiting(false);
}, []); }, []);
const onRoomWidgetCameraPublishedEvent = useCallback((event: RoomWidgetCameraPublishedEvent) => CreateMessageHook(CameraPurchaseOKMessageEvent, onCameraPurchaseOKMessageEvent);
const onCameraPublishStatusMessageEvent = useCallback((event: CameraPublishStatusMessageEvent) =>
{ {
const parser = event.getParser(); const parser = event.getParser();
setPublishCooldown(parser.cooldownSeconds); setPublishCooldown(parser.secondsToWait);
setWasPicturePublished(parser.wasSuccessful); setWasPicturePublished(parser.ok);
setIsWaiting(false); setIsWaiting(false);
}, []); }, []);
CreateMessageHook(RoomWidgetCameraPurchaseSuccessfulEvent, onCameraPurchaseSuccessfulEvent); CreateMessageHook(CameraPublishStatusMessageEvent, onCameraPublishStatusMessageEvent);
CreateMessageHook(RoomWidgetCameraPublishedEvent, onRoomWidgetCameraPublishedEvent);
const getCurrentPicture = useCallback(() => const getCurrentPicture = useCallback(() =>
{ {
return GetRoomCameraWidgetManager().applyEffects(cameraWidgetContext.cameraRoll[cameraWidgetContext.selectedPictureIndex], cameraWidgetContext.selectedEffects, cameraWidgetContext.isZoomed); return GetRoomCameraWidgetManager().applyEffects(cameraRoll[selectedPictureIndex].texture, selectedEffects, isZoomed);
}, [ cameraWidgetContext ]); }, [ cameraRoll, selectedPictureIndex, selectedEffects, isZoomed ]);
const processAction = useCallback((type: string, value: string | number = null) => const processAction = useCallback((type: string, value: string | number = null) =>
{ {
@ -54,13 +53,13 @@ export const CameraWidgetCheckoutView: FC<CameraWidgetCheckoutViewProps> = props
if(isWaiting) return; if(isWaiting) return;
setIsWaiting(true); setIsWaiting(true);
SendMessageHook(new RoomWidgetCameraPurchaseComposer()); SendMessageHook(new PurchasePhotoMessageComposer('1_1627697499'));
return; return;
case 'publish': case 'publish':
if(isWaiting) return; if(isWaiting) return;
setIsWaiting(true); setIsWaiting(true);
SendMessageHook(new RoomWidgetCameraPublishComposer()); SendMessageHook(new PublishPhotoMessageComposer());
return; return;
case 'cancel': case 'cancel':
onCancelClick(); onCancelClick();
@ -88,7 +87,7 @@ export const CameraWidgetCheckoutView: FC<CameraWidgetCheckoutViewProps> = props
<div className="bg-muted rounded p-2 text-black mb-2"> <div className="bg-muted rounded p-2 text-black mb-2">
<div className="d-flex justify-content-between align-items-center"> <div className="d-flex justify-content-between align-items-center">
<div className="me-2"> <div className="me-2">
<div className="fw-bold d-flex justify-content-start">{ LocalizeText(wasPicturePublished ? 'camera.publish.successful' : 'camera.publish.explanation') }{ !wasPicturePublished && price.points > 0 && <>: { price.points } <CurrencyIcon type={ price.pointsType } /></> }</div> <div className="fw-bold d-flex justify-content-start">{ LocalizeText(wasPicturePublished ? 'camera.publish.successful' : 'camera.publish.explanation') }{ !wasPicturePublished && price.duckets > 0 && <>: { price.duckets } <CurrencyIcon type={ price.publishDucketPrice } /></> }</div>
<div>{ LocalizeText(wasPicturePublished ? 'camera.publish.success.short.info' : 'camera.publish.detailed.explanation') }</div> <div>{ LocalizeText(wasPicturePublished ? 'camera.publish.success.short.info' : 'camera.publish.detailed.explanation') }</div>
{ wasPicturePublished && <a href="#">{ LocalizeText('camera.link.to.published') }</a> } { wasPicturePublished && <a href="#">{ LocalizeText('camera.link.to.published') }</a> }
</div> </div>

View File

@ -2,5 +2,5 @@ export interface CameraWidgetCheckoutViewProps
{ {
onCloseClick: () => void; onCloseClick: () => void;
onCancelClick: () => void; onCancelClick: () => void;
price: {credits: Number, points: Number, pointsType: number}; price: { credits: number, duckets: number, publishDucketPrice: number };
} }

View File

@ -7,17 +7,15 @@ import { LocalizeText } from '../../../../../../utils/LocalizeText';
import { useCameraWidgetContext } from '../../context/CameraWidgetContext'; import { useCameraWidgetContext } from '../../context/CameraWidgetContext';
import { CameraWidgetEditorTabs, CameraWidgetEditorViewProps } from './CameraWidgetEditorView.types'; import { CameraWidgetEditorTabs, CameraWidgetEditorViewProps } from './CameraWidgetEditorView.types';
export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
{
const { availableEffects = null, myLevel = null, onCloseClick = null, onCancelClick = null, onCheckoutClick = null } = props;
const TABS: string[] = [ CameraWidgetEditorTabs.COLORMATRIX, CameraWidgetEditorTabs.COMPOSITE ]; const TABS: string[] = [ CameraWidgetEditorTabs.COLORMATRIX, CameraWidgetEditorTabs.COMPOSITE ];
const cameraWidgetContext = useCameraWidgetContext(); export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
{
const [ currentTab, setCurrentTab ] = useState(CameraWidgetEditorTabs.COLORMATRIX); const { availableEffects = null, myLevel = 1, onClose = null, onCancel = null, onCheckout = null } = props;
const [ currentTab, setCurrentTab ] = useState(TABS[0]);
const [ selectedEffectName, setSelectedEffectName ] = useState(null); const [ selectedEffectName, setSelectedEffectName ] = useState(null);
const [ effectsThumbnails, setEffectsThumbnails ] = useState<{ name: string, image: HTMLImageElement }[]>([]); const [ effectsThumbnails, setEffectsThumbnails ] = useState<{ name: string, image: HTMLImageElement }[]>([]);
const { cameraRoll = null, selectedPictureIndex = -1, selectedEffects = null, isZoomed = false, setSelectedEffects = null, setIsZoomed = null } = useCameraWidgetContext();
useEffect(() => useEffect(() =>
{ {
@ -25,11 +23,11 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
for(const effect of availableEffects) for(const effect of availableEffects)
{ {
thumbnails.push({name: effect.name, image: GetRoomCameraWidgetManager().applyEffects(cameraWidgetContext.cameraRoll[cameraWidgetContext.selectedPictureIndex], [ new RoomCameraWidgetSelectedEffect(effect, 1) ], false)}); thumbnails.push({name: effect.name, image: GetRoomCameraWidgetManager().applyEffects(cameraRoll[selectedPictureIndex].texture, [ new RoomCameraWidgetSelectedEffect(effect, 1) ], false)});
} }
setEffectsThumbnails(thumbnails); setEffectsThumbnails(thumbnails);
}, [ cameraWidgetContext, availableEffects ]); }, [ cameraRoll, selectedPictureIndex, availableEffects ]);
const getEffectThumbnail = useCallback((effectName: string) => const getEffectThumbnail = useCallback((effectName: string) =>
{ {
@ -54,24 +52,24 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
const getCurrentPicture = useCallback(() => const getCurrentPicture = useCallback(() =>
{ {
return GetRoomCameraWidgetManager().applyEffects(cameraWidgetContext.cameraRoll[cameraWidgetContext.selectedPictureIndex], cameraWidgetContext.selectedEffects, cameraWidgetContext.isZoomed); return GetRoomCameraWidgetManager().applyEffects(cameraRoll[selectedPictureIndex].texture, selectedEffects, isZoomed);
}, [ cameraWidgetContext ]); }, [ cameraRoll, selectedPictureIndex, selectedEffects, isZoomed ]);
const getCurrentEffectAlpha = useCallback(() => const getCurrentEffectAlpha = useCallback(() =>
{ {
if(!selectedEffectName) return 0; if(!selectedEffectName) return 0;
const selectedEffect = cameraWidgetContext.selectedEffects.find(effect => effect.effect.name === selectedEffectName); const selectedEffect = selectedEffects.find(effect => effect.effect.name === selectedEffectName);
if(!selectedEffect) return 0; if(!selectedEffect) return 0;
return selectedEffect.alpha; return selectedEffect.alpha;
}, [ selectedEffectName, cameraWidgetContext.selectedEffects ]); }, [ selectedEffectName, selectedEffects ]);
const getEffectIndex = useCallback((effectName) => const getEffectIndex = useCallback((effectName) =>
{ {
return cameraWidgetContext.selectedEffects.findIndex(effect => effect.effect.name === effectName); return selectedEffects.findIndex(effect => effect.effect.name === effectName);
}, [ cameraWidgetContext.selectedEffects ]) }, [ selectedEffects ])
const setSelectedEffectAlpha = useCallback((newAlpha: number) => const setSelectedEffectAlpha = useCallback((newAlpha: number) =>
{ {
@ -81,27 +79,27 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
if(selectedEffectIndex === -1) return; if(selectedEffectIndex === -1) return;
const clone = Array.from(cameraWidgetContext.selectedEffects); const clone = Array.from(selectedEffects);
const selectedEffect = clone[selectedEffectIndex]; const selectedEffect = clone[selectedEffectIndex];
clone[selectedEffectIndex] = new RoomCameraWidgetSelectedEffect(selectedEffect.effect, newAlpha); clone[selectedEffectIndex] = new RoomCameraWidgetSelectedEffect(selectedEffect.effect, newAlpha);
cameraWidgetContext.setSelectedEffects(clone); setSelectedEffects(clone);
}, [ selectedEffectName, getEffectIndex, cameraWidgetContext ]); }, [ selectedEffectName, getEffectIndex, selectedEffects, setSelectedEffects ]);
const processAction = useCallback((type: string, value: string | number = null) => const processAction = useCallback((type: string, value: string | number = null) =>
{ {
switch(type) switch(type)
{ {
case 'close': case 'close':
onCloseClick(); onClose();
return; return;
case 'cancel': case 'cancel':
onCancelClick(); onCancel();
return; return;
case 'checkout': case 'checkout':
onCheckoutClick(); onCheckout();
return; return;
case 'change_tab': case 'change_tab':
setCurrentTab(String(value)); setCurrentTab(String(value));
@ -110,7 +108,7 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
{ {
let existingIndex = -1; let existingIndex = -1;
if(cameraWidgetContext.selectedEffects.length > 0) if(selectedEffects.length > 0)
{ {
existingIndex = getEffectIndex(value); existingIndex = getEffectIndex(value);
} }
@ -123,7 +121,7 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
if(effect.minLevel > myLevel) return; if(effect.minLevel > myLevel) return;
cameraWidgetContext.setSelectedEffects([...cameraWidgetContext.selectedEffects, new RoomCameraWidgetSelectedEffect(effect, 0.5)]); setSelectedEffects([...selectedEffects, new RoomCameraWidgetSelectedEffect(effect, 0.5)]);
} }
if(effect && effect.minLevel > myLevel) return; if(effect && effect.minLevel > myLevel) return;
@ -144,32 +142,32 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
if(existingIndex > -1) if(existingIndex > -1)
{ {
const effect = cameraWidgetContext.selectedEffects[existingIndex]; const effect = selectedEffects[existingIndex];
if(effect.effect.name === selectedEffectName) if(effect.effect.name === selectedEffectName)
{ {
setSelectedEffectName(null); setSelectedEffectName(null);
} }
const clone = Array.from(cameraWidgetContext.selectedEffects); const clone = Array.from(selectedEffects);
clone.splice(existingIndex, 1); clone.splice(existingIndex, 1);
cameraWidgetContext.setSelectedEffects(clone); setSelectedEffects(clone);
} }
} }
return; return;
case 'clear_effects': case 'clear_effects':
setSelectedEffectName(null); setSelectedEffectName(null);
cameraWidgetContext.setSelectedEffects([]); setSelectedEffects([]);
return; return;
case 'download': case 'download':
window.open(getCurrentPicture().src, '_blank'); window.open(getCurrentPicture().src, '_blank');
return; return;
case 'zoom': case 'zoom':
cameraWidgetContext.setIsZoomed(!cameraWidgetContext.isZoomed); setIsZoomed(!isZoomed);
return; return;
} }
}, [onCloseClick, onCancelClick, onCheckoutClick, cameraWidgetContext, getCurrentPicture, myLevel, selectedEffectName, getEffectIndex, availableEffects]); }, [ onClose, onCancel, onCheckout, getCurrentPicture, myLevel, selectedEffectName, getEffectIndex, availableEffects, isZoomed, setIsZoomed, selectedEffects, setSelectedEffects ]);
return ( return (
<NitroCardView className="nitro-camera-editor"> <NitroCardView className="nitro-camera-editor">
@ -219,7 +217,7 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
<div className="btn-group"> <div className="btn-group">
<button className="btn btn-primary" onClick={ event => processAction('clear_effects') }><i className="fas fa-trash"></i></button> <button className="btn btn-primary" onClick={ event => processAction('clear_effects') }><i className="fas fa-trash"></i></button>
<button className="btn btn-primary" onClick={ event => processAction('download') }><i className="fas fa-save"></i></button> <button className="btn btn-primary" onClick={ event => processAction('download') }><i className="fas fa-save"></i></button>
<button className="btn btn-primary" onClick={ event => processAction('zoom') }><i className={"fas " + classNames({'fa-search-plus': !cameraWidgetContext.isZoomed, 'fa-search-minus': cameraWidgetContext.isZoomed})}></i></button> <button className="btn btn-primary" onClick={ event => processAction('zoom') }><i className={"fas " + classNames({'fa-search-plus': !isZoomed, 'fa-search-minus': isZoomed})}></i></button>
</div> </div>
<div className="d-flex justify-content-end"> <div className="d-flex justify-content-end">
<button className="btn btn-primary me-2" onClick={ event => processAction('cancel') }>{ LocalizeText('generic.cancel') }</button> <button className="btn btn-primary me-2" onClick={ event => processAction('cancel') }>{ LocalizeText('generic.cancel') }</button>

View File

@ -4,9 +4,9 @@ export interface CameraWidgetEditorViewProps
{ {
availableEffects: IRoomCameraWidgetEffect[]; availableEffects: IRoomCameraWidgetEffect[];
myLevel: number; myLevel: number;
onCloseClick: () => void; onClose: () => void;
onCancelClick: () => void; onCancel: () => void;
onCheckoutClick: () => void; onCheckout: () => void;
} }
export class CameraWidgetEditorTabs export class CameraWidgetEditorTabs