Merge pull request #1034 from Lyall-A/private-mode

add private mode toggle to app menu
This commit is contained in:
Jeff 2025-08-27 22:02:28 -07:00 committed by GitHub
commit 59065d24bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 81 additions and 20 deletions

View file

@ -288,6 +288,11 @@
"updateServer": { "updateServer": {
"success": "server updated successfully", "success": "server updated successfully",
"title": "update server" "title": "update server"
},
"privateMode": {
"enabled": "private mode enabled, playback status is now hidden from external integrations",
"disabled": "private mode disabled, playback status is now visible to enabled external integrations",
"title": "private mode"
} }
}, },
"page": { "page": {
@ -321,6 +326,8 @@
"goBack": "go back", "goBack": "go back",
"goForward": "go forward", "goForward": "go forward",
"manageServers": "manage servers", "manageServers": "manage servers",
"privateModeOff": "turn off private mode",
"privateModeOn": "turn on private mode",
"openBrowserDevtools": "open browser devtools", "openBrowserDevtools": "open browser devtools",
"quit": "$t(common.quit)", "quit": "$t(common.quit)",
"selectServer": "select server", "selectServer": "select server",

View file

@ -6,6 +6,7 @@ import { controller } from '/@/renderer/api/controller';
import { import {
DiscordDisplayType, DiscordDisplayType,
getServerById, getServerById,
useAppStore,
useDiscordSetttings, useDiscordSetttings,
useGeneralSettings, useGeneralSettings,
usePlayerStore, usePlayerStore,
@ -18,6 +19,7 @@ const discordRpc = isElectron() ? window.api.discordRpc : null;
export const useDiscordRpc = () => { export const useDiscordRpc = () => {
const discordSettings = useDiscordSetttings(); const discordSettings = useDiscordSetttings();
const generalSettings = useGeneralSettings(); const generalSettings = useGeneralSettings();
const { privateMode } = useAppStore();
const [lastUniqueId, setlastUniqueId] = useState(''); const [lastUniqueId, setlastUniqueId] = useState('');
const setActivity = useCallback( const setActivity = useCallback(
@ -148,15 +150,15 @@ export const useDiscordRpc = () => {
); );
useEffect(() => { useEffect(() => {
if (!discordSettings.enabled) return discordRpc?.quit(); if (!discordSettings.enabled || privateMode) return discordRpc?.quit();
return () => { return () => {
discordRpc?.quit(); discordRpc?.quit();
}; };
}, [discordSettings.clientId, discordSettings.enabled]); }, [discordSettings.clientId, privateMode, discordSettings.enabled]);
useEffect(() => { useEffect(() => {
if (!discordSettings.enabled) return; if (!discordSettings.enabled || privateMode) return;
const unsubSongChange = usePlayerStore.subscribe( const unsubSongChange = usePlayerStore.subscribe(
(state) => [state.current.song, state.current.time, state.current.status], (state) => [state.current.song, state.current.time, state.current.status],
setActivity, setActivity,
@ -164,5 +166,5 @@ export const useDiscordRpc = () => {
return () => { return () => {
unsubSongChange(); unsubSongChange();
}; };
}, [discordSettings.enabled, setActivity]); }, [discordSettings.enabled, privateMode, setActivity]);
}; };

View file

@ -1,8 +1,7 @@
import { useCallback, useEffect, useRef, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import { useSendScrobble } from '/@/renderer/features/player/mutations/scrobble-mutation'; import { useSendScrobble } from '/@/renderer/features/player/mutations/scrobble-mutation';
import { usePlayerStore } from '/@/renderer/store'; import { useAppStore, usePlaybackSettings, usePlayerStore } from '/@/renderer/store';
import { usePlaybackSettings } from '/@/renderer/store/settings.store';
import { QueueSong, ServerType } from '/@/shared/types/domain-types'; import { QueueSong, ServerType } from '/@/shared/types/domain-types';
import { PlayerStatus } from '/@/shared/types/types'; import { PlayerStatus } from '/@/shared/types/types';
@ -59,13 +58,14 @@ const checkScrobbleConditions = (args: {
export const useScrobble = () => { export const useScrobble = () => {
const scrobbleSettings = usePlaybackSettings().scrobble; const scrobbleSettings = usePlaybackSettings().scrobble;
const isScrobbleEnabled = scrobbleSettings?.enabled; const isScrobbleEnabled = scrobbleSettings?.enabled;
const isPrivateModeEnabled = useAppStore().privateMode;
const sendScrobble = useSendScrobble(); const sendScrobble = useSendScrobble();
const [isCurrentSongScrobbled, setIsCurrentSongScrobbled] = useState(false); const [isCurrentSongScrobbled, setIsCurrentSongScrobbled] = useState(false);
const handleScrobbleFromSeek = useCallback( const handleScrobbleFromSeek = useCallback(
(currentTime: number) => { (currentTime: number) => {
if (!isScrobbleEnabled) return; if (!isScrobbleEnabled || isPrivateModeEnabled) return;
const currentSong = usePlayerStore.getState().current.song; const currentSong = usePlayerStore.getState().current.song;
@ -84,7 +84,7 @@ export const useScrobble = () => {
serverId: currentSong?.serverId, serverId: currentSong?.serverId,
}); });
}, },
[isScrobbleEnabled, sendScrobble], [isScrobbleEnabled, isPrivateModeEnabled, sendScrobble],
); );
const progressIntervalId = useRef<null | ReturnType<typeof setInterval>>(null); const progressIntervalId = useRef<null | ReturnType<typeof setInterval>>(null);
@ -119,7 +119,7 @@ export const useScrobble = () => {
}, 1000); }, 1000);
} }
if (!isScrobbleEnabled) return; if (!isScrobbleEnabled || isPrivateModeEnabled) return;
if (progressIntervalId.current) { if (progressIntervalId.current) {
clearInterval(progressIntervalId.current); clearInterval(progressIntervalId.current);
@ -201,6 +201,7 @@ export const useScrobble = () => {
scrobbleSettings?.scrobbleAtDuration, scrobbleSettings?.scrobbleAtDuration,
scrobbleSettings?.scrobbleAtPercentage, scrobbleSettings?.scrobbleAtPercentage,
isScrobbleEnabled, isScrobbleEnabled,
isPrivateModeEnabled,
isCurrentSongScrobbled, isCurrentSongScrobbled,
sendScrobble, sendScrobble,
handleScrobbleFromSeek, handleScrobbleFromSeek,
@ -209,7 +210,7 @@ export const useScrobble = () => {
const handleScrobbleFromStatusChange = useCallback( const handleScrobbleFromStatusChange = useCallback(
(current: PlayerEvent, previous: PlayerEvent) => { (current: PlayerEvent, previous: PlayerEvent) => {
if (!isScrobbleEnabled) return; if (!isScrobbleEnabled || isPrivateModeEnabled) return;
const currentSong = usePlayerStore.getState().current.song; const currentSong = usePlayerStore.getState().current.song;
@ -293,6 +294,7 @@ export const useScrobble = () => {
}, },
[ [
isScrobbleEnabled, isScrobbleEnabled,
isPrivateModeEnabled,
sendScrobble, sendScrobble,
handleScrobbleFromSeek, handleScrobbleFromSeek,
scrobbleSettings?.scrobbleAtDuration, scrobbleSettings?.scrobbleAtDuration,
@ -306,7 +308,7 @@ export const useScrobble = () => {
// need to perform another check to see if the scrobble conditions are met // need to perform another check to see if the scrobble conditions are met
const handleScrobbleFromSongRestart = useCallback( const handleScrobbleFromSongRestart = useCallback(
(currentTime: number) => { (currentTime: number) => {
if (!isScrobbleEnabled) return; if (!isScrobbleEnabled || isPrivateModeEnabled) return;
const currentSong = usePlayerStore.getState().current.song; const currentSong = usePlayerStore.getState().current.song;
@ -349,6 +351,7 @@ export const useScrobble = () => {
}, },
[ [
isScrobbleEnabled, isScrobbleEnabled,
isPrivateModeEnabled,
scrobbleSettings?.scrobbleAtDuration, scrobbleSettings?.scrobbleAtDuration,
scrobbleSettings?.scrobbleAtPercentage, scrobbleSettings?.scrobbleAtPercentage,
isCurrentSongScrobbled, isCurrentSongScrobbled,

View file

@ -10,6 +10,7 @@ import { ServerList } from '/@/renderer/features/servers';
import { EditServerForm } from '/@/renderer/features/servers/components/edit-server-form'; import { EditServerForm } from '/@/renderer/features/servers/components/edit-server-form';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { import {
useAppStore,
useAppStoreActions, useAppStoreActions,
useAuthStoreActions, useAuthStoreActions,
useCurrentServer, useCurrentServer,
@ -18,6 +19,7 @@ import {
} from '/@/renderer/store'; } from '/@/renderer/store';
import { DropdownMenu } from '/@/shared/components/dropdown-menu/dropdown-menu'; import { DropdownMenu } from '/@/shared/components/dropdown-menu/dropdown-menu';
import { Icon } from '/@/shared/components/icon/icon'; import { Icon } from '/@/shared/components/icon/icon';
import { toast } from '/@/shared/components/toast/toast';
import { ServerListItem, ServerType } from '/@/shared/types/domain-types'; import { ServerListItem, ServerType } from '/@/shared/types/domain-types';
const browser = isElectron() ? window.api.browser : null; const browser = isElectron() ? window.api.browser : null;
@ -30,7 +32,8 @@ export const AppMenu = () => {
const serverList = useServerList(); const serverList = useServerList();
const { setCurrentServer } = useAuthStoreActions(); const { setCurrentServer } = useAuthStoreActions();
const { collapsed } = useSidebarStore(); const { collapsed } = useSidebarStore();
const { setSideBar } = useAppStoreActions(); const { privateMode } = useAppStore();
const { setPrivateMode, setSideBar } = useAppStoreActions();
const handleSetCurrentServer = (server: ServerListItem) => { const handleSetCurrentServer = (server: ServerListItem) => {
navigate(AppRoute.HOME); navigate(AppRoute.HOME);
@ -80,6 +83,22 @@ export const AppMenu = () => {
setSideBar({ collapsed: false }); setSideBar({ collapsed: false });
}; };
const handlePrivateModeOff = () => {
setPrivateMode(false);
toast.info({
message: t('form.privateMode.disabled', { postProcess: 'sentenceCase' }),
title: t('form.privateMode.title', { postProcess: 'sentenceCase' }),
});
};
const handlePrivateModeOn = () => {
setPrivateMode(true);
toast.info({
message: t('form.privateMode.enabled', { postProcess: 'sentenceCase' }),
title: t('form.privateMode.title', { postProcess: 'sentenceCase' }),
});
};
const handleQuit = () => { const handleQuit = () => {
browser?.quit(); browser?.quit();
}; };
@ -127,7 +146,21 @@ export const AppMenu = () => {
> >
{t('page.appMenu.manageServers', { postProcess: 'sentenceCase' })} {t('page.appMenu.manageServers', { postProcess: 'sentenceCase' })}
</DropdownMenu.Item> </DropdownMenu.Item>
{privateMode ? (
<DropdownMenu.Item
leftSection={<Icon color="error" icon="lock" />}
onClick={handlePrivateModeOff}
>
{t('page.appMenu.privateModeOff', { postProcess: 'sentenceCase' })}
</DropdownMenu.Item>
) : (
<DropdownMenu.Item
leftSection={<Icon icon="lockOpen" />}
onClick={handlePrivateModeOn}
>
{t('page.appMenu.privateModeOn', { postProcess: 'sentenceCase' })}
</DropdownMenu.Item>
)}
<DropdownMenu.Divider /> <DropdownMenu.Divider />
<DropdownMenu.Label> <DropdownMenu.Label>
{t('page.appMenu.selectServer', { postProcess: 'sentenceCase' })} {t('page.appMenu.selectServer', { postProcess: 'sentenceCase' })}

View file

@ -12,8 +12,12 @@ import macMinHover from './assets/min-mac-hover.png';
import macMin from './assets/min-mac.png'; import macMin from './assets/min-mac.png';
import styles from './window-bar.module.css'; import styles from './window-bar.module.css';
import { useCurrentStatus, useQueueStatus } from '/@/renderer/store'; import {
import { useWindowSettings } from '/@/renderer/store/settings.store'; useAppStore,
useCurrentStatus,
useQueueStatus,
useWindowSettings,
} from '/@/renderer/store';
import { Text } from '/@/shared/components/text/text'; import { Text } from '/@/shared/components/text/text';
import { Platform, PlayerStatus } from '/@/shared/types/types'; import { Platform, PlayerStatus } from '/@/shared/types/types';
@ -126,14 +130,16 @@ export const WindowBar = () => {
const playerStatus = useCurrentStatus(); const playerStatus = useCurrentStatus();
const { currentSong, index, length } = useQueueStatus(); const { currentSong, index, length } = useQueueStatus();
const { windowBarStyle } = useWindowSettings(); const { windowBarStyle } = useWindowSettings();
const { privateMode } = useAppStore();
const statusString = playerStatus === PlayerStatus.PAUSED ? '(Paused) ' : ''; const statusString = playerStatus === PlayerStatus.PAUSED ? '(Paused) ' : '';
const queueString = length ? `(${index + 1} / ${length}) ` : ''; const queueString = length ? `(${index + 1} / ${length}) ` : '';
const title = length const privateModeString = privateMode ? '(Private mode)' : '';
? currentSong?.artistName const title = `${
? `${statusString}${queueString}${currentSong?.name}${currentSong?.artistName}` length
: `${statusString}${queueString}${currentSong?.name}` ? `${statusString}${queueString}${currentSong?.name}${currentSong?.artistName ? `${currentSong?.artistName}` : ''}`
: 'Feishin'; : 'Feishin'
}${privateMode ? ` ${privateModeString}` : ''}`;
document.title = title; document.title = title;
const [max, setMax] = useState(localSettings?.env.START_MAXIMIZED || false); const [max, setMax] = useState(localSettings?.env.START_MAXIMIZED || false);

View file

@ -8,6 +8,7 @@ import { Platform } from '/@/shared/types/types';
export interface AppSlice extends AppState { export interface AppSlice extends AppState {
actions: { actions: {
setAppStore: (data: Partial<AppSlice>) => void; setAppStore: (data: Partial<AppSlice>) => void;
setPrivateMode: (enabled: boolean) => void;
setSideBar: (options: Partial<SidebarProps>) => void; setSideBar: (options: Partial<SidebarProps>) => void;
setTitleBar: (options: Partial<TitlebarProps>) => void; setTitleBar: (options: Partial<TitlebarProps>) => void;
}; };
@ -17,6 +18,7 @@ export interface AppState {
commandPalette: CommandPaletteProps; commandPalette: CommandPaletteProps;
isReorderingQueue: boolean; isReorderingQueue: boolean;
platform: Platform; platform: Platform;
privateMode: boolean;
sidebar: SidebarProps; sidebar: SidebarProps;
titlebar: TitlebarProps; titlebar: TitlebarProps;
} }
@ -50,6 +52,11 @@ export const useAppStore = createWithEqualityFn<AppSlice>()(
setAppStore: (data) => { setAppStore: (data) => {
set({ ...get(), ...data }); set({ ...get(), ...data });
}, },
setPrivateMode: (privateMode) => {
set((state) => {
state.privateMode = privateMode;
});
},
setSideBar: (options) => { setSideBar: (options) => {
set((state) => { set((state) => {
state.sidebar = { ...state.sidebar, ...options }; state.sidebar = { ...state.sidebar, ...options };
@ -81,6 +88,7 @@ export const useAppStore = createWithEqualityFn<AppSlice>()(
}, },
isReorderingQueue: false, isReorderingQueue: false,
platform: Platform.WINDOWS, platform: Platform.WINDOWS,
privateMode: false,
sidebar: { sidebar: {
collapsed: false, collapsed: false,
expanded: [], expanded: [],

View file

@ -59,6 +59,7 @@ import {
LuListPlus, LuListPlus,
LuLoader, LuLoader,
LuLock, LuLock,
LuLockOpen,
LuLogIn, LuLogIn,
LuLogOut, LuLogOut,
LuMenu, LuMenu,
@ -163,6 +164,7 @@ export const AppIcon = {
listInfinite: LuInfinity, listInfinite: LuInfinity,
listPaginated: LuArrowRightToLine, listPaginated: LuArrowRightToLine,
lock: LuLock, lock: LuLock,
lockOpen: LuLockOpen,
mediaNext: LuSkipForward, mediaNext: LuSkipForward,
mediaPause: LuPause, mediaPause: LuPause,
mediaPlay: LuPlay, mediaPlay: LuPlay,