Skip to content

Commit

Permalink
🐛 fix: now-playing quit animation
Browse files Browse the repository at this point in the history
Signed-off-by: SimonShiki <sinangentoo@gmail.com>
  • Loading branch information
SimonShiki committed Aug 13, 2024
1 parent f0a54e1 commit cbab1a3
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 79 deletions.
2 changes: 1 addition & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"title": "Cicadas",
"width": 800,
"height": 600,
"minWidth": 600,
"minWidth": 770,
"minHeight": 540,
"resizable": true,
"fullscreen": false,
Expand Down
89 changes: 16 additions & 73 deletions src/components/now-playing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@ import defaultCover from '../assets/default-cover.png';
import Button from './base/button';
import * as player from '../utils/player';
import Progress from './base/progress';
import { useState, useCallback } from 'react';
import { useState, useCallback, useEffect } from 'react';
import Slider from './base/slider';
import Tooltip from './base/tooltip';
import { Virtuoso } from 'react-virtuoso';
import Lyrics from './lyrics';
import { focusAtom } from 'jotai-optics';
import { settingsJotai } from '../jotais/settings';
import { FormattedMessage } from 'react-intl';
import PlaylistTooltip from './playlist-tooltip';

const playModeIconMap: Record<PlayMode, string> = {
list: 'i-fluent:arrow-repeat-all-off-20-regular',
Expand All @@ -34,29 +33,29 @@ function formatMilliseconds (ms: number): string {
const streamingJotai = focusAtom(settingsJotai, (optic) => optic.prop('streaming'));

export default function NowPlaying () {
const [fullscreen, setFullscreen] = useAtom(nowPlayingPageJotai);
const [globalFullscreen, setGlobalFullscreen] = useAtom(nowPlayingPageJotai);
const [localFullscreen, setLocalFullscreen] = useState(globalFullscreen);
const [playing, setPlaying] = useAtom(playingJotai);
const playMode = useAtomValue(playModeJotai);
const [volume, setVolume] = useAtom(volumeJotai);
const playlist = useAtomValue(playlistJotai);
const barOpen = useAtomValue(nowPlayingBarJotai);
const song = useAtomValue(currentSongJotai);
const progress = useAtomValue(progressJotai);
const buffering = useAtomValue(bufferingJotai);
const streaming = useAtomValue(streamingJotai);

const [isAnimating, setIsAnimating] = useState(false);

const handlePlayPause = useCallback(() => {
setPlaying(playing => !playing);
}, [playing, setPlaying]);
}, [setPlaying]);

const toggleFullscreen = useCallback(() => {
useEffect(() => {
setIsAnimating(true);
setFullscreen(!fullscreen);
setLocalFullscreen(globalFullscreen);
// Reset animation state after animation completes
setTimeout(() => setIsAnimating(false), 300); // 300ms matches the animation duration
}, [fullscreen, setFullscreen]);
}, [globalFullscreen]);

const handleChangePlayProgress = useCallback(async (value: number) => {
const actualElapsedSecs = value * song!.duration! / 100000;
Expand All @@ -72,7 +71,9 @@ export default function NowPlaying () {
<Progress value={progress * 1000} infinite={buffering} max={song.duration} height='h-0.5' />
<div className='flex items-center m-4 justify-between'>
<div className='flex flex-row gap-4 w-1/3'>
<img draggable={false} src={song.cover ?? defaultCover} alt={song.name} className='rounded-md w-10 h-10 cursor-pointer' onClick={toggleFullscreen} />
<img draggable={false} src={song.cover ?? defaultCover} alt={song.name} className='rounded-md w-10 h-10 cursor-pointer' onClick={() => {
setGlobalFullscreen(true);
}} />
<div className='flex flex-col gap-1 lg:max-w-60 overflow-hidden *:text-truncate'>
<span className='color-text-pri font-size-sm font-500'>{song.name}</span>
<span className='color-text-sec font-size-xs'>{song.album}</span>
Expand Down Expand Up @@ -103,44 +104,14 @@ export default function NowPlaying () {
>
<span className='i-fluent:speaker-2-20-regular w-5 h-5 cursor-pointer' />
</Tooltip>
<Tooltip
content={(
<div className='flex flex-col h-64 w-72 gap-4 p-2'>
<span className='font-(500 size-lg)'>
<FormattedMessage defaultMessage='Playlist ({total})' values={{ total: playlist.length }} />
</span>
<Virtuoso
totalCount={playlist.length}
itemContent={(index) => {
const thatSong = playlist[index];
return (
<div onDoubleClickCapture={() => {
player.setCurrentSong(thatSong);
}} className='flex gap-2 py-2 border-b-(1 solid outline-pri) hover:bg-bg-pri cursor-pointer transition-colors items-center' onDoubleClick={() => player.setCurrentSong(song)}>
<img draggable={false} src={thatSong.cover ?? defaultCover} alt={thatSong.name} className='rounded-md w-8 h-8' />
<div className='flex flex-col *:text-truncate max-w-56'>
<span className={`color-text-pri font-size-xs font-500 ${song.id === thatSong.id ? 'color-fg-pri font-600' : ''}`}>{thatSong.name}</span>
<span className={`color-text-sec font-size-xs ${song.id === thatSong.id ? '!color-fg-pri' : ''}`}>{thatSong.album}</span>
</div>
</div>
);
}}
/>
</div>
)}
className='flex w-5 h-5'
placement='top-right'
trigger='click'
>
<span className='i-fluent:navigation-play-20-regular w-5 h-5 cursor-pointer' />
</Tooltip>
<PlaylistTooltip />
</div>
</div>
</Card>
</div>
{(fullscreen || isAnimating) && (
{(localFullscreen || isAnimating) && (
<div
className={`translate-z-0 absolute top-0 left-0 w-full h-full ms-bezier bg-cover animate-duration-300 ${fullscreen ? 'animate-slide-in-up' : 'animate-slide-out-down'}`}
className={`translate-z-0 absolute top-0 left-0 w-full h-full ms-bezier bg-cover animate-duration-300 ${localFullscreen ? 'animate-slide-in-up' : 'animate-slide-out-down'}`}
style={{ backgroundImage: `url("${song.cover}")` }}
>
<div className='w-full h-full translate-z-0 backdrop-filter backdrop-blur-256 bg-black bg-op-40 flex flex-col items-center justify-center'>
Expand Down Expand Up @@ -186,35 +157,7 @@ export default function NowPlaying () {
>
<span className='i-fluent:speaker-2-20-filled cursor-pointer w-5 h-5' />
</Tooltip>
<Tooltip
content={(
<div className='flex flex-col h-64 w-72 gap-4 p-2'>
<span className='font-(500 size-lg)'>
<FormattedMessage defaultMessage='Playlist ({total})' values={{ total: playlist.length }} />
</span>
<Virtuoso
totalCount={playlist.length}
itemContent={(index) => {
const thatSong = playlist[index];
return (
<div className='flex gap-2 py-2 border-b-(1 solid outline-pri) hover:bg-bg-pri cursor-pointer transition-colors items-center' onDoubleClick={() => player.setCurrentSong(song)}>
<img draggable={false} src={thatSong.cover ?? defaultCover} alt={thatSong.name} className='rounded-md w-8 h-8' />
<div className='flex flex-col *:text-truncate max-w-56'>
<span className={`color-text-pri font-size-xs font-500 ${song.id === thatSong.id ? 'color-fg-pri font-600' : ''}`}>{thatSong.name}</span>
<span className={`color-text-sec font-size-xs ${song.id === thatSong.id ? '!color-fg-pri' : ''}`}>{thatSong.album}</span>
</div>
</div>
);
}}
/>
</div>
)}
className='flex w-5 h-5'
trigger='click'
placement='top-right'
>
<span className='i-fluent:navigation-play-20-filled cursor-pointer w-5 h-5' />
</Tooltip>
<PlaylistTooltip />
</div>
</div>
</div>
Expand Down
45 changes: 45 additions & 0 deletions src/components/playlist-tooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import Tooltip from './base/tooltip';
import { Virtuoso } from 'react-virtuoso';
import { FormattedMessage } from 'react-intl';
import defaultCover from '../assets/default-cover.png';
import * as player from '../utils/player';
import { useAtomValue } from 'jotai';
import { currentSongJotai, playlistJotai } from '../jotais/play';

export default function PlaylistTooltip () {
const playlist = useAtomValue(playlistJotai);
const currentSong = useAtomValue(currentSongJotai);
return (
<Tooltip
content={(
<div className='flex flex-col h-64 w-72 gap-4 p-2'>
<span className='font-(500 size-lg)'>
<FormattedMessage defaultMessage='Playlist ({total})' values={{ total: playlist.length }} />
</span>
<Virtuoso
totalCount={playlist.length}
itemContent={(index) => {
const thatSong = playlist[index];
return (
<div onDoubleClickCapture={() => {
player.setCurrentSong(thatSong);
}} className='flex gap-2 py-2 border-b-(1 solid outline-pri) hover:bg-bg-pri cursor-pointer transition-colors items-center'>
<img draggable={false} src={thatSong.cover ?? defaultCover} alt={thatSong.name} className='rounded-md w-8 h-8' />
<div className='flex flex-col *:text-truncate max-w-56'>
<span className={`color-text-pri font-size-xs font-500 ${currentSong!.id === thatSong.id ? 'color-fg-pri font-600' : ''}`}>{thatSong.name}</span>
<span className={`color-text-sec font-size-xs ${currentSong!.id === thatSong.id ? '!color-fg-pri' : ''}`}>{thatSong.album}</span>
</div>
</div>
);
}}
/>
</div>
)}
className='flex w-5 h-5'
placement='top-right'
trigger='click'
>
<span className='i-fluent:navigation-play-20-regular w-5 h-5 cursor-pointer' />
</Tooltip>
);
}
5 changes: 5 additions & 0 deletions src/jotais/settings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { focusAtom } from 'jotai-optics';
import { atomWithStorage } from 'jotai/utils';
import { SortOptions } from '../utils/sort';

export interface StorageConfig<Idenfiter extends string> {
identifer: Idenfiter;
Expand All @@ -21,3 +22,7 @@ export const settingsJotai = atomWithStorage<Setting>('settings', {

export const localeJotai = focusAtom(settingsJotai, (optic) => optic.prop('locale'));
export const storagesConfigJotai = focusAtom(settingsJotai, (optic) => optic.prop('storages'));

export const sortOptionJotai = atomWithStorage<SortOptions>('sortOption', 'a-z', undefined, {
getOnInit: true
});
5 changes: 3 additions & 2 deletions src/pages/local.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { storagesJotai } from '../jotais/storage';
import { useAtomValue } from 'jotai';
import { useAtom, useAtomValue } from 'jotai';
import SongItem from '../components/song-item';
import Button from '../components/base/button';
import Select from '../components/base/select';
Expand All @@ -13,6 +13,7 @@ import { useCallback, useEffect, useState } from 'react';
import { filterSongList, SortOptions, sortSongList } from '../utils/sort';
import { nowPlayingBarJotai } from '../jotais/play';
import { FormattedMessage, useIntl } from 'react-intl';
import { sortOptionJotai } from '../jotais/settings';

const localStorageJotai = focusAtom(storagesJotai, (optic) => optic.prop('local'));
const songlistJotai = focusAtom(localStorageJotai, (optic) => optic.prop('songList'));
Expand All @@ -24,7 +25,7 @@ export default function Local () {
const barOpen = useAtomValue(nowPlayingBarJotai);
const scanned = useAtomValue(scannedJotai);
const [keyword, setKeyword] = useState('');
const [sortBy, setSortBy] = useState<SortOptions>('a-z');
const [sortBy, setSortBy] = useAtom(sortOptionJotai);
const intl = useIntl();
const sortOptions = [
{ value: 'default', label: intl.formatMessage({ defaultMessage: 'Default'}) } as const,
Expand Down
5 changes: 3 additions & 2 deletions src/pages/song.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ import { nowPlayingBarJotai } from '../jotais/play';
import * as player from '../utils/player';
import { scannedJotai } from '../jotais/storage';
import type { Song } from '../jotais/storage';
import { SortOptions, filterSongList, sortSongList } from '../utils/sort';
import { filterSongList, sortSongList } from '../utils/sort';
import { libraryJotai, songlistsJotai } from '../jotais/library';
import { FormattedMessage, useIntl } from 'react-intl';
import { Menu, MenuItem, PredefinedMenuItem, Submenu } from '@tauri-apps/api/menu';
import { sortOptionJotai } from '../jotais/settings';

export default function SongPage () {
const _list = useAtomValue(libraryJotai);
Expand All @@ -25,7 +26,7 @@ export default function SongPage () {
const [multiselect, setMultiselect] = useState(false);
const [selected, setSelected] = useState<(number | string)[]>([]);
const [keyword, setKeyword] = useState('');
const [sortBy, setSortBy] = useState<SortOptions>('a-z');
const [sortBy, setSortBy] = useAtom(sortOptionJotai);
const sortOptions = [
{ value: 'default', label: intl.formatMessage({ defaultMessage: 'Default' }) } as const,
{ value: 'a-z', label: intl.formatMessage({ defaultMessage: 'A - Z' }) } as const,
Expand Down
3 changes: 2 additions & 1 deletion src/pages/songlist.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Tooltip from '../components/base/tooltip';
import Input from '../components/base/input';
import { SortOptions, sortSongList } from '../utils/sort';
import Select from '../components/base/select';
import { sortOptionJotai } from '../jotais/settings';

export default function SonglistPage () {
const scanned = useAtomValue(scannedJotai);
Expand All @@ -21,7 +22,7 @@ export default function SonglistPage () {
const [songlistName, setSonglistName] = useState('');
const [_currentSonglist, _setCurrentSonglist] = useState<Songlist | null>(null);
const [currentSonglist, setCurrentSonglist] = useState<Songlist | null>(null);
const [sortBy, setSortBy] = useState<SortOptions>('a-z');
const [sortBy, setSortBy] = useAtom(sortOptionJotai);

const sortOptions = [
{ value: 'default', label: intl.formatMessage({ defaultMessage: 'Default' }) } as const,
Expand Down

0 comments on commit cbab1a3

Please sign in to comment.