diff --git a/public/configuration/UITexts_nl.json5.example b/public/configuration/UITexts_nl.json5.example index 68f003f..8bbedba 100644 --- a/public/configuration/UITexts_nl.json5.example +++ b/public/configuration/UITexts_nl.json5.example @@ -639,6 +639,8 @@ 'wheel.free.today': 'Je hebt vandaag %count% gratis draaibeurten!', 'wheel.extra': 'Extra draaibeurten: %count%', 'wheel.spin': 'DRAAIEN', + 'wheel.settings': 'Settings', + 'wheel.settings.title': 'Rad van Fortuin Settings', 'wheel.buy': 'Draaibeurt kopen', 'wheel.winners': 'Laatste winnaars', 'wheel.winners.empty': 'Nog geen winnaars', diff --git a/public/configuration/renderer-config.example b/public/configuration/renderer-config.example index 792be76..c40cdee 100644 --- a/public/configuration/renderer-config.example +++ b/public/configuration/renderer-config.example @@ -30,6 +30,9 @@ "pet.asset.url": "${asset.url}/pets/%libname%.nitro", "generic.asset.url": "${asset.url}/generic/%libname%.nitro", "badge.asset.url": "${image.library.url}album1584/%badgename%.gif", + "radio.url": "${gamedata.url}/radio-stations.json5?t=%timestamp%", + "soundboard.url": "${gamedata.url}/soundboard-sounds.json5?t=%timestamp%", + "radio_ui": false, "furni.rotation.bounce.steps": 20, "furni.rotation.bounce.height": 0.0625, "enable.avatar.arrow": false, diff --git a/src/components/MainView.tsx b/src/components/MainView.tsx index aeb5d91..e155f05 100644 --- a/src/components/MainView.tsx +++ b/src/components/MainView.tsx @@ -1,6 +1,7 @@ import { AddLinkEventTracker, GetCommunication, GetRoomSessionManager, HabboWebTools, ILinkEventTracker, RemoveLinkEventTracker, RoomSessionEvent } from '@nitrots/nitro-renderer'; import { AnimatePresence, motion } from 'framer-motion'; import { FC, useEffect, useState } from 'react'; +import { GetConfigurationValue } from '../api'; import { useNitroEventReducer } from '../hooks'; import { AchievementsView } from './achievements/AchievementsView'; import { AvatarEditorView } from './avatar-editor'; @@ -183,7 +184,7 @@ export const MainView: FC<{}> = props => - + { GetConfigurationValue('radio_ui', true) && } ); diff --git a/src/components/fortune-wheel/FortuneWheelSettingsView.tsx b/src/components/fortune-wheel/FortuneWheelSettingsView.tsx new file mode 100644 index 0000000..628eeb0 --- /dev/null +++ b/src/components/fortune-wheel/FortuneWheelSettingsView.tsx @@ -0,0 +1,147 @@ +import { IWheelAdminPrize, IWheelAdminPrizeEdit } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { LocalizeText } from '../../api'; +import { Column, Flex, Text } from '../../common'; +import { useFortuneWheel } from '../../hooks'; +import { NitroCard } from '../../layout'; + +interface EditRow +{ + id: number; + category: string; + num: number; + weight: number; + label: string; +} + +interface CategoryDef +{ + key: string; + labelKey: string; +} + +const CATEGORIES: CategoryDef[] = [ + { key: 'item', labelKey: 'rarevalues.editor.cat.item' }, + { key: 'diamonds', labelKey: 'achievements.activitypoint.5' }, + { key: 'duckets', labelKey: 'achievements.activitypoint.0' }, + { key: 'credits', labelKey: 'credits' }, + { key: 'spins', labelKey: 'rarevalues.editor.cat.spin' }, + { key: 'nothing', labelKey: 'rarevalues.editor.cat.nothing' } +]; + +const prizeToCategory = (prize: IWheelAdminPrize): string => +{ + switch(prize.type) + { + case 'item': return 'item'; + case 'points': return (prize.pointsType === 5) ? 'diamonds' : 'duckets'; + case 'credits': return 'credits'; + case 'spin': return 'spins'; + default: return 'nothing'; + } +}; + +const prizeToNum = (prize: IWheelAdminPrize): number => + (prize.type === 'item') ? (parseInt(prize.value) || 0) : prize.amount; + +const rowToEdit = (row: EditRow): IWheelAdminPrizeEdit => +{ + const base = { id: row.id, weight: row.weight, label: row.label }; + + switch(row.category) + { + case 'item': return { ...base, type: 'item', value: String(row.num), amount: 1, pointsType: 0 }; + case 'diamonds': return { ...base, type: 'points', value: '', amount: row.num, pointsType: 5 }; + case 'duckets': return { ...base, type: 'points', value: '', amount: row.num, pointsType: 0 }; + case 'credits': return { ...base, type: 'credits', value: '', amount: row.num, pointsType: 0 }; + case 'spins': return { ...base, type: 'spin', value: '', amount: row.num, pointsType: 0 }; + default: return { ...base, type: 'nothing', value: '', amount: 0, pointsType: 0 }; + } +}; + +interface FortuneWheelSettingsViewProps +{ + onClose: () => void; +} + +export const FortuneWheelSettingsView: FC = ({ onClose }) => +{ + const { adminPrizes = [], loadAdminPrizes = null, saveAdminPrizes = null } = useFortuneWheel(); + const [ editRows, setEditRows ] = useState([]); + + useEffect(() => + { + if(loadAdminPrizes) loadAdminPrizes(); + }, [ loadAdminPrizes ]); + + useEffect(() => + { + setEditRows(adminPrizes.map(prize => ({ + id: prize.id, + category: prizeToCategory(prize), + num: prizeToNum(prize), + weight: prize.weight, + label: prize.label + }))); + }, [ adminPrizes ]); + + const updateRow = (id: number, patch: Partial) => + setEditRows(prev => prev.map(row => (row.id === id) ? { ...row, ...patch } : row)); + + return ( + + + + + + { LocalizeText('rarevalues.editor.type') } + { LocalizeText('rarevalues.editor.value') } + { LocalizeText('rarevalues.editor.weight') } + { LocalizeText('rarevalues.editor.label') } + + + { editRows.map(row => ( + + + updateRow(row.id, { num: parseInt(event.target.value) || 0 }) } + className="w-16 rounded border border-black/20 bg-white px-1 py-0.5 text-sm text-[#1f2d34] disabled:opacity-40" /> + updateRow(row.id, { weight: parseInt(event.target.value) || 0 }) } + className="w-12 rounded border border-black/20 bg-white px-1 py-0.5 text-sm text-[#1f2d34]" /> + updateRow(row.id, { label: event.target.value }) } + className="min-w-0 grow rounded border border-black/20 bg-white px-1 py-0.5 text-sm text-[#1f2d34]" /> + + )) } + { !editRows.length && + { LocalizeText('wheel.settings.empty') } } + + + + + + ); +}; diff --git a/src/components/fortune-wheel/FortuneWheelView.tsx b/src/components/fortune-wheel/FortuneWheelView.tsx index a66af12..37aa94b 100644 --- a/src/components/fortune-wheel/FortuneWheelView.tsx +++ b/src/components/fortune-wheel/FortuneWheelView.tsx @@ -2,8 +2,9 @@ import { AddLinkEventTracker, GetRoomEngine, ILinkEventTracker, IWheelPrize, Rem import { FC, useEffect, useMemo, useRef, useState } from 'react'; import { LocalizeText } from '../../api'; import { Column, Flex, LayoutAvatarImageView, LayoutBadgeImageView, LayoutCurrencyIcon, LayoutImage, Text } from '../../common'; -import { useFortuneWheel } from '../../hooks'; +import { useFortuneWheel, useHasPermission } from '../../hooks'; import { NitroCard } from '../../layout'; +import { FortuneWheelSettingsView } from './FortuneWheelSettingsView'; // Stock UI palette (white / light-blue / grey / black). const SLICE_COLORS = [ '#eef2f5', '#c3dcec' ]; @@ -42,7 +43,9 @@ const renderPrizeIcon = (prize: IWheelPrize) => export const FortuneWheelView: FC<{}> = () => { const [ isVisible, setIsVisible ] = useState(false); + const [ isSettingsOpen, setIsSettingsOpen ] = useState(false); const { freeSpins, extraSpins, spinCost, spinCostType, prizes, recentWins, pendingPrizeId, isSpinning, open, spin, buySpin, finishSpin } = useFortuneWheel(); + const canManage = useHasPermission('acc_wheeladmin'); const [ rotation, setRotation ] = useState(0); const rotationRef = useRef(0); const prizesRef = useRef([]); @@ -164,6 +167,12 @@ export const FortuneWheelView: FC<{}> = () => { LocalizeText('wheel.buy') } { spinCost } + { canManage && + } @@ -186,6 +195,8 @@ export const FortuneWheelView: FC<{}> = () => + { canManage && isSettingsOpen && + setIsSettingsOpen(false) } /> } ); }; diff --git a/src/components/rare-values/RareValuesView.tsx b/src/components/rare-values/RareValuesView.tsx index 5cb4ce7..c03f787 100644 --- a/src/components/rare-values/RareValuesView.tsx +++ b/src/components/rare-values/RareValuesView.tsx @@ -1,8 +1,8 @@ -import { AddLinkEventTracker, GetRoomEngine, GetSessionDataManager, ILinkEventTracker, IRareValue, IWheelAdminPrize, IWheelAdminPrizeEdit, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; +import { AddLinkEventTracker, GetRoomEngine, GetSessionDataManager, ILinkEventTracker, IRareValue, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; import { FC, useEffect, useMemo, useState } from 'react'; import { LocalizeFormattedNumber, LocalizeText } from '../../api'; import { Column, Flex, LayoutCurrencyIcon, LayoutImage, Text } from '../../common'; -import { useFortuneWheel, useHasPermission, useRareValues } from '../../hooks'; +import { useRareValues } from '../../hooks'; import { NitroCard, NitroInput } from '../../layout'; interface RareValueRow @@ -13,63 +13,11 @@ interface RareValueRow value: IRareValue; } -interface EditRow -{ - id: number; - category: string; - num: number; - weight: number; - label: string; -} - -const CATEGORIES: { key: string; label: string }[] = [ - { key: 'item', label: 'Raro (ID)' }, - { key: 'diamanti', label: 'Diamanti' }, - { key: 'duckets', label: 'Duckets' }, - { key: 'crediti', label: 'Crediti' }, - { key: 'giri', label: 'Giri extra' }, - { key: 'nulla', label: 'Nulla' } -]; - -const prizeToCategory = (prize: IWheelAdminPrize): string => -{ - switch(prize.type) - { - case 'item': return 'item'; - case 'points': return (prize.pointsType === 5) ? 'diamanti' : 'duckets'; - case 'credits': return 'crediti'; - case 'spin': return 'giri'; - default: return 'nulla'; - } -}; - -const prizeToNum = (prize: IWheelAdminPrize): number => - (prize.type === 'item') ? (parseInt(prize.value) || 0) : prize.amount; - -const rowToEdit = (row: EditRow): IWheelAdminPrizeEdit => -{ - const base = { id: row.id, weight: row.weight, label: row.label }; - - switch(row.category) - { - case 'item': return { ...base, type: 'item', value: String(row.num), amount: 1, pointsType: 0 }; - case 'diamanti': return { ...base, type: 'points', value: '', amount: row.num, pointsType: 5 }; - case 'duckets': return { ...base, type: 'points', value: '', amount: row.num, pointsType: 0 }; - case 'crediti': return { ...base, type: 'credits', value: '', amount: row.num, pointsType: 0 }; - case 'giri': return { ...base, type: 'spin', value: '', amount: row.num, pointsType: 0 }; - default: return { ...base, type: 'nothing', value: '', amount: 0, pointsType: 0 }; - } -}; - export const RareValuesView: FC<{}> = () => { const [ isVisible, setIsVisible ] = useState(false); - const [ tab, setTab ] = useState<'values' | 'editor'>('values'); const [ searchValue, setSearchValue ] = useState(''); const { values = null, loaded = false } = useRareValues(); - const { adminPrizes = [], loadAdminPrizes = null, saveAdminPrizes = null } = useFortuneWheel(); - const canEdit = useHasPermission('acc_supporttool'); - const [ editRows, setEditRows ] = useState([]); useEffect(() => { @@ -94,16 +42,6 @@ export const RareValuesView: FC<{}> = () => return () => RemoveLinkEventTracker(linkTracker); }, []); - useEffect(() => - { - if(isVisible && (tab === 'editor') && canEdit && loadAdminPrizes) loadAdminPrizes(); - }, [ isVisible, tab, canEdit, loadAdminPrizes ]); - - useEffect(() => - { - setEditRows(adminPrizes.map(prize => ({ id: prize.id, category: prizeToCategory(prize), num: prizeToNum(prize), weight: prize.weight, label: prize.label }))); - }, [ adminPrizes ]); - const rows = useMemo(() => { if(!values) return []; @@ -143,91 +81,34 @@ export const RareValuesView: FC<{}> = () => if(!isVisible) return null; - const updateRow = (id: number, patch: Partial) => - setEditRows(prev => prev.map(row => (row.id === id) ? { ...row, ...patch } : row)); - return ( setIsVisible(false) } /> - { canEdit && - - setTab('values') }> - { LocalizeText('rarevalues.title') } - - setTab('editor') }> - { LocalizeText('rarevalues.editor.tab') } - - } - { (tab === 'values' || !canEdit) && - - setSearchValue(event.target.value) } /> - - { !loaded && - { LocalizeText('rarevalues.loading') } } - { (loaded && !filtered.length) && - { LocalizeText('rarevalues.empty') } } - { filtered.map(row => ( - - - { row.name } - - { LocalizeFormattedNumber(row.value.points) } - - + + setSearchValue(event.target.value) } /> + + { !loaded && + { LocalizeText('rarevalues.loading') } } + { (loaded && !filtered.length) && + { LocalizeText('rarevalues.empty') } } + { filtered.map(row => ( + + + { row.name } + + { LocalizeFormattedNumber(row.value.points) } + - )) } - - } - - { (tab === 'editor' && canEdit) && - - - { LocalizeText('rarevalues.editor.type') } - { LocalizeText('rarevalues.editor.value') } - { LocalizeText('rarevalues.editor.weight') } - { LocalizeText('rarevalues.editor.label') } - - - { editRows.map(row => ( - - - updateRow(row.id, { num: parseInt(event.target.value) || 0 }) } - className="w-16 rounded border border-black/20 bg-white px-1 py-0.5 text-sm text-[#1f2d34] disabled:opacity-40" /> - updateRow(row.id, { weight: parseInt(event.target.value) || 0 }) } - className="w-12 rounded border border-black/20 bg-white px-1 py-0.5 text-sm text-[#1f2d34]" /> - updateRow(row.id, { label: event.target.value }) } - className="min-w-0 grow rounded border border-black/20 bg-white px-1 py-0.5 text-sm text-[#1f2d34]" /> - - )) } - - - } + + )) } + + ); diff --git a/src/hooks/rooms/widgets/useChatCommandSelector.ts b/src/hooks/rooms/widgets/useChatCommandSelector.ts index 9584e92..31dd495 100644 --- a/src/hooks/rooms/widgets/useChatCommandSelector.ts +++ b/src/hooks/rooms/widgets/useChatCommandSelector.ts @@ -1,36 +1,35 @@ import { AvailableCommandsEvent, GetCommunication } from '@nitrots/nitro-renderer'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { CommandDefinition } from '../../../api'; +import { CommandDefinition, LocalizeText } from '../../../api'; import { createNitroStore } from '../../../state/createNitroStore'; import { useMessageEvent } from '../../events'; -// Client-only commands are static; safe to keep at module scope. -const CLIENT_COMMANDS: CommandDefinition[] = [ - // Effetti stanza - { key: 'shake', description: 'Scuoti la stanza' }, - { key: 'rotate', description: 'Ruota la stanza' }, - { key: 'zoom', description: 'Zoom stanza' }, - { key: 'flip', description: 'Reset zoom' }, - { key: 'iddqd', description: 'Reset zoom' }, - { key: 'screenshot', description: 'Screenshot stanza' }, - { key: 'togglefps', description: 'Toggle FPS' }, - // Espressioni - { key: 'd', description: 'Ridi (VIP)' }, - { key: 'kiss', description: 'Manda un bacio (VIP)' }, - { key: 'jump', description: 'Salta (VIP)' }, - { key: 'idle', description: 'Vai in idle' }, - { key: 'sign', description: 'Mostra cartello' }, - // Gestione stanza - { key: 'furni', description: 'Furni chooser' }, - { key: 'chooser', description: 'User chooser' }, - { key: 'floor', description: 'Floor editor' }, - { key: 'bcfloor', description: 'Floor editor' }, - { key: 'pickall', description: 'Raccogli tutti i furni' }, - { key: 'ejectall', description: 'Espelli tutti i furni' }, - { key: 'settings', description: 'Impostazioni stanza' }, +const CLIENT_COMMANDS: { key: string; descriptionKey: string }[] = [ + // Room effects + { key: 'shake', descriptionKey: 'chatcmd.client.shake' }, + { key: 'rotate', descriptionKey: 'chatcmd.client.rotate' }, + { key: 'zoom', descriptionKey: 'chatcmd.client.zoom' }, + { key: 'flip', descriptionKey: 'chatcmd.client.flip' }, + { key: 'iddqd', descriptionKey: 'chatcmd.client.iddqd' }, + { key: 'screenshot', descriptionKey: 'chatcmd.client.screenshot' }, + { key: 'togglefps', descriptionKey: 'chatcmd.client.togglefps' }, + // Expressions + { key: 'd', descriptionKey: 'chatcmd.client.laugh' }, + { key: 'kiss', descriptionKey: 'chatcmd.client.kiss' }, + { key: 'jump', descriptionKey: 'chatcmd.client.jump' }, + { key: 'idle', descriptionKey: 'chatcmd.client.idle' }, + { key: 'sign', descriptionKey: 'chatcmd.client.sign' }, + // Room management + { key: 'furni', descriptionKey: 'chatcmd.client.furni' }, + { key: 'chooser', descriptionKey: 'chatcmd.client.chooser' }, + { key: 'floor', descriptionKey: 'chatcmd.client.floor' }, + { key: 'bcfloor', descriptionKey: 'chatcmd.client.floor' }, + { key: 'pickall', descriptionKey: 'chatcmd.client.pickall' }, + { key: 'ejectall', descriptionKey: 'chatcmd.client.ejectall' }, + { key: 'settings', descriptionKey: 'chatcmd.client.settings' }, // Info - { key: 'client', description: 'Info client' }, - { key: 'nitro', description: 'Info client' }, + { key: 'client', descriptionKey: 'chatcmd.client.info' }, + { key: 'nitro', descriptionKey: 'chatcmd.client.info' }, ]; /** @@ -110,11 +109,12 @@ export const useChatCommandSelector = (chatValue: string) => const allCommands = useMemo(() => { - const merged = [ ...serverCommands ]; + const merged: CommandDefinition[] = [ ...serverCommands ]; for(const clientCmd of CLIENT_COMMANDS) { - if(!merged.some(cmd => cmd.key === clientCmd.key)) merged.push(clientCmd); + if(merged.some(cmd => cmd.key === clientCmd.key)) continue; + merged.push({ key: clientCmd.key, description: LocalizeText(clientCmd.descriptionKey) }); } return merged.sort((a, b) => a.key.localeCompare(b.key));