diff --git a/src/components/purse/PurseView.tsx b/src/components/purse/PurseView.tsx index 32d2fe8..1ac10ef 100644 --- a/src/components/purse/PurseView.tsx +++ b/src/components/purse/PurseView.tsx @@ -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('system.currency.types', []), []); const currencyDisplayNumberShort = useMemo(() => GetConfigurationValue('currency.display.number.short', false), []); @@ -123,13 +130,22 @@ export const PurseView: FC<{}> = props => + { settingsMenuOpen && +
+ + + + + + +
} { otherCurrencies.length > 0 &&
{ otherCurrencies.map(type => ) } diff --git a/src/components/user-settings/UserAccountSettingsView.tsx b/src/components/user-settings/UserAccountSettingsView.tsx index bcf3adb..68678ab 100644 --- a/src/components/user-settings/UserAccountSettingsView.tsx +++ b/src/components/user-settings/UserAccountSettingsView.tsx @@ -34,7 +34,7 @@ const passwordStrength = (value: string): { score: number; labelKey: string; col return { score: 4, labelKey: 'usersettings.strength.strong', color: 'bg-[#00800b]' }; }; -export const UserAccountSettingsView: FC<{ embedded?: boolean }> = ({ embedded = false }) => +export const UserAccountSettingsView: FC<{}> = () => { const [ isVisible, setIsVisible ] = useState(false); const [ section, setSection ] = useState
('menu'); @@ -390,10 +390,12 @@ export const UserAccountSettingsView: FC<{ embedded?: boolean }> = ({ embedded = } }; - if(!embedded && !isVisible) return null; + if(!isVisible) return null; + + return ( + + - const accountBody = ( - <>
{ session.figure && ( @@ -751,15 +753,6 @@ export const UserAccountSettingsView: FC<{ embedded?: boolean }> = ({ embedded =
) } - - ); - - if(embedded) return accountBody; - - return ( - - - { accountBody } ); }; diff --git a/src/components/user-settings/UserSettingsView.tsx b/src/components/user-settings/UserSettingsView.tsx index 2e8ac23..7d4acf5 100644 --- a/src/components/user-settings/UserSettingsView.tsx +++ b/src/components/user-settings/UserSettingsView.tsx @@ -1,11 +1,10 @@ -import { AddLinkEventTracker, ILinkEventTracker, NitroSettingsEvent, RemoveLinkEventTracker, UserSettingsCameraFollowComposer, UserSettingsEvent, UserSettingsOldChatComposer, UserSettingsRoomInvitesComposer, UserSettingsSoundComposer } from '@nitrots/nitro-renderer'; +import { AddLinkEventTracker, CreateLinkEvent, ILinkEventTracker, NitroSettingsEvent, RemoveLinkEventTracker, UserSettingsCameraFollowComposer, UserSettingsEvent, UserSettingsOldChatComposer, UserSettingsRoomInvitesComposer, UserSettingsSoundComposer } from '@nitrots/nitro-renderer'; import { FC, useEffect, useState } from 'react'; -import { FaVolumeDown, FaVolumeMute, FaVolumeUp } from 'react-icons/fa'; +import { FaUserCog, FaVolumeDown, FaVolumeMute, FaVolumeUp } from 'react-icons/fa'; import { DispatchMainEvent, DispatchUiEvent, LocalizeText, SendMessageComposer } from '../../api'; -import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, Text } from '../../common'; +import { Button, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common'; import { useCatalogPlaceMultipleItems, useCatalogSkipPurchaseConfirmation, useChatWindow, useMessageEvent } from '../../hooks'; import { classNames } from '../../layout'; -import { UserAccountSettingsView } from './UserAccountSettingsView'; const localizeWithFallback = (key: string, fallback: string) => { @@ -13,12 +12,14 @@ const localizeWithFallback = (key: string, fallback: string) => return (text && text !== key) ? text : fallback; }; -type SettingsTab = 'audio' | 'chat' | 'other' | 'account'; +// 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 [ activeTab, setActiveTab ] = useState('audio'); + const [ section, setSection ] = useState(null); const [ userSettings, setUserSettings ] = useState(null); const [ catalogPlaceMultipleObjects, setCatalogPlaceMultipleObjects ] = useCatalogPlaceMultipleItems(); const [ catalogSkipPurchaseConfirmation, setCatalogSkipPurchaseConfirmation ] = useCatalogSkipPurchaseConfirmation(); @@ -107,19 +108,17 @@ export const UserSettingsView: FC<{}> = props => if(parts.length < 2) return; - const tab = parts[2] as SettingsTab; - switch(parts[1]) { case 'show': - if(tab) setActiveTab(tab); + setSection((parts[2] as SettingsSection) || null); setIsVisible(true); return; case 'hide': setIsVisible(false); return; case 'toggle': - if(tab) setActiveTab(tab); + setSection((parts[2] as SettingsSection) || null); setIsVisible(prevValue => !prevValue); return; } @@ -141,92 +140,105 @@ 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 ( - - processAction('close_view') } /> - - setActiveTab('audio') }> - { localizeWithFallback('widget.memenu.settings.volume', 'Audio') } - - setActiveTab('chat') }> - { localizeWithFallback('room.chat.settings.title', 'Chat') } - - setActiveTab('other') }> - { localizeWithFallback('memenu.settings.other', 'Altre') } - - setActiveTab('account') }> - { localizeWithFallback('usersettings.account.label', 'Account') } - - - { (activeTab === 'account') - ? - : ( - - { (activeTab === 'chat') && - <> - { localizeWithFallback('room.chat.settings.title', 'Chat') } - - - } - { (activeTab === 'other') && - <> - { localizeWithFallback('memenu.settings.other', 'Altre') } - - - - - } - { (activeTab === 'audio') && - <> - { localizeWithFallback('widget.memenu.settings.volume', 'Audio') } -
- { LocalizeText('widget.memenu.settings.volume.ui') } -
- { (userSettings.volumeSystem === 0) && = 50) && 'text-muted', 'fa-icon') } /> } - { (userSettings.volumeSystem > 0) && = 50) && 'text-muted', 'fa-icon') } /> } - processAction('system_volume', event.target.value) } onMouseUp={ () => saveRangeSlider('volume') } /> - -
-
-
- { LocalizeText('widget.memenu.settings.volume.furni') } -
- { (userSettings.volumeFurni === 0) && = 50) && 'text-muted', 'fa-icon') } /> } - { (userSettings.volumeFurni > 0) && = 50) && 'text-muted', 'fa-icon') } /> } - processAction('furni_volume', event.target.value) } onMouseUp={ () => saveRangeSlider('volume') } /> - -
-
-
- { LocalizeText('widget.memenu.settings.volume.trax') } -
- { (userSettings.volumeTrax === 0) && = 50) && 'text-muted', 'fa-icon') } /> } - { (userSettings.volumeTrax > 0) && = 50) && 'text-muted', 'fa-icon') } /> } - processAction('trax_volume', event.target.value) } onMouseUp={ () => saveRangeSlider('volume') } /> - -
-
- } -
- ) } + + processAction('close_view') } /> + + { showChat && +
+
+ processAction('oldchat', event.target.checked) } /> + { LocalizeText('memenu.settings.chat.prefer.old.chat') } +
+
+ setChatWindowEnabled(event.target.checked) } /> + { LocalizeText('memenu.settings.other.enable.chat.window') } +
+
} + { showOther && +
+
+ processAction('room_invites', event.target.checked) } /> + { LocalizeText('memenu.settings.other.ignore.room.invites') } +
+
+ processAction('camera_follow', event.target.checked) } /> + { LocalizeText('memenu.settings.other.disable.room.camera.follow') } +
+
+ setCatalogPlaceMultipleObjects(event.target.checked) } /> + { LocalizeText('memenu.settings.other.place.multiple.objects') } +
+
+ setCatalogSkipPurchaseConfirmation(event.target.checked) } /> + { LocalizeText('memenu.settings.other.skip.purchase.confirmation') } +
+
} + { showAudio && +
+ { LocalizeText('widget.memenu.settings.volume') } +
+ { LocalizeText('widget.memenu.settings.volume.ui') } +
+ { (userSettings.volumeSystem === 0) && = 50) && 'text-muted', 'fa-icon') } /> } + { (userSettings.volumeSystem > 0) && = 50) && 'text-muted', 'fa-icon') } /> } + processAction('system_volume', event.target.value) } onMouseUp={ () => saveRangeSlider('volume') } /> + +
+
+
+ { LocalizeText('widget.memenu.settings.volume.furni') } +
+ { (userSettings.volumeFurni === 0) && = 50) && 'text-muted', 'fa-icon') } /> } + { (userSettings.volumeFurni > 0) && = 50) && 'text-muted', 'fa-icon') } /> } + processAction('furni_volume', event.target.value) } onMouseUp={ () => saveRangeSlider('volume') } /> + +
+
+
+ { LocalizeText('widget.memenu.settings.volume.trax') } +
+ { (userSettings.volumeTrax === 0) && = 50) && 'text-muted', 'fa-icon') } /> } + { (userSettings.volumeTrax > 0) && = 50) && 'text-muted', 'fa-icon') } /> } + processAction('trax_volume', event.target.value) } onMouseUp={ () => saveRangeSlider('volume') } /> + +
+
+
} + { showAccountLink && +
+ +
} + { (section !== null) && +
+ +
} +
); }; diff --git a/src/css/purse/PurseView.css b/src/css/purse/PurseView.css index b60fe8b..f6a1294 100644 --- a/src/css/purse/PurseView.css +++ b/src/css/purse/PurseView.css @@ -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%;