mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
feat(settings): gear dropdown opening focused settings sections
The purse gear now opens a dropdown (Audio / Discord / Chat / Altre / Filtro Parole). Audio/Chat/Altre open UserSettingsView focused on that section (reusing the existing volume + preference controls) with a Back button; Discord and Filtro Parole are placeholders for now.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { CreateLinkEvent } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useMemo } from 'react';
|
||||
import { FC, useCallback, useMemo, useState } from 'react';
|
||||
import { FaChartBar, FaCog, FaSignOutAlt } from 'react-icons/fa';
|
||||
import { ClearRememberLogin, GetConfigurationValue, GetRememberLogin, LocalizeText } from '../../api';
|
||||
import { Column, LayoutCurrencyIcon } from '../../common';
|
||||
@@ -16,6 +16,13 @@ const localizeWithFallback = (key: string, fallback: string) =>
|
||||
export const PurseView: FC<{}> = props =>
|
||||
{
|
||||
const { purse = null, hcDisabled = false } = usePurse();
|
||||
const [ settingsMenuOpen, setSettingsMenuOpen ] = useState(false);
|
||||
|
||||
const openSettingsSection = useCallback((section: string) =>
|
||||
{
|
||||
CreateLinkEvent('user-settings/show/' + section);
|
||||
setSettingsMenuOpen(false);
|
||||
}, []);
|
||||
|
||||
const displayedCurrencies = useMemo(() => GetConfigurationValue<number[]>('system.currency.types', []), []);
|
||||
const currencyDisplayNumberShort = useMemo(() => GetConfigurationValue<boolean>('currency.display.number.short', false), []);
|
||||
@@ -123,13 +130,21 @@ export const PurseView: FC<{}> = props =>
|
||||
</button>
|
||||
<button type="button" className="nitro-purse__btn nitro-purse__btn--icon nitro-purse__btn--settings" onClick={ event =>
|
||||
{
|
||||
event.stopPropagation(); CreateLinkEvent('user-settings/toggle');
|
||||
event.stopPropagation(); setSettingsMenuOpen(value => !value);
|
||||
} } title={ LocalizeText('widget.memenu.settings.title') }>
|
||||
<FaCog />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{ settingsMenuOpen &&
|
||||
<div className="nitro-purse-menu">
|
||||
<button type="button" className="nitro-purse-menu__item" onClick={ () => openSettingsSection('audio') }>Impostazioni Audio</button>
|
||||
<button type="button" className="nitro-purse-menu__item nitro-purse-menu__item--disabled" disabled>Impostazioni Discord</button>
|
||||
<button type="button" className="nitro-purse-menu__item" onClick={ () => openSettingsSection('chat') }>Impostazioni Chat</button>
|
||||
<button type="button" className="nitro-purse-menu__item" onClick={ () => openSettingsSection('other') }>Altre Impostazioni</button>
|
||||
<button type="button" className="nitro-purse-menu__item nitro-purse-menu__item--disabled" disabled>Filtro Parole</button>
|
||||
</div> }
|
||||
{ otherCurrencies.length > 0 &&
|
||||
<div className="nitro-purse__other">
|
||||
{ otherCurrencies.map(type => <SeasonalView key={ type } type={ type } amount={ purse.activityPoints.get(type) || 0 } />) }
|
||||
|
||||
@@ -2,13 +2,24 @@ import { AddLinkEventTracker, CreateLinkEvent, ILinkEventTracker, NitroSettingsE
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { FaUserCog, FaVolumeDown, FaVolumeMute, FaVolumeUp } from 'react-icons/fa';
|
||||
import { DispatchMainEvent, DispatchUiEvent, LocalizeText, SendMessageComposer } from '../../api';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common';
|
||||
import { Button, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common';
|
||||
import { useCatalogPlaceMultipleItems, useCatalogSkipPurchaseConfirmation, useChatWindow, useMessageEvent } from '../../hooks';
|
||||
import { classNames } from '../../layout';
|
||||
|
||||
const localizeWithFallback = (key: string, fallback: string) =>
|
||||
{
|
||||
const text = LocalizeText(key);
|
||||
return (text && text !== key) ? text : fallback;
|
||||
};
|
||||
|
||||
// null = full window (legacy). 'audio' | 'chat' | 'other' = focused section
|
||||
// opened from the purse gear dropdown.
|
||||
type SettingsSection = null | 'audio' | 'chat' | 'other';
|
||||
|
||||
export const UserSettingsView: FC<{}> = props =>
|
||||
{
|
||||
const [ isVisible, setIsVisible ] = useState(false);
|
||||
const [ section, setSection ] = useState<SettingsSection>(null);
|
||||
const [ userSettings, setUserSettings ] = useState<NitroSettingsEvent>(null);
|
||||
const [ catalogPlaceMultipleObjects, setCatalogPlaceMultipleObjects ] = useCatalogPlaceMultipleItems();
|
||||
const [ catalogSkipPurchaseConfirmation, setCatalogSkipPurchaseConfirmation ] = useCatalogSkipPurchaseConfirmation();
|
||||
@@ -100,12 +111,14 @@ export const UserSettingsView: FC<{}> = props =>
|
||||
switch(parts[1])
|
||||
{
|
||||
case 'show':
|
||||
setSection((parts[2] as SettingsSection) || null);
|
||||
setIsVisible(true);
|
||||
return;
|
||||
case 'hide':
|
||||
setIsVisible(false);
|
||||
return;
|
||||
case 'toggle':
|
||||
setSection((parts[2] as SettingsSection) || null);
|
||||
setIsVisible(prevValue => !prevValue);
|
||||
return;
|
||||
}
|
||||
@@ -127,81 +140,104 @@ export const UserSettingsView: FC<{}> = props =>
|
||||
|
||||
if(!isVisible || !userSettings) return null;
|
||||
|
||||
const showChat = (section === null || section === 'chat');
|
||||
const showOther = (section === null || section === 'other');
|
||||
const showAudio = (section === null || section === 'audio');
|
||||
const showAccountLink = (section === null);
|
||||
|
||||
const headerText = (section === 'audio')
|
||||
? localizeWithFallback('widget.memenu.settings.volume', 'Audio settings')
|
||||
: (section === 'chat')
|
||||
? localizeWithFallback('room.chat.settings.title', 'Chat settings')
|
||||
: (section === 'other')
|
||||
? localizeWithFallback('memenu.settings.other', 'Other settings')
|
||||
: LocalizeText('widget.memenu.settings.title');
|
||||
|
||||
return (
|
||||
<NitroCardView className="user-settings-window" theme="primary-slim" uniqueKey="user-settings">
|
||||
<NitroCardHeaderView headerText={ LocalizeText('widget.memenu.settings.title') } onCloseClick={ event => processAction('close_view') } />
|
||||
<NitroCardHeaderView headerText={ headerText } onCloseClick={ event => processAction('close_view') } />
|
||||
<NitroCardContentView className="text-black">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ userSettings.oldChat } className="form-check-input" type="checkbox" onChange={ event => processAction('oldchat', event.target.checked) } />
|
||||
<Text>{ LocalizeText('memenu.settings.chat.prefer.old.chat') }</Text>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ userSettings.roomInvites } className="form-check-input" type="checkbox" onChange={ event => processAction('room_invites', event.target.checked) } />
|
||||
<Text>{ LocalizeText('memenu.settings.other.ignore.room.invites') }</Text>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ userSettings.cameraFollow } className="form-check-input" type="checkbox" onChange={ event => processAction('camera_follow', event.target.checked) } />
|
||||
<Text>{ LocalizeText('memenu.settings.other.disable.room.camera.follow') }</Text>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ catalogPlaceMultipleObjects } className="form-check-input" type="checkbox" onChange={ event => setCatalogPlaceMultipleObjects(event.target.checked) } />
|
||||
<Text>{ LocalizeText('memenu.settings.other.place.multiple.objects') }</Text>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ catalogSkipPurchaseConfirmation } className="form-check-input" type="checkbox" onChange={ event => setCatalogSkipPurchaseConfirmation(event.target.checked) } />
|
||||
<Text>{ LocalizeText('memenu.settings.other.skip.purchase.confirmation') }</Text>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ chatWindowEnabled } className="form-check-input" type="checkbox" onChange={ event => setChatWindowEnabled(event.target.checked) } />
|
||||
<Text>{ LocalizeText('memenu.settings.other.enable.chat.window') }</Text>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<Text bold>{ LocalizeText('widget.memenu.settings.volume') }</Text>
|
||||
{ showChat &&
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text>{ LocalizeText('widget.memenu.settings.volume.ui') }</Text>
|
||||
<div className="flex items-center gap-1">
|
||||
{ (userSettings.volumeSystem === 0) && <FaVolumeMute className={ classNames((userSettings.volumeSystem >= 50) && 'text-muted', 'fa-icon') } /> }
|
||||
{ (userSettings.volumeSystem > 0) && <FaVolumeDown className={ classNames((userSettings.volumeSystem >= 50) && 'text-muted', 'fa-icon') } /> }
|
||||
<input className="custom-range w-full" id="volumeSystem" max="100" min="0" step="1" type="range" value={ userSettings.volumeSystem } onChange={ event => processAction('system_volume', event.target.value) } onMouseUp={ () => saveRangeSlider('volume') } />
|
||||
<FaVolumeUp className={ classNames((userSettings.volumeSystem < 50) && 'text-muted', 'fa-icon') } />
|
||||
<input checked={ userSettings.oldChat } className="form-check-input" type="checkbox" onChange={ event => processAction('oldchat', event.target.checked) } />
|
||||
<Text>{ LocalizeText('memenu.settings.chat.prefer.old.chat') }</Text>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ chatWindowEnabled } className="form-check-input" type="checkbox" onChange={ event => setChatWindowEnabled(event.target.checked) } />
|
||||
<Text>{ LocalizeText('memenu.settings.other.enable.chat.window') }</Text>
|
||||
</div>
|
||||
</div> }
|
||||
{ showOther &&
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text>{ LocalizeText('widget.memenu.settings.volume.furni') }</Text>
|
||||
<div className="flex items-center gap-1">
|
||||
{ (userSettings.volumeFurni === 0) && <FaVolumeMute className={ classNames((userSettings.volumeFurni >= 50) && 'text-muted', 'fa-icon') } /> }
|
||||
{ (userSettings.volumeFurni > 0) && <FaVolumeDown className={ classNames((userSettings.volumeFurni >= 50) && 'text-muted', 'fa-icon') } /> }
|
||||
<input className="custom-range w-full" id="volumeFurni" max="100" min="0" step="1" type="range" value={ userSettings.volumeFurni } onChange={ event => processAction('furni_volume', event.target.value) } onMouseUp={ () => saveRangeSlider('volume') } />
|
||||
<FaVolumeUp className={ classNames((userSettings.volumeFurni < 50) && 'text-muted', 'fa-icon') } />
|
||||
<input checked={ userSettings.roomInvites } className="form-check-input" type="checkbox" onChange={ event => processAction('room_invites', event.target.checked) } />
|
||||
<Text>{ LocalizeText('memenu.settings.other.ignore.room.invites') }</Text>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text>{ LocalizeText('widget.memenu.settings.volume.trax') }</Text>
|
||||
<div className="flex items-center gap-1">
|
||||
{ (userSettings.volumeTrax === 0) && <FaVolumeMute className={ classNames((userSettings.volumeTrax >= 50) && 'text-muted', 'fa-icon') } /> }
|
||||
{ (userSettings.volumeTrax > 0) && <FaVolumeDown className={ classNames((userSettings.volumeTrax >= 50) && 'text-muted', 'fa-icon') } /> }
|
||||
<input className="custom-range w-full" id="volumeTrax" max="100" min="0" step="1" type="range" value={ userSettings.volumeTrax } onChange={ event => processAction('trax_volume', event.target.value) } onMouseUp={ () => saveRangeSlider('volume') } />
|
||||
<FaVolumeUp className={ classNames((userSettings.volumeTrax < 50) && 'text-muted', 'fa-icon') } />
|
||||
<input checked={ userSettings.cameraFollow } className="form-check-input" type="checkbox" onChange={ event => processAction('camera_follow', event.target.checked) } />
|
||||
<Text>{ LocalizeText('memenu.settings.other.disable.room.camera.follow') }</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col pt-2 mt-1 border-t border-black/10">
|
||||
<button
|
||||
type="button"
|
||||
onClick={ () => CreateLinkEvent('user-account-settings/show') }
|
||||
className="group flex items-center gap-2 rounded-md border border-black/10 bg-white px-2 py-1.5 hover:bg-[#f5fbfd] hover:border-[#1e7295] transition-colors cursor-pointer text-left">
|
||||
<div className="flex items-center justify-center w-7 h-7 rounded-full bg-[#1e7295] text-white shadow-[inset_0_2px_#ffffff26,inset_0_-2px_#0000001a]">
|
||||
<FaUserCog size={ 12 } />
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ catalogPlaceMultipleObjects } className="form-check-input" type="checkbox" onChange={ event => setCatalogPlaceMultipleObjects(event.target.checked) } />
|
||||
<Text>{ LocalizeText('memenu.settings.other.place.multiple.objects') }</Text>
|
||||
</div>
|
||||
<div className="flex flex-col flex-1 leading-tight">
|
||||
<Text bold>{ LocalizeText('usersettings.open.title') }</Text>
|
||||
<Text small className="text-black/60">{ LocalizeText('usersettings.open.subtitle') }</Text>
|
||||
<div className="flex items-center gap-1">
|
||||
<input checked={ catalogSkipPurchaseConfirmation } className="form-check-input" type="checkbox" onChange={ event => setCatalogSkipPurchaseConfirmation(event.target.checked) } />
|
||||
<Text>{ LocalizeText('memenu.settings.other.skip.purchase.confirmation') }</Text>
|
||||
</div>
|
||||
<span className="text-black/30 group-hover:text-[#1e7295] text-[10px]">›</span>
|
||||
</button>
|
||||
</div>
|
||||
</div> }
|
||||
{ showAudio &&
|
||||
<div className="flex flex-col">
|
||||
<Text bold>{ LocalizeText('widget.memenu.settings.volume') }</Text>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text>{ LocalizeText('widget.memenu.settings.volume.ui') }</Text>
|
||||
<div className="flex items-center gap-1">
|
||||
{ (userSettings.volumeSystem === 0) && <FaVolumeMute className={ classNames((userSettings.volumeSystem >= 50) && 'text-muted', 'fa-icon') } /> }
|
||||
{ (userSettings.volumeSystem > 0) && <FaVolumeDown className={ classNames((userSettings.volumeSystem >= 50) && 'text-muted', 'fa-icon') } /> }
|
||||
<input className="custom-range w-full" id="volumeSystem" max="100" min="0" step="1" type="range" value={ userSettings.volumeSystem } onChange={ event => processAction('system_volume', event.target.value) } onMouseUp={ () => saveRangeSlider('volume') } />
|
||||
<FaVolumeUp className={ classNames((userSettings.volumeSystem < 50) && 'text-muted', 'fa-icon') } />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text>{ LocalizeText('widget.memenu.settings.volume.furni') }</Text>
|
||||
<div className="flex items-center gap-1">
|
||||
{ (userSettings.volumeFurni === 0) && <FaVolumeMute className={ classNames((userSettings.volumeFurni >= 50) && 'text-muted', 'fa-icon') } /> }
|
||||
{ (userSettings.volumeFurni > 0) && <FaVolumeDown className={ classNames((userSettings.volumeFurni >= 50) && 'text-muted', 'fa-icon') } /> }
|
||||
<input className="custom-range w-full" id="volumeFurni" max="100" min="0" step="1" type="range" value={ userSettings.volumeFurni } onChange={ event => processAction('furni_volume', event.target.value) } onMouseUp={ () => saveRangeSlider('volume') } />
|
||||
<FaVolumeUp className={ classNames((userSettings.volumeFurni < 50) && 'text-muted', 'fa-icon') } />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text>{ LocalizeText('widget.memenu.settings.volume.trax') }</Text>
|
||||
<div className="flex items-center gap-1">
|
||||
{ (userSettings.volumeTrax === 0) && <FaVolumeMute className={ classNames((userSettings.volumeTrax >= 50) && 'text-muted', 'fa-icon') } /> }
|
||||
{ (userSettings.volumeTrax > 0) && <FaVolumeDown className={ classNames((userSettings.volumeTrax >= 50) && 'text-muted', 'fa-icon') } /> }
|
||||
<input className="custom-range w-full" id="volumeTrax" max="100" min="0" step="1" type="range" value={ userSettings.volumeTrax } onChange={ event => processAction('trax_volume', event.target.value) } onMouseUp={ () => saveRangeSlider('volume') } />
|
||||
<FaVolumeUp className={ classNames((userSettings.volumeTrax < 50) && 'text-muted', 'fa-icon') } />
|
||||
</div>
|
||||
</div>
|
||||
</div> }
|
||||
{ showAccountLink &&
|
||||
<div className="flex flex-col pt-2 mt-1 border-t border-black/10">
|
||||
<button
|
||||
type="button"
|
||||
onClick={ () => CreateLinkEvent('user-account-settings/show') }
|
||||
className="group flex items-center gap-2 rounded-md border border-black/10 bg-white px-2 py-1.5 hover:bg-[#f5fbfd] hover:border-[#1e7295] transition-colors cursor-pointer text-left">
|
||||
<div className="flex items-center justify-center w-7 h-7 rounded-full bg-[#1e7295] text-white shadow-[inset_0_2px_#ffffff26,inset_0_-2px_#0000001a]">
|
||||
<FaUserCog size={ 12 } />
|
||||
</div>
|
||||
<div className="flex flex-col flex-1 leading-tight">
|
||||
<Text bold>{ LocalizeText('usersettings.open.title') }</Text>
|
||||
<Text small className="text-black/60">{ LocalizeText('usersettings.open.subtitle') }</Text>
|
||||
</div>
|
||||
<span className="text-black/30 group-hover:text-[#1e7295] text-[10px]">›</span>
|
||||
</button>
|
||||
</div> }
|
||||
{ (section !== null) &&
|
||||
<div className="flex pt-2 mt-1 border-t border-black/10">
|
||||
<Button variant="secondary" onClick={ event => processAction('close_view') }>{ localizeWithFallback('generic.back', 'Indietro') }</Button>
|
||||
</div> }
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
|
||||
@@ -230,6 +230,45 @@
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
/* ---- Settings dropdown (gear menu) ---- */
|
||||
.nitro-purse-menu {
|
||||
width: 100%;
|
||||
max-width: 200px;
|
||||
margin-top: 4px;
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
border: 2px solid #41403c;
|
||||
border-radius: 8px;
|
||||
background: rgba(10, 10, 12, 0.92);
|
||||
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.3);
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.nitro-purse-menu__item {
|
||||
padding: 6px 10px;
|
||||
text-align: left;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 500;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
background: transparent;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
transition: background 0.12s ease;
|
||||
}
|
||||
|
||||
.nitro-purse-menu__item:hover {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.nitro-purse-menu__item--disabled,
|
||||
.nitro-purse-menu__item--disabled:hover {
|
||||
color: rgba(255, 255, 255, 0.35);
|
||||
background: transparent;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.nitro-purse {
|
||||
max-width: 100%;
|
||||
|
||||
Reference in New Issue
Block a user