Merge branch 'dev' into feature/habbopages
110
src/api/nitro/room/widgets/events/RoomWidgetPollUpdateEvent.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import { PollQuestion } from '@nitrots/nitro-renderer';
|
||||
import { RoomWidgetUpdateEvent } from './RoomWidgetUpdateEvent';
|
||||
|
||||
export class RoomWidgetPollUpdateEvent extends RoomWidgetUpdateEvent
|
||||
{
|
||||
public static readonly OFFER = 'RWPUW_OFFER';
|
||||
public static readonly ERROR = 'RWPUW_ERROR';
|
||||
public static readonly CONTENT = 'RWPUW_CONTENT';
|
||||
|
||||
private _id = -1;
|
||||
private _summary: string;
|
||||
private _headline: string;
|
||||
private _numQuestions = 0;
|
||||
private _startMessage = '';
|
||||
private _endMessage = '';
|
||||
private _questionArray: PollQuestion[] = null;
|
||||
private _pollType = '';
|
||||
private _npsPoll = false;
|
||||
|
||||
constructor(type: string, id: number)
|
||||
{
|
||||
super(type);
|
||||
this._id = id;
|
||||
}
|
||||
|
||||
public get id(): number
|
||||
{
|
||||
return this._id;
|
||||
}
|
||||
|
||||
public get summary(): string
|
||||
{
|
||||
return this._summary;
|
||||
}
|
||||
|
||||
public set summary(k: string)
|
||||
{
|
||||
this._summary = k;
|
||||
}
|
||||
|
||||
public get headline(): string
|
||||
{
|
||||
return this._headline;
|
||||
}
|
||||
|
||||
public set headline(k: string)
|
||||
{
|
||||
this._headline = k;
|
||||
}
|
||||
|
||||
public get numQuestions(): number
|
||||
{
|
||||
return this._numQuestions;
|
||||
}
|
||||
|
||||
public set numQuestions(k: number)
|
||||
{
|
||||
this._numQuestions = k;
|
||||
}
|
||||
|
||||
public get startMessage(): string
|
||||
{
|
||||
return this._startMessage;
|
||||
}
|
||||
|
||||
public set startMessage(k: string)
|
||||
{
|
||||
this._startMessage = k;
|
||||
}
|
||||
|
||||
public get endMessage(): string
|
||||
{
|
||||
return this._endMessage;
|
||||
}
|
||||
|
||||
public set endMessage(k: string)
|
||||
{
|
||||
this._endMessage = k;
|
||||
}
|
||||
|
||||
public get questionArray(): PollQuestion[]
|
||||
{
|
||||
return this._questionArray;
|
||||
}
|
||||
|
||||
public set questionArray(k: PollQuestion[])
|
||||
{
|
||||
this._questionArray = k;
|
||||
}
|
||||
|
||||
public get pollType(): string
|
||||
{
|
||||
return this._pollType;
|
||||
}
|
||||
|
||||
public set pollType(k: string)
|
||||
{
|
||||
this._pollType = k;
|
||||
}
|
||||
|
||||
public get npsPoll(): boolean
|
||||
{
|
||||
return this._npsPoll;
|
||||
}
|
||||
|
||||
public set npsPoll(k: boolean)
|
||||
{
|
||||
this._npsPoll = k;
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
import { IQuestion } from '@nitrots/nitro-renderer';
|
||||
import { RoomWidgetUpdateEvent } from './RoomWidgetUpdateEvent';
|
||||
|
||||
export class RoomWidgetWordQuizUpdateEvent extends RoomWidgetUpdateEvent
|
||||
{
|
||||
public static readonly NEW_QUESTION = 'RWPUW_NEW_QUESTION';
|
||||
public static readonly QUESTION_FINISHED = 'RWPUW_QUESION_FINSIHED';
|
||||
public static readonly QUESTION_ANSWERED = 'RWPUW_QUESTION_ANSWERED';
|
||||
|
||||
private _id: number = -1;
|
||||
private _pollType: string = null;
|
||||
private _pollId: number = -1;
|
||||
private _questionId: number = -1;
|
||||
private _duration: number = -1;
|
||||
private _question: IQuestion = null;
|
||||
private _userId: number = -1;
|
||||
private _value: string;
|
||||
private _answerCounts: Map<string, number>;
|
||||
|
||||
constructor(type: string, id: number)
|
||||
{
|
||||
super(type);
|
||||
this._id = id;
|
||||
}
|
||||
|
||||
public get id(): number
|
||||
{
|
||||
return this._id;
|
||||
}
|
||||
|
||||
public get pollType(): string
|
||||
{
|
||||
return this._pollType;
|
||||
}
|
||||
|
||||
public set pollType(k: string)
|
||||
{
|
||||
this._pollType = k;
|
||||
}
|
||||
|
||||
public get pollId(): number
|
||||
{
|
||||
return this._pollId;
|
||||
}
|
||||
|
||||
public set pollId(k: number)
|
||||
{
|
||||
this._pollId = k;
|
||||
}
|
||||
|
||||
public get questionId(): number
|
||||
{
|
||||
return this._questionId;
|
||||
}
|
||||
|
||||
public set questionId(k: number)
|
||||
{
|
||||
this._questionId = k;
|
||||
}
|
||||
|
||||
public get duration(): number
|
||||
{
|
||||
return this._duration;
|
||||
}
|
||||
|
||||
public set duration(k: number)
|
||||
{
|
||||
this._duration = k;
|
||||
}
|
||||
|
||||
public get question(): IQuestion
|
||||
{
|
||||
return this._question;
|
||||
}
|
||||
|
||||
public set question(k: IQuestion)
|
||||
{
|
||||
this._question = k;
|
||||
}
|
||||
|
||||
public get userId(): number
|
||||
{
|
||||
return this._userId;
|
||||
}
|
||||
|
||||
public set userId(k: number)
|
||||
{
|
||||
this._userId = k;
|
||||
}
|
||||
|
||||
public get value(): string
|
||||
{
|
||||
return this._value;
|
||||
}
|
||||
|
||||
public set value(k: string)
|
||||
{
|
||||
this._value = k;
|
||||
}
|
||||
|
||||
public get answerCounts(): Map<string, number>
|
||||
{
|
||||
return this._answerCounts;
|
||||
}
|
||||
|
||||
public set answerCounts(k: Map<string, number>)
|
||||
{
|
||||
this._answerCounts = k;
|
||||
}
|
||||
}
|
75
src/api/nitro/room/widgets/handlers/PollWidgetHandler.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import { NitroEvent, RoomSessionPollEvent, RoomWidgetEnum } from '@nitrots/nitro-renderer';
|
||||
import { RoomWidgetPollUpdateEvent } from '../events/RoomWidgetPollUpdateEvent';
|
||||
import { RoomWidgetUpdateEvent } from '../events/RoomWidgetUpdateEvent';
|
||||
import { RoomWidgetMessage } from '../messages/RoomWidgetMessage';
|
||||
import { RoomWidgetPollMessage } from '../messages/RoomWidgetPollMessage';
|
||||
import { RoomWidgetHandler } from './RoomWidgetHandler';
|
||||
|
||||
export class PollWidgetHandler extends RoomWidgetHandler
|
||||
{
|
||||
public processEvent(event: NitroEvent): void
|
||||
{
|
||||
const pollEvent = (event as RoomSessionPollEvent);
|
||||
|
||||
let widgetEvent: RoomWidgetPollUpdateEvent;
|
||||
|
||||
switch(event.type)
|
||||
{
|
||||
case RoomSessionPollEvent.OFFER:
|
||||
widgetEvent = new RoomWidgetPollUpdateEvent(RoomWidgetPollUpdateEvent.OFFER, pollEvent.id);
|
||||
widgetEvent.summary = pollEvent.summary;
|
||||
widgetEvent.headline = pollEvent.headline;
|
||||
break;
|
||||
case RoomSessionPollEvent.ERROR:
|
||||
widgetEvent = new RoomWidgetPollUpdateEvent(RoomWidgetPollUpdateEvent.ERROR, pollEvent.id);
|
||||
widgetEvent.summary = pollEvent.summary;
|
||||
widgetEvent.headline = pollEvent.headline;
|
||||
break;
|
||||
case RoomSessionPollEvent.CONTENT:
|
||||
widgetEvent = new RoomWidgetPollUpdateEvent(RoomWidgetPollUpdateEvent.CONTENT, pollEvent.id);
|
||||
widgetEvent.startMessage = pollEvent.startMessage;
|
||||
widgetEvent.endMessage = pollEvent.endMessage;
|
||||
widgetEvent.numQuestions = pollEvent.numQuestions;
|
||||
widgetEvent.questionArray = pollEvent.questionArray;
|
||||
widgetEvent.npsPoll = pollEvent.npsPoll;
|
||||
break;
|
||||
}
|
||||
|
||||
if(!widgetEvent) return;
|
||||
|
||||
this.container.eventDispatcher.dispatchEvent(widgetEvent);
|
||||
}
|
||||
|
||||
public processWidgetMessage(message: RoomWidgetMessage): RoomWidgetUpdateEvent
|
||||
{
|
||||
const pollMessage = (message as RoomWidgetPollMessage);
|
||||
switch(message.type)
|
||||
{
|
||||
case RoomWidgetPollMessage.START:
|
||||
this.container.roomSession.sendPollStartMessage(pollMessage.id);
|
||||
break;
|
||||
case RoomWidgetPollMessage.REJECT:
|
||||
this.container.roomSession.sendPollRejectMessage(pollMessage.id);
|
||||
break;
|
||||
case RoomWidgetPollMessage.ANSWER:
|
||||
this.container.roomSession.sendPollAnswerMessage(pollMessage.id, pollMessage.questionId, pollMessage.answers);
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public get type(): string
|
||||
{
|
||||
return RoomWidgetEnum.ROOM_POLL;
|
||||
}
|
||||
|
||||
public get eventTypes(): string[]
|
||||
{
|
||||
return [RoomSessionPollEvent.OFFER, RoomSessionPollEvent.ERROR, RoomSessionPollEvent.CONTENT];
|
||||
}
|
||||
|
||||
public get messageTypes(): string[]
|
||||
{
|
||||
return [RoomWidgetPollMessage.ANSWER, RoomWidgetPollMessage.REJECT, RoomWidgetPollMessage.START];
|
||||
}
|
||||
}
|
74
src/api/nitro/room/widgets/handlers/WordQuizWidgetHandler.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { AvatarAction, NitroEvent, RoomSessionWordQuizEvent, RoomWidgetEnum } from '@nitrots/nitro-renderer';
|
||||
import { RoomWidgetHandler } from '.';
|
||||
import { GetRoomEngine } from '../../GetRoomEngine';
|
||||
import { RoomWidgetUpdateEvent } from '../events';
|
||||
import { RoomWidgetWordQuizUpdateEvent } from '../events/RoomWidgetWordQuizUpdateEvent';
|
||||
import { RoomWidgetMessage } from '../messages';
|
||||
|
||||
export class WordQuizWidgetHandler extends RoomWidgetHandler
|
||||
{
|
||||
public processEvent(event: NitroEvent): void
|
||||
{
|
||||
const roomQuizEvent = (event as RoomSessionWordQuizEvent);
|
||||
let widgetEvent: RoomWidgetWordQuizUpdateEvent;
|
||||
switch(event.type)
|
||||
{
|
||||
case RoomSessionWordQuizEvent.ANSWERED:
|
||||
const roomId = this.container.roomSession.roomId;
|
||||
const userData = this.container.roomSession.userDataManager.getUserData(roomQuizEvent.userId);
|
||||
if(!userData) return;
|
||||
widgetEvent = new RoomWidgetWordQuizUpdateEvent(RoomWidgetWordQuizUpdateEvent.QUESTION_ANSWERED, roomQuizEvent.id);
|
||||
widgetEvent.value = roomQuizEvent.value;
|
||||
widgetEvent.userId = roomQuizEvent.userId;
|
||||
widgetEvent.answerCounts = roomQuizEvent.answerCounts;
|
||||
|
||||
if(widgetEvent.value === '0')
|
||||
{
|
||||
GetRoomEngine().updateRoomObjectUserGesture(roomId, userData.roomIndex, AvatarAction.getGestureId(AvatarAction.GESTURE_SAD));
|
||||
}
|
||||
else
|
||||
{
|
||||
GetRoomEngine().updateRoomObjectUserGesture(roomId, userData.roomIndex, AvatarAction.getGestureId(AvatarAction.GESTURE_SMILE));
|
||||
}
|
||||
break;
|
||||
case RoomSessionWordQuizEvent.FINISHED:
|
||||
widgetEvent = new RoomWidgetWordQuizUpdateEvent(RoomWidgetWordQuizUpdateEvent.QUESTION_FINISHED, roomQuizEvent.id);
|
||||
widgetEvent.pollId = roomQuizEvent.pollId;
|
||||
widgetEvent.questionId = roomQuizEvent.questionId;
|
||||
widgetEvent.answerCounts = roomQuizEvent.answerCounts;
|
||||
break;
|
||||
case RoomSessionWordQuizEvent.QUESTION:
|
||||
widgetEvent = new RoomWidgetWordQuizUpdateEvent(RoomWidgetWordQuizUpdateEvent.NEW_QUESTION, roomQuizEvent.id);
|
||||
widgetEvent.question = roomQuizEvent.question;
|
||||
widgetEvent.duration = roomQuizEvent.duration;
|
||||
widgetEvent.pollType = roomQuizEvent.pollType;
|
||||
widgetEvent.questionId = roomQuizEvent.questionId;
|
||||
widgetEvent.pollId = roomQuizEvent.pollId;
|
||||
break;
|
||||
}
|
||||
|
||||
if(!widgetEvent) return;
|
||||
|
||||
this.container.eventDispatcher.dispatchEvent(widgetEvent);
|
||||
}
|
||||
|
||||
public processWidgetMessage(message: RoomWidgetMessage): RoomWidgetUpdateEvent
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public get type(): string
|
||||
{
|
||||
return RoomWidgetEnum.WORD_QUIZZ;
|
||||
}
|
||||
|
||||
public get eventTypes(): string[]
|
||||
{
|
||||
return [RoomSessionWordQuizEvent.ANSWERED, RoomSessionWordQuizEvent.FINISHED, RoomSessionWordQuizEvent.QUESTION];
|
||||
}
|
||||
|
||||
public get messageTypes(): string[]
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
44
src/api/nitro/room/widgets/messages/RoomWidgetPollMessage.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { RoomWidgetMessage } from './RoomWidgetMessage';
|
||||
|
||||
export class RoomWidgetPollMessage extends RoomWidgetMessage
|
||||
{
|
||||
public static readonly START = 'RWPM_START';
|
||||
public static readonly REJECT = 'RWPM_REJECT';
|
||||
public static readonly ANSWER = 'RWPM_ANSWER';
|
||||
|
||||
private _id = -1;
|
||||
private _questionId = 0;
|
||||
private _answers: string[] = null;
|
||||
|
||||
constructor(type: string, id: number)
|
||||
{
|
||||
super(type);
|
||||
|
||||
this._id = id;
|
||||
}
|
||||
|
||||
public get id(): number
|
||||
{
|
||||
return this._id;
|
||||
}
|
||||
|
||||
public get questionId(): number
|
||||
{
|
||||
return this._questionId;
|
||||
}
|
||||
|
||||
public set questionId(k: number)
|
||||
{
|
||||
this._questionId = k;
|
||||
}
|
||||
|
||||
public get answers(): string[]
|
||||
{
|
||||
return this._answers;
|
||||
}
|
||||
|
||||
public set answers(k: string[])
|
||||
{
|
||||
this._answers = k;
|
||||
}
|
||||
}
|
BIN
src/assets/images/avatareditor/avatar-editor-spritesheet.png
Normal file
After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 177 B |
BIN
src/assets/images/friends/friends-spritesheet.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 174 B After Width: | Height: | Size: 174 B |
BIN
src/assets/images/friends/icon-add.png
Normal file
After Width: | Height: | Size: 205 B |
Before Width: | Height: | Size: 169 B After Width: | Height: | Size: 169 B |
Before Width: | Height: | Size: 199 B After Width: | Height: | Size: 199 B |
Before Width: | Height: | Size: 173 B After Width: | Height: | Size: 173 B |
Before Width: | Height: | Size: 162 B After Width: | Height: | Size: 162 B |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 201 B After Width: | Height: | Size: 201 B |
BIN
src/assets/images/friends/icon-new-message.png
Normal file
After Width: | Height: | Size: 219 B |
Before Width: | Height: | Size: 177 B After Width: | Height: | Size: 177 B |
Before Width: | Height: | Size: 264 B After Width: | Height: | Size: 264 B |
Before Width: | Height: | Size: 257 B After Width: | Height: | Size: 257 B |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 205 B After Width: | Height: | Size: 205 B |
Before Width: | Height: | Size: 225 B After Width: | Height: | Size: 225 B |
After Width: | Height: | Size: 145 B |
BIN
src/assets/images/room-widgets/wordquiz-widget/thumbs-down.png
Normal file
After Width: | Height: | Size: 182 B |
After Width: | Height: | Size: 135 B |
BIN
src/assets/images/room-widgets/wordquiz-widget/thumbs-up.png
Normal file
After Width: | Height: | Size: 174 B |
@ -159,18 +159,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.icon-deny {
|
||||
background: url("../images/icons/deny.png");
|
||||
width: 13px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
&.icon-accept {
|
||||
background: url("../images/icons/accept.png");
|
||||
width: 13px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
&.icon-wired-trigger {
|
||||
background-image: url("../images/wired/icon_trigger.png");
|
||||
width: 13px;
|
||||
@ -189,176 +177,6 @@
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
&.arrow-left-icon {
|
||||
background-image: url("../images/avatareditor/arrow-left-icon.png");
|
||||
width: 28px;
|
||||
height: 21px;
|
||||
}
|
||||
|
||||
&.arrow-right-icon {
|
||||
background-image: url("../images/avatareditor/arrow-right-icon.png");
|
||||
width: 28px;
|
||||
height: 21px;
|
||||
}
|
||||
|
||||
&.clear-icon {
|
||||
background-image: url("../images/avatareditor/clear-icon.png");
|
||||
width: 27px;
|
||||
height: 27px;
|
||||
}
|
||||
|
||||
&.ca-icon {
|
||||
background-image: url("../images/avatareditor/ca-icon.png");
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
|
||||
&.selected {
|
||||
background-image: url("../images/avatareditor/ca-selected-icon.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.cc-icon {
|
||||
background-image: url("../images/avatareditor/cc-icon.png");
|
||||
width: 31px;
|
||||
height: 29px;
|
||||
|
||||
&.selected {
|
||||
background-image: url("../images/avatareditor/cc-selected-icon.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.ch-icon {
|
||||
background-image: url("../images/avatareditor/ch-icon.png");
|
||||
width: 29px;
|
||||
height: 24px;
|
||||
|
||||
&.selected {
|
||||
background-image: url("../images/avatareditor/ch-selected-icon.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.cp-icon {
|
||||
background-image: url("../images/avatareditor/cp-icon.png");
|
||||
width: 30px;
|
||||
height: 24px;
|
||||
|
||||
&.selected {
|
||||
background-image: url("../images/avatareditor/cp-selected-icon.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.ea-icon {
|
||||
background-image: url("../images/avatareditor/ea-icon.png");
|
||||
width: 35px;
|
||||
height: 16px;
|
||||
|
||||
&.selected {
|
||||
background-image: url("../images/avatareditor/ea-selected-icon.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.fa-icon {
|
||||
background-image: url("../images/avatareditor/fa-icon.png");
|
||||
width: 27px;
|
||||
height: 20px;
|
||||
|
||||
&.selected {
|
||||
background-image: url("../images/avatareditor/fa-selected-icon.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.female-icon {
|
||||
background-image: url("../images/avatareditor/female-icon.png");
|
||||
width: 18px;
|
||||
height: 27px;
|
||||
|
||||
&.selected {
|
||||
background-image: url("../images/avatareditor/female-selected-icon.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.ha-icon {
|
||||
background-image: url("../images/avatareditor/ha-icon.png");
|
||||
width: 25px;
|
||||
height: 22px;
|
||||
|
||||
&.selected {
|
||||
background-image: url("../images/avatareditor/ha-selected-icon.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.he-icon {
|
||||
background-image: url("../images/avatareditor/he-icon.png");
|
||||
width: 31px;
|
||||
height: 27px;
|
||||
|
||||
&.selected {
|
||||
background-image: url("../images/avatareditor/he-selected-icon.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.hr-icon {
|
||||
background-image: url("../images/avatareditor/hr-icon.png");
|
||||
width: 29px;
|
||||
height: 25px;
|
||||
|
||||
&.selected {
|
||||
background-image: url("../images/avatareditor/hr-selected-icon.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.lg-icon {
|
||||
background-image: url("../images/avatareditor/lg-icon.png");
|
||||
width: 19px;
|
||||
height: 20px;
|
||||
|
||||
&.selected {
|
||||
background-image: url("../images/avatareditor/lg-selected-icon.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.loading-icon {
|
||||
background-image: url("../images/icons/loading-icon.png");
|
||||
width: 17px;
|
||||
height: 21px;
|
||||
}
|
||||
|
||||
&.male-icon {
|
||||
background-image: url("../images/avatareditor/male-icon.png");
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
|
||||
&.selected {
|
||||
background-image: url("../images/avatareditor/male-selected-icon.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.sh-icon {
|
||||
background-image: url("../images/avatareditor/sh-icon.png");
|
||||
width: 37px;
|
||||
height: 10px;
|
||||
|
||||
&.selected {
|
||||
background-image: url("../images/avatareditor/sh-selected-icon.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.wa-icon {
|
||||
background-image: url("../images/avatareditor/wa-icon.png");
|
||||
width: 36px;
|
||||
height: 18px;
|
||||
|
||||
&.selected {
|
||||
background-image: url("../images/avatareditor/wa-selected-icon.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.sellable-icon {
|
||||
background-image: url("../images/avatareditor/sellable-icon.png");
|
||||
width: 17px;
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
&.chatstyles-icon {
|
||||
background-image: url("../images/chat/styles-icon.png");
|
||||
width: 17px;
|
||||
@ -491,12 +309,6 @@
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
&.icon-fb-profile {
|
||||
background: url("../images/toolbar/icons/friend-bar/profile.png");
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
}
|
||||
|
||||
&.icon-camera-colormatrix {
|
||||
background: url("../images/icons/camera-colormatrix.png");
|
||||
width: 32px;
|
||||
@ -509,34 +321,6 @@
|
||||
height: 21px;
|
||||
}
|
||||
|
||||
&.icon-user-profile {
|
||||
background: url("../images/icons/user-profile.png");
|
||||
width: 13px;
|
||||
height: 11px;
|
||||
|
||||
&:hover {
|
||||
background: url("../images/icons/user-profile-hover.png");
|
||||
}
|
||||
}
|
||||
|
||||
&.icon-fb-profile {
|
||||
background: url("../images/toolbar/icons/friend-bar/profile.png");
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
}
|
||||
|
||||
&.icon-fb-chat {
|
||||
background: url("../images/toolbar/icons/friend-bar/chat.png");
|
||||
width: 20px;
|
||||
height: 21px;
|
||||
}
|
||||
|
||||
&.icon-fb-visit {
|
||||
background: url("../images/toolbar/icons/friend-bar/visit.png");
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
}
|
||||
|
||||
&.icon-pf-online {
|
||||
background: url("../images/profile/icons/online.gif");
|
||||
width: 40px;
|
||||
@ -555,30 +339,6 @@
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
&.icon-relationship-none {
|
||||
background: url("../images/friendlist/icons/icon_relationship_none.png");
|
||||
width: 16px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
&.icon-relationship-heart {
|
||||
background: url("../images/profile/icons/heart.png");
|
||||
width: 16px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
&.icon-relationship-bobba {
|
||||
background: url("../images/profile/icons/bobba.png");
|
||||
width: 16px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
&.icon-relationship-smile {
|
||||
background: url("../images/profile/icons/smile.png");
|
||||
width: 16px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
&.icon-group-type-0 {
|
||||
background: url("../images/groups/icons/grouptype_icon_0.png");
|
||||
width: 16px;
|
||||
@ -681,18 +441,6 @@
|
||||
height: 11px;
|
||||
}
|
||||
|
||||
&.icon-friendlist-follow {
|
||||
background: url("../images/friendlist/icons/icon_follow.png");
|
||||
width: 16px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
&.icon-friendlist-chat {
|
||||
background: url("../images/friendlist/icons/icon_chat.png");
|
||||
width: 17px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
&.icon-youtube-next {
|
||||
background: url("../images/room-widgets/youtube-widget/next.png");
|
||||
width: 21px;
|
||||
@ -704,17 +452,6 @@
|
||||
width: 21px;
|
||||
height: 16px;
|
||||
}
|
||||
&.icon-friendlist-warning {
|
||||
background: url("../images/friendlist/icons/icon_warning.png");
|
||||
width: 23px;
|
||||
height: 21px;
|
||||
}
|
||||
|
||||
&.icon-friendlist-new-message {
|
||||
background: url("../images/friendlist/icons/icon_new_message.png");
|
||||
width: 14px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
&.icon-hc-banner {
|
||||
background: url("../images/catalog/hc_big.png");
|
||||
|
@ -70,3 +70,11 @@ ul {
|
||||
.grayscale {
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
.flex-none {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.z-index-1 {
|
||||
z-index: 1;
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { NitroLayoutBaseProps } from './NitroLayoutBase.types';
|
||||
|
||||
export const NitroLayoutBase: FC<NitroLayoutBaseProps> = props =>
|
||||
{
|
||||
const { className = '', overflow = null, position = null, gap = null, children = null, ...rest } = props;
|
||||
const { className = '', overflow = null, position = null, gap = null, ref = null, innerRef = null, children = null, ...rest } = props;
|
||||
|
||||
const getClassName = useMemo(() =>
|
||||
{
|
||||
@ -21,7 +21,7 @@ export const NitroLayoutBase: FC<NitroLayoutBaseProps> = props =>
|
||||
}, [ className, overflow, position, gap ]);
|
||||
|
||||
return (
|
||||
<div className={ getClassName } { ...rest }>
|
||||
<div className={ getClassName } ref={ innerRef } { ...rest }>
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { DetailedHTMLProps, HTMLAttributes } from 'react';
|
||||
import { DetailedHTMLProps, HTMLAttributes, LegacyRef } from 'react';
|
||||
import { NitroLayoutOverflow, NitroLayoutPosition, NitroLayoutSpacing } from '../common';
|
||||
|
||||
export interface NitroLayoutBaseProps extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
|
||||
{
|
||||
innerRef?: LegacyRef<HTMLDivElement>;
|
||||
overflow?: NitroLayoutOverflow;
|
||||
position?: NitroLayoutPosition;
|
||||
gap?: NitroLayoutSpacing;
|
||||
|
21
src/layout/card/accordion/NitroCardAccordionContext.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { createContext, Dispatch, FC, ProviderProps, SetStateAction, useContext } from 'react';
|
||||
|
||||
export interface INitroCardAccordionContext
|
||||
{
|
||||
closers: Function[];
|
||||
setClosers: Dispatch<SetStateAction<Function[]>>;
|
||||
closeAll: () => void;
|
||||
}
|
||||
|
||||
const NitroCardAccordionContext = createContext<INitroCardAccordionContext>({
|
||||
closers: null,
|
||||
setClosers: null,
|
||||
closeAll: null
|
||||
});
|
||||
|
||||
export const NitroCardAccordionContextProvider: FC<ProviderProps<INitroCardAccordionContext>> = props =>
|
||||
{
|
||||
return <NitroCardAccordionContext.Provider { ...props } />;
|
||||
}
|
||||
|
||||
export const useNitroCardAccordionContext = () => useContext(NitroCardAccordionContext);
|
@ -1 +1,6 @@
|
||||
.nitro-card-accordion {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@import "./set/NitroCardAccordionSetView";
|
||||
|
@ -1,13 +1,32 @@
|
||||
import { FC } from 'react';
|
||||
import { FC, useCallback, useMemo, useState } from 'react';
|
||||
import { NitroLayoutFlexColumn } from '../..';
|
||||
import { NitroCardAccordionContextProvider } from './NitroCardAccordionContext';
|
||||
import { NitroCardAccordionViewProps } from './NitroCardAccordionView.types';
|
||||
|
||||
export const NitroCardAccordionView: FC<NitroCardAccordionViewProps> = props =>
|
||||
{
|
||||
const { className = '' } = props;
|
||||
const { className = '', children = null, ...rest } = props;
|
||||
const [ closers, setClosers ] = useState<Function[]>([]);
|
||||
|
||||
const getClassName = useMemo(() =>
|
||||
{
|
||||
let newClassName = 'nitro-card-accordion text-black';
|
||||
|
||||
if(className && className.length) newClassName += ` ${ className }`;
|
||||
|
||||
return newClassName;
|
||||
}, [ className ]);
|
||||
|
||||
const closeAll = useCallback(() =>
|
||||
{
|
||||
for(const closer of closers) closer();
|
||||
}, [ closers ]);
|
||||
|
||||
return (
|
||||
<div className={ 'nitro-card-accordion text-black ' + className }>
|
||||
{ props.children }
|
||||
</div>
|
||||
<NitroCardAccordionContextProvider value={ { closers, setClosers, closeAll } }>
|
||||
<NitroLayoutFlexColumn className={ getClassName } { ...rest }>
|
||||
{ children }
|
||||
</NitroLayoutFlexColumn>
|
||||
</NitroCardAccordionContextProvider>
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
export interface NitroCardAccordionViewProps
|
||||
import { NitroLayoutFlexColumnProps } from '../..';
|
||||
|
||||
export interface NitroCardAccordionViewProps extends NitroLayoutFlexColumnProps
|
||||
{
|
||||
className?: string;
|
||||
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
.nitro-card-accordion-set {
|
||||
.nitro-card-accordion-set-header {
|
||||
|
||||
&.active {
|
||||
height: 100%;
|
||||
background: rgba($white, 0.5);
|
||||
border-bottom: 1px solid rgba($black, 0.2);
|
||||
}
|
||||
|
||||
.nitro-card-accordion-set-content {
|
||||
background: rgba($white, 0.5);
|
||||
.nitro-card-accordion-set-header {
|
||||
border-bottom: 1px solid rgba($black, 0.2);
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,15 @@
|
||||
import classNames from 'classnames';
|
||||
import { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { NitroLayoutFlex } from '../../..';
|
||||
import { NitroLayoutBase } from '../../../base';
|
||||
import { useNitroCardAccordionContext } from '../NitroCardAccordionContext';
|
||||
import { NitroCardAccordionSetViewProps } from './NitroCardAccordionSetView.types';
|
||||
|
||||
export const NitroCardAccordionSetView: FC<NitroCardAccordionSetViewProps> = props =>
|
||||
{
|
||||
const { headerText = '', isExpanded = false, className = '', children = null, ...rest } = props;
|
||||
const [ isOpen, setIsOpen ] = useState(false);
|
||||
const { setClosers = null, closeAll = null } = useNitroCardAccordionContext();
|
||||
|
||||
const getClassName = useMemo(() =>
|
||||
{
|
||||
@ -20,14 +22,66 @@ export const NitroCardAccordionSetView: FC<NitroCardAccordionSetViewProps> = pro
|
||||
return newClassName;
|
||||
}, [ className, isOpen ]);
|
||||
|
||||
const onClick = useCallback(() =>
|
||||
{
|
||||
closeAll();
|
||||
|
||||
setIsOpen(prevValue => !prevValue);
|
||||
// if(isOpen)
|
||||
// {
|
||||
// closeAll();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// BatchUpdates(() =>
|
||||
// {
|
||||
// closeAll();
|
||||
// setIsOpen(true);
|
||||
// });
|
||||
// }
|
||||
}, [ closeAll ]);
|
||||
|
||||
const close = useCallback(() =>
|
||||
{
|
||||
setIsOpen(false);
|
||||
}, []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setIsOpen(isExpanded);
|
||||
}, [ isExpanded ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
const closeFunction = close;
|
||||
|
||||
setClosers(prevValue =>
|
||||
{
|
||||
const newClosers = [ ...prevValue ];
|
||||
|
||||
newClosers.push(closeFunction);
|
||||
|
||||
return newClosers;
|
||||
});
|
||||
|
||||
return () =>
|
||||
{
|
||||
setClosers(prevValue =>
|
||||
{
|
||||
const newClosers = [ ...prevValue ];
|
||||
|
||||
const index = newClosers.indexOf(closeFunction);
|
||||
|
||||
if(index >= 0) newClosers.splice(index, 1);
|
||||
|
||||
return newClosers;
|
||||
});
|
||||
}
|
||||
}, [ close, setClosers ]);
|
||||
|
||||
return (
|
||||
<NitroLayoutBase className={ getClassName } { ...rest }>
|
||||
<NitroLayoutFlex className="nitro-card-accordion-set-header px-2 py-1" onClick={ event => setIsOpen(!isOpen) }>
|
||||
<NitroLayoutFlex className="nitro-card-accordion-set-header px-2 py-1" onClick={ onClick }>
|
||||
<div className="w-100">
|
||||
{ headerText }
|
||||
</div>
|
||||
|
@ -1 +1 @@
|
||||
export type NitroLayoutVariant = 'primary' | 'success' | 'danger' | 'secondary';
|
||||
export type NitroLayoutVariant = 'primary' | 'success' | 'danger' | 'secondary' | 'link' | 'black';
|
||||
|
@ -14,3 +14,4 @@ export * from './notification-alert';
|
||||
export * from './notification-bubble';
|
||||
export * from './transitions';
|
||||
export * from './trophy';
|
||||
export * from './user-profile-icon';
|
||||
|
26
src/layout/user-profile-icon/UserProfileIconView.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { FC, useMemo } from 'react';
|
||||
import { GetUserProfile } from '../../api';
|
||||
import { NitroLayoutBase } from '../base';
|
||||
import { UserProfileIconViewProps } from './UserProfileIconView.types';
|
||||
|
||||
export const UserProfileIconView: FC<UserProfileIconViewProps> = props =>
|
||||
{
|
||||
const { userId = 0, userName = null } = props;
|
||||
|
||||
const { className = '', children = null, ...rest } = props;
|
||||
|
||||
const getClassName = useMemo(() =>
|
||||
{
|
||||
let newClassName = 'nitro-friends-spritesheet icon-profile-sm me-1 cursor-pointer';
|
||||
|
||||
if(className && className.length) newClassName += ` ${ className }`;
|
||||
|
||||
return newClassName;
|
||||
}, [ className ]);
|
||||
|
||||
return (
|
||||
<NitroLayoutBase className={ getClassName } onClick={ event => GetUserProfile(userId) }>
|
||||
{ children }
|
||||
</NitroLayoutBase>
|
||||
);
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import { NitroLayoutBaseProps } from '../base';
|
||||
|
||||
export interface UserProfileIconViewProps extends NitroLayoutBaseProps
|
||||
{
|
||||
userId?: number;
|
||||
userName?: string;
|
||||
}
|
2
src/layout/user-profile-icon/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './UserProfileIconView';
|
||||
export * from './UserProfileIconView.types';
|
@ -1,3 +1,217 @@
|
||||
.nitro-avatar-editor-spritesheet {
|
||||
background: url('../../assets/images/avatareditor/avatar-editor-spritesheet.png') transparent no-repeat;
|
||||
|
||||
&.arrow-left-icon {
|
||||
width: 28px;
|
||||
height: 21px;
|
||||
background-position: -226px -131px;
|
||||
}
|
||||
|
||||
&.arrow-right-icon {
|
||||
width: 28px;
|
||||
height: 21px;
|
||||
background-position: -226px -162px;
|
||||
}
|
||||
|
||||
&.ca-icon {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
background-position: -226px -61px;
|
||||
|
||||
&.selected {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
background-position: -226px -96px;
|
||||
}
|
||||
}
|
||||
|
||||
&.cc-icon {
|
||||
width: 31px;
|
||||
height: 29px;
|
||||
background-position: -145px -5px;
|
||||
|
||||
&.selected {
|
||||
width: 31px;
|
||||
height: 29px;
|
||||
background-position: -145px -44px;
|
||||
}
|
||||
}
|
||||
|
||||
&.ch-icon {
|
||||
width: 29px;
|
||||
height: 24px;
|
||||
background-position: -186px -39px;
|
||||
|
||||
&.selected {
|
||||
width: 29px;
|
||||
height: 24px;
|
||||
background-position: -186px -73px;
|
||||
}
|
||||
}
|
||||
|
||||
&.clear-icon {
|
||||
width: 27px;
|
||||
height: 27px;
|
||||
background-position: -145px -157px;
|
||||
}
|
||||
|
||||
&.cp-icon {
|
||||
width: 30px;
|
||||
height: 24px;
|
||||
background-position: -145px -264px;
|
||||
|
||||
&.selected {
|
||||
width: 30px;
|
||||
height: 24px;
|
||||
background-position: -186px -5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&.ea-icon {
|
||||
width: 35px;
|
||||
height: 16px;
|
||||
background-position: -226px -193px;
|
||||
|
||||
&.selected {
|
||||
width: 35px;
|
||||
height: 16px;
|
||||
background-position: -226px -219px;
|
||||
}
|
||||
}
|
||||
|
||||
&.fa-icon {
|
||||
width: 27px;
|
||||
height: 20px;
|
||||
background-position: -186px -137px;
|
||||
|
||||
&.selected {
|
||||
width: 27px;
|
||||
height: 20px;
|
||||
background-position: -186px -107px;
|
||||
}
|
||||
}
|
||||
|
||||
&.female-icon {
|
||||
width: 18px;
|
||||
height: 27px;
|
||||
background-position: -186px -202px;
|
||||
|
||||
&.selected {
|
||||
width: 18px;
|
||||
height: 27px;
|
||||
background-position: -186px -239px;
|
||||
}
|
||||
}
|
||||
|
||||
&.ha-icon {
|
||||
width: 25px;
|
||||
height: 22px;
|
||||
background-position: -226px -245px;
|
||||
|
||||
&.selected {
|
||||
width: 25px;
|
||||
height: 22px;
|
||||
background-position: -226px -277px;
|
||||
}
|
||||
}
|
||||
|
||||
&.he-icon {
|
||||
width: 31px;
|
||||
height: 27px;
|
||||
background-position: -145px -83px;
|
||||
|
||||
&.selected {
|
||||
width: 31px;
|
||||
height: 27px;
|
||||
background-position: -145px -120px;
|
||||
}
|
||||
}
|
||||
|
||||
&.hr-icon {
|
||||
width: 29px;
|
||||
height: 25px;
|
||||
background-position: -145px -194px;
|
||||
|
||||
&.selected {
|
||||
width: 29px;
|
||||
height: 25px;
|
||||
background-position: -145px -229px;
|
||||
}
|
||||
}
|
||||
|
||||
&.lg-icon {
|
||||
width: 19px;
|
||||
height: 20px;
|
||||
background-position: -303px -45px;
|
||||
|
||||
&.selected {
|
||||
width: 19px;
|
||||
height: 20px;
|
||||
background-position: -303px -75px;
|
||||
}
|
||||
}
|
||||
|
||||
&.loading-icon {
|
||||
width: 21px;
|
||||
height: 25px;
|
||||
background-position: -186px -167px;
|
||||
}
|
||||
|
||||
|
||||
&.male-icon {
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
background-position: -186px -276px;
|
||||
|
||||
&.selected {
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
background-position: -272px -5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&.sellable-icon {
|
||||
width: 17px;
|
||||
height: 15px;
|
||||
background-position: -303px -105px;
|
||||
}
|
||||
|
||||
|
||||
&.sh-icon {
|
||||
width: 37px;
|
||||
height: 10px;
|
||||
background-position: -303px -5px;
|
||||
|
||||
&.selected {
|
||||
width: 37px;
|
||||
height: 10px;
|
||||
background-position: -303px -25px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&.spotlight {
|
||||
width: 130px;
|
||||
height: 305px;
|
||||
background-position: -5px -5px;
|
||||
}
|
||||
|
||||
|
||||
&.wa-icon {
|
||||
width: 36px;
|
||||
height: 18px;
|
||||
background-position: -226px -5px;
|
||||
|
||||
&.selected {
|
||||
width: 36px;
|
||||
height: 18px;
|
||||
background-position: -226px -33px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nitro-avatar-editor {
|
||||
width: $avatar-editor-width;
|
||||
height: $avatar-editor-height;
|
||||
@ -37,13 +251,10 @@
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.avatar-spotlight {
|
||||
.spotlight {
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
width: 100%;
|
||||
height: 305px;
|
||||
margin: 0 auto;
|
||||
background: transparent url('../../assets/images/avatareditor/spotlight.png') no-repeat center;
|
||||
opacity: 0.3;
|
||||
pointer-events: none;
|
||||
z-index: 3;
|
||||
|
@ -45,11 +45,11 @@ export const AvatarEditorFigurePreviewView: FC<AvatarEditorFigurePreviewViewProp
|
||||
return (
|
||||
<NitroLayoutFlexColumn className="figure-preview-container" overflow="hidden" position="relative">
|
||||
<AvatarImageView figure={ figureData.getFigureString() } direction={ figureData.direction } scale={ 2 } />
|
||||
<NitroLayoutBase className="avatar-spotlight" />
|
||||
<NitroLayoutBase className="nitro-avatar-editor-spritesheet spotlight" />
|
||||
<NitroLayoutBase className="avatar-shadow" />
|
||||
<NitroLayoutBase className="arrow-container">
|
||||
<i className="icon arrow-left-icon" onClick={ event => rotateFigure(figureData.direction + 1) } />
|
||||
<i className="icon arrow-right-icon" onClick={ event => rotateFigure(figureData.direction - 1) } />
|
||||
<div className="nitro-avatar-editor-spritesheet arrow-left-icon" onClick={ event => rotateFigure(figureData.direction + 1) } />
|
||||
<div className="nitro-avatar-editor-spritesheet arrow-right-icon" onClick={ event => rotateFigure(figureData.direction - 1) } />
|
||||
</NitroLayoutBase>
|
||||
</NitroLayoutFlexColumn>
|
||||
);
|
||||
|
@ -26,8 +26,8 @@ export const AvatarEditorFigureSetItemView: FC<AvatarEditorFigureSetItemViewProp
|
||||
return (
|
||||
<NitroCardGridItemView itemImage={ (partItem.isClear ? undefined : partItem.imageUrl) } itemActive={ partItem.isSelected } onClick={ event => onClick(partItem) }>
|
||||
{ partItem.isHC && <CurrencyIcon className="position-absolute end-1 bottom-1" type={ 'hc' } /> }
|
||||
{ partItem.isClear && <i className="icon clear-icon" /> }
|
||||
{ partItem.isSellable && <i className="position-absolute icon sellable-icon end-1 bottom-1" /> }
|
||||
{ partItem.isClear && <div className="nitro-avatar-editor-spritesheet clear-icon" /> }
|
||||
{ partItem.isSellable && <div className="position-absolute nitro-avatar-editor-spritesheet sellable-icon end-1 bottom-1" /> }
|
||||
</NitroCardGridItemView>
|
||||
);
|
||||
}
|
||||
|
@ -52,10 +52,10 @@ export const AvatarEditorModelView: FC<AvatarEditorModelViewProps> = props =>
|
||||
{ model.canSetGender &&
|
||||
<>
|
||||
<NitroLayoutFlex className="justify-content-center align-items-center category-item cursor-pointer" onClick={ event => setGender(FigureData.MALE) }>
|
||||
<i className={ `icon male-icon ${ (gender === FigureData.MALE) ? ' selected' : ''}` } />
|
||||
<div className={ `nitro-avatar-editor-spritesheet male-icon ${ (gender === FigureData.MALE) ? ' selected' : ''}` } />
|
||||
</NitroLayoutFlex>
|
||||
<NitroLayoutFlex className="justify-content-center align-items-center category-item cursor-pointer" onClick={ event => setGender(FigureData.FEMALE) }>
|
||||
<i className={ `icon female-icon ${ (gender === FigureData.FEMALE) ? ' selected' : ''}` } />
|
||||
<div className={ `nitro-avatar-editor-spritesheet female-icon ${ (gender === FigureData.FEMALE) ? ' selected' : ''}` } />
|
||||
</NitroLayoutFlex>
|
||||
</> }
|
||||
{ !model.canSetGender && model.categories && (model.categories.size > 0) && Array.from(model.categories.keys()).map(name =>
|
||||
@ -64,7 +64,7 @@ export const AvatarEditorModelView: FC<AvatarEditorModelViewProps> = props =>
|
||||
|
||||
return (
|
||||
<NitroLayoutFlex key={ name } className="justify-content-center align-items-center category-item cursor-pointer" onClick={ event => selectCategory(name) }>
|
||||
<i className={ `icon ${ category.name }-icon ${ (activeCategory === category) ? ' selected' : ''}` } />
|
||||
<div className={ `nitro-avatar-editor-spritesheet ${ category.name }-icon ${ (activeCategory === category) ? ' selected' : ''}` } />
|
||||
</NitroLayoutFlex>
|
||||
);
|
||||
})}
|
||||
|
@ -1,18 +1,13 @@
|
||||
.nitro-chat-history
|
||||
{
|
||||
width: 300px;
|
||||
.nitro-chat-history {
|
||||
width: $chat-history-width;
|
||||
height: $chat-history-height;
|
||||
|
||||
.chat-history-content
|
||||
{
|
||||
|
||||
.chat-history-container
|
||||
{
|
||||
.chat-history-content {
|
||||
.chat-history-container {
|
||||
min-height: 200px;
|
||||
|
||||
.chat-history-list
|
||||
{
|
||||
.chathistory-entry
|
||||
{
|
||||
.chat-history-list {
|
||||
.chathistory-entry {
|
||||
.light {
|
||||
background-color: #121f27;
|
||||
}
|
||||
|
@ -1,6 +1,95 @@
|
||||
.nitro-friends-spritesheet {
|
||||
background: url('../../assets/images/friends/friends-spritesheet.png') transparent no-repeat;
|
||||
|
||||
&.icon-friendbar-visit {
|
||||
width: 21px; height: 21px;
|
||||
background-position: -38px -5px;
|
||||
}
|
||||
|
||||
&.icon-heart {
|
||||
width: 16px; height: 14px;
|
||||
background-position: -5px -67px;
|
||||
}
|
||||
|
||||
&.icon-new-message {
|
||||
width: 14px; height: 14px;
|
||||
background-position: -96px -53px;
|
||||
}
|
||||
|
||||
&.icon-none {
|
||||
width: 16px; height: 14px;
|
||||
background-position: -31px -67px;
|
||||
}
|
||||
|
||||
&.icon-profile {
|
||||
width: 21px; height: 21px;
|
||||
background-position: -5px -36px;
|
||||
}
|
||||
|
||||
&.icon-profile-sm {
|
||||
width: 13px; height: 11px;
|
||||
background-position: -51px -91px;
|
||||
|
||||
&:hover {
|
||||
width: 13px; height: 11px;
|
||||
background-position: -74px -91px;
|
||||
}
|
||||
}
|
||||
|
||||
&.icon-smile {
|
||||
width: 16px; height: 14px;
|
||||
background-position: -57px -67px;
|
||||
}
|
||||
|
||||
&.icon-warning {
|
||||
width: 23px; height: 21px;
|
||||
background-position: -5px -5px;
|
||||
}
|
||||
|
||||
&.icon-accept {
|
||||
width: 13px; height: 14px;
|
||||
background-position: -5px -91px;
|
||||
}
|
||||
|
||||
&.icon-add {
|
||||
width: 16px; height: 15px;
|
||||
background-position: -69px -31px;
|
||||
}
|
||||
|
||||
&.icon-bobba {
|
||||
width: 16px; height: 14px;
|
||||
background-position: -96px -5px;
|
||||
}
|
||||
|
||||
&.icon-chat {
|
||||
width: 17px; height: 16px;
|
||||
background-position: -69px -5px;
|
||||
}
|
||||
|
||||
&.icon-deny {
|
||||
width: 13px; height: 14px;
|
||||
background-position: -28px -91px;
|
||||
}
|
||||
|
||||
&.icon-follow {
|
||||
width: 16px; height: 14px;
|
||||
background-position: -96px -29px;
|
||||
}
|
||||
|
||||
&.icon-friendbar-chat {
|
||||
width: 20px; height: 21px;
|
||||
background-position: -36px -36px;
|
||||
}
|
||||
}
|
||||
|
||||
.nitro-friends {
|
||||
width: $friends-list-width;
|
||||
height: $friends-list-height;
|
||||
|
||||
.search-input {
|
||||
border: 0;
|
||||
border-bottom: 1px solid rgba($black, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
@import "./views/friend-bar/FriendBarView";
|
||||
|
@ -344,7 +344,7 @@ export const FriendsView: FC<{}> = props =>
|
||||
}, [ requests ]);
|
||||
|
||||
return (
|
||||
<FriendsContextProvider value={ { friends, requests, settings, acceptFriend, declineFriend } }>
|
||||
<FriendsContextProvider value={ { friends, requests, settings, canRequestFriend, requestFriend, acceptFriend, declineFriend } }>
|
||||
{ isReady &&
|
||||
createPortal(<FriendBarView onlineFriends={ onlineFriends } />, document.getElementById('toolbar-friend-bar-container')) }
|
||||
{ isVisible &&
|
||||
|
@ -5,6 +5,8 @@ const FriendsContext = createContext<IFriendsContext>({
|
||||
friends: null,
|
||||
requests: null,
|
||||
settings: null,
|
||||
canRequestFriend: null,
|
||||
requestFriend: null,
|
||||
acceptFriend: null,
|
||||
declineFriend: null
|
||||
});
|
||||
|
@ -8,6 +8,8 @@ export interface IFriendsContext
|
||||
friends: MessengerFriend[];
|
||||
requests: MessengerRequest[];
|
||||
settings: MessengerSettings;
|
||||
canRequestFriend: (userId: number) => boolean;
|
||||
requestFriend: (userId: number, userName: string) => void;
|
||||
acceptFriend: (userId: number) => void;
|
||||
declineFriend: (userId: number, declineAll?: boolean) => void;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import { FollowFriendMessageComposer, MouseEventType } from '@nitrots/nitro-rend
|
||||
import { FC, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { GetUserProfile, LocalizeText, OpenMessengerChat } from '../../../../api';
|
||||
import { SendMessageHook } from '../../../../hooks/messages';
|
||||
import { NitroLayoutBase } from '../../../../layout/base';
|
||||
import { AvatarImageView } from '../../../shared/avatar-image/AvatarImageView';
|
||||
import { FriendBarItemViewProps } from './FriendBarItemView.types';
|
||||
|
||||
@ -63,9 +64,10 @@ export const FriendBarItemView: FC<FriendBarItemViewProps> = props =>
|
||||
<div className="text-truncate">{ friend.name }</div>
|
||||
{ isVisible &&
|
||||
<div className="d-flex justify-content-between">
|
||||
<i onClick={ openMessengerChat } className="icon icon-fb-chat cursor-pointer" />
|
||||
{ friend.followingAllowed && <i onClick={ followFriend } className="icon icon-fb-visit cursor-pointer" /> }
|
||||
<i onClick={ event => GetUserProfile(friend.id) } className="icon icon-fb-profile cursor-pointer" />
|
||||
<NitroLayoutBase className="nitro-friends-spritesheet icon-friendbar-chat cursor-pointer" onClick={ openMessengerChat } />
|
||||
{ friend.followingAllowed &&
|
||||
<NitroLayoutBase className="nitro-friends-spritesheet icon-friendbar-visit cursor-pointer" onClick={ followFriend } /> }
|
||||
<NitroLayoutBase className="nitro-friends-spritesheet icon-profile cursor-pointer" onClick={ event => GetUserProfile(friend.id) } />
|
||||
</div> }
|
||||
</div>
|
||||
);
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { FC, useMemo, useState } from 'react';
|
||||
import { NitroLayoutButton, NitroLayoutFlex } from '../../../../layout';
|
||||
import { NitroLayoutBase } from '../../../../layout/base';
|
||||
import { FriendBarItemView } from '../friend-bar-item/FriendBarItemView';
|
||||
import { FriendBarViewProps } from './FriendBarView.types';
|
||||
|
||||
@ -24,10 +26,10 @@ export const FriendBarView: FC<FriendBarViewProps> = props =>
|
||||
}, [ maxDisplayCount, indexOffset, onlineFriends ]);
|
||||
|
||||
return (
|
||||
<div className="d-flex friend-bar align-items-center">
|
||||
<button type="button" className="btn btn-sm btn-black align-self-center friend-bar-button" disabled={ !canDecreaseIndex } onClick={ event => setIndexOffset(indexOffset - 1) }>
|
||||
<i className="fas fa-chevron-left" />
|
||||
</button>
|
||||
<NitroLayoutFlex className="friend-bar align-items-center">
|
||||
<NitroLayoutButton className="friend-bar-button" variant="black" size="sm" disabled={ !canDecreaseIndex } onClick={ event => setIndexOffset(indexOffset - 1) }>
|
||||
<NitroLayoutBase className="fas fa-chevron-left" />
|
||||
</NitroLayoutButton>
|
||||
{ Array.from(Array(maxDisplayCount), (e, i) =>
|
||||
{
|
||||
return <FriendBarItemView key={ i } friend={ (onlineFriends[ indexOffset + i ] || null) } />;
|
||||
@ -35,6 +37,6 @@ export const FriendBarView: FC<FriendBarViewProps> = props =>
|
||||
<button type="button" className="btn btn-sm btn-black align-self-center friend-bar-button" disabled={ !canIncreaseIndex } onClick={ event => setIndexOffset(indexOffset + 1) }>
|
||||
<i className="fas fa-chevron-right" />
|
||||
</button>
|
||||
</div>
|
||||
</NitroLayoutFlex>
|
||||
);
|
||||
}
|
||||
|
@ -2,7 +2,8 @@ import { FollowFriendMessageComposer, SetRelationshipStatusComposer } from '@nit
|
||||
import { FC, useCallback, useState } from 'react';
|
||||
import { LocalizeText, OpenMessengerChat } from '../../../../api';
|
||||
import { SendMessageHook } from '../../../../hooks';
|
||||
import { UserProfileIconView } from '../../../shared/user-profile-icon/UserProfileIconView';
|
||||
import { NitroLayoutFlex, UserProfileIconView } from '../../../../layout';
|
||||
import { NitroLayoutBase } from '../../../../layout/base';
|
||||
import { MessengerFriend } from '../../common/MessengerFriend';
|
||||
import { FriendsGroupItemViewProps } from './FriendsGroupItemView.types';
|
||||
|
||||
@ -52,19 +53,23 @@ export const FriendsGroupItemView: FC<FriendsGroupItemViewProps> = props =>
|
||||
<div className="px-2 py-1 d-flex gap-1 align-items-center">
|
||||
<UserProfileIconView userId={ friend.id } />
|
||||
<div>{ friend.name }</div>
|
||||
<div className="ms-auto d-flex align-items-center gap-1">
|
||||
{ !isExpanded && <>
|
||||
{ friend.followingAllowed && <i onClick={ followFriend } className="icon icon-friendlist-follow cursor-pointer" title={ LocalizeText('friendlist.tip.follow') } /> }
|
||||
{ friend.online && <i className="icon icon-friendlist-chat cursor-pointer" onClick={ openMessengerChat } title={ LocalizeText('friendlist.tip.im') } /> }
|
||||
<i className={ 'icon cursor-pointer icon-relationship-' + getCurrentRelationshipName() } onClick={ () => setIsExpanded(true) } title={ LocalizeText('infostand.link.relationship') } />
|
||||
<NitroLayoutFlex className="ms-auto align-items-center" gap={ 1 }>
|
||||
{ !isExpanded &&
|
||||
<>
|
||||
{ friend.followingAllowed &&
|
||||
<NitroLayoutBase onClick={ followFriend } className="nitro-friends-spritesheet icon-follow cursor-pointer" title={ LocalizeText('friendlist.tip.follow') } /> }
|
||||
{ friend.online &&
|
||||
<NitroLayoutBase className="nitro-friends-spritesheet icon-chat cursor-pointer" onClick={ openMessengerChat } title={ LocalizeText('friendlist.tip.im') } /> }
|
||||
<NitroLayoutBase className={ `nitro-friends-spritesheet icon-${ getCurrentRelationshipName() } cursor-pointer` }onClick={ event => setIsExpanded(true) } title={ LocalizeText('infostand.link.relationship') } />
|
||||
</> }
|
||||
{ isExpanded && <>
|
||||
<i className="icon icon-relationship-heart cursor-pointer" onClick={ () => updateRelationship(MessengerFriend.RELATIONSHIP_HEART) } />
|
||||
<i className="icon icon-relationship-smile cursor-pointer" onClick={ () => updateRelationship(MessengerFriend.RELATIONSHIP_SMILE) } />
|
||||
<i className="icon icon-relationship-bobba cursor-pointer" onClick={ () => updateRelationship(MessengerFriend.RELATIONSHIP_BOBBA) } />
|
||||
<i className="icon icon-relationship-none cursor-pointer" onClick={ () => updateRelationship(MessengerFriend.RELATIONSHIP_NONE) } />
|
||||
{ isExpanded &&
|
||||
<>
|
||||
<NitroLayoutBase className="nitro-friends-spritesheet icon-heart cursor-pointer" onClick={ () => updateRelationship(MessengerFriend.RELATIONSHIP_HEART) } />
|
||||
<NitroLayoutBase className="nitro-friends-spritesheet icon-smile cursor-pointer" onClick={ () => updateRelationship(MessengerFriend.RELATIONSHIP_SMILE) } />
|
||||
<NitroLayoutBase className="nitro-friends-spritesheet icon-bobba cursor-pointer" onClick={ () => updateRelationship(MessengerFriend.RELATIONSHIP_BOBBA) } />
|
||||
<NitroLayoutBase className="nitro-friends-spritesheet icon-none cursor-pointer" onClick={ () => updateRelationship(MessengerFriend.RELATIONSHIP_NONE) } />
|
||||
</> }
|
||||
</div>
|
||||
</NitroLayoutFlex>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import { LocalizeText } from '../../../../api';
|
||||
import { NitroCardAccordionSetView, NitroCardAccordionView, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../../../layout';
|
||||
import { FriendsGroupView } from '../friends-group/FriendsGroupView';
|
||||
import { FriendsRequestView } from '../friends-request/FriendsRequestView';
|
||||
import { FriendsSearchView } from '../friends-search/FriendsSearchView';
|
||||
import { FriendsListViewProps } from './FriendsListView.types';
|
||||
|
||||
const MODE_FRIENDS: number = 0;
|
||||
@ -15,7 +16,7 @@ export const FriendsListView: FC<FriendsListViewProps> = props =>
|
||||
const [ mode, setMode ] = useState<number>(0);
|
||||
|
||||
return (
|
||||
<NitroCardView className="nitro-friends">
|
||||
<NitroCardView className="nitro-friends" uniqueKey="nitro-friends">
|
||||
<NitroCardHeaderView headerText={ LocalizeText('friendlist.friends') } onCloseClick={ onCloseClick } />
|
||||
<NitroCardTabsView>
|
||||
<NitroCardTabsItemView isActive={ (mode === MODE_FRIENDS) } count={ friendRequests.length } onClick={ event => setMode(MODE_FRIENDS) }>
|
||||
@ -34,13 +35,10 @@ export const FriendsListView: FC<FriendsListViewProps> = props =>
|
||||
<NitroCardAccordionSetView headerText={ LocalizeText('friendlist.friends.offlinecaption') + ` (${offlineFriends.length})` }>
|
||||
<FriendsGroupView list={ offlineFriends } />
|
||||
</NitroCardAccordionSetView>
|
||||
|
||||
|
||||
<FriendsRequestView requests={ friendRequests } />
|
||||
</NitroCardAccordionView> }
|
||||
{ (mode === MODE_SEARCH) &&
|
||||
<>
|
||||
</> }
|
||||
<FriendsSearchView /> }
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { FC } from 'react';
|
||||
import { NitroCardAccordionItemView } from '../../../../layout';
|
||||
import { UserProfileIconView } from '../../../shared/user-profile-icon/UserProfileIconView';
|
||||
import { NitroCardAccordionItemView, NitroLayoutFlex, UserProfileIconView } from '../../../../layout';
|
||||
import { NitroLayoutBase } from '../../../../layout/base';
|
||||
import { useFriendsContext } from '../../context/FriendsContext';
|
||||
import { FriendsRequestItemViewProps } from './FriendsRequestItemView.types';
|
||||
|
||||
@ -14,11 +14,11 @@ export const FriendsRequestItemView: FC<FriendsRequestItemViewProps> = props =>
|
||||
return (
|
||||
<NitroCardAccordionItemView>
|
||||
<UserProfileIconView userId={ request.id } />
|
||||
<div>{ request.name }</div>
|
||||
<div className="ms-auto d-flex align-items-center gap-1">
|
||||
<i className="icon icon-accept cursor-pointer" onClick={ event => acceptFriend(request.requesterUserId) } />
|
||||
<i className="icon icon-deny cursor-pointer" onClick={ event => declineFriend(request.requesterUserId) } />
|
||||
</div>
|
||||
<NitroLayoutBase>{ request.name }</NitroLayoutBase>
|
||||
<NitroLayoutFlex className="ms-auto align-items-center" gap={ 1 }>
|
||||
<NitroLayoutBase className="nitro-friends-spritesheet icon-accept cursor-pointer" onClick={ event => acceptFriend(request.requesterUserId) } />
|
||||
<NitroLayoutBase className="nitro-friends-spritesheet icon-deny cursor-pointer" onClick={ event => declineFriend(request.requesterUserId) } />
|
||||
</NitroLayoutFlex>
|
||||
</NitroCardAccordionItemView>
|
||||
);
|
||||
};
|
||||
|
80
src/views/friends/views/friends-search/FriendsSearchView.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
import { HabboSearchComposer, HabboSearchResultData, HabboSearchResultEvent } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { LocalizeText, OpenMessengerChat } from '../../../../api';
|
||||
import { BatchUpdates, CreateMessageHook, SendMessageHook } from '../../../../hooks';
|
||||
import { NitroCardAccordionItemView, NitroCardAccordionSetView, NitroCardAccordionView, NitroLayoutFlex, UserProfileIconView } from '../../../../layout';
|
||||
import { NitroLayoutBase } from '../../../../layout/base';
|
||||
import { useFriendsContext } from '../../context/FriendsContext';
|
||||
|
||||
export const FriendsSearchView: FC<{}> = props =>
|
||||
{
|
||||
const [ searchValue, setSearchValue ] = useState('');
|
||||
const [ friendResults, setFriendResults ] = useState<HabboSearchResultData[]>([]);
|
||||
const [ otherResults, setOtherResults ] = useState<HabboSearchResultData[]>([]);
|
||||
const { canRequestFriend = null, requestFriend = null } = useFriendsContext();
|
||||
|
||||
const onHabboSearchResultEvent = useCallback((event: HabboSearchResultEvent) =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
BatchUpdates(() =>
|
||||
{
|
||||
setFriendResults(parser.friends);
|
||||
setOtherResults(parser.others);
|
||||
});
|
||||
}, []);
|
||||
|
||||
CreateMessageHook(HabboSearchResultEvent, onHabboSearchResultEvent);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!searchValue || !searchValue.length) return;
|
||||
|
||||
const timeout = setTimeout(() =>
|
||||
{
|
||||
if(!searchValue || !searchValue.length) return;
|
||||
|
||||
SendMessageHook(new HabboSearchComposer(searchValue));
|
||||
}, 500);
|
||||
|
||||
return () => clearTimeout(timeout);
|
||||
}, [ searchValue ]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<input type="text" className="search-input form-control form-control-sm w-100 rounded-0" placeholder={ LocalizeText('generic.search') } value={ searchValue } onChange={ event => setSearchValue(event.target.value) } />
|
||||
<NitroCardAccordionView>
|
||||
<NitroCardAccordionSetView headerText={ LocalizeText('friendlist.search.friendscaption', [ 'cnt' ], [ friendResults.length.toString() ]) } isExpanded={ true }>
|
||||
{ (friendResults.length > 0) && friendResults.map(result =>
|
||||
{
|
||||
return (
|
||||
<NitroCardAccordionItemView key={ result.avatarId }>
|
||||
<UserProfileIconView userId={ result.avatarId } />
|
||||
<NitroLayoutBase>{ result.avatarName }</NitroLayoutBase>
|
||||
<NitroLayoutFlex className="ms-auto align-items-center" gap={ 1 }>
|
||||
{ result.isAvatarOnline &&
|
||||
<NitroLayoutBase className="nitro-friends-spritesheet icon-chat cursor-pointer" onClick={ event => OpenMessengerChat(result.avatarId) } title={ LocalizeText('friendlist.tip.im') } /> }
|
||||
</NitroLayoutFlex>
|
||||
</NitroCardAccordionItemView>
|
||||
);
|
||||
}) }
|
||||
</NitroCardAccordionSetView>
|
||||
<NitroCardAccordionSetView headerText={ LocalizeText('friendlist.search.otherscaption', [ 'cnt' ], [ otherResults.length.toString() ]) } isExpanded={ true }>
|
||||
{ (otherResults.length > 0) && otherResults.map(result =>
|
||||
{
|
||||
return (
|
||||
<NitroCardAccordionItemView key={ result.avatarId }>
|
||||
<UserProfileIconView userId={ result.avatarId } />
|
||||
<NitroLayoutBase>{ result.avatarName }</NitroLayoutBase>
|
||||
<NitroLayoutFlex className="ms-auto align-items-center" gap={ 1 }>
|
||||
{ canRequestFriend(result.avatarId) &&
|
||||
<NitroLayoutBase className="nitro-friends-spritesheet icon-add cursor-pointer" onClick={ event => requestFriend(result.avatarId, result.avatarName) } title={ LocalizeText('friendlist.tip.addfriend') } /> }
|
||||
</NitroLayoutFlex>
|
||||
</NitroCardAccordionItemView>
|
||||
);
|
||||
}) }
|
||||
</NitroCardAccordionSetView>
|
||||
</NitroCardAccordionView>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
import { FC } from 'react';
|
||||
import { GetSessionDataManager } from '../../../../api';
|
||||
import { NitroLayoutFlex } from '../../../../layout';
|
||||
import { NitroLayoutBase } from '../../../../layout/base';
|
||||
import { AvatarImageView } from '../../../shared/avatar-image/AvatarImageView';
|
||||
import { MessengerThreadChat } from '../../common/MessengerThreadChat';
|
||||
import { FriendsMessengerThreadGroupProps } from './FriendsMessengerThreadGroup.types';
|
||||
@ -17,13 +19,13 @@ export const FriendsMessengerThreadGroup: FC<FriendsMessengerThreadGroupProps> =
|
||||
{ group.chats.map((chat, index) =>
|
||||
{
|
||||
return (
|
||||
<div key={ index } className="text-break">
|
||||
<NitroLayoutBase key={ index } className="text-break">
|
||||
{ chat.type === MessengerThreadChat.SECURITY_NOTIFICATION &&
|
||||
<div className="bg-light rounded mb-2 d-flex gap-2 px-2 py-1 small text-muted align-items-center">
|
||||
<i className="icon icon-friendlist-warning flex-shrink-0" />
|
||||
<div>{ chat.message }</div>
|
||||
</div> }
|
||||
</div>
|
||||
<NitroLayoutBase className="bg-light rounded mb-2 d-flex gap-2 px-2 py-1 small text-muted align-items-center">
|
||||
<NitroLayoutBase className="nitro-friends-spritesheet icon-warning flex-shrink-0" />
|
||||
<NitroLayoutBase>{ chat.message }</NitroLayoutBase>
|
||||
</NitroLayoutBase> }
|
||||
</NitroLayoutBase>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
@ -31,18 +33,18 @@ export const FriendsMessengerThreadGroup: FC<FriendsMessengerThreadGroupProps> =
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ 'd-flex gap-2 w-100 justify-content-' + (group.userId === 0 ? 'end' : 'start') }>
|
||||
<NitroLayoutFlex className={ 'w-100 justify-content-' + (group.userId === 0 ? 'end' : 'start') } gap={ 2 }>
|
||||
{ (group.userId > 0) &&
|
||||
<div className="message-avatar flex-shrink-0">
|
||||
<NitroLayoutBase className="message-avatar flex-shrink-0">
|
||||
<AvatarImageView figure={ thread.participant.figure } direction={ 2 } />
|
||||
</div> }
|
||||
<div className={ 'bg-light text-black border-radius mb-2 rounded py-1 px-2 messages-group-' + (group.userId === 0 ? 'right' : 'left') }>
|
||||
{ group.chats.map((chat, index) => <div key={ index } className="text-break">{ chat.message }</div>) }
|
||||
</div>
|
||||
</NitroLayoutBase> }
|
||||
<NitroLayoutBase className={ 'bg-light text-black border-radius mb-2 rounded py-1 px-2 messages-group-' + (group.userId === 0 ? 'right' : 'left') }>
|
||||
{ group.chats.map((chat, index) => <NitroLayoutBase key={ index } className="text-break">{ chat.message }</NitroLayoutBase>) }
|
||||
</NitroLayoutBase>
|
||||
{ (group.userId === 0) &&
|
||||
<div className="message-avatar flex-shrink-0">
|
||||
<NitroLayoutBase className="message-avatar flex-shrink-0">
|
||||
<AvatarImageView figure={ GetSessionDataManager().figure } direction={ 4 } />
|
||||
</div> }
|
||||
</div>
|
||||
</NitroLayoutBase> }
|
||||
</NitroLayoutFlex>
|
||||
);
|
||||
}
|
||||
|
@ -3,7 +3,8 @@ import { FC, KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState }
|
||||
import { AddEventLinkTracker, GetUserProfile, LocalizeText, RemoveLinkEventTracker } from '../../../../api';
|
||||
import { FriendsMessengerIconEvent } from '../../../../events';
|
||||
import { BatchUpdates, CreateMessageHook, dispatchUiEvent, SendMessageHook } from '../../../../hooks';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroLayoutFlex } from '../../../../layout';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroLayoutButton, NitroLayoutButtonGroup, NitroLayoutFlex, NitroLayoutFlexColumn } from '../../../../layout';
|
||||
import { NitroLayoutBase } from '../../../../layout/base';
|
||||
import { AvatarImageView } from '../../../shared/avatar-image/AvatarImageView';
|
||||
import { MessengerThread } from '../../common/MessengerThread';
|
||||
import { MessengerThreadChat } from '../../common/MessengerThreadChat';
|
||||
@ -232,7 +233,7 @@ export const FriendsMessengerView: FC<{}> = props =>
|
||||
if(!isVisible) return null;
|
||||
|
||||
return (
|
||||
<NitroCardView className="nitro-friends-messenger" simple={ true }>
|
||||
<NitroCardView className="nitro-friends-messenger" uniqueKey="nitro-friends-messenger" simple={ true }>
|
||||
<NitroCardHeaderView headerText={ LocalizeText('messenger.window.title', [ 'OPEN_CHAT_COUNT' ], [ visibleThreads.length.toString() ]) } onCloseClick={ event => setIsVisible(false) } />
|
||||
<NitroCardContentView>
|
||||
<NitroLayoutFlex gap={ 2 } overflow="auto">
|
||||
@ -241,8 +242,9 @@ export const FriendsMessengerView: FC<{}> = props =>
|
||||
const messageThreadIndex = messageThreads.indexOf(thread);
|
||||
|
||||
return (
|
||||
<div key={ index } className="friend-head rounded flex-shrink-0 cursor-pointer bg-muted" onClick={ event => setActiveThreadIndex(messageThreadIndex) }>
|
||||
{ thread.unread && <i className="icon icon-friendlist-new-message" /> }
|
||||
<div key={ index } className="position-relative friend-head rounded flex-shrink-0 cursor-pointer bg-muted" onClick={ event => setActiveThreadIndex(messageThreadIndex) }>
|
||||
{ thread.unread &&
|
||||
<NitroLayoutBase className="position-absolute nitro-friends-spritesheet icon-new-message top-1 end-1 z-index-1" /> }
|
||||
<AvatarImageView figure={ thread.participant.figure } headOnly={ true } direction={ 3 } />
|
||||
</div>
|
||||
);
|
||||
@ -250,32 +252,38 @@ export const FriendsMessengerView: FC<{}> = props =>
|
||||
</NitroLayoutFlex>
|
||||
<NitroLayoutFlex className="align-items-center my-1" position="relative">
|
||||
{ (activeThreadIndex >= 0) &&
|
||||
<div className="text-black bg-light pe-2 flex-none">
|
||||
<NitroLayoutBase className="text-black bg-light pe-2 flex-none">
|
||||
{ LocalizeText('messenger.window.separator', [ 'FRIEND_NAME' ], [ messageThreads[activeThreadIndex].participant.name ]) }
|
||||
</div> }
|
||||
</NitroLayoutBase> }
|
||||
<hr className="bg-dark m-0 w-100" />
|
||||
</NitroLayoutFlex>
|
||||
{ (activeThreadIndex >= 0) &&
|
||||
<>
|
||||
<div className="d-flex gap-2 mb-2">
|
||||
<div className="btn-group">
|
||||
<button className="d-flex justify-content-center align-items-center btn btn-sm btn-primary" onClick={ followFriend }>
|
||||
<i className="icon icon-friendlist-follow" />
|
||||
</button>
|
||||
<button className="d-flex justify-content-center align-items-center btn btn-sm btn-primary" onClick={ openProfile }>
|
||||
<i className="icon icon-user-profile" />
|
||||
</button>
|
||||
</div>
|
||||
<button className="btn btn-sm btn-danger">{ LocalizeText('messenger.window.button.report') }</button>
|
||||
<button className="btn btn-sm btn-primary ms-auto" onClick={ event => closeThread(activeThreadIndex) }><i className="fas fa-times" /></button>
|
||||
</div>
|
||||
<div ref={ messagesBox } className="bg-muted p-2 rounded chat-messages mb-2 d-flex flex-column">
|
||||
<NitroLayoutFlex className="mb-2" gap={ 2 }>
|
||||
<NitroLayoutButtonGroup>
|
||||
<NitroLayoutButton variant="primary" size="sm" onClick={ followFriend }>
|
||||
<NitroLayoutBase className="nitro-friends-spritesheet icon-follow" />
|
||||
</NitroLayoutButton>
|
||||
<NitroLayoutButton variant="primary" size="sm" onClick={ openProfile }>
|
||||
<NitroLayoutBase className="nitro-friends-spritesheet icon-profile-sm" />
|
||||
</NitroLayoutButton>
|
||||
</NitroLayoutButtonGroup>
|
||||
<NitroLayoutButton variant="danger" size="sm" onClick={ openProfile }>
|
||||
{ LocalizeText('messenger.window.button.report') }
|
||||
</NitroLayoutButton>
|
||||
<NitroLayoutButton className="ms-auto" variant="primary" size="sm" onClick={ event => closeThread(activeThreadIndex) }>
|
||||
<NitroLayoutBase className="fas fa-times" />
|
||||
</NitroLayoutButton>
|
||||
</NitroLayoutFlex>
|
||||
<NitroLayoutFlexColumn innerRef={ messagesBox } className="bg-muted p-2 rounded chat-messages mb-2">
|
||||
<FriendsMessengerThreadView thread={ messageThreads[activeThreadIndex] } />
|
||||
</div>
|
||||
<div className="d-flex gap-2">
|
||||
</NitroLayoutFlexColumn>
|
||||
<NitroLayoutFlex gap={ 2 }>
|
||||
<input type="text" className="form-control form-control-sm" placeholder={ LocalizeText('messenger.window.input.default', [ 'FRIEND_NAME' ], [ messageThreads[activeThreadIndex].participant.name ]) } value={ messageText } onChange={ event => setMessageText(event.target.value) } onKeyDown={ onKeyDown } />
|
||||
<button className="btn btn-sm btn-success" onClick={ sendMessage }>{ LocalizeText('widgets.chatinput.say') }</button>
|
||||
</div>
|
||||
<NitroLayoutButton variant="success" size="sm" onClick={ sendMessage }>
|
||||
{ LocalizeText('widgets.chatinput.say') }
|
||||
</NitroLayoutButton>
|
||||
</NitroLayoutFlex>
|
||||
</> }
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { AcceptFriendMessageComposer, DeclineFriendMessageComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback } from 'react';
|
||||
import { SendMessageHook } from '../../../../hooks/messages/message-event';
|
||||
import { UserProfileIconView } from '../../../shared/user-profile-icon/UserProfileIconView';
|
||||
import { SendMessageHook } from '../../../../hooks';
|
||||
import { UserProfileIconView } from '../../../../layout';
|
||||
import { FriendsRequestItemViewProps } from './FriendsRequestItemView.types';
|
||||
|
||||
export const FriendsRequestItemView: FC<FriendsRequestItemViewProps> = props =>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { FC } from 'react';
|
||||
import { LocalizeText } from '../../../../../api';
|
||||
import { UserProfileIconView } from '../../../../../layout';
|
||||
import { AvatarImageView } from '../../../../shared/avatar-image/AvatarImageView';
|
||||
import { UserProfileIconView } from '../../../../shared/user-profile-icon/UserProfileIconView';
|
||||
import { HallOfFameItemViewProps } from './HallOfFameItemView.types';
|
||||
|
||||
export const HallOfFameItemView: FC<HallOfFameItemViewProps> = props =>
|
||||
|
@ -7,9 +7,8 @@ import { FloorplanEditorEvent } from '../../../../events/floorplan-editor/Floorp
|
||||
import { RoomWidgetThumbnailEvent } from '../../../../events/room-widgets/thumbnail';
|
||||
import { dispatchUiEvent } from '../../../../hooks/events';
|
||||
import { SendMessageHook } from '../../../../hooks/messages';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView, UserProfileIconView } from '../../../../layout';
|
||||
import { BadgeImageView } from '../../../shared/badge-image/BadgeImageView';
|
||||
import { UserProfileIconView } from '../../../shared/user-profile-icon/UserProfileIconView';
|
||||
import { useNavigatorContext } from '../../context/NavigatorContext';
|
||||
import { NavigatorActions } from '../../reducers/NavigatorReducer';
|
||||
import { NavigatorRoomInfoViewProps } from './NavigatorRoomInfoView.types';
|
||||
|
@ -2,7 +2,7 @@ import { RemoveAllRightsMessageComposer, RoomGiveRightsComposer, RoomTakeRightsC
|
||||
import { FC, useCallback, useMemo } from 'react';
|
||||
import { LocalizeText } from '../../../../../../api';
|
||||
import { SendMessageHook } from '../../../../../../hooks';
|
||||
import { UserProfileIconView } from '../../../../../shared/user-profile-icon/UserProfileIconView';
|
||||
import { UserProfileIconView } from '../../../../../../layout';
|
||||
import { NavigatorRoomSettingsRightsTabViewProps } from './NavigatorRoomSettingsRightsTabView.types';
|
||||
|
||||
export const NavigatorRoomSettingsRightsTabView: FC<NavigatorRoomSettingsRightsTabViewProps> = props =>
|
||||
|
@ -2,6 +2,8 @@ import { EventDispatcher, NitroRectangle, RoomGeometry, RoomVariableEnum, Vector
|
||||
import { FC, useEffect, useRef, useState } from 'react';
|
||||
import { DispatchMouseEvent, DispatchTouchEvent, DoorbellWidgetHandler, FriendRequestHandler, FurniChooserWidgetHandler, FurnitureContextMenuWidgetHandler, FurnitureCreditWidgetHandler, FurnitureCustomStackHeightWidgetHandler, FurnitureDimmerWidgetHandler, FurnitureExternalImageWidgetHandler, FurnitureMannequinWidgetHandler, FurniturePresentWidgetHandler, GetNitroInstance, GetRoomEngine, InitializeRoomInstanceRenderingCanvas, IRoomWidgetHandlerManager, RoomWidgetAvatarInfoHandler, RoomWidgetChatHandler, RoomWidgetChatInputHandler, RoomWidgetHandlerManager, RoomWidgetInfostandHandler, RoomWidgetRoomToolsHandler, RoomWidgetUpdateRoomViewEvent, UserChooserWidgetHandler } from '../../api';
|
||||
import { FurnitureYoutubeDisplayWidgetHandler } from '../../api/nitro/room/widgets/handlers/FurnitureYoutubeDisplayWidgetHandler';
|
||||
import { PollWidgetHandler } from '../../api/nitro/room/widgets/handlers/PollWidgetHandler';
|
||||
import { WordQuizWidgetHandler } from '../../api/nitro/room/widgets/handlers/WordQuizWidgetHandler';
|
||||
import { RoomContextProvider } from './context/RoomContext';
|
||||
import { RoomColorView } from './RoomColorView';
|
||||
import { RoomViewProps } from './RoomView.types';
|
||||
@ -37,6 +39,8 @@ export const RoomView: FC<RoomViewProps> = props =>
|
||||
widgetHandlerManager.registerHandler(new RoomWidgetChatHandler());
|
||||
widgetHandlerManager.registerHandler(new UserChooserWidgetHandler());
|
||||
widgetHandlerManager.registerHandler(new DoorbellWidgetHandler());
|
||||
widgetHandlerManager.registerHandler(new WordQuizWidgetHandler());
|
||||
widgetHandlerManager.registerHandler(new PollWidgetHandler());
|
||||
widgetHandlerManager.registerHandler(new FriendRequestHandler());
|
||||
|
||||
widgetHandlerManager.registerHandler(new FurniChooserWidgetHandler());
|
||||
|
@ -8,3 +8,4 @@
|
||||
@import './object-location/ObjectLocationView';
|
||||
@import './room-tools/RoomToolsWidgetView';
|
||||
@import './choosers/ChooserWidgetView';
|
||||
@import './word-quiz/WordQuizWidgetView';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { RoomEngineEvent, RoomEngineObjectEvent, RoomEngineRoomAdEvent, RoomEngineTriggerWidgetEvent, RoomEngineUseProductEvent, RoomId, RoomObjectCategory, RoomObjectOperationType, RoomObjectVariable, RoomSessionChatEvent, RoomSessionDanceEvent, RoomSessionDimmerPresetsEvent, RoomSessionDoorbellEvent, RoomSessionErrorMessageEvent, RoomSessionEvent, RoomSessionFriendRequestEvent, RoomSessionPetInfoUpdateEvent, RoomSessionPresentEvent, RoomSessionUserBadgesEvent, RoomZoomEvent } from '@nitrots/nitro-renderer';
|
||||
import { RoomEngineEvent, RoomEngineObjectEvent, RoomEngineRoomAdEvent, RoomEngineTriggerWidgetEvent, RoomEngineUseProductEvent, RoomId, RoomObjectCategory, RoomObjectOperationType, RoomObjectVariable, RoomSessionChatEvent, RoomSessionDanceEvent, RoomSessionDimmerPresetsEvent, RoomSessionDoorbellEvent, RoomSessionErrorMessageEvent, RoomSessionEvent, RoomSessionFriendRequestEvent, RoomSessionPetInfoUpdateEvent, RoomSessionPollEvent, RoomSessionPresentEvent, RoomSessionUserBadgesEvent, RoomSessionWordQuizEvent, RoomZoomEvent } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback } from 'react';
|
||||
import { CanManipulateFurniture, GetRoomEngine, GetSessionDataManager, IsFurnitureSelectionDisabled, LocalizeText, ProcessRoomObjectOperation, RoomWidgetFurniToWidgetMessage, RoomWidgetUpdateRoomEngineEvent, RoomWidgetUpdateRoomObjectEvent } from '../../../api';
|
||||
import { useRoomEngineEvent, useRoomSessionManagerEvent } from '../../../hooks/events';
|
||||
@ -15,6 +15,7 @@ import { FurnitureWidgetsView } from './furniture/FurnitureWidgetsView';
|
||||
import { InfoStandWidgetView } from './infostand/InfoStandWidgetView';
|
||||
import { RoomThumbnailWidgetView } from './room-thumbnail/RoomThumbnailWidgetView';
|
||||
import { RoomToolsWidgetView } from './room-tools/RoomToolsWidgetView';
|
||||
import { WordQuizWidgetView } from './word-quiz/WordQuizWidgetView';
|
||||
|
||||
export const RoomWidgetsView: FC<{}> = props =>
|
||||
{
|
||||
@ -268,6 +269,12 @@ export const RoomWidgetsView: FC<{}> = props =>
|
||||
useRoomSessionManagerEvent(RoomSessionFriendRequestEvent.RSFRE_FRIEND_REQUEST, onRoomSessionEvent);
|
||||
useRoomSessionManagerEvent(RoomSessionPresentEvent.RSPE_PRESENT_OPENED, onRoomSessionEvent);
|
||||
useRoomSessionManagerEvent(RoomSessionPetInfoUpdateEvent.PET_INFO, onRoomSessionEvent);
|
||||
useRoomSessionManagerEvent(RoomSessionWordQuizEvent.ANSWERED, onRoomSessionEvent);
|
||||
useRoomSessionManagerEvent(RoomSessionWordQuizEvent.FINISHED, onRoomSessionEvent);
|
||||
useRoomSessionManagerEvent(RoomSessionWordQuizEvent.QUESTION, onRoomSessionEvent);
|
||||
useRoomSessionManagerEvent(RoomSessionPollEvent.OFFER, onRoomSessionEvent);
|
||||
useRoomSessionManagerEvent(RoomSessionPollEvent.ERROR, onRoomSessionEvent);
|
||||
useRoomSessionManagerEvent(RoomSessionPollEvent.CONTENT, onRoomSessionEvent);
|
||||
|
||||
const onRoomSessionErrorMessageEvent = useCallback((event: RoomSessionErrorMessageEvent) =>
|
||||
{
|
||||
@ -349,6 +356,7 @@ export const RoomWidgetsView: FC<{}> = props =>
|
||||
<RoomThumbnailWidgetView />
|
||||
<FurniChooserWidgetView />
|
||||
<UserChooserWidgetView />
|
||||
<WordQuizWidgetView />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -2,10 +2,10 @@ import { CrackableDataType, GroupInformationComposer, GroupInformationEvent, Roo
|
||||
import { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { CreateLinkEvent, GetGroupInformation, GetRoomEngine, LocalizeText, RoomWidgetFurniActionMessage } from '../../../../../../api';
|
||||
import { CreateMessageHook, SendMessageHook } from '../../../../../../hooks';
|
||||
import { UserProfileIconView } from '../../../../../../layout';
|
||||
import { BadgeImageView } from '../../../../../shared/badge-image/BadgeImageView';
|
||||
import { LimitedEditionCompactPlateView } from '../../../../../shared/limited-edition/compact-plate/LimitedEditionCompactPlateView';
|
||||
import { RarityLevelView } from '../../../../../shared/rarity-level/RarityLevelView';
|
||||
import { UserProfileIconView } from '../../../../../shared/user-profile-icon/UserProfileIconView';
|
||||
import { useRoomContext } from '../../../../context/RoomContext';
|
||||
import { InfoStandBaseView } from '../base/InfoStandBaseView';
|
||||
import { InfoStandWidgetFurniViewProps } from './InfoStandWidgetFurniView.types';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { FC } from 'react';
|
||||
import { LocalizeText } from '../../../../../../api';
|
||||
import { UserProfileIconView } from '../../../../../../layout';
|
||||
import { PetImageView } from '../../../../../shared/pet-image/PetImageView';
|
||||
import { UserProfileIconView } from '../../../../../shared/user-profile-icon/UserProfileIconView';
|
||||
import { InfoStandBaseView } from '../base/InfoStandBaseView';
|
||||
import { InfoStandWidgetPetViewProps } from './InfoStandWidgetPetView.types';
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { BotRemoveComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useMemo } from 'react';
|
||||
import { LocalizeText } from '../../../../../../api';
|
||||
import { SendMessageHook } from '../../../../../../hooks/messages';
|
||||
import { SendMessageHook } from '../../../../../../hooks';
|
||||
import { UserProfileIconView } from '../../../../../../layout';
|
||||
import { AvatarImageView } from '../../../../../shared/avatar-image/AvatarImageView';
|
||||
import { BadgeImageView } from '../../../../../shared/badge-image/BadgeImageView';
|
||||
import { UserProfileIconView } from '../../../../../shared/user-profile-icon/UserProfileIconView';
|
||||
import { BotSkillsEnum } from '../../../avatar-info/common/BotSkillsEnum';
|
||||
import { InfoStandBaseView } from '../base/InfoStandBaseView';
|
||||
import { InfoStandWidgetRentableBotViewProps } from './InfoStandWidgetRentableBotView.types';
|
||||
|
@ -4,9 +4,9 @@ import { FC, FocusEvent, KeyboardEvent, useCallback, useEffect, useState } from
|
||||
import { GetGroupInformation, LocalizeText, RoomWidgetChangeMottoMessage, RoomWidgetUpdateInfostandUserEvent } from '../../../../../../api';
|
||||
import { CreateMessageHook, SendMessageHook } from '../../../../../../hooks';
|
||||
import { CreateEventDispatcherHook } from '../../../../../../hooks/events';
|
||||
import { UserProfileIconView } from '../../../../../../layout';
|
||||
import { AvatarImageView } from '../../../../../shared/avatar-image/AvatarImageView';
|
||||
import { BadgeImageView } from '../../../../../shared/badge-image/BadgeImageView';
|
||||
import { UserProfileIconView } from '../../../../../shared/user-profile-icon/UserProfileIconView';
|
||||
import { RelationshipsContainerView } from '../../../../../user-profile/views/relationships-container/RelationshipsContainerView';
|
||||
import { useRoomContext } from '../../../../context/RoomContext';
|
||||
import { InfoStandWidgetUserViewProps } from './InfoStandWidgetUserView.types';
|
||||
|
39
src/views/room/widgets/word-quiz/WordQuizWidgetView.scss
Normal file
@ -0,0 +1,39 @@
|
||||
.wordquiz-question {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: large;
|
||||
background: rgba($dark, 0.9);
|
||||
//box-shadow: inset 0px 5px lighten(rgba($dark,.6),2.5), inset 0 -4px darken(rgba($dark,.6),4);
|
||||
border-radius: $border-radius;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
.question {
|
||||
max-width: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
.word-quiz-dislike {
|
||||
background: url("../../../../assets/images/room-widgets/wordquiz-widget/thumbs-down.png");
|
||||
width: 31px;
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
.word-quiz-like {
|
||||
background: url("../../../../assets/images/room-widgets/wordquiz-widget/thumbs-up.png");
|
||||
width: 31px;
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
.word-quiz-dislike-sm {
|
||||
background: url("../../../../assets/images/room-widgets/wordquiz-widget/thumbs-down-small.png");
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.word-quiz-like-sm {
|
||||
background: url("../../../../assets/images/room-widgets/wordquiz-widget/thumbs-up-small.png");
|
||||
height: 22px;
|
||||
width: 22px;
|
||||
}
|
167
src/views/room/widgets/word-quiz/WordQuizWidgetView.tsx
Normal file
@ -0,0 +1,167 @@
|
||||
import { IQuestion } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { RoomWidgetWordQuizUpdateEvent } from '../../../../api/nitro/room/widgets/events/RoomWidgetWordQuizUpdateEvent';
|
||||
import { RoomWidgetPollMessage } from '../../../../api/nitro/room/widgets/messages/RoomWidgetPollMessage';
|
||||
import { BatchUpdates, CreateEventDispatcherHook } from '../../../../hooks';
|
||||
import { useRoomContext } from '../../context/RoomContext';
|
||||
import { VALUE_KEY_DISLIKE, VALUE_KEY_LIKE, VoteValue } from './common/VoteValue';
|
||||
import { QuestionView } from './views/question/QuestionView';
|
||||
import { VoteView } from './views/vote/VoteView';
|
||||
|
||||
const DEFAULT_DISPLAY_DELAY = 4000;
|
||||
const SIGN_FADE_DELAY = 3;
|
||||
|
||||
export const WordQuizWidgetView: FC<{}> = props =>
|
||||
{
|
||||
const { eventDispatcher = null, widgetHandler = null, roomSession = null } = useRoomContext();
|
||||
const [pollId, setPollId] = useState(-1);
|
||||
const [question, setQuestion] = useState<IQuestion>(null);
|
||||
const [answerSent, setAnswerSent] = useState(false);
|
||||
const [questionClearTimeout, setQuestionClearTimeout] = useState<number>(null);
|
||||
const [answerCounts, setAnswerCounts] = useState<Map<string, number>>(new Map());
|
||||
const [userAnswers, setUserAnswers] = useState<Map<number, VoteValue>>(new Map());
|
||||
|
||||
const clearQuestion = useCallback(() =>
|
||||
{
|
||||
setPollId(-1);
|
||||
setQuestion(null);
|
||||
}, []);
|
||||
|
||||
const onRoomWidgetWordQuizUpdateEvent = useCallback((event: RoomWidgetWordQuizUpdateEvent) =>
|
||||
{
|
||||
switch(event.type)
|
||||
{
|
||||
case RoomWidgetWordQuizUpdateEvent.NEW_QUESTION:
|
||||
BatchUpdates(() =>
|
||||
{
|
||||
setPollId(event.id);
|
||||
setQuestion(event.question);
|
||||
setAnswerSent(false);
|
||||
setAnswerCounts(new Map());
|
||||
setUserAnswers(new Map());
|
||||
if(questionClearTimeout) clearTimeout(questionClearTimeout);
|
||||
});
|
||||
|
||||
if(event.duration > 0)
|
||||
{
|
||||
const delay = event.duration < 1000 ? DEFAULT_DISPLAY_DELAY : event.duration;
|
||||
setQuestionClearTimeout(prevValue =>
|
||||
{
|
||||
if(prevValue) clearTimeout(prevValue);
|
||||
|
||||
return setTimeout((clearQuestion as TimerHandler), delay);
|
||||
})
|
||||
}
|
||||
break;
|
||||
case RoomWidgetWordQuizUpdateEvent.QUESTION_ANSWERED:
|
||||
const userData = roomSession.userDataManager.getUserData(event.userId);
|
||||
if(!userData) return;
|
||||
|
||||
setAnswerCounts(event.answerCounts);
|
||||
|
||||
if(!userAnswers.has(userData.roomIndex))
|
||||
{
|
||||
const answersCopy = new Map(userAnswers);
|
||||
answersCopy.set(userData.roomIndex, { value: event.value, secondsLeft: SIGN_FADE_DELAY });
|
||||
setUserAnswers(answersCopy);
|
||||
}
|
||||
break;
|
||||
case RoomWidgetWordQuizUpdateEvent.QUESTION_FINISHED:
|
||||
if(question && question.id === event.questionId)
|
||||
{
|
||||
BatchUpdates(() =>
|
||||
{
|
||||
setAnswerCounts(event.answerCounts);
|
||||
setAnswerSent(true);
|
||||
setQuestionClearTimeout(prevValue =>
|
||||
{
|
||||
if(prevValue) clearTimeout(prevValue);
|
||||
|
||||
return setTimeout((clearQuestion as TimerHandler), DEFAULT_DISPLAY_DELAY);
|
||||
});
|
||||
})
|
||||
}
|
||||
setUserAnswers(new Map());
|
||||
break;
|
||||
}
|
||||
}, [clearQuestion, question, questionClearTimeout, roomSession.userDataManager, userAnswers]);
|
||||
|
||||
CreateEventDispatcherHook(RoomWidgetWordQuizUpdateEvent.NEW_QUESTION, eventDispatcher, onRoomWidgetWordQuizUpdateEvent);
|
||||
CreateEventDispatcherHook(RoomWidgetWordQuizUpdateEvent.QUESTION_ANSWERED, eventDispatcher, onRoomWidgetWordQuizUpdateEvent);
|
||||
CreateEventDispatcherHook(RoomWidgetWordQuizUpdateEvent.QUESTION_FINISHED, eventDispatcher, onRoomWidgetWordQuizUpdateEvent);
|
||||
|
||||
const vote = useCallback((vote: string) =>
|
||||
{
|
||||
if(answerSent || !question) return;
|
||||
|
||||
const updateMessage = new RoomWidgetPollMessage(RoomWidgetPollMessage.ANSWER, pollId);
|
||||
updateMessage.questionId = question.id;
|
||||
updateMessage.answers = [vote];
|
||||
widgetHandler.processWidgetMessage(updateMessage);
|
||||
setAnswerSent(true);
|
||||
|
||||
}, [answerSent, pollId, question, widgetHandler]);
|
||||
|
||||
const checkSignFade = useCallback(() =>
|
||||
{
|
||||
setUserAnswers(prev =>
|
||||
{
|
||||
const copy = new Map(prev);
|
||||
const keysToRemove: number[] = [];
|
||||
copy.forEach((value, key) =>
|
||||
{
|
||||
value.secondsLeft--;
|
||||
|
||||
if(value.secondsLeft <= 0)
|
||||
{
|
||||
keysToRemove.push(key);
|
||||
}
|
||||
});
|
||||
|
||||
keysToRemove.forEach(key => copy.delete(key));
|
||||
|
||||
return copy;
|
||||
})
|
||||
|
||||
}, []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
const interval = setInterval(() =>
|
||||
{
|
||||
checkSignFade();
|
||||
}, 1000)
|
||||
|
||||
return () =>
|
||||
{
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, [checkSignFade]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
return () =>
|
||||
{
|
||||
setQuestionClearTimeout(prev =>
|
||||
{
|
||||
if(prev) clearTimeout(prev);
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{question &&
|
||||
<QuestionView question={question.content} canVote={!answerSent} vote={vote} noVotes={answerCounts.get(VALUE_KEY_DISLIKE) || 0} yesVotes={answerCounts.get(VALUE_KEY_LIKE) || 0} />
|
||||
}
|
||||
{userAnswers &&
|
||||
Array.from(userAnswers.entries()).map(([key, value], index) =>
|
||||
{
|
||||
return <VoteView key={index} userIndex={key} vote={value.value} />
|
||||
})
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
8
src/views/room/widgets/word-quiz/common/VoteValue.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export const VALUE_KEY_DISLIKE = '0';
|
||||
export const VALUE_KEY_LIKE = '1';
|
||||
|
||||
export interface VoteValue
|
||||
{
|
||||
value: string;
|
||||
secondsLeft: number;
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import { FC } from 'react';
|
||||
import { VALUE_KEY_DISLIKE, VALUE_KEY_LIKE } from '../../common/VoteValue';
|
||||
import { QuestionViewProps } from './QuestionView.types';
|
||||
|
||||
export const QuestionView:FC<QuestionViewProps> = props =>
|
||||
{
|
||||
const { question = null, canVote = null, vote = null, noVotes = null, yesVotes = null } = props;
|
||||
|
||||
return (
|
||||
<div className="wordquiz-question p-2 d-flex flex-column gap-2">
|
||||
<div className="w-100 d-flex align-items-center gap-2">
|
||||
{ !canVote && <button className="btn btn-danger btn-sm">{noVotes}</button> }
|
||||
<div className="question text-center text-break">{question}</div>
|
||||
{ !canVote && <button className="btn btn-success btn-sm">{yesVotes}</button> }
|
||||
</div>
|
||||
{canVote &&
|
||||
<div className="w-100 d-flex justify-content-center gap-2">
|
||||
<button className="btn btn-danger btn-sm"><div className="word-quiz-dislike" onClick={ () => vote(VALUE_KEY_DISLIKE) }/></button>
|
||||
<button className="btn btn-success btn-sm"><div className="word-quiz-like" onClick={ () => vote(VALUE_KEY_LIKE) }/></button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
|
||||
export interface QuestionViewProps
|
||||
{
|
||||
question: string;
|
||||
canVote: boolean;
|
||||
vote(value: string): void;
|
||||
noVotes: number;
|
||||
yesVotes: number;
|
||||
}
|
16
src/views/room/widgets/word-quiz/views/vote/VoteView.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { RoomObjectCategory } from '@nitrots/nitro-renderer/src';
|
||||
import { FC } from 'react';
|
||||
import { ObjectLocationView } from '../../../object-location/ObjectLocationView';
|
||||
import { VALUE_KEY_DISLIKE } from '../../common/VoteValue';
|
||||
import { VoteViewProps } from './VoteView.types';
|
||||
|
||||
export const VoteView:FC<VoteViewProps> = props =>
|
||||
{
|
||||
const { userIndex = null , vote = null } = props;
|
||||
|
||||
return (
|
||||
<ObjectLocationView objectId={userIndex} category={RoomObjectCategory.UNIT}>
|
||||
<button className={`btn btn-${(vote === VALUE_KEY_DISLIKE) ? 'danger' : 'success'} btn-sm px-1`}><div className={`word-quiz-${(vote === VALUE_KEY_DISLIKE) ? 'dislike' : 'like'}-sm`} /></button>
|
||||
</ObjectLocationView>
|
||||
)
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
export interface VoteViewProps
|
||||
{
|
||||
userIndex: number;
|
||||
vote: string;
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
import { FC } from 'react';
|
||||
import { GetUserProfile } from '../../../api';
|
||||
import { UserProfileIconViewProps } from './UserProfileIconView.types';
|
||||
|
||||
export const UserProfileIconView: FC<UserProfileIconViewProps> = props =>
|
||||
{
|
||||
const { userId = 0, userName = null } = props;
|
||||
|
||||
return (<>
|
||||
<i className="icon icon-user-profile me-1 cursor-pointer" onClick={ () => GetUserProfile(userId) } />
|
||||
</>);
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
export interface UserProfileIconViewProps
|
||||
{
|
||||
userId?: number;
|
||||
userName?: string;
|
||||
}
|