diff --git a/src/api/nitro/session/GetFurnitureDataForProductOffer.ts b/src/api/nitro/session/GetFurnitureDataForProductOffer.ts new file mode 100644 index 00000000..3c8ce1b0 --- /dev/null +++ b/src/api/nitro/session/GetFurnitureDataForProductOffer.ts @@ -0,0 +1,21 @@ +import { CatalogProductOfferData, FurnitureType, IFurnitureData } from 'nitro-renderer'; +import { GetSessionDataManager } from './GetSessionDataManager'; + +export function GetFurnitureDataForProductOffer(offer: CatalogProductOfferData): IFurnitureData +{ + if(!offer) return null; + + let furniData: IFurnitureData = null; + + switch((offer.productType.toUpperCase())) + { + case FurnitureType.FLOOR: + furniData = GetSessionDataManager().getFloorItemData(offer.furniClassId); + break; + case FurnitureType.WALL: + furniData = GetSessionDataManager().getWallItemData(offer.furniClassId); + break; + } + + return furniData; +} diff --git a/src/api/nitro/session/GetProductDataForLocalization.ts b/src/api/nitro/session/GetProductDataForLocalization.ts new file mode 100644 index 00000000..155e3c80 --- /dev/null +++ b/src/api/nitro/session/GetProductDataForLocalization.ts @@ -0,0 +1,9 @@ +import { IProductData } from 'nitro-renderer'; +import { GetSessionDataManager } from './GetSessionDataManager'; + +export function GetProductDataForLocalization(localizationId: string): IProductData +{ + if(!localizationId) return null; + + return GetSessionDataManager().getProductData(localizationId); +} diff --git a/src/api/nitro/session/index.ts b/src/api/nitro/session/index.ts index 082222e7..96478e85 100644 --- a/src/api/nitro/session/index.ts +++ b/src/api/nitro/session/index.ts @@ -1,4 +1,6 @@ +export * from './GetFurnitureDataForProductOffer'; export * from './GetRoomSession'; export * from './GetRoomSessionManager'; export * from './GetSessionDataManager'; +export * from './SendChatTypingMessage'; export * from './StartRoomSession'; diff --git a/src/assets/styles/bootstrap/_variables.scss b/src/assets/styles/bootstrap/_variables.scss index 71bec3c8..cd899423 100644 --- a/src/assets/styles/bootstrap/_variables.scss +++ b/src/assets/styles/bootstrap/_variables.scss @@ -80,6 +80,10 @@ $light: #DFDFDF !default; $dark: $gray-900 !default; // scss-docs-end theme-color-variables +.bg-primary-split { + background: repeating-linear-gradient(#1E7295, #1E7295 49.9%, #185D79 50.1%, #185D79 100%); +} + // scss-docs-start theme-colors-map $theme-colors: ( "primary": $primary, @@ -364,7 +368,7 @@ $container-padding-x: $grid-gutter-width / 2 !default; // Define common padding and border radius sizes and more. // scss-docs-start border-variables -$border-width: 1.5px !default; +$border-width: 1px !default; $border-widths: ( 1: 1px, 2: 2px, diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss index 32725adb..ccfaba62 100644 --- a/src/assets/styles/index.scss +++ b/src/assets/styles/index.scss @@ -8,3 +8,19 @@ @import './grid'; @import './icons'; @import './utils'; + +.btn-sm { + min-height: 28px; +} + +/* Chrome, Safari, Edge, Opera */ +input::-webkit-outer-spin-button, +input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +/* Firefox */ +input[type=number] { + -moz-appearance: textfield; +} diff --git a/src/assets/styles/utils.scss b/src/assets/styles/utils.scss index c579f007..880f0bfa 100644 --- a/src/assets/styles/utils.scss +++ b/src/assets/styles/utils.scss @@ -18,6 +18,10 @@ text-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); } +.min-height-0 { + min-height: 0 !important; +} + ul { &.columns-3 { diff --git a/src/events/catalog/CatalogEvent.ts b/src/events/catalog/CatalogEvent.ts index bbf1cf29..0020a066 100644 --- a/src/events/catalog/CatalogEvent.ts +++ b/src/events/catalog/CatalogEvent.ts @@ -2,6 +2,10 @@ import { NitroEvent } from 'nitro-renderer'; export class CatalogEvent extends NitroEvent { - public static SHOW_CATALOG: string = 'IE_SHOW_CATALOG'; - public static TOGGLE_CATALOG: string = 'IE_TOGGLE_CATALOG'; + public static SHOW_CATALOG: string = 'CE_SHOW_CATALOG'; + public static HIDE_CATALOG: string = 'CE_HIDE_CATALOG'; + public static TOGGLE_CATALOG: string = 'CE_TOGGLE_CATALOG'; + public static PURCHASE_SUCCESS: string = 'CE_PURCHASE_SUCCESS'; + public static PURCHASE_FAILED: string = 'CE_PURCHASE_FAILED'; + public static SOLD_OUT: string = 'CE_SOLD_OUT'; } diff --git a/src/events/catalog/CatalogPurchaseFailureEvent.ts b/src/events/catalog/CatalogPurchaseFailureEvent.ts new file mode 100644 index 00000000..e684ee5a --- /dev/null +++ b/src/events/catalog/CatalogPurchaseFailureEvent.ts @@ -0,0 +1,18 @@ +import { CatalogEvent } from './CatalogEvent'; + +export class CatalogPurchaseFailureEvent extends CatalogEvent +{ + private _code: number; + + constructor(code: number) + { + super(CatalogEvent.PURCHASE_FAILED); + + this._code = code; + } + + public get code(): number + { + return this._code; + } +} diff --git a/src/events/catalog/CatalogPurchaseSoldOutEvent.ts b/src/events/catalog/CatalogPurchaseSoldOutEvent.ts new file mode 100644 index 00000000..5d0d8edb --- /dev/null +++ b/src/events/catalog/CatalogPurchaseSoldOutEvent.ts @@ -0,0 +1,9 @@ +import { CatalogEvent } from './CatalogEvent'; + +export class CatalogPurchaseSoldOutEvent extends CatalogEvent +{ + constructor() + { + super(CatalogEvent.SOLD_OUT); + } +} diff --git a/src/events/catalog/CatalogPurchasedEvent.ts b/src/events/catalog/CatalogPurchasedEvent.ts new file mode 100644 index 00000000..cef77656 --- /dev/null +++ b/src/events/catalog/CatalogPurchasedEvent.ts @@ -0,0 +1,19 @@ +import { CatalogPurchaseData } from 'nitro-renderer'; +import { CatalogEvent } from './CatalogEvent'; + +export class CatalogPurchasedEvent extends CatalogEvent +{ + private _purchase: CatalogPurchaseData; + + constructor(purchase: CatalogPurchaseData) + { + super(CatalogEvent.PURCHASE_SUCCESS); + + this._purchase = purchase; + } + + public get purchase(): CatalogPurchaseData + { + return this._purchase; + } +} diff --git a/src/events/catalog/index.ts b/src/events/catalog/index.ts index 6f06fa3f..60dd21d0 100644 --- a/src/events/catalog/index.ts +++ b/src/events/catalog/index.ts @@ -1 +1,4 @@ export * from './CatalogEvent'; +export * from './CatalogPurchasedEvent'; +export * from './CatalogPurchaseFailureEvent'; +export * from './CatalogPurchaseSoldOutEvent'; diff --git a/src/hooks/draggable-window/DraggableWindow.tsx b/src/hooks/draggable-window/DraggableWindow.tsx index 19484805..6f4878f0 100644 --- a/src/hooks/draggable-window/DraggableWindow.tsx +++ b/src/hooks/draggable-window/DraggableWindow.tsx @@ -1,12 +1,14 @@ -import { createRef, MouseEvent, useEffect } from 'react'; +import { FC, MouseEvent, useEffect, useRef } from 'react'; import Draggable from 'react-draggable'; import { DraggableWindowProps } from './DraggableWindow.types'; const currentWindows: HTMLDivElement[] = []; -export function DraggableWindow(props: DraggableWindowProps): JSX.Element +export const DraggableWindow: FC = props => { - const elementRef = createRef(); + const { disableDrag = false } = props; + + const elementRef = useRef(); function bringToTop(): void { @@ -51,8 +53,8 @@ export function DraggableWindow(props: DraggableWindowProps): JSX.Element bringToTop(); - const left = ((document.body.clientWidth - element.clientWidth) / 2); - const top = ((document.body.clientHeight - element.clientHeight) / 2); + const left = ((element.parentElement.clientWidth - element.clientWidth) / 2); + const top = ((element.parentElement.clientHeight - element.clientHeight) / 2); element.style.left = `${ left }px`; element.style.top = `${ top }px`; @@ -65,11 +67,20 @@ export function DraggableWindow(props: DraggableWindowProps): JSX.Element } }, [ elementRef ]); - return ( - + function getWindowContent(): JSX.Element + { + return (
{ props.children }
+ ); + } + + if(disableDrag) return getWindowContent(); + + return ( + + { getWindowContent() } ); } diff --git a/src/hooks/draggable-window/DraggableWindow.types.tsx b/src/hooks/draggable-window/DraggableWindow.types.tsx index 1ec07457..0bcccf9b 100644 --- a/src/hooks/draggable-window/DraggableWindow.types.tsx +++ b/src/hooks/draggable-window/DraggableWindow.types.tsx @@ -5,5 +5,6 @@ export interface DraggableWindowProps { handle: string; draggableOptions?: Partial; + disableDrag?: boolean; children?: ReactNode; } diff --git a/src/index.scss b/src/index.scss index 01e8ffde..16d977fb 100644 --- a/src/index.scss +++ b/src/index.scss @@ -35,4 +35,5 @@ $grid-active-border-color: $white; @import './utils/Styles'; @import './App'; @import './hooks/Styles'; +@import './layout/Layout'; @import './views/Styles'; diff --git a/src/layout/Layout.scss b/src/layout/Layout.scss index e69de29b..33696337 100644 --- a/src/layout/Layout.scss +++ b/src/layout/Layout.scss @@ -0,0 +1,2 @@ +@import './card/NitroCardView'; +@import './loading-spinner/LoadingSpinnerView'; diff --git a/src/layout/card/NitroCardView.scss b/src/layout/card/NitroCardView.scss index e69de29b..4020b759 100644 --- a/src/layout/card/NitroCardView.scss +++ b/src/layout/card/NitroCardView.scss @@ -0,0 +1,8 @@ +$nitro-card-header-height: 33px; +$nitro-card-tabs-height: 33px; +$nitro-card-top-height: $nitro-card-header-height + $nitro-card-tabs-height; + +@import './content/NitroCardContentView'; +@import './header/NitroCardHeaderView'; +@import './simple-header/NitroCardSimpleHeaderView'; +@import './tabs/NitroCardTabsView'; diff --git a/src/layout/card/NitroCardView.tsx b/src/layout/card/NitroCardView.tsx index e4d16599..621619a7 100644 --- a/src/layout/card/NitroCardView.tsx +++ b/src/layout/card/NitroCardView.tsx @@ -4,11 +4,11 @@ import { NitroCardViewProps } from './NitroCardView.types'; export const NitroCardView: FC = props => { - const { className = '' } = props; + const { className = '', disableDrag = false } = props; return ( - -
+ +
{ props.children }
diff --git a/src/layout/card/NitroCardView.types.ts b/src/layout/card/NitroCardView.types.ts index 49a236a2..9b4c8cb4 100644 --- a/src/layout/card/NitroCardView.types.ts +++ b/src/layout/card/NitroCardView.types.ts @@ -1,4 +1,5 @@ export interface NitroCardViewProps { className?: string; + disableDrag?: boolean; } diff --git a/src/layout/card/content/NitroCardContentView.tsx b/src/layout/card/content/NitroCardContentView.tsx index 573522ef..96d9f85b 100644 --- a/src/layout/card/content/NitroCardContentView.tsx +++ b/src/layout/card/content/NitroCardContentView.tsx @@ -4,7 +4,7 @@ import { NitroCardContentViewProps } from './NitroCardContextView.types'; export const NitroCardContentView: FC = props => { return ( -
+
{ props.children }
); diff --git a/src/layout/card/header/NitroCardHeaderView.scss b/src/layout/card/header/NitroCardHeaderView.scss index e69de29b..bdf82f76 100644 --- a/src/layout/card/header/NitroCardHeaderView.scss +++ b/src/layout/card/header/NitroCardHeaderView.scss @@ -0,0 +1,4 @@ +.nitro-card-header { + min-height: $nitro-card-header-height; + max-height: $nitro-card-header-height; +} diff --git a/src/layout/card/header/NitroCardHeaderView.tsx b/src/layout/card/header/NitroCardHeaderView.tsx index a2306f63..6b0741e3 100644 --- a/src/layout/card/header/NitroCardHeaderView.tsx +++ b/src/layout/card/header/NitroCardHeaderView.tsx @@ -6,7 +6,7 @@ export const NitroCardHeaderView: FC = props => const { headerText = null, onCloseClick = null } = props; return ( -
+
{ headerText }
diff --git a/src/layout/card/simple-header/NitroCardSimpleHeaderView.scss b/src/layout/card/simple-header/NitroCardSimpleHeaderView.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/layout/card/simple-header/NitroCardSimpleHeaderView.tsx b/src/layout/card/simple-header/NitroCardSimpleHeaderView.tsx new file mode 100644 index 00000000..ff926fa3 --- /dev/null +++ b/src/layout/card/simple-header/NitroCardSimpleHeaderView.tsx @@ -0,0 +1,19 @@ +import { FC } from 'react'; +import { NitroCardSimpleHeaderViewProps } from './NitroCardSimpleHeaderView.types'; + +export const NitroCardSimpleHeaderView: FC = props => +{ + const { headerText = null, onCloseClick = null } = props; + + return ( +
+
+
+
{ headerText }
+
+
+ +
+
+ ); +} diff --git a/src/layout/card/simple-header/NitroCardSimpleHeaderView.types.ts b/src/layout/card/simple-header/NitroCardSimpleHeaderView.types.ts new file mode 100644 index 00000000..87018f3a --- /dev/null +++ b/src/layout/card/simple-header/NitroCardSimpleHeaderView.types.ts @@ -0,0 +1,7 @@ +import { MouseEvent } from 'react'; + +export interface NitroCardSimpleHeaderViewProps +{ + headerText: string; + onCloseClick: (event: MouseEvent) => void; +} diff --git a/src/layout/card/simple-header/index.ts b/src/layout/card/simple-header/index.ts new file mode 100644 index 00000000..aff643d5 --- /dev/null +++ b/src/layout/card/simple-header/index.ts @@ -0,0 +1,2 @@ +export * from './NitroCardSimpleHeaderView'; +export * from './NitroCardSimpleHeaderView.types'; diff --git a/src/layout/card/tabs/NitroCardTabsView.scss b/src/layout/card/tabs/NitroCardTabsView.scss index e69de29b..e9f125b4 100644 --- a/src/layout/card/tabs/NitroCardTabsView.scss +++ b/src/layout/card/tabs/NitroCardTabsView.scss @@ -0,0 +1,6 @@ +.nitro-card-tabs { + min-height: $nitro-card-tabs-height; + max-height: $nitro-card-tabs-height; +} + +@import './tabs-item/NitroCardTabsItemView'; diff --git a/src/layout/card/tabs/NitroCardTabsView.tsx b/src/layout/card/tabs/NitroCardTabsView.tsx index 5b9d8583..76d344ca 100644 --- a/src/layout/card/tabs/NitroCardTabsView.tsx +++ b/src/layout/card/tabs/NitroCardTabsView.tsx @@ -4,7 +4,7 @@ import { NitroCardTabsViewProps } from './NitroCardTabsView.types'; export const NitroCardTabsView: FC = props => { return ( -
    +
      { props.children }
    ); diff --git a/src/layout/index.ts b/src/layout/index.ts index cb5809fe..de8d17b6 100644 --- a/src/layout/index.ts +++ b/src/layout/index.ts @@ -1 +1,2 @@ export * from './card'; +export * from './loading-spinner/LoadingSpinnerView'; diff --git a/src/layout/loading-spinner/LoadingSpinnerView.scss b/src/layout/loading-spinner/LoadingSpinnerView.scss new file mode 100644 index 00000000..24089f0d --- /dev/null +++ b/src/layout/loading-spinner/LoadingSpinnerView.scss @@ -0,0 +1,42 @@ +.spinner-container { + display: flex; + align-items: center; + justify-content: center; + + .spinner { + margin: 2px; + width: 10px; + height: 10px; + border: $border-width solid $white; + background-color: rgba($white, 0.8); + border-radius: 100%; + display: inline-block; + -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both; + animation: sk-bouncedelay 1.4s infinite ease-in-out both; + + &:nth-child(1) { + -webkit-animation-delay: -0.32s; + animation-delay: -0.32s; + } + + &:nth-child(2) { + -webkit-animation-delay: -0.16s; + animation-delay: -0.16s; + } + } +} + +@-webkit-keyframes sk-bouncedelay { + 0%, 80%, 100% { -webkit-transform: scale(0) } + 40% { -webkit-transform: scale(1.0) } +} + +@keyframes sk-bouncedelay { + 0%, 80%, 100% { + -webkit-transform: scale(0); + transform: scale(0); + } 40% { + -webkit-transform: scale(1.0); + transform: scale(1.0); + } +} diff --git a/src/layout/loading-spinner/LoadingSpinnerView.tsx b/src/layout/loading-spinner/LoadingSpinnerView.tsx new file mode 100644 index 00000000..d51f869a --- /dev/null +++ b/src/layout/loading-spinner/LoadingSpinnerView.tsx @@ -0,0 +1,12 @@ +import { FC } from 'react'; + +export const LoadingSpinnerView: FC = props => +{ + return ( +
    +
    +
    +
    +
    + ); +} diff --git a/src/utils/currency-icon/CurrencyIcon.scss b/src/utils/currency-icon/CurrencyIcon.scss index 27f4d925..4134543b 100644 --- a/src/utils/currency-icon/CurrencyIcon.scss +++ b/src/utils/currency-icon/CurrencyIcon.scss @@ -1,6 +1,6 @@ .nitro-currency-icon { background-position: center; background-repeat: no-repeat; - width: 25px; - height: 25px; + width: 20px; + height: 20px; } diff --git a/src/views/Styles.scss b/src/views/Styles.scss index 9861a5b0..159653cb 100644 --- a/src/views/Styles.scss +++ b/src/views/Styles.scss @@ -2,9 +2,10 @@ @import './badge-image/BadgeImage'; @import './catalog/CatalogView'; @import './catalog-icon/CatalogIconView'; +@import './friend-list/FriendListView'; @import './hotel-view/HotelView'; @import './inventory/InventoryView'; -@import './friend-list/FriendListView'; +@import './limited-edition/LimitedEdition'; @import './loading/LoadingView'; @import './main/MainView'; @import './navigator/NavigatorView'; diff --git a/src/views/catalog/CatalogMessageHandler.tsx b/src/views/catalog/CatalogMessageHandler.tsx index f81eb975..ecb1f18c 100644 --- a/src/views/catalog/CatalogMessageHandler.tsx +++ b/src/views/catalog/CatalogMessageHandler.tsx @@ -1,5 +1,9 @@ -import { CatalogPageEvent, CatalogPagesEvent } from 'nitro-renderer'; +import { CatalogPageEvent, CatalogPagesEvent, CatalogPurchaseEvent, CatalogPurchaseFailedEvent, CatalogPurchaseUnavailableEvent, CatalogSearchEvent, CatalogSoldOutEvent } from 'nitro-renderer'; import { FC, useCallback } from 'react'; +import { CatalogPurchaseFailureEvent } from '../../events'; +import { CatalogPurchasedEvent } from '../../events/catalog/CatalogPurchasedEvent'; +import { CatalogPurchaseSoldOutEvent } from '../../events/catalog/CatalogPurchaseSoldOutEvent'; +import { dispatchUiEvent } from '../../hooks/events/ui/ui-event'; import { CreateMessageHook } from '../../hooks/messages/message-event'; import { CatalogMessageHandlerProps } from './CatalogMessageHandler.types'; import { useCatalogContext } from './context/CatalogContext'; @@ -33,8 +37,51 @@ export const CatalogMessageHandler: FC = props => }); }, [ dispatchCatalogState ]); + const onCatalogPurchaseEvent = useCallback((event: CatalogPurchaseEvent) => + { + const parser = event.getParser(); + + dispatchUiEvent(new CatalogPurchasedEvent(parser.offer)); + }, []); + + const onCatalogPurchaseFailedEvent = useCallback((event: CatalogPurchaseFailedEvent) => + { + const parser = event.getParser(); + + dispatchUiEvent(new CatalogPurchaseFailureEvent(parser.code)); + }, []); + + const onCatalogPurchaseUnavailableEvent = useCallback((event: CatalogPurchaseUnavailableEvent) => + { + const parser = event.getParser(); + }, []); + + const onCatalogSoldOutEvent = useCallback((event: CatalogSoldOutEvent) => + { + const parser = event.getParser(); + + dispatchUiEvent(new CatalogPurchaseSoldOutEvent()); + }, []); + + const onCatalogSearchEvent = useCallback((event: CatalogSearchEvent) => + { + const parser = event.getParser(); + + dispatchCatalogState({ + type: CatalogActions.SET_CATALOG_ACTIVE_OFFER, + payload: { + activeOffer: parser.offer + } + }); + }, [ dispatchCatalogState ]); + CreateMessageHook(CatalogPagesEvent, onCatalogPagesEvent); CreateMessageHook(CatalogPageEvent, onCatalogPageEvent); + CreateMessageHook(CatalogPurchaseEvent, onCatalogPurchaseEvent); + CreateMessageHook(CatalogPurchaseFailedEvent, onCatalogPurchaseFailedEvent); + CreateMessageHook(CatalogPurchaseUnavailableEvent, onCatalogPurchaseUnavailableEvent); + CreateMessageHook(CatalogSoldOutEvent, onCatalogSoldOutEvent); + CreateMessageHook(CatalogSearchEvent, onCatalogSearchEvent); return null; } diff --git a/src/views/catalog/CatalogView.scss b/src/views/catalog/CatalogView.scss index 58047992..681824ef 100644 --- a/src/views/catalog/CatalogView.scss +++ b/src/views/catalog/CatalogView.scss @@ -1,5 +1,10 @@ .nitro-catalog { - width: 600px; + width: 620px; + + .content-area { + height: 330px; + max-height: 330px; + } } @import './views/CatalogViews'; diff --git a/src/views/catalog/CatalogView.tsx b/src/views/catalog/CatalogView.tsx index 3b840d5e..13136a9c 100644 --- a/src/views/catalog/CatalogView.tsx +++ b/src/views/catalog/CatalogView.tsx @@ -1,5 +1,6 @@ -import { CatalogModeComposer, ICatalogPageData } from 'nitro-renderer'; +import { CatalogModeComposer, ICatalogPageData, RoomPreviewer } from 'nitro-renderer'; import { FC, useCallback, useEffect, useReducer, useState } from 'react'; +import { GetRoomEngine } from '../../api'; import { CatalogEvent } from '../../events'; import { useUiEvent } from '../../hooks/events/ui/ui-event'; import { SendMessageHook } from '../../hooks/messages/message-event'; @@ -8,16 +9,17 @@ import { LocalizeText } from '../../utils/LocalizeText'; import { CatalogMessageHandler } from './CatalogMessageHandler'; import { CatalogMode, CatalogViewProps } from './CatalogView.types'; import { CatalogContextProvider } from './context/CatalogContext'; -import { CatalogReducer, initialCatalog } from './reducers/CatalogReducer'; +import { CatalogActions, CatalogReducer, initialCatalog } from './reducers/CatalogReducer'; import { CatalogNavigationView } from './views/navigation/CatalogNavigationView'; import { CatalogPageView } from './views/page/CatalogPageView'; +import { CatalogSearchView } from './views/search/CatalogSearchView'; export const CatalogView: FC = props => { const [ isVisible, setIsVisible ] = useState(false); - const [ currentTab, setCurrentTab ] = useState(null); + const [ roomPreviewer, setRoomPreviewer ] = useState(null); const [ catalogState, dispatchCatalogState ] = useReducer(CatalogReducer, initialCatalog); - const { root = null } = catalogState; + const { root = null, currentTab = null, searchResult = null } = catalogState; const onCatalogEvent = useCallback((event: CatalogEvent) => { @@ -26,6 +28,9 @@ export const CatalogView: FC = props => case CatalogEvent.SHOW_CATALOG: setIsVisible(true); return; + case CatalogEvent.HIDE_CATALOG: + setIsVisible(false); + return; case CatalogEvent.TOGGLE_CATALOG: setIsVisible(value => !value); return; @@ -33,6 +38,7 @@ export const CatalogView: FC = props => }, []); useUiEvent(CatalogEvent.SHOW_CATALOG, onCatalogEvent); + useUiEvent(CatalogEvent.HIDE_CATALOG, onCatalogEvent); useUiEvent(CatalogEvent.TOGGLE_CATALOG, onCatalogEvent); useEffect(() => @@ -43,12 +49,35 @@ export const CatalogView: FC = props => { SendMessageHook(new CatalogModeComposer(CatalogMode.MODE_NORMAL)); } - else - { - setCurrentTab(catalogState.root.children[0]); - } }, [ isVisible, catalogState.root ]); + useEffect(() => + { + setRoomPreviewer(new RoomPreviewer(GetRoomEngine(), ++RoomPreviewer.PREVIEW_COUNTER)); + + return () => + { + setRoomPreviewer(prevValue => + { + prevValue.dispose(); + + return null; + }); + } + }, []); + + function setCurrentTab(page: ICatalogPageData): void + { + dispatchCatalogState({ + type: CatalogActions.SET_CATALOG_CURRENT_TAB, + payload: { + currentTab: page + } + }); + } + + const currentNavigationPage = ((searchResult && searchResult.page) || currentTab); + return ( @@ -62,12 +91,13 @@ export const CatalogView: FC = props => }) } -
    +
    - + +
    - +
    diff --git a/src/views/catalog/enums/FurniCategory.ts b/src/views/catalog/enums/FurniCategory.ts new file mode 100644 index 00000000..dff1f526 --- /dev/null +++ b/src/views/catalog/enums/FurniCategory.ts @@ -0,0 +1,26 @@ +export class FurniCategory +{ + public static DEFAULT: number = 1; + public static WALL_PAPER: number = 2; + public static FLOOR: number = 3; + public static LANDSCAPE: number = 4; + public static POST_IT: number = 5; + public static POSTER: number = 6; + public static SOUND_SET: number = 7; + public static TRAX_SONG: number = 8; + public static PRESENT: number = 9; + public static ECOTRON_BOX: number = 10; + public static TROPHY: number = 11; + public static CREDIT_FURNI: number = 12; + public static PET_SHAMPOO: number = 13; + public static PET_CUSTOM_PART: number = 14; + public static PET_CUSTOM_PART_SHAMPOO: number = 15; + public static PET_SADDLE: number = 16; + public static GUILD_FURNI: number = 17; + public static GAME_FURNI: number = 18; + public static MONSTERPLANT_SEED: number = 19; + public static MONSTERPLANT_REVIVAL: number = 20; + public static MONSTERPLANT_REBREED: number = 21; + public static MONSTERPLANT_FERTILIZE: number = 22; + public static FIGURE_PURCHASABLE_SET: number = 23; +} \ No newline at end of file diff --git a/src/views/catalog/enums/ProductTypeEnum.ts b/src/views/catalog/enums/ProductTypeEnum.ts new file mode 100644 index 00000000..942782eb --- /dev/null +++ b/src/views/catalog/enums/ProductTypeEnum.ts @@ -0,0 +1,11 @@ +export class ProductTypeEnum +{ + public static WALL: string = 'i'; + public static FLOOR: string = 's'; + public static EFFECT: string = 'e'; + public static HABBO_CLUB: string = 'h'; + public static BADGE: string = 'b'; + public static GAME_TOKEN: string = 'GAME_TOKEN'; + public static PET: string = 'p'; + public static ROBOT: string = 'r'; +} \ No newline at end of file diff --git a/src/views/catalog/reducers/CatalogReducer.tsx b/src/views/catalog/reducers/CatalogReducer.tsx index 0f409867..eae3e3da 100644 --- a/src/views/catalog/reducers/CatalogReducer.tsx +++ b/src/views/catalog/reducers/CatalogReducer.tsx @@ -1,34 +1,46 @@ -import { ICatalogPageData, ICatalogPageParser } from 'nitro-renderer'; +import { CatalogPageOfferData, ICatalogPageData, ICatalogPageParser } from 'nitro-renderer'; import { Reducer } from 'react'; +import { ICatalogOffers, ICatalogSearchResult, SetOffersToNodes } from '../utils/CatalogUtilities'; export interface ICatalogState { - needsCatalogUpdate: boolean; root: ICatalogPageData; + offerRoot: ICatalogOffers; + currentTab: ICatalogPageData; pageParser: ICatalogPageParser; + activeOffer: CatalogPageOfferData; + searchResult: ICatalogSearchResult; } export interface ICatalogAction { type: string; payload: { - flag?: boolean; root?: ICatalogPageData; + offerRoot?: ICatalogOffers; + currentTab?: ICatalogPageData; pageParser?: ICatalogPageParser; + activeOffer?: CatalogPageOfferData; + searchResult?: ICatalogSearchResult; } } export class CatalogActions { - public static SET_NEEDS_UPDATE: string = 'CA_SET_NEEDS_UPDATE'; public static SET_CATALOG_ROOT: string = 'CA_SET_CATALOG_ROOT'; + public static SET_CATALOG_CURRENT_TAB: string = 'CA_SET_CATALOG_CURRENT_TAB'; public static SET_CATALOG_PAGE_PARSER: string = 'CA_SET_CATALOG_PAGE'; + public static SET_CATALOG_ACTIVE_OFFER: string = 'CA_SET_ACTIVE_OFFER'; + public static SET_SEARCH_RESULT: string = 'CA_SET_SEARCH_RESULT'; } export const initialCatalog: ICatalogState = { - needsCatalogUpdate: true, root: null, - pageParser: null + offerRoot: null, + currentTab: null, + pageParser: null, + activeOffer: null, + searchResult: null } export const CatalogReducer: Reducer = (state, action) => @@ -37,13 +49,57 @@ export const CatalogReducer: Reducer = (state, ac { case CatalogActions.SET_CATALOG_ROOT: { const root = (action.payload.root || state.root || null); + const currentTab = ((root && (root.children.length > 0) && root.children[0]) || null); - return { ...state, root }; + const offerRoot: ICatalogOffers = {}; + + SetOffersToNodes(offerRoot, root); + + return { ...state, root, offerRoot, currentTab }; + } + case CatalogActions.SET_CATALOG_CURRENT_TAB: { + const currentTab = (action.payload.currentTab || state.currentTab || null); + const searchResult = null; + + return { ...state, currentTab, searchResult }; } case CatalogActions.SET_CATALOG_PAGE_PARSER: { - const pageParser = (action.payload.pageParser || state.pageParser || null); + const pageParser = action.payload.pageParser; - return { ...state, pageParser }; + let activeOffer = null; + + if(state.activeOffer) + { + for(const offer of pageParser.offers) + { + if(offer.offerId !== state.activeOffer.offerId) continue; + + activeOffer = offer; + + break; + } + } + + if(!activeOffer) activeOffer = ((pageParser && (pageParser.offers.length > 0) && pageParser.offers[0]) || null); + + const searchResult = state.searchResult; + + if(searchResult) + { + searchResult.furniture = null; + } + + return { ...state, pageParser, activeOffer, searchResult }; + } + case CatalogActions.SET_CATALOG_ACTIVE_OFFER: { + const activeOffer = (action.payload.activeOffer || state.activeOffer || null); + + return { ...state, activeOffer }; + } + case CatalogActions.SET_SEARCH_RESULT: { + const searchResult = (action.payload.searchResult || null); + + return { ...state, searchResult }; } default: return state; diff --git a/src/views/catalog/utils/CatalogUtilities.ts b/src/views/catalog/utils/CatalogUtilities.ts new file mode 100644 index 00000000..caaf4e99 --- /dev/null +++ b/src/views/catalog/utils/CatalogUtilities.ts @@ -0,0 +1,67 @@ +import { CatalogPageOfferData, ICatalogPageData, IFurnitureData } from 'nitro-renderer'; +import { GetProductDataForLocalization } from '../../../api/nitro/session/GetProductDataForLocalization'; + +export interface ICatalogOffers +{ + [key: string]: ICatalogPageData[]; +} + +export interface ICatalogSearchResult +{ + page: ICatalogPageData; + furniture: IFurnitureData[]; +} + +export function GetOfferName(offer: CatalogPageOfferData): string +{ + const productData = GetProductDataForLocalization(offer.localizationId); + + if(productData) return productData.name; + + return offer.localizationId; +} + +export function GetOfferNodes(offers: ICatalogOffers, offerId: number): ICatalogPageData[] +{ + const pages = offers[offerId.toString()]; + const allowedPages: ICatalogPageData[] = []; + + if(pages && pages.length) + { + for(const page of pages) + { + if(!page.visible) continue; + + allowedPages.push(page); + } + } + + return allowedPages; +} + +export function SetOffersToNodes(offers: ICatalogOffers, pageData: ICatalogPageData): void +{ + if(pageData.offerIds && pageData.offerIds.length) + { + for(const offerId of pageData.offerIds) + { + let existing = offers[offerId.toString()]; + + if(!existing) + { + existing = []; + + offers[offerId.toString()] = existing; + } + + if(existing.indexOf(pageData) >= 0) continue; + + existing.push(pageData); + } + } + + if(pageData.children && pageData.children.length) + { + for(const child of pageData.children) SetOffersToNodes(offers, child); + } +} diff --git a/src/views/catalog/views/CatalogViews.scss b/src/views/catalog/views/CatalogViews.scss index 97eb4a5e..9b318dd0 100644 --- a/src/views/catalog/views/CatalogViews.scss +++ b/src/views/catalog/views/CatalogViews.scss @@ -1,6 +1,3 @@ -.nitro-catalog { - -} - @import './navigation/CatalogNavigationView'; @import './page/CatalogPageView'; +@import './search/CatalogSearchView'; diff --git a/src/views/catalog/views/navigation/CatalogNavigationView.scss b/src/views/catalog/views/navigation/CatalogNavigationView.scss index 929393b0..7ae55335 100644 --- a/src/views/catalog/views/navigation/CatalogNavigationView.scss +++ b/src/views/catalog/views/navigation/CatalogNavigationView.scss @@ -3,8 +3,8 @@ background-color: $grid-bg-color !important; .navigation-container { - height: 275px; - max-height: 275px; + height: 270px; + max-height: 270px; overflow-y: auto; } } diff --git a/src/views/catalog/views/navigation/CatalogNavigationView.tsx b/src/views/catalog/views/navigation/CatalogNavigationView.tsx index f250ce1d..7d666952 100644 --- a/src/views/catalog/views/navigation/CatalogNavigationView.tsx +++ b/src/views/catalog/views/navigation/CatalogNavigationView.tsx @@ -1,13 +1,23 @@ -import { FC } from 'react'; +import { ICatalogPageData } from 'nitro-renderer'; +import { FC, useEffect } from 'react'; import { CatalogNavigationViewProps } from './CatalogNavigationView.types'; import { CatalogNavigationSetView } from './set/CatalogNavigationSetView'; +export let ACTIVE_PAGES: ICatalogPageData[] = []; + export const CatalogNavigationView: FC = props => { const { page = null } = props; + + useEffect(() => + { + if(!page) return; + + ACTIVE_PAGES = [ page ]; + }, [ page ]); return ( -
    +
    diff --git a/src/views/catalog/views/navigation/item/CatalogNavigationItemView.tsx b/src/views/catalog/views/navigation/item/CatalogNavigationItemView.tsx index 66685b36..85d2c98f 100644 --- a/src/views/catalog/views/navigation/item/CatalogNavigationItemView.tsx +++ b/src/views/catalog/views/navigation/item/CatalogNavigationItemView.tsx @@ -12,17 +12,19 @@ export const CatalogNavigationItemView: FC = pro useEffect(() => { - if(!isActive) return; + if(!isActive || !page) return; SendMessageHook(GetCatalogPageComposer(page.pageId, -1, CatalogMode.MODE_NORMAL)); }, [ isActive, page ]); function select(): void { + if(!page) return; + setActiveChild(prevValue => { if(prevValue === page) return null; - + return page; }); } @@ -31,7 +33,7 @@ export const CatalogNavigationItemView: FC = pro
    -
    { page.localization }
    +
    { page.localization }
    { (page.children.length > 0) && }
    { isActive && page.children && (page.children.length > 0) && diff --git a/src/views/catalog/views/navigation/set/CatalogNavigationSetView.tsx b/src/views/catalog/views/navigation/set/CatalogNavigationSetView.tsx index d8dce51f..0acdd03e 100644 --- a/src/views/catalog/views/navigation/set/CatalogNavigationSetView.tsx +++ b/src/views/catalog/views/navigation/set/CatalogNavigationSetView.tsx @@ -1,5 +1,6 @@ import { ICatalogPageData } from 'nitro-renderer'; import { FC, useEffect, useState } from 'react'; +import { ACTIVE_PAGES } from '../CatalogNavigationView'; import { CatalogNavigationItemView } from '../item/CatalogNavigationItemView'; import { CatalogNavigationSetViewProps } from './CatalogNavigationSetView.types'; @@ -10,10 +11,22 @@ export const CatalogNavigationSetView: FC = props useEffect(() => { - if(!isFirstSet) return; + if(!isFirstSet || !page || (page.pageId === -1)) return; if(page && page.children.length) setActiveChild(page.children[0]); }, [ page, isFirstSet ]); + + useEffect(() => + { + if(!activeChild) return; + + const index = (ACTIVE_PAGES.push(activeChild) - 1); + + return () => + { + ACTIVE_PAGES.splice(index, (ACTIVE_PAGES.length - index)); + } + }, [ activeChild ]); return (
    diff --git a/src/views/catalog/views/page/CatalogPageView.scss b/src/views/catalog/views/page/CatalogPageView.scss index 8521e0ed..2281883f 100644 --- a/src/views/catalog/views/page/CatalogPageView.scss +++ b/src/views/catalog/views/page/CatalogPageView.scss @@ -1 +1,5 @@ @import './layout/CatalogLayout'; +@import './offer/CatalogPageOfferView'; +@import './offers/CatalogPageOffersView'; +@import './purchase/CatalogPurchaseView'; +@import './search-result/CatalogLayoutSearchResultView'; diff --git a/src/views/catalog/views/page/CatalogPageView.tsx b/src/views/catalog/views/page/CatalogPageView.tsx index bf89118e..53e9c84b 100644 --- a/src/views/catalog/views/page/CatalogPageView.tsx +++ b/src/views/catalog/views/page/CatalogPageView.tsx @@ -2,20 +2,18 @@ import { FC } from 'react'; import { useCatalogContext } from '../../context/CatalogContext'; import { CatalogPageViewProps } from './CatalogPageView.types'; import { GetCatalogLayout } from './layout/GetCatalogLayout'; +import { CatalogLayoutSearchResultView } from './search-result/CatalogLayoutSearchResultView'; export const CatalogPageView: FC = props => { + const { roomPreviewer = null } = props; const { catalogState = null } = useCatalogContext(); - const { pageParser = null } = catalogState; + const { pageParser = null, searchResult = null } = catalogState; - return ( -
    -
    - { pageParser && GetCatalogLayout(pageParser) } -
    -
    - preview area -
    -
    - ); + if(searchResult && searchResult.furniture) + { + return ; + } + + return ((pageParser && GetCatalogLayout(pageParser, roomPreviewer)) || null); } diff --git a/src/views/catalog/views/page/CatalogPageView.types.ts b/src/views/catalog/views/page/CatalogPageView.types.ts index d24a1928..80a2554b 100644 --- a/src/views/catalog/views/page/CatalogPageView.types.ts +++ b/src/views/catalog/views/page/CatalogPageView.types.ts @@ -1,4 +1,6 @@ +import { RoomPreviewer } from 'nitro-renderer'; + export interface CatalogPageViewProps { - + roomPreviewer: RoomPreviewer; } diff --git a/src/views/catalog/views/page/layout/CatalogLayout.types.ts b/src/views/catalog/views/page/layout/CatalogLayout.types.ts index f392b017..4ebfe1b7 100644 --- a/src/views/catalog/views/page/layout/CatalogLayout.types.ts +++ b/src/views/catalog/views/page/layout/CatalogLayout.types.ts @@ -1,6 +1,7 @@ -import { ICatalogPageParser } from 'nitro-renderer'; +import { ICatalogPageParser, RoomPreviewer } from 'nitro-renderer'; export interface CatalogLayoutProps { - pageParser: ICatalogPageParser; + roomPreviewer: RoomPreviewer; + pageParser?: ICatalogPageParser; } diff --git a/src/views/catalog/views/page/layout/GetCatalogLayout.tsx b/src/views/catalog/views/page/layout/GetCatalogLayout.tsx index 7cd6f4cc..7f9a2412 100644 --- a/src/views/catalog/views/page/layout/GetCatalogLayout.tsx +++ b/src/views/catalog/views/page/layout/GetCatalogLayout.tsx @@ -1,7 +1,7 @@ -import { ICatalogPageParser } from 'nitro-renderer'; +import { ICatalogPageParser, RoomPreviewer } from 'nitro-renderer'; import { CatalogLayoutDefaultView } from './default/CatalogLayoutDefaultView'; -export function GetCatalogLayout(pageParser: ICatalogPageParser): JSX.Element +export function GetCatalogLayout(pageParser: ICatalogPageParser, roomPreviewer: RoomPreviewer): JSX.Element { switch(pageParser.catalogType) { @@ -37,6 +37,6 @@ export function GetCatalogLayout(pageParser: ICatalogPageParser): JSX.Element return null; case 'default_3x3': default: - return + return } } diff --git a/src/views/catalog/views/page/layout/default/CatalogLayoutDefaultView.tsx b/src/views/catalog/views/page/layout/default/CatalogLayoutDefaultView.tsx index 15de9492..23984c80 100644 --- a/src/views/catalog/views/page/layout/default/CatalogLayoutDefaultView.tsx +++ b/src/views/catalog/views/page/layout/default/CatalogLayoutDefaultView.tsx @@ -1,13 +1,123 @@ -import { FC } from 'react'; +import { Vector3d } from 'nitro-renderer'; +import { FC, useEffect } from 'react'; +import { GetAvatarRenderManager, GetFurnitureDataForProductOffer, GetSessionDataManager } from '../../../../../../api'; +import { LimitedEditionCompletePlateView } from '../../../../../limited-edition/complete-plate/LimitedEditionCompletePlateView'; +import { RoomPreviewerView } from '../../../../../room-previewer/RoomPreviewerView'; +import { useCatalogContext } from '../../../../context/CatalogContext'; +import { FurniCategory } from '../../../../enums/FurniCategory'; +import { ProductTypeEnum } from '../../../../enums/ProductTypeEnum'; +import { GetOfferName } from '../../../../utils/CatalogUtilities'; +import { CatalogPageOffersView } from '../../offers/CatalogPageOffersView'; +import { CatalogPurchaseView } from '../../purchase/CatalogPurchaseView'; import { CatalogLayoutDefaultViewProps } from './CatalogLayoutDefaultView.types'; export const CatalogLayoutDefaultView: FC = props => { - const { pageParser = null } = props; + const { roomPreviewer = null, pageParser = null } = props; + const { catalogState } = useCatalogContext(); + const { activeOffer = null } = catalogState; + + useEffect(() => + { + if(!roomPreviewer) return; + + if(!activeOffer) + { + roomPreviewer && roomPreviewer.reset(false); + + return; + } + + const product = activeOffer.products[0]; + + if(!product) return; + + const furniData = GetFurnitureDataForProductOffer(product); + + if(!furniData && product.productType !== ProductTypeEnum.ROBOT) return; + + switch(product.productType) + { + case ProductTypeEnum.ROBOT: { + roomPreviewer.updateObjectRoom('default', 'default', 'default'); + const figure = GetAvatarRenderManager().getFigureStringWithFigureIds(product.extraParam, 'm', []); + + roomPreviewer.addAvatarIntoRoom(figure, 0); + + return; + } + case ProductTypeEnum.FLOOR: { + roomPreviewer.updateObjectRoom('default', 'default', 'default'); + + if(furniData.specialType === FurniCategory.FIGURE_PURCHASABLE_SET) + { + const setIds: number[] = []; + const sets = furniData.customParams.split(','); + + for(const set of sets) + { + const setId = parseInt(set); + + if(GetAvatarRenderManager().isValidFigureSetForGender(setId, GetSessionDataManager().gender)) setIds.push(setId); + } + + const figure = GetAvatarRenderManager().getFigureStringWithFigureIds(GetSessionDataManager().figure, GetSessionDataManager().gender, setIds); + + roomPreviewer.addAvatarIntoRoom(figure, 0); + } + else + { + roomPreviewer.addFurnitureIntoRoom(product.furniClassId, new Vector3d(90)); + } + return; + } + case ProductTypeEnum.WALL: + + switch(furniData.className) + { + case 'floor': + roomPreviewer.reset(false); + roomPreviewer.updateObjectRoom(product.extraParam); + break; + case 'wallpaper': + roomPreviewer.reset(false); + roomPreviewer.updateObjectRoom(null, product.extraParam); + break; + case 'landscape': + roomPreviewer.reset(false); + roomPreviewer.updateObjectRoom(null, null, product.extraParam); + break; + default: + roomPreviewer.updateObjectRoom('default', 'default', 'default'); + roomPreviewer.addWallItemIntoRoom(product.furniClassId, new Vector3d(90), product.extraParam); + return; + } + + // const windowData = Nitro.instance.sessionDataManager.getWallItemDataByName('ads_twi_windw'); + + // if(windowData) + // { + // this._roomPreviewer.addWallItemIntoRoom(windowData.id, new Vector3d(90), windowData.customParams) + // } + return; + } + }, [ roomPreviewer, activeOffer ]); + + const product = ((activeOffer && activeOffer.products[0]) || null); return ( -
    - { pageParser && pageParser.localization.texts[0] } +
    +
    + +
    + { product && +
    + + { product.uniqueLimitedItem && + } +
    { GetOfferName(activeOffer) }
    + +
    }
    ); } diff --git a/src/views/catalog/views/page/offer/CatalogPageOfferView.scss b/src/views/catalog/views/page/offer/CatalogPageOfferView.scss new file mode 100644 index 00000000..28e96ee4 --- /dev/null +++ b/src/views/catalog/views/page/offer/CatalogPageOfferView.scss @@ -0,0 +1,25 @@ +.catalog-offer-item-container { + height: 48px; + max-height: 48px; + + .catalog-offer-item { + width: 100%; + height: 100%; + border-color: $grid-border-color !important; + background-color: $grid-bg-color !important; + background-position: center; + background-repeat: no-repeat; + overflow: hidden; + + &.active { + border-color: $grid-active-border-color !important; + background-color: $grid-active-bg-color !important; + } + + .badge { + top: 2px; + right: 2px; + font-size: 8px; + } + } +} diff --git a/src/views/catalog/views/page/offer/CatalogPageOfferView.tsx b/src/views/catalog/views/page/offer/CatalogPageOfferView.tsx new file mode 100644 index 00000000..20fa2cfa --- /dev/null +++ b/src/views/catalog/views/page/offer/CatalogPageOfferView.tsx @@ -0,0 +1,67 @@ +import { FurnitureType, MouseEventType } from 'nitro-renderer'; +import { FC, MouseEvent, useCallback } from 'react'; +import { GetRoomEngine, GetSessionDataManager } from '../../../../../api'; +import { LimitedEditionStyledNumberView } from '../../../../limited-edition/styled-number/LimitedEditionStyledNumberView'; +import { useCatalogContext } from '../../../context/CatalogContext'; +import { CatalogActions } from '../../../reducers/CatalogReducer'; +import { CatalogPageOfferViewProps } from './CatalogPageOfferView.types'; + +export const CatalogPageOfferView: FC = props => +{ + const { isActive = false, offer = null } = props; + const { dispatchCatalogState = null } = useCatalogContext(); + + const onMouseEvent = useCallback((event: MouseEvent) => + { + switch(event.type) + { + case MouseEventType.MOUSE_DOWN: + dispatchCatalogState({ + type: CatalogActions.SET_CATALOG_ACTIVE_OFFER, + payload: { + activeOffer: offer + } + }); + return; + case MouseEventType.MOUSE_UP: + return; + case MouseEventType.ROLL_OUT: + return; + } + }, [ offer, dispatchCatalogState ]); + + const product = ((offer.products && offer.products[0]) || null); + + if(!product) return null; + + function getIconUrl(): string + { + const productType = product.productType.toUpperCase(); + + switch(productType) + { + case FurnitureType.BADGE: + return GetSessionDataManager().getBadgeUrl(product.extraParam); + case FurnitureType.FLOOR: + return GetRoomEngine().getFurnitureFloorIconUrl(product.furniClassId); + case FurnitureType.WALL: + return GetRoomEngine().getFurnitureWallIconUrl(product.furniClassId, product.extraParam); + } + + return ''; + } + + const imageUrl = `url(${ getIconUrl() })`; + + return ( +
    +
    + { (product.productCount > 1) && { product.productCount } } + { product.uniqueLimitedItem && +
    + +
    } +
    +
    + ); +} diff --git a/src/views/catalog/views/page/offer/CatalogPageOfferView.types.ts b/src/views/catalog/views/page/offer/CatalogPageOfferView.types.ts new file mode 100644 index 00000000..7c027112 --- /dev/null +++ b/src/views/catalog/views/page/offer/CatalogPageOfferView.types.ts @@ -0,0 +1,7 @@ +import { CatalogPageOfferData } from 'nitro-renderer'; + +export interface CatalogPageOfferViewProps +{ + isActive: boolean; + offer: CatalogPageOfferData; +} diff --git a/src/views/catalog/views/page/offers/CatalogPageOffersView.scss b/src/views/catalog/views/page/offers/CatalogPageOffersView.scss new file mode 100644 index 00000000..c883abb7 --- /dev/null +++ b/src/views/catalog/views/page/offers/CatalogPageOffersView.scss @@ -0,0 +1,5 @@ +.catalog-offers-container { + height: 314px; + max-height: 314px; + overflow-y: auto; +} diff --git a/src/views/catalog/views/page/offers/CatalogPageOffersView.tsx b/src/views/catalog/views/page/offers/CatalogPageOffersView.tsx new file mode 100644 index 00000000..243e96b8 --- /dev/null +++ b/src/views/catalog/views/page/offers/CatalogPageOffersView.tsx @@ -0,0 +1,20 @@ +import { FC } from 'react'; +import { useCatalogContext } from '../../../context/CatalogContext'; +import { CatalogPageOfferView } from '../offer/CatalogPageOfferView'; +import { CatalogPageOffersViewProps } from './CatalogPageOffersView.types'; + +export const CatalogPageOffersView: FC = props => +{ + const { offers = [] } = props; + const { catalogState } = useCatalogContext(); + const { activeOffer = null } = catalogState; + + return ( +
    + { offers && (offers.length > 0) && offers.map((offer, index) => + { + return + }) } +
    + ); +} diff --git a/src/views/catalog/views/page/offers/CatalogPageOffersView.types.ts b/src/views/catalog/views/page/offers/CatalogPageOffersView.types.ts new file mode 100644 index 00000000..09307878 --- /dev/null +++ b/src/views/catalog/views/page/offers/CatalogPageOffersView.types.ts @@ -0,0 +1,6 @@ +import { CatalogPageOfferData } from 'nitro-renderer'; + +export interface CatalogPageOffersViewProps +{ + offers: CatalogPageOfferData[]; +} diff --git a/src/views/catalog/views/page/purchase/CatalogPurchaseView.scss b/src/views/catalog/views/page/purchase/CatalogPurchaseView.scss new file mode 100644 index 00000000..4559e425 --- /dev/null +++ b/src/views/catalog/views/page/purchase/CatalogPurchaseView.scss @@ -0,0 +1,8 @@ +.quantity-input { + min-height: 20px; + max-height: 20px; + width: 29px; + padding: 3px 5px; +} + +@import './purchase-button/CatalogPurchaseButtonView'; diff --git a/src/views/catalog/views/page/purchase/CatalogPurchaseView.tsx b/src/views/catalog/views/page/purchase/CatalogPurchaseView.tsx new file mode 100644 index 00000000..82c20958 --- /dev/null +++ b/src/views/catalog/views/page/purchase/CatalogPurchaseView.tsx @@ -0,0 +1,75 @@ +import { FC, useEffect, useState } from 'react'; +import { CurrencyIcon } from '../../../../../utils/currency-icon/CurrencyIcon'; +import { LocalizeText } from '../../../../../utils/LocalizeText'; +import { CatalogPurchaseViewProps } from './CatalogPurchaseView.types'; +import { CatalogPurchaseButtonView } from './purchase-button/CatalogPurchaseButtonView'; + +export const CatalogPurchaseView: FC = props => +{ + const { offer = null, pageId = -1 } = props; + const [ quantity, setQuantity ] = useState(1); + + useEffect(() => + { + setQuantity(1); + }, [ offer ]); + + function increaseQuantity(): void + { + let newQuantity = quantity + 1; + + if(newQuantity > 99) newQuantity = 99 + + setQuantity(newQuantity); + } + + function decreaseQuantity(): void + { + let newQuantity = quantity - 1; + + if(newQuantity <= 0) newQuantity = 1; + + setQuantity(newQuantity); + } + + function updateQuantity(amount: number): void + { + if(isNaN(amount) || (amount <= 0)) amount = 1; + + if(amount > 99) amount = 99; + + setQuantity(amount); + } + + return ( +
    +
    +
    + { LocalizeText('catalog.bundlewidget.price') } + { offer.bundlePurchaseAllowed && +
    + + updateQuantity(event.target.valueAsNumber)} /> + +
    } +
    +
    + { (offer.priceCredits > 0) && +
    + { offer.priceCredits * quantity } + +
    } + { (offer.priceActivityPoints > 0) && +
    + { offer.priceActivityPoints * quantity } + +
    } +
    +
    +
    + + { offer.giftable && } +
    +
    + ); +} diff --git a/src/views/catalog/views/page/purchase/CatalogPurchaseView.types.ts b/src/views/catalog/views/page/purchase/CatalogPurchaseView.types.ts new file mode 100644 index 00000000..789457ce --- /dev/null +++ b/src/views/catalog/views/page/purchase/CatalogPurchaseView.types.ts @@ -0,0 +1,7 @@ +import { CatalogPageOfferData } from 'nitro-renderer'; + +export interface CatalogPurchaseViewProps +{ + offer: CatalogPageOfferData; + pageId: number; +} diff --git a/src/views/catalog/views/page/purchase/purchase-button/CatalogPurchaseButtonView.scss b/src/views/catalog/views/page/purchase/purchase-button/CatalogPurchaseButtonView.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/views/catalog/views/page/purchase/purchase-button/CatalogPurchaseButtonView.tsx b/src/views/catalog/views/page/purchase/purchase-button/CatalogPurchaseButtonView.tsx new file mode 100644 index 00000000..1f874412 --- /dev/null +++ b/src/views/catalog/views/page/purchase/purchase-button/CatalogPurchaseButtonView.tsx @@ -0,0 +1,73 @@ +import { CatalogPurchaseComposer } from 'nitro-renderer'; +import { FC, useCallback, useEffect, useState } from 'react'; +import { CatalogEvent } from '../../../../../../events'; +import { useUiEvent } from '../../../../../../hooks/events/ui/ui-event'; +import { SendMessageHook } from '../../../../../../hooks/messages/message-event'; +import { LoadingSpinnerView } from '../../../../../../layout'; +import { LocalizeText } from '../../../../../../utils/LocalizeText'; +import { GetCurrencyAmount } from '../../../../../purse/utils/CurrencyHelper'; +import { CatalogPurchaseButtonViewProps, CatalogPurchaseState } from './CatalogPurchaseButtonView.types'; + +export const CatalogPurchaseButtonView: FC = props => +{ + const { className = '', offer = null, pageId = -1, extra = null, quantity = 1 } = props; + const [ purchaseState, setPurchaseState ] = useState(CatalogPurchaseState.NONE); + + const onCatalogEvent = useCallback((event: CatalogEvent) => + { + switch(event.type) + { + case CatalogEvent.PURCHASE_SUCCESS: + setPurchaseState(CatalogPurchaseState.NONE); + return; + case CatalogEvent.SOLD_OUT: + setPurchaseState(CatalogPurchaseState.SOLD_OUT); + return; + } + }, []); + + useUiEvent(CatalogEvent.PURCHASE_SUCCESS, onCatalogEvent); + useUiEvent(CatalogEvent.SOLD_OUT, onCatalogEvent); + + useEffect(() => + { + setPurchaseState(CatalogPurchaseState.NONE); + }, [ offer, quantity ]); + + const purchase = useCallback(() => + { + setPurchaseState(CatalogPurchaseState.PURCHASE); + + SendMessageHook(new CatalogPurchaseComposer(pageId, offer.offerId, extra, quantity)); + }, [ pageId, offer, extra, quantity ]); + + const product = offer.products[0]; + + if(product && product.uniqueLimitedItem && !product.uniqueLimitedItemsLeft) + { + return ; + } + + if((offer.priceCredits * quantity) > GetCurrencyAmount(-1)) + { + return ; + } + + if((offer.priceActivityPoints * quantity) > GetCurrencyAmount(offer.priceActivityPointsType)) + { + return ; + } + + switch(purchaseState) + { + case CatalogPurchaseState.CONFIRM: + return ; + case CatalogPurchaseState.PURCHASE: + return ; + case CatalogPurchaseState.SOLD_OUT: + return ; + case CatalogPurchaseState.NONE: + default: + return + } +} diff --git a/src/views/catalog/views/page/purchase/purchase-button/CatalogPurchaseButtonView.types.ts b/src/views/catalog/views/page/purchase/purchase-button/CatalogPurchaseButtonView.types.ts new file mode 100644 index 00000000..05cf20b5 --- /dev/null +++ b/src/views/catalog/views/page/purchase/purchase-button/CatalogPurchaseButtonView.types.ts @@ -0,0 +1,20 @@ +import { CatalogPageOfferData } from 'nitro-renderer'; + +export interface CatalogPurchaseButtonViewProps +{ + className?: string; + offer: CatalogPageOfferData; + pageId: number; + extra?: string; + quantity?: number; +} + +export class CatalogPurchaseState +{ + public static NONE = 0; + public static CONFIRM = 1; + public static PURCHASE = 2; + public static NO_CREDITS = 3; + public static NO_POINTS = 4; + public static SOLD_OUT = 5; +} diff --git a/src/views/catalog/views/page/search-result/CatalogLayoutSearchResultView.scss b/src/views/catalog/views/page/search-result/CatalogLayoutSearchResultView.scss new file mode 100644 index 00000000..ce60508f --- /dev/null +++ b/src/views/catalog/views/page/search-result/CatalogLayoutSearchResultView.scss @@ -0,0 +1,2 @@ +@import './offer/CatalogSearchResultOfferView'; +@import './offers/CatalogSearchResultOffersView'; diff --git a/src/views/catalog/views/page/search-result/CatalogLayoutSearchResultView.tsx b/src/views/catalog/views/page/search-result/CatalogLayoutSearchResultView.tsx new file mode 100644 index 00000000..eb275f08 --- /dev/null +++ b/src/views/catalog/views/page/search-result/CatalogLayoutSearchResultView.tsx @@ -0,0 +1,123 @@ +import { Vector3d } from 'nitro-renderer'; +import { FC, useEffect } from 'react'; +import { GetAvatarRenderManager, GetFurnitureDataForProductOffer, GetSessionDataManager } from '../../../../../api'; +import { LimitedEditionCompletePlateView } from '../../../../limited-edition/complete-plate/LimitedEditionCompletePlateView'; +import { RoomPreviewerView } from '../../../../room-previewer/RoomPreviewerView'; +import { useCatalogContext } from '../../../context/CatalogContext'; +import { FurniCategory } from '../../../enums/FurniCategory'; +import { ProductTypeEnum } from '../../../enums/ProductTypeEnum'; +import { GetOfferName } from '../../../utils/CatalogUtilities'; +import { CatalogPurchaseView } from '../purchase/CatalogPurchaseView'; +import { CatalogLayoutSearchResultViewProps } from './CatalogLayoutSearchResultView.types'; +import { CatalogSearchResultOffersView } from './offers/CatalogSearchResultOffersView'; + +export const CatalogLayoutSearchResultView: FC = props => +{ + const { roomPreviewer = null, furnitureDatas = null } = props; + const { catalogState } = useCatalogContext(); + const { activeOffer = null } = catalogState; + + useEffect(() => + { + if(!roomPreviewer) return; + + if(!activeOffer) + { + roomPreviewer && roomPreviewer.reset(false); + + return; + } + + const product = activeOffer.products[0]; + + if(!product) return; + + const furniData = GetFurnitureDataForProductOffer(product); + + if(!furniData && product.productType !== ProductTypeEnum.ROBOT) return; + + switch(product.productType) + { + case ProductTypeEnum.ROBOT: { + roomPreviewer.updateObjectRoom('default', 'default', 'default'); + const figure = GetAvatarRenderManager().getFigureStringWithFigureIds(product.extraParam, 'm', []); + + roomPreviewer.addAvatarIntoRoom(figure, 0); + + return; + } + case ProductTypeEnum.FLOOR: { + roomPreviewer.updateObjectRoom('default', 'default', 'default'); + + if(furniData.specialType === FurniCategory.FIGURE_PURCHASABLE_SET) + { + const setIds: number[] = []; + const sets = furniData.customParams.split(','); + + for(const set of sets) + { + const setId = parseInt(set); + + if(GetAvatarRenderManager().isValidFigureSetForGender(setId, GetSessionDataManager().gender)) setIds.push(setId); + } + + const figure = GetAvatarRenderManager().getFigureStringWithFigureIds(GetSessionDataManager().figure, GetSessionDataManager().gender, setIds); + + roomPreviewer.addAvatarIntoRoom(figure, 0); + } + else + { + roomPreviewer.addFurnitureIntoRoom(product.furniClassId, new Vector3d(90)); + } + return; + } + case ProductTypeEnum.WALL: + + switch(furniData.className) + { + case 'floor': + roomPreviewer.reset(false); + roomPreviewer.updateObjectRoom(product.extraParam); + break; + case 'wallpaper': + roomPreviewer.reset(false); + roomPreviewer.updateObjectRoom(null, product.extraParam); + break; + case 'landscape': + roomPreviewer.reset(false); + roomPreviewer.updateObjectRoom(null, null, product.extraParam); + break; + default: + roomPreviewer.updateObjectRoom('default', 'default', 'default'); + roomPreviewer.addWallItemIntoRoom(product.furniClassId, new Vector3d(90), product.extraParam); + return; + } + + // const windowData = Nitro.instance.sessionDataManager.getWallItemDataByName('ads_twi_windw'); + + // if(windowData) + // { + // this._roomPreviewer.addWallItemIntoRoom(windowData.id, new Vector3d(90), windowData.customParams) + // } + return; + } + }, [ roomPreviewer, activeOffer ]); + + const product = ((activeOffer && activeOffer.products[0]) || null); + + return ( +
    +
    + +
    + { product && +
    + + { product.uniqueLimitedItem && + } +
    { GetOfferName(activeOffer) }
    + +
    } +
    + ); +} diff --git a/src/views/catalog/views/page/search-result/CatalogLayoutSearchResultView.types.ts b/src/views/catalog/views/page/search-result/CatalogLayoutSearchResultView.types.ts new file mode 100644 index 00000000..2ba05ff8 --- /dev/null +++ b/src/views/catalog/views/page/search-result/CatalogLayoutSearchResultView.types.ts @@ -0,0 +1,7 @@ +import { IFurnitureData, RoomPreviewer } from 'nitro-renderer'; + +export interface CatalogLayoutSearchResultViewProps +{ + roomPreviewer: RoomPreviewer; + furnitureDatas: IFurnitureData[]; +} diff --git a/src/views/catalog/views/page/search-result/offer/CatalogSearchResultOfferView.scss b/src/views/catalog/views/page/search-result/offer/CatalogSearchResultOfferView.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/views/catalog/views/page/search-result/offer/CatalogSearchResultOfferView.tsx b/src/views/catalog/views/page/search-result/offer/CatalogSearchResultOfferView.tsx new file mode 100644 index 00000000..e17249b6 --- /dev/null +++ b/src/views/catalog/views/page/search-result/offer/CatalogSearchResultOfferView.tsx @@ -0,0 +1,49 @@ +import { CatalogSearchComposer, FurnitureType, MouseEventType } from 'nitro-renderer'; +import { FC, MouseEvent, useCallback } from 'react'; +import { GetRoomEngine, GetSessionDataManager } from '../../../../../../api'; +import { SendMessageHook } from '../../../../../../hooks/messages/message-event'; +import { CatalogSearchResultOfferViewProps } from './CatalogSearchResultOfferView.types'; + +export const CatalogSearchResultOfferView: FC = props => +{ + const { isActive = false, offer = null } = props; + + const onMouseEvent = useCallback((event: MouseEvent) => + { + switch(event.type) + { + case MouseEventType.MOUSE_DOWN: + SendMessageHook(new CatalogSearchComposer(offer.purchaseOfferId)); + return; + case MouseEventType.MOUSE_UP: + return; + case MouseEventType.ROLL_OUT: + return; + } + }, [ offer ]); + + function getIconUrl(): string + { + const productType = offer.type.toUpperCase(); + + switch(productType) + { + case FurnitureType.BADGE: + return GetSessionDataManager().getBadgeUrl(offer.customParams); + case FurnitureType.FLOOR: + return GetRoomEngine().getFurnitureFloorIconUrl(offer.id); + case FurnitureType.WALL: + return GetRoomEngine().getFurnitureWallIconUrl(offer.id, offer.customParams); + } + + return ''; + } + + const imageUrl = `url(${ getIconUrl() })`; + + return ( +
    +
    +
    + ); +} diff --git a/src/views/catalog/views/page/search-result/offer/CatalogSearchResultOfferView.types.ts b/src/views/catalog/views/page/search-result/offer/CatalogSearchResultOfferView.types.ts new file mode 100644 index 00000000..d56f834a --- /dev/null +++ b/src/views/catalog/views/page/search-result/offer/CatalogSearchResultOfferView.types.ts @@ -0,0 +1,7 @@ +import { IFurnitureData } from 'nitro-renderer'; + +export interface CatalogSearchResultOfferViewProps +{ + isActive: boolean; + offer: IFurnitureData; +} diff --git a/src/views/catalog/views/page/search-result/offers/CatalogSearchResultOffersView.scss b/src/views/catalog/views/page/search-result/offers/CatalogSearchResultOffersView.scss new file mode 100644 index 00000000..c883abb7 --- /dev/null +++ b/src/views/catalog/views/page/search-result/offers/CatalogSearchResultOffersView.scss @@ -0,0 +1,5 @@ +.catalog-offers-container { + height: 314px; + max-height: 314px; + overflow-y: auto; +} diff --git a/src/views/catalog/views/page/search-result/offers/CatalogSearchResultOffersView.tsx b/src/views/catalog/views/page/search-result/offers/CatalogSearchResultOffersView.tsx new file mode 100644 index 00000000..4c18c125 --- /dev/null +++ b/src/views/catalog/views/page/search-result/offers/CatalogSearchResultOffersView.tsx @@ -0,0 +1,22 @@ +import { FC } from 'react'; +import { useCatalogContext } from '../../../../context/CatalogContext'; +import { CatalogSearchResultOfferView } from '../offer/CatalogSearchResultOfferView'; +import { CatalogSearchResultOffersViewProps } from './CatalogSearchResultOffersView.types'; + +export const CatalogSearchResultOffersView: FC = props => +{ + const { offers = [] } = props; + const { catalogState } = useCatalogContext(); + const { activeOffer = null } = catalogState; + + return ( +
    + { offers && (offers.length > 0) && offers.map((offer, index) => + { + const isActive = (activeOffer && (activeOffer.products[0].furniClassId === offer.id)); + + return + }) } +
    + ); +} diff --git a/src/views/catalog/views/page/search-result/offers/CatalogSearchResultOffersView.types.ts b/src/views/catalog/views/page/search-result/offers/CatalogSearchResultOffersView.types.ts new file mode 100644 index 00000000..8e9b6b0d --- /dev/null +++ b/src/views/catalog/views/page/search-result/offers/CatalogSearchResultOffersView.types.ts @@ -0,0 +1,6 @@ +import { IFurnitureData } from 'nitro-renderer'; + +export interface CatalogSearchResultOffersViewProps +{ + offers: IFurnitureData[]; +} diff --git a/src/views/catalog/views/search/CatalogSearchView.scss b/src/views/catalog/views/search/CatalogSearchView.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/views/catalog/views/search/CatalogSearchView.tsx b/src/views/catalog/views/search/CatalogSearchView.tsx new file mode 100644 index 00000000..67ae21a1 --- /dev/null +++ b/src/views/catalog/views/search/CatalogSearchView.tsx @@ -0,0 +1,125 @@ +import { ICatalogPageData, IFurnitureData } from 'nitro-renderer'; +import { FC, useCallback, useEffect, useState } from 'react'; +import { GetSessionDataManager } from '../../../../api'; +import { LocalizeText } from '../../../../utils/LocalizeText'; +import { useCatalogContext } from '../../context/CatalogContext'; +import { CatalogActions } from '../../reducers/CatalogReducer'; +import { GetOfferNodes } from '../../utils/CatalogUtilities'; +import { CatalogSearchViewProps } from './CatalogSearchView.types'; + +export const CatalogSearchView: FC = props => +{ + const [ searchValue, setSearchValue ] = useState(''); + const { catalogState = null, dispatchCatalogState = null } = useCatalogContext(); + const { offerRoot = null, searchResult = null } = catalogState; + + useEffect(() => + { + setSearchValue(prevValue => + { + if(!searchResult && prevValue && prevValue.length) return ''; + + return prevValue; + }); + }, [ searchResult ]); + + const processSearch = useCallback((search: string) => + { + if(!search || !search.length || !offerRoot) + { + dispatchCatalogState({ + type: CatalogActions.SET_SEARCH_RESULT, + payload: { + searchResult: null + } + }); + + return; + } + + search = search.toLocaleLowerCase(); + + const furnitureData = GetSessionDataManager().getAllFurnitureData({ + loadFurnitureData: null + }); + + if(!furnitureData) return; + + const foundPages: ICatalogPageData[] = []; + const foundFurniture: IFurnitureData[] = []; + + for(const furniture of furnitureData) + { + if((furniture.purchaseOfferId === -1) && (furniture.rentOfferId === -1)) continue; + + const pages = [ + ...GetOfferNodes(offerRoot, furniture.purchaseOfferId), + ...GetOfferNodes(offerRoot, furniture.rentOfferId) + ]; + + if(!pages.length) continue; + + const searchValue = [ furniture.className, furniture.name ].join(' ').toLocaleLowerCase(); + + if(searchValue.indexOf(search) === -1) continue; + + foundPages.push(...pages); + foundFurniture.push(furniture); + } + + const uniquePages = foundPages.filter((value, index, self) => + { + return (self.indexOf(value) === index); + }); + + const catalogPage: ICatalogPageData = { + visible: true, + icon: 0, + pageId: -1, + pageName: LocalizeText('generic.search'), + localization: LocalizeText('generic.search'), + children: [ ...uniquePages ], + offerIds: [] + }; + + dispatchCatalogState({ + type: CatalogActions.SET_SEARCH_RESULT, + payload: { + searchResult: { + page: catalogPage, + furniture: foundFurniture + } + } + }); + }, [ offerRoot, dispatchCatalogState ]); + + useEffect(() => + { + if(!searchValue) + { + processSearch(searchValue); + + return; + } + + const timeout = setTimeout(() => processSearch(searchValue), 300); + + return () => + { + clearTimeout(timeout); + } + }, [ searchValue, processSearch ]); + + return ( +
    +
    + setSearchValue(event.target.value) } /> +
    +
    + +
    +
    + ); +} diff --git a/src/views/catalog/views/search/CatalogSearchView.types.ts b/src/views/catalog/views/search/CatalogSearchView.types.ts new file mode 100644 index 00000000..ac0164e4 --- /dev/null +++ b/src/views/catalog/views/search/CatalogSearchView.types.ts @@ -0,0 +1,4 @@ +export interface CatalogSearchViewProps +{ + +} diff --git a/src/views/inventory/InventoryView.scss b/src/views/inventory/InventoryView.scss index 83248603..4f6c5fc4 100644 --- a/src/views/inventory/InventoryView.scss +++ b/src/views/inventory/InventoryView.scss @@ -1,7 +1,5 @@ .nitro-inventory { width: 475px; - // height: 300px; - // max-height: 300px; .content-area { height: 243.5px; diff --git a/src/views/inventory/views/furniture/InventoryFurnitureView.scss b/src/views/inventory/views/furniture/InventoryFurnitureView.scss index 93e0341b..19515f7a 100644 --- a/src/views/inventory/views/furniture/InventoryFurnitureView.scss +++ b/src/views/inventory/views/furniture/InventoryFurnitureView.scss @@ -1,3 +1,9 @@ +.limited-edition-info-container { + position: absolute; + top: 5px; + right: 15px; +} + @import './item/InventoryFurnitureItemView'; @import './results/InventoryFurnitureResultsView'; @import './search/InventoryFurnitureSearchView'; diff --git a/src/views/inventory/views/furniture/InventoryFurnitureView.tsx b/src/views/inventory/views/furniture/InventoryFurnitureView.tsx index 67609c41..4be5059f 100644 --- a/src/views/inventory/views/furniture/InventoryFurnitureView.tsx +++ b/src/views/inventory/views/furniture/InventoryFurnitureView.tsx @@ -3,6 +3,7 @@ import { FC, useEffect, useState } from 'react'; import { GetRoomEngine } from '../../../../api'; import { SendMessageHook } from '../../../../hooks/messages/message-event'; import { LocalizeText } from '../../../../utils/LocalizeText'; +import { LimitedEditionCompactPlateView } from '../../../limited-edition/compact-plate/LimitedEditionCompactPlateView'; import { RoomPreviewerView } from '../../../room-previewer/RoomPreviewerView'; import { useInventoryContext } from '../../context/InventoryContext'; import { InventoryFurnitureActions } from '../../reducers/InventoryFurnitureReducer'; @@ -120,8 +121,12 @@ export const InventoryFurnitureView: FC = props =>
    -
    +
    + { groupItem && groupItem.stuffData.isUnique && +
    + +
    } { groupItem &&

    { groupItem.name }

    { !!roomSession && } diff --git a/src/views/inventory/views/furniture/item/InventoryFurnitureItemView.tsx b/src/views/inventory/views/furniture/item/InventoryFurnitureItemView.tsx index f1179c9c..f86f611d 100644 --- a/src/views/inventory/views/furniture/item/InventoryFurnitureItemView.tsx +++ b/src/views/inventory/views/furniture/item/InventoryFurnitureItemView.tsx @@ -1,5 +1,6 @@ import { MouseEventType } from 'nitro-renderer'; import { FC, MouseEvent, useCallback, useState } from 'react'; +import { LimitedEditionStyledNumberView } from '../../../../limited-edition/styled-number/LimitedEditionStyledNumberView'; import { useInventoryContext } from '../../../context/InventoryContext'; import { InventoryFurnitureActions } from '../../../reducers/InventoryFurnitureReducer'; import { attemptItemPlacement } from '../../../utils/FurnitureUtilities'; @@ -39,8 +40,12 @@ export const InventoryFurnitureItemView: FC = p return (
    -
    +
    { groupItem.getUnlockedCount() } + { groupItem.stuffData.isUnique && +
    + +
    }
    ); diff --git a/src/views/limited-edition/LimitedEdition.scss b/src/views/limited-edition/LimitedEdition.scss new file mode 100644 index 00000000..c3095bbb --- /dev/null +++ b/src/views/limited-edition/LimitedEdition.scss @@ -0,0 +1,46 @@ +.unique-item { + background: url("../../assets/images/unique/grid-bg.png") center no-repeat, rgba(0, 0, 0, 0.1); + + &:before { + position: absolute; + content: ' '; + width: 100%; + height: 100%; + background: url("../../assets/images/unique/grid-bg-glass.png") center no-repeat; + } + + &.sold-out:before { + background: url("../../assets/images/unique/grid-bg-sold-out.png") center no-repeat, url("../../assets/images/unique/grid-bg-glass.png") center no-repeat; + } + + .unique-item-counter { + margin: 0 auto; + display: flex; + justify-content: center; + align-items: center; + bottom: 1px; + width: 100%; + height: 9px; + background: url("../../assets/images/unique/grid-count-bg.png") center no-repeat; + } +} + +.unique-sold-out-blocker { + width: 364px; + height: 30px; + background: url("../../assets/images/unique/catalog-info-sold-out.png"); + + div { + float: right; + width: 140px; + text-align: center; + font-weight: bold; + margin-top: 5px; + margin-right: 17px; + color: #000; + } +} + +@import './compact-plate/LimitedEditionCompactPlateView'; +@import './complete-plate/LimitedEditionCompletePlateView'; +@import './styled-number/LimitedEditionStyledNumberView'; diff --git a/src/views/limited-edition/compact-plate/LimitedEditionCompactPlateView.scss b/src/views/limited-edition/compact-plate/LimitedEditionCompactPlateView.scss new file mode 100644 index 00000000..f4190dc7 --- /dev/null +++ b/src/views/limited-edition/compact-plate/LimitedEditionCompactPlateView.scss @@ -0,0 +1,17 @@ +.unique-compact-plate { + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: center; + right: 16px; + width: 34px; + height: 37px; + background: url("../../../assets/images/unique/inventory-info-amount-bg.png"); + + div { + display: flex; + justify-content: center; + align-items: center; + height: 9.5px; + } +} diff --git a/src/views/limited-edition/compact-plate/LimitedEditionCompactPlateView.tsx b/src/views/limited-edition/compact-plate/LimitedEditionCompactPlateView.tsx new file mode 100644 index 00000000..29440b99 --- /dev/null +++ b/src/views/limited-edition/compact-plate/LimitedEditionCompactPlateView.tsx @@ -0,0 +1,19 @@ +import { FC } from 'react'; +import { LimitedEditionStyledNumberView } from '../styled-number/LimitedEditionStyledNumberView'; +import { LimitedEditionCompactPlateViewProps } from './LimitedEditionCompactPlateView.types'; + +export const LimitedEditionCompactPlateView: FC = props => +{ + const { uniqueNumber = 0, uniqueSeries = 0 } = props; + + return ( +
    +
    + +
    +
    + +
    +
    + ); +} diff --git a/src/views/limited-edition/compact-plate/LimitedEditionCompactPlateView.types.ts b/src/views/limited-edition/compact-plate/LimitedEditionCompactPlateView.types.ts new file mode 100644 index 00000000..8eb893e2 --- /dev/null +++ b/src/views/limited-edition/compact-plate/LimitedEditionCompactPlateView.types.ts @@ -0,0 +1,5 @@ +export interface LimitedEditionCompactPlateViewProps +{ + uniqueNumber: number; + uniqueSeries: number; +} diff --git a/src/views/limited-edition/complete-plate/LimitedEditionCompletePlateView.scss b/src/views/limited-edition/complete-plate/LimitedEditionCompletePlateView.scss new file mode 100644 index 00000000..f77718a5 --- /dev/null +++ b/src/views/limited-edition/complete-plate/LimitedEditionCompletePlateView.scss @@ -0,0 +1,20 @@ +.unique-complete-plate { + top: 145px; + left: 10px; + width: 170px; + height: 29px; + background: url("../../../assets/images/unique/catalog-info-amount-bg.png"); + + div { + position: relative; + padding-left: 45px; + padding-right: 20px; + font-size: 10px; + color: #000; + + div { + position: absolute; + right: 0px; + } + } +} diff --git a/src/views/limited-edition/complete-plate/LimitedEditionCompletePlateView.tsx b/src/views/limited-edition/complete-plate/LimitedEditionCompletePlateView.tsx new file mode 100644 index 00000000..bd1049f6 --- /dev/null +++ b/src/views/limited-edition/complete-plate/LimitedEditionCompletePlateView.tsx @@ -0,0 +1,26 @@ +import { FC } from 'react'; +import { LocalizeText } from '../../../utils/LocalizeText'; +import { LimitedEditionStyledNumberView } from '../styled-number/LimitedEditionStyledNumberView'; +import { LimitedEditionCompletePlateViewProps } from './LimitedEditionCompletePlateView.types'; + +export const LimitedEditionCompletePlateView: FC = props => +{ + const { uniqueLimitedItemsLeft = 0, uniqueLimitedSeriesSize = 0 } = props; + + return ( +
    +
    +
    + +
    + { LocalizeText('unique.items.left') } +
    +
    +
    + +
    + { LocalizeText('unique.items.number.sold') } +
    +
    + ); +} diff --git a/src/views/limited-edition/complete-plate/LimitedEditionCompletePlateView.types.ts b/src/views/limited-edition/complete-plate/LimitedEditionCompletePlateView.types.ts new file mode 100644 index 00000000..7e341e69 --- /dev/null +++ b/src/views/limited-edition/complete-plate/LimitedEditionCompletePlateView.types.ts @@ -0,0 +1,5 @@ +export interface LimitedEditionCompletePlateViewProps +{ + uniqueLimitedItemsLeft: number; + uniqueLimitedSeriesSize: number; +} diff --git a/src/views/limited-edition/styled-number/LimitedEditionStyledNumberView.scss b/src/views/limited-edition/styled-number/LimitedEditionStyledNumberView.scss new file mode 100644 index 00000000..9d2a8414 --- /dev/null +++ b/src/views/limited-edition/styled-number/LimitedEditionStyledNumberView.scss @@ -0,0 +1,62 @@ +.limited-edition-number { + display: inline-block; + outline: 0; + height: 5px; + margin-right: 1px; + background-image: url('../../../assets/images/unique/numbers.png'); + background-repeat: no-repeat; + + &:last-child { + margin-right: 0px; + } + + &.n-0 { + width: 4px; + background-position: -1px 0px; + } + + &.n-1 { + width: 2px; + background-position: -6px 0px; + } + + &.n-2 { + width: 4px; + background-position: -9px 0px; + } + + &.n-3 { + width: 4px; + background-position: -14px 0px; + } + + &.n-4 { + width: 4px; + background-position: -19px 0px; + } + + &.n-5 { + width: 4px; + background-position: -24px 0px; + } + + &.n-6 { + width: 4px; + background-position: -29px 0px; + } + + &.n-7 { + width: 4px; + background-position: -34px 0px; + } + + &.n-8 { + width: 4px; + background-position: -39px 0px; + } + + &.n-9 { + width: 4px; + background-position: -44px 0px; + } +} diff --git a/src/views/limited-edition/styled-number/LimitedEditionStyledNumberView.tsx b/src/views/limited-edition/styled-number/LimitedEditionStyledNumberView.tsx new file mode 100644 index 00000000..9d2c6483 --- /dev/null +++ b/src/views/limited-edition/styled-number/LimitedEditionStyledNumberView.tsx @@ -0,0 +1,18 @@ +import { FC } from 'react'; +import { LimitedEditionStyledNumberViewProps } from './LimitedEditionStyledNumberView.types'; + +export const LimitedEditionStyledNumberView: FC = props => +{ + const { value = 0 } = props; + + const numbers = value.toString().split(''); + + return ( + <> + { numbers.map((number, index) => + { + return ; + })} + + ); +} diff --git a/src/views/limited-edition/styled-number/LimitedEditionStyledNumberView.types.ts b/src/views/limited-edition/styled-number/LimitedEditionStyledNumberView.types.ts new file mode 100644 index 00000000..75bdde39 --- /dev/null +++ b/src/views/limited-edition/styled-number/LimitedEditionStyledNumberView.types.ts @@ -0,0 +1,4 @@ +export interface LimitedEditionStyledNumberViewProps +{ + value: number; +} diff --git a/src/views/purse/PurseView.tsx b/src/views/purse/PurseView.tsx index d9c6d1f4..284b657e 100644 --- a/src/views/purse/PurseView.tsx +++ b/src/views/purse/PurseView.tsx @@ -7,6 +7,7 @@ import { CurrencyView } from './currency/CurrencyView'; import { PurseMessageHandler } from './PurseMessageHandler'; import { PurseViewProps } from './PurseView.types'; import { initialPurse, PurseReducer } from './reducers/PurseReducer'; +import { SetLastCurrencies } from './utils/CurrencyHelper'; export const PurseView: FC = props => { @@ -23,6 +24,8 @@ export const PurseView: FC = props => SendMessageHook(new UserCurrencyComposer()); }, []); + SetLastCurrencies(currencies); + return ( diff --git a/src/views/purse/utils/CurrencyHelper.ts b/src/views/purse/utils/CurrencyHelper.ts new file mode 100644 index 00000000..6f382a9b --- /dev/null +++ b/src/views/purse/utils/CurrencyHelper.ts @@ -0,0 +1,20 @@ +import { Currency } from './Currency'; + +let lastCurrencies: Currency[] = []; + +export function SetLastCurrencies(currencies: Currency[]): void +{ + lastCurrencies = currencies; +} + +export function GetCurrencyAmount(type: number): number +{ + for(const currency of lastCurrencies) + { + if(currency.type !== type) continue; + + return currency.amount; + } + + return 0; +} diff --git a/src/views/room-previewer/RoomPreviewerView.scss b/src/views/room-previewer/RoomPreviewerView.scss index 34f7230d..bfbcbd74 100644 --- a/src/views/room-previewer/RoomPreviewerView.scss +++ b/src/views/room-previewer/RoomPreviewerView.scss @@ -7,6 +7,12 @@ border-radius: $border-radius; height: 100%; + &.border-0 { + &::after { + content: none; + } + } + &::after { position: absolute; content: ''; diff --git a/src/views/room/RoomView.tsx b/src/views/room/RoomView.tsx index 43fc69a2..0a399fdd 100644 --- a/src/views/room/RoomView.tsx +++ b/src/views/room/RoomView.tsx @@ -26,6 +26,8 @@ export function RoomView(props: RoomViewProps): JSX.Element return; } + Nitro.instance.renderer.resize(window.innerWidth, window.innerHeight); + const canvasId = 1; const displayObject = GetRoomEngine().getRoomInstanceDisplay(roomSession.roomId, canvasId, Nitro.instance.width, Nitro.instance.height, RoomGeometry.SCALE_ZOOMED_IN);