More camera changes

This commit is contained in:
Bill 2021-08-07 19:05:31 -04:00
parent 47c0ab6c44
commit a3350b8b54
11 changed files with 240 additions and 174 deletions

View File

@ -87,6 +87,53 @@
.nitro-camera-editor {
width: 600px;
.content-area {
min-height: 350px;
height: 350px;
resize: vertical;
.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 {
height: 60px !important;
max-height: 60px !important;
.grid-item {
overflow: unset;
.remove-effect {
position: absolute;
top: -1px;
right: -1px;
padding: 2px;
font-size: 11px;
line-height: 11px;
min-height: unset;
}
.effect-thumbnail-image {
img {
width: 40px;
height: 40px;
image-rendering: auto;
}
}
}
}
}
.effects {
height: 363px;
min-height: 363px;
@ -117,16 +164,6 @@
}
}
}
.picture-preview {
width: 320px;
height: 320px;
.slider {
background: linear-gradient(180deg, transparent, black);
text-shadow: 1px 1px rgba(0, 0, 0, .5);
}
}
}
.nitro-camera-checkout {

View File

@ -24,7 +24,7 @@ export const CameraWidgetView: FC<{}> = props =>
const [ selectedPictureIndex, setSelectedPictureIndex ] = useState(-1);
const [ selectedEffects, setSelectedEffects ] = useState<IRoomCameraWidgetSelectedEffect[]>([]);
const [ isZoomed, setIsZoomed ] = useState(false);
const [ myLevel, setMyLevel ] = useState(10);
const [ myLevel, setMyLevel ] = useState(1);
const [ price, setPrice ] = useState<{ credits: number, duckets: number, publishDucketPrice: number }>(null);
const onNitroEvent = useCallback((event: RoomWidgetCameraEvent) =>
@ -111,7 +111,7 @@ export const CameraWidgetView: FC<{}> = props =>
return (
<CameraWidgetContextProvider value={ { cameraRoll, setCameraRoll, selectedPictureIndex, setSelectedPictureIndex, selectedEffects, setSelectedEffects, isZoomed, setIsZoomed } }>
{ (mode === MODE_CAPTURE) && <CameraWidgetCaptureView onClose={ () => processAction('close') } onEdit={ () => processAction('edit') } onDelete={ () => processAction('delete') } /> }
{ (mode === MODE_EDITOR) && <CameraWidgetEditorView myLevel={ myLevel } onClose={ () => processAction('close') } onCancel={ () => processAction('editor_cancel') } onCheckout={ () => processAction('checkout') } availableEffects={ availableEffects } /> }
{ (mode === MODE_EDITOR) && <CameraWidgetEditorView picture={ cameraRoll[selectedPictureIndex] } myLevel={ myLevel } onClose={ () => processAction('close') } onCancel={ () => processAction('editor_cancel') } onCheckout={ () => processAction('checkout') } availableEffects={ availableEffects } /> }
{ (mode === MODE_CHECKOUT) && <CameraWidgetCheckoutView onCloseClick={ () => processAction('close') } onCancelClick={ () => processAction('editor_cancel') } price={ price }></CameraWidgetCheckoutView> }
</CameraWidgetContextProvider>
);

View File

@ -0,0 +1,6 @@
export class CameraPictureThumbnail
{
constructor(
public effectName: string,
public thumbnailUrl: string) {}
}

View File

@ -1,17 +1,17 @@
import { IRoomCameraWidgetSelectedEffect } from '@nitrots/nitro-renderer';
import { ProviderProps } from 'react';
import { Dispatch, ProviderProps, SetStateAction } from 'react';
import { CameraPicture } from '../common/CameraPicture';
export interface ICameraWidgetContext
{
cameraRoll: CameraPicture[],
setCameraRoll: (cameraRoll: CameraPicture[]) => void,
setCameraRoll: Dispatch<SetStateAction<CameraPicture[]>>;
selectedPictureIndex: number,
setSelectedPictureIndex: (index: number) => void,
setSelectedPictureIndex: Dispatch<SetStateAction<number>>;
selectedEffects: IRoomCameraWidgetSelectedEffect[],
setSelectedEffects: (selectedEffects: IRoomCameraWidgetSelectedEffect[]) => void,
setSelectedEffects: Dispatch<SetStateAction<IRoomCameraWidgetSelectedEffect[]>>;
isZoomed: boolean,
setIsZoomed: (isZoomed: boolean) => void
setIsZoomed: Dispatch<SetStateAction<boolean>>;
}
export interface CameraWidgetContextProps extends ProviderProps<ICameraWidgetContext>

View File

@ -1,94 +1,85 @@
import { RoomCameraWidgetSelectedEffect } from '@nitrots/nitro-renderer';
import classNames from 'classnames';
import { FC, useCallback, useEffect, useState } from 'react';
import { IRoomCameraWidgetSelectedEffect, RoomCameraWidgetSelectedEffect } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { GetRoomCameraWidgetManager } from '../../../../../../api';
import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../../../../../layout';
import { LocalizeText } from '../../../../../../utils/LocalizeText';
import { useCameraWidgetContext } from '../../context/CameraWidgetContext';
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 const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
{
const { availableEffects = null, myLevel = 1, onClose = null, onCancel = null, onCheckout = null } = props;
const { picture = null, availableEffects = null, myLevel = 1, onClose = null, onCancel = null, onCheckout = null } = props;
const [ currentTab, setCurrentTab ] = useState(TABS[0]);
const [ selectedEffectName, setSelectedEffectName ] = useState(null);
const [ effectsThumbnails, setEffectsThumbnails ] = useState<{ name: string, image: HTMLImageElement }[]>([]);
const { cameraRoll = null, selectedPictureIndex = -1, selectedEffects = null, isZoomed = false, setSelectedEffects = null, setIsZoomed = null } = useCameraWidgetContext();
const [ selectedEffectName, setSelectedEffectName ] = useState<string>(null);
const [ selectedEffects, setSelectedEffects ] = useState<IRoomCameraWidgetSelectedEffect[]>([]);
const [ effectsThumbnails, setEffectsThumbnails ] = useState<CameraPictureThumbnail[]>([]);
const [ isZoomed, setIsZoomed ] = useState(false);
useEffect(() =>
const getColorMatrixEffects = useMemo(() =>
{
const thumbnails = [];
return availableEffects.filter(effect => effect.colorMatrix);
}, [ availableEffects ]);
for(const effect of availableEffects)
{
thumbnails.push({name: effect.name, image: GetRoomCameraWidgetManager().applyEffects(cameraRoll[selectedPictureIndex].texture, [ new RoomCameraWidgetSelectedEffect(effect, 1) ], false)});
}
setEffectsThumbnails(thumbnails);
}, [ cameraRoll, selectedPictureIndex, availableEffects ]);
const getEffectThumbnail = useCallback((effectName: string) =>
const getCompositeEffects = useMemo(() =>
{
const search = effectsThumbnails.find(thumbnail => thumbnail.name === effectName);
if(search) return search.image.src;
return null;
}, [ effectsThumbnails ]);
return availableEffects.filter(effect => effect.texture);
}, [ availableEffects ]);
const getEffectList = useCallback(() =>
{
if(currentTab === CameraWidgetEditorTabs.COLORMATRIX)
{
return availableEffects.filter(effect => effect.colorMatrix);
return getColorMatrixEffects;
}
else
{
return availableEffects.filter(effect => effect.texture);
}
}, [ currentTab, availableEffects ]);
const getCurrentPicture = useCallback(() =>
return getCompositeEffects;
}, [ currentTab, getColorMatrixEffects, getCompositeEffects ]);
const getSelectedEffectIndex = useCallback((name: string) =>
{
return GetRoomCameraWidgetManager().applyEffects(cameraRoll[selectedPictureIndex].texture, selectedEffects, isZoomed);
}, [ cameraRoll, selectedPictureIndex, selectedEffects, isZoomed ]);
if(!name || !name.length || !selectedEffects || !selectedEffects.length) return -1;
const getCurrentEffectAlpha = useCallback(() =>
{
if(!selectedEffectName) return 0;
const selectedEffect = selectedEffects.find(effect => effect.effect.name === selectedEffectName);
if(!selectedEffect) return 0;
return selectedEffect.alpha;
}, [ selectedEffectName, selectedEffects ]);
const getEffectIndex = useCallback((effectName) =>
{
return selectedEffects.findIndex(effect => effect.effect.name === effectName);
return selectedEffects.findIndex(effect => (effect.effect.name === name));
}, [ selectedEffects ])
const setSelectedEffectAlpha = useCallback((newAlpha: number) =>
const getCurrentEffectIndex = useMemo(() =>
{
if(!selectedEffectName) return;
return getSelectedEffectIndex(selectedEffectName)
}, [ selectedEffectName, getSelectedEffectIndex ])
const selectedEffectIndex = getEffectIndex(selectedEffectName);
const getCurrentEffect = useMemo(() =>
{
if(!selectedEffectName) return null;
if(selectedEffectIndex === -1) return;
return (selectedEffects[getCurrentEffectIndex] || null);
}, [ selectedEffectName, getCurrentEffectIndex, selectedEffects ]);
const clone = Array.from(selectedEffects);
const setCurrentEffectAlpha = useCallback((alpha: number) =>
{
const index = getCurrentEffectIndex;
const selectedEffect = clone[selectedEffectIndex];
if(index === -1) return;
clone[selectedEffectIndex] = new RoomCameraWidgetSelectedEffect(selectedEffect.effect, newAlpha);
setSelectedEffects(prevValue =>
{
const clone = [ ...prevValue ];
const currentEffect = clone[index];
setSelectedEffects(clone);
}, [ selectedEffectName, getEffectIndex, selectedEffects, setSelectedEffects ]);
clone[getCurrentEffectIndex] = new RoomCameraWidgetSelectedEffect(currentEffect.effect, alpha);
const processAction = useCallback((type: string, value: string | number = null) =>
return clone;
});
}, [ getCurrentEffectIndex, setSelectedEffects ]);
const getCurrentPictureUrl = useMemo(() =>
{
return GetRoomCameraWidgetManager().applyEffects(picture.texture, selectedEffects, isZoomed).src;
}, [ picture, selectedEffects, isZoomed ]);
const processAction = useCallback((type: string, effectName: string = null) =>
{
switch(type)
{
@ -102,131 +93,88 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
onCheckout();
return;
case 'change_tab':
setCurrentTab(String(value));
setCurrentTab(String(effectName));
return;
case 'select_effect':
{
let existingIndex = -1;
case 'select_effect': {
let existingIndex = getSelectedEffectIndex(effectName);
if(selectedEffects.length > 0)
{
existingIndex = getEffectIndex(value);
}
let effect = null;
if(existingIndex >= 0) return;
const effect = availableEffects.find(effect => (effect.name === effectName));
if(existingIndex === -1)
{
effect = availableEffects.find(effect => effect.name === value);
if(effect.minLevel > myLevel) return;
setSelectedEffects([...selectedEffects, new RoomCameraWidgetSelectedEffect(effect, 0.5)]);
}
if(effect && effect.minLevel > myLevel) return;
if(!effect) return;
if(selectedEffectName !== value)
setSelectedEffects(prevValue =>
{
setSelectedEffectName(value);
}
else
{
setSelectedEffectName(null);
}
}
return [ ...prevValue, new RoomCameraWidgetSelectedEffect(effect, 1) ];
});
setSelectedEffectName(effect.name);
return;
case 'remove_effect':
{
const existingIndex = getEffectIndex(value);
}
case 'remove_effect': {
let existingIndex = getSelectedEffectIndex(effectName);
if(existingIndex > -1)
if(existingIndex === -1) return;
setSelectedEffects(prevValue =>
{
const effect = selectedEffects[existingIndex];
const clone = [ ...prevValue ];
if(effect.effect.name === selectedEffectName)
{
setSelectedEffectName(null);
}
const clone = Array.from(selectedEffects);
clone.splice(existingIndex, 1);
setSelectedEffects(clone);
}
}
return clone;
});
if(selectedEffectName === effectName) setSelectedEffectName(null);
return;
}
case 'clear_effects':
setSelectedEffectName(null);
setSelectedEffects([]);
return;
case 'download':
window.open(getCurrentPicture().src, '_blank');
//window.open(getCurrentPicture().src, '_blank');
return;
case 'zoom':
setIsZoomed(!isZoomed);
return;
}
}, [ onClose, onCancel, onCheckout, getCurrentPicture, myLevel, selectedEffectName, getEffectIndex, availableEffects, isZoomed, setIsZoomed, selectedEffects, setSelectedEffects ]);
}, [ isZoomed, availableEffects, getSelectedEffectIndex, selectedEffectName, onCancel, onCheckout, onClose, setIsZoomed, setSelectedEffects ]);
useEffect(() =>
{
const thumbnails: CameraPictureThumbnail[] = [];
for(const effect of availableEffects)
{
thumbnails.push(new CameraPictureThumbnail(effect.name, GetRoomCameraWidgetManager().applyEffects(picture.texture, [ new RoomCameraWidgetSelectedEffect(effect, 1) ], false).src));
}
setEffectsThumbnails(thumbnails);
}, [ picture, availableEffects ]);
return (
<NitroCardView className="nitro-camera-editor">
<NitroCardHeaderView headerText={ LocalizeText('camera.editor.button.text') } onCloseClick={ event => processAction('close') } />
<div className="d-flex">
<div className="w-100">
<NitroCardTabsView>
{ TABS.map(tab =>
{
return <NitroCardTabsItemView key={ tab } isActive={ currentTab === tab } onClick={ event => processAction('change_tab', tab) }><i className={ 'icon icon-camera-' + tab }></i></NitroCardTabsItemView>
}) }
</NitroCardTabsView>
<NitroCardContentView>
<div className="d-flex h-100 overflow-auto effects px-2">
<div className="row row-cols-3">
{ getEffectList().map(effect =>
{
return (
<div key={ effect.name } className="col mb-3 position-relative">
{ getEffectIndex(effect.name) > -1 && <button className="btn btn-danger btn-sm p-0 position-absolute btn-remove-effect" onClick={ event => processAction('remove_effect', effect.name) }><i className="fas fa-times"></i></button> }
<div title={ effect.minLevel <= myLevel ? LocalizeText('camera.effect.name.' + effect.name) : LocalizeText('camera.effect.required.level') + ' ' + effect.minLevel } onClick={ event => processAction('select_effect', effect.name) } className={"effect-thumbnail cursor-pointer position-relative border border-2 rounded d-flex flex-column justify-content-center align-items-center py-1" + classNames({' active': selectedEffectName === effect.name})}>
{ effect.minLevel <= myLevel && <div className="effect-thumbnail-image border">
<img alt="" src={ getEffectThumbnail(effect.name) } />
</div> }
{ effect.minLevel > myLevel && <div className="text-center text-black">
<div><i className="fas fa-lock"></i></div>
<div className="fw-bold">{ effect.minLevel }</div>
</div> }
</div>
</div>
);
}) }
</div>
<NitroCardTabsView>
{ TABS.map(tab =>
{
return <NitroCardTabsItemView key={ tab } isActive={ currentTab === tab } onClick={ event => processAction('change_tab', tab) }><i className={ 'icon icon-camera-' + tab }></i></NitroCardTabsItemView>
}) }
</NitroCardTabsView>
<NitroCardContentView>
<div className="row h-100">
<div className="col-5 d-flex flex-column h-100">
<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>
</NitroCardContentView>
</div>
</div>
<div className="w-100">
<NitroCardTabsView></NitroCardTabsView>
<NitroCardContentView>
<div className="d-flex align-items-end picture-preview border" style={ { backgroundImage: 'url(' + getCurrentPicture().src + ')' } }>
{ 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) + ' - ' + getCurrentEffectAlpha() }</div>
<input type="range" min="0" max="1" step="0.01" value={ getCurrentEffectAlpha() } onChange={ event => setSelectedEffectAlpha(Number(event.target.value)) } className="form-range w-100" />
</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 " + classNames({'fa-search-plus': !isZoomed, 'fa-search-minus': isZoomed})}></i></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>
</NitroCardContentView>
</div>
</div>
</NitroCardContentView>
</NitroCardView>
);
}

View File

@ -1,7 +1,9 @@
import { IRoomCameraWidgetEffect } from '@nitrots/nitro-renderer';
import { CameraPicture } from '../../common/CameraPicture';
export interface CameraWidgetEditorViewProps
{
picture: CameraPicture;
availableEffects: IRoomCameraWidgetEffect[];
myLevel: number;
onClose: () => void;

View File

@ -0,0 +1,29 @@
import { FC } from 'react';
import { NitroCardGridItemView } from '../../../../../../../layout/card/grid/item/NitroCardGridItemView';
import { LocalizeText } from '../../../../../../../utils';
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>
);
}

View File

@ -0,0 +1,11 @@
import { IRoomCameraWidgetEffect } from '@nitrots/nitro-renderer';
export interface CameraWidgetEffectListItemViewProps
{
effect: IRoomCameraWidgetEffect;
thumbnailUrl: string;
isActive: boolean;
isLocked: boolean;
selectEffect: () => void;
removeEffect: () => void;
}

View File

@ -0,0 +1,21 @@
import { FC } from 'react';
import { NitroCardGridView } from '../../../../../../../layout/card/grid/NitroCardGridView';
import { CameraWidgetEffectListItemView } from '../effect-list-item/CameraWidgetEffectListItemView';
import { CameraWidgetEffectListViewProps } from './CameraWidgetEffectListView.types';
export const CameraWidgetEffectListView: FC<CameraWidgetEffectListViewProps> = props =>
{
const { myLevel = 0, selectedEffects = [], effects = [], thumbnails = [], processAction = null } = props;
return (
<NitroCardGridView className="effect-grid" columns={ 3 }>
{ effects && (effects.length > 0) && effects.map((effect, index) =>
{
const thumbnailUrl = (thumbnails.find(thumbnail => (thumbnail.effectName === effect.name)));
const isActive = (selectedEffects.findIndex(selectedEffect => (selectedEffect.effect.name === effect.name)) > -1);
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>
);
}

View File

@ -0,0 +1,11 @@
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;
}

View File

@ -23,6 +23,7 @@
"jsx": "react-jsx"
},
"include": [
"src"
"src",
"node_modules/@nitrots/nitro-renderer/src/**/*.ts",
]
}