Files
feishin/src/renderer/features/settings/components/general/path-settings.tsx
T
jeffvli 61cc87e0b7 refactor song path replacement
- path replacement during runtime instead of during API normalization
- fix Navidrome API path not appending libraryPath which caused inconsistency between ND and Subsonic paths
2026-06-19 22:02:25 -07:00

100 lines
3.6 KiB
TypeScript

import { useQuery } from '@tanstack/react-query';
import { memo, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { songsQueries } from '/@/renderer/features/songs/api/songs-api';
import { useCurrentServerId, useGeneralSettings, useSettingsStoreActions } from '/@/renderer/store';
import { useResolvedSongPath } from '/@/renderer/utils/resolve-song-path';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { Code } from '/@/shared/components/code/code';
import { Group } from '/@/shared/components/group/group';
import { Stack } from '/@/shared/components/stack/stack';
import { TextInput } from '/@/shared/components/text-input/text-input';
import { Text } from '/@/shared/components/text/text';
import { useDebouncedCallback } from '/@/shared/hooks/use-debounced-callback';
import { Played } from '/@/shared/types/domain-types';
export const PathSettings = memo(() => {
const { t } = useTranslation();
const serverId = useCurrentServerId();
const randomSong = useQuery({
...songsQueries.random({
query: { limit: 1, played: Played.All },
serverId,
}),
gcTime: Infinity,
staleTime: Infinity,
});
const { pathReplace, pathReplaceWith } = useGeneralSettings();
const { setSettings } = useSettingsStoreActions();
const resolvedPreviewPath = useResolvedSongPath(randomSong.data?.items[0]?.path);
const [localPathReplace, setLocalPathReplace] = useState(pathReplace);
const [localPathReplaceWith, setLocalPathReplaceWith] = useState(pathReplaceWith);
useEffect(() => {
setLocalPathReplace(pathReplace);
}, [pathReplace]);
useEffect(() => {
setLocalPathReplaceWith(pathReplaceWith);
}, [pathReplaceWith]);
const debouncedSetPathReplace = useDebouncedCallback((value: string) => {
setSettings({
general: {
pathReplace: value,
},
});
}, 500);
const debouncedSetPathReplaceWith = useDebouncedCallback((value: string) => {
setSettings({
general: {
pathReplaceWith: value,
},
});
}, 500);
return (
<Stack>
<Group>
<Text>{t('setting.pathReplace')}</Text>
<ActionIcon
icon="refresh"
loading={randomSong.isFetching}
onClick={() => randomSong.refetch()}
size="xs"
variant="transparent"
/>
</Group>
<Code>
<Text isMuted size="md">
{resolvedPreviewPath || ''}
</Text>
</Code>
<Group grow>
<TextInput
onChange={(e) => {
const value = e.currentTarget.value;
setLocalPathReplace(value);
debouncedSetPathReplace(value);
}}
placeholder={t('setting.pathReplace_optionRemovePrefix')}
value={localPathReplace}
/>
<TextInput
onChange={(e) => {
const value = e.currentTarget.value;
setLocalPathReplaceWith(value);
debouncedSetPathReplaceWith(value);
}}
placeholder={t('setting.pathReplace_optionAddPrefix')}
value={localPathReplaceWith}
/>
</Group>
</Stack>
);
});