diff --git a/public/renderer-config.json b/public/renderer-config.json index c6fa1e3a..bf3a12a3 100644 --- a/public/renderer-config.json +++ b/public/renderer-config.json @@ -22,6 +22,8 @@ "generic.asset.url": "${asset.url}/bundled/generic/%libname%.nitro", "badge.asset.url": "${image.library.url}album1584/%badgename%.gif", "badge.asset.group.url": "https://cdn.ironhotel.biz/group-badge/%badgedata%.gif", + "badge.asset.group.external.url": "", + "badge.asset.grouparts.url": "https://cdn.ironhotel.biz/static_iron_active/c_images/Badgeparts/badgepart_%part%.png", "furni.rotation.bounce.steps": 20, "furni.rotation.bounce.height": 0.0625, "enable.avatar.arrow": false, diff --git a/src/api/groups/GetGroupManager.ts b/src/api/groups/GetGroupManager.ts new file mode 100644 index 00000000..bd5a6c70 --- /dev/null +++ b/src/api/groups/GetGroupManager.ts @@ -0,0 +1,6 @@ +import { CreateLinkEvent } from '..'; + +export function GetGroupManager(groupId: number): void +{ + CreateLinkEvent(`groups/manage/${groupId}`); +} diff --git a/src/api/groups/GetGroupMembers.ts b/src/api/groups/GetGroupMembers.ts new file mode 100644 index 00000000..58dbce6e --- /dev/null +++ b/src/api/groups/GetGroupMembers.ts @@ -0,0 +1,6 @@ +import { CreateLinkEvent } from '..'; + +export function GetGroupMembers(groupId: number): void +{ + CreateLinkEvent(`groups/members/${groupId}`); +} diff --git a/src/api/nitro/room/widgets/handlers/RoomWidgetInfostandHandler.ts b/src/api/nitro/room/widgets/handlers/RoomWidgetInfostandHandler.ts index 8693cb23..1ee584ce 100644 --- a/src/api/nitro/room/widgets/handlers/RoomWidgetInfostandHandler.ts +++ b/src/api/nitro/room/widgets/handlers/RoomWidgetInfostandHandler.ts @@ -522,7 +522,7 @@ export class RoomWidgetInfostandHandler extends RoomWidgetHandler } event.groupId = parseInt(userData.guildId); - //event._Str_5235 = GetSessionDataManager()._Str_17173(int(userData._Str_4592)); + event.groupBadgeId = GetSessionDataManager().getGroupBadge(event.groupId); event.groupName = userData.groupName; event.badges = this.container.roomSession.userDataManager.getUserBadges(userData.webID); event.figure = userData.figure; diff --git a/src/assets/images/groups/icons/group_favorite.png b/src/assets/images/groups/icons/group_favorite.png new file mode 100644 index 00000000..4cd734d1 Binary files /dev/null and b/src/assets/images/groups/icons/group_favorite.png differ diff --git a/src/assets/images/groups/icons/group_icon_admin.png b/src/assets/images/groups/icons/group_icon_admin.png new file mode 100644 index 00000000..bad07e4a Binary files /dev/null and b/src/assets/images/groups/icons/group_icon_admin.png differ diff --git a/src/assets/images/groups/icons/group_icon_not_admin.png b/src/assets/images/groups/icons/group_icon_not_admin.png new file mode 100644 index 00000000..7c8b9ee4 Binary files /dev/null and b/src/assets/images/groups/icons/group_icon_not_admin.png differ diff --git a/src/assets/images/groups/icons/group_icon_remove_member.png b/src/assets/images/groups/icons/group_icon_remove_member.png new file mode 100644 index 00000000..e6302cb2 Binary files /dev/null and b/src/assets/images/groups/icons/group_icon_remove_member.png differ diff --git a/src/assets/images/groups/icons/group_icon_small_owner.png b/src/assets/images/groups/icons/group_icon_small_owner.png new file mode 100644 index 00000000..7230ecc5 Binary files /dev/null and b/src/assets/images/groups/icons/group_icon_small_owner.png differ diff --git a/src/assets/images/groups/icons/group_notfavorite.png b/src/assets/images/groups/icons/group_notfavorite.png new file mode 100644 index 00000000..835d8eb7 Binary files /dev/null and b/src/assets/images/groups/icons/group_notfavorite.png differ diff --git a/src/assets/styles/bootstrap/_variables.scss b/src/assets/styles/bootstrap/_variables.scss index 51f8ef36..9533508f 100644 --- a/src/assets/styles/bootstrap/_variables.scss +++ b/src/assets/styles/bootstrap/_variables.scss @@ -390,7 +390,7 @@ $container-max-widths: ( $grid-columns: 12 !default; $grid-gutter-width: 16px !default; -$grid-row-columns: 6 !default; +$grid-row-columns: 18 !default; $gutters: $spacers !default; diff --git a/src/assets/styles/icons.scss b/src/assets/styles/icons.scss index 889f6c82..6ca7b485 100644 --- a/src/assets/styles/icons.scss +++ b/src/assets/styles/icons.scss @@ -591,6 +591,42 @@ height: 20px; } + &.icon-group-favorite { + background: url('../images/groups/icons/group_favorite.png'); + width: 14px; + height: 14px; + } + + &.icon-group-not-favorite { + background: url('../images/groups/icons/group_notfavorite.png'); + width: 14px; + height: 14px; + } + + &.icon-group-small-admin { + background: url('../images/groups/icons/group_icon_admin.png'); + width: 11px; + height: 13px; + } + + &.icon-group-small-not-admin { + background: url('../images/groups/icons/group_icon_not_admin.png'); + width: 11px; + height: 13px; + } + + &.icon-group-remove-member { + background: url('../images/groups/icons/group_icon_remove_member.png'); + width: 13px; + height: 14px; + } + + &.icon-group-small-owner { + background: url('../images/groups/icons/group_icon_small_owner.png'); + width: 13px; + height: 13px; + } + &.icon-navigator-info { background: url('../images/navigator/icons/info.png'); width: 18px; diff --git a/src/layout/card/grid/NitroCardGridView.scss b/src/layout/card/grid/NitroCardGridView.scss index a924d2ce..f5794406 100644 --- a/src/layout/card/grid/NitroCardGridView.scss +++ b/src/layout/card/grid/NitroCardGridView.scss @@ -34,5 +34,38 @@ } } + .row-cols-6 { + + .col { + padding-right: 0.25rem; + + &:nth-child(6n+6) { + padding-right: 0; + } + } + } + + .row-cols-7 { + + .col { + padding-right: 0.25rem; + + &:nth-child(7n+7) { + padding-right: 0; + } + } + } + + .row-cols-8 { + + .col { + padding-right: 0.25rem; + + &:nth-child(8n+8) { + padding-right: 0; + } + } + } + @import './item/NitroCardGridItemView.scss'; } diff --git a/src/layout/trophy/NitroLayoutTrophyView.tsx b/src/layout/trophy/NitroLayoutTrophyView.tsx index 2e4c39bd..707eb0c4 100644 --- a/src/layout/trophy/NitroLayoutTrophyView.tsx +++ b/src/layout/trophy/NitroLayoutTrophyView.tsx @@ -5,7 +5,7 @@ import { NitroLayoutTrophyViewProps } from './NitroLayoutTrophyView.types'; export const NitroLayoutTrophyView: FC = props => { - const { color = '', message = '', date = '', senderName = '', onCloseClick = null } = props; + const { color = '', message = '', date = '', senderName = '', customTitle = null, onCloseClick = null } = props; return ( @@ -17,6 +17,7 @@ export const NitroLayoutTrophyView: FC = props =>
+ { customTitle &&
{ customTitle }
} { message }
diff --git a/src/layout/trophy/NitroLayoutTrophyView.types.ts b/src/layout/trophy/NitroLayoutTrophyView.types.ts index fd196e94..410fa332 100644 --- a/src/layout/trophy/NitroLayoutTrophyView.types.ts +++ b/src/layout/trophy/NitroLayoutTrophyView.types.ts @@ -4,5 +4,6 @@ export interface NitroLayoutTrophyViewProps message: string; date: string; senderName: string; + customTitle?: string; onCloseClick: () => void; } diff --git a/src/views/groups/GroupView.scss b/src/views/groups/GroupView.scss index acfe8f77..934c8a05 100644 --- a/src/views/groups/GroupView.scss +++ b/src/views/groups/GroupView.scss @@ -1,4 +1,54 @@ +.nitro-groups { + .tab-image { + width: 122px; + height: 68px; + display: flex; + align-items: center; + justify-content: center; + + div { + background-image: url('../../assets/images/groups/creator_images.png'); + background-repeat: no-repeat; + } + + &.tab-1 { + div { + background-position: 0px 0px; + width: 99px; + height: 50px; + } + } + + &.tab-2 { + div { + background-position: -99px 0px; + width: 98px; + height: 62px; + } + } + + &.tab-3 { + div { + background-position: 0px -50px; + width: 96px; + height: 45px; + } + } + + &.tab-4, &.tab-5 { + div { + background-position: 0px -95px; + width: 114px; + height: 61px; + } + } + } +} + @import './views/creator/GroupCreatorView'; @import './views/information/GroupInformationView'; @import './views/information-standalone/GroupInformationStandaloneView'; +@import './views/manager/GroupManagerView'; +@import './views/members/GroupMembersView'; @import './views/room-information/GroupRoomInformationView'; +@import './views/shared-tabs/GroupSharedTabs'; diff --git a/src/views/groups/GroupsMessageHandler.tsx b/src/views/groups/GroupsMessageHandler.tsx index 54299d25..efa2b1bf 100644 --- a/src/views/groups/GroupsMessageHandler.tsx +++ b/src/views/groups/GroupsMessageHandler.tsx @@ -1,27 +1,141 @@ -import { GroupBadgePartsEvent } from '@nitrots/nitro-renderer'; +import { GroupBadgePartsEvent, GroupBuyDataEvent, GroupSettingsEvent, RoomCreatedEvent } from '@nitrots/nitro-renderer'; import { FC, useCallback } from 'react'; import { CreateMessageHook } from '../../hooks'; +import { GroupBadgePart } from './common/GroupBadgePart'; import { useGroupsContext } from './context/GroupsContext'; import { GroupsActions } from './context/GroupsContext.types'; +function compareId(a, b) +{ + if( a.id < b.id ) return -1; + if( a.id > b.id ) return 1; + return 0; +} + export const GroupsMessageHandler: FC<{}> = props => { const { groupsState = null, dispatchGroupsState = null } = useGroupsContext(); + const { availableRooms = null } = groupsState; + + const onGroupBuyDataEvent = useCallback((event: GroupBuyDataEvent) => + { + const parser = event.getParser(); + + const rooms: { id: number, name: string }[] = []; + + parser.availableRooms.forEach((name, id) => + { + rooms.push({ id, name }); + }); + + dispatchGroupsState({ + type: GroupsActions.SET_PURHCASE_SETTINGS, + payload: { + objectValues: rooms, + numberValues: [ parser.groupCost ] + } + }); + }, [ dispatchGroupsState ]); + + + const onRoomCreatedEvent = useCallback((event: RoomCreatedEvent) => + { + const parser = event.getParser(); + + const clonedRooms = Array.from(availableRooms); + clonedRooms.push({ + id: parser.roomId, + name: parser.roomName + }); + + dispatchGroupsState({ + type: GroupsActions.SET_PURHCASE_SETTINGS, + payload: { + objectValues: clonedRooms + } + }); + }, [ availableRooms, dispatchGroupsState ]); const onGroupBadgePartsEvent = useCallback((event: GroupBadgePartsEvent) => { const parser = event.getParser(); + const bases: { id: number, images: string[] }[] = []; + const symbols: { id: number, images: string[] }[] = []; + const partColors: { id: number, color: string }[] = []; + const colorsA: { id: number, color: string }[] = []; + const colorsB: { id: number, color: string }[] = []; + + parser.bases.forEach((images, id) => + { + bases.push({ id, images }); + }); + + parser.symbols.forEach((images, id) => + { + symbols.push({ id, images }); + }); + + parser.partColors.forEach((color, id) => + { + partColors.push({ id, color }); + }); + + parser.colorsA.forEach((color, id) => + { + colorsA.push({ id, color }); + }); + + parser.colorsB.forEach((color, id) => + { + colorsB.push({ id, color }); + }); + + bases.sort(compareId); + symbols.sort(compareId); + partColors.sort(compareId); + colorsA.sort(compareId); + colorsB.sort(compareId); + dispatchGroupsState({ - type: GroupsActions.SET_BADGE_PARTS, + type: GroupsActions.SET_GROUP_BADGE_PARTS_CONFIG, payload: { - arrayMaps: [ parser.bases, parser.symbols ], - stringMaps: [ parser.partColors, parser.colorsA, parser.colorsB ] + objectValues: [ bases, symbols, partColors, colorsA, colorsB ] } - }) + }); }, [ dispatchGroupsState ]); + const onGroupSettingsEvent = useCallback((event: GroupSettingsEvent) => + { + const parser = event.getParser(); + + const groupBadgeParts: GroupBadgePart[] = []; + + parser.badgeParts.forEach((part, id) => + { + groupBadgeParts.push(new GroupBadgePart( + part.isBase ? GroupBadgePart.BASE : part.key >= 100 ? GroupBadgePart.SYMBOL_ALT : GroupBadgePart.SYMBOL, + part.key >= 100 ? part.key - 100 : part.key, + part.color, + part.position + )); + }); + + dispatchGroupsState({ + type: GroupsActions.SET_GROUP_SETTINGS, + payload: { + stringValues: [ parser.title, parser.description ], + numberValues: [ parser.id, parser.state, parser.colorA, parser.colorB ], + boolValues: [ parser.canMembersDecorate ], + objectValues: groupBadgeParts + } + }); + }, [ dispatchGroupsState ]); + + CreateMessageHook(GroupBuyDataEvent, onGroupBuyDataEvent); + CreateMessageHook(RoomCreatedEvent, onRoomCreatedEvent); CreateMessageHook(GroupBadgePartsEvent, onGroupBadgePartsEvent); + CreateMessageHook(GroupSettingsEvent, onGroupSettingsEvent); return null; }; diff --git a/src/views/groups/GroupsView.tsx b/src/views/groups/GroupsView.tsx index bdfc443a..89300d77 100644 --- a/src/views/groups/GroupsView.tsx +++ b/src/views/groups/GroupsView.tsx @@ -1,18 +1,22 @@ -import { GroupBadgePartsComposer, ILinkEventTracker } from '@nitrots/nitro-renderer'; +import { GroupBadgePartsComposer, GroupPurchasedEvent, GroupSettingsComposer, ILinkEventTracker } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useReducer, useState } from 'react'; -import { AddEventLinkTracker, RemoveLinkEventTracker } from '../../api'; -import { SendMessageHook } from '../../hooks'; +import { AddEventLinkTracker, RemoveLinkEventTracker, TryVisitRoom } from '../../api'; +import { CreateMessageHook, SendMessageHook } from '../../hooks'; import { GroupsContextProvider } from './context/GroupsContext'; import { GroupsReducer, initialGroups } from './context/GroupsContext.types'; import { GroupsMessageHandler } from './GroupsMessageHandler'; import { GroupCreatorView } from './views/creator/GroupCreatorView'; import { GroupInformationStandaloneView } from './views/information-standalone/GroupInformationStandaloneView'; +import { GroupManagerView } from './views/manager/GroupManagerView'; +import { GroupMembersView } from './views/members/GroupMembersView'; export const GroupsView: FC<{}> = props => { const [ groupsState, dispatchGroupsState ] = useReducer(GroupsReducer, initialGroups); const [ isCreatorVisible, setIsCreatorVisible ] = useState(false); + const [ groupMembersId, setGroupMembersId ] = useState(null); + const [ groupMembersLevel, setGroupMembersLevel ] = useState(null); useEffect(() => { @@ -30,6 +34,18 @@ export const GroupsView: FC<{}> = props => case 'create': setIsCreatorVisible(true); return; + case 'manage': + if(!parts[2]) return; + + SendMessageHook(new GroupSettingsComposer(Number(parts[2]))); + return; + case 'members': + if(!parts[2]) return; + + setGroupMembersId(Number(parts[2])); + + if(parts[3]) setGroupMembersLevel(Number(parts[3])); + return; } }, []); @@ -44,12 +60,32 @@ export const GroupsView: FC<{}> = props => return () => RemoveLinkEventTracker(linkTracker); }, [ linkReceived ]); + + const onGroupPurchasedEvent = useCallback((event: GroupPurchasedEvent) => + { + const parser = event.getParser(); + + setIsCreatorVisible(false); + TryVisitRoom(parser.roomId); + }, []); + + CreateMessageHook(GroupPurchasedEvent, onGroupPurchasedEvent); + + const closeMembers = useCallback(() => + { + setGroupMembersId(null); + setGroupMembersLevel(null); + }, []); return ( +
setIsCreatorVisible(false) } /> + + +
); }; diff --git a/src/views/groups/common/GroupBadgePart.ts b/src/views/groups/common/GroupBadgePart.ts index 03338223..d6846d86 100644 --- a/src/views/groups/common/GroupBadgePart.ts +++ b/src/views/groups/common/GroupBadgePart.ts @@ -9,18 +9,23 @@ export class GroupBadgePart public color: number; public position: number; - constructor(type: string) + constructor(type: string, key?: number, color?: number, position?: number) { this.type = type; - this.key = 0; - this.color = 0; - this.position = 4; + this.key = key ? key : 0; + this.color = color ? color : 0; + this.position = position ? position : 4; } public get code(): string { if(this.key === 0) return null; - return this.type + (this.key < 10 ? '0' : '') + this.key + (this.color < 10 ? '0' : '') + this.color + (this.type === GroupBadgePart.BASE ? '' : this.position); + return GroupBadgePart.getCode(this.type, this.key, this.color, this.position); + } + + public static getCode(type: string, key: number, color: number, position: number): string + { + return (type === GroupBadgePart.BASE ? type : key >= 100 ? GroupBadgePart.SYMBOL_ALT : GroupBadgePart.SYMBOL) + (key < 10 ? '0' : '') + (type === GroupBadgePart.BASE ? key : key >= 100 ? key - 100 : key) + (color < 10 ? '0' : '') + color + position; } } diff --git a/src/views/groups/common/GroupSettings.ts b/src/views/groups/common/GroupSettings.ts deleted file mode 100644 index cf1e4ae3..00000000 --- a/src/views/groups/common/GroupSettings.ts +++ /dev/null @@ -1,172 +0,0 @@ -import { GroupBadgePart } from './GroupBadgePart'; - -export class GroupSettings -{ - private _id: number; - private _name: string; - private _description: string; - private _roomId: string; - - private _badgeParts: GroupBadgePart[]; - private _colorA: number; - private _colorB: number; - - private _state: number; - private _canMembersDecorate: boolean; - - constructor() - { - this._id = 0; - this._name = ''; - this._description = ''; - this._roomId = '0'; - - this._badgeParts = []; - this._colorA = 0; - this.colorB = 0; - - this._state = 0; - this._canMembersDecorate = false; - - this._badgeParts.push(new GroupBadgePart(GroupBadgePart.BASE)); - this._badgeParts.push(new GroupBadgePart(GroupBadgePart.SYMBOL)); - this._badgeParts.push(new GroupBadgePart(GroupBadgePart.SYMBOL)); - this._badgeParts.push(new GroupBadgePart(GroupBadgePart.SYMBOL)); - this._badgeParts.push(new GroupBadgePart(GroupBadgePart.SYMBOL)); - } - - public getBadgePart(index: number): GroupBadgePart - { - return this._badgeParts[index]; - } - - public setPartsColor(color: number): void - { - this._badgeParts.forEach((symbol) => - { - symbol.color = color; - }); - } - - public get id(): number - { - return this._id; - } - - public set id(id: number) - { - this._id = id; - } - - public get name(): string - { - return this._name; - } - - public set name(name: string) - { - this._name = name; - } - - public get description(): string - { - return this._description; - } - - public set description(description: string) - { - this._description = description; - } - - public get roomId(): string - { - return this._roomId; - } - - public set roomId(id: string) - { - this._roomId = id; - } - - public get badgeParts(): GroupBadgePart[] - { - return this._badgeParts; - } - - public set badgeParts(parts: GroupBadgePart[]) - { - this._badgeParts = parts; - } - - public get colorA(): number - { - return this._colorA; - } - - public set colorA(id: number) - { - this._colorA = id; - } - - public get colorB(): number - { - return this._colorB; - } - - public set colorB(id: number) - { - this._colorB = id; - } - - public get currentBadgeCode(): string - { - let code = ''; - - this._badgeParts.forEach((part) => - { - if(part.code) - { - code = code + part.code; - } - }); - - return code; - } - - public get currentBadgeArray(): number[] - { - const badge = []; - - this._badgeParts.forEach((part) => - { - if(part.code) - { - badge.push(part.key); - badge.push(part.color); - badge.push(part.position); - } - }); - - return badge; - } - - public get state(): number - { - return this._state; - } - - public set state(state: number) - { - this._state = state; - } - - public get canMembersDecorate(): boolean - { - return this._canMembersDecorate; - } - - public set canMembersDecorate(value: boolean) - { - this._canMembersDecorate = value; - } -} diff --git a/src/views/groups/context/GroupsContext.types.ts b/src/views/groups/context/GroupsContext.types.ts index dcef1768..ac4fa922 100644 --- a/src/views/groups/context/GroupsContext.types.ts +++ b/src/views/groups/context/GroupsContext.types.ts @@ -1,4 +1,5 @@ import { Dispatch, ProviderProps, Reducer } from 'react'; +import { GroupBadgePart } from './../common/GroupBadgePart'; export interface IGroupsContext { @@ -13,81 +14,144 @@ export interface GroupsContextProps extends ProviderProps export interface IGroupsState { - badgeBases: Map; - badgeSymbols: Map; - badgePartColors: Map; - groupColorsA: Map; - groupColorsB: Map; + availableRooms: { id: number, name: string }[]; + purchaseCost: number; + badgeBases: { id: number, images: string[] }[]; + badgeSymbols: { id: number, images: string[] }[]; + badgePartColors: { id: number, color: string }[]; + groupId: number; + groupColorsA: { id: number, color: string }[]; + groupColorsB: { id: number, color: string }[]; groupName: string; groupDescription: string; groupHomeroomId: number; + groupBadgeParts: GroupBadgePart[]; + groupColors: number[]; + groupState: number; + groupCanMembersDecorate: boolean; } export interface IGroupsAction { type: string; payload?: { - arrayMaps?: Map[]; - stringMaps?: Map[]; - stringValue?: string; - numberValue?: number; + objectValues?: any[]; + stringValues?: string[]; + numberValues?: number[]; + boolValues?: boolean[]; + badgeParts?: GroupBadgePart[]; } } export class GroupsActions { - public static SET_BADGE_PARTS: string = 'GA_SET_BADGE_PARTS'; + public static SET_PURHCASE_SETTINGS: string = 'GA_SET_PURHCASE_SETTINGS'; + public static SET_GROUP_BADGE_PARTS_CONFIG: string = 'GA_SET_GROUP_BADGE_PARTS_CONFIG'; public static SET_GROUP_NAME: string = 'GA_SET_GROUP_NAME'; public static SET_GROUP_DESCRIPTION: string = 'GA_SET_GROUP_DESCRIPTION'; public static SET_GROUP_HOMEROOM_ID: string = 'GA_SET_GROUP_HOMEROOM_ID'; + public static SET_GROUP_BADGE_PARTS: string = 'GA_SET_BADGE_PARTS'; + public static SET_GROUP_COLORS: string = 'GA_SET_GROUP_COLORS'; + public static SET_GROUP_STATE: string = 'GA_SET_GROUP_STATE'; + public static SET_GROUP_CAN_MEMBERS_DECORATE: string = 'GA_SET_GROUP_CAN_MEMBERS_DECORATE'; + public static SET_GROUP_SETTINGS: string = 'GA_SET_GROUP_SETTINGS'; public static RESET_GROUP_SETTINGS: string = 'GA_RESET_GROUP_SETTINGS'; } export const initialGroups: IGroupsState = { + availableRooms: null, + purchaseCost: null, badgeBases: null, badgeSymbols: null, badgePartColors: null, + groupId: null, groupColorsA: null, groupColorsB: null, groupName: '', groupDescription: '', - groupHomeroomId: 0 + groupHomeroomId: 0, + groupBadgeParts: null, + groupColors: null, + groupState: null, + groupCanMembersDecorate: null }; export const GroupsReducer: Reducer = (state, action) => { switch(action.type) { - case GroupsActions.SET_BADGE_PARTS: { - const badgeBases = (action.payload.arrayMaps[0] || state.badgeBases || null); - const badgeSymbols = (action.payload.arrayMaps[1] || state.badgeSymbols || null); - const badgePartColors = (action.payload.stringMaps[0] || state.badgePartColors || null); - const groupColorsA = (action.payload.stringMaps[1] || state.groupColorsA || null); - const groupColorsB = (action.payload.stringMaps[2] || state.groupColorsB || null); + case GroupsActions.SET_PURHCASE_SETTINGS: { + const availableRooms = action.payload.objectValues; + const purchaseCost = (action.payload.numberValues[0] || state.purchaseCost || 0); + + return { ...state, availableRooms, purchaseCost }; + } + case GroupsActions.SET_GROUP_BADGE_PARTS_CONFIG: { + const badgeBases = (action.payload.objectValues[0] || state.badgeBases || null); + const badgeSymbols = (action.payload.objectValues[1] || state.badgeSymbols || null); + const badgePartColors = (action.payload.objectValues[2] || state.badgePartColors || null); + const groupColorsA = (action.payload.objectValues[3] || state.groupColorsA || null); + const groupColorsB = (action.payload.objectValues[4] || state.groupColorsB || null); return { ...state, badgeBases, badgeSymbols, badgePartColors, groupColorsA, groupColorsB }; } case GroupsActions.SET_GROUP_NAME: { - const groupName = action.payload.stringValue; + const groupName = action.payload.stringValues[0]; return { ...state, groupName }; } case GroupsActions.SET_GROUP_DESCRIPTION: { - const groupDescription = action.payload.stringValue; + const groupDescription = action.payload.stringValues[0]; return { ...state, groupDescription }; } case GroupsActions.SET_GROUP_HOMEROOM_ID: { - const groupHomeroomId = action.payload.numberValue; + const groupHomeroomId = action.payload.numberValues[0]; return { ...state, groupHomeroomId }; } + case GroupsActions.SET_GROUP_BADGE_PARTS: { + const groupBadgeParts = action.payload.badgeParts; + + return { ...state, groupBadgeParts }; + } + case GroupsActions.SET_GROUP_COLORS: { + const groupColors = action.payload.objectValues; + + return { ...state, groupColors }; + } + case GroupsActions.SET_GROUP_STATE: { + const groupState = action.payload.numberValues[0]; + + return { ...state, groupState }; + } + case GroupsActions.SET_GROUP_CAN_MEMBERS_DECORATE: { + const groupCanMembersDecorate = action.payload.boolValues[0]; + + return { ...state, groupCanMembersDecorate }; + } + case GroupsActions.SET_GROUP_SETTINGS: { + const groupId = action.payload.numberValues[0]; + const groupName = action.payload.stringValues[0]; + const groupDescription = action.payload.stringValues[1]; + const groupBadgeParts = action.payload.objectValues; + const groupState = action.payload.numberValues[1]; + const groupColors = action.payload.numberValues.slice(2); + const groupCanMembersDecorate = action.payload.boolValues[0]; + + return { ...state, groupId, groupName, groupDescription, groupBadgeParts, groupColors, groupState, groupCanMembersDecorate }; + } case GroupsActions.RESET_GROUP_SETTINGS: { + const groupId = null; const groupName = ''; const groupDescription = ''; const groupHomeroomId = 0; - - return { ...state, groupName, groupDescription, groupHomeroomId }; + const groupBadgeParts = null; + const groupColors = null; + const groupState = null; + const groupCanMembersDecorate = null; + + return { ...state, groupId, groupName, groupDescription, groupHomeroomId, groupBadgeParts, groupColors, groupState, groupCanMembersDecorate }; } default: return state; diff --git a/src/views/groups/views/creator/GroupCreatorView.scss b/src/views/groups/views/creator/GroupCreatorView.scss index e1c376ab..8cecc536 100644 --- a/src/views/groups/views/creator/GroupCreatorView.scss +++ b/src/views/groups/views/creator/GroupCreatorView.scss @@ -68,48 +68,9 @@ } } - .tab-image { - width: 122px; - height: 68px; - display: flex; - align-items: center; - justify-content: center; - - div { - background-image: url('../../../../assets/images/groups/creator_images.png'); - background-repeat: no-repeat; - } - - &.tab-1 { - div { - background-position: 0px 0px; - width: 99px; - height: 50px; - } - } - - &.tab-2 { - div { - background-position: -99px 0px; - width: 98px; - height: 62px; - } - } - - &.tab-3 { - div { - background-position: 0px -50px; - width: 96px; - height: 45px; - } - } - - &.tab-4 { - div { - background-position: 0px -95px; - width: 114px; - height: 61px; - } - } + .creator-tab { + height: 230px; + min-height: 230px; + max-height: 230px; } } diff --git a/src/views/groups/views/creator/GroupCreatorView.tsx b/src/views/groups/views/creator/GroupCreatorView.tsx index a62a3969..958b6138 100644 --- a/src/views/groups/views/creator/GroupCreatorView.tsx +++ b/src/views/groups/views/creator/GroupCreatorView.tsx @@ -1,27 +1,28 @@ -import { GroupBuyDataComposer, GroupBuyDataEvent } from '@nitrots/nitro-renderer'; +import { GroupBuyComposer, GroupBuyDataComposer } from '@nitrots/nitro-renderer'; import classNames from 'classnames'; import { FC, useCallback, useEffect, useState } from 'react'; import { Button } from 'react-bootstrap'; -import { LocalizeText } from '../../../../api'; -import { CreateMessageHook, SendMessageHook } from '../../../../hooks'; +import { HasHabboClub, LocalizeText } from '../../../../api'; +import { SendMessageHook } from '../../../../hooks'; import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout'; import { useGroupsContext } from '../../context/GroupsContext'; import { GroupsActions } from '../../context/GroupsContext.types'; +import { GroupSharedTabBadgeView } from '../shared-tabs/tab-badge/GroupSharedTabBadgeView'; +import { GroupSharedTabColorsView } from '../shared-tabs/tab-colors/GroupSharedTabColorsView'; +import { GroupSharedTabIdentityView } from '../shared-tabs/tab-identity/GroupSharedTabIdentityView'; import { GroupCreatorViewProps } from './GroupCreatorView.types'; -import { GroupCreatorTabIdentityView } from './views/creator-tab-identity/GroupCreatorTabIdentityView'; +import { GroupCreatorTabConfirmationView } from './tab-confirmation/GroupCreatorTabConfirmationView'; const TABS: number[] = [1, 2, 3, 4]; export const GroupCreatorView: FC = props => { const { groupsState = null, dispatchGroupsState = null } = useGroupsContext(); - const { groupName = null, groupHomeroomId = null } = groupsState; + const { groupName = null, groupDescription = null, groupHomeroomId = null, groupColors = null, groupBadgeParts = null } = groupsState; const { isVisible = false, onClose = null } = props; const [ currentTab, setCurrentTab ] = useState(1); - const [ availableRooms, setAvailableRooms ] = useState>(null); - const [ cost, setCost ] = useState(0); useEffect(() => { @@ -38,15 +39,24 @@ export const GroupCreatorView: FC = props => } }, [ isVisible ]); - const onGroupBuyDataEvent = useCallback((event: GroupBuyDataEvent) => + const buyGroup = useCallback(() => { - const parser = event.getParser(); + const badge = []; - setAvailableRooms(parser.availableRooms); - setCost(parser.groupCost); - }, []); + if(!groupBadgeParts) return; - CreateMessageHook(GroupBuyDataEvent, onGroupBuyDataEvent); + groupBadgeParts.forEach((part) => + { + if(part.code) + { + badge.push(part.key); + badge.push(part.color); + badge.push(part.position); + } + }); + + SendMessageHook(new GroupBuyComposer(groupName, groupDescription, groupHomeroomId, groupColors[0], groupColors[1], badge)); + }, [ groupName, groupDescription, groupHomeroomId, groupColors, groupBadgeParts ]); const previousStep = useCallback(() => { @@ -67,6 +77,10 @@ export const GroupCreatorView: FC = props => } break; } + case 4: { + buyGroup(); + break; + } } setCurrentTab(value => @@ -98,12 +112,15 @@ export const GroupCreatorView: FC = props =>
{ LocalizeText('group.create.stepdesc.' + currentTab) }
-
- { currentTab === 1 && } +
+ { currentTab === 1 && } + { currentTab === 2 && } + { currentTab === 3 && } + { currentTab === 4 && }
- +
diff --git a/src/views/groups/views/creator/tab-confirmation/GroupCreatorTabConfirmationView.tsx b/src/views/groups/views/creator/tab-confirmation/GroupCreatorTabConfirmationView.tsx new file mode 100644 index 00000000..d0ef989f --- /dev/null +++ b/src/views/groups/views/creator/tab-confirmation/GroupCreatorTabConfirmationView.tsx @@ -0,0 +1,55 @@ +import { FC, useCallback } from 'react'; +import { LocalizeText } from '../../../../../api'; +import { BadgeImageView } from '../../../../shared/badge-image/BadgeImageView'; +import { useGroupsContext } from '../../../context/GroupsContext'; + +export const GroupCreatorTabConfirmationView: FC<{}> = props => +{ + const { groupsState = null, dispatchGroupsState = null } = useGroupsContext(); + const { groupName = '', groupDescription = '', groupBadgeParts = null, groupColors = null, groupColorsA = null, groupColorsB = null, purchaseCost = 0 } = groupsState; + + const getCompleteBadgeCode = useCallback(() => + { + let code = ''; + + if(!groupBadgeParts) return code; + + groupBadgeParts.forEach((badgePart) => + { + if(badgePart.code) code = code + badgePart.code; + }); + + return code; + }, [ groupBadgeParts ]); + + const getGroupColor = useCallback((colorIndex: number) => + { + if(colorIndex === 0) return groupColorsA.find(c => c.id === groupColors[colorIndex]).color; + + return groupColorsB.find(c => c.id === groupColors[colorIndex]).color; + }, [ groupColors, groupColorsA, groupColorsB ]); + + return ( +
+
+
{ LocalizeText('group.create.confirm.guildbadge') }
+
+ +
+
+
{ LocalizeText('group.edit.color.guild.color') }
+ { groupColors &&
+
+
+
} +
+
+
+
{ groupName }
+
{ groupDescription }
+
+
{ LocalizeText('group.create.confirm.buyinfo', ['amount'], [purchaseCost.toString()]) }
+
+
+ ); +}; diff --git a/src/views/groups/views/creator/views/creator-tab-identity/GroupCreatorTabIdentityView.tsx b/src/views/groups/views/creator/views/creator-tab-identity/GroupCreatorTabIdentityView.tsx deleted file mode 100644 index b19421b6..00000000 --- a/src/views/groups/views/creator/views/creator-tab-identity/GroupCreatorTabIdentityView.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { FC, useCallback } from 'react'; -import { CreateLinkEvent, LocalizeText } from '../../../../../../api'; -import { useGroupsContext } from '../../../../context/GroupsContext'; -import { GroupsActions } from '../../../../context/GroupsContext.types'; -import { GroupCreatorTabIdentityViewProps } from './GroupCreatorTabIdentityView.types'; - -export const GroupCreatorTabIdentityView: FC = props => -{ - const { groupsState = null, dispatchGroupsState = null } = useGroupsContext(); - const { groupName = '', groupDescription = '', groupHomeroomId = 0 } = groupsState; - - const { availableRooms = null } = props; - - const setName = useCallback((name: string) => - { - dispatchGroupsState({ - type: GroupsActions.SET_GROUP_NAME, - payload: { - stringValue: name - } - }) - }, [ dispatchGroupsState ]); - - const setDescription = useCallback((description: string) => - { - dispatchGroupsState({ - type: GroupsActions.SET_GROUP_DESCRIPTION, - payload: { - stringValue: description - } - }) - }, [ dispatchGroupsState ]); - - const setHomeroomId = useCallback((id: number) => - { - dispatchGroupsState({ - type: GroupsActions.SET_GROUP_HOMEROOM_ID, - payload: { - numberValue: id - } - }) - }, [ dispatchGroupsState ]); - - return (<> -
- - setName(e.target.value) } /> -
-
- - setDescription(e.target.value) } /> -
-
- - -
-
{ LocalizeText('group.edit.base.warning') }
-
CreateLinkEvent('navigator/create') }>{ LocalizeText('group.createroom') }
- ); -}; diff --git a/src/views/groups/views/creator/views/creator-tab-identity/GroupCreatorTabIdentityView.types.ts b/src/views/groups/views/creator/views/creator-tab-identity/GroupCreatorTabIdentityView.types.ts deleted file mode 100644 index f53468af..00000000 --- a/src/views/groups/views/creator/views/creator-tab-identity/GroupCreatorTabIdentityView.types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface GroupCreatorTabIdentityViewProps -{ - availableRooms: Map; -} diff --git a/src/views/groups/views/information/GroupInformationView.tsx b/src/views/groups/views/information/GroupInformationView.tsx index 8f13cf28..99e32161 100644 --- a/src/views/groups/views/information/GroupInformationView.tsx +++ b/src/views/groups/views/information/GroupInformationView.tsx @@ -1,6 +1,8 @@ -import { GroupDeleteComposer, GroupRemoveMemberComposer } from '@nitrots/nitro-renderer'; +import { GroupRemoveMemberComposer } from '@nitrots/nitro-renderer'; import { FC, useCallback } from 'react'; import { CreateLinkEvent, GetSessionDataManager, LocalizeText, TryVisitRoom } from '../../../../api'; +import { GetGroupManager } from '../../../../api/groups/GetGroupManager'; +import { GetGroupMembers } from '../../../../api/groups/GetGroupMembers'; import { TryJoinGroup } from '../../../../api/groups/TryJoinGroup'; import { SendMessageHook } from '../../../../hooks'; import { CatalogPageName } from '../../../catalog/common/CatalogPageName'; @@ -75,19 +77,18 @@ export const GroupInformationView: FC = props => { switch(action) { + case 'members': + GetGroupMembers(groupInformation.id); + break; + case 'manage': + GetGroupManager(groupInformation.id); + break; case 'homeroom': TryVisitRoom(groupInformation.roomId); break; case 'furniture': CreateLinkEvent('catalog/open/' + CatalogPageName.GUILD_CUSTOM_FURNI); break; - case 'delete': - if(window.confirm(LocalizeText('group.deleteconfirm.title') + ' - ' + LocalizeText('group.deleteconfirm.desc'))) - { - SendMessageHook(new GroupDeleteComposer(groupInformation.id)); - if(onClose) onClose(); - } - break; } }, [ groupInformation, onClose ]); @@ -96,26 +97,22 @@ export const GroupInformationView: FC = props => return (
-
- -
- - { LocalizeText('group.membercount', ['totalMembers'], [groupInformation.membersCount.toString()]) } - +
+
+
-
- { groupInformation.pendingRequestsCount > 0 && - { LocalizeText('group.pendingmembercount', ['totalMembers'], [groupInformation.pendingRequestsCount.toString()]) } - } +
handleAction('members') }> + { LocalizeText('group.membercount', ['totalMembers'], [groupInformation.membersCount.toString()]) }
-
+ { groupInformation.pendingRequestsCount > 0 &&
+ { LocalizeText('group.pendingmembercount', ['totalMembers'], [groupInformation.pendingRequestsCount.toString()]) } +
} + { groupInformation.isOwner &&
handleAction('manage') }> + { LocalizeText('group.manage') } +
} +
{ getRoleIcon() }
-
@@ -126,20 +123,16 @@ export const GroupInformationView: FC = props =>
{ LocalizeText('group.created', ['date', 'owner'], [groupInformation.createdAt, groupInformation.ownerName]) }
{ groupInformation.description }
-
- handleAction('homeroom') }> + { groupInformation.type !== GroupType.PRIVATE && + +
+
+ + + ); +}; diff --git a/src/views/groups/views/manager/tab-settings/GroupManagerTabSettingsView.tsx b/src/views/groups/views/manager/tab-settings/GroupManagerTabSettingsView.tsx new file mode 100644 index 00000000..27ea5f1c --- /dev/null +++ b/src/views/groups/views/manager/tab-settings/GroupManagerTabSettingsView.tsx @@ -0,0 +1,55 @@ +import { FC, useCallback } from 'react'; +import { LocalizeText } from '../../../../../api/utils/LocalizeText'; +import { useGroupsContext } from '../../../context/GroupsContext'; +import { GroupsActions } from '../../../context/GroupsContext.types'; + +const STATES: string[] = ['regular', 'exclusive', 'private']; + +export const GroupManagerTabSettingsView: FC<{}> = props => +{ + const { groupsState = null, dispatchGroupsState = null } = useGroupsContext(); + const { groupState = null, groupCanMembersDecorate = false } = groupsState; + + const setState = useCallback((state: number) => + { + dispatchGroupsState({ + type: GroupsActions.SET_GROUP_STATE, + payload: { + numberValues: [ state ] + } + }) + }, [ dispatchGroupsState ]); + + const toggleCanMembersDecorate = useCallback(() => + { + dispatchGroupsState({ + type: GroupsActions.SET_GROUP_CAN_MEMBERS_DECORATE, + payload: { + boolValues: [ !groupCanMembersDecorate ] + } + }) + }, [ dispatchGroupsState, groupCanMembersDecorate ]); + + return ( + <> + { STATES.map((state, index) => + { + return
+ setState(index) } /> + +
{ LocalizeText(`group.edit.settings.type.${state}.help`) }
+
+ }) } +
+
+ toggleCanMembersDecorate() } /> + +
{ LocalizeText('group.edit.settings.rights.members.help') }
+
+ + ); +}; diff --git a/src/views/groups/views/members/GroupMembersView.scss b/src/views/groups/views/members/GroupMembersView.scss new file mode 100644 index 00000000..7973c711 --- /dev/null +++ b/src/views/groups/views/members/GroupMembersView.scss @@ -0,0 +1,29 @@ +.nitro-group-members { + width: 400px; + + .group-badge { + width: 50px; + height: 50px; + } + + .members-list { + height: 300px; + + .list-member { + border: 1px solid #ced4da; + + .avatar-head { + position: relative; + overflow: hidden; + width: 40px; + height: 40px; + + .avatar-image { + position: absolute; + left: -25px; + top: -25px; + } + } + } + } +} diff --git a/src/views/groups/views/members/GroupMembersView.tsx b/src/views/groups/views/members/GroupMembersView.tsx new file mode 100644 index 00000000..92391e4a --- /dev/null +++ b/src/views/groups/views/members/GroupMembersView.tsx @@ -0,0 +1,142 @@ +import { GroupMemberParser, GroupMembersComposer, GroupMembersEvent, GroupMembersParser, GroupRank } from '@nitrots/nitro-renderer'; +import classNames from 'classnames'; +import { FC, KeyboardEvent, useCallback, useEffect, useState } from 'react'; +import { GetSessionDataManager, LocalizeText } from '../../../../api'; +import { CreateMessageHook, SendMessageHook } from '../../../../hooks'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout'; +import { AvatarImageView } from '../../../shared/avatar-image/AvatarImageView'; +import { BadgeImageView } from '../../../shared/badge-image/BadgeImageView'; +import { GroupMembersViewProps } from './GroupMembersView.types'; + +export const GroupMembersView: FC = props => +{ + const { groupId = null, levelId = null, onClose = null } = props; + + const [ pageData, setPageData ] = useState(null); + const [ searchQuery, setSearchQuery ] = useState(''); + const [ searchLevelId, setSearchLevelId ] = useState(3); + const [ totalPages, setTotalPages ] = useState(0); + + const searchMembers = useCallback((pageId: number) => + { + if(!groupId) return; + + SendMessageHook(new GroupMembersComposer(groupId, pageId, searchQuery, searchLevelId)); + }, [ groupId, searchQuery, searchLevelId ]); + + const onGroupMembersEvent = useCallback((event: GroupMembersEvent) => + { + const parser = event.getParser(); + + setPageData(parser); + setTotalPages(Math.ceil(parser.totalMembersCount / parser.pageSize)); + }, []); + + CreateMessageHook(GroupMembersEvent, onGroupMembersEvent); + + useEffect(() => + { + setPageData(null); + setSearchQuery(''); + setSearchLevelId(0); + + if(!groupId) return; + + if(levelId !== null) setSearchLevelId(levelId); + searchMembers(0); + }, [ groupId, levelId ]); + + useEffect(() => + { + searchMembers(0); + }, [ searchLevelId ]); + + const previousPage = useCallback(() => + { + searchMembers(pageData.pageIndex - 1); + }, [ searchMembers, pageData ]); + + const nextPage = useCallback(() => + { + searchMembers(pageData.pageIndex + 1); + }, [ searchMembers, pageData ]); + + const onKeyDown = useCallback((event: KeyboardEvent) => + { + if(event.key !== 'Enter') return; + + searchMembers(pageData.pageIndex); + }, [ searchMembers, pageData ]); + + const getRankDescription = useCallback((member: GroupMemberParser) => + { + if(member.rank === GroupRank.OWNER) return 'group.members.owner'; + + if(pageData.admin) + { + if(member.rank === GroupRank.ADMIN) return 'group.members.removerights'; + + if(member.rank === GroupRank.MEMBER) return 'group.members.giverights'; + } + + return ''; + }, [ pageData ]); + + if(!pageData) return null; + + return ( + + + +
+
+ +
+
+ setSearchQuery(e.target.value) } onBlur={ () => searchMembers(pageData.pageIndex) } onKeyDown={ onKeyDown } /> + +
+
+
+ { pageData.result.map((member, index) => + { + return ( +
+
+
+ +
+
+
{ member.name }
+
{ LocalizeText('group.members.since', ['date'], [member.joinedAt]) }
+
+
+
+ +
+ { member.rank !== GroupRank.OWNER && pageData.admin && member.id !== GetSessionDataManager().userId &&
+ +
} +
+
+
+ ); + }) } +
+
+
+ +
+
{ LocalizeText('group.members.pageinfo', ['amount', 'page', 'totalPages'], [pageData.totalMembersCount.toString(), (pageData.pageIndex + 1).toString(), totalPages.toString()]) }
+
+ +
+
+
+
+ ); +}; diff --git a/src/views/groups/views/members/GroupMembersView.types.ts b/src/views/groups/views/members/GroupMembersView.types.ts new file mode 100644 index 00000000..44d151f6 --- /dev/null +++ b/src/views/groups/views/members/GroupMembersView.types.ts @@ -0,0 +1,6 @@ +export interface GroupMembersViewProps +{ + groupId: number; + levelId: number; + onClose: () => void; +} diff --git a/src/views/groups/views/room-information/GroupRoomInformationView.tsx b/src/views/groups/views/room-information/GroupRoomInformationView.tsx index de94bcdb..78a5e7fe 100644 --- a/src/views/groups/views/room-information/GroupRoomInformationView.tsx +++ b/src/views/groups/views/room-information/GroupRoomInformationView.tsx @@ -1,6 +1,7 @@ import { DesktopViewEvent, GroupInformationComposer, GroupInformationEvent, GroupInformationParser, GroupJoinComposer, GroupRemoveMemberComposer, RoomInfoEvent } from '@nitrots/nitro-renderer'; import { FC, useCallback, useState } from 'react'; import { GetGroupInformation, GetSessionDataManager, LocalizeText } from '../../../../api'; +import { GetGroupManager } from '../../../../api/groups/GetGroupManager'; import { CreateMessageHook, SendMessageHook } from '../../../../hooks'; import { BadgeImageView } from '../../../shared/badge-image/BadgeImageView'; import { GroupMembershipType } from '../../common/GroupMembershipType'; @@ -67,8 +68,6 @@ export const GroupRoomInformationView: FC<{}> = props => SendMessageHook(new GroupRemoveMemberComposer(groupInformation.id, GetSessionDataManager().userId)); SendMessageHook(new GroupInformationComposer(groupInformation.id, false)); }, [ groupInformation ]); - - const manageGroup = useCallback(() => {}, []); const getButtonText = useCallback(() => { @@ -90,14 +89,14 @@ export const GroupRoomInformationView: FC<{}> = props => const handleButtonClick = useCallback(() => { - if(isRealOwner()) return manageGroup(); + if(isRealOwner()) return GetGroupManager(groupInformation.id); if(groupInformation.type === GroupType.PRIVATE && groupInformation.membershipType === GroupMembershipType.NOT_MEMBER) return; if(groupInformation.membershipType === GroupMembershipType.MEMBER) return tryLeaveGroup(); return tryJoinGroup(); - }, [ groupInformation, tryLeaveGroup, tryJoinGroup, isRealOwner, manageGroup ]); + }, [ groupInformation, tryLeaveGroup, tryJoinGroup, isRealOwner ]); if(!groupInformation) return null; diff --git a/src/views/groups/views/shared-tabs/GroupSharedTabs.scss b/src/views/groups/views/shared-tabs/GroupSharedTabs.scss new file mode 100644 index 00000000..6da2ee1e --- /dev/null +++ b/src/views/groups/views/shared-tabs/GroupSharedTabs.scss @@ -0,0 +1,40 @@ +.nitro-groups { + .badge-preview { + position: relative; + width: 50px; + height: 50px; + background: $white; + border-radius: $border-radius; + + &.active { + &:before { + position: absolute; + content: ' '; + width: 0; + height: 0; + border-top: 10px solid white; + border-left: 10px solid transparent; + border-right: 10px solid transparent; + bottom: -10px; + } + } + } + + .group-color-swatch { + width: 30px; + height: 40px; + + &:first-child { + border-top-left-radius: $border-radius; + border-bottom-left-radius: $border-radius; + } + + &:last-child { + border-top-right-radius: $border-radius; + border-bottom-right-radius: $border-radius; + } + } +} + +@import './tab-badge/GroupSharedTabBadgeView'; +@import './tab-colors/GroupSharedTabColorsView'; diff --git a/src/views/groups/views/shared-tabs/tab-badge/GroupSharedTabBadgeView.scss b/src/views/groups/views/shared-tabs/tab-badge/GroupSharedTabBadgeView.scss new file mode 100644 index 00000000..1ad61e27 --- /dev/null +++ b/src/views/groups/views/shared-tabs/tab-badge/GroupSharedTabBadgeView.scss @@ -0,0 +1,26 @@ +.shared-tab-badge { + .position-swatch { + position: relative; + border-radius: $border-radius; + width: 16px; + height: 12px; + background: $white; + + &.active { + background: $primary; + } + } + + .color-swatch { + position: relative; + border-radius: $border-radius; + width: 16px; + height: 12px; + } + + .selection-list { + height: 160px; + min-height: 160px; + max-height: 160px; + } +} diff --git a/src/views/groups/views/shared-tabs/tab-badge/GroupSharedTabBadgeView.tsx b/src/views/groups/views/shared-tabs/tab-badge/GroupSharedTabBadgeView.tsx new file mode 100644 index 00000000..bb800f1f --- /dev/null +++ b/src/views/groups/views/shared-tabs/tab-badge/GroupSharedTabBadgeView.tsx @@ -0,0 +1,131 @@ +import classNames from 'classnames'; +import { FC, useCallback, useEffect, useState } from 'react'; +import { BadgeImageView } from '../../../../shared/badge-image/BadgeImageView'; +import { GroupBadgePart } from '../../../common/GroupBadgePart'; +import { useGroupsContext } from '../../../context/GroupsContext'; +import { GroupsActions } from '../../../context/GroupsContext.types'; +import { GroupSharedTabBadgeViewProps } from './GroupSharedTabBadgeView.types'; + +const POSITIONS: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8]; + +export const GroupSharedTabBadgeView: FC = props => +{ + const { groupsState = null, dispatchGroupsState = null } = useGroupsContext(); + const { badgeBases = null, badgeSymbols = null, badgePartColors = null, groupBadgeParts = null } = groupsState; + + const { skipDefault = null } = props; + + const [ editingIndex, setEditingIndex ] = useState(0); + const [ isSelectingModel, setIsSelectingModel ] = useState(false); + + useEffect(() => + { + if(skipDefault || !badgeBases || !badgePartColors || groupBadgeParts) return; + + const badgeParts: GroupBadgePart[] = [ + new GroupBadgePart(GroupBadgePart.BASE, badgeBases[0].id, badgePartColors[0].id), + new GroupBadgePart(GroupBadgePart.SYMBOL), + new GroupBadgePart(GroupBadgePart.SYMBOL), + new GroupBadgePart(GroupBadgePart.SYMBOL), + new GroupBadgePart(GroupBadgePart.SYMBOL) + ]; + + dispatchGroupsState({ + type: GroupsActions.SET_GROUP_BADGE_PARTS, + payload: { badgeParts } + }); + + }, [ badgeBases, badgePartColors, groupBadgeParts ]); + + const selectPartProperty = useCallback((property: string, key: number) => + { + const clonedBadgeParts = Array.from(groupBadgeParts); + + clonedBadgeParts[editingIndex][property] = key; + + dispatchGroupsState({ + type: GroupsActions.SET_GROUP_BADGE_PARTS, + payload: { + badgeParts: clonedBadgeParts + } + }); + + if(property === 'key') setIsSelectingModel(false); + }, [ editingIndex, groupBadgeParts, dispatchGroupsState ]); + + const getCompleteBadgeCode = useCallback(() => + { + let code = ''; + + if(!groupBadgeParts) return code; + + groupBadgeParts.forEach((badgePart) => + { + if(badgePart.code) code = code + badgePart.code; + }); + + return code; + }, [ groupBadgeParts ]); + + const getCurrentPart = useCallback((property: string) => + { + return groupBadgeParts[editingIndex][property]; + }, [ groupBadgeParts, editingIndex ]); + + return ( +
+
+
+ +
+
+
+ { groupBadgeParts && groupBadgeParts.map((badgePart, partIndex) => + { + return ( +
setEditingIndex(partIndex) }> + { badgePart.code && } + { !badgePart.code && } +
+ ) + }) } +
+ { !isSelectingModel && groupBadgeParts &&
+
setIsSelectingModel(true) }> + { getCurrentPart('code') && } + { !getCurrentPart('code') && } +
+
+
+ { POSITIONS.map((position) => + { + return
selectPartProperty('position', position) }>
+ }) } +
+
+
+
+ { badgePartColors && badgePartColors.map((item, index) => + { + return
selectPartProperty('color', item.id) }>
+ }) } +
+
+
} + { isSelectingModel &&
+ { groupBadgeParts[editingIndex].type !== GroupBadgePart.BASE && <> +
selectPartProperty('key', 0) }> + +
+ } + { (groupBadgeParts[editingIndex].type === GroupBadgePart.BASE ? badgeBases : badgeSymbols).map((item, index) => + { + return
selectPartProperty('key', item.id) }> + +
+ }) } +
} +
+
+
); +}; diff --git a/src/views/groups/views/shared-tabs/tab-badge/GroupSharedTabBadgeView.types.ts b/src/views/groups/views/shared-tabs/tab-badge/GroupSharedTabBadgeView.types.ts new file mode 100644 index 00000000..75d04aab --- /dev/null +++ b/src/views/groups/views/shared-tabs/tab-badge/GroupSharedTabBadgeView.types.ts @@ -0,0 +1,4 @@ +export interface GroupSharedTabBadgeViewProps +{ + skipDefault?: boolean; +} diff --git a/src/views/groups/views/shared-tabs/tab-colors/GroupSharedTabColorsView.scss b/src/views/groups/views/shared-tabs/tab-colors/GroupSharedTabColorsView.scss new file mode 100644 index 00000000..06045fff --- /dev/null +++ b/src/views/groups/views/shared-tabs/tab-colors/GroupSharedTabColorsView.scss @@ -0,0 +1,9 @@ +.shared-tab-colors { + + .color-swatch { + position: relative; + border-radius: $border-radius; + width: 15px; + height: 12px; + } +} diff --git a/src/views/groups/views/shared-tabs/tab-colors/GroupSharedTabColorsView.tsx b/src/views/groups/views/shared-tabs/tab-colors/GroupSharedTabColorsView.tsx new file mode 100644 index 00000000..570299ba --- /dev/null +++ b/src/views/groups/views/shared-tabs/tab-colors/GroupSharedTabColorsView.tsx @@ -0,0 +1,87 @@ +import { FC, useCallback, useEffect, useState } from 'react'; +import { LocalizeText } from '../../../../../api'; +import { useGroupsContext } from '../../../context/GroupsContext'; +import { GroupsActions } from '../../../context/GroupsContext.types'; + +export const GroupSharedTabColorsView: FC<{}> = props => +{ + const { groupsState = null, dispatchGroupsState = null } = useGroupsContext(); + const { groupColors = null, groupColorsA = null, groupColorsB = null } = groupsState; + + const [ selectingColorIndex, setSelectingColorIndex ] = useState(0); + + useEffect(() => + { + if(!groupColorsA || !groupColorsB || groupColors) return; + + const colors: number[] = [ + groupColorsA[0].id, + groupColorsB[0].id + ]; + + dispatchGroupsState({ + type: GroupsActions.SET_GROUP_COLORS, + payload: { + objectValues: colors + } + }); + + }, [ groupColors, groupColorsA, groupColorsB ]); + + const getGroupColor = useCallback((colorIndex: number) => + { + if(colorIndex === 0) return groupColorsA.find(c => c.id === groupColors[colorIndex]).color; + + return groupColorsB.find(c => c.id === groupColors[colorIndex]).color; + }, [ groupColors, groupColorsA, groupColorsB ]); + + const selectColor = useCallback((colorId: number) => + { + const clonedGroupColors = Array.from(groupColors); + + clonedGroupColors[selectingColorIndex] = colorId; + + dispatchGroupsState({ + type: GroupsActions.SET_GROUP_COLORS, + payload: { + objectValues: clonedGroupColors + } + }); + }, [ selectingColorIndex, groupColors, dispatchGroupsState ]); + + return ( +
+
+
{ LocalizeText('group.edit.color.guild.color') }
+ { groupColors &&
+
+
+
} +
+ { selectingColorIndex === 0 &&
+
{ LocalizeText('group.edit.color.primary.color') }
+
+
+ { groupColorsA && groupColorsA.map((item, index) => + { + return
selectColor(item.id) }>
+ }) } +
+
setSelectingColorIndex(1) } />
+
+
} + { selectingColorIndex === 1 &&
+
{ LocalizeText('group.edit.color.secondary.color') }
+
+
setSelectingColorIndex(0) } />
+
+ { groupColorsB && groupColorsB.map((item, index) => + { + return
selectColor(item.id) }>
+ }) } +
+
+
} +
+ ); +}; diff --git a/src/views/groups/views/shared-tabs/tab-identity/GroupSharedTabIdentityView.tsx b/src/views/groups/views/shared-tabs/tab-identity/GroupSharedTabIdentityView.tsx new file mode 100644 index 00000000..78e4303a --- /dev/null +++ b/src/views/groups/views/shared-tabs/tab-identity/GroupSharedTabIdentityView.tsx @@ -0,0 +1,68 @@ +import { FC, useCallback } from 'react'; +import { CreateLinkEvent, LocalizeText } from '../../../../../api'; +import { useGroupsContext } from '../../../context/GroupsContext'; +import { GroupsActions } from '../../../context/GroupsContext.types'; +import { GroupSharedTabIdentityViewProps } from './GroupSharedTabIdentityView.types'; + +export const GroupSharedTabIdentityView: FC = props => +{ + const { groupsState = null, dispatchGroupsState = null } = useGroupsContext(); + const { groupName = '', groupDescription = '', groupHomeroomId = 0, availableRooms = null } = groupsState; + + const { isCreator = false } = props; + + const setName = useCallback((name: string) => + { + dispatchGroupsState({ + type: GroupsActions.SET_GROUP_NAME, + payload: { + stringValues: [ name ] + } + }) + }, [ dispatchGroupsState ]); + + const setDescription = useCallback((description: string) => + { + dispatchGroupsState({ + type: GroupsActions.SET_GROUP_DESCRIPTION, + payload: { + stringValues: [ description ] + } + }) + }, [ dispatchGroupsState ]); + + const setHomeroomId = useCallback((id: number) => + { + dispatchGroupsState({ + type: GroupsActions.SET_GROUP_HOMEROOM_ID, + payload: { + numberValues: [ id ] + } + }) + }, [ dispatchGroupsState ]); + + return (
+
+ + setName(e.target.value) } /> +
+
+ + setDescription(e.target.value) } /> +
+ { isCreator && <> +
+ + +
+
{ LocalizeText('group.edit.base.warning') }
+
CreateLinkEvent('navigator/create') }>{ LocalizeText('group.createroom') }
+ } +
); +}; diff --git a/src/views/groups/views/shared-tabs/tab-identity/GroupSharedTabIdentityView.types.ts b/src/views/groups/views/shared-tabs/tab-identity/GroupSharedTabIdentityView.types.ts new file mode 100644 index 00000000..d8648ac0 --- /dev/null +++ b/src/views/groups/views/shared-tabs/tab-identity/GroupSharedTabIdentityView.types.ts @@ -0,0 +1,4 @@ +export interface GroupSharedTabIdentityViewProps +{ + isCreator?: boolean; +} diff --git a/src/views/hotel-view/views/widgets/hall-of-fame-item/HallOfFameItemView.tsx b/src/views/hotel-view/views/widgets/hall-of-fame-item/HallOfFameItemView.tsx index a5d7d08a..431a6f6d 100644 --- a/src/views/hotel-view/views/widgets/hall-of-fame-item/HallOfFameItemView.tsx +++ b/src/views/hotel-view/views/widgets/hall-of-fame-item/HallOfFameItemView.tsx @@ -1,6 +1,7 @@ import { FC } from 'react'; import { LocalizeText } from '../../../../../api'; import { AvatarImageView } from '../../../../shared/avatar-image/AvatarImageView'; +import { UserProfileIconView } from '../../../../shared/user-profile-icon/UserProfileIconView'; import { HallOfFameItemViewProps } from './HallOfFameItemView.types'; export const HallOfFameItemView: FC = props => @@ -11,7 +12,7 @@ export const HallOfFameItemView: FC = props =>
-
{ level }. { data.userName }
+
{ level }. { data.userName }
{ LocalizeText('landing.view.competition.hof.points', [ 'points' ], [ data.currentScore.toString() ])}
diff --git a/src/views/room/widgets/furniture/FurnitureWidgetsView.tsx b/src/views/room/widgets/furniture/FurnitureWidgetsView.tsx index 02b060de..b37b0a8d 100644 --- a/src/views/room/widgets/furniture/FurnitureWidgetsView.tsx +++ b/src/views/room/widgets/furniture/FurnitureWidgetsView.tsx @@ -1,5 +1,6 @@ import { FC } from 'react'; import { FurnitureBackgroundColorView } from './background-color/FurnitureBackgroundColorView'; +import { FurnitureBadgeDisplayView } from './badge-display/FurnitureBadgeDisplayView'; import { FurnitureContextMenuView } from './context-menu/FurnitureContextMenuView'; import { FurnitureCustomStackHeightView } from './custom-stack-height/FurnitureCustomStackHeightView'; import { FurnitureDimmerView } from './dimmer/FurnitureDimmerView'; @@ -29,6 +30,7 @@ export const FurnitureWidgetsView: FC<{}> = props => +
); diff --git a/src/views/room/widgets/furniture/badge-display/FurnitureBadgeDisplayView.tsx b/src/views/room/widgets/furniture/badge-display/FurnitureBadgeDisplayView.tsx index 38359fbe..c9c5fbde 100644 --- a/src/views/room/widgets/furniture/badge-display/FurnitureBadgeDisplayView.tsx +++ b/src/views/room/widgets/furniture/badge-display/FurnitureBadgeDisplayView.tsx @@ -1,19 +1,23 @@ import { NitroEvent, RoomEngineTriggerWidgetEvent, StringDataType } from '@nitrots/nitro-renderer'; -import { FC, useCallback } from 'react'; -import { GetRoomEngine, LocalizeBadgeDescription, LocalizeBadgeName } from '../../../../../api'; +import { FC, useCallback, useState } from 'react'; +import { GetRoomEngine, LocalizeBadgeDescription, LocalizeBadgeName, RoomWidgetRoomObjectUpdateEvent } from '../../../../../api'; +import { CreateEventDispatcherHook } from '../../../../../hooks'; import { useRoomEngineEvent } from '../../../../../hooks/events/nitro/room/room-engine-event'; +import { NitroLayoutTrophyView } from '../../../../../layout'; import { useRoomContext } from '../../../context/RoomContext'; +import { FurnitureTrophyData } from '../trophy/FurnitureTrophyData'; export const FurnitureBadgeDisplayView: FC<{}> = props => { const { eventDispatcher = null, widgetHandler = null } = useRoomContext(); + const [ trophyData, setTrophyData ] = useState(null); const onNitroEvent = useCallback((event: NitroEvent) => { switch(event.type) { - case RoomEngineTriggerWidgetEvent.REQUEST_BADGE_DISPLAY_ENGRAVING: - case RoomEngineTriggerWidgetEvent.REQUEST_ACHIEVEMENT_RESOLUTION_ENGRAVING: { + case RoomEngineTriggerWidgetEvent.REQUEST_ACHIEVEMENT_RESOLUTION_ENGRAVING: + case RoomEngineTriggerWidgetEvent.REQUEST_BADGE_DISPLAY_ENGRAVING: { const widgetEvent = (event as RoomEngineTriggerWidgetEvent); const roomObject = GetRoomEngine().getRoomObject(widgetEvent.roomId, widgetEvent.objectId, widgetEvent.category); @@ -24,10 +28,23 @@ export const FurnitureBadgeDisplayView: FC<{}> = props => stringStuff.initializeFromRoomObjectModel(roomObject.model); - const bagdeName = LocalizeBadgeName(stringStuff.getValue(1)); + const badgeName = LocalizeBadgeName(stringStuff.getValue(1)); const badgeDesc = LocalizeBadgeDescription(stringStuff.getValue(1)); const date = stringStuff.getValue(2); const senderName = stringStuff.getValue(3); + + setTrophyData(new FurnitureTrophyData(widgetEvent.objectId, widgetEvent.category, '1', senderName, date, badgeDesc, badgeName)); + return; + } + case RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED: { + const widgetEvent = (event as RoomWidgetRoomObjectUpdateEvent); + + setTrophyData(prevState => + { + if(!prevState || (widgetEvent.id !== prevState.objectId) || (widgetEvent.category !== prevState.category)) return prevState; + + return null; + }); return; } } @@ -35,20 +52,19 @@ export const FurnitureBadgeDisplayView: FC<{}> = props => useRoomEngineEvent(RoomEngineTriggerWidgetEvent.REQUEST_BADGE_DISPLAY_ENGRAVING, onNitroEvent); useRoomEngineEvent(RoomEngineTriggerWidgetEvent.REQUEST_ACHIEVEMENT_RESOLUTION_ENGRAVING, onNitroEvent); + CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED, widgetHandler.eventDispatcher, onNitroEvent); const processAction = useCallback((type: string, value: string = null) => { switch(type) { case 'close': - //setTrophyData(null); + setTrophyData(null); return; } }, []); - return null; + if(!trophyData) return null; - // if(!trophyData) return null; - - // return processAction('close') } />; + return processAction('close') } />; } diff --git a/src/views/room/widgets/furniture/context-menu/FurnitureContextMenuView.tsx b/src/views/room/widgets/furniture/context-menu/FurnitureContextMenuView.tsx index 37fe9422..8b3f879a 100644 --- a/src/views/room/widgets/furniture/context-menu/FurnitureContextMenuView.tsx +++ b/src/views/room/widgets/furniture/context-menu/FurnitureContextMenuView.tsx @@ -1,7 +1,7 @@ import { ContextMenuEnum, GroupFurniContextMenuInfoMessageParser, RoomEngineTriggerWidgetEvent, RoomObjectCategory } from '@nitrots/nitro-renderer'; import { GroupFurniContextMenuInfoMessageEvent } from '@nitrots/nitro-renderer/src/nitro/communication/messages/incoming/room/furniture/GroupFurniContextMenuInfoMessageEvent'; import { FC, useCallback, useState } from 'react'; -import { GetRoomEngine, IsOwnerOfFurniture, LocalizeText, RoomWidgetFurniActionMessage, TryVisitRoom } from '../../../../../api'; +import { GetGroupInformation, GetRoomEngine, IsOwnerOfFurniture, LocalizeText, RoomWidgetFurniActionMessage, TryVisitRoom } from '../../../../../api'; import { TryJoinGroup } from '../../../../../api/groups/TryJoinGroup'; import { CreateMessageHook } from '../../../../../hooks'; import { useRoomEngineEvent } from '../../../../../hooks/events'; @@ -183,7 +183,7 @@ export const FurnitureContextMenuView: FC<{}> = props => } { (mode === GROUP_FURNITURE) && groupData && <> - + GetGroupInformation(groupData.guildId) }> { groupData.guildName } { !isGroupMember && processAction('join_group') }> diff --git a/src/views/room/widgets/furniture/trophy/FurnitureTrophyData.ts b/src/views/room/widgets/furniture/trophy/FurnitureTrophyData.ts index 714cccfb..c1411c24 100644 --- a/src/views/room/widgets/furniture/trophy/FurnitureTrophyData.ts +++ b/src/views/room/widgets/furniture/trophy/FurnitureTrophyData.ts @@ -6,5 +6,6 @@ export class FurnitureTrophyData public color: string, public ownerName: string, public date: string, - public message: string) {} + public message: string, + public customTitle?: string) {} } diff --git a/src/views/room/widgets/infostand/views/base/InfoStandBaseView.tsx b/src/views/room/widgets/infostand/views/base/InfoStandBaseView.tsx index 7e5049ab..19569bd0 100644 --- a/src/views/room/widgets/infostand/views/base/InfoStandBaseView.tsx +++ b/src/views/room/widgets/infostand/views/base/InfoStandBaseView.tsx @@ -7,7 +7,7 @@ export const InfoStandBaseView: FC = props => return (
-
+
{ headerText }
diff --git a/src/views/room/widgets/infostand/views/bot/InfoStandWidgetBotView.tsx b/src/views/room/widgets/infostand/views/bot/InfoStandWidgetBotView.tsx index af52860a..682f801c 100644 --- a/src/views/room/widgets/infostand/views/bot/InfoStandWidgetBotView.tsx +++ b/src/views/room/widgets/infostand/views/bot/InfoStandWidgetBotView.tsx @@ -26,7 +26,7 @@ export const InfoStandWidgetBotView: FC = props =>
{ (botData.badges.length > 0) && botData.badges.map(result => { - return ; + return ; }) }
diff --git a/src/views/room/widgets/infostand/views/rentable-bot/InfoStandWidgetRentableBotView.tsx b/src/views/room/widgets/infostand/views/rentable-bot/InfoStandWidgetRentableBotView.tsx index 896098aa..af4a2566 100644 --- a/src/views/room/widgets/infostand/views/rentable-bot/InfoStandWidgetRentableBotView.tsx +++ b/src/views/room/widgets/infostand/views/rentable-bot/InfoStandWidgetRentableBotView.tsx @@ -39,7 +39,7 @@ export const InfoStandWidgetRentableBotView: FC { (rentableBotData.badges.length > 0) && rentableBotData.badges.map(result => { - return ; + return ; }) }
diff --git a/src/views/room/widgets/infostand/views/user/InfoStandWidgetUserView.tsx b/src/views/room/widgets/infostand/views/user/InfoStandWidgetUserView.tsx index 92a873b4..c952eff7 100644 --- a/src/views/room/widgets/infostand/views/user/InfoStandWidgetUserView.tsx +++ b/src/views/room/widgets/infostand/views/user/InfoStandWidgetUserView.tsx @@ -1,6 +1,7 @@ import { RelationshipStatusInfoEvent, RelationshipStatusInfoMessageParser, RoomSessionUserBadgesEvent, UserRelationshipsComposer } from '@nitrots/nitro-renderer'; +import classNames from 'classnames'; import { FC, FocusEvent, KeyboardEvent, useCallback, useEffect, useState } from 'react'; -import { LocalizeText, RoomWidgetChangeMottoMessage, RoomWidgetUpdateInfostandUserEvent } from '../../../../../../api'; +import { GetGroupInformation, LocalizeText, RoomWidgetChangeMottoMessage, RoomWidgetUpdateInfostandUserEvent } from '../../../../../../api'; import { CreateMessageHook, SendMessageHook } from '../../../../../../hooks'; import { CreateEventDispatcherHook } from '../../../../../../hooks/events'; import { AvatarImageView } from '../../../../../shared/avatar-image/AvatarImageView'; @@ -82,7 +83,7 @@ export const InfoStandWidgetUserView: FC = props = return (
-
+
@@ -98,26 +99,26 @@ export const InfoStandWidgetUserView: FC = props =
- { badges[0] && } + { badges[0] && }
-
- { userData.groupId > 0 && } +
0 }) } onClick={ () => GetGroupInformation(userData.groupId) }> + { userData.groupId > 0 && }
- { badges[1] && } + { badges[1] && }
- { badges[2] && } + { badges[2] && }
- { badges[3] && } + { badges[3] && }
- { badges[4] && } + { badges[4] && }
diff --git a/src/views/shared/badge-image/BadgeImage.scss b/src/views/shared/badge-image/BadgeImage.scss index bce21ef4..d7067045 100644 --- a/src/views/shared/badge-image/BadgeImage.scss +++ b/src/views/shared/badge-image/BadgeImage.scss @@ -4,4 +4,16 @@ height: 100%; background-repeat: no-repeat; background-position: center; + + .badge-information { + display: none; + } + + &:hover { + .badge-information { + display: block; + } + } + + @import './badge-info/BadgeInformationView'; } diff --git a/src/views/shared/badge-image/BadgeImageView.tsx b/src/views/shared/badge-image/BadgeImageView.tsx index 35ca8d7d..c815f7f7 100644 --- a/src/views/shared/badge-image/BadgeImageView.tsx +++ b/src/views/shared/badge-image/BadgeImageView.tsx @@ -1,10 +1,11 @@ import { FC } from 'react'; -import { GetConfiguration } from '../../../api'; +import { GetConfiguration, LocalizeBadgeDescription, LocalizeBadgeName, LocalizeText } from '../../../api'; +import { BadgeInformationView } from './badge-info/BadgeInformationView'; import { BadgeImageViewProps } from './BadgeImageView.types'; export const BadgeImageView: FC = props => { - const { badgeCode = null, isGroup = false } = props; + const { badgeCode = null, isGroup = false, showInfo = false, customTitle = null } = props; function getBadgeUrl(): string { @@ -20,5 +21,7 @@ export const BadgeImageView: FC = props => const url = `url('${ getBadgeUrl() }')`; - return
; + return
+ { showInfo && } +
; } diff --git a/src/views/shared/badge-image/BadgeImageView.types.ts b/src/views/shared/badge-image/BadgeImageView.types.ts index 73207a60..76fba1c5 100644 --- a/src/views/shared/badge-image/BadgeImageView.types.ts +++ b/src/views/shared/badge-image/BadgeImageView.types.ts @@ -2,4 +2,6 @@ export interface BadgeImageViewProps { badgeCode: string; isGroup?: boolean; + showInfo?: boolean; + customTitle?: string; } diff --git a/src/views/shared/badge-image/badge-info/BadgeInformationView.scss b/src/views/shared/badge-image/badge-info/BadgeInformationView.scss new file mode 100644 index 00000000..0ae69892 --- /dev/null +++ b/src/views/shared/badge-image/badge-info/BadgeInformationView.scss @@ -0,0 +1,22 @@ +.badge-information { + position: absolute; + pointer-events: none; + user-select: none; + width: 210px; + border-radius: $border-radius; + background: $white; + left: -220px; + z-index: 100; + + &:before { + position: absolute; + content: ' '; + width: 0; + height: 0; + border-left: 10px solid $white; + border-bottom: 10px solid transparent; + border-top: 10px solid transparent; + top: 10px; + right: -10px; + } +} diff --git a/src/views/shared/badge-image/badge-info/BadgeInformationView.tsx b/src/views/shared/badge-image/badge-info/BadgeInformationView.tsx new file mode 100644 index 00000000..f7e7db74 --- /dev/null +++ b/src/views/shared/badge-image/badge-info/BadgeInformationView.tsx @@ -0,0 +1,14 @@ +import { FC } from 'react'; +import { BadgeInformationViewProps } from './BadgeInformationView.types'; + +export const BadgeInformationView: FC = props => +{ + const { title = null, description = null } = props; + + return ( +
+
{ title }
+
{ description }
+
+ ); +}; diff --git a/src/views/shared/badge-image/badge-info/BadgeInformationView.types.ts b/src/views/shared/badge-image/badge-info/BadgeInformationView.types.ts new file mode 100644 index 00000000..dcabc965 --- /dev/null +++ b/src/views/shared/badge-image/badge-info/BadgeInformationView.types.ts @@ -0,0 +1,5 @@ +export interface BadgeInformationViewProps +{ + title: string; + description: string; +} diff --git a/src/views/user-profile/views/groups-container/GroupsContainerView.scss b/src/views/user-profile/views/groups-container/GroupsContainerView.scss index 0389d704..c4d8b17e 100644 --- a/src/views/user-profile/views/groups-container/GroupsContainerView.scss +++ b/src/views/user-profile/views/groups-container/GroupsContainerView.scss @@ -13,5 +13,11 @@ border-color: $grid-active-border-color !important; background-color: $grid-active-bg-color !important; } + + .icon { + z-index: 1; + top: 0px; + right: 0px; + } } } diff --git a/src/views/user-profile/views/groups-container/GroupsContainerView.tsx b/src/views/user-profile/views/groups-container/GroupsContainerView.tsx index dc7ee616..50eadaa5 100644 --- a/src/views/user-profile/views/groups-container/GroupsContainerView.tsx +++ b/src/views/user-profile/views/groups-container/GroupsContainerView.tsx @@ -1,4 +1,4 @@ -import { GroupInformationComposer, GroupInformationEvent, GroupInformationParser } from '@nitrots/nitro-renderer'; +import { GroupFavoriteComposer, GroupInformationComposer, GroupInformationEvent, GroupInformationParser } from '@nitrots/nitro-renderer'; import classNames from 'classnames'; import { FC, useCallback, useEffect, useState } from 'react'; import { CreateMessageHook, SendMessageHook } from '../../../../hooks'; @@ -36,6 +36,11 @@ export const GroupsContainerView: FC = props => if(selectedGroupId) SendMessageHook(new GroupInformationComposer(selectedGroupId, false)); }, [ selectedGroupId ]); + const favoriteGroup = useCallback((groupId: number) => + { + SendMessageHook(new GroupFavoriteComposer(groupId)); + }, []); + if(!groups) return null; return ( @@ -44,7 +49,8 @@ export const GroupsContainerView: FC = props =>
{ groups.map((group, index) => { - return
setSelectedGroupId(group.id) } className={ 'profile-groups-item flex-shrink-0 d-flex align-items-center justify-content-center cursor-pointer' + classNames({ ' active': selectedGroupId === group.id }) }> + return
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 }) }> + favoriteGroup(group.id) } />
}) } diff --git a/src/views/user-profile/views/user-container/UserContainerView.tsx b/src/views/user-profile/views/user-container/UserContainerView.tsx index 8d024a39..e639b8e6 100644 --- a/src/views/user-profile/views/user-container/UserContainerView.tsx +++ b/src/views/user-profile/views/user-container/UserContainerView.tsx @@ -33,7 +33,7 @@ export const UserContainerView: FC = props =>
{userProfile.username}
-
{userProfile.motto}
+
{userProfile.motto}
{LocalizeText('extendedprofile.achievementscore')} {userProfile.achievementPoints}
diff --git a/src/views/wired/views/base/WiredBaseView.tsx b/src/views/wired/views/base/WiredBaseView.tsx index 8dd73248..919677c2 100644 --- a/src/views/wired/views/base/WiredBaseView.tsx +++ b/src/views/wired/views/base/WiredBaseView.tsx @@ -74,7 +74,7 @@ export const WiredBaseView: FC = props =>
- +
{ wiredName }
{ wiredDescription }