diff --git a/src/layout/card/NitroCardView.scss b/src/layout/card/NitroCardView.scss index 1781a2ec..53da0829 100644 --- a/src/layout/card/NitroCardView.scss +++ b/src/layout/card/NitroCardView.scss @@ -5,37 +5,37 @@ $nitro-card-top-height: $nitro-card-header-height + $nitro-card-tabs-height; .nitro-card { pointer-events: all; - .nitro-card-close-parent { - right: -3px; - top: 5px; - position: absolute; - width: auto; - - .nitro-card-close { - cursor: pointer; - border-radius: $border-radius; - box-shadow: inset 0 0 0 1px #921911, inset 0 2px rgba($white,.2); - padding:2px 6px 0 6px; - border:1px solid $white; - background: rgb(245,80,65); - background: linear-gradient(180deg, rgba(245,80,65,1) 0%, rgba(245,80,65,1) 50%, rgba(194,48,39,1) 50%, rgba(194,48,39,1) 100%); - - &:hover { - filter: brightness(1.2); - } - - &:active { - filter: brightness(0.8); - } - } - } - @import './content/NitroCardContentView'; @import './header/NitroCardHeaderView'; @import './simple-header/NitroCardSimpleHeaderView'; @import './tabs/NitroCardTabsView'; } +.nitro-card-close-parent { + right: -3px; + top: 5px; + position: absolute; + width: auto; + + .nitro-card-close { + cursor: pointer; + border-radius: $border-radius; + box-shadow: inset 0 0 0 1px #921911, inset 0 2px rgba($white,.2); + padding:2px 6px 0 6px; + border:1px solid $white; + background: rgb(245,80,65); + background: linear-gradient(180deg, rgba(245,80,65,1) 0%, rgba(245,80,65,1) 50%, rgba(194,48,39,1) 50%, rgba(194,48,39,1) 100%); + + &:hover { + filter: brightness(1.2); + } + + &:active { + filter: brightness(0.8); + } + } +} + .nitro-card-responsive { position: absolute; top: 0; diff --git a/src/views/room/widgets/camera/CameraWidgetView.scss b/src/views/room/widgets/camera/CameraWidgetView.scss index b019f057..f3b8f31c 100644 --- a/src/views/room/widgets/camera/CameraWidgetView.scss +++ b/src/views/room/widgets/camera/CameraWidgetView.scss @@ -1,4 +1,9 @@ .nitro-camera-capture { + .nitro-card-close-parent { + top: 7px; + right: 10px; + } + .camera-canvas { width: 340px; height: 462px; @@ -9,7 +14,7 @@ position: absolute; width: 320px; height: 320px; - margin-top: 9px; + margin-top: 37px; margin-left: 11.4px; &.bg { @@ -25,7 +30,7 @@ width: 94px; height: 94px; cursor: pointer; - margin-top: 334px; + margin-top: 362px; background-image: url('../../../../assets/images/room-widgets/camera-widget/camera-spritesheet.png'); background-position: -340px 0px; @@ -94,10 +99,19 @@ width: 320px; height: 320px; - &.zoomed { - background-size: 636px; - background-position: center; + .slider { + background: linear-gradient(180deg, transparent, black); + text-shadow: 1px 1px rgba(0, 0, 0, .5); } + } +} + +.nitro-camera-checkout { + width: 336px; + + .picture-preview { + width: 320px; + height: 320px; .slider { background: linear-gradient(180deg, transparent, black); diff --git a/src/views/room/widgets/camera/CameraWidgetView.tsx b/src/views/room/widgets/camera/CameraWidgetView.tsx index e20574ad..a93e5006 100644 --- a/src/views/room/widgets/camera/CameraWidgetView.tsx +++ b/src/views/room/widgets/camera/CameraWidgetView.tsx @@ -1,24 +1,32 @@ +import { RoomWidgetCameraConfigurationComposer, RoomWidgetCameraConfigurationEvent } from 'nitro-renderer'; import { RoomCameraWidgetManagerEvent } from 'nitro-renderer/src/nitro/camera/events/RoomCameraWidgetManagerEvent'; +import { IRoomCameraWidgetSelectedEffect } from 'nitro-renderer/src/nitro/camera/IRoomCameraWidgetSelectedEffect'; import { FC, useCallback, useEffect, useMemo, useState } from 'react'; -import { IRoomCameraWidgetSelectedEffect } from '../../../../../../nitro-renderer/src/nitro/camera/IRoomCameraWidgetSelectedEffect'; import { GetRoomCameraWidgetManager } from '../../../../api'; import { RoomWidgetCameraEvent } from '../../../../events/room-widgets/camera/RoomWidgetCameraEvent'; import { useCameraEvent } from '../../../../hooks/events/nitro/camera/camera-event'; import { useUiEvent } from '../../../../hooks/events/ui/ui-event'; +import { CreateMessageHook, SendMessageHook } from '../../../../hooks/messages/message-event'; import { CameraWidgetViewProps } from './CameraWidgetView.types'; import { CameraWidgetContextProvider } from './context/CameraWidgetContext'; import { CameraWidgetCaptureView } from './views/capture/CameraWidgetCaptureView'; +import { CameraWidgetCheckoutView } from './views/checkout/CameraWidgetCheckoutView'; import { CameraWidgetEditorView } from './views/editor/CameraWidgetEditorView'; export const CameraWidgetView: FC = props => { const [ effectsReady, setEffectsReady ] = useState(false); - const [ isCaptureVisible, setIsCaptureVisible ] = useState(false); - const [ isEditorVisible, setIsEditorVisible ] = useState(false); + + const [ isCaptureVisible, setIsCaptureVisible ] = useState(false); + const [ isEditorVisible, setIsEditorVisible ] = useState(false); + const [ isCheckoutVisible, setIsCheckoutVisible ] = useState(false); + const [ myLevel, setMyLevel ] = useState(10); const [ cameraRoll, setCameraRoll ] = useState([]); const [ selectedPictureIndex, setSelectedPictureIndex ] = useState(-1); const [ selectedEffects, setSelectedEffects ] = useState([]); + const [ isZoomed, setIsZoomed ] = useState(false); + const [ price, setPrice ] = useState<{credits: Number, points: Number, pointsType: number}>(null); const onNitroEvent = useCallback((event: RoomWidgetCameraEvent) => { @@ -30,10 +38,12 @@ export const CameraWidgetView: FC = props => case RoomWidgetCameraEvent.HIDE_CAMERA: setIsCaptureVisible(false); setIsEditorVisible(false); + setIsCheckoutVisible(false); return; case RoomWidgetCameraEvent.TOGGLE_CAMERA: - setIsEditorVisible(false); setIsCaptureVisible(value => !value); + setIsEditorVisible(false); + setIsCheckoutVisible(false); return; } }, []); @@ -42,6 +52,22 @@ export const CameraWidgetView: FC = props => useUiEvent(RoomWidgetCameraEvent.HIDE_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 }); + }, [ price ]); + + CreateMessageHook(RoomWidgetCameraConfigurationEvent, onCameraConfigurationEvent); + const availableEffects = useMemo(() => { if(!effectsReady) return null; @@ -75,6 +101,7 @@ export const CameraWidgetView: FC = props => case 'close': setIsCaptureVisible(false); setIsEditorVisible(false); + setIsCheckoutVisible(false); return; case 'capture_edit': setIsCaptureVisible(false); @@ -83,14 +110,20 @@ export const CameraWidgetView: FC = props => case 'editor_cancel': setIsCaptureVisible(true); setIsEditorVisible(false); + setIsCheckoutVisible(false); + return; + case 'editor_checkout': + setIsEditorVisible(false); + setIsCheckoutVisible(true); return; } }, []); return ( - - { ( isCaptureVisible && processAction('close') } onEditClick={ () => processAction('capture_edit') } /> ) || - ( isEditorVisible && processAction('close') } onCancelClick={ () => processAction('editor_cancel') } availableEffects={ availableEffects } /> ) } + + { isCaptureVisible && processAction('close') } onEditClick={ () => processAction('capture_edit') } /> } + { isEditorVisible && processAction('close') } onCancelClick={ () => processAction('editor_cancel') } onCheckoutClick={ () => processAction('editor_checkout') } availableEffects={ availableEffects } /> } + { isCheckoutVisible && processAction('close') } onCancelClick={ () => processAction('editor_cancel') } price={ price }> } ); } diff --git a/src/views/room/widgets/camera/context/CameraWidgetContext.tsx b/src/views/room/widgets/camera/context/CameraWidgetContext.tsx index 55d7ba6e..3fd936aa 100644 --- a/src/views/room/widgets/camera/context/CameraWidgetContext.tsx +++ b/src/views/room/widgets/camera/context/CameraWidgetContext.tsx @@ -7,7 +7,9 @@ const CameraWidgetContext = createContext({ selectedPictureIndex: null, setSelectedPictureIndex: null, selectedEffects: null, - setSelectedEffects: null + setSelectedEffects: null, + isZoomed: null, + setIsZoomed: null }); export const CameraWidgetContextProvider: FC = props => diff --git a/src/views/room/widgets/camera/context/CameraWidgetContext.types.ts b/src/views/room/widgets/camera/context/CameraWidgetContext.types.ts index af69cc2b..f0c3ca0b 100644 --- a/src/views/room/widgets/camera/context/CameraWidgetContext.types.ts +++ b/src/views/room/widgets/camera/context/CameraWidgetContext.types.ts @@ -8,7 +8,9 @@ export interface ICameraWidgetContext selectedPictureIndex: number, setSelectedPictureIndex: (index: number) => void, selectedEffects: IRoomCameraWidgetSelectedEffect[], - setSelectedEffects: (selectedEffects: IRoomCameraWidgetSelectedEffect[]) => void + setSelectedEffects: (selectedEffects: IRoomCameraWidgetSelectedEffect[]) => void, + isZoomed: boolean, + setIsZoomed: (isZoomed: boolean) => void } export interface CameraWidgetContextProps extends ProviderProps diff --git a/src/views/room/widgets/camera/views/capture/CameraWidgetCaptureView.tsx b/src/views/room/widgets/camera/views/capture/CameraWidgetCaptureView.tsx index a3433ac7..3518f59f 100644 --- a/src/views/room/widgets/camera/views/capture/CameraWidgetCaptureView.tsx +++ b/src/views/room/widgets/camera/views/capture/CameraWidgetCaptureView.tsx @@ -79,22 +79,22 @@ export const CameraWidgetCaptureView: FC = props =
-
-
processAction('close') }> - +
+
processAction('close') }> +
-1}) }> { cameraWidgetContext.selectedPictureIndex > -1 &&
- +
}
-
+
{ cameraWidgetContext.cameraRoll.length > 0 &&
diff --git a/src/views/room/widgets/camera/views/checkout/CameraWidgetCheckoutView.tsx b/src/views/room/widgets/camera/views/checkout/CameraWidgetCheckoutView.tsx new file mode 100644 index 00000000..c0ed5d00 --- /dev/null +++ b/src/views/room/widgets/camera/views/checkout/CameraWidgetCheckoutView.tsx @@ -0,0 +1,107 @@ +import { RoomWidgetCameraPublishComposer, RoomWidgetCameraPublishedEvent, RoomWidgetCameraPurchaseComposer, RoomWidgetCameraPurchaseSuccessfulEvent } from 'nitro-renderer'; +import { FC, useCallback, useState } from 'react'; +import { GetRoomCameraWidgetManager } from '../../../../../../api/nitro/camera/GetRoomCameraWidgetManager'; +import { CreateMessageHook, SendMessageHook } from '../../../../../../hooks/messages/message-event'; +import { NitroCardContentView } from '../../../../../../layout/card/content/NitroCardContentView'; +import { NitroCardView } from '../../../../../../layout/card/NitroCardView'; +import { NitroCardSimpleHeaderView } from '../../../../../../layout/card/simple-header/NitroCardSimpleHeaderView'; +import { LocalizeText } from '../../../../../../utils/LocalizeText'; +import { CurrencyIcon } from '../../../../../currency-icon/CurrencyIcon'; +import { useCameraWidgetContext } from '../../context/CameraWidgetContext'; +import { CameraWidgetCheckoutViewProps } from './CameraWidgetCheckoutView.types'; + +export const CameraWidgetCheckoutView: FC = props => +{ + const { onCloseClick = null, onCancelClick = null, price = null } = props; + + const cameraWidgetContext = useCameraWidgetContext(); + + const [ picturesBought, setPicturesBought ] = useState(0); + const [ wasPicturePublished, setWasPicturePublished ] = useState(false); + const [ isWaiting, setIsWaiting ] = useState(false); + const [ publishCooldown, setPublishCooldown ] = useState(0); + + const onCameraPurchaseSuccessfulEvent = useCallback((event: RoomWidgetCameraPurchaseSuccessfulEvent) => + { + setPicturesBought(value => value + 1); + setIsWaiting(false); + }, [ picturesBought ]); + + const onRoomWidgetCameraPublishedEvent = useCallback((event: RoomWidgetCameraPublishedEvent) => + { + const parser = event.getParser(); + + setPublishCooldown(parser.cooldownSeconds); + setWasPicturePublished(parser.wasSuccessful); + setIsWaiting(false); + }, [ wasPicturePublished, publishCooldown ]); + + CreateMessageHook(RoomWidgetCameraPurchaseSuccessfulEvent, onCameraPurchaseSuccessfulEvent); + CreateMessageHook(RoomWidgetCameraPublishedEvent, onRoomWidgetCameraPublishedEvent); + + const getCurrentPicture = useCallback(() => + { + return GetRoomCameraWidgetManager().applyEffects(cameraWidgetContext.cameraRoll[cameraWidgetContext.selectedPictureIndex], cameraWidgetContext.selectedEffects, cameraWidgetContext.isZoomed); + }, [ cameraWidgetContext.selectedEffects, cameraWidgetContext.isZoomed ]); + + const processAction = useCallback((type: string, value: string | number = null) => + { + switch(type) + { + case 'close': + onCloseClick(); + return; + case 'buy': + if(isWaiting) return; + + setIsWaiting(true); + SendMessageHook(new RoomWidgetCameraPurchaseComposer()); + return; + case 'publish': + if(isWaiting) return; + + setIsWaiting(true); + SendMessageHook(new RoomWidgetCameraPublishComposer()); + return; + case 'cancel': + onCancelClick(); + return; + } + }, [ onCloseClick, onCancelClick ]); + + if(!price) return null; + + return ( + + processAction('close') } /> + +
+
+
+
{ LocalizeText('camera.purchase.header') }{ price.credits > 0 && <>: { price.credits } }
+ { picturesBought > 0 &&
{ LocalizeText('camera.purchase.count.info') + ' ' + picturesBought }
} +
+
+ + { picturesBought > 0 && } +
+
+
+
+
+
{ LocalizeText(wasPicturePublished ? 'camera.publish.successful' : 'camera.publish.explanation') }{ !wasPicturePublished && price.points > 0 && <>: { price.points } }
+
{ LocalizeText(wasPicturePublished ? 'camera.publish.success.short.info' : 'camera.publish.detailed.explanation') }
+ { wasPicturePublished && { LocalizeText('camera.link.to.published') } } +
+ { !wasPicturePublished && } +
+ { publishCooldown > 0 &&
{ LocalizeText('camera.publish.wait', ['minutes'], [Math.ceil(publishCooldown/60).toString()]) }
} +
+
{ LocalizeText('camera.warning.disclaimer') }
+
+ +
+
+
+ ); +} diff --git a/src/views/room/widgets/camera/views/checkout/CameraWidgetCheckoutView.types.ts b/src/views/room/widgets/camera/views/checkout/CameraWidgetCheckoutView.types.ts new file mode 100644 index 00000000..c73dc021 --- /dev/null +++ b/src/views/room/widgets/camera/views/checkout/CameraWidgetCheckoutView.types.ts @@ -0,0 +1,6 @@ +export interface CameraWidgetCheckoutViewProps +{ + onCloseClick: () => void; + onCancelClick: () => void; + price: {credits: Number, points: Number, pointsType: number}; +} diff --git a/src/views/room/widgets/camera/views/editor/CameraWidgetEditorView.tsx b/src/views/room/widgets/camera/views/editor/CameraWidgetEditorView.tsx index 9b447110..759eb7ec 100644 --- a/src/views/room/widgets/camera/views/editor/CameraWidgetEditorView.tsx +++ b/src/views/room/widgets/camera/views/editor/CameraWidgetEditorView.tsx @@ -13,17 +13,15 @@ import { CameraWidgetEditorTabs, CameraWidgetEditorViewProps } from './CameraWid export const CameraWidgetEditorView: FC = props => { - const { availableEffects = null, onCloseClick = null, onCancelClick = null } = props; + const { availableEffects = null, myLevel = null, onCloseClick = null, onCancelClick = null, onCheckoutClick = null } = props; const TABS: string[] = [ CameraWidgetEditorTabs.COLORMATRIX, CameraWidgetEditorTabs.COMPOSITE ]; - const MY_LEVEL: number = 6; const cameraWidgetContext = useCameraWidgetContext(); const [ currentTab, setCurrentTab ] = useState(CameraWidgetEditorTabs.COLORMATRIX); const [ selectedEffectName, setSelectedEffectName ] = useState(null); const [ effectsThumbnails, setEffectsThumbnails ] = useState<{ name: string, image: HTMLImageElement }[]>([]); - const [ isZoomed, setIsZoomed ] = useState(false); useEffect(() => { @@ -60,8 +58,8 @@ export const CameraWidgetEditorView: FC = props => const getCurrentPicture = useCallback(() => { - return GetRoomCameraWidgetManager().applyEffects(cameraWidgetContext.cameraRoll[cameraWidgetContext.selectedPictureIndex], cameraWidgetContext.selectedEffects, isZoomed); - }, [ cameraWidgetContext.selectedEffects, isZoomed ]); + return GetRoomCameraWidgetManager().applyEffects(cameraWidgetContext.cameraRoll[cameraWidgetContext.selectedPictureIndex], cameraWidgetContext.selectedEffects, cameraWidgetContext.isZoomed); + }, [ cameraWidgetContext.selectedEffects, cameraWidgetContext.isZoomed ]); const getCurrentEffectAlpha = useCallback(() => { @@ -106,6 +104,9 @@ export const CameraWidgetEditorView: FC = props => case 'cancel': onCancelClick(); return; + case 'checkout': + onCheckoutClick(); + return; case 'change_tab': setCurrentTab(String(value)); return; @@ -124,12 +125,12 @@ export const CameraWidgetEditorView: FC = props => { effect = availableEffects.find(effect => effect.name === value); - if(effect.minLevel > MY_LEVEL) return; + if(effect.minLevel > myLevel) return; cameraWidgetContext.setSelectedEffects([...cameraWidgetContext.selectedEffects, new RoomCameraWidgetSelectedEffect(effect, 0.5)]); } - if(effect && effect.minLevel > MY_LEVEL) return; + if(effect && effect.minLevel > myLevel) return; if(selectedEffectName !== value) { @@ -169,7 +170,7 @@ export const CameraWidgetEditorView: FC = props => window.open(getCurrentPicture().src, '_blank'); return; case 'zoom': - setIsZoomed(isZoomed => !isZoomed); + cameraWidgetContext.setIsZoomed(!cameraWidgetContext.isZoomed); return; } }, [ onCloseClick, onCancelClick, availableEffects, cameraWidgetContext.selectedEffects, selectedEffectName ]); @@ -193,11 +194,11 @@ export const CameraWidgetEditorView: FC = props => return (
{ getEffectIndex(effect.name) > -1 && } -
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 <= MY_LEVEL &&
+
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 &&
} - { effect.minLevel > MY_LEVEL &&
+ { effect.minLevel > myLevel &&
{ effect.minLevel }
} @@ -212,7 +213,7 @@ export const CameraWidgetEditorView: FC = props =>
-
+
{ selectedEffectName &&
{ LocalizeText('camera.effect.name.' + selectedEffectName) + ' - ' + getCurrentEffectAlpha() }
setSelectedEffectAlpha(Number(event.target.value)) } className="form-range w-100" /> @@ -222,11 +223,11 @@ export const CameraWidgetEditorView: FC = props =>
- +
- +
diff --git a/src/views/room/widgets/camera/views/editor/CameraWidgetEditorView.types.ts b/src/views/room/widgets/camera/views/editor/CameraWidgetEditorView.types.ts index 69c6512a..b953f117 100644 --- a/src/views/room/widgets/camera/views/editor/CameraWidgetEditorView.types.ts +++ b/src/views/room/widgets/camera/views/editor/CameraWidgetEditorView.types.ts @@ -3,8 +3,10 @@ import { IRoomCameraWidgetEffect } from 'nitro-renderer/src/nitro/camera/IRoomCa export interface CameraWidgetEditorViewProps { availableEffects: IRoomCameraWidgetEffect[]; + myLevel: number; onCloseClick: () => void; onCancelClick: () => void; + onCheckoutClick: () => void; } export class CameraWidgetEditorTabs