Merge branch 'dev' into feature/hc-center
59
package-lock.json
generated
@ -1817,12 +1817,13 @@
|
||||
"@nitrots/nitro-renderer": {
|
||||
"version": "file:../nitro-renderer",
|
||||
"requires": {
|
||||
"@pixi/canvas-renderer": "^6.1.0",
|
||||
"@pixi/extract": "^6.1.0",
|
||||
"@pixi/canvas-renderer": "^6.1.3",
|
||||
"@pixi/extract": "^6.1.3",
|
||||
"@pixi/filter-adjustment": "^4.1.3",
|
||||
"@pixi/tilemap": "^3.2.2",
|
||||
"pako": "^2.0.4",
|
||||
"pixi.js": "^6.1.0",
|
||||
"tslib": "^2.3.0"
|
||||
"pixi.js": "^6.1.3",
|
||||
"tslib": "^2.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/code-frame": {
|
||||
@ -2108,6 +2109,11 @@
|
||||
"resolved": "https://registry.npmjs.org/@pixi/ticker/-/ticker-6.1.0.tgz",
|
||||
"integrity": "sha512-tCh1dhmriLKLMcxTJ9usm1UZEK2+M5nEwvyec9kouF4EMi/PiGup65+pwTHK4SvjXD+9vbtTDam39fWYXCpRxg=="
|
||||
},
|
||||
"@pixi/tilemap": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@pixi/tilemap/-/tilemap-3.2.2.tgz",
|
||||
"integrity": "sha512-svdmMyJP63vdae3t66tCmE8IWeO/6lD1xXU+5gzfxqxJS5seTp2bm8mQok2c8PF0O6l/NYlLz6BRklOuEuHboQ=="
|
||||
},
|
||||
"@pixi/utils": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@pixi/utils/-/utils-6.1.0.tgz",
|
||||
@ -11826,6 +11832,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"load-script": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz",
|
||||
"integrity": "sha1-BJGTngvuVkPuSUp+PaPSuscMbKQ="
|
||||
},
|
||||
"loader-runner": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz",
|
||||
@ -15025,6 +15036,16 @@
|
||||
"react-lifecycles-compat": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"react-youtube": {
|
||||
"version": "7.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-youtube/-/react-youtube-7.13.1.tgz",
|
||||
"integrity": "sha512-b++TLHmHDpd0ZBS1wcbYabbuchU+W4jtx5A2MUQX0BINNKKsaIQX29sn/aLvZ9v5luwAoceia3VGtyz9blaB9w==",
|
||||
"requires": {
|
||||
"fast-deep-equal": "3.1.3",
|
||||
"prop-types": "15.7.2",
|
||||
"youtube-player": "5.5.2"
|
||||
}
|
||||
},
|
||||
"read-pkg": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
|
||||
@ -16219,6 +16240,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sister": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/sister/-/sister-3.0.2.tgz",
|
||||
"integrity": "sha512-p19rtTs+NksBRKW9qn0UhZ8/TUI9BPw9lmtHny+Y3TinWlOa9jWh9xB0AtPSdmOy49NJJJSSe0Ey4C7h0TrcYA=="
|
||||
},
|
||||
"sisteransi": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
||||
@ -19328,6 +19354,31 @@
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
|
||||
},
|
||||
"youtube-player": {
|
||||
"version": "5.5.2",
|
||||
"resolved": "https://registry.npmjs.org/youtube-player/-/youtube-player-5.5.2.tgz",
|
||||
"integrity": "sha512-ZGtsemSpXnDky2AUYWgxjaopgB+shFHgXVpiJFeNB5nWEugpW1KWYDaHKuLqh2b67r24GtP6HoSW5swvf0fFIQ==",
|
||||
"requires": {
|
||||
"debug": "^2.6.6",
|
||||
"load-script": "^1.0.0",
|
||||
"sister": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
"hof.furni.url": "https://swf.nitrots.co/dcr/hof_furni",
|
||||
"images.url": "${asset.url}/images",
|
||||
"gamedata.url": "${asset.url}/gamedata",
|
||||
"sounds.url": "${asset.url}/sounds",
|
||||
"sounds.url": "${asset.url}/sounds/%sample%.mp3",
|
||||
"external.texts.url": [ "${gamedata.url}/ExternalTexts.json", "${gamedata.url}/UITexts.json" ],
|
||||
"external.samples.url": "${hof.furni.url}/mp3/sound_machine_sample_%sample%.mp3",
|
||||
"furnidata.url": "${gamedata.url}/FurnitureData.json",
|
||||
|
14
src/api/utils/PlaySound.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { NitroSoundEvent } from '@nitrots/nitro-renderer/src/nitro/events/NitroSoundEvent';
|
||||
import { dispatchMainEvent } from '../../hooks';
|
||||
|
||||
export function PlaySound(sampleCode: string): void
|
||||
{
|
||||
dispatchMainEvent(new NitroSoundEvent(NitroSoundEvent.PLAY_SOUND, sampleCode));
|
||||
}
|
||||
|
||||
export const CAMERA_SHUTTER = 'camera_shutter';
|
||||
export const CREDITS = 'credits';
|
||||
export const DUCKETS = 'duckets';
|
||||
export const MESSENGER_NEW_THREAD = 'messenger_new_thread';
|
||||
export const MESSENGER_MESSAGE_RECEIVED = 'messenger_message_received';
|
||||
export const MODTOOLS_NEW_TICKET = 'modtools_new_ticket';
|
BIN
src/assets/images/campaign/available.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
src/assets/images/campaign/campaign_day_generic_bg.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
src/assets/images/campaign/campaign_opened.png
Normal file
After Width: | Height: | Size: 744 B |
BIN
src/assets/images/campaign/locked.png
Normal file
After Width: | Height: | Size: 220 B |
BIN
src/assets/images/campaign/locked_bg.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
src/assets/images/campaign/next.png
Normal file
After Width: | Height: | Size: 244 B |
BIN
src/assets/images/campaign/prev.png
Normal file
After Width: | Height: | Size: 235 B |
BIN
src/assets/images/campaign/unavailable.png
Normal file
After Width: | Height: | Size: 448 B |
BIN
src/assets/images/campaign/unlocked_bg.png
Normal file
After Width: | Height: | Size: 8.6 KiB |
@ -16,11 +16,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.theme-dark {
|
||||
background-color: #3d5f6e !important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.bg-tertiary-split {
|
||||
position: relative;
|
||||
border-bottom: 2px solid darken($quaternary, 5);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { NitroRectangle } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useRef } from 'react';
|
||||
import { GetRoomEngine, LocalizeText } from '../../api';
|
||||
import { CAMERA_SHUTTER, PlaySound } from '../../api/utils/PlaySound';
|
||||
import { DraggableWindow } from '../draggable-window';
|
||||
import { NitroLayoutMiniCameraViewProps } from './NitroLayoutMiniCameraView.types';
|
||||
|
||||
@ -20,6 +21,7 @@ export const NitroLayoutMiniCameraView: FC<NitroLayoutMiniCameraViewProps> = pro
|
||||
|
||||
const takePicture = useCallback(() =>
|
||||
{
|
||||
PlaySound(CAMERA_SHUTTER);
|
||||
textureReceiver(GetRoomEngine().createTextureFromRoom(roomId, 1, getCameraBounds()));
|
||||
}, [ roomId, getCameraBounds, textureReceiver ]);
|
||||
|
||||
|
@ -25,3 +25,4 @@
|
||||
@import "./floorplan-editor/FloorplanEditorView";
|
||||
@import "./nitropedia/NitropediaView";
|
||||
@import "./hc-center/HcCenterView.scss";
|
||||
@import './campaign/CampaignView';
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { NitroRectangle, TextureUtils } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useRef } from 'react';
|
||||
import { GetRoomEngine, GetRoomSession, LocalizeText } from '../../../../api';
|
||||
import { CAMERA_SHUTTER, PlaySound } from '../../../../api/utils/PlaySound';
|
||||
import { DraggableWindow } from '../../../../layout';
|
||||
import { CameraPicture } from '../../common/CameraPicture';
|
||||
import { useCameraWidgetContext } from '../../context/CameraWidgetContext';
|
||||
@ -30,7 +31,6 @@ export const CameraWidgetCaptureView: FC<CameraWidgetCaptureViewProps> = props =
|
||||
if(selectedPictureIndex > -1)
|
||||
{
|
||||
setSelectedPictureIndex(-1);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -45,6 +45,7 @@ export const CameraWidgetCaptureView: FC<CameraWidgetCaptureViewProps> = props =
|
||||
clone.pop();
|
||||
}
|
||||
|
||||
PlaySound(CAMERA_SHUTTER);
|
||||
clone.push(new CameraPicture(texture, TextureUtils.generateImageUrl(texture)));
|
||||
|
||||
setCameraRoll(clone);
|
||||
|
1
src/views/campaign/CampaignView.scss
Normal file
@ -0,0 +1 @@
|
||||
@import './views/calendar/CalendarView';
|
113
src/views/campaign/CampaignView.tsx
Normal file
@ -0,0 +1,113 @@
|
||||
import { CampaignCalendarData, CampaignCalendarDataMessageEvent, CampaignCalendarDoorOpenedMessageEvent, OpenCampaignCalendarDoorAsStaffComposer, OpenCampaignCalendarDoorComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { AddEventLinkTracker, RemoveLinkEventTracker } from '../../api';
|
||||
import { BatchUpdates, CreateMessageHook, SendMessageHook } from '../../hooks';
|
||||
import { CalendarView } from './views/calendar/CalendarView';
|
||||
|
||||
export const CampaignView: FC<{}> = props =>
|
||||
{
|
||||
const [ calendarData, setCalendarData ] = useState<CampaignCalendarData>(null);
|
||||
const [ lastOpenAttempt, setLastOpenAttempt ] = useState<number>(-1);
|
||||
const [ receivedProducts, setReceivedProducts ] = useState<Map<number, string>>(new Map());
|
||||
const [ isCalendarOpen, setCalendarOpen ] = useState(false);
|
||||
|
||||
const onCampaignCalendarDataMessageEvent = useCallback((event: CampaignCalendarDataMessageEvent) =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
if(!parser) return;
|
||||
setCalendarData(parser.calendarData);
|
||||
}, []);
|
||||
|
||||
CreateMessageHook(CampaignCalendarDataMessageEvent, onCampaignCalendarDataMessageEvent);
|
||||
|
||||
const onCampaignCalendarDoorOpenedMessageEvent = useCallback((event: CampaignCalendarDoorOpenedMessageEvent) =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
if(!parser) return;
|
||||
|
||||
const lastAttempt = lastOpenAttempt;
|
||||
|
||||
if(parser.doorOpened)
|
||||
{
|
||||
BatchUpdates(() =>
|
||||
{
|
||||
setCalendarData(prev =>
|
||||
{
|
||||
const copy = prev.clone();
|
||||
copy.openedDays.push(lastOpenAttempt);
|
||||
|
||||
return copy;
|
||||
});
|
||||
|
||||
setReceivedProducts(prev =>
|
||||
{
|
||||
const copy = new Map(prev);
|
||||
copy.set(lastAttempt, parser.furnitureClassName);
|
||||
|
||||
return copy;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setLastOpenAttempt(-1);
|
||||
}, [lastOpenAttempt]);
|
||||
|
||||
CreateMessageHook(CampaignCalendarDoorOpenedMessageEvent, onCampaignCalendarDoorOpenedMessageEvent);
|
||||
|
||||
const openPackage = useCallback((id: number, asStaff = false) =>
|
||||
{
|
||||
if(!calendarData) return;
|
||||
|
||||
setLastOpenAttempt(id);
|
||||
|
||||
if(asStaff)
|
||||
{
|
||||
SendMessageHook(new OpenCampaignCalendarDoorAsStaffComposer(calendarData.campaignName, id));
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
SendMessageHook(new OpenCampaignCalendarDoorComposer(calendarData.campaignName, id));
|
||||
}
|
||||
}, [calendarData]);
|
||||
|
||||
const onCalendarClose = useCallback(() =>
|
||||
{
|
||||
setCalendarOpen(false);
|
||||
}, []);
|
||||
|
||||
const onLinkReceived = useCallback((link: string) =>
|
||||
{
|
||||
const value = link.split('/');
|
||||
|
||||
if(value.length < 2) return;
|
||||
|
||||
switch(value[1])
|
||||
{
|
||||
case 'calendar':
|
||||
setCalendarOpen(true);
|
||||
break;
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
const linkTracker = { linkReceived: onLinkReceived, eventUrlPrefix: 'openView/' };
|
||||
AddEventLinkTracker(linkTracker);
|
||||
|
||||
return () =>
|
||||
{
|
||||
RemoveLinkEventTracker(linkTracker);
|
||||
}
|
||||
}, [onLinkReceived]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{(calendarData && isCalendarOpen) &&
|
||||
<CalendarView close={onCalendarClose} campaignName={calendarData.campaignName} currentDay={calendarData.currentDay} numDays={calendarData.campaignDays} openedDays={calendarData.openedDays} missedDays={calendarData.missedDays} openPackage={openPackage} receivedProducts={receivedProducts} />
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
7
src/views/campaign/common/CalendarItemState.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export class CalendarItemState
|
||||
{
|
||||
public static readonly STATE_UNLOCKED = 1;
|
||||
public static readonly STATE_LOCKED_AVAILABLE = 2;
|
||||
public static readonly STATE_LOCKED_EXPIRED = 3;
|
||||
public static readonly STATE_LOCKED_FUTURE = 4;
|
||||
}
|
4
src/views/campaign/common/Utils.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export const getNumItemsDisplayed = (): number =>
|
||||
{
|
||||
return Math.min(Math.max(2, Math.floor(window.screen.width / 135) - 3), 7);
|
||||
}
|
50
src/views/campaign/views/calendar-item/CalendarItemView.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import { FC, useCallback } from 'react';
|
||||
import { GetRoomEngine, GetSessionDataManager } from '../../../../api';
|
||||
import { NitroLayoutFlexColumn } from '../../../../layout';
|
||||
import { CalendarItemState } from '../../common/CalendarItemState';
|
||||
import { CalendarItemViewProps } from './CalendarItemView.types';
|
||||
|
||||
export const CalendarItemView: FC<CalendarItemViewProps> = props =>
|
||||
{
|
||||
const { state = null, productName = null, active = false, onClick = null, id = null } = props;
|
||||
|
||||
const getFurnitureIcon = useCallback((name: string) =>
|
||||
{
|
||||
|
||||
let furniData = GetSessionDataManager().getFloorItemDataByName(name);
|
||||
let url = null;
|
||||
if(furniData) url = GetRoomEngine().getFurnitureFloorIconUrl(furniData.id);
|
||||
else
|
||||
{
|
||||
furniData = GetSessionDataManager().getWallItemDataByName(name);
|
||||
if(furniData) url = GetRoomEngine().getFurnitureWallIconUrl(furniData.id);
|
||||
}
|
||||
|
||||
return url;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<NitroLayoutFlexColumn className={`calendar-item h-100 w-100 cursor-pointer align-items-center justify-content-center ${active ? 'active' : ''}`} onClick={() => onClick(id)}>
|
||||
{ (state === CalendarItemState.STATE_UNLOCKED) &&
|
||||
<div className="unlocked-generic-bg d-flex justify-content-center align-items-center">
|
||||
<div className="opened d-flex justify-content-center align-items-center">
|
||||
{ productName &&
|
||||
<img className="furni-icon" src={getFurnitureIcon(productName)} alt='' />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
{ (state !== CalendarItemState.STATE_UNLOCKED) &&
|
||||
<div className="locked-generic-bg d-flex justify-content-center align-items-center">
|
||||
{ (state === CalendarItemState.STATE_LOCKED_AVAILABLE) &&
|
||||
<div className="available"/>
|
||||
}
|
||||
{ (state === CalendarItemState.STATE_LOCKED_EXPIRED || state === CalendarItemState.STATE_LOCKED_FUTURE) &&
|
||||
<div className="unavailable" />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</NitroLayoutFlexColumn>
|
||||
);
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
export interface CalendarItemViewProps
|
||||
{
|
||||
id: number;
|
||||
productName?: string;
|
||||
state: number;
|
||||
active?: boolean;
|
||||
onClick(itemId: number): void;
|
||||
}
|
68
src/views/campaign/views/calendar/CalendarView.scss
Normal file
@ -0,0 +1,68 @@
|
||||
.nitro-campaign-calendar {
|
||||
width: 1055px;
|
||||
height: 400px;
|
||||
|
||||
.calendar-item {
|
||||
background: url("../../../../assets/images/campaign/campaign_day_generic_bg.png");
|
||||
max-height: 100%;
|
||||
min-width: 135px;
|
||||
filter: brightness(80%);
|
||||
|
||||
&.active {
|
||||
filter: brightness(100%);
|
||||
}
|
||||
|
||||
.unlocked-generic-bg {
|
||||
background: url("../../../../assets/images/campaign/unlocked_bg.png");
|
||||
background-size: 132px 132px;
|
||||
width: 132px;
|
||||
height: 132px;
|
||||
|
||||
//width: 190px;
|
||||
//height: 189px;
|
||||
|
||||
.opened {
|
||||
background: url("../../../../assets/images/campaign/campaign_opened.png");
|
||||
width: 96px;
|
||||
height: 66px;
|
||||
|
||||
.furni-icon
|
||||
{
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.locked-generic-bg {
|
||||
background: url("../../../../assets/images/campaign/locked_bg.png");
|
||||
width: 132px;
|
||||
height: 132px;
|
||||
|
||||
.available {
|
||||
background: url("../../../../assets/images/campaign/available.png");
|
||||
width: 69px;
|
||||
height: 78px;
|
||||
}
|
||||
|
||||
.unavailable {
|
||||
background: url("../../../../assets/images/campaign/unavailable.png");
|
||||
width: 68px;
|
||||
height: 78px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.calendar-prev {
|
||||
background: url("../../../../assets/images/campaign/prev.png");
|
||||
width: 33px;
|
||||
min-width: 33px;
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
.calendar-next {
|
||||
background: url("../../../../assets/images/campaign/next.png");
|
||||
width: 33px;
|
||||
min-width: 33px;
|
||||
height: 34px;
|
||||
}
|
||||
}
|
134
src/views/campaign/views/calendar/CalendarView.tsx
Normal file
@ -0,0 +1,134 @@
|
||||
import { FC, useCallback, useState } from 'react';
|
||||
import { GetSessionDataManager, LocalizeText } from '../../../../api';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroLayoutFlex } from '../../../../layout';
|
||||
import { CalendarItemState } from '../../common/CalendarItemState';
|
||||
import { getNumItemsDisplayed } from '../../common/Utils';
|
||||
import { CalendarItemView } from '../calendar-item/CalendarItemView';
|
||||
import { CalendarViewProps } from './CalendarView.types';
|
||||
|
||||
export const CalendarView: FC<CalendarViewProps> = props =>
|
||||
{
|
||||
const { close = null, campaignName = null, currentDay = null, numDays = null, missedDays = null, openedDays = null, openPackage = null, receivedProducts = null } = props;
|
||||
const [selectedDay, setSelectedDay] = useState(currentDay);
|
||||
const [index, setIndex] = useState(Math.max(0, selectedDay - 1));
|
||||
|
||||
const getDayState = useCallback((day: number) =>
|
||||
{
|
||||
if(openedDays.includes(day))
|
||||
{
|
||||
return CalendarItemState.STATE_UNLOCKED;
|
||||
}
|
||||
if(day > currentDay)
|
||||
{
|
||||
return CalendarItemState.STATE_LOCKED_FUTURE;
|
||||
}
|
||||
|
||||
if(missedDays.includes(day))
|
||||
{
|
||||
return CalendarItemState.STATE_LOCKED_EXPIRED;
|
||||
}
|
||||
|
||||
return CalendarItemState.STATE_LOCKED_AVAILABLE;
|
||||
}, [currentDay, missedDays, openedDays]);
|
||||
|
||||
const dayMessage = useCallback((day: number) =>
|
||||
{
|
||||
const state = getDayState(day);
|
||||
|
||||
switch(state)
|
||||
{
|
||||
case CalendarItemState.STATE_UNLOCKED:
|
||||
return LocalizeText('campaign.calendar.info.unlocked');
|
||||
case CalendarItemState.STATE_LOCKED_FUTURE:
|
||||
return LocalizeText('campaign.calendar.info.future');
|
||||
case CalendarItemState.STATE_LOCKED_EXPIRED:
|
||||
return LocalizeText('campaign.calendar.info.expired');
|
||||
default: return LocalizeText('campaign.calendar.info.available.desktop');
|
||||
}
|
||||
}, [getDayState]);
|
||||
|
||||
const onClickNext = useCallback(() =>
|
||||
{
|
||||
const nextDay = selectedDay + 1;
|
||||
|
||||
if((nextDay) === numDays) return;
|
||||
|
||||
setSelectedDay(nextDay);
|
||||
|
||||
if((index + getNumItemsDisplayed()) < nextDay + 1)
|
||||
{
|
||||
setIndex(index + 1);
|
||||
}
|
||||
|
||||
}, [index, numDays, selectedDay]);
|
||||
|
||||
const onClickPrev = useCallback(() =>
|
||||
{
|
||||
const prevDay = selectedDay - 1;
|
||||
|
||||
if((prevDay < 0)) return;
|
||||
|
||||
setSelectedDay(prevDay);
|
||||
|
||||
if(index > prevDay)
|
||||
{
|
||||
setIndex(index - 1);
|
||||
}
|
||||
}, [index, selectedDay]);
|
||||
|
||||
const onClickItem = useCallback((item: number) =>
|
||||
{
|
||||
if(selectedDay === item)
|
||||
{
|
||||
//handle opening
|
||||
const state = getDayState(item);
|
||||
if(state === CalendarItemState.STATE_LOCKED_AVAILABLE) openPackage(item, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
setSelectedDay(item);
|
||||
}
|
||||
}, [getDayState, openPackage, selectedDay]);
|
||||
|
||||
const forceOpen = useCallback(() =>
|
||||
{
|
||||
const id = selectedDay;
|
||||
const state = getDayState(id);
|
||||
if(GetSessionDataManager().isModerator && state !== CalendarItemState.STATE_UNLOCKED)
|
||||
{
|
||||
openPackage(id, true);
|
||||
}
|
||||
}, [getDayState, openPackage, selectedDay]);
|
||||
|
||||
return (
|
||||
<NitroCardView className="nitro-campaign-calendar">
|
||||
<NitroCardHeaderView headerText={LocalizeText(`campaign.calendar.${campaignName}.title`)} onCloseClick={close} />
|
||||
<NitroCardContentView>
|
||||
<div className="d-flex justify-content-between mx-5">
|
||||
<div className="text-black">
|
||||
<h3>{LocalizeText('campaign.calendar.heading.day', ['number'], [(selectedDay + 1).toString()])}</h3>
|
||||
<p>{dayMessage(selectedDay)}</p>
|
||||
</div>
|
||||
{GetSessionDataManager().isModerator &&
|
||||
<button className="btn btn-sm btn-danger my-4" onClick={forceOpen}>Force open</button>
|
||||
}
|
||||
</div>
|
||||
<div className="button-container">
|
||||
|
||||
|
||||
</div>
|
||||
<NitroLayoutFlex className="h-100 align-items-center" gap={1}>
|
||||
<div className="calendar-prev cursor-pointer" onClick={onClickPrev} />
|
||||
{
|
||||
[...Array(getNumItemsDisplayed())].map((e, i) =>
|
||||
{
|
||||
const day = index + i;
|
||||
return <CalendarItemView key={i} state={getDayState(day)} active={selectedDay === day} onClick={onClickItem} id={day} productName={receivedProducts.has(day) ? receivedProducts.get(day) : null} />
|
||||
})
|
||||
}
|
||||
<div className="calendar-next cursor-pointer" onClick={onClickNext} />
|
||||
</NitroLayoutFlex>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
)
|
||||
}
|
11
src/views/campaign/views/calendar/CalendarView.types.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export interface CalendarViewProps
|
||||
{
|
||||
close(): void;
|
||||
openPackage(id: number, asStaff: boolean): void;
|
||||
receivedProducts: Map<number, string>;
|
||||
campaignName: string;
|
||||
currentDay: number;
|
||||
numDays: number;
|
||||
openedDays: number[];
|
||||
missedDays: number[];
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import { GetCatalogIndexComposer, GetCatalogPageComposer, GetGiftWrappingConfigurationComposer, ILinkEventTracker, INodeData, RoomPreviewer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useReducer, useState } from 'react';
|
||||
import { AddEventLinkTracker, GetRoomEngine, LocalizeText, RemoveLinkEventTracker } from '../../api';
|
||||
import { CREDITS, PlaySound } from '../../api/utils/PlaySound';
|
||||
import { CatalogEvent } from '../../events';
|
||||
import { useUiEvent } from '../../hooks/events/ui/ui-event';
|
||||
import { SendMessageHook } from '../../hooks/messages/message-event';
|
||||
@ -47,6 +48,9 @@ export const CatalogView: FC<CatalogViewProps> = props =>
|
||||
save = true;
|
||||
setIsVisible(value => !value);
|
||||
return;
|
||||
case CatalogEvent.PURCHASE_SUCCESS:
|
||||
PlaySound(CREDITS);
|
||||
return;
|
||||
}
|
||||
|
||||
if(save) saveActivePages();
|
||||
@ -56,6 +60,7 @@ export const CatalogView: FC<CatalogViewProps> = props =>
|
||||
useUiEvent(CatalogEvent.HIDE_CATALOG, onCatalogEvent);
|
||||
useUiEvent(CatalogEvent.TOGGLE_CATALOG, onCatalogEvent);
|
||||
useUiEvent(CatalogEvent.CATALOG_RESET, onCatalogEvent);
|
||||
useUiEvent(CatalogEvent.PURCHASE_SUCCESS, onCatalogEvent);
|
||||
|
||||
const linkReceived = useCallback((url: string) =>
|
||||
{
|
||||
|
@ -2,6 +2,15 @@
|
||||
width: $chat-history-width;
|
||||
height: $chat-history-height;
|
||||
|
||||
background-color: #1C323F;
|
||||
border: 2px solid rgba(255, 255, 255, 0.5);
|
||||
border-radius: 0.25rem;
|
||||
|
||||
.nitro-card-header-container {
|
||||
background-color: #3d5f6e;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.chat-history-content {
|
||||
.chat-history-container {
|
||||
min-height: 200px;
|
||||
|
@ -6,12 +6,14 @@
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
min-height: 300px;
|
||||
overflow-x: scroll;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.color {
|
||||
height: 50px;
|
||||
width: 10px;
|
||||
.arrow-button {
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
display:none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ export class FloorplanEditor extends PixiApplicationProxy
|
||||
this._width = 0;
|
||||
this._height = 0;
|
||||
this._isHolding = false;
|
||||
this._lastUsedTile = new NitroPoint(-1,-1);
|
||||
this._lastUsedTile = new NitroPoint(-1, -1);
|
||||
this._actionSettings = new ActionSettings();
|
||||
}
|
||||
|
||||
@ -52,7 +52,7 @@ export class FloorplanEditor extends PixiApplicationProxy
|
||||
if(!this._isInitialized)
|
||||
{
|
||||
this.loader.add('tiles', GetConfiguration<string>('floorplan.tile.url'));
|
||||
|
||||
|
||||
this.loader.load((_, resources) =>
|
||||
{
|
||||
this._tilemapRenderer = new NitroTilemap(resources['tiles'].spritesheet.baseTexture);
|
||||
@ -87,10 +87,10 @@ export class FloorplanEditor extends PixiApplicationProxy
|
||||
|
||||
this._tilemapRenderer.on('pointerdown', (event: PixiInteractionEventProxy) =>
|
||||
{
|
||||
if(!(event.data.originalEvent instanceof PointerEvent)) return;
|
||||
if(!(event.data.originalEvent instanceof PointerEvent) && !(event.data.originalEvent instanceof TouchEvent)) return;
|
||||
|
||||
const pointerEvent = event.data.originalEvent;
|
||||
if(pointerEvent.button === 2) return;
|
||||
if((pointerEvent instanceof MouseEvent) && pointerEvent.button === 2) return;
|
||||
|
||||
|
||||
const location = event.data.global;
|
||||
@ -127,9 +127,8 @@ export class FloorplanEditor extends PixiApplicationProxy
|
||||
const bufIndex = j + bufSize;
|
||||
const data = buffer.slice(j, bufIndex);
|
||||
|
||||
const width = data[4];
|
||||
const height = data[5];
|
||||
|
||||
const width = TILE_SIZE;
|
||||
const height = TILE_SIZE / 2;
|
||||
|
||||
const mousePositionX = Math.floor(tempPoint.x);
|
||||
const mousePositionY = Math.floor(tempPoint.y);
|
||||
@ -137,12 +136,11 @@ export class FloorplanEditor extends PixiApplicationProxy
|
||||
const tileStartX = data[2];
|
||||
const tileStartY = data[3];
|
||||
|
||||
|
||||
const centreX = tileStartX + (width / 2);
|
||||
const centreY = tileStartY + (height / 2);
|
||||
|
||||
const dx = Math.abs(mousePositionX - centreX - 2);
|
||||
const dy = Math.abs(mousePositionY - centreY - 2);
|
||||
const dx = Math.abs(mousePositionX - centreX);
|
||||
const dy = Math.abs(mousePositionY - centreY);
|
||||
|
||||
const solution = (dx / (width * 0.5) + dy / (height * 0.5) <= 1);//todo: improve this
|
||||
if(solution)
|
||||
@ -171,7 +169,6 @@ export class FloorplanEditor extends PixiApplicationProxy
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private onClick(x: number, y: number): void
|
||||
{
|
||||
const tile = this._tilemap[y][x];
|
||||
@ -212,7 +209,7 @@ export class FloorplanEditor extends PixiApplicationProxy
|
||||
{
|
||||
if((x + 1) > this._width) this._width = x + 1;
|
||||
|
||||
if( (y + 1) > this._height) this._height = y + 1;
|
||||
if((y + 1) > this._height) this._height = y + 1;
|
||||
}
|
||||
|
||||
const newHeight = HEIGHT_SCHEME[futureHeightIndex];
|
||||
@ -229,7 +226,7 @@ export class FloorplanEditor extends PixiApplicationProxy
|
||||
public renderTiles(): void
|
||||
{
|
||||
this.tilemapRenderer.clear();
|
||||
|
||||
|
||||
for(let y = 0; y < this._tilemap.length; y++)
|
||||
{
|
||||
for(let x = 0; x < this.tilemap[y].length; x++)
|
||||
@ -237,13 +234,13 @@ export class FloorplanEditor extends PixiApplicationProxy
|
||||
const tile = this.tilemap[y][x];
|
||||
let assetName = tile.height;
|
||||
|
||||
if(this._doorLocation.x === x && this._doorLocation.y === y)
|
||||
if(this._doorLocation.x === x && this._doorLocation.y === y)
|
||||
assetName = FloorplanEditor.TILE_DOOR;
|
||||
|
||||
if(tile.isBlocked) assetName = FloorplanEditor.TILE_BLOCKED;
|
||||
|
||||
|
||||
//if((tile.height === 'x') || tile.height === 'X') continue;
|
||||
const [positionX, positionY ] = getScreenPositionForTile(x, y);
|
||||
const [positionX, positionY] = getScreenPositionForTile(x, y);
|
||||
this._tilemapRenderer.tile(`${assetName}.png`, positionX, positionY);
|
||||
}
|
||||
}
|
||||
@ -337,10 +334,10 @@ export class FloorplanEditor extends PixiApplicationProxy
|
||||
|
||||
if(tile.height !== 'x')
|
||||
{
|
||||
if( (x + 1) > this._width)
|
||||
if((x + 1) > this._width)
|
||||
this._width = x + 1;
|
||||
|
||||
if( (y + 1) > this._height)
|
||||
if((y + 1) > this._height)
|
||||
this._height = y + 1;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { GetOccupiedTilesMessageComposer, GetRoomEntryTileMessageComposer, NitroPoint, RoomEntryTileMessageEvent, RoomOccupiedTilesMessageEvent } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { CreateMessageHook, SendMessageHook, UseMountEffect } from '../../../hooks';
|
||||
import { NitroLayoutFlex } from '../../../layout';
|
||||
import { FloorplanEditor } from '../common/FloorplanEditor';
|
||||
import { useFloorplanEditorContext } from '../context/FloorplanEditorContext';
|
||||
|
||||
@ -68,6 +69,29 @@ export const FloorplanCanvasView: FC<{}> = props =>
|
||||
|
||||
CreateMessageHook(RoomEntryTileMessageEvent, onRoomEntryTileMessageEvent);
|
||||
|
||||
const onClickArrowButton = useCallback((scrollDirection: string) =>
|
||||
{
|
||||
const element = elementRef.current;
|
||||
|
||||
if(!element) return;
|
||||
|
||||
switch(scrollDirection)
|
||||
{
|
||||
case 'up':
|
||||
element.scrollBy({ top: -10 });
|
||||
break;
|
||||
case 'down':
|
||||
element.scrollBy({ top: 10 });
|
||||
break;
|
||||
case 'left':
|
||||
element.scrollBy({ left: -10 });
|
||||
break;
|
||||
case 'right':
|
||||
element.scrollBy({ left: 10 });
|
||||
break;
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(entryTileReceived && occupiedTilesReceived)
|
||||
@ -75,6 +99,18 @@ export const FloorplanCanvasView: FC<{}> = props =>
|
||||
}, [entryTileReceived, occupiedTilesReceived])
|
||||
|
||||
return (
|
||||
<div ref={elementRef} className="editor-area" />
|
||||
<>
|
||||
<NitroLayoutFlex className="align-items-center justify-content-center">
|
||||
<div className="arrow-button"><button className="btn btn-primary" onClick={() => onClickArrowButton('up')}><i className="fas fa-arrow-up"/></button></div>
|
||||
</NitroLayoutFlex>
|
||||
<NitroLayoutFlex className="align-items-center justify-content-center">
|
||||
<div className="arrow-button"><button className="btn btn-primary" onClick={() => onClickArrowButton('left')}><i className="fas fa-arrow-left"/></button></div>
|
||||
<div ref={elementRef} className="editor-area" />
|
||||
<div className="arrow-button"><button className="btn btn-primary" onClick={() => onClickArrowButton('right')}><i className="fas fa-arrow-right"/></button></div>
|
||||
</NitroLayoutFlex>
|
||||
<NitroLayoutFlex className="align-items-center justify-content-center">
|
||||
<div className="arrow-button"><button className="btn btn-primary" onClick={() => onClickArrowButton('down')}><i className="fas fa-arrow-down"/></button></div>
|
||||
</NitroLayoutFlex>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { FollowFriendMessageComposer, ILinkEventTracker, NewConsoleMessageEvent, SendMessageComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { AddEventLinkTracker, GetUserProfile, LocalizeText, RemoveLinkEventTracker } from '../../../../api';
|
||||
import { MESSENGER_MESSAGE_RECEIVED, MESSENGER_NEW_THREAD, PlaySound } from '../../../../api/utils/PlaySound';
|
||||
import { FriendsMessengerIconEvent } from '../../../../events';
|
||||
import { BatchUpdates, CreateMessageHook, dispatchUiEvent, SendMessageHook } from '../../../../hooks';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroLayoutButton, NitroLayoutButtonGroup, NitroLayoutFlex, NitroLayoutFlexColumn } from '../../../../layout';
|
||||
@ -114,6 +115,8 @@ export const FriendsMessengerView: FC<{}> = props =>
|
||||
|
||||
SendMessageHook(new SendMessageComposer(thread.participant.id, messageText));
|
||||
|
||||
if(messageThreads.length === 1 && thread.groups.length === 1) PlaySound(MESSENGER_NEW_THREAD);
|
||||
|
||||
thread.addMessage(0, messageText, 0, null, MessengerThreadChat.CHAT);
|
||||
|
||||
BatchUpdates(() =>
|
||||
@ -227,6 +230,8 @@ export const FriendsMessengerView: FC<{}> = props =>
|
||||
}
|
||||
}
|
||||
|
||||
if(isUnread) PlaySound(MESSENGER_MESSAGE_RECEIVED);
|
||||
|
||||
dispatchUiEvent(new FriendsMessengerIconEvent(FriendsMessengerIconEvent.UPDATE_ICON, isUnread ? FriendsMessengerIconEvent.UNREAD_ICON : FriendsMessengerIconEvent.SHOW_ICON));
|
||||
}, [ visibleThreads, updateValue ]);
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { CallForHelpResultMessageEvent } from '@nitrots/nitro-renderer';
|
||||
import { CallForHelpResultMessageEvent, GetPendingCallsForHelpMessageComposer, IssueCloseNotificationMessageEvent } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback } from 'react';
|
||||
import { LocalizeText } from '../../api';
|
||||
import { CreateMessageHook } from '../../hooks/messages/message-event';
|
||||
import { CreateMessageHook, SendMessageHook } from '../../hooks/messages/message-event';
|
||||
import { NotificationAlertType } from '../notification-center/common/NotificationAlertType';
|
||||
import { NotificationUtilities } from '../notification-center/common/NotificationUtilities';
|
||||
import { CallForHelpResult } from './common/CallForHelpResult';
|
||||
import { GetCloseReasonKey } from './common/GetCloseReasonKey';
|
||||
|
||||
export const HelpMessageHandler: FC<{}> = props =>
|
||||
@ -14,12 +15,36 @@ export const HelpMessageHandler: FC<{}> = props =>
|
||||
|
||||
let message = parser.messageText;
|
||||
|
||||
if(!message || !message.length) message = LocalizeText('help.cfh.closed.' + GetCloseReasonKey(parser.resultType))
|
||||
|
||||
NotificationUtilities.simpleAlert(message, NotificationAlertType.MODERATION, null, null, LocalizeText('mod.alert.title'));
|
||||
switch(parser.resultType)
|
||||
{
|
||||
case CallForHelpResult.TOO_MANY_PENDING_CALLS_CODE:
|
||||
SendMessageHook(new GetPendingCallsForHelpMessageComposer());
|
||||
NotificationUtilities.simpleAlert(LocalizeText('help.cfh.error.pending'), NotificationAlertType.MODERATION, null, null, LocalizeText('help.cfh.error.title'));
|
||||
break;
|
||||
case CallForHelpResult.HAS_ABUSIVE_CALL_CODE:
|
||||
NotificationUtilities.simpleAlert(LocalizeText('help.cfh.error.abusive'), NotificationAlertType.MODERATION, null, null, LocalizeText('help.cfh.error.title'));
|
||||
break;
|
||||
default:
|
||||
if(message.trim().length === 0)
|
||||
{
|
||||
message = LocalizeText('help.cfh.sent.text');
|
||||
}
|
||||
NotificationUtilities.simpleAlert(message, NotificationAlertType.MODERATION, null, null, LocalizeText('help.cfh.sent.title'));
|
||||
}
|
||||
}, []);
|
||||
|
||||
CreateMessageHook(CallForHelpResultMessageEvent, onCallForHelpResultMessageEvent);
|
||||
|
||||
const onIssueCloseNotificationMessageEvent = useCallback((event: IssueCloseNotificationMessageEvent) =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
const message = LocalizeText('help.cfh.closed.' + GetCloseReasonKey(parser.closeReason))
|
||||
|
||||
NotificationUtilities.simpleAlert(message, NotificationAlertType.MODERATION, null, null, LocalizeText('mod.alert.title'));
|
||||
}, []);
|
||||
|
||||
CreateMessageHook(IssueCloseNotificationMessageEvent, onIssueCloseNotificationMessageEvent);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
5
src/views/help/common/CallForHelpResult.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export class CallForHelpResult
|
||||
{
|
||||
public static readonly TOO_MANY_PENDING_CALLS_CODE = 1;
|
||||
public static readonly HAS_ABUSIVE_CALL_CODE = 2;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { FC } from 'react';
|
||||
import { LocalizeText } from '../../../../../api';
|
||||
import { LocalizeFormattedNumber, LocalizeText } from '../../../../../api';
|
||||
import { UserProfileIconView } from '../../../../../layout';
|
||||
import { AvatarImageView } from '../../../../shared/avatar-image/AvatarImageView';
|
||||
import { HallOfFameItemViewProps } from './HallOfFameItemView.types';
|
||||
@ -9,12 +9,12 @@ export const HallOfFameItemView: FC<HallOfFameItemViewProps> = props =>
|
||||
const { data = null, level = 0 } = props;
|
||||
|
||||
return (
|
||||
<div className="hof-user-container cursor-pointer">
|
||||
<div className="hof-user-container">
|
||||
<div className="hof-tooltip">
|
||||
<div className="hof-tooltip-content">
|
||||
<div className="fw-bold">{ level }. { data.userName } <UserProfileIconView userId={ data.userId } /></div>
|
||||
<div className="muted fst-italic small text-center">{ LocalizeText('landing.view.competition.hof.points', [ 'points' ], [ data.currentScore.toString() ])}</div>
|
||||
<div className="hof-header">
|
||||
{ level }. { data.userName } <UserProfileIconView userId={ data.userId } />
|
||||
</div>
|
||||
<div className="small text-center text-white">{ LocalizeText('landing.view.competition.hof.points', [ 'points' ], [ LocalizeFormattedNumber(data.currentScore).toString() ])}</div>
|
||||
</div>
|
||||
<AvatarImageView figure={ data.figure } direction={ 2 } />
|
||||
</div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
.hall-of-fame {
|
||||
background-color: rgba($black,.3);
|
||||
border-radius: $border-radius;
|
||||
display: inline-block;
|
||||
justify-content: center;
|
||||
|
||||
.hof-user-container {
|
||||
display:inline-flex;
|
||||
@ -16,22 +16,31 @@
|
||||
|
||||
.hof-tooltip {
|
||||
position: absolute;
|
||||
display: inline;
|
||||
z-index: 2;
|
||||
display: none;
|
||||
background-color: #1c323f;
|
||||
width: 125px;
|
||||
max-width: 125px;
|
||||
padding: 2px;
|
||||
background-color: $gable-green;
|
||||
border: 2px solid rgba($white, 0.5);
|
||||
border-radius: $border-radius;
|
||||
bottom:calc(100% - 20px);
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
padding:2px;
|
||||
color: #fff;
|
||||
font-size: $font-size-sm;
|
||||
z-index: $context-menu-zindex;
|
||||
pointer-events: all;
|
||||
left: -15px;
|
||||
bottom: calc(100% - 10px);
|
||||
|
||||
.hof-tooltip-content {
|
||||
padding:3px;
|
||||
background-color: #3d5f6e;
|
||||
.hof-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
background-color: $william;
|
||||
color: $white;
|
||||
min-width: 117px;
|
||||
height: 25px;
|
||||
max-height: 25px;
|
||||
font-size: 16px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
&:after {
|
||||
|
@ -26,7 +26,7 @@ export const HallOfFameWidgetView: FC<HallOfFameWidgetViewProps> = props =>
|
||||
if(!data) return null;
|
||||
|
||||
return (
|
||||
<div className="hall-of-fame widget">
|
||||
<div className="hall-of-fame d-flex">
|
||||
{ data.hof && (data.hof.length > 0) && data.hof.map((entry, index) =>
|
||||
{
|
||||
return <HallOfFameItemView key={ index } data={ entry } level={ (index + 1) } />;
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { RoomSessionEvent } from '@nitrots/nitro-renderer';
|
||||
import { HabboWebTools, RoomSessionEvent } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { GetCommunication } from '../../api';
|
||||
import { AddEventLinkTracker, GetCommunication, RemoveLinkEventTracker } from '../../api';
|
||||
import { useRoomSessionManagerEvent } from '../../hooks/events/nitro/session/room-session-manager-event';
|
||||
import { TransitionAnimation, TransitionAnimationTypes } from '../../layout';
|
||||
import { AchievementsView } from '../achievements/AchievementsView';
|
||||
import { AvatarEditorView } from '../avatar-editor/AvatarEditorView';
|
||||
import { CameraWidgetView } from '../camera/CameraWidgetView';
|
||||
import { CampaignView } from '../campaign/CampaignView';
|
||||
import { CatalogView } from '../catalog/CatalogView';
|
||||
import { ChatHistoryView } from '../chat-history/ChatHistoryView';
|
||||
import { FloorplanEditorView } from '../floorplan-editor/FloorplanEditorView';
|
||||
@ -47,6 +48,32 @@ export const MainView: FC<MainViewProps> = props =>
|
||||
useRoomSessionManagerEvent(RoomSessionEvent.CREATED, onRoomSessionEvent);
|
||||
useRoomSessionManagerEvent(RoomSessionEvent.ENDED, onRoomSessionEvent);
|
||||
|
||||
const onLinkReceived = useCallback((link: string) =>
|
||||
{
|
||||
const parts = link.split('/');
|
||||
|
||||
if(parts.length < 2) return;
|
||||
|
||||
switch(parts[1])
|
||||
{
|
||||
case 'open':
|
||||
if(parts.length > 2)
|
||||
{
|
||||
switch(parts[2])
|
||||
{
|
||||
case 'credits':
|
||||
//HabboWebTools.openWebPageAndMinimizeClient(this._windowManager.getProperty(ExternalVariables.WEB_SHOP_RELATIVE_URL));
|
||||
break;
|
||||
default: {
|
||||
const name = parts[2];
|
||||
HabboWebTools.openHabblet(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setIsReady(true);
|
||||
@ -54,6 +81,17 @@ export const MainView: FC<MainViewProps> = props =>
|
||||
GetCommunication().connection.onReady();
|
||||
}, []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
const linkTracker = { linkReceived: onLinkReceived, eventUrlPrefix: 'habblet/' };
|
||||
AddEventLinkTracker(linkTracker);
|
||||
|
||||
return () =>
|
||||
{
|
||||
RemoveLinkEventTracker(linkTracker);
|
||||
}
|
||||
}, [onLinkReceived]);
|
||||
|
||||
return (
|
||||
<div className="nitro-main">
|
||||
<TransitionAnimation type={ TransitionAnimationTypes.FADE_IN } inProp={ landingViewVisible } timeout={ 300 }>
|
||||
@ -79,6 +117,7 @@ export const MainView: FC<MainViewProps> = props =>
|
||||
<FloorplanEditorView />
|
||||
<NitropediaView />
|
||||
<HcCenterView />
|
||||
<CampaignView />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { CfhSanctionMessageEvent, CfhTopicsInitEvent, IssueDeletedMessageEvent, IssueInfoMessageEvent, IssuePickFailedMessageEvent, ModeratorActionResultMessageEvent, ModeratorInitMessageEvent, ModeratorToolPreferencesEvent, RoomEngineEvent } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback } from 'react';
|
||||
import { MODTOOLS_NEW_TICKET, PlaySound } from '../../api/utils/PlaySound';
|
||||
import { NotificationAlertEvent } from '../../events';
|
||||
import { ModToolsEvent } from '../../events/mod-tools/ModToolsEvent';
|
||||
import { ModToolsOpenRoomChatlogEvent } from '../../events/mod-tools/ModToolsOpenRoomChatlogEvent';
|
||||
@ -49,7 +50,7 @@ export const ModToolsMessageHandler: FC<{}> = props =>
|
||||
|
||||
const newTickets = tickets ? Array.from(tickets) : [];
|
||||
const existingIndex = newTickets.findIndex( entry => entry.issueId === parser.issueData.issueId)
|
||||
|
||||
|
||||
if(existingIndex > -1)
|
||||
{
|
||||
newTickets[existingIndex] = parser.issueData;
|
||||
@ -57,6 +58,7 @@ export const ModToolsMessageHandler: FC<{}> = props =>
|
||||
else
|
||||
{
|
||||
newTickets.push(parser.issueData);
|
||||
PlaySound(MODTOOLS_NEW_TICKET);
|
||||
}
|
||||
|
||||
dispatchModToolsState({
|
||||
@ -66,8 +68,6 @@ export const ModToolsMessageHandler: FC<{}> = props =>
|
||||
}
|
||||
});
|
||||
|
||||
//todo: play ticket sound
|
||||
//GetNitroInstance().events.dispatchEvent(new NitroSoundEvent(NitroSoundEvent.PLAY_SOUND, sound)
|
||||
console.log(parser);
|
||||
}, [dispatchModToolsState, tickets]);
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { ILinkEventTracker, NavigatorInitComposer, NavigatorSearchComposer, RoomDataParser, RoomSessionEvent } from '@nitrots/nitro-renderer';
|
||||
import { ConvertGlobalRoomIdMessageComposer, HabboWebTools, ILinkEventTracker, LegacyExternalInterface, NavigatorInitComposer, NavigatorSearchComposer, RoomDataParser, RoomSessionEvent } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useMemo, useReducer, useState } from 'react';
|
||||
import { AddEventLinkTracker, GoToDesktop, LocalizeText, RemoveLinkEventTracker, TryVisitRoom } from '../../api';
|
||||
import { NavigatorEvent, UpdateDoorStateEvent } from '../../events';
|
||||
import { UseMountEffect } from '../../hooks';
|
||||
import { useRoomSessionManagerEvent } from '../../hooks/events/nitro/session/room-session-manager-event';
|
||||
import { useUiEvent } from '../../hooks/events/ui/ui-event';
|
||||
import { SendMessageHook } from '../../hooks/messages/message-event';
|
||||
@ -181,6 +182,16 @@ export const NavigatorView: FC<NavigatorViewProps> = props =>
|
||||
return () => RemoveLinkEventTracker(linkTracker);
|
||||
}, [ linkReceived]);
|
||||
|
||||
const enterRoomWebRequest = useCallback((k: string, _arg_2:boolean=false, _arg_3:string=null) =>
|
||||
{
|
||||
SendMessageHook(new ConvertGlobalRoomIdMessageComposer(k));
|
||||
}, []);
|
||||
|
||||
UseMountEffect(() =>
|
||||
{
|
||||
LegacyExternalInterface.addCallback(HabboWebTools.OPENROOM, enterRoomWebRequest);
|
||||
});
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!isVisible || !needsNavigatorUpdate) return;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { ActivityPointNotificationMessageEvent, UserCreditsEvent, UserCurrencyEvent, UserSubscriptionEvent, UserSubscriptionParser } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback } from 'react';
|
||||
import { CREDITS, DUCKETS, PlaySound } from '../../api/utils/PlaySound';
|
||||
import { CreateMessageHook } from '../../hooks/messages/message-event';
|
||||
import { usePurseContext } from './context/PurseContext';
|
||||
import { PurseMessageHandlerProps } from './PurseMessageHandler.types';
|
||||
@ -12,6 +13,8 @@ export const PurseMessageHandler: FC<PurseMessageHandlerProps> = props =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
if(purse.credits && purse.credits !== parseFloat(parser.credits)) PlaySound(CREDITS);
|
||||
|
||||
purse.credits = parseFloat(parser.credits);
|
||||
|
||||
purse.notify();
|
||||
@ -32,6 +35,8 @@ export const PurseMessageHandler: FC<PurseMessageHandlerProps> = props =>
|
||||
|
||||
purse.activityPoints.set(parser.type, parser.amount);
|
||||
|
||||
if(parser.type === 0) PlaySound(DUCKETS);
|
||||
|
||||
purse.notify();
|
||||
}, [ purse ]);
|
||||
|
||||
|
@ -106,9 +106,8 @@ export const WordQuizWidgetView: FC<{}> = props =>
|
||||
{
|
||||
setUserAnswers(prev =>
|
||||
{
|
||||
const copy = new Map(prev);
|
||||
const keysToRemove: number[] = [];
|
||||
copy.forEach((value, key) =>
|
||||
prev.forEach((value, key) =>
|
||||
{
|
||||
value.secondsLeft--;
|
||||
|
||||
@ -118,8 +117,10 @@ export const WordQuizWidgetView: FC<{}> = props =>
|
||||
}
|
||||
});
|
||||
|
||||
keysToRemove.forEach(key => copy.delete(key));
|
||||
if(keysToRemove.length === 0) return prev;
|
||||
|
||||
const copy = new Map(prev);
|
||||
keysToRemove.forEach(key => copy.delete(key));
|
||||
return copy;
|
||||
})
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
}
|
||||
|
||||
.navigation-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
@ -28,7 +28,7 @@ export const GroupsContainerView: FC<GroupsContainerViewProps> = props =>
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(groups.length > 0 && !selectedGroupId) setSelectedGroupId(groups[0].id);
|
||||
if(groups.length > 0 && !selectedGroupId) setSelectedGroupId(groups[0].groupId);
|
||||
}, [ groups, selectedGroupId ]);
|
||||
|
||||
useEffect(() =>
|
||||
@ -49,9 +49,9 @@ export const GroupsContainerView: FC<GroupsContainerViewProps> = props =>
|
||||
<div className="h-100 overflow-auto d-flex flex-column gap-1">
|
||||
{ groups.map((group, index) =>
|
||||
{
|
||||
return <div key={ index } onClick={ () => setSelectedGroupId(group.id) } className={ 'profile-groups-item position-relative flex-shrink-0 d-flex align-items-center justify-content-center cursor-pointer' + classNames({ ' active': selectedGroupId === group.id }) }>
|
||||
{ itsMe && <i className={ 'position-absolute icon icon-group-' + (group.ownerOrFavorite ? 'favorite' : 'not-favorite') } onClick={ () => favoriteGroup(group.id) } /> }
|
||||
<BadgeImageView badgeCode={ group.badge } isGroup={ true } />
|
||||
return <div key={ index } onClick={ () => setSelectedGroupId(group.groupId) } className={ 'profile-groups-item position-relative flex-shrink-0 d-flex align-items-center justify-content-center cursor-pointer' + classNames({ ' active': selectedGroupId === group.groupId }) }>
|
||||
{ itsMe && <i className={ 'position-absolute icon icon-group-' + (group.favourite ? 'favorite' : 'not-favorite') } onClick={ () => favoriteGroup(group.groupId) } /> }
|
||||
<BadgeImageView badgeCode={ group.badgeCode } isGroup={ true } />
|
||||
</div>
|
||||
}) }
|
||||
</div>
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { GroupDataParser } from '@nitrots/nitro-renderer';
|
||||
import { HabboGroupEntryData } from '@nitrots/nitro-renderer';
|
||||
|
||||
export interface GroupsContainerViewProps
|
||||
{
|
||||
itsMe: boolean;
|
||||
groups: GroupDataParser[];
|
||||
groups: HabboGroupEntryData[];
|
||||
onLeaveGroup: () => void;
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ export const RelationshipsContainerView: FC<RelationshipsContainerViewProps> = p
|
||||
|
||||
return (
|
||||
<div className="relationship-container d-flex flex-row align-items-center w-100">
|
||||
<i className={`icon icon-relationship-${relationshipName} flex-shrink-0 align-self-baseline mt-2`} />
|
||||
<i className={`nitro-friends-spritesheet icon-${relationshipName} flex-shrink-0 align-self-baseline mt-2`} />
|
||||
<div className="w-100 d-flex flex-column">
|
||||
<span className={'relationship mx-2' + (!simple ? ' advanced' : '')}>
|
||||
<span className="cursor-pointer relationship-text" onClick={ event => OnUserClick(relationshipInfo)}>
|
||||
|