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",
|
||||
"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": {
|
||||
"create": "Create $t(entity.playlist, {\"count\": 1}) {{playlist}}",
|
||||
"input_playlists": "$t(entity.playlist, {\"count\": 2})",
|
||||
@@ -1075,6 +1079,8 @@
|
||||
"passwordStore": "Passwords/secret store",
|
||||
"playerFilters": "Filter songs from the queue",
|
||||
"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_optionCrossFade": "Crossfade",
|
||||
"playbackStyle_optionNormal": "Normal",
|
||||
|
||||
@@ -138,7 +138,7 @@ const QueuePlaybackIcons = ({ tableRef }: { tableRef: RefObject<ItemListHandle |
|
||||
variant="subtle"
|
||||
/>
|
||||
<ActionIcon
|
||||
icon="x"
|
||||
icon="delete"
|
||||
iconProps={{ size: 'lg' }}
|
||||
onClick={handleClearQueue}
|
||||
tooltip={{ label: t('action.clearQueue') }}
|
||||
|
||||
@@ -17,7 +17,12 @@ import {
|
||||
} from '/@/renderer/features/player/utils';
|
||||
import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-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 { shuffle as shuffleArray } from '/@/renderer/utils/shuffle';
|
||||
import { sortSongsByFetchedOrder } from '/@/shared/api/utils';
|
||||
@@ -164,6 +169,7 @@ export const PlayerProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const { t } = useTranslation();
|
||||
const queryClient = useQueryClient();
|
||||
const storeActions = usePlayerActions();
|
||||
const settingsActions = useSettingsStoreActions();
|
||||
const timeoutIds = useRef<null | Record<string, ReturnType<typeof setTimeout>>>({});
|
||||
|
||||
const [doNotShowAgain, setDoNotShowAgain] = useLocalStorage({
|
||||
@@ -171,6 +177,48 @@ export const PlayerProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
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> => {
|
||||
if (doNotShowAgain) {
|
||||
return Promise.resolve(true);
|
||||
@@ -225,29 +273,42 @@ export const PlayerProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
filteredData = tagPlaylistContext(filteredData, resolvedContextId);
|
||||
}
|
||||
|
||||
if (typeof type === 'object' && 'edge' in type && type.edge !== null) {
|
||||
const edge = type.edge === 'top' ? 'top' : 'bottom';
|
||||
const addToQueue = () => {
|
||||
if (typeof type === 'object' && 'edge' in type && type.edge !== null) {
|
||||
const edge = type.edge === 'top' ? 'top' : 'bottom';
|
||||
|
||||
logger.debug('Added to queue by data', {
|
||||
data: data.length,
|
||||
edge,
|
||||
filtered: filteredData.length,
|
||||
type,
|
||||
uniqueId: type.uniqueId,
|
||||
});
|
||||
logger.debug('Added to queue by data', {
|
||||
data: data.length,
|
||||
edge,
|
||||
filtered: filteredData.length,
|
||||
type,
|
||||
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 {
|
||||
logger.debug('Added to queue by type', {
|
||||
data: data.length,
|
||||
filtered: filteredData.length,
|
||||
type,
|
||||
});
|
||||
|
||||
storeActions.addToQueueByType(filteredData, type as Play, playSongId);
|
||||
addToQueue();
|
||||
}
|
||||
},
|
||||
[storeActions],
|
||||
[confirmQueueChange, storeActions],
|
||||
);
|
||||
|
||||
const addToQueueByFetch = useCallback(
|
||||
@@ -324,11 +385,19 @@ export const PlayerProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
filteredSongs = tagPlaylistContext(filteredSongs, resolvedContextId);
|
||||
}
|
||||
|
||||
if (typeof type === 'object' && 'edge' in type && type.edge !== null) {
|
||||
const edge = type.edge === 'top' ? 'top' : 'bottom';
|
||||
storeActions.addToQueueByUniqueId(filteredSongs, type.uniqueId, edge);
|
||||
const addToQueue = () => {
|
||||
if (typeof type === 'object' && 'edge' in type && type.edge !== null) {
|
||||
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 {
|
||||
storeActions.addToQueueByType(filteredSongs, type as Play);
|
||||
addToQueue();
|
||||
}
|
||||
} catch (err: any) {
|
||||
if (instanceOfCancellationError(err)) {
|
||||
@@ -347,7 +416,7 @@ export const PlayerProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
});
|
||||
}
|
||||
},
|
||||
[queryClient, storeActions, t],
|
||||
[confirmQueueChange, queryClient, storeActions, t],
|
||||
);
|
||||
|
||||
const addToQueueByListQuery = useCallback(
|
||||
@@ -526,10 +595,12 @@ export const PlayerProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
);
|
||||
|
||||
const clearQueue = useCallback(() => {
|
||||
logger.debug('Cleared queue');
|
||||
confirmQueueChange(() => {
|
||||
logger.debug('Cleared queue');
|
||||
|
||||
storeActions.clearQueue();
|
||||
}, [storeActions]);
|
||||
storeActions.clearQueue();
|
||||
});
|
||||
}, [confirmQueueChange, storeActions]);
|
||||
|
||||
const clearSelected = useCallback(
|
||||
(items: QueueSong[]) => {
|
||||
@@ -637,15 +708,17 @@ export const PlayerProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
|
||||
const setQueue = useCallback(
|
||||
(data: Song[], index?: number, position?: number) => {
|
||||
logger.debug('Set queue', {
|
||||
data: data.length,
|
||||
index,
|
||||
position,
|
||||
});
|
||||
confirmQueueChange(() => {
|
||||
logger.debug('Set queue', {
|
||||
data: data.length,
|
||||
index,
|
||||
position,
|
||||
});
|
||||
|
||||
storeActions.setQueue(data, index, position);
|
||||
storeActions.setQueue(data, index, position);
|
||||
});
|
||||
},
|
||||
[storeActions],
|
||||
[confirmQueueChange, storeActions],
|
||||
);
|
||||
|
||||
const setSpeed = useCallback(
|
||||
|
||||
@@ -363,6 +363,24 @@ export const ApplicationSettings = memo(() => {
|
||||
isHidden: !isElectron(),
|
||||
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: (
|
||||
<Switch
|
||||
|
||||
@@ -519,6 +519,7 @@ export const GeneralSettingsSchema = z.object({
|
||||
buttonSize: z.number(),
|
||||
collections: z.array(CollectionSchema),
|
||||
combinedLyricsAndVisualizer: z.boolean(),
|
||||
confirmQueueChanges: z.boolean(),
|
||||
disabledContextMenu: z.record(z.string(), z.boolean()),
|
||||
enableGridMultiSelect: z.boolean(),
|
||||
externalLinks: z.boolean(),
|
||||
@@ -1302,6 +1303,7 @@ const initialState: SettingsState = {
|
||||
buttonSize: 15,
|
||||
collections: [],
|
||||
combinedLyricsAndVisualizer: false,
|
||||
confirmQueueChanges: true,
|
||||
disabledContextMenu: {},
|
||||
enableGridMultiSelect: false,
|
||||
externalLinks: true,
|
||||
|
||||
Reference in New Issue
Block a user