Compare commits
24 Commits
dev
...
backup-bef
| Author | SHA1 | Date | |
|---|---|---|---|
| 245620d4a7 | |||
| b426cc05ff | |||
| 699a27b0b9 | |||
| b5fc05382e | |||
| 43a51b165b | |||
| c64e40d56b | |||
| 6d1e4fb063 | |||
| eb56096992 | |||
| df248497ae | |||
| 7b036d8b6c | |||
| a0051576c6 | |||
| 52a00ca899 | |||
| 7710bf3cc9 | |||
| 9427a2a237 | |||
| 1f6ebf18a3 | |||
| c999c43288 | |||
| a352021dbc | |||
| 147602ad8c | |||
| 18f0811787 | |||
| 7a1c7e1eae | |||
| 7e6a28e4f4 | |||
| 36c1edd01e | |||
| 3839a1be2d | |||
| 0a0feb3748 |
@@ -22,18 +22,3 @@ build
|
|||||||
.turbo
|
.turbo
|
||||||
.github
|
.github
|
||||||
4xnored.png
|
4xnored.png
|
||||||
|
|
||||||
# Documentation and non-runtime files
|
|
||||||
docs/
|
|
||||||
CHANGELOG.md
|
|
||||||
cliff.toml
|
|
||||||
*.md
|
|
||||||
!README.md
|
|
||||||
|
|
||||||
# Docker compose files
|
|
||||||
docker-compose*.yml
|
|
||||||
Dockerfile
|
|
||||||
|
|
||||||
# Git and backup files
|
|
||||||
.git*
|
|
||||||
backup-*
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
NEXT_PUBLIC_COMMIT_SHA=477b172
|
NEXT_PUBLIC_COMMIT_SHA=b5fc053
|
||||||
|
|||||||
3
.github/workflows/github-release.yml
vendored
3
.github/workflows/github-release.yml
vendored
@@ -3,8 +3,7 @@ name: GitHub Release
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- '[0-9][0-9][0-9][0-9].[0-9][0-9].[0-9][0-9]'
|
- 'v*'
|
||||||
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|||||||
7
.github/workflows/nightly.yml
vendored
7
.github/workflows/nightly.yml
vendored
@@ -1,9 +1,13 @@
|
|||||||
name: Docker Image (Nightly)
|
name: Development Docker Image (Nightly)
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
# Run every night at 5:00 UTC
|
# Run every night at 5:00 UTC
|
||||||
- cron: '0 5 * * *'
|
- cron: '0 5 * * *'
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
workflow_dispatch: # Allow manual triggering
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: docker.io
|
REGISTRY: docker.io
|
||||||
@@ -85,6 +89,7 @@ jobs:
|
|||||||
tags: |
|
tags: |
|
||||||
type=raw,value=nightly
|
type=raw,value=nightly
|
||||||
type=raw,value=dev-latest
|
type=raw,value=dev-latest
|
||||||
|
type=sha,prefix=dev-
|
||||||
labels: |
|
labels: |
|
||||||
org.opencontainers.image.created=${{ github.event.head_commit.timestamp }}
|
org.opencontainers.image.created=${{ github.event.head_commit.timestamp }}
|
||||||
org.opencontainers.image.licenses=MIT
|
org.opencontainers.image.licenses=MIT
|
||||||
|
|||||||
9
.github/workflows/release.yml
vendored
9
.github/workflows/release.yml
vendored
@@ -68,7 +68,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags || 'latest' }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: |
|
labels: |
|
||||||
${{ steps.meta.outputs.labels }}
|
${{ steps.meta.outputs.labels }}
|
||||||
org.opencontainers.image.description=$(cat README.md | head -20 | tr '\n' ' ')
|
org.opencontainers.image.description=$(cat README.md | head -20 | tr '\n' ' ')
|
||||||
@@ -81,3 +81,10 @@ jobs:
|
|||||||
cache-to: |
|
cache-to: |
|
||||||
type=gha,mode=max,scope=deps-only
|
type=gha,mode=max,scope=deps-only
|
||||||
|
|
||||||
|
|
||||||
|
# - name: Docker Hub Description
|
||||||
|
# uses: peter-evans/dockerhub-description@v4
|
||||||
|
# with:
|
||||||
|
# username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||||
|
# password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
# repository: sillyangel/mice
|
||||||
|
|||||||
63
CHANGELOG.md
63
CHANGELOG.md
@@ -1,50 +1,45 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
## [unreleased]
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Features
|
||||||
|
- Fix menubar, add lazy loading, improve image quality, limit search results, filter browse artists
|
||||||
|
- Add pagination to library/songs and remove listening streaks
|
||||||
|
- Improve SortableQueueItem component with enhanced click handling and styling
|
||||||
|
- Add keyboard shortcuts and queue management features
|
||||||
|
- Add ListeningStreakCard component for tracking listening streaks
|
||||||
|
- Enhance OfflineManagement component with improved card styling and layout
|
||||||
|
- Implement Auto-Tagging Settings and MusicBrainz integration
|
||||||
|
- Enhance audio settings with ReplayGain, crossfade, and equalizer presets; add AudioSettingsDialog component
|
||||||
|
- Update cover art retrieval to use higher resolution images and enhance download manager with new features
|
||||||
|
- Enhance UI with Framer Motion animations for album artwork and artist icons
|
||||||
|
- Add page transition animations and notification settings for audio playback
|
||||||
|
- Implement offline library synchronization with IndexedDB
|
||||||
|
- Implement offline library management with IndexedDB support
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
- Use git commit SHA for versioning, fix audio playback resume, remove all streak localStorage code
|
- Use git commit SHA for versioning, fix audio playback resume, remove all streak localStorage code
|
||||||
- Docker startup issue, add GitHub release workflow and changelog config
|
- Docker startup issue, add GitHub release workflow and changelog config
|
||||||
|
|
||||||
### Documentation
|
### Refactoring
|
||||||
|
- Simplify service worker by removing offline download functionality
|
||||||
- Add CHANGELOG and commit rewriting script
|
- Remove all offline download and caching functionality
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- Implement offline library management with IndexedDB support
|
|
||||||
- Implement offline library synchronization with IndexedDB
|
|
||||||
- Add page transition animations and notification settings for audio playback
|
|
||||||
- Enhance UI with Framer Motion animations for album artwork and artist icons
|
|
||||||
- Update cover art retrieval to use higher resolution images and enhance download manager with new features
|
|
||||||
- Enhance audio settings with ReplayGain, crossfade, and equalizer presets; add AudioSettingsDialog component
|
|
||||||
- Implement Auto-Tagging Settings and MusicBrainz integration
|
|
||||||
- Enhance OfflineManagement component with improved card styling and layout
|
|
||||||
- Refactor service worker registration and enhance offline download manager with client-side checks
|
|
||||||
- Move service worker registration to a dedicated component for improved client-side handling
|
- Move service worker registration to a dedicated component for improved client-side handling
|
||||||
- Add ListeningStreakCard component for tracking listening streaks
|
- Refactor service worker registration and enhance offline download manager with client-side checks
|
||||||
- Add keyboard shortcuts and queue management features
|
|
||||||
- Improve SortableQueueItem component with enhanced click handling and styling
|
|
||||||
- Add pagination to library/songs and remove listening streaks
|
|
||||||
- Fix menubar, add lazy loading, improve image quality, limit search results, filter browse artists
|
|
||||||
|
|
||||||
### Miscellaneous
|
### Miscellaneous
|
||||||
|
- Organize documentation: move markdown files to docs/ folder
|
||||||
- C
|
|
||||||
- Merge pull request #39 from sillyangel/dependabot/npm_and_yarn/dev-99ea30e4b7
|
|
||||||
- Remove PostHog analytics and update dependencies to latest minor versions
|
|
||||||
- Update pnpm-lock.yaml to match new overrides configuration
|
|
||||||
- Update version to 2026.01.24 and add changelog for January 2026 release
|
- Update version to 2026.01.24 and add changelog for January 2026 release
|
||||||
- Organize documentation - move markdown files to docs/ folder
|
- Update pnpm-lock.yaml to match new overrides configuration
|
||||||
|
- Remove PostHog analytics and update dependencies to latest minor versions
|
||||||
### Refactoring
|
- Bump the dev group across 1 directory with 2 updates
|
||||||
|
- Merge pull request #39 from sillyangel/dependabot/npm_and_yarn/dev-99ea30e4b7
|
||||||
- Remove all offline download and caching functionality
|
|
||||||
- Simplify service worker by removing offline download functionality
|
|
||||||
|
|
||||||
### Styling
|
### Styling
|
||||||
|
|
||||||
- Update README formatting and improve content clarity
|
- Update README formatting and improve content clarity
|
||||||
|
|
||||||
|
## [2026.01.24] - 2026-01-24
|
||||||
|
|
||||||
|
Previous release before changelog tracking.
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ This is a "Modern" Navidrome (or Subsonic) client built with [Next.js](https://n
|
|||||||
- **Search** - Find music across your entire library
|
- **Search** - Find music across your entire library
|
||||||
- **Audio Player** with queue management
|
- **Audio Player** with queue management
|
||||||
- **Scrobbling** - Track your listening history
|
- **Scrobbling** - Track your listening history
|
||||||
<!-- - **Playlist Management** - Create and manage playlists -->
|
- **Playlist Management** - Create and manage playlists
|
||||||
|
|
||||||
### Preview
|
### Preview
|
||||||

|

|
||||||
@@ -34,8 +34,8 @@ This is a "Modern" Navidrome (or Subsonic) client built with [Next.js](https://n
|
|||||||
1. **Clone and install the required dependencies**
|
1. **Clone and install the required dependencies**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/sillyangel/mice.git
|
git clone https://github.com/sillyangel/project-still.git
|
||||||
cd mice/
|
cd project-still/
|
||||||
pnpm install
|
pnpm install
|
||||||
|
|
||||||
# or npm
|
# or npm
|
||||||
@@ -113,7 +113,7 @@ docker run -p 3000:3000 \
|
|||||||
sillyangel/mice:latest
|
sillyangel/mice:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
**For detailed Docker configuration, environment variables, troubleshooting, and advanced setups, see [DOCKER.md](./docs/DOCKER.md)**
|
**For detailed Docker configuration, environment variables, troubleshooting, and advanced setups, see [DOCKER.md](./DOCKER.md)**
|
||||||
|
|
||||||
## Tech Stack
|
## Tech Stack
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useParams } from 'next/navigation';
|
|||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { Album, Song } from '@/lib/navidrome';
|
import { Album, Song } from '@/lib/navidrome';
|
||||||
import { useNavidrome } from '@/app/components/NavidromeContext';
|
import { useNavidrome } from '@/app/components/NavidromeContext';
|
||||||
import { Play, Heart, Shuffle } from 'lucide-react';
|
import { Play, Heart } from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useAudioPlayer } from '@/app/components/AudioPlayerContext'
|
import { useAudioPlayer } from '@/app/components/AudioPlayerContext'
|
||||||
@@ -111,19 +111,6 @@ export default function AlbumPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleShuffleAlbum = async (): Promise<void> => {
|
|
||||||
if (!album || !tracklist.length) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Shuffle the tracklist
|
|
||||||
const shuffled = [...tracklist].sort(() => Math.random() - 0.5);
|
|
||||||
// Play the first shuffled track
|
|
||||||
await playAlbumFromTrack(album.id, shuffled[0].id);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to shuffle album:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const isCurrentlyPlaying = (song: Song): boolean => {
|
const isCurrentlyPlaying = (song: Song): boolean => {
|
||||||
return currentTrack?.id === song.id;
|
return currentTrack?.id === song.id;
|
||||||
};
|
};
|
||||||
@@ -137,13 +124,13 @@ export default function AlbumPage() {
|
|||||||
// Dynamic cover art URLs based on image size
|
// Dynamic cover art URLs based on image size
|
||||||
const getMobileCoverArtUrl = () => {
|
const getMobileCoverArtUrl = () => {
|
||||||
return album.coverArt && api
|
return album.coverArt && api
|
||||||
? api.getCoverArtUrl(album.coverArt, 300)
|
? api.getCoverArtUrl(album.coverArt, 600)
|
||||||
: '/default-user.jpg';
|
: '/default-user.jpg';
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDesktopCoverArtUrl = () => {
|
const getDesktopCoverArtUrl = () => {
|
||||||
return album.coverArt && api
|
return album.coverArt && api
|
||||||
? api.getCoverArtUrl(album.coverArt, 300)
|
? api.getCoverArtUrl(album.coverArt, 600)
|
||||||
: '/default-user.jpg';
|
: '/default-user.jpg';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -159,8 +146,8 @@ export default function AlbumPage() {
|
|||||||
<Image
|
<Image
|
||||||
src={getMobileCoverArtUrl()}
|
src={getMobileCoverArtUrl()}
|
||||||
alt={album.name}
|
alt={album.name}
|
||||||
width={300}
|
width={600}
|
||||||
height={300}
|
height={600}
|
||||||
className="rounded-md shadow-lg"
|
className="rounded-md shadow-lg"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -186,14 +173,6 @@ export default function AlbumPage() {
|
|||||||
>
|
>
|
||||||
<Play className="w-6 h-6" />
|
<Play className="w-6 h-6" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
className="w-12 h-12 rounded-full p-0"
|
|
||||||
onClick={handleShuffleAlbum}
|
|
||||||
title="Shuffle Album"
|
|
||||||
>
|
|
||||||
<Shuffle className="w-5 h-5" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -203,8 +182,8 @@ export default function AlbumPage() {
|
|||||||
<Image
|
<Image
|
||||||
src={getDesktopCoverArtUrl()}
|
src={getDesktopCoverArtUrl()}
|
||||||
alt={album.name}
|
alt={album.name}
|
||||||
width={300}
|
width={600}
|
||||||
height={300}
|
height={600}
|
||||||
className="rounded-md"
|
className="rounded-md"
|
||||||
/>
|
/>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -221,13 +200,8 @@ export default function AlbumPage() {
|
|||||||
{/* Controls row */}
|
{/* Controls row */}
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Button className="px-5" onClick={() => playAlbum(album.id)}>
|
<Button className="px-5" onClick={() => playAlbum(album.id)}>
|
||||||
<Play className="w-4 h-4 mr-2" />
|
|
||||||
Play
|
Play
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outline" className="px-5" onClick={handleShuffleAlbum}>
|
|
||||||
<Shuffle className="w-4 h-4 mr-2" />
|
|
||||||
Shuffle
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Album info */}
|
{/* Album info */}
|
||||||
|
|||||||
@@ -388,11 +388,6 @@ export const AudioPlayer: React.FC = () => {
|
|||||||
|
|
||||||
// Auto-play only if the track has the autoPlay flag and audio is initialized
|
// Auto-play only if the track has the autoPlay flag and audio is initialized
|
||||||
if (currentTrack.autoPlay && (!isMobile || audioInitialized)) {
|
if (currentTrack.autoPlay && (!isMobile || audioInitialized)) {
|
||||||
// Start crossfade fade-in if enabled
|
|
||||||
if (audioSettings.crossfadeDuration > 0 && audioEffects) {
|
|
||||||
audioEffects.startCrossfade();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a small delay for iOS compatibility
|
// Add a small delay for iOS compatibility
|
||||||
const playPromise = isMobile ?
|
const playPromise = isMobile ?
|
||||||
new Promise(resolve => setTimeout(resolve, 100)).then(() => audioCurrent.play()) :
|
new Promise(resolve => setTimeout(resolve, 100)).then(() => audioCurrent.play()) :
|
||||||
@@ -415,7 +410,7 @@ export const AudioPlayer: React.FC = () => {
|
|||||||
setIsPlaying(false);
|
setIsPlaying(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [currentTrack, onTrackStart, onTrackPlay, isMobile, audioInitialized, audioEffects, audioSettings.gaplessPlayback, audioSettings.replayGainEnabled, audioSettings.crossfadeDuration, queue]);
|
}, [currentTrack, onTrackStart, onTrackPlay, isMobile, audioInitialized, audioEffects, audioSettings.gaplessPlayback, audioSettings.replayGainEnabled, queue]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const audioCurrent = audioRef.current;
|
const audioCurrent = audioRef.current;
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import { useNavidromeConfig } from '@/app/components/NavidromeConfigContext';
|
|||||||
import { useTheme } from '@/app/components/ThemeProvider';
|
import { useTheme } from '@/app/components/ThemeProvider';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
import { FaServer, FaUser, FaLock, FaCheck, FaTimes, FaPalette, FaLastfm } from 'react-icons/fa';
|
import { FaServer, FaUser, FaLock, FaCheck, FaTimes, FaPalette, FaLastfm } from 'react-icons/fa';
|
||||||
import type { SidebarItem, SidebarLayoutSettings } from '@/hooks/use-sidebar-layout';
|
|
||||||
|
|
||||||
export function LoginForm({
|
export function LoginForm({
|
||||||
className,
|
className,
|
||||||
@@ -37,7 +36,6 @@ export function LoginForm({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const [isTesting, setIsTesting] = useState(false);
|
const [isTesting, setIsTesting] = useState(false);
|
||||||
const [hasError, setHasError] = useState(false);
|
|
||||||
|
|
||||||
// Settings for step 2
|
// Settings for step 2
|
||||||
const [scrobblingEnabled, setScrobblingEnabled] = useState(() => {
|
const [scrobblingEnabled, setScrobblingEnabled] = useState(() => {
|
||||||
@@ -47,8 +45,7 @@ export function LoginForm({
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sidebar settings with new defaults
|
// New settings - removed sidebar and standalone lastfm options
|
||||||
const [sidebarShortcuts, setSidebarShortcuts] = useState<'albums' | 'playlists' | 'both'>('playlists');
|
|
||||||
|
|
||||||
// Check if Navidrome is configured via environment variables
|
// Check if Navidrome is configured via environment variables
|
||||||
const hasEnvConfig = React.useMemo(() => {
|
const hasEnvConfig = React.useMemo(() => {
|
||||||
@@ -122,7 +119,6 @@ export function LoginForm({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!formData.serverUrl || !formData.username || !formData.password) {
|
if (!formData.serverUrl || !formData.username || !formData.password) {
|
||||||
setHasError(true);
|
|
||||||
toast({
|
toast({
|
||||||
title: "Missing Information",
|
title: "Missing Information",
|
||||||
description: "Please fill in all fields before proceeding.",
|
description: "Please fill in all fields before proceeding.",
|
||||||
@@ -132,7 +128,6 @@ export function LoginForm({
|
|||||||
}
|
}
|
||||||
|
|
||||||
setIsTesting(true);
|
setIsTesting(true);
|
||||||
setHasError(false);
|
|
||||||
try {
|
try {
|
||||||
// Strip trailing slash from server URL before testing
|
// Strip trailing slash from server URL before testing
|
||||||
const cleanServerUrl = formData.serverUrl.replace(/\/+$/, '');
|
const cleanServerUrl = formData.serverUrl.replace(/\/+$/, '');
|
||||||
@@ -159,7 +154,6 @@ export function LoginForm({
|
|||||||
// Move to settings step
|
// Move to settings step
|
||||||
setStep('settings');
|
setStep('settings');
|
||||||
} else {
|
} else {
|
||||||
setHasError(true);
|
|
||||||
toast({
|
toast({
|
||||||
title: "Connection Failed",
|
title: "Connection Failed",
|
||||||
description: "Could not connect to the server. Please check your settings.",
|
description: "Could not connect to the server. Please check your settings.",
|
||||||
@@ -167,7 +161,6 @@ export function LoginForm({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setHasError(true);
|
|
||||||
toast({
|
toast({
|
||||||
title: "Connection Error",
|
title: "Connection Error",
|
||||||
description: "An error occurred while testing the connection.",
|
description: "An error occurred while testing the connection.",
|
||||||
@@ -182,30 +175,6 @@ export function LoginForm({
|
|||||||
// Save all settings
|
// Save all settings
|
||||||
localStorage.setItem('lastfm-scrobbling-enabled', scrobblingEnabled.toString());
|
localStorage.setItem('lastfm-scrobbling-enabled', scrobblingEnabled.toString());
|
||||||
|
|
||||||
// Save sidebar settings with new defaults
|
|
||||||
const defaultSidebarItems: SidebarItem[] = [
|
|
||||||
{ id: 'home', label: 'Home', visible: true, icon: 'home', href: '/' },
|
|
||||||
{ id: 'queue', label: 'Queue', visible: true, icon: 'queue', href: '/queue' },
|
|
||||||
{ id: 'artists', label: 'Artists', visible: true, icon: 'artists', href: '/library/artists' },
|
|
||||||
{ id: 'albums', label: 'Albums', visible: true, icon: 'albums', href: '/library/albums' },
|
|
||||||
{ id: 'playlists', label: 'Playlists', visible: true, icon: 'playlists', href: '/library/playlists' },
|
|
||||||
{ id: 'favorites', label: 'Favorites', visible: true, icon: 'favorites', href: '/favorites' },
|
|
||||||
{ id: 'settings', label: 'Settings', visible: true, icon: 'settings', href: '/settings' },
|
|
||||||
// Hidden by default
|
|
||||||
{ id: 'search', label: 'Search', visible: false, icon: 'search', href: '/search' },
|
|
||||||
{ id: 'radio', label: 'Radio', visible: false, icon: 'radio', href: '/radio' },
|
|
||||||
{ id: 'browse', label: 'Browse', visible: false, icon: 'browse', href: '/browse' },
|
|
||||||
{ id: 'songs', label: 'Songs', visible: false, icon: 'songs', href: '/library/songs' },
|
|
||||||
{ id: 'history', label: 'History', visible: false, icon: 'history', href: '/history' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const sidebarSettings: SidebarLayoutSettings = {
|
|
||||||
items: defaultSidebarItems,
|
|
||||||
shortcuts: sidebarShortcuts,
|
|
||||||
showIcons: true,
|
|
||||||
};
|
|
||||||
localStorage.setItem('sidebar-layout-settings', JSON.stringify(sidebarSettings));
|
|
||||||
|
|
||||||
// Mark onboarding as complete
|
// Mark onboarding as complete
|
||||||
localStorage.setItem('onboarding-completed', '1.1.0');
|
localStorage.setItem('onboarding-completed', '1.1.0');
|
||||||
|
|
||||||
@@ -291,7 +260,6 @@ export function LoginForm({
|
|||||||
<SelectValue placeholder="Select a theme" />
|
<SelectValue placeholder="Select a theme" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="default">Default</SelectItem>
|
|
||||||
<SelectItem value="blue">Blue</SelectItem>
|
<SelectItem value="blue">Blue</SelectItem>
|
||||||
<SelectItem value="violet">Violet</SelectItem>
|
<SelectItem value="violet">Violet</SelectItem>
|
||||||
<SelectItem value="red">Red</SelectItem>
|
<SelectItem value="red">Red</SelectItem>
|
||||||
@@ -328,24 +296,6 @@ export function LoginForm({
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Sidebar Shortcuts */}
|
|
||||||
<div className="grid gap-3">
|
|
||||||
<Label>Sidebar Shortcuts</Label>
|
|
||||||
<Select value={sidebarShortcuts} onValueChange={(value: 'albums' | 'playlists' | 'both') => setSidebarShortcuts(value)}>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="playlists">Playlists Only</SelectItem>
|
|
||||||
<SelectItem value="albums">Albums Only</SelectItem>
|
|
||||||
<SelectItem value="both">Both</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Choose what shortcuts appear in your sidebar
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<Button onClick={handleFinishSetup} className="w-full">
|
<Button onClick={handleFinishSetup} className="w-full">
|
||||||
<FaCheck className="w-4 h-4 mr-2" />
|
<FaCheck className="w-4 h-4 mr-2" />
|
||||||
@@ -399,7 +349,6 @@ export function LoginForm({
|
|||||||
placeholder="https://your-navidrome-server.com"
|
placeholder="https://your-navidrome-server.com"
|
||||||
value={formData.serverUrl}
|
value={formData.serverUrl}
|
||||||
onChange={(e) => handleInputChange('serverUrl', e.target.value)}
|
onChange={(e) => handleInputChange('serverUrl', e.target.value)}
|
||||||
className={hasError ? "border-destructive focus-visible:ring-destructive" : ""}
|
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -414,7 +363,6 @@ export function LoginForm({
|
|||||||
placeholder="your-username"
|
placeholder="your-username"
|
||||||
value={formData.username}
|
value={formData.username}
|
||||||
onChange={(e) => handleInputChange('username', e.target.value)}
|
onChange={(e) => handleInputChange('username', e.target.value)}
|
||||||
className={hasError ? "border-destructive focus-visible:ring-destructive" : ""}
|
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -428,7 +376,6 @@ export function LoginForm({
|
|||||||
type="password"
|
type="password"
|
||||||
value={formData.password}
|
value={formData.password}
|
||||||
onChange={(e) => handleInputChange('password', e.target.value)}
|
onChange={(e) => handleInputChange('password', e.target.value)}
|
||||||
className={hasError ? "border-destructive focus-visible:ring-destructive" : ""}
|
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -41,6 +41,6 @@ commit_parsers = [
|
|||||||
]
|
]
|
||||||
protect_breaking_commits = false
|
protect_breaking_commits = false
|
||||||
filter_commits = false
|
filter_commits = false
|
||||||
tag_pattern = "[0-9][0-9][0-9][0-9].[0-9][0-9].[0-9][0-9]"
|
tag_pattern = "v[0-9].*"
|
||||||
topo_order = false
|
topo_order = false
|
||||||
sort_commits = "oldest"
|
sort_commits = "oldest"
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const ToastViewport = React.forwardRef<
|
|||||||
<ToastPrimitives.Viewport
|
<ToastPrimitives.Viewport
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed top-0 z-[9999] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
|
"fixed top-0 z-100 flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -126,7 +126,6 @@ export class AudioEffects {
|
|||||||
public setCrossfadeTime(seconds: number) {
|
public setCrossfadeTime(seconds: number) {
|
||||||
if (this.crossfadeGainNode) {
|
if (this.crossfadeGainNode) {
|
||||||
const now = this.context.currentTime;
|
const now = this.context.currentTime;
|
||||||
this.crossfadeGainNode.gain.cancelScheduledValues(now);
|
|
||||||
this.crossfadeGainNode.gain.setValueAtTime(1, now);
|
this.crossfadeGainNode.gain.setValueAtTime(1, now);
|
||||||
this.crossfadeGainNode.gain.linearRampToValueAtTime(0, now + seconds);
|
this.crossfadeGainNode.gain.linearRampToValueAtTime(0, now + seconds);
|
||||||
}
|
}
|
||||||
@@ -134,10 +133,7 @@ export class AudioEffects {
|
|||||||
|
|
||||||
public startCrossfade() {
|
public startCrossfade() {
|
||||||
if (this.crossfadeGainNode) {
|
if (this.crossfadeGainNode) {
|
||||||
const now = this.context.currentTime;
|
this.crossfadeGainNode.gain.value = 1;
|
||||||
this.crossfadeGainNode.gain.cancelScheduledValues(now);
|
|
||||||
this.crossfadeGainNode.gain.setValueAtTime(0, now);
|
|
||||||
this.crossfadeGainNode.gain.linearRampToValueAtTime(1, now + 0.5); // Fast fade in
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user