mirror of
https://github.com/billsonnn/nitro-react.git
synced 2024-11-26 23:50:52 +01:00
Update camera
This commit is contained in:
parent
188ac1bc5d
commit
0a8e8fa67a
@ -62,6 +62,11 @@ $messenger-height: 370px;
|
||||
$marketplace-post-offer-width: 430px;
|
||||
$marketplace-post-offer-height: 250px;
|
||||
|
||||
$camera-editor-width: 600px;
|
||||
$camera-editor-height: 500px;
|
||||
|
||||
$camera-checkout-width: 350px;
|
||||
|
||||
$room-info-width: 325px;
|
||||
|
||||
.nitro-app {
|
||||
|
@ -85,89 +85,41 @@
|
||||
}
|
||||
|
||||
.nitro-camera-editor {
|
||||
width: 600px;
|
||||
|
||||
.content-area {
|
||||
min-height: 441px;
|
||||
height: 441px;
|
||||
resize: vertical;
|
||||
width: $camera-editor-width;
|
||||
height: $camera-editor-height;
|
||||
|
||||
.picture-preview {
|
||||
width: 320px;
|
||||
height: 320px;
|
||||
|
||||
.slider {
|
||||
background: linear-gradient(180deg, transparent, black);
|
||||
text-shadow: 1px 1px rgba(0, 0, 0, .5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.effect-grid {
|
||||
|
||||
.grid-item-container {
|
||||
.layout-grid-item {
|
||||
height: 60px !important;
|
||||
max-height: 60px !important;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
overflow: unset;
|
||||
.effect-thumbnail-image {
|
||||
|
||||
img {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
image-rendering: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.remove-effect {
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
right: -1px;
|
||||
top: 1px;
|
||||
right: 1px;
|
||||
padding: 2px;
|
||||
font-size: 11px;
|
||||
line-height: 11px;
|
||||
font-size: 10px;
|
||||
min-height: unset;
|
||||
}
|
||||
|
||||
.effect-thumbnail-image {
|
||||
|
||||
img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
image-rendering: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.effects {
|
||||
height: 363px;
|
||||
min-height: 363px;
|
||||
max-height: 363px;
|
||||
|
||||
.btn-remove-effect {
|
||||
z-index: 10;
|
||||
min-height: 0px;
|
||||
border-radius: 100px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.effect-thumbnail {
|
||||
border-color: $grid-border-color !important;
|
||||
background-color: $grid-bg-color;
|
||||
height: 60px;
|
||||
|
||||
&.active {
|
||||
border-color: $grid-active-border-color !important;
|
||||
background-color: $grid-active-bg-color;
|
||||
}
|
||||
|
||||
.effect-thumbnail-image {
|
||||
img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nitro-camera-checkout {
|
||||
width: 388px;
|
||||
width: $camera-checkout-width;
|
||||
|
||||
.picture-preview {
|
||||
width: 320px;
|
5
src/components/camera/common/CameraEditorTabs.ts
Normal file
5
src/components/camera/common/CameraEditorTabs.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export class CameraEditorTabs
|
||||
{
|
||||
public static readonly COLORMATRIX: string = 'colormatrix';
|
||||
public static readonly COMPOSITE: string = 'composite';
|
||||
}
|
24
src/components/camera/context/CameraWidgetContext.tsx
Normal file
24
src/components/camera/context/CameraWidgetContext.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { createContext, Dispatch, FC, ProviderProps, SetStateAction, useContext } from 'react';
|
||||
import { CameraPicture } from '../common/CameraPicture';
|
||||
|
||||
export interface ICameraWidgetContext
|
||||
{
|
||||
cameraRoll: CameraPicture[],
|
||||
setCameraRoll: Dispatch<SetStateAction<CameraPicture[]>>;
|
||||
selectedPictureIndex: number,
|
||||
setSelectedPictureIndex: Dispatch<SetStateAction<number>>;
|
||||
}
|
||||
|
||||
const CameraWidgetContext = createContext<ICameraWidgetContext>({
|
||||
cameraRoll: null,
|
||||
setCameraRoll: null,
|
||||
selectedPictureIndex: null,
|
||||
setSelectedPictureIndex: null
|
||||
});
|
||||
|
||||
export const CameraWidgetContextProvider: FC<ProviderProps<ICameraWidgetContext>> = props =>
|
||||
{
|
||||
return <CameraWidgetContext.Provider value={ props.value }>{ props.children }</CameraWidgetContext.Provider>
|
||||
}
|
||||
|
||||
export const useCameraWidgetContext = () => useContext(CameraWidgetContext);
|
@ -1,11 +1,21 @@
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { NitroRectangle, TextureUtils } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useRef } from 'react';
|
||||
import { GetRoomEngine, GetRoomSession, LocalizeText } from '../../../../api';
|
||||
import { CAMERA_SHUTTER, PlaySound } from '../../../../api/utils/PlaySound';
|
||||
import { Column } from '../../../../common/Column';
|
||||
import { Flex } from '../../../../common/Flex';
|
||||
import { DraggableWindow } from '../../../../layout';
|
||||
import { NotificationUtilities } from '../../../../views/notification-center/common/NotificationUtilities';
|
||||
import { CameraPicture } from '../../common/CameraPicture';
|
||||
import { useCameraWidgetContext } from '../../context/CameraWidgetContext';
|
||||
import { CameraWidgetCaptureViewProps } from './CameraWidgetCaptureView.types';
|
||||
|
||||
export interface CameraWidgetCaptureViewProps
|
||||
{
|
||||
onClose: () => void;
|
||||
onEdit: () => void;
|
||||
onDelete: () => void;
|
||||
}
|
||||
|
||||
const CAMERA_ROLL_LIMIT: number = 5;
|
||||
|
||||
@ -40,7 +50,7 @@ export const CameraWidgetCaptureView: FC<CameraWidgetCaptureViewProps> = props =
|
||||
|
||||
if(clone.length >= CAMERA_ROLL_LIMIT)
|
||||
{
|
||||
alert(LocalizeText('camera.full.body'));
|
||||
NotificationUtilities.simpleAlert(LocalizeText('camera.full.body'));
|
||||
|
||||
clone.pop();
|
||||
}
|
||||
@ -52,12 +62,12 @@ export const CameraWidgetCaptureView: FC<CameraWidgetCaptureViewProps> = props =
|
||||
}, [ cameraRoll, selectedPictureIndex, getCameraBounds, setCameraRoll, setSelectedPictureIndex ]);
|
||||
|
||||
return (
|
||||
<DraggableWindow>
|
||||
<div className="d-flex flex-column justify-content-center align-items-center nitro-camera-capture">
|
||||
<DraggableWindow uniqueKey="nitro-camera-capture">
|
||||
<Column center className="nitro-camera-capture" gap={ 0 }>
|
||||
{ selectedPicture && <img alt="" className="camera-area" src={ selectedPicture.imageUrl } /> }
|
||||
<div className="camera-canvas drag-handler">
|
||||
<div className="position-absolute header-close" onClick={ onClose }>
|
||||
<i className="fas fa-times" />
|
||||
<FontAwesomeIcon icon="times" />
|
||||
</div>
|
||||
{ !selectedPicture && <div ref={ elementRef } className="camera-area camera-view-finder" /> }
|
||||
{ selectedPicture &&
|
||||
@ -72,13 +82,13 @@ export const CameraWidgetCaptureView: FC<CameraWidgetCaptureViewProps> = props =
|
||||
</div>
|
||||
</div>
|
||||
{ (cameraRoll.length > 0) &&
|
||||
<div className="camera-roll d-flex justify-content-center py-2">
|
||||
<Flex gap={ 2 } justifyContent="center" className="camera-roll d-flex justify-content-center py-2">
|
||||
{ cameraRoll.map((picture, index) =>
|
||||
{
|
||||
return <img alt="" key={ index } className={ (index < (cameraRoll.length - 1) ? 'me-2' : '') } src={ picture.imageUrl } onClick={ event => setSelectedPictureIndex(index) } />;
|
||||
return <img alt="" key={ index } src={ picture.imageUrl } onClick={ event => setSelectedPictureIndex(index) } />;
|
||||
}) }
|
||||
</div> }
|
||||
</div>
|
||||
</Flex> }
|
||||
</Column>
|
||||
</DraggableWindow>
|
||||
);
|
||||
}
|
@ -0,0 +1,175 @@
|
||||
import { CameraPublishStatusMessageEvent, CameraPurchaseOKMessageEvent, CameraStorageUrlMessageEvent, PublishPhotoMessageComposer, PurchasePhotoMessageComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { GetConfiguration, GetRoomEngine, LocalizeText } from '../../../../api';
|
||||
import { Button } from '../../../../common/Button';
|
||||
import { Column } from '../../../../common/Column';
|
||||
import { Flex } from '../../../../common/Flex';
|
||||
import { LayoutImage } from '../../../../common/layout/LayoutImage';
|
||||
import { Text } from '../../../../common/Text';
|
||||
import { InventoryEvent } from '../../../../events';
|
||||
import { BatchUpdates, CreateMessageHook, dispatchUiEvent, SendMessageHook } from '../../../../hooks';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout';
|
||||
import { CurrencyIcon } from '../../../../views/shared/currency-icon/CurrencyIcon';
|
||||
|
||||
export interface CameraWidgetCheckoutViewProps
|
||||
{
|
||||
base64Url: string;
|
||||
onCloseClick: () => void;
|
||||
onCancelClick: () => void;
|
||||
price: { credits: number, duckets: number, publishDucketPrice: number };
|
||||
}
|
||||
|
||||
export const CameraWidgetCheckoutView: FC<CameraWidgetCheckoutViewProps> = props =>
|
||||
{
|
||||
const { base64Url = null, onCloseClick = null, onCancelClick = null, price = null } = props;
|
||||
const [ pictureUrl, setPictureUrl ] = useState<string>(null);
|
||||
const [ publishUrl, setPublishUrl ] = useState<string>(null);
|
||||
const [ picturesBought, setPicturesBought ] = useState(0);
|
||||
const [ wasPicturePublished, setWasPicturePublished ] = useState(false);
|
||||
const [ isWaiting, setIsWaiting ] = useState(false);
|
||||
const [ publishCooldown, setPublishCooldown ] = useState(0);
|
||||
|
||||
const onCameraPurchaseOKMessageEvent = useCallback((event: CameraPurchaseOKMessageEvent) =>
|
||||
{
|
||||
BatchUpdates(() =>
|
||||
{
|
||||
setPicturesBought(value => (value + 1));
|
||||
setIsWaiting(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
CreateMessageHook(CameraPurchaseOKMessageEvent, onCameraPurchaseOKMessageEvent);
|
||||
|
||||
const onCameraPublishStatusMessageEvent = useCallback((event: CameraPublishStatusMessageEvent) =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
BatchUpdates(() =>
|
||||
{
|
||||
setPublishUrl(parser.extraDataId);
|
||||
setPublishCooldown(parser.secondsToWait);
|
||||
setWasPicturePublished(parser.ok);
|
||||
setIsWaiting(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
CreateMessageHook(CameraPublishStatusMessageEvent, onCameraPublishStatusMessageEvent);
|
||||
|
||||
const onCameraStorageUrlMessageEvent = useCallback((event: CameraStorageUrlMessageEvent) =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
setPictureUrl(GetConfiguration<string>('camera.url') + '/' + parser.url);
|
||||
}, []);
|
||||
|
||||
CreateMessageHook(CameraStorageUrlMessageEvent, onCameraStorageUrlMessageEvent);
|
||||
|
||||
const processAction = (type: string, value: string | number = null) =>
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case 'close':
|
||||
onCloseClick();
|
||||
return;
|
||||
case 'buy':
|
||||
if(isWaiting) return;
|
||||
|
||||
setIsWaiting(true);
|
||||
SendMessageHook(new PurchasePhotoMessageComposer(''));
|
||||
return;
|
||||
case 'publish':
|
||||
if(isWaiting) return;
|
||||
|
||||
setIsWaiting(true);
|
||||
SendMessageHook(new PublishPhotoMessageComposer());
|
||||
return;
|
||||
case 'cancel':
|
||||
onCancelClick();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!base64Url) return;
|
||||
|
||||
GetRoomEngine().saveBase64AsScreenshot(base64Url);
|
||||
}, [ base64Url ]);
|
||||
|
||||
if(!price) return null;
|
||||
|
||||
return (
|
||||
<NitroCardView className="nitro-camera-checkout" simple={ true }>
|
||||
<NitroCardHeaderView headerText={ LocalizeText('camera.confirm_phase.title') } onCloseClick={ event => processAction('close') } />
|
||||
<NitroCardContentView>
|
||||
<Flex center>
|
||||
{ (pictureUrl && pictureUrl.length) &&
|
||||
<LayoutImage fit={ false } className="picture-preview border" imageUrl={ pictureUrl } /> }
|
||||
{ (!pictureUrl || !pictureUrl.length) &&
|
||||
<Flex center className="picture-preview border">
|
||||
<Text bold>{ LocalizeText('camera.loading') }</Text>
|
||||
</Flex> }
|
||||
</Flex>
|
||||
<Flex justifyContent="between" alignItems="center" className="bg-muted rounded p-2">
|
||||
<Column gap={ 1 }>
|
||||
<Text bold>
|
||||
{ LocalizeText('camera.purchase.header') }
|
||||
</Text>
|
||||
{ ((price.credits > 0) || (price.duckets > 0)) &&
|
||||
<Flex gap={ 1 }>
|
||||
<Text>{ LocalizeText('catalog.purchase.confirmation.dialog.cost') }</Text>
|
||||
{ (price.credits > 0) &&
|
||||
<Flex gap={ 1 }>
|
||||
<Text bold>{ price.credits }</Text>
|
||||
<CurrencyIcon type={ -1 } />
|
||||
</Flex> }
|
||||
{ (price.duckets > 0) &&
|
||||
<Flex gap={ 1 }>
|
||||
<Text bold>{ price.duckets }</Text>
|
||||
<CurrencyIcon type={ 5 } />
|
||||
</Flex> }
|
||||
</Flex> }
|
||||
{ (picturesBought > 0) &&
|
||||
<Text>
|
||||
<Text bold>{ LocalizeText('camera.purchase.count.info') }</Text> { picturesBought }
|
||||
<u className="ms-1 cursor-pointer" onClick={ () => dispatchUiEvent(new InventoryEvent(InventoryEvent.SHOW_INVENTORY)) }>{ LocalizeText('camera.open.inventory') }</u>
|
||||
</Text> }
|
||||
</Column>
|
||||
<Flex alignItems="center">
|
||||
<Button variant="success" disabled={ isWaiting } onClick={ event => processAction('buy') }>{ LocalizeText(!picturesBought ? 'buy' : 'camera.buy.another.button.text') }</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex justifyContent="between" alignItems="center" className="bg-muted rounded p-2">
|
||||
<Column gap={ 1 }>
|
||||
<Text bold>
|
||||
{ LocalizeText(wasPicturePublished ? 'camera.publish.successful' : 'camera.publish.explanation') }
|
||||
</Text>
|
||||
<Text>
|
||||
{ LocalizeText(wasPicturePublished ? 'camera.publish.success.short.info' : 'camera.publish.detailed.explanation') }
|
||||
</Text>
|
||||
{ wasPicturePublished && <a href={ publishUrl } rel="noreferrer" target="_blank">{ LocalizeText('camera.link.to.published') }</a> }
|
||||
{ !wasPicturePublished && (price.publishDucketPrice > 0) &&
|
||||
<Flex gap={ 1 }>
|
||||
<Text>{ LocalizeText('catalog.purchase.confirmation.dialog.cost') }</Text>
|
||||
<Flex gap={ 1 }>
|
||||
<Text bold>{ price.publishDucketPrice }</Text>
|
||||
<CurrencyIcon type={ 5 } />
|
||||
</Flex>
|
||||
</Flex> }
|
||||
{ (publishCooldown > 0) && <div className="mt-1 text-center fw-bold">{ LocalizeText('camera.publish.wait', [ 'minutes' ], [ Math.ceil( publishCooldown / 60).toString() ]) }</div> }
|
||||
</Column>
|
||||
{ !wasPicturePublished &&
|
||||
<Flex className="d-flex align-items-end">
|
||||
<Button variant="success" size="sm" disabled={ (isWaiting || (publishCooldown > 0)) } onClick={ event => processAction('publish') }>
|
||||
{ LocalizeText('camera.publish.button.text') }
|
||||
</Button>
|
||||
</Flex> }
|
||||
</Flex>
|
||||
<Text center>{ LocalizeText('camera.warning.disclaimer') }</Text>
|
||||
<Flex justifyContent="end">
|
||||
<Button onClick={ event => processAction('cancel') }>{ LocalizeText('generic.cancel') }</Button>
|
||||
</Flex>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
}
|
@ -1,13 +1,32 @@
|
||||
import { IRoomCameraWidgetSelectedEffect, RoomCameraWidgetSelectedEffect } from '@nitrots/nitro-renderer';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { IRoomCameraWidgetEffect, IRoomCameraWidgetSelectedEffect, RoomCameraWidgetSelectedEffect } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import ReactSlider from 'react-slider';
|
||||
import { GetRoomCameraWidgetManager, LocalizeText } from '../../../../api';
|
||||
import { Button } from '../../../../common/Button';
|
||||
import { ButtonGroup } from '../../../../common/ButtonGroup';
|
||||
import { Column } from '../../../../common/Column';
|
||||
import { Flex } from '../../../../common/Flex';
|
||||
import { Grid } from '../../../../common/Grid';
|
||||
import { LayoutImage } from '../../../../common/layout/LayoutImage';
|
||||
import { Text } from '../../../../common/Text';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../../../layout';
|
||||
import { CameraEditorTabs } from '../../common/CameraEditorTabs';
|
||||
import { CameraPicture } from '../../common/CameraPicture';
|
||||
import { CameraPictureThumbnail } from '../../common/CameraPictureThumbnail';
|
||||
import { CameraWidgetEditorTabs, CameraWidgetEditorViewProps } from './CameraWidgetEditorView.types';
|
||||
import { CameraWidgetEffectListView } from './effect-list/CameraWidgetEffectListView';
|
||||
|
||||
const TABS: string[] = [ CameraWidgetEditorTabs.COLORMATRIX, CameraWidgetEditorTabs.COMPOSITE ];
|
||||
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<CameraWidgetEditorViewProps> = props =>
|
||||
{
|
||||
@ -30,7 +49,7 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
|
||||
|
||||
const getEffectList = useCallback(() =>
|
||||
{
|
||||
if(currentTab === CameraWidgetEditorTabs.COLORMATRIX)
|
||||
if(currentTab === CameraEditorTabs.COLORMATRIX)
|
||||
{
|
||||
return getColorMatrixEffects;
|
||||
}
|
||||
@ -170,17 +189,16 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
|
||||
}) }
|
||||
</NitroCardTabsView>
|
||||
<NitroCardContentView>
|
||||
<div className="row h-100">
|
||||
<div className="col-5 d-flex flex-column h-100">
|
||||
<Grid>
|
||||
<Column size={ 5 } overflow="hidden">
|
||||
<CameraWidgetEffectListView myLevel={ myLevel } selectedEffects={ selectedEffects } effects={ getEffectList() } thumbnails={ effectsThumbnails } processAction={ processAction } />
|
||||
</div>
|
||||
<div className="col-7 d-flex flex-column h-100">
|
||||
<div className="picture-preview">
|
||||
<img alt="" src={ getCurrentPictureUrl } />
|
||||
</div>
|
||||
</Column>
|
||||
<Column size={ 7 } justifyContent="between" overflow="hidden">
|
||||
<Column center>
|
||||
<LayoutImage fit={ false } imageUrl={ getCurrentPictureUrl } className="picture-preview" />
|
||||
{ selectedEffectName &&
|
||||
<div className="w-100 p-2 d-flex flex-column justify-content-center slider">
|
||||
<div className="w-100 text-center">{ LocalizeText('camera.effect.name.' + selectedEffectName) }</div>
|
||||
<Column center fullWidth gap={ 1 }>
|
||||
<Text>{ LocalizeText('camera.effect.name.' + selectedEffectName) }</Text>
|
||||
<ReactSlider
|
||||
className={ 'nitro-slider' }
|
||||
min={ 0 }
|
||||
@ -189,26 +207,31 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
|
||||
value={ getCurrentEffect.alpha }
|
||||
onChange={ event => setSelectedEffectAlpha(event) }
|
||||
renderThumb={ (props, state) => <div { ...props }>{ state.valueNow }</div> } />
|
||||
</div> }
|
||||
<div className="d-flex justify-content-between mt-2">
|
||||
<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('download') }>
|
||||
<i className="fas fa-save"></i>
|
||||
</button>
|
||||
<button className="btn btn-primary" onClick={ event => processAction('zoom') }>
|
||||
<i className={ `fas fa-search-${ isZoomed ? 'minus': 'plus' }` } />
|
||||
</button>
|
||||
</div>
|
||||
<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-success" onClick={ event => processAction('checkout') }>{ LocalizeText('camera.preview.button.text') }</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Column> }
|
||||
</Column>
|
||||
<Flex justifyContent="between">
|
||||
<ButtonGroup>
|
||||
<Button size="sm" onClick={ event => processAction('clear_effects') }>
|
||||
<FontAwesomeIcon icon="trash" />
|
||||
</Button>
|
||||
<Button size="sm" onClick={ event => processAction('download') }>
|
||||
<FontAwesomeIcon icon="save" />
|
||||
</Button>
|
||||
<Button size="sm" onClick={ event => processAction('zoom') }>
|
||||
<FontAwesomeIcon icon={ isZoomed ? 'search-minus' : 'search-plus' } />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<Flex gap={ 1 }>
|
||||
<Button size="sm" onClick={ event => processAction('cancel') }>
|
||||
{ LocalizeText('generic.cancel') }
|
||||
</Button>
|
||||
<Button size="sm" onClick={ event => processAction('checkout') }>
|
||||
{ LocalizeText('camera.preview.button.text') }
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Column>
|
||||
</Grid>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
@ -0,0 +1,42 @@
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { IRoomCameraWidgetEffect } from '@nitrots/nitro-renderer';
|
||||
import { FC } from 'react';
|
||||
import { LocalizeText } from '../../../../../api';
|
||||
import { Button } from '../../../../../common/Button';
|
||||
import { LayoutGridItem } from '../../../../../common/layout/LayoutGridItem';
|
||||
import { Text } from '../../../../../common/Text';
|
||||
|
||||
export interface CameraWidgetEffectListItemViewProps
|
||||
{
|
||||
effect: IRoomCameraWidgetEffect;
|
||||
thumbnailUrl: string;
|
||||
isActive: boolean;
|
||||
isLocked: boolean;
|
||||
selectEffect: () => void;
|
||||
removeEffect: () => void;
|
||||
}
|
||||
|
||||
export const CameraWidgetEffectListItemView: FC<CameraWidgetEffectListItemViewProps> = props =>
|
||||
{
|
||||
const { effect = null, thumbnailUrl = null, isActive = false, isLocked = false, selectEffect = null, removeEffect = null } = props;
|
||||
|
||||
return (
|
||||
<LayoutGridItem title={ LocalizeText(!isLocked ? (`camera.effect.name.${ effect.name }`) : `camera.effect.required.level ${ effect.minLevel }`) } itemActive={ isActive } onClick={ event => (!isActive && selectEffect()) }>
|
||||
{ isActive &&
|
||||
<Button variant="danger" size="sm" className="rounded-circle remove-effect" onClick={ removeEffect }>
|
||||
<FontAwesomeIcon icon="times" />
|
||||
</Button> }
|
||||
{ !isLocked && (thumbnailUrl && thumbnailUrl.length > 0) &&
|
||||
<div className="effect-thumbnail-image border">
|
||||
<img alt="" src={ thumbnailUrl } />
|
||||
</div> }
|
||||
{ isLocked &&
|
||||
<Text center bold>
|
||||
<div>
|
||||
<FontAwesomeIcon icon="lock" />
|
||||
</div>
|
||||
{ effect.minLevel }
|
||||
</Text> }
|
||||
</LayoutGridItem>
|
||||
);
|
||||
}
|
@ -1,14 +1,24 @@
|
||||
import { IRoomCameraWidgetEffect, IRoomCameraWidgetSelectedEffect } from '@nitrots/nitro-renderer';
|
||||
import { FC } from 'react';
|
||||
import { NitroCardGridView } from '../../../../../layout';
|
||||
import { CameraWidgetEffectListItemView } from '../effect-list-item/CameraWidgetEffectListItemView';
|
||||
import { CameraWidgetEffectListViewProps } from './CameraWidgetEffectListView.types';
|
||||
import { Grid } from '../../../../../common/Grid';
|
||||
import { CameraPictureThumbnail } from '../../../common/CameraPictureThumbnail';
|
||||
import { CameraWidgetEffectListItemView } from './CameraWidgetEffectListItemView';
|
||||
|
||||
export interface CameraWidgetEffectListViewProps
|
||||
{
|
||||
myLevel: number;
|
||||
selectedEffects: IRoomCameraWidgetSelectedEffect[];
|
||||
effects: IRoomCameraWidgetEffect[];
|
||||
thumbnails: CameraPictureThumbnail[];
|
||||
processAction: (type: string, name: string) => void;
|
||||
}
|
||||
|
||||
export const CameraWidgetEffectListView: FC<CameraWidgetEffectListViewProps> = props =>
|
||||
{
|
||||
const { myLevel = 0, selectedEffects = [], effects = [], thumbnails = [], processAction = null } = props;
|
||||
|
||||
return (
|
||||
<NitroCardGridView className="effect-grid" columns={ 3 }>
|
||||
<Grid columnCount={ 2 } overflow="auto" columnMinHeight={ 60 }>
|
||||
{ effects && (effects.length > 0) && effects.map((effect, index) =>
|
||||
{
|
||||
const thumbnailUrl = (thumbnails.find(thumbnail => (thumbnail.effectName === effect.name)));
|
||||
@ -16,6 +26,6 @@ export const CameraWidgetEffectListView: FC<CameraWidgetEffectListViewProps> = p
|
||||
|
||||
return <CameraWidgetEffectListItemView key={ index } effect={ effect } thumbnailUrl={ ((thumbnailUrl && thumbnailUrl.thumbnailUrl) || null) } isActive={ isActive } isLocked={ (effect.minLevel > myLevel) } selectEffect={ () => processAction('select_effect', effect.name) } removeEffect={ () => processAction('remove_effect', effect.name) } />
|
||||
}) }
|
||||
</NitroCardGridView>
|
||||
</Grid>
|
||||
);
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
@import './achievements/AchievementsView';
|
||||
@import './avatar-editor/AvatarEditorView';
|
||||
@import './camera/CameraWidgetView';
|
||||
@import './catalog/CatalogView';
|
||||
@import './inventory/InventoryView';
|
||||
@import './navigator/NavigatorView';
|
||||
|
@ -1,5 +1,4 @@
|
||||
@import "./shared/Shared";
|
||||
@import "./camera/CameraWidgetView";
|
||||
@import "./friends/FriendsView";
|
||||
@import "./groups/GroupView";
|
||||
@import "./hotel-view/HotelView";
|
||||
|
@ -1,16 +0,0 @@
|
||||
import { createContext, FC, useContext } from 'react';
|
||||
import { CameraWidgetContextProps, ICameraWidgetContext } from './CameraWidgetContext.types';
|
||||
|
||||
const CameraWidgetContext = createContext<ICameraWidgetContext>({
|
||||
cameraRoll: null,
|
||||
setCameraRoll: null,
|
||||
selectedPictureIndex: null,
|
||||
setSelectedPictureIndex: null
|
||||
});
|
||||
|
||||
export const CameraWidgetContextProvider: FC<CameraWidgetContextProps> = props =>
|
||||
{
|
||||
return <CameraWidgetContext.Provider value={ props.value }>{ props.children }</CameraWidgetContext.Provider>
|
||||
}
|
||||
|
||||
export const useCameraWidgetContext = () => useContext(CameraWidgetContext);
|
@ -1,13 +0,0 @@
|
||||
import { Dispatch, ProviderProps, SetStateAction } from 'react';
|
||||
import { CameraPicture } from '../common/CameraPicture';
|
||||
|
||||
export interface ICameraWidgetContext
|
||||
{
|
||||
cameraRoll: CameraPicture[],
|
||||
setCameraRoll: Dispatch<SetStateAction<CameraPicture[]>>;
|
||||
selectedPictureIndex: number,
|
||||
setSelectedPictureIndex: Dispatch<SetStateAction<number>>;
|
||||
}
|
||||
|
||||
export interface CameraWidgetContextProps extends ProviderProps<ICameraWidgetContext>
|
||||
{}
|
@ -1,6 +0,0 @@
|
||||
export interface CameraWidgetCaptureViewProps
|
||||
{
|
||||
onClose: () => void;
|
||||
onEdit: () => void;
|
||||
onDelete: () => void;
|
||||
}
|
@ -1,147 +0,0 @@
|
||||
import { CameraPublishStatusMessageEvent, CameraPurchaseOKMessageEvent, CameraStorageUrlMessageEvent, PublishPhotoMessageComposer, PurchasePhotoMessageComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { GetConfiguration, GetRoomEngine, LocalizeText } from '../../../../api';
|
||||
import { InventoryEvent } from '../../../../events';
|
||||
import { CreateMessageHook, dispatchUiEvent, SendMessageHook } from '../../../../hooks';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout';
|
||||
import { CurrencyIcon } from '../../../shared/currency-icon/CurrencyIcon';
|
||||
import { CameraWidgetCheckoutViewProps } from './CameraWidgetCheckoutView.types';
|
||||
|
||||
export const CameraWidgetCheckoutView: FC<CameraWidgetCheckoutViewProps> = props =>
|
||||
{
|
||||
const { base64Url = null, onCloseClick = null, onCancelClick = null, price = null } = props;
|
||||
const [ pictureUrl, setPictureUrl ] = useState<string>(null);
|
||||
const [ publishUrl, setPublishUrl ] = useState<string>(null);
|
||||
const [ picturesBought, setPicturesBought ] = useState(0);
|
||||
const [ wasPicturePublished, setWasPicturePublished ] = useState(false);
|
||||
const [ isWaiting, setIsWaiting ] = useState(false);
|
||||
const [ publishCooldown, setPublishCooldown ] = useState(0);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!base64Url) return;
|
||||
|
||||
GetRoomEngine().saveBase64AsScreenshot(base64Url);
|
||||
}, [ base64Url ]);
|
||||
|
||||
const onCameraPurchaseOKMessageEvent = useCallback((event: CameraPurchaseOKMessageEvent) =>
|
||||
{
|
||||
setPicturesBought(value => (value + 1));
|
||||
setIsWaiting(false);
|
||||
}, []);
|
||||
|
||||
CreateMessageHook(CameraPurchaseOKMessageEvent, onCameraPurchaseOKMessageEvent);
|
||||
|
||||
const onCameraPublishStatusMessageEvent = useCallback((event: CameraPublishStatusMessageEvent) =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
setPublishUrl(parser.extraDataId);
|
||||
setPublishCooldown(parser.secondsToWait);
|
||||
setWasPicturePublished(parser.ok);
|
||||
setIsWaiting(false);
|
||||
}, []);
|
||||
|
||||
CreateMessageHook(CameraPublishStatusMessageEvent, onCameraPublishStatusMessageEvent);
|
||||
|
||||
const onCameraStorageUrlMessageEvent = useCallback((event: CameraStorageUrlMessageEvent) =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
setPictureUrl(GetConfiguration<string>('camera.url') + '/' + parser.url);
|
||||
}, []);
|
||||
|
||||
CreateMessageHook(CameraStorageUrlMessageEvent, onCameraStorageUrlMessageEvent);
|
||||
|
||||
const processAction = useCallback((type: string, value: string | number = null) =>
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case 'close':
|
||||
onCloseClick();
|
||||
return;
|
||||
case 'buy':
|
||||
if(isWaiting) return;
|
||||
|
||||
setIsWaiting(true);
|
||||
SendMessageHook(new PurchasePhotoMessageComposer(''));
|
||||
return;
|
||||
case 'publish':
|
||||
if(isWaiting) return;
|
||||
|
||||
setIsWaiting(true);
|
||||
SendMessageHook(new PublishPhotoMessageComposer());
|
||||
return;
|
||||
case 'cancel':
|
||||
onCancelClick();
|
||||
return;
|
||||
}
|
||||
}, [onCloseClick, isWaiting, onCancelClick]);
|
||||
|
||||
if(!price) return null;
|
||||
|
||||
return (
|
||||
<NitroCardView className="nitro-camera-checkout" simple={ true }>
|
||||
<NitroCardHeaderView headerText={ LocalizeText('camera.confirm_phase.title') } onCloseClick={ event => processAction('close') } />
|
||||
<NitroCardContentView>
|
||||
<div className="d-flex justify-content-center align-items-center picture-preview border mb-2" style={ pictureUrl ? { backgroundImage: 'url(' + pictureUrl + ')' } : {} }>
|
||||
{ !pictureUrl &&
|
||||
<div className="text-black fw-bold">
|
||||
{ LocalizeText('camera.loading') }
|
||||
</div> }
|
||||
</div>
|
||||
{ pictureUrl && <div className="text-black mb-2">{ LocalizeText('camera.confirm_phase.info') }</div> }
|
||||
<div className="d-flex justify-content-between bg-muted rounded p-2 text-black mb-2">
|
||||
<div className="d-flex flex-column">
|
||||
<div className="fw-bold d-flex justify-content-start">{ LocalizeText('camera.purchase.header') }</div>
|
||||
{ ((price.credits > 0) || (price.duckets > 0)) &&
|
||||
<div className="d-flex">
|
||||
<div className="me-1">{ LocalizeText('catalog.purchase.confirmation.dialog.cost') }</div>
|
||||
{ (price.credits > 0) &&
|
||||
<div className="d-flex fw-bold">
|
||||
{ price.credits } <CurrencyIcon type={ -1 } />
|
||||
</div> }
|
||||
{ (price.duckets > 0) &&
|
||||
<div className="d-flex fw-bold">
|
||||
{ price.duckets } <CurrencyIcon type={ 5 } />
|
||||
</div> }
|
||||
</div> }
|
||||
{ (picturesBought > 0) &&
|
||||
<div>
|
||||
<span className="fw-bold">{ LocalizeText('camera.purchase.count.info') }</span> { picturesBought }
|
||||
<u className="ms-1 cursor-pointer" onClick={ () => dispatchUiEvent(new InventoryEvent(InventoryEvent.SHOW_INVENTORY)) }>{ LocalizeText('camera.open.inventory') }</u>
|
||||
</div> }
|
||||
</div>
|
||||
<div className="d-flex align-items-center">
|
||||
<button className="btn btn-success" disabled={ isWaiting } onClick={ event => processAction('buy') }>{ LocalizeText(!picturesBought ? 'buy' : 'camera.buy.another.button.text') }</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="d-flex justify-content-between bg-muted rounded p-2 text-black mb-2">
|
||||
<div className="d-flex flex-column">
|
||||
<div className="fw-bold d-flex justify-content-start">{ LocalizeText(wasPicturePublished ? 'camera.publish.successful' : 'camera.publish.explanation') }</div>
|
||||
<div>
|
||||
{ LocalizeText(wasPicturePublished ? 'camera.publish.success.short.info' : 'camera.publish.detailed.explanation') }
|
||||
</div>
|
||||
{ wasPicturePublished && <a href={ publishUrl } rel="noreferrer" target="_blank">{ LocalizeText('camera.link.to.published') }</a> }
|
||||
{ !wasPicturePublished && (price.publishDucketPrice > 0) &&
|
||||
<div className="d-flex">
|
||||
<div className="me-1">{ LocalizeText('catalog.purchase.confirmation.dialog.cost') }</div>
|
||||
<div className="d-flex fw-bold">
|
||||
{ price.publishDucketPrice } <CurrencyIcon type={ 5 } />
|
||||
</div>
|
||||
</div> }
|
||||
{ (publishCooldown > 0) && <div className="mt-1 text-center fw-bold">{ LocalizeText('camera.publish.wait', [ 'minutes' ], [ Math.ceil( publishCooldown / 60).toString() ]) }</div> }
|
||||
</div>
|
||||
{ !wasPicturePublished &&
|
||||
<div className="d-flex align-items-end">
|
||||
<button className="btn btn-success" disabled={ isWaiting || publishCooldown > 0 } onClick={ event => processAction('publish') }>{ LocalizeText('camera.publish.button.text') }</button>
|
||||
</div> }
|
||||
</div>
|
||||
<div className="text-black mb-2 text-center">{ LocalizeText('camera.warning.disclaimer') }</div>
|
||||
<div className="d-flex justify-content-end">
|
||||
<button className="btn btn-primary" onClick={ event => processAction('cancel') }>{ LocalizeText('generic.cancel') }</button>
|
||||
</div>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
export interface CameraWidgetCheckoutViewProps
|
||||
{
|
||||
base64Url: string;
|
||||
onCloseClick: () => void;
|
||||
onCancelClick: () => void;
|
||||
price: { credits: number, duckets: number, publishDucketPrice: number };
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
import { IRoomCameraWidgetEffect } from '@nitrots/nitro-renderer';
|
||||
import { CameraPicture } from '../../common/CameraPicture';
|
||||
|
||||
export interface CameraWidgetEditorViewProps
|
||||
{
|
||||
picture: CameraPicture;
|
||||
availableEffects: IRoomCameraWidgetEffect[];
|
||||
myLevel: number;
|
||||
onClose: () => void;
|
||||
onCancel: () => void;
|
||||
onCheckout: (pictureUrl: string) => void;
|
||||
}
|
||||
|
||||
export class CameraWidgetEditorTabs
|
||||
{
|
||||
public static readonly COLORMATRIX: string = 'colormatrix';
|
||||
public static readonly COMPOSITE: string = 'composite';
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
import { FC } from 'react';
|
||||
import { LocalizeText } from '../../../../../api';
|
||||
import { NitroCardGridItemView } from '../../../../../layout';
|
||||
import { CameraWidgetEffectListItemViewProps } from './CameraWidgetEffectListItemView.types';
|
||||
|
||||
export const CameraWidgetEffectListItemView: FC<CameraWidgetEffectListItemViewProps> = props =>
|
||||
{
|
||||
const { effect = null, thumbnailUrl = null, isActive = false, isLocked = false, selectEffect = null, removeEffect = null } = props;
|
||||
|
||||
return (
|
||||
<NitroCardGridItemView title={ LocalizeText(!isLocked ? (`camera.effect.name.${ effect.name }`) : `camera.effect.required.level ${ effect.minLevel }`) } itemActive={ isActive } onClick={ event => (!isActive && selectEffect()) }>
|
||||
{ isActive &&
|
||||
<button className="btn btn-danger btn-sm rounded-circle remove-effect" onClick={ removeEffect }>
|
||||
<i className="fas fa-times" />
|
||||
</button> }
|
||||
{ !isLocked && (thumbnailUrl && thumbnailUrl.length > 0) &&
|
||||
<div className="effect-thumbnail-image border">
|
||||
<img alt="" src={ thumbnailUrl } />
|
||||
</div> }
|
||||
{ isLocked &&
|
||||
<div className="text-center text-black">
|
||||
<div>
|
||||
<i className="fas fa-lock" />
|
||||
</div>
|
||||
<div className="fw-bold">{ effect.minLevel }</div>
|
||||
</div> }
|
||||
</NitroCardGridItemView>
|
||||
);
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
import { IRoomCameraWidgetEffect } from '@nitrots/nitro-renderer';
|
||||
|
||||
export interface CameraWidgetEffectListItemViewProps
|
||||
{
|
||||
effect: IRoomCameraWidgetEffect;
|
||||
thumbnailUrl: string;
|
||||
isActive: boolean;
|
||||
isLocked: boolean;
|
||||
selectEffect: () => void;
|
||||
removeEffect: () => void;
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
import { IRoomCameraWidgetEffect, IRoomCameraWidgetSelectedEffect } from '@nitrots/nitro-renderer';
|
||||
import { CameraPictureThumbnail } from '../../../common/CameraPictureThumbnail';
|
||||
|
||||
export interface CameraWidgetEffectListViewProps
|
||||
{
|
||||
myLevel: number;
|
||||
selectedEffects: IRoomCameraWidgetSelectedEffect[];
|
||||
effects: IRoomCameraWidgetEffect[];
|
||||
thumbnails: CameraPictureThumbnail[];
|
||||
processAction: (type: string, name: string) => void;
|
||||
}
|
@ -3,13 +3,13 @@ import { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { AddEventLinkTracker, GetCommunication, RemoveLinkEventTracker } from '../../api';
|
||||
import { AchievementsView } from '../../components/achievements/AchievementsView';
|
||||
import { AvatarEditorView } from '../../components/avatar-editor/AvatarEditorView';
|
||||
import { CameraWidgetView } from '../../components/camera/CameraWidgetView';
|
||||
import { CatalogView } from '../../components/catalog/CatalogView';
|
||||
import { InventoryView } from '../../components/inventory/InventoryView';
|
||||
import { NavigatorView } from '../../components/navigator/NavigatorView';
|
||||
import { ToolbarView } from '../../components/toolbar/ToolbarView';
|
||||
import { useRoomSessionManagerEvent } from '../../hooks/events/nitro/session/room-session-manager-event';
|
||||
import { TransitionAnimation, TransitionAnimationTypes } from '../../layout';
|
||||
import { CameraWidgetView } from '../camera/CameraWidgetView';
|
||||
import { CampaignView } from '../campaign/CampaignView';
|
||||
import { ChatHistoryView } from '../chat-history/ChatHistoryView';
|
||||
import { FloorplanEditorView } from '../floorplan-editor/FloorplanEditorView';
|
||||
|
Loading…
Reference in New Issue
Block a user