mirror of
https://github.com/jeffvli/feishin.git
synced 2026-08-08 05:12:57 +02:00
feat: confirm before discarding the current queue (#2300)
* feat: confirm before discarding the current queue
This commit is contained in:
@@ -364,6 +364,10 @@
|
|||||||
"title": "Add items to the queue",
|
"title": "Add items to the queue",
|
||||||
"description": "This action will add all items in the current filtered view"
|
"description": "This action will add all items in the current filtered view"
|
||||||
},
|
},
|
||||||
|
"queueChangeConfirmation": {
|
||||||
|
"title": "Discard the current queue?",
|
||||||
|
"description": "This will remove all items from the current queue."
|
||||||
|
},
|
||||||
"addToPlaylist": {
|
"addToPlaylist": {
|
||||||
"create": "Create $t(entity.playlist, {\"count\": 1}) {{playlist}}",
|
"create": "Create $t(entity.playlist, {\"count\": 1}) {{playlist}}",
|
||||||
"input_playlists": "$t(entity.playlist, {\"count\": 2})",
|
"input_playlists": "$t(entity.playlist, {\"count\": 2})",
|
||||||
@@ -1075,6 +1079,8 @@
|
|||||||
"passwordStore": "Passwords/secret store",
|
"passwordStore": "Passwords/secret store",
|
||||||
"playerFilters": "Filter songs from the queue",
|
"playerFilters": "Filter songs from the queue",
|
||||||
"playerFilters_description": "Omit songs from being added to the queue based on the following criteria",
|
"playerFilters_description": "Omit songs from being added to the queue based on the following criteria",
|
||||||
|
"confirmQueueChanges": "Confirm queue changes",
|
||||||
|
"confirmQueueChanges_description": "Ask for confirmation before discarding the current queue",
|
||||||
"playbackStyle_description": "Select the playback style to use for the audio player",
|
"playbackStyle_description": "Select the playback style to use for the audio player",
|
||||||
"playbackStyle_optionCrossFade": "Crossfade",
|
"playbackStyle_optionCrossFade": "Crossfade",
|
||||||
"playbackStyle_optionNormal": "Normal",
|
"playbackStyle_optionNormal": "Normal",
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ const QueuePlaybackIcons = ({ tableRef }: { tableRef: RefObject<ItemListHandle |
|
|||||||
variant="subtle"
|
variant="subtle"
|
||||||
/>
|
/>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
icon="x"
|
icon="delete"
|
||||||
iconProps={{ size: 'lg' }}
|
iconProps={{ size: 'lg' }}
|
||||||
onClick={handleClearQueue}
|
onClick={handleClearQueue}
|
||||||
tooltip={{ label: t('action.clearQueue') }}
|
tooltip={{ label: t('action.clearQueue') }}
|
||||||
|
|||||||
@@ -17,7 +17,12 @@ import {
|
|||||||
} from '/@/renderer/features/player/utils';
|
} from '/@/renderer/features/player/utils';
|
||||||
import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-api';
|
import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-api';
|
||||||
import { songsQueries } from '/@/renderer/features/songs/api/songs-api';
|
import { songsQueries } from '/@/renderer/features/songs/api/songs-api';
|
||||||
import { AddToQueueType, usePlayerActions, useSettingsStore } from '/@/renderer/store';
|
import {
|
||||||
|
AddToQueueType,
|
||||||
|
usePlayerActions,
|
||||||
|
useSettingsStore,
|
||||||
|
useSettingsStoreActions,
|
||||||
|
} from '/@/renderer/store';
|
||||||
import { logger } from '/@/renderer/utils/logger';
|
import { logger } from '/@/renderer/utils/logger';
|
||||||
import { shuffle as shuffleArray } from '/@/renderer/utils/shuffle';
|
import { shuffle as shuffleArray } from '/@/renderer/utils/shuffle';
|
||||||
import { sortSongsByFetchedOrder } from '/@/shared/api/utils';
|
import { sortSongsByFetchedOrder } from '/@/shared/api/utils';
|
||||||
@@ -164,6 +169,7 @@ export const PlayerProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const storeActions = usePlayerActions();
|
const storeActions = usePlayerActions();
|
||||||
|
const settingsActions = useSettingsStoreActions();
|
||||||
const timeoutIds = useRef<null | Record<string, ReturnType<typeof setTimeout>>>({});
|
const timeoutIds = useRef<null | Record<string, ReturnType<typeof setTimeout>>>({});
|
||||||
|
|
||||||
const [doNotShowAgain, setDoNotShowAgain] = useLocalStorage({
|
const [doNotShowAgain, setDoNotShowAgain] = useLocalStorage({
|
||||||
@@ -171,6 +177,48 @@ export const PlayerProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
key: 'large_fetch_confirmation',
|
key: 'large_fetch_confirmation',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const confirmQueueChange = useCallback(
|
||||||
|
(onConfirm: () => void) => {
|
||||||
|
const shouldConfirm = useSettingsStore.getState().general.confirmQueueChanges;
|
||||||
|
|
||||||
|
if (!shouldConfirm || storeActions.getQueue().items.length === 0) {
|
||||||
|
onConfirm();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
openModal({
|
||||||
|
children: (
|
||||||
|
<ConfirmModal
|
||||||
|
labels={{
|
||||||
|
cancel: t('common.cancel'),
|
||||||
|
confirm: t('common.confirm'),
|
||||||
|
}}
|
||||||
|
onConfirm={() => {
|
||||||
|
closeAllModals();
|
||||||
|
onConfirm();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack>
|
||||||
|
<Text>{t('form.queueChangeConfirmation.description')}</Text>
|
||||||
|
<Checkbox
|
||||||
|
label={t('common.doNotShowAgain')}
|
||||||
|
onChange={(event) => {
|
||||||
|
settingsActions.setSettings({
|
||||||
|
general: {
|
||||||
|
confirmQueueChanges: !event.currentTarget.checked,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</ConfirmModal>
|
||||||
|
),
|
||||||
|
title: t('form.queueChangeConfirmation.title'),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[settingsActions, storeActions, t],
|
||||||
|
);
|
||||||
|
|
||||||
const confirmLargeFetch = useCallback((): Promise<boolean> => {
|
const confirmLargeFetch = useCallback((): Promise<boolean> => {
|
||||||
if (doNotShowAgain) {
|
if (doNotShowAgain) {
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
@@ -225,29 +273,42 @@ export const PlayerProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
filteredData = tagPlaylistContext(filteredData, resolvedContextId);
|
filteredData = tagPlaylistContext(filteredData, resolvedContextId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof type === 'object' && 'edge' in type && type.edge !== null) {
|
const addToQueue = () => {
|
||||||
const edge = type.edge === 'top' ? 'top' : 'bottom';
|
if (typeof type === 'object' && 'edge' in type && type.edge !== null) {
|
||||||
|
const edge = type.edge === 'top' ? 'top' : 'bottom';
|
||||||
|
|
||||||
logger.debug('Added to queue by data', {
|
logger.debug('Added to queue by data', {
|
||||||
data: data.length,
|
data: data.length,
|
||||||
edge,
|
edge,
|
||||||
filtered: filteredData.length,
|
filtered: filteredData.length,
|
||||||
type,
|
type,
|
||||||
uniqueId: type.uniqueId,
|
uniqueId: type.uniqueId,
|
||||||
});
|
});
|
||||||
|
|
||||||
storeActions.addToQueueByUniqueId(filteredData, type.uniqueId, edge, playSongId);
|
storeActions.addToQueueByUniqueId(
|
||||||
|
filteredData,
|
||||||
|
type.uniqueId,
|
||||||
|
edge,
|
||||||
|
playSongId,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
logger.debug('Added to queue by type', {
|
||||||
|
data: data.length,
|
||||||
|
filtered: filteredData.length,
|
||||||
|
type,
|
||||||
|
});
|
||||||
|
|
||||||
|
storeActions.addToQueueByType(filteredData, type as Play, playSongId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isReplaceQueueType(type)) {
|
||||||
|
confirmQueueChange(addToQueue);
|
||||||
} else {
|
} else {
|
||||||
logger.debug('Added to queue by type', {
|
addToQueue();
|
||||||
data: data.length,
|
|
||||||
filtered: filteredData.length,
|
|
||||||
type,
|
|
||||||
});
|
|
||||||
|
|
||||||
storeActions.addToQueueByType(filteredData, type as Play, playSongId);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[storeActions],
|
[confirmQueueChange, storeActions],
|
||||||
);
|
);
|
||||||
|
|
||||||
const addToQueueByFetch = useCallback(
|
const addToQueueByFetch = useCallback(
|
||||||
@@ -324,11 +385,19 @@ export const PlayerProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
filteredSongs = tagPlaylistContext(filteredSongs, resolvedContextId);
|
filteredSongs = tagPlaylistContext(filteredSongs, resolvedContextId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof type === 'object' && 'edge' in type && type.edge !== null) {
|
const addToQueue = () => {
|
||||||
const edge = type.edge === 'top' ? 'top' : 'bottom';
|
if (typeof type === 'object' && 'edge' in type && type.edge !== null) {
|
||||||
storeActions.addToQueueByUniqueId(filteredSongs, type.uniqueId, edge);
|
const edge = type.edge === 'top' ? 'top' : 'bottom';
|
||||||
|
storeActions.addToQueueByUniqueId(filteredSongs, type.uniqueId, edge);
|
||||||
|
} else {
|
||||||
|
storeActions.addToQueueByType(filteredSongs, type as Play);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isReplaceQueueType(type)) {
|
||||||
|
confirmQueueChange(addToQueue);
|
||||||
} else {
|
} else {
|
||||||
storeActions.addToQueueByType(filteredSongs, type as Play);
|
addToQueue();
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (instanceOfCancellationError(err)) {
|
if (instanceOfCancellationError(err)) {
|
||||||
@@ -347,7 +416,7 @@ export const PlayerProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[queryClient, storeActions, t],
|
[confirmQueueChange, queryClient, storeActions, t],
|
||||||
);
|
);
|
||||||
|
|
||||||
const addToQueueByListQuery = useCallback(
|
const addToQueueByListQuery = useCallback(
|
||||||
@@ -526,10 +595,12 @@ export const PlayerProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const clearQueue = useCallback(() => {
|
const clearQueue = useCallback(() => {
|
||||||
logger.debug('Cleared queue');
|
confirmQueueChange(() => {
|
||||||
|
logger.debug('Cleared queue');
|
||||||
|
|
||||||
storeActions.clearQueue();
|
storeActions.clearQueue();
|
||||||
}, [storeActions]);
|
});
|
||||||
|
}, [confirmQueueChange, storeActions]);
|
||||||
|
|
||||||
const clearSelected = useCallback(
|
const clearSelected = useCallback(
|
||||||
(items: QueueSong[]) => {
|
(items: QueueSong[]) => {
|
||||||
@@ -637,15 +708,17 @@ export const PlayerProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
|
|
||||||
const setQueue = useCallback(
|
const setQueue = useCallback(
|
||||||
(data: Song[], index?: number, position?: number) => {
|
(data: Song[], index?: number, position?: number) => {
|
||||||
logger.debug('Set queue', {
|
confirmQueueChange(() => {
|
||||||
data: data.length,
|
logger.debug('Set queue', {
|
||||||
index,
|
data: data.length,
|
||||||
position,
|
index,
|
||||||
});
|
position,
|
||||||
|
});
|
||||||
|
|
||||||
storeActions.setQueue(data, index, position);
|
storeActions.setQueue(data, index, position);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[storeActions],
|
[confirmQueueChange, storeActions],
|
||||||
);
|
);
|
||||||
|
|
||||||
const setSpeed = useCallback(
|
const setSpeed = useCallback(
|
||||||
|
|||||||
@@ -363,6 +363,24 @@ export const ApplicationSettings = memo(() => {
|
|||||||
isHidden: !isElectron(),
|
isHidden: !isElectron(),
|
||||||
title: t('setting.savePlayQueue'),
|
title: t('setting.savePlayQueue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Switch
|
||||||
|
aria-label={t('setting.confirmQueueChanges')}
|
||||||
|
checked={settings.confirmQueueChanges}
|
||||||
|
onChange={(event) => {
|
||||||
|
setSettings({
|
||||||
|
general: {
|
||||||
|
...settings,
|
||||||
|
confirmQueueChanges: event.currentTarget.checked,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.confirmQueueChanges', { context: 'description' }),
|
||||||
|
title: t('setting.confirmQueueChanges'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
control: (
|
control: (
|
||||||
<Switch
|
<Switch
|
||||||
|
|||||||
@@ -519,6 +519,7 @@ export const GeneralSettingsSchema = z.object({
|
|||||||
buttonSize: z.number(),
|
buttonSize: z.number(),
|
||||||
collections: z.array(CollectionSchema),
|
collections: z.array(CollectionSchema),
|
||||||
combinedLyricsAndVisualizer: z.boolean(),
|
combinedLyricsAndVisualizer: z.boolean(),
|
||||||
|
confirmQueueChanges: z.boolean(),
|
||||||
disabledContextMenu: z.record(z.string(), z.boolean()),
|
disabledContextMenu: z.record(z.string(), z.boolean()),
|
||||||
enableGridMultiSelect: z.boolean(),
|
enableGridMultiSelect: z.boolean(),
|
||||||
externalLinks: z.boolean(),
|
externalLinks: z.boolean(),
|
||||||
@@ -1302,6 +1303,7 @@ const initialState: SettingsState = {
|
|||||||
buttonSize: 15,
|
buttonSize: 15,
|
||||||
collections: [],
|
collections: [],
|
||||||
combinedLyricsAndVisualizer: false,
|
combinedLyricsAndVisualizer: false,
|
||||||
|
confirmQueueChanges: true,
|
||||||
disabledContextMenu: {},
|
disabledContextMenu: {},
|
||||||
enableGridMultiSelect: false,
|
enableGridMultiSelect: false,
|
||||||
externalLinks: true,
|
externalLinks: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user