+
{ props.children }
);
diff --git a/src/layout/card/content/NitroCardContextView.types.ts b/src/layout/card/content/NitroCardContextView.types.ts
index ae0dfc8a..78ff6979 100644
--- a/src/layout/card/content/NitroCardContextView.types.ts
+++ b/src/layout/card/content/NitroCardContextView.types.ts
@@ -1,4 +1,4 @@
export interface NitroCardContentViewProps
{
- isDark?: boolean;
+ className?: string;
}
diff --git a/src/views/room/widgets/avatar-info/AvatarInfoWidgetView.tsx b/src/views/room/widgets/avatar-info/AvatarInfoWidgetView.tsx
index 12d825da..61d859a0 100644
--- a/src/views/room/widgets/avatar-info/AvatarInfoWidgetView.tsx
+++ b/src/views/room/widgets/avatar-info/AvatarInfoWidgetView.tsx
@@ -34,7 +34,7 @@ export const AvatarInfoWidgetView: FC
= props =>
return value;
});
- return [ ...existing, new RoomObjectNameData(nameData.objectId, nameData.category, nameData.id, nameData.name, nameData.type) ]
+ return [ ...existing, nameData ]
});
}
return;
diff --git a/src/views/room/widgets/camera/CameraWidgetView.scss b/src/views/room/widgets/camera/CameraWidgetView.scss
index 4b7f8363..e24d2e21 100644
--- a/src/views/room/widgets/camera/CameraWidgetView.scss
+++ b/src/views/room/widgets/camera/CameraWidgetView.scss
@@ -1,29 +1,94 @@
-.nitro-camera {
- width: 340px;
- height: 462px;
+.nitro-camera-capture {
+ .camera-canvas {
+ width: 340px;
+ 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');
+ .camera-frame {
+ position: absolute;
+ width: 318px;
+ height: 318px;
+ margin-top: 9px;
+ margin-left: 11.4px;
- .camera-frame {
- width: 300px;
- height: 300px;
+ &.bg {
+ background: black;
+ }
+
+ .camera-frame-preview-actions {
+ background: rgba(0, 0, 0, .5);
+ }
+ }
+
+ .camera-button {
+ width: 94px;
+ height: 94px;
+ cursor: pointer;
+ margin-top: 334px;
+
+ background-image: url('../../../../assets/images/room-widgets/camera-widget/camera-spritesheet.png');
+ background-position: -340px 0px;
+
+ &:hover {
+ background-position: -340px -94px;
+ }
+
+ &:active {
+ background-position: -340px -188px;
+ }
+ }
}
- .camera-button {
- width: 94px;
- height: 94px;
- cursor: pointer;
- margin-top: 334px;
+ .camera-roll {
+ width: 330px;
+ background: #bab8b4;
+ border-bottom-left-radius: 10px;
+ border-bottom-right-radius: 10px;
+ border: 1px solid black;
+ box-shadow: inset 1px 0px white, inset -1px -1px white;
- background-image: url('../../../../assets/images/room-widgets/camera-widget/camera-spritesheet.png');
- background-position: -340px 0px;
-
- &:hover {
- background-position: -340px -94px;
- }
-
- &:active {
- background-position: -340px -188px;
+ img {
+ width: 56px;
+ height: 56px;
+ border: 1px solid black;
+ }
+ }
+}
+
+.nitro-camera-editor {
+ width: 600px;
+
+ .effects {
+ max-height: 354px;
+
+ .effect-thumbnail {
+ border-color: $grid-border-color !important;
+ background-color: $grid-bg-color;
+
+ &.active {
+ border-color: $grid-active-border-color !important;
+ background-color: $grid-active-bg-color;
+ }
+
+ .effect-thumbnail-image {
+ background: black;
+
+ img {
+ width: 56px;
+ height: 56px;
+ }
+ }
+ }
+ }
+
+ .picture-preview {
+ width: 318px;
+ height: 318px;
+
+ &.zoomed {
+ background-size: 636px;
+ background-position: center;
}
}
}
diff --git a/src/views/room/widgets/camera/CameraWidgetView.tsx b/src/views/room/widgets/camera/CameraWidgetView.tsx
index 6b43ad2c..6e3a3ad3 100644
--- a/src/views/room/widgets/camera/CameraWidgetView.tsx
+++ b/src/views/room/widgets/camera/CameraWidgetView.tsx
@@ -1,72 +1,75 @@
-import { NitroRectangle } from 'nitro-renderer';
-import { FC, useCallback, useRef, useState } from 'react';
-import { GetRoomEngine, GetRoomSession } from '../../../../api';
+import { RoomCameraWidgetEditorEffect } from 'nitro-renderer/src/nitro/room/camera-widget/RoomCameraWidgetEditorEffect';
+import { RoomCameraWidgetManagerEvent } from 'nitro-renderer/src/nitro/room/events/RoomCameraWidgetManagerEvent';
+import { FC, useCallback, useState } from 'react';
+import { GetRoomEngine } from '../../../../api';
import { RoomWidgetCameraEvent } from '../../../../events/room-widgets/camera/RoomWidgetCameraEvent';
-import { DraggableWindow } from '../../../../hooks/draggable-window/DraggableWindow';
+import { useRoomEngineEvent } from '../../../../hooks/events/nitro/room/room-engine-event';
import { useUiEvent } from '../../../../hooks/events/ui/ui-event';
import { CameraWidgetViewProps } from './CameraWidgetView.types';
+import { CameraWidgetCaptureView } from './views/capture/CameraWidgetCaptureView';
+import { CameraWidgetEditorView } from './views/editor/CameraWidgetEditorView';
export const CameraWidgetView: FC = props =>
{
- const [ isVisible, setIsVisible ] = useState(false);
- const cameraFrameRef = useRef();
+ const [ isCaptureVisible, setIsCaptureVisible ] = useState(false);
+ const [ isEditorVisible, setIsEditorVisible ] = useState(false);
+ const [ chosenPicture, setChosenPicture ] = useState(null);
+ const [ availableEffects, setAvailableEffects ] = useState(null);
- const onRoomWidgetCameraEvent = useCallback((event: RoomWidgetCameraEvent) =>
+ const getAvailableEffects = useCallback(() =>
+ {
+ if(GetRoomEngine().roomCameraWidgetManager.isLoaded)
+ {
+ setAvailableEffects(Array.from(GetRoomEngine().roomCameraWidgetManager.loadedEffects.values()));
+ }
+ }, []);
+
+ const onNitroEvent = useCallback((event: RoomWidgetCameraEvent) =>
{
switch(event.type)
{
case RoomWidgetCameraEvent.SHOW_CAMERA:
- setIsVisible(true);
+ setIsCaptureVisible(true);
+ getAvailableEffects();
return;
case RoomWidgetCameraEvent.HIDE_CAMERA:
- setIsVisible(false);
+ setIsCaptureVisible(false);
+ setIsEditorVisible(false);
return;
case RoomWidgetCameraEvent.TOGGLE_CAMERA:
- setIsVisible(value => !value);
+ setIsEditorVisible(false);
+ setIsCaptureVisible(value => !value);
+ getAvailableEffects();
+ return;
+ case RoomCameraWidgetManagerEvent.INITIALIZED:
+ getAvailableEffects();
return;
}
}, []);
- useUiEvent(RoomWidgetCameraEvent.SHOW_CAMERA, onRoomWidgetCameraEvent);
- useUiEvent(RoomWidgetCameraEvent.HIDE_CAMERA, onRoomWidgetCameraEvent);
- useUiEvent(RoomWidgetCameraEvent.TOGGLE_CAMERA, onRoomWidgetCameraEvent);
+ useUiEvent(RoomWidgetCameraEvent.SHOW_CAMERA, onNitroEvent);
+ useUiEvent(RoomWidgetCameraEvent.HIDE_CAMERA, onNitroEvent);
+ useUiEvent(RoomWidgetCameraEvent.TOGGLE_CAMERA, onNitroEvent);
+ useRoomEngineEvent(RoomCameraWidgetManagerEvent.INITIALIZED, onNitroEvent);
- const processAction = useCallback((type: string, value: string = null) =>
+ const processAction = useCallback((type: string, value: any = null) =>
{
switch(type)
{
case 'close':
- setIsVisible(false);
+ setIsCaptureVisible(false);
+ setIsEditorVisible(false);
+ return;
+ case 'capture_choose_picture':
+ setChosenPicture(value);
+ setIsCaptureVisible(false);
+ setIsEditorVisible(true);
return;
}
}, []);
- const takePicture = useCallback(() =>
- {
- const frameBounds = cameraFrameRef.current.getBoundingClientRect();
-
- if(!frameBounds) return;
-
- const rectangle = new NitroRectangle(Math.floor(frameBounds.x), Math.floor(frameBounds.y), Math.floor(frameBounds.width), Math.floor(frameBounds.height));
-
- GetRoomEngine().createRoomScreenshot(GetRoomSession().roomId, 1, rectangle);
- }, []);
-
- if(!isVisible) return null;
-
return (
-
-
-
-
processAction('close') }>
-
-
-
-
-
-
+ ( isCaptureVisible && processAction('close') } onChoosePicture={ (picture) => processAction('capture_choose_picture', picture) } /> ) ||
+ ( isEditorVisible && processAction('close') } picture={ chosenPicture } availableEffects={ availableEffects } /> )
);
}
diff --git a/src/views/room/widgets/camera/views/capture/CameraWidgetCaptureView.tsx b/src/views/room/widgets/camera/views/capture/CameraWidgetCaptureView.tsx
new file mode 100644
index 00000000..e57f2626
--- /dev/null
+++ b/src/views/room/widgets/camera/views/capture/CameraWidgetCaptureView.tsx
@@ -0,0 +1,102 @@
+import classNames from 'classnames';
+import { NitroRectangle } from 'nitro-renderer';
+import { FC, useCallback, useRef, useState } from 'react';
+import { GetRoomEngine } from '../../../../../../api/nitro/room/GetRoomEngine';
+import { GetRoomSession } from '../../../../../../api/nitro/session/GetRoomSession';
+import { DraggableWindow } from '../../../../../../hooks/draggable-window/DraggableWindow';
+import { LocalizeText } from '../../../../../../utils/LocalizeText';
+import { CameraWidgetCaptureViewProps } from './CameraWidgetCaptureView.types';
+
+export const CameraWidgetCaptureView: FC = props =>
+{
+ const CAMERA_ROLL_LIMIT: number = 5;
+
+ const [ picturesTaken, setPicturesTaken ] = useState([]);
+ const [ selectedPictureIndex, setSelectedPictureIndex ] = useState(-1);
+ const cameraFrameRef = useRef();
+
+ const takePicture = useCallback(() =>
+ {
+ if(selectedPictureIndex > -1)
+ {
+ setSelectedPictureIndex(-1);
+ return;
+ }
+
+ const frameBounds = cameraFrameRef.current.getBoundingClientRect();
+
+ if(!frameBounds) return;
+
+ const rectangle = new NitroRectangle(Math.floor(frameBounds.x), Math.floor(frameBounds.y), Math.floor(frameBounds.width), Math.floor(frameBounds.height));
+
+ const image = GetRoomEngine().createRoomScreenshot(GetRoomSession().roomId, 1, rectangle);
+
+ if(picturesTaken.length + 1 === CAMERA_ROLL_LIMIT)
+ {
+ alert(LocalizeText('camera.full.body'));
+ }
+
+ if(picturesTaken.length === CAMERA_ROLL_LIMIT)
+ {
+ setPicturesTaken(picturesTaken => [ ...picturesTaken.slice(0, CAMERA_ROLL_LIMIT - 1), image ]);
+ }
+ else
+ {
+ setPicturesTaken(picturesTaken => [ ...picturesTaken, image ]);
+ }
+ }, [ picturesTaken, selectedPictureIndex ]);
+
+ const processAction = useCallback((type: string, value: string | number = null) =>
+ {
+ switch(type)
+ {
+ case 'take_picture':
+ takePicture();
+ return;
+ case 'preview_picture':
+ setSelectedPictureIndex(Number(value));
+ return;
+ case 'discard_picture':
+ setSelectedPictureIndex(-1);
+ const newPicturesTaken = picturesTaken;
+ picturesTaken.splice(selectedPictureIndex, 1);
+ setPicturesTaken(newPicturesTaken);
+ return;
+ case 'edit_picture':
+ props.onChoosePicture(picturesTaken[selectedPictureIndex]);
+ return;
+ }
+ }, [ picturesTaken, selectedPictureIndex ]);
+
+ return (
+
+
+
+
+
-1}) }>
+ { selectedPictureIndex > -1 &&
+
+
+
+
+
+
}
+
+
+
+ { picturesTaken.length > 0 &&
+ { picturesTaken.map((picture, index) =>
+ {
+ return
processAction('preview_picture', index) } />;
+ }) }
+
}
+
+
+ );
+}
diff --git a/src/views/room/widgets/camera/views/capture/CameraWidgetCaptureView.types.ts b/src/views/room/widgets/camera/views/capture/CameraWidgetCaptureView.types.ts
new file mode 100644
index 00000000..435e6d29
--- /dev/null
+++ b/src/views/room/widgets/camera/views/capture/CameraWidgetCaptureView.types.ts
@@ -0,0 +1,5 @@
+export interface CameraWidgetCaptureViewProps
+{
+ onCloseClick: () => void;
+ onChoosePicture: (picture: HTMLImageElement) => void;
+}
diff --git a/src/views/room/widgets/camera/views/editor/CameraWidgetEditorView.tsx b/src/views/room/widgets/camera/views/editor/CameraWidgetEditorView.tsx
new file mode 100644
index 00000000..0153ced7
--- /dev/null
+++ b/src/views/room/widgets/camera/views/editor/CameraWidgetEditorView.tsx
@@ -0,0 +1,127 @@
+import classNames from 'classnames';
+import { RoomCameraWidgetEditorSelectedEffect } from 'nitro-renderer/src/nitro/room/camera-widget/RoomCameraWidgetEditorSelectedEffect';
+import { FC, useCallback, useEffect, useState } from 'react';
+import { GetRoomEngine } from '../../../../../../api';
+import { NitroCardContentView } from '../../../../../../layout/card/content/NitroCardContentView';
+import { NitroCardHeaderView } from '../../../../../../layout/card/header/NitroCardHeaderView';
+import { NitroCardView } from '../../../../../../layout/card/NitroCardView';
+import { NitroCardTabsView } from '../../../../../../layout/card/tabs/NitroCardTabsView';
+import { NitroCardTabsItemView } from '../../../../../../layout/card/tabs/tabs-item/NitroCardTabsItemView';
+import { LocalizeText } from '../../../../../../utils/LocalizeText';
+import { CameraWidgetEditorTabs, CameraWidgetEditorViewProps } from './CameraWidgetEditorView.types';
+
+export const CameraWidgetEditorView: FC = props =>
+{
+ const TABS: string[] = [ CameraWidgetEditorTabs.COLORMATRIX, CameraWidgetEditorTabs.COMPOSITE ];
+ const MY_LEVEL: number = 0;
+
+ const [ currentTab, setCurrentTab ] = useState(CameraWidgetEditorTabs.COLORMATRIX);
+ const [ isZoomed, setIsZoomed ] = useState(false);
+ const [ selectedEffects, setSelectedEffects ] = useState([]);
+ const [ effectsThumbnails, setEffectThumbnails ] = useState<{name: string, image: HTMLImageElement}[]>([]);
+
+ useEffect(() =>
+ {
+ const thumbnails = [];
+
+ for(const effect of props.availableEffects)
+ {
+ let alpha = 126;
+
+ if(effect.colorMatrix.length > 0) alpha = 0.5;
+
+ thumbnails.push({name: effect.name, image: GetRoomEngine().roomCameraWidgetManager.editImage(props.picture, [new RoomCameraWidgetEditorSelectedEffect(effect, alpha)])});
+ }
+
+ setEffectThumbnails(thumbnails);
+ }, [ props.availableEffects ]);
+
+ const getEffectThumbnail = useCallback((effectName: string) =>
+ {
+ const search = effectsThumbnails.find(thumbnail => thumbnail.name === effectName);
+
+ if(search) return search.image.src;
+
+ return null;
+ }, [ effectsThumbnails ]);
+
+ const getEffectList = useCallback(() =>
+ {
+ if(currentTab === CameraWidgetEditorTabs.COLORMATRIX)
+ {
+ return props.availableEffects.filter(effect => effect.colorMatrix.length > 0);
+ }
+ else
+ {
+ return props.availableEffects.filter(effect => effect.colorMatrix.length === 0);
+ }
+ }, [ currentTab, props.availableEffects ]);
+
+ const processAction = useCallback((type: string, value: string | number = null) =>
+ {
+ switch(type)
+ {
+ case 'close':
+ props.onCloseClick();
+ return;
+ case 'change_tab':
+ setCurrentTab(String(value));
+ return;
+ case 'zoom':
+ setIsZoomed(isZoomed => !isZoomed);
+ return;
+ }
+ }, []);
+
+ return (
+
+ processAction('close') } />
+
+
+
+ { TABS.map(tab =>
+ {
+ return processAction('change_tab', tab) }>
+ }) }
+
+
+
+
+ { getEffectList().map(effect =>
+ {
+ return (
+
+
+
+
+
+
+
+ );
+ }) }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/views/room/widgets/camera/views/editor/CameraWidgetEditorView.types.ts b/src/views/room/widgets/camera/views/editor/CameraWidgetEditorView.types.ts
new file mode 100644
index 00000000..4a3407c6
--- /dev/null
+++ b/src/views/room/widgets/camera/views/editor/CameraWidgetEditorView.types.ts
@@ -0,0 +1,14 @@
+import { RoomCameraWidgetEditorEffect } from '../../../../../../../../nitro-renderer/src/nitro/room/camera-widget/RoomCameraWidgetEditorEffect';
+
+export interface CameraWidgetEditorViewProps
+{
+ onCloseClick: () => void;
+ picture: HTMLImageElement;
+ availableEffects: RoomCameraWidgetEditorEffect[];
+}
+
+export class CameraWidgetEditorTabs
+{
+ public static readonly COLORMATRIX: string = 'colormatrix';
+ public static readonly COMPOSITE: string = 'composite';
+}