mirror of
https://github.com/jeffvli/feishin.git
synced 2026-07-19 08:56:31 +02:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| da445b815d | |||
| c875146779 | |||
| 9806d2f553 | |||
| 18a7fd0731 |
@@ -1175,6 +1175,8 @@
|
|||||||
"webAudio": "Use web audio",
|
"webAudio": "Use web audio",
|
||||||
"windowBarStyle_description": "Select the style of the window bar",
|
"windowBarStyle_description": "Select the style of the window bar",
|
||||||
"windowBarStyle": "Window bar style",
|
"windowBarStyle": "Window bar style",
|
||||||
|
"windowBarTrackinfo": "Track info in Window Title",
|
||||||
|
"windowBarTrackinfo_description": "Show current track's title and artist, queue position, and Playing/Paused state in the Window's Title.",
|
||||||
"zoom_description": "Sets the zoom percentage for the application",
|
"zoom_description": "Sets the zoom percentage for the application",
|
||||||
"zoom": "Zoom percentage",
|
"zoom": "Zoom percentage",
|
||||||
"queryBuilder": "Query builder",
|
"queryBuilder": "Query builder",
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import {
|
|||||||
DeleteInternetRadioStationImageResponse,
|
DeleteInternetRadioStationImageResponse,
|
||||||
DeletePlaylistImageArgs,
|
DeletePlaylistImageArgs,
|
||||||
DeletePlaylistImageResponse,
|
DeletePlaylistImageResponse,
|
||||||
genreListSortMap,
|
|
||||||
InternalControllerEndpoint,
|
InternalControllerEndpoint,
|
||||||
playlistListSortMap,
|
playlistListSortMap,
|
||||||
PlaylistSongListArgs,
|
PlaylistSongListArgs,
|
||||||
@@ -596,26 +595,7 @@ export const NavidromeController: InternalControllerEndpoint = {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await ndApiClient(apiClientProps).getGenreList({
|
return SubsonicController.getGenreList(args);
|
||||||
query: {
|
|
||||||
_end: query.startIndex + (query.limit || 0),
|
|
||||||
_order: sortOrderMap.navidrome[query.sortOrder],
|
|
||||||
_sort: genreListSortMap.navidrome[query.sortBy],
|
|
||||||
_start: query.startIndex,
|
|
||||||
library_id: getLibraryId(query.musicFolderId),
|
|
||||||
name: query.searchTerm,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.status !== 200) {
|
|
||||||
throw new Error('Failed to get genre list');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
items: res.body.data.map((genre) => ndNormalize.genre(genre, apiClientProps.server)),
|
|
||||||
startIndex: query.startIndex || 0,
|
|
||||||
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
getImageRequest: SubsonicController.getImageRequest,
|
getImageRequest: SubsonicController.getImageRequest,
|
||||||
getImageUrl: SubsonicController.getImageUrl,
|
getImageUrl: SubsonicController.getImageUrl,
|
||||||
|
|||||||
@@ -1090,9 +1090,15 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (query.sortBy) {
|
switch (query.sortBy) {
|
||||||
|
case GenreListSort.ALBUM_COUNT:
|
||||||
|
results = orderBy(results, [(v) => v.albumCount], [sortOrder]);
|
||||||
|
break;
|
||||||
case GenreListSort.NAME:
|
case GenreListSort.NAME:
|
||||||
results = orderBy(results, [(v) => v.value.toLowerCase()], [sortOrder]);
|
results = orderBy(results, [(v) => v.value.toLowerCase()], [sortOrder]);
|
||||||
break;
|
break;
|
||||||
|
case GenreListSort.SONG_COUNT:
|
||||||
|
results = orderBy(results, [(v) => v.songCount], [sortOrder]);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,14 @@ import { useSortOrderFilter } from '/@/renderer/features/shared/hooks/use-sort-o
|
|||||||
import { FILTER_KEYS } from '/@/renderer/features/shared/utils';
|
import { FILTER_KEYS } from '/@/renderer/features/shared/utils';
|
||||||
import { setMultipleSearchParams } from '/@/renderer/utils/query-params';
|
import { setMultipleSearchParams } from '/@/renderer/utils/query-params';
|
||||||
import { runInUrlTransition } from '/@/renderer/utils/url-transition';
|
import { runInUrlTransition } from '/@/renderer/utils/url-transition';
|
||||||
import { AlbumArtistListSort } from '/@/shared/types/domain-types';
|
import { AlbumArtistListSort, ArtistListSort } from '/@/shared/types/domain-types';
|
||||||
import { ItemListKey } from '/@/shared/types/types';
|
import { ItemListKey } from '/@/shared/types/types';
|
||||||
|
|
||||||
export const useAlbumArtistListFilters = () => {
|
export const useAlbumArtistListFilters = () => {
|
||||||
const { sortBy } = useSortByFilter<AlbumArtistListSort>(null, ItemListKey.ALBUM_ARTIST);
|
const { sortBy } = useSortByFilter<AlbumArtistListSort>(
|
||||||
|
ArtistListSort.NAME,
|
||||||
|
ItemListKey.ALBUM_ARTIST,
|
||||||
|
);
|
||||||
|
|
||||||
const { sortOrder } = useSortOrderFilter(null, ItemListKey.ALBUM_ARTIST);
|
const { sortOrder } = useSortOrderFilter(null, ItemListKey.ALBUM_ARTIST);
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { ArtistListSort } from '/@/shared/types/domain-types';
|
|||||||
import { ItemListKey } from '/@/shared/types/types';
|
import { ItemListKey } from '/@/shared/types/types';
|
||||||
|
|
||||||
export const useArtistListFilters = () => {
|
export const useArtistListFilters = () => {
|
||||||
const { sortBy } = useSortByFilter<ArtistListSort>(null, ItemListKey.ARTIST);
|
const { sortBy } = useSortByFilter<ArtistListSort>(ArtistListSort.NAME, ItemListKey.ARTIST);
|
||||||
|
|
||||||
const { sortOrder } = useSortOrderFilter(null, ItemListKey.ARTIST);
|
const { sortOrder } = useSortOrderFilter(null, ItemListKey.ARTIST);
|
||||||
|
|
||||||
|
|||||||
@@ -81,6 +81,15 @@ export const useDiscordRpc = () => {
|
|||||||
privateModeRef.current = privateMode;
|
privateModeRef.current = privateMode;
|
||||||
}, [privateMode]);
|
}, [privateMode]);
|
||||||
|
|
||||||
|
// If the component is unmounted while RPC is enabled, quit RPC
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (previousEnabledRef.current) {
|
||||||
|
discordRpc?.quit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const setActivity = useCallback(
|
const setActivity = useCallback(
|
||||||
async (current: ActivityState, trigger: ActivityTrigger) => {
|
async (current: ActivityState, trigger: ActivityTrigger) => {
|
||||||
const song = current[0];
|
const song = current[0];
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { GenreListSort } from '/@/shared/types/domain-types';
|
|||||||
import { ItemListKey } from '/@/shared/types/types';
|
import { ItemListKey } from '/@/shared/types/types';
|
||||||
|
|
||||||
export const useGenreListFilters = () => {
|
export const useGenreListFilters = () => {
|
||||||
const { sortBy } = useSortByFilter<GenreListSort>(null, ItemListKey.GENRE);
|
const { sortBy } = useSortByFilter<GenreListSort>(GenreListSort.NAME, ItemListKey.GENRE);
|
||||||
|
|
||||||
const { sortOrder } = useSortOrderFilter(null, ItemListKey.GENRE);
|
const { sortOrder } = useSortOrderFilter(null, ItemListKey.GENRE);
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,10 @@ import { PlaylistListSort } from '/@/shared/types/domain-types';
|
|||||||
import { ItemListKey } from '/@/shared/types/types';
|
import { ItemListKey } from '/@/shared/types/types';
|
||||||
|
|
||||||
export const usePlaylistListFilters = () => {
|
export const usePlaylistListFilters = () => {
|
||||||
const sortByFilter = useSortByFilter<PlaylistListSort>(null, ItemListKey.PLAYLIST);
|
const sortByFilter = useSortByFilter<PlaylistListSort>(
|
||||||
|
PlaylistListSort.NAME,
|
||||||
|
ItemListKey.PLAYLIST,
|
||||||
|
);
|
||||||
const sortOrderFilter = useSortOrderFilter(null, ItemListKey.PLAYLIST);
|
const sortOrderFilter = useSortOrderFilter(null, ItemListKey.PLAYLIST);
|
||||||
|
|
||||||
const { searchTerm, setSearchTerm } = useSearchTermFilter('');
|
const { searchTerm, setSearchTerm } = useSearchTermFilter('');
|
||||||
|
|||||||
@@ -63,6 +63,30 @@ export const WindowSettings = memo(() => {
|
|||||||
isHidden: !isElectron(),
|
isHidden: !isElectron(),
|
||||||
title: t('setting.windowBarStyle'),
|
title: t('setting.windowBarStyle'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Switch
|
||||||
|
aria-label="Toggle track info in Window Bar"
|
||||||
|
defaultChecked={settings.windowBarTrackinfo}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (!e) return;
|
||||||
|
setSettings({
|
||||||
|
window: {
|
||||||
|
windowBarTrackinfo: e.currentTarget.checked,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.windowBarTrackinfo', {
|
||||||
|
context: 'description',
|
||||||
|
}),
|
||||||
|
// tab is hidden entirely right now
|
||||||
|
// but if it was shown we would want to show this option
|
||||||
|
// as it also controls the tab title in web
|
||||||
|
isHidden: false,
|
||||||
|
title: t('setting.windowBarTrackinfo'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
control: (
|
control: (
|
||||||
<Switch
|
<Switch
|
||||||
|
|||||||
@@ -823,18 +823,38 @@ const GENRE_LIST_FILTERS: Partial<
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
[ServerType.NAVIDROME]: [
|
[ServerType.NAVIDROME]: [
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.albumCount'),
|
||||||
|
value: GenreListSort.ALBUM_COUNT,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
defaultOrder: SortOrder.ASC,
|
defaultOrder: SortOrder.ASC,
|
||||||
name: i18n.t('filter.name'),
|
name: i18n.t('filter.name'),
|
||||||
value: GenreListSort.NAME,
|
value: GenreListSort.NAME,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.songCount'),
|
||||||
|
value: GenreListSort.SONG_COUNT,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
[ServerType.SUBSONIC]: [
|
[ServerType.SUBSONIC]: [
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.albumCount'),
|
||||||
|
value: GenreListSort.ALBUM_COUNT,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
defaultOrder: SortOrder.ASC,
|
defaultOrder: SortOrder.ASC,
|
||||||
name: i18n.t('filter.name'),
|
name: i18n.t('filter.name'),
|
||||||
value: GenreListSort.NAME,
|
value: GenreListSort.NAME,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.albumCount'),
|
||||||
|
value: GenreListSort.SONG_COUNT,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { parseStringParam, setSearchParam } from '/@/renderer/utils/query-params
|
|||||||
import { runInUrlTransition } from '/@/renderer/utils/url-transition';
|
import { runInUrlTransition } from '/@/renderer/utils/url-transition';
|
||||||
import { ItemListKey } from '/@/shared/types/types';
|
import { ItemListKey } from '/@/shared/types/types';
|
||||||
|
|
||||||
export const useSortByFilter = <TSortBy>(defaultValue: null | string, listKey: ItemListKey) => {
|
export const useSortByFilter = <TSortBy>(defaultValue: string, listKey: ItemListKey) => {
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
const { getFilter, setFilter } = useListFilterPersistence(server.id, listKey);
|
const { getFilter, setFilter } = useListFilterPersistence(server.id, listKey);
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|||||||
@@ -14,7 +14,13 @@ import macMin from './assets/min-mac.png';
|
|||||||
import styles from './window-bar.module.css';
|
import styles from './window-bar.module.css';
|
||||||
|
|
||||||
import { useRadioPlayer } from '/@/renderer/features/radio/hooks/use-radio-player';
|
import { useRadioPlayer } from '/@/renderer/features/radio/hooks/use-radio-player';
|
||||||
import { useAppStore, usePlayerData, usePlayerStatus, useWindowSettings } from '/@/renderer/store';
|
import {
|
||||||
|
useAppStore,
|
||||||
|
usePlayerData,
|
||||||
|
usePlayerStatus,
|
||||||
|
useWindowBarTrackinfo,
|
||||||
|
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';
|
||||||
|
|
||||||
@@ -130,6 +136,8 @@ const MacOsControls = ({ controls, title }: WindowBarControlsProps) => {
|
|||||||
export const WindowBar = () => {
|
export const WindowBar = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { windowBarStyle } = useWindowSettings();
|
const { windowBarStyle } = useWindowSettings();
|
||||||
|
const windowBarTrackinfo = useWindowBarTrackinfo();
|
||||||
|
|
||||||
const playerStatus = usePlayerStatus();
|
const playerStatus = usePlayerStatus();
|
||||||
const privateMode = useAppStore((state) => state.privateMode);
|
const privateMode = useAppStore((state) => state.privateMode);
|
||||||
const handleMinimize = () => minimize();
|
const handleMinimize = () => minimize();
|
||||||
@@ -153,6 +161,10 @@ export const WindowBar = () => {
|
|||||||
const title = useMemo(() => {
|
const title = useMemo(() => {
|
||||||
const privateModeString = privateMode ? t('page.windowBar.privateMode') : '';
|
const privateModeString = privateMode ? t('page.windowBar.privateMode') : '';
|
||||||
|
|
||||||
|
if (!windowBarTrackinfo) {
|
||||||
|
return `Feishin${privateMode ? ` ${privateModeString}` : ''}`;
|
||||||
|
}
|
||||||
|
|
||||||
// Show radio information if radio is active
|
// Show radio information if radio is active
|
||||||
if (isRadioActive) {
|
if (isRadioActive) {
|
||||||
const radioStatusString = !isRadioPlaying ? t('page.windowBar.paused') : '';
|
const radioStatusString = !isRadioPlaying ? t('page.windowBar.paused') : '';
|
||||||
@@ -194,6 +206,7 @@ export const WindowBar = () => {
|
|||||||
queueLength,
|
queueLength,
|
||||||
stationName,
|
stationName,
|
||||||
t,
|
t,
|
||||||
|
windowBarTrackinfo,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -678,6 +678,7 @@ const WindowSettingsSchema = z.object({
|
|||||||
startMinimized: z.boolean(),
|
startMinimized: z.boolean(),
|
||||||
tray: z.boolean(),
|
tray: z.boolean(),
|
||||||
windowBarStyle: z.nativeEnum(Platform),
|
windowBarStyle: z.nativeEnum(Platform),
|
||||||
|
windowBarTrackinfo: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const QueryValueInputTypeSchema = z.enum([
|
const QueryValueInputTypeSchema = z.enum([
|
||||||
@@ -2014,6 +2015,7 @@ const initialState: SettingsState = {
|
|||||||
startMinimized: false,
|
startMinimized: false,
|
||||||
tray: true,
|
tray: true,
|
||||||
windowBarStyle: platformDefaultWindowBarStyle,
|
windowBarStyle: platformDefaultWindowBarStyle,
|
||||||
|
windowBarTrackinfo: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2560,6 +2562,9 @@ export const useWindowSettings = () => useSettingsStore((state) => state.window,
|
|||||||
export const useWindowBarStyle = () =>
|
export const useWindowBarStyle = () =>
|
||||||
useSettingsStore((state) => state.window.windowBarStyle, shallow);
|
useSettingsStore((state) => state.window.windowBarStyle, shallow);
|
||||||
|
|
||||||
|
export const useWindowBarTrackinfo = () =>
|
||||||
|
useSettingsStore((state) => state.window.windowBarTrackinfo, shallow);
|
||||||
|
|
||||||
export const useHotkeySettings = () => useSettingsStore((state) => state.hotkeys, shallow);
|
export const useHotkeySettings = () => useSettingsStore((state) => state.hotkeys, shallow);
|
||||||
|
|
||||||
export const useHotkeyBindings = () => useSettingsStore((state) => state.hotkeys.bindings, shallow);
|
export const useHotkeyBindings = () => useSettingsStore((state) => state.hotkeys.bindings, shallow);
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ export enum NDAlbumListSort {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum NDGenreListSort {
|
export enum NDGenreListSort {
|
||||||
|
ALBUM_COUNT = 'albumCount',
|
||||||
NAME = 'name',
|
NAME = 'name',
|
||||||
|
SONG_COUNT = 'songCount',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum NDPlaylistListSort {
|
export enum NDPlaylistListSort {
|
||||||
@@ -754,6 +756,8 @@ const tag = z.object({
|
|||||||
const tagList = z.array(tag);
|
const tagList = z.array(tag);
|
||||||
|
|
||||||
export enum NDTagListSort {
|
export enum NDTagListSort {
|
||||||
|
ALBUM_COUNT = 'albumCount',
|
||||||
|
SONG_COUNT = 'songCount',
|
||||||
TAG_VALUE = 'tagValue',
|
TAG_VALUE = 'tagValue',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -155,7 +155,9 @@ export enum ExternalType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum GenreListSort {
|
export enum GenreListSort {
|
||||||
|
ALBUM_COUNT = 'albumCount',
|
||||||
NAME = 'name',
|
NAME = 'name',
|
||||||
|
SONG_COUNT = 'songCount',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ImageType {
|
export enum ImageType {
|
||||||
@@ -166,7 +168,9 @@ export enum ImageType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum TagListSort {
|
export enum TagListSort {
|
||||||
|
ALBUM_COUNT = 'albumCount',
|
||||||
NAME = 'name',
|
NAME = 'name',
|
||||||
|
SONG_COUNT = 'songCount',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Album = {
|
export type Album = {
|
||||||
@@ -430,19 +434,25 @@ type BaseEndpointArgs = {
|
|||||||
|
|
||||||
type GenreListSortMap = {
|
type GenreListSortMap = {
|
||||||
jellyfin: Record<GenreListSort, JFGenreListSort | undefined>;
|
jellyfin: Record<GenreListSort, JFGenreListSort | undefined>;
|
||||||
navidrome: Record<GenreListSort, NDGenreListSort | undefined>;
|
navidrome: Record<GenreListSort, NDGenreListSort>;
|
||||||
subsonic: Record<UserListSort, undefined>;
|
subsonic: Record<GenreListSort, undefined>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const genreListSortMap: GenreListSortMap = {
|
export const genreListSortMap: GenreListSortMap = {
|
||||||
jellyfin: {
|
jellyfin: {
|
||||||
|
albumCount: undefined,
|
||||||
name: JFGenreListSort.NAME,
|
name: JFGenreListSort.NAME,
|
||||||
|
songCount: undefined,
|
||||||
},
|
},
|
||||||
navidrome: {
|
navidrome: {
|
||||||
|
albumCount: NDGenreListSort.NAME,
|
||||||
name: NDGenreListSort.NAME,
|
name: NDGenreListSort.NAME,
|
||||||
|
songCount: NDGenreListSort.NAME,
|
||||||
},
|
},
|
||||||
subsonic: {
|
subsonic: {
|
||||||
|
albumCount: undefined,
|
||||||
name: undefined,
|
name: undefined,
|
||||||
|
songCount: undefined,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -454,13 +464,19 @@ type TagListSortMap = {
|
|||||||
|
|
||||||
export const tagListSortMap: TagListSortMap = {
|
export const tagListSortMap: TagListSortMap = {
|
||||||
jellyfin: {
|
jellyfin: {
|
||||||
|
albumCount: undefined,
|
||||||
name: undefined,
|
name: undefined,
|
||||||
|
songCount: undefined,
|
||||||
},
|
},
|
||||||
navidrome: {
|
navidrome: {
|
||||||
|
albumCount: NDTagListSort.ALBUM_COUNT,
|
||||||
name: NDTagListSort.TAG_VALUE,
|
name: NDTagListSort.TAG_VALUE,
|
||||||
|
songCount: NDTagListSort.SONG_COUNT,
|
||||||
},
|
},
|
||||||
subsonic: {
|
subsonic: {
|
||||||
|
albumCount: undefined,
|
||||||
name: undefined,
|
name: undefined,
|
||||||
|
songCount: undefined,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user