Compare commits

..

6 Commits

Author SHA1 Message Date
jeffvli ba835bec3e update to v1.4.1 2026-02-01 20:38:17 -08:00
jeffvli 9850874dfd support viewing up to 5 previous releases in the release notes modal 2026-02-01 20:37:51 -08:00
jeffvli 51a8285ba2 adjust fullscreen player z-indexes back
- the modal needs to appear above
- instead, move the titlebar controls z-index under the fullscreen players
2026-02-01 20:28:47 -08:00
jeffvli e12150d026 match Save as Collection popover width to its target 2026-02-01 20:26:02 -08:00
jeffvli 54bc241984 add Save as Collection button to the filters modal 2026-02-01 20:25:43 -08:00
jeffvli a698f83c45 fix butterchurn preset display not updating 2026-02-01 20:14:25 -08:00
8 changed files with 74 additions and 14 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "feishin",
"version": "1.4.0",
"version": "1.4.1",
"description": "A modern self-hosted music player.",
"keywords": [
"subsonic",
@@ -2,7 +2,7 @@
position: absolute;
top: 0;
left: 0;
z-index: 5100;
z-index: 200;
display: flex;
justify-content: center;
padding: 2rem;
@@ -2,7 +2,7 @@
position: absolute;
top: 0;
left: 0;
z-index: 5100;
z-index: 200;
display: flex;
flex-direction: column;
width: 100%;
@@ -8,6 +8,7 @@ import { SubsonicAlbumFilters } from '/@/renderer/features/albums/components/sub
import { useAlbumListFilters } from '/@/renderer/features/albums/hooks/use-album-list-filters';
import { ComponentErrorBoundary } from '/@/renderer/features/shared/components/component-error-boundary';
import { FilterButton } from '/@/renderer/features/shared/components/filter-button';
import { SaveAsCollectionButton } from '/@/renderer/features/shared/components/save-as-collection-button';
import { JellyfinSongFilters } from '/@/renderer/features/songs/components/jellyfin-song-filters';
import { NavidromeSongFilters } from '/@/renderer/features/songs/components/navidrome-song-filters';
import { SubsonicSongFilters } from '/@/renderer/features/songs/components/subsonic-song-filters';
@@ -18,6 +19,7 @@ import { Button } from '/@/shared/components/button/button';
import { Group } from '/@/shared/components/group/group';
import { Modal } from '/@/shared/components/modal/modal';
import { Spinner } from '/@/shared/components/spinner/spinner';
import { Stack } from '/@/shared/components/stack/stack';
import { Text } from '/@/shared/components/text/text';
import { useDisclosure } from '/@/shared/hooks/use-disclosure';
import { LibraryItem, ServerType } from '/@/shared/types/domain-types';
@@ -103,6 +105,12 @@ export const ListFiltersModal = ({ isActive, itemType }: ListFiltersProps) => {
disableArtistFilter={disableArtistFilter}
disableGenreFilter={disableGenreFilter}
/>
<Stack p="md">
<SaveAsCollectionButton
fullWidth
itemType={itemType as LibraryItem.ALBUM | LibraryItem.SONG}
/>
</Stack>
</Modal>
</>
);
@@ -94,7 +94,7 @@ export const SaveAsCollectionButton = ({ fullWidth, itemType }: SaveAsCollection
}, []);
return (
<Popover onClose={handlers.close} opened={isOpen} position="bottom-start" width={320}>
<Popover onClose={handlers.close} opened={isOpen} width="target">
<Popover.Target>
{fullWidth ? (
<Button fullWidth onClick={handleOpen} variant="default">
@@ -479,7 +479,8 @@ const VisualizerInner = () => {
};
const CurrentPresetDisplay = () => {
const currentPreset = useSettingsStore.getState().visualizer.butterchurn.currentPreset;
const currentPreset = useSettingsStore((store) => store.visualizer.butterchurn.currentPreset);
return (
<Text className={styles['preset-overlay']} isNoSelect size="sm">
{currentPreset}
+59 -8
View File
@@ -2,7 +2,7 @@ import { closeAllModals, openModal } from '@mantine/modals';
import { useQuery } from '@tanstack/react-query';
import axios from 'axios';
import DOMPurify from 'dompurify';
import { useCallback, useEffect, useMemo } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import packageJson from '../../package.json';
@@ -12,32 +12,67 @@ import { Center } from '/@/shared/components/center/center';
import { Group } from '/@/shared/components/group/group';
import { Icon } from '/@/shared/components/icon/icon';
import { ScrollArea } from '/@/shared/components/scroll-area/scroll-area';
import { Select } from '/@/shared/components/select/select';
import { Spinner } from '/@/shared/components/spinner/spinner';
import { Stack } from '/@/shared/components/stack/stack';
import { Text } from '/@/shared/components/text/text';
import { useLocalStorage } from '/@/shared/hooks/use-local-storage';
const GITHUB_RELEASES_URL = 'https://api.github.com/repos/jeffvli/feishin/releases';
const RELEASES_TO_FETCH = 5;
interface GitHubRelease {
body: null | string;
name: null | string;
published_at: string;
tag_name: string;
}
interface ReleaseNotesContentProps {
onDismiss: () => void;
version: string;
}
function parseVersionFromTag(tagName: string): string {
return tagName.startsWith('v') ? tagName.slice(1) : tagName;
}
const ReleaseNotesContent = ({ onDismiss, version }: ReleaseNotesContentProps) => {
const { t } = useTranslation();
const [selectedVersion, setSelectedVersion] = useState(version);
// Fetch list of recent releases for the selector
const { data: releasesList = [] } = useQuery({
queryFn: async () => {
const response = await axios.get<GitHubRelease[]>(GITHUB_RELEASES_URL, {
params: { per_page: RELEASES_TO_FETCH },
});
return response.data;
},
queryKey: ['github-releases-list'],
retry: 2,
});
const releaseOptions = useMemo(() => {
const versions = releasesList.map((r) => parseVersionFromTag(r.tag_name));
if (!versions.includes(version)) {
versions.unshift(version);
}
return versions.slice(0, RELEASES_TO_FETCH).map((v) => ({ label: v, value: v }));
}, [releasesList, version]);
// Fetch release notes from GitHub API
const {
data: releaseData,
isError,
isLoading,
} = useQuery({
queryFn: async () => {
const response = await axios.get(
`https://api.github.com/repos/jeffvli/feishin/releases/tags/v${version}`,
const response = await axios.get<GitHubRelease>(
`${GITHUB_RELEASES_URL}/tags/v${selectedVersion}`,
);
return response.data;
},
queryKey: ['github-release', version],
queryKey: ['github-release', selectedVersion],
retry: 2,
});
@@ -49,7 +84,7 @@ const ReleaseNotesContent = ({ onDismiss, version }: ReleaseNotesContentProps) =
'https://api.github.com/markdown',
{
mode: 'gfm',
text: releaseData.body,
text: releaseData?.body ?? '',
},
{
headers: {
@@ -103,11 +138,19 @@ const ReleaseNotesContent = ({ onDismiss, version }: ReleaseNotesContentProps) =
if (isError || !releaseData) {
return (
<Stack gap="md">
{releaseOptions.length > 1 && (
<Select
data={releaseOptions}
label={t('common.version', { postProcess: 'sentenceCase' })}
onChange={(v) => v && setSelectedVersion(v)}
value={selectedVersion}
/>
)}
<Text size="sm">{t('error.genericError', { postProcess: 'sentenceCase' })}</Text>
<Group justify="flex-end">
<Button
component="a"
href={`https://github.com/jeffvli/feishin/releases/tag/v${version}`}
href={`https://github.com/jeffvli/feishin/releases/tag/v${selectedVersion}`}
onClick={onDismiss}
rightSection={<Icon icon="externalLink" />}
target="_blank"
@@ -125,6 +168,14 @@ const ReleaseNotesContent = ({ onDismiss, version }: ReleaseNotesContentProps) =
return (
<Stack gap="md">
{releaseOptions.length > 1 && (
<Select
data={releaseOptions}
label={t('common.version', { postProcess: 'sentenceCase' })}
onChange={(v) => v && setSelectedVersion(v)}
value={selectedVersion}
/>
)}
<ScrollArea
style={{
height: '400px',
@@ -140,7 +191,7 @@ const ReleaseNotesContent = ({ onDismiss, version }: ReleaseNotesContentProps) =
<Group justify="flex-end">
<Button
component="a"
href={`https://github.com/jeffvli/feishin/releases/tag/v${version}`}
href={`https://github.com/jeffvli/feishin/releases/tag/v${selectedVersion}`}
onClick={onDismiss}
rightSection={<Icon icon="externalLink" />}
target="_blank"
@@ -2,7 +2,7 @@
position: absolute;
top: 0;
right: 0;
z-index: 5000;
z-index: 195;
height: 65px;
-webkit-app-region: drag;
}