mirror of
https://github.com/GiriNeko/YesPlayMusic.git
synced 2025-12-17 05:38:04 +00:00
feat: updates
This commit is contained in:
parent
ce757215a3
commit
c1cd31840e
86 changed files with 1048 additions and 778 deletions
|
|
@ -96,6 +96,10 @@ const MenuItem = ({
|
|||
`
|
||||
)}
|
||||
></div>
|
||||
|
||||
{/* 增加三角形,避免斜着移动到submenu时意外关闭菜单 */}
|
||||
<div className='absolute -right-8 -bottom-6 h-12 w-12 rotate-45'></div>
|
||||
<div className='absolute -right-8 -top-6 h-12 w-12 rotate-45'></div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -73,13 +73,21 @@ const Playlist = ({ playlist }: { playlist: Playlist }) => {
|
|||
}, [playlist.id])
|
||||
|
||||
return (
|
||||
<Image
|
||||
onClick={goTo}
|
||||
key={playlist.id}
|
||||
src={resizeImage(playlist.coverImgUrl || playlist?.picUrl || '', 'md')}
|
||||
className='aspect-square rounded-24'
|
||||
onMouseOver={prefetch}
|
||||
/>
|
||||
<div className='group relative'>
|
||||
<Image
|
||||
onClick={goTo}
|
||||
key={playlist.id}
|
||||
src={resizeImage(playlist.coverImgUrl || playlist?.picUrl || '', 'md')}
|
||||
className='aspect-square rounded-24'
|
||||
onMouseOver={prefetch}
|
||||
/>
|
||||
{/* Hover mask layer */}
|
||||
<div className='pointer-events-none absolute inset-0 w-full bg-gradient-to-b from-transparent to-black opacity-0 transition-all duration-400 group-hover:opacity-100 '></div>
|
||||
{/* Name */}
|
||||
<div className='pointer-events-none absolute bottom-0 p-3 text-sm font-medium text-neutral-300 opacity-0 transition-all duration-400 group-hover:opacity-100'>
|
||||
{playlist.name}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,11 +46,7 @@ const CoverRow = ({
|
|||
return (
|
||||
<div className={className}>
|
||||
{/* Title */}
|
||||
{title && (
|
||||
<h4 className='mb-6 text-14 font-bold uppercase dark:text-neutral-300'>
|
||||
{title}
|
||||
</h4>
|
||||
)}
|
||||
{title && <h4 className='mb-6 text-14 font-bold uppercase dark:text-neutral-300'>{title}</h4>}
|
||||
|
||||
<Virtuoso
|
||||
className='no-scrollbar'
|
||||
|
|
@ -66,20 +62,14 @@ const CoverRow = ({
|
|||
Footer: () => <div className='h-16'></div>,
|
||||
}}
|
||||
itemContent={(index, row) => (
|
||||
<div
|
||||
key={index}
|
||||
className='grid w-full grid-cols-4 gap-4 lg:mb-6 lg:gap-6'
|
||||
>
|
||||
<div key={index} className='grid w-full grid-cols-4 gap-4 lg:mb-6 lg:gap-6'>
|
||||
{row.map((item: Item) => (
|
||||
<img
|
||||
onClick={() => goTo(item.id)}
|
||||
key={item.id}
|
||||
alt={item.name}
|
||||
src={resizeImage(
|
||||
item?.picUrl ||
|
||||
(item as Playlist)?.coverImgUrl ||
|
||||
item?.picUrl ||
|
||||
'',
|
||||
item?.picUrl || (item as Playlist)?.coverImgUrl || item?.picUrl || '',
|
||||
'md'
|
||||
)}
|
||||
className='aspect-square w-full rounded-24'
|
||||
|
|
|
|||
|
|
@ -22,11 +22,7 @@ const sizes = {
|
|||
},
|
||||
} as const
|
||||
|
||||
const CoverWall = ({
|
||||
albums,
|
||||
}: {
|
||||
albums: { id: number; coverUrl: string; large: boolean }[]
|
||||
}) => {
|
||||
const CoverWall = ({ albums }: { albums: { id: number; coverUrl: string; large: boolean }[] }) => {
|
||||
const navigate = useNavigate()
|
||||
const breakpoint = useBreakpoint()
|
||||
|
||||
|
|
@ -41,10 +37,7 @@ const CoverWall = ({
|
|||
>
|
||||
{albums.map(album => (
|
||||
<Image
|
||||
src={resizeImage(
|
||||
album.coverUrl,
|
||||
sizes[album.large ? 'large' : 'small'][breakpoint]
|
||||
)}
|
||||
src={resizeImage(album.coverUrl, sizes[album.large ? 'large' : 'small'][breakpoint])}
|
||||
key={album.id}
|
||||
className={cx(
|
||||
'aspect-square h-full w-full rounded-20 lg:rounded-24',
|
||||
|
|
|
|||
|
|
@ -1,27 +1,16 @@
|
|||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
||||
import useIsMobile from '@/web/hooks/useIsMobile'
|
||||
|
||||
const Devtool = () => {
|
||||
const isMobile = useIsMobile()
|
||||
return (
|
||||
<ReactQueryDevtools
|
||||
initialIsOpen={false}
|
||||
toggleButtonProps={{
|
||||
style: {
|
||||
position: 'fixed',
|
||||
...(isMobile
|
||||
? {
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 'auto',
|
||||
left: 'atuo',
|
||||
}
|
||||
: {
|
||||
top: 36,
|
||||
right: 148,
|
||||
bottom: 'atuo',
|
||||
left: 'auto',
|
||||
}),
|
||||
top: 36,
|
||||
right: 148,
|
||||
bottom: 'atuo',
|
||||
left: 'auto',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
45
packages/web/components/Dropdown.tsx
Normal file
45
packages/web/components/Dropdown.tsx
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import { css, cx } from '@emotion/css'
|
||||
import { motion } from 'framer-motion'
|
||||
|
||||
export interface DropdownItem {
|
||||
label: string
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
function Dropdown({ items, onClose }: { items: DropdownItem[]; onClose: () => void }) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.96 }}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
scale: 1,
|
||||
transition: {
|
||||
duration: 0.1,
|
||||
},
|
||||
}}
|
||||
exit={{ opacity: 0, scale: 0.96 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className={cx(
|
||||
'origin-top rounded-12 border border-white/[.06] bg-gray-900/95 p-px py-2.5 shadow-xl outline outline-1 outline-black backdrop-blur-3xl',
|
||||
css`
|
||||
min-width: 200px;
|
||||
`
|
||||
)}
|
||||
>
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
className='active:bg-gray/50 relative flex w-full items-center justify-between whitespace-nowrap rounded-[5px] p-3 text-16 font-medium text-neutral-200 transition-colors duration-400 hover:bg-white/[.06]'
|
||||
key={index}
|
||||
onClick={() => {
|
||||
item.onClick()
|
||||
onClose()
|
||||
}}
|
||||
>
|
||||
{item.label}
|
||||
</div>
|
||||
))}
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Dropdown
|
||||
|
|
@ -11,9 +11,7 @@ const ErrorBoundary = ({ children }: { children: ReactNode }) => {
|
|||
>
|
||||
<div className='app-region-no-drag'>
|
||||
<p>Something went wrong:</p>
|
||||
<pre className='mb-2 text-18 dark:text-white'>
|
||||
{error.toString()}
|
||||
</pre>
|
||||
<pre className='mb-2 text-18 dark:text-white'>{error.toString()}</pre>
|
||||
<div className='max-h-72 max-w-2xl overflow-scroll whitespace-pre-line rounded-12 border border-white/10 px-3 py-2 dark:text-white/50'>
|
||||
{componentStack?.trim()}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -101,8 +101,7 @@ const ImageDesktop = ({
|
|||
}
|
||||
|
||||
const ImageMobile = (props: Props) => {
|
||||
const { src, className, srcSet, sizes, lazyLoad, onClick, onMouseOver } =
|
||||
props
|
||||
const { src, className, srcSet, sizes, lazyLoad, onClick, onMouseOver } = props
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
|
|
|
|||
|
|
@ -21,11 +21,8 @@ const Layout = () => {
|
|||
<div
|
||||
id='layout'
|
||||
className={cx(
|
||||
'relative grid h-screen select-none overflow-hidden bg-white dark:bg-black',
|
||||
window.env?.isElectron && !fullscreen && 'rounded-24',
|
||||
css`
|
||||
min-width: 720px;
|
||||
`
|
||||
'relative grid h-screen select-none overflow-hidden bg-black',
|
||||
window.env?.isElectron && !fullscreen && 'rounded-24'
|
||||
)}
|
||||
>
|
||||
<BlurBackground />
|
||||
|
|
@ -47,7 +44,15 @@ const Layout = () => {
|
|||
|
||||
<ContextMenus />
|
||||
|
||||
{/* {window.env?.isElectron && <Airplay />} */}
|
||||
{/* Border */}
|
||||
<div
|
||||
className={cx(
|
||||
'pointer-events-none fixed inset-0 z-50 rounded-24',
|
||||
css`
|
||||
box-shadow: inset 0px 0px 0px 1px rgba(255, 255, 255, 0.06);
|
||||
`
|
||||
)}
|
||||
></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,10 +94,7 @@ const LoginWithQRCode = () => {
|
|||
)
|
||||
|
||||
const text = useMemo(
|
||||
() =>
|
||||
key?.data?.unikey
|
||||
? `https://music.163.com/login?codekey=${key.data.unikey}`
|
||||
: '',
|
||||
() => (key?.data?.unikey ? `https://music.163.com/login?codekey=${key.data.unikey}` : ''),
|
||||
[key?.data?.unikey]
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@ import { MotionConfig, motion } from 'framer-motion'
|
|||
import { useSnapshot } from 'valtio'
|
||||
import Icon from '../Icon'
|
||||
import { State as PlayerState } from '@/web/utils/player'
|
||||
import useUserLikedTracksIDs, {
|
||||
useMutationLikeATrack,
|
||||
} from '@/web/api/hooks/useUserLikedTracksIDs'
|
||||
import useUserLikedTracksIDs, { useMutationLikeATrack } from '@/web/api/hooks/useUserLikedTracksIDs'
|
||||
|
||||
const LikeButton = () => {
|
||||
const { track } = useSnapshot(player)
|
||||
|
|
@ -38,9 +36,7 @@ const Controls = () => {
|
|||
<motion.div
|
||||
className={cx(
|
||||
'fixed bottom-0 right-0 flex',
|
||||
mini
|
||||
? 'flex-col items-center justify-between'
|
||||
: 'items-center justify-between',
|
||||
mini ? 'flex-col items-center justify-between' : 'items-center justify-between',
|
||||
mini
|
||||
? css`
|
||||
right: 24px;
|
||||
|
|
@ -85,11 +81,7 @@ const Controls = () => {
|
|||
className='rounded-full bg-black/10 p-2.5 transition-colors duration-400 dark:bg-white/10 hover:dark:bg-white/20'
|
||||
>
|
||||
<Icon
|
||||
name={
|
||||
[PlayerState.Playing, PlayerState.Loading].includes(state)
|
||||
? 'pause'
|
||||
: 'play'
|
||||
}
|
||||
name={[PlayerState.Playing, PlayerState.Loading].includes(state) ? 'pause' : 'play'}
|
||||
className='h-6 w-6 '
|
||||
/>
|
||||
</motion.button>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import persistedUiStates from '@/web/states/persistedUiStates'
|
|||
import Controls from './Controls'
|
||||
import Cover from './Cover'
|
||||
import Progress from './Progress'
|
||||
import { ease } from '@/web/utils/const'
|
||||
|
||||
const NowPlaying = () => {
|
||||
const { track } = useSnapshot(player)
|
||||
|
|
@ -21,6 +22,7 @@ const NowPlaying = () => {
|
|||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ ease, duration: 0.4 }}
|
||||
className={cx(
|
||||
'relative flex aspect-square h-full w-full flex-col justify-end overflow-hidden rounded-24 border',
|
||||
css`
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ const Player = () => {
|
|||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ ease, duration: 0.4 }}
|
||||
>
|
||||
<PlayingNext />
|
||||
</motion.div>
|
||||
|
|
|
|||
|
|
@ -8,9 +8,7 @@ import { resizeImage } from '@/web/utils/common'
|
|||
import { motion, PanInfo } from 'framer-motion'
|
||||
import { useLockBodyScroll } from 'react-use'
|
||||
import { useState } from 'react'
|
||||
import useUserLikedTracksIDs, {
|
||||
useMutationLikeATrack,
|
||||
} from '@/web/api/hooks/useUserLikedTracksIDs'
|
||||
import useUserLikedTracksIDs, { useMutationLikeATrack } from '@/web/api/hooks/useUserLikedTracksIDs'
|
||||
import uiStates from '@/web/states/uiStates'
|
||||
import { ease } from '@/web/utils/const'
|
||||
|
||||
|
|
@ -27,10 +25,7 @@ const LikeButton = () => {
|
|||
className='flex h-full items-center'
|
||||
onClick={() => track?.id && likeATrack.mutateAsync(track.id)}
|
||||
>
|
||||
<Icon
|
||||
name={isLiked ? 'heart' : 'heart-outline'}
|
||||
className='h-7 w-7 text-white/10'
|
||||
/>
|
||||
<Icon name={isLiked ? 'heart' : 'heart-outline'} className='h-7 w-7 text-white/10' />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
|
@ -42,10 +37,7 @@ const PlayerMobile = () => {
|
|||
useLockBodyScroll(locked)
|
||||
const { mobileShowPlayingNext } = useSnapshot(uiStates)
|
||||
|
||||
const onDragEnd = (
|
||||
event: MouseEvent | TouchEvent | PointerEvent,
|
||||
info: PanInfo
|
||||
) => {
|
||||
const onDragEnd = (event: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => {
|
||||
console.log(JSON.stringify(info))
|
||||
const { x, y } = info.offset
|
||||
const offset = 100
|
||||
|
|
@ -107,9 +99,7 @@ const PlayerMobile = () => {
|
|||
className='flex h-full flex-grow items-center '
|
||||
>
|
||||
<div className='flex-shrink-0'>
|
||||
<div className='line-clamp-1 text-14 font-bold text-white'>
|
||||
{track?.name}
|
||||
</div>
|
||||
<div className='line-clamp-1 text-14 font-bold text-white'>{track?.name}</div>
|
||||
<div className='line-clamp-1 mt-1 text-12 font-bold text-white/60'>
|
||||
{track?.ar?.map(a => a.name).join(', ')}
|
||||
</div>
|
||||
|
|
@ -143,10 +133,7 @@ const PlayerMobile = () => {
|
|||
onClick={() => player.playOrPause()}
|
||||
className='ml-2.5 flex items-center justify-center rounded-full bg-white/20 p-2.5'
|
||||
>
|
||||
<Icon
|
||||
name={state === 'playing' ? 'pause' : 'play'}
|
||||
className='h-6 w-6 text-white/80'
|
||||
/>
|
||||
<Icon name={state === 'playing' ? 'pause' : 'play'} className='h-6 w-6 text-white/80' />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -67,10 +67,7 @@ const PlayingNextMobile = () => {
|
|||
`
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
name='player-handler'
|
||||
className='mb-5 h-2.5 rotate-180 text-brand-700'
|
||||
/>
|
||||
<Icon name='player-handler' className='mb-5 h-2.5 rotate-180 text-brand-700' />
|
||||
</motion.div>
|
||||
|
||||
{/* List */}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,17 @@
|
|||
import { Route, Routes, useLocation } from 'react-router-dom'
|
||||
import { AnimatePresence } from 'framer-motion'
|
||||
import React, { ReactNode, Suspense } from 'react'
|
||||
import React, { lazy, Suspense } from 'react'
|
||||
import VideoPlayer from './VideoPlayer'
|
||||
|
||||
const My = React.lazy(() => import('@/web/pages/My'))
|
||||
const Discover = React.lazy(() => import('@/web/pages/Discover'))
|
||||
const Browse = React.lazy(() => import('@/web/pages/Browse'))
|
||||
const Album = React.lazy(() => import('@/web/pages/Album'))
|
||||
const Playlist = React.lazy(() => import('@/web/pages/Playlist'))
|
||||
const Artist = React.lazy(() => import('@/web/pages/Artist'))
|
||||
const Lyrics = React.lazy(() => import('@/web/pages/Lyrics'))
|
||||
const Search = React.lazy(() => import('@/web/pages/Search'))
|
||||
const Settings = React.lazy(() => import('@/web/pages/Settings'))
|
||||
|
||||
const lazy = (component: ReactNode) => {
|
||||
return <Suspense>{component}</Suspense>
|
||||
}
|
||||
const My = lazy(() => import('@/web/pages/My'))
|
||||
const Discover = lazy(() => import('@/web/pages/Discover'))
|
||||
const Browse = lazy(() => import('@/web/pages/Browse'))
|
||||
const Album = lazy(() => import('@/web/pages/Album'))
|
||||
const Playlist = lazy(() => import('@/web/pages/Playlist'))
|
||||
const Artist = lazy(() => import('@/web/pages/Artist'))
|
||||
const Lyrics = lazy(() => import('@/web/pages/Lyrics'))
|
||||
const Search = lazy(() => import('@/web/pages/Search'))
|
||||
const Settings = lazy(() => import('@/web/pages/Settings'))
|
||||
|
||||
const Router = () => {
|
||||
const location = useLocation()
|
||||
|
|
@ -24,16 +20,16 @@ const Router = () => {
|
|||
<AnimatePresence mode='wait'>
|
||||
<VideoPlayer />
|
||||
<Routes location={location} key={location.pathname}>
|
||||
<Route path='/' element={lazy(<My />)} />
|
||||
<Route path='/discover' element={lazy(<Discover />)} />
|
||||
<Route path='/browse' element={lazy(<Browse />)} />
|
||||
<Route path='/album/:id' element={lazy(<Album />)} />
|
||||
<Route path='/playlist/:id' element={lazy(<Playlist />)} />
|
||||
<Route path='/artist/:id' element={lazy(<Artist />)} />
|
||||
<Route path='/settings' element={lazy(<Settings />)} />
|
||||
<Route path='/lyrics' element={lazy(<Lyrics />)} />
|
||||
<Route path='/search/:keywords' element={lazy(<Search />)}>
|
||||
<Route path=':type' element={lazy(<Search />)} />
|
||||
<Route path='/' element={<My />} />
|
||||
<Route path='/discover' element={<Discover />} />
|
||||
<Route path='/browse' element={<Browse />} />
|
||||
<Route path='/album/:id' element={<Album />} />
|
||||
<Route path='/playlist/:id' element={<Playlist />} />
|
||||
<Route path='/artist/:id' element={<Artist />} />
|
||||
<Route path='/settings' element={<Settings />} />
|
||||
<Route path='/lyrics' element={<Lyrics />} />
|
||||
<Route path='/search/:keywords' element={<Search />}>
|
||||
<Route path=':type' element={<Search />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</AnimatePresence>
|
||||
|
|
|
|||
|
|
@ -23,8 +23,7 @@ const Slider = ({
|
|||
const [isDragging, setIsDragging] = useState(false)
|
||||
const [draggingValue, setDraggingValue] = useState(value)
|
||||
const memoedValue = useMemo(
|
||||
() =>
|
||||
isDragging && onlyCallOnChangeAfterDragEnded ? draggingValue : value,
|
||||
() => (isDragging && onlyCallOnChangeAfterDragEnded ? draggingValue : value),
|
||||
[isDragging, draggingValue, value, onlyCallOnChangeAfterDragEnded]
|
||||
)
|
||||
|
||||
|
|
@ -50,8 +49,7 @@ const Slider = ({
|
|||
* Handle slider click event
|
||||
*/
|
||||
const handleClick = useCallback(
|
||||
(e: React.MouseEvent<HTMLDivElement>) =>
|
||||
onChange(getNewValue({ x: e.clientX, y: e.clientY })),
|
||||
(e: React.MouseEvent<HTMLDivElement>) => onChange(getNewValue({ x: e.clientX, y: e.clientY })),
|
||||
[getNewValue, onChange]
|
||||
)
|
||||
|
||||
|
|
@ -69,22 +67,14 @@ const Slider = ({
|
|||
const handlePointerMove = (e: { clientX: number; clientY: number }) => {
|
||||
if (!isDragging) return
|
||||
const newValue = getNewValue({ x: e.clientX, y: e.clientY })
|
||||
onlyCallOnChangeAfterDragEnded
|
||||
? setDraggingValue(newValue)
|
||||
: onChange(newValue)
|
||||
onlyCallOnChangeAfterDragEnded ? setDraggingValue(newValue) : onChange(newValue)
|
||||
}
|
||||
document.addEventListener('pointermove', handlePointerMove)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('pointermove', handlePointerMove)
|
||||
}
|
||||
}, [
|
||||
isDragging,
|
||||
onChange,
|
||||
setDraggingValue,
|
||||
onlyCallOnChangeAfterDragEnded,
|
||||
getNewValue,
|
||||
])
|
||||
}, [isDragging, onChange, setDraggingValue, onlyCallOnChangeAfterDragEnded, getNewValue])
|
||||
|
||||
/**
|
||||
* Handle pointer up events
|
||||
|
|
@ -102,28 +92,18 @@ const Slider = ({
|
|||
return () => {
|
||||
document.removeEventListener('pointerup', handlePointerUp)
|
||||
}
|
||||
}, [
|
||||
isDragging,
|
||||
setIsDragging,
|
||||
onlyCallOnChangeAfterDragEnded,
|
||||
draggingValue,
|
||||
onChange,
|
||||
])
|
||||
}, [isDragging, setIsDragging, onlyCallOnChangeAfterDragEnded, draggingValue, onChange])
|
||||
|
||||
/**
|
||||
* Track and thumb styles
|
||||
*/
|
||||
const usedTrackStyle = useMemo(() => {
|
||||
const percentage = `${(memoedValue / max) * 100}%`
|
||||
return orientation === 'horizontal'
|
||||
? { width: percentage }
|
||||
: { height: percentage }
|
||||
return orientation === 'horizontal' ? { width: percentage } : { height: percentage }
|
||||
}, [max, memoedValue, orientation])
|
||||
const thumbStyle = useMemo(() => {
|
||||
const percentage = `${(memoedValue / max) * 100}%`
|
||||
return orientation === 'horizontal'
|
||||
? { left: percentage }
|
||||
: { bottom: percentage }
|
||||
return orientation === 'horizontal' ? { left: percentage } : { bottom: percentage }
|
||||
}, [max, memoedValue, orientation])
|
||||
|
||||
return (
|
||||
|
|
@ -159,9 +139,7 @@ const Slider = ({
|
|||
<div
|
||||
className={cx(
|
||||
'absolute flex h-2 w-2 items-center justify-center rounded-full bg-black bg-opacity-20 transition-opacity dark:bg-white',
|
||||
isDragging || alwaysShowThumb
|
||||
? 'opacity-100'
|
||||
: 'opacity-0 group-hover:opacity-100',
|
||||
isDragging || alwaysShowThumb ? 'opacity-100' : 'opacity-0 group-hover:opacity-100',
|
||||
orientation === 'horizontal' && '-translate-x-1',
|
||||
orientation === 'vertical' && 'translate-y-1'
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,14 @@ import { IpcChannels } from '@/shared/IpcChannels'
|
|||
import useIpcRenderer from '@/web/hooks/useIpcRenderer'
|
||||
import { useState } from 'react'
|
||||
import { css, cx } from '@emotion/css'
|
||||
import uiStates from '../states/uiStates'
|
||||
|
||||
const Controls = () => {
|
||||
const [isMaximized, setIsMaximized] = useState(false)
|
||||
|
||||
useIpcRenderer(IpcChannels.IsMaximized, (e, value) => {
|
||||
setIsMaximized(value)
|
||||
uiStates.fullscreen = value
|
||||
})
|
||||
|
||||
const minimize = () => {
|
||||
|
|
@ -38,10 +40,7 @@ const Controls = () => {
|
|||
<Icon className='h-3 w-3' name='windows-minimize' />
|
||||
</button>
|
||||
<button onClick={maxRestore} className={classNames}>
|
||||
<Icon
|
||||
className='h-3 w-3'
|
||||
name={isMaximized ? 'windows-un-maximize' : 'windows-maximize'}
|
||||
/>
|
||||
<Icon className='h-3 w-3' name={isMaximized ? 'windows-un-maximize' : 'windows-maximize'} />
|
||||
</button>
|
||||
<button
|
||||
onClick={close}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
import { css, cx } from '@emotion/css'
|
||||
import Icon from '../Icon'
|
||||
import { resizeImage } from '@/web/utils/common'
|
||||
import useUser, { useMutationLogout } from '@/web/api/hooks/useUser'
|
||||
import useUser, {
|
||||
useDailyCheckIn,
|
||||
useMutationLogout,
|
||||
useRefreshCookie,
|
||||
} from '@/web/api/hooks/useUser'
|
||||
import uiStates from '@/web/states/uiStates'
|
||||
import { useRef, useState } from 'react'
|
||||
import BasicContextMenu from '../ContextMenus/BasicContextMenu'
|
||||
|
|
@ -15,6 +19,9 @@ const Avatar = ({ className }: { className?: string }) => {
|
|||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
|
||||
useRefreshCookie()
|
||||
useDailyCheckIn()
|
||||
|
||||
const avatarUrl = user?.profile?.avatarUrl
|
||||
? resizeImage(user?.profile?.avatarUrl ?? '', 'sm')
|
||||
: ''
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@ const TrafficLight = () => {
|
|||
return <></>
|
||||
}
|
||||
|
||||
const className =
|
||||
'mr-2 h-3 w-3 rounded-full last-of-type:mr-0 dark:bg-white/20'
|
||||
const className = 'mr-2 h-3 w-3 rounded-full last-of-type:mr-0 dark:bg-white/20'
|
||||
return (
|
||||
<div className='flex'>
|
||||
<div className={className}></div>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const delay = ['-100ms', '-500ms', '-1200ms', '-1000ms', '-700ms']
|
|||
|
||||
const Wave = ({ playing }: { playing: boolean }) => {
|
||||
return (
|
||||
<div className='grid h-3 grid-cols-5 items-end gap-0.5'>
|
||||
<div className='grid h-3 flex-shrink-0 grid-cols-5 items-end gap-0.5'>
|
||||
{[...new Array(5).keys()].map(i => (
|
||||
<div
|
||||
key={i}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue