diff --git a/packages/main/database.ts b/packages/main/database.ts index 606b2d7..3db1510 100644 --- a/packages/main/database.ts +++ b/packages/main/database.ts @@ -1,23 +1,41 @@ import Realm from 'realm' import type { FetchTracksResponse } from '../renderer/src/api/track' +import type { FetchAlbumResponse } from '../renderer/src/api/album' enum ModelNames { TRACK = 'Track', + ALBUM = 'Album', + ARTIST = 'Artist', + PLAYLIST = 'Playlist', +} + +const universalProperties = { + id: 'int', + json: 'string', + updateAt: 'int', } const TrackSchema = { name: ModelNames.TRACK, - properties: { - id: 'int', - json: 'string', - updateAt: 'int', - }, + properties: universalProperties, + primaryKey: 'id', +} + +const AlbumSchema = { + name: ModelNames.ALBUM, + properties: universalProperties, + primaryKey: 'id', +} + +const PlaylistSchema = { + name: ModelNames.PLAYLIST, + properties: universalProperties, primaryKey: 'id', } const realm = new Realm({ - path: './dist/db.realm', - schema: [TrackSchema], + path: './.tmp/db.realm', + schema: [TrackSchema, AlbumSchema, PlaylistSchema], }) export const database = { @@ -29,7 +47,7 @@ export const database = { model, { id: key, - updateAt: ~~(Date.now() / 1000), + updateAt: Date.now(), json: JSON.stringify(value), }, 'modified' @@ -40,29 +58,43 @@ export const database = { }, } -export function setTracks(data: FetchTracksResponse) { - const tracks = data.songs +export async function setTracks(data: FetchTracksResponse) { if (!data.songs) return - const write = async () => - realm.write(() => { - tracks.forEach(track => { - database.set(ModelNames.TRACK, track.id, track) - }) + const tracks = data.songs + realm.write(() => { + tracks.forEach(track => { + database.set(ModelNames.TRACK, track.id, track) }) - write() + }) } export async function setCache(api: string, data: any) { switch (api) { - case 'song_detail': + case 'song_detail': { setTracks(data) + return + } + case 'album': { + if (!data.album) return + realm.write(() => { + database.set(ModelNames.ALBUM, Number(data.album.id), data) + }) + return + } + case 'playlist_detail': { + if (!data.playlist) return + realm.write(() => { + database.set(ModelNames.PLAYLIST, Number(data.playlist.id), data) + }) + return + } } } export function getCache(api: string, query: any) { switch (api) { case 'song_detail': { - const ids: string[] = query.ids.split(',') + const ids: string[] = query?.ids.split(',') const idsQuery = ids.map(id => `id = ${id}`).join(' OR ') const tracksRaw = realm .objects(ModelNames.TRACK) @@ -78,5 +110,23 @@ export function getCache(api: string, query: any) { privileges: {}, } } + case 'album': { + if (!query?.id) return + const album = realm.objectForPrimaryKey( + ModelNames.ALBUM, + Number(query?.id) + )?.json + if (album) return JSON.parse(album) + return + } + case 'playlist_detail': { + if (!query?.id) return + const playlist = realm.objectForPrimaryKey( + ModelNames.PLAYLIST, + Number(query?.id) + )?.json + if (playlist) return JSON.parse(playlist) + return + } } } diff --git a/packages/renderer/src/App.tsx b/packages/renderer/src/App.tsx index 2a688f0..3ef507d 100644 --- a/packages/renderer/src/App.tsx +++ b/packages/renderer/src/App.tsx @@ -9,13 +9,13 @@ import Main from './components/Main' const App = () => { return ( -
+
- + {/* Devtool */} {artists.map((artist, index) => ( - {artist.name} + {artist.name} {index < artists.length - 1 ? ', ' : ''}  ))} diff --git a/packages/renderer/src/components/Cover.tsx b/packages/renderer/src/components/Cover.tsx index b29590d..4a6242f 100644 --- a/packages/renderer/src/components/Cover.tsx +++ b/packages/renderer/src/components/Cover.tsx @@ -12,7 +12,7 @@ const Cover = ({ const [isError, setIsError] = useState(false) return ( -
+
{/* Neon shadow */}
- +
+
) : ( -
diff --git a/packages/renderer/src/components/CoverRow.tsx b/packages/renderer/src/components/CoverRow.tsx index 0ce2f2e..8bd1485 100644 --- a/packages/renderer/src/components/CoverRow.tsx +++ b/packages/renderer/src/components/CoverRow.tsx @@ -20,12 +20,12 @@ const Title = ({ seeMoreLink: string }) => { return ( -
-
+
+
{title}
{seeMoreLink && ( -
+
See More
)} @@ -83,6 +83,8 @@ const CoverRow = ({ seeMoreLink, isSkeleton, className, + rows = 2, + navigateCallback, // Callback function when click on the cover/title }: { title?: string albums?: Album[] @@ -92,13 +94,15 @@ const CoverRow = ({ seeMoreLink?: string isSkeleton?: boolean className?: string + rows?: number + navigateCallback?: () => void }) => { const renderItems = useMemo(() => { if (isSkeleton) { - return new Array(10).fill({}) as Array + return new Array(rows * 5).fill({}) as Array } return albums ?? playlists ?? artists ?? [] - }, [albums, artists, isSkeleton, playlists]) + }, [albums, artists, isSkeleton, playlists, rows]) const navigate = useNavigate() const goTo = (id: number) => { @@ -106,6 +110,7 @@ const CoverRow = ({ if (albums) navigate(`/album/${id}`) if (playlists) navigate(`/playlist/${id}`) if (artists) navigate(`/artist/${id}`) + if (navigateCallback) navigateCallback() } const prefetch = (id: number) => { @@ -129,12 +134,12 @@ const CoverRow = ({
prefetch(item.id)} - className="grid gap-x-[24px] gap-y-7" + className='grid gap-x-[24px] gap-y-7' >
{/* Cover */} {isSkeleton ? ( - + ) : ( goTo(item.id)} @@ -143,30 +148,30 @@ const CoverRow = ({ )} {/* Info */} -
-
+
+
{/* Name */} {isSkeleton ? ( -
- +
+ PLACEHOLDER - + PLACEHOLDER
) : ( - + {/* Playlist private icon */} {(item as Playlist).privacy && ( )} goTo(item.id)} - className="decoration-gray-600 decoration-2 hover:underline dark:text-white dark:decoration-gray-200" + className='decoration-gray-600 decoration-2 hover:underline dark:text-white dark:decoration-gray-200' > {item.name} @@ -176,11 +181,11 @@ const CoverRow = ({ {/* Subtitle */} {isSkeleton ? ( - + PLACEHOLDER ) : ( -
+
{getSubtitleText(item, subtitle)}
)} diff --git a/packages/renderer/src/components/DailyTracksCard.tsx b/packages/renderer/src/components/DailyTracksCard.tsx index cee65fd..c9f58bb 100644 --- a/packages/renderer/src/components/DailyTracksCard.tsx +++ b/packages/renderer/src/components/DailyTracksCard.tsx @@ -3,19 +3,19 @@ import style from './DailyTracksCard.module.scss' const DailyTracksCard = () => { return ( -
+
{/* Cover */} {/* 每日推荐 */} -
-
+
+
{Array.from('每日推荐').map(word => (
{word}
))} @@ -23,8 +23,8 @@ const DailyTracksCard = () => {
{/* Play button */} -
) diff --git a/packages/renderer/src/components/FMCard.tsx b/packages/renderer/src/components/FMCard.tsx index 4bd7303..7874a6d 100644 --- a/packages/renderer/src/components/FMCard.tsx +++ b/packages/renderer/src/components/FMCard.tsx @@ -25,36 +25,36 @@ const FMCard = () => { return (
- + -
+
{/* Track info */}
-
How Can I Make It OK?
-
Wolf Alice
+
How Can I Make It OK?
+
Wolf Alice
-
+
{/* Actions */}
{Object.values(ACTION).map(action => ( ))}
{/* FM logo */} -
- - 私人FM +
+ + 私人FM
diff --git a/packages/renderer/src/components/Main.tsx b/packages/renderer/src/components/Main.tsx index 8fd5021..4ffa6ec 100644 --- a/packages/renderer/src/components/Main.tsx +++ b/packages/renderer/src/components/Main.tsx @@ -4,11 +4,11 @@ import Topbar from '@/components/Topbar' const Main = () => { return (
-
+
diff --git a/packages/renderer/src/components/Player.tsx b/packages/renderer/src/components/Player.tsx index d29f734..c269c6e 100644 --- a/packages/renderer/src/components/Player.tsx +++ b/packages/renderer/src/components/Player.tsx @@ -29,39 +29,39 @@ const PlayingTrack = () => { return ( {track && ( -
+
{track?.al?.picUrl && ( )} {!track?.al?.picUrl && (
- +
)} -
+
{track?.name}
-
+
toast('Work in progress')}>
@@ -76,22 +76,22 @@ const MediaControls = () => { const state = useMemo(() => playerSnapshot.state, [playerSnapshot.state]) const track = useMemo(() => playerSnapshot.track, [playerSnapshot.track]) return ( -
+
track && player.prevTrack()} disabled={!track}> - + track && player.playOrPause()} disabled={!track} - className="rounded-2xl" + className='rounded-2xl' > track && player.nextTrack()} disabled={!track}> - +
) @@ -99,21 +99,21 @@ const MediaControls = () => { const Others = () => { return ( -
+
toast('Work in progress')}> - + toast('Work in progress')}> - + toast('Work in progress')}> - + toast('Work in progress')}> - + toast('Work in progress')}> - +
) @@ -128,7 +128,7 @@ const Progress = () => { const track = useMemo(() => playerSnapshot.track, [playerSnapshot.track]) return ( -
+
{track && ( { /> )} {!track && ( -
+
)}
) @@ -149,7 +149,7 @@ const Progress = () => { const Player = () => { return ( -
+
diff --git a/packages/renderer/src/components/Sidebar.tsx b/packages/renderer/src/components/Sidebar.tsx index a896bc4..636702c 100644 --- a/packages/renderer/src/components/Sidebar.tsx +++ b/packages/renderer/src/components/Sidebar.tsx @@ -3,6 +3,7 @@ import SvgIcon from '@/components/SvgIcon' import { prefetchPlaylist } from '@/hooks/usePlaylist' import useUser from '@/hooks/useUser' import useUserPlaylists from '@/hooks/useUserPlaylists' +import { scrollToTop } from '@/utils/common' interface Tab { name: string @@ -34,9 +35,10 @@ const primaryTabs: PrimaryTab[] = [ const PrimaryTabs = () => { return (
-
+
{primaryTabs.map(tab => ( scrollToTop()} key={tab.route} to={tab.route} className={({ isActive }: { isActive: boolean }) => @@ -47,12 +49,12 @@ const PrimaryTabs = () => { ) } > - - {tab.name} + + {tab.name} ))} -
+
) } @@ -65,10 +67,11 @@ const Playlists = () => { }) return ( -
+
{playlists?.playlist?.map(playlist => ( scrollToTop()} to={`/playlist/${playlist.id}`} onMouseOver={() => prefetchPlaylist({ id: playlist.id })} className={({ isActive }: { isActive: boolean }) => @@ -78,7 +81,7 @@ const Playlists = () => { ) } > - {playlist.name} + {playlist.name} ))}
@@ -88,8 +91,8 @@ const Playlists = () => { const Sidebar = () => { return ( ) diff --git a/packages/renderer/src/components/Slider2.tsx b/packages/renderer/src/components/Slider2.tsx index d3a4c67..294447c 100644 --- a/packages/renderer/src/components/Slider2.tsx +++ b/packages/renderer/src/components/Slider2.tsx @@ -27,7 +27,7 @@ const Slider = () => { onMouseEnter={() => setIsHover(true)} onMouseLeave={() => setIsHover(false)} > -
+
{ )} style={thumbStyle} > -
+
setValue(Number(e.target.value))} - className="absolute h-[2px] w-full appearance-none opacity-0" + className='absolute h-[2px] w-full appearance-none opacity-0' />
) diff --git a/packages/renderer/src/components/SvgIcon.tsx b/packages/renderer/src/components/SvgIcon.tsx index c74faca..6f72b72 100644 --- a/packages/renderer/src/components/SvgIcon.tsx +++ b/packages/renderer/src/components/SvgIcon.tsx @@ -1,8 +1,8 @@ const SvgIcon = ({ name, className }: { name: string; className?: string }) => { const symbolId = `#icon-${name}` return ( -
+
{[ACTION.BACK, ACTION.FORWARD].map(action => (
handleNavigate(action)} key={action} - className="app-region-no-drag btn-hover-animation rounded-lg p-3 text-gray-500 transition duration-300 after:rounded-full after:bg-black/[.06] hover:text-gray-900 dark:text-gray-300 dark:after:bg-white/10 dark:hover:text-gray-200" + className='app-region-no-drag btn-hover-animation rounded-lg p-3 text-gray-500 transition duration-300 after:rounded-full after:bg-black/[.06] hover:text-gray-900 dark:text-gray-300 dark:after:bg-white/10 dark:hover:text-gray-200' > - +
))}
@@ -30,15 +30,15 @@ const NavigationButtons = () => { const SearchBox = () => { return ( -
+
) @@ -46,8 +46,8 @@ const SearchBox = () => { const Settings = () => { return ( -
- +
+
) } @@ -59,7 +59,7 @@ const Avatar = () => { navigate('/login')} - className="app-region-no-drag h-9 w-9 rounded-full bg-gray-100 dark:bg-gray-700" + className='app-region-no-drag h-9 w-9 rounded-full bg-gray-100 dark:bg-gray-700' /> ) } @@ -83,12 +83,12 @@ const Topbar = () => { 'bg-white bg-opacity-[.86] backdrop-blur-xl backdrop-saturate-[1.8] dark:bg-[#222] dark:bg-opacity-[.86]' )} > -
+
-
+
diff --git a/packages/renderer/src/components/TracksAlbum.tsx b/packages/renderer/src/components/TracksAlbum.tsx index afd7cba..1c8ce57 100644 --- a/packages/renderer/src/components/TracksAlbum.tsx +++ b/packages/renderer/src/components/TracksAlbum.tsx @@ -34,7 +34,7 @@ const PlayOrPauseButtonInTrack = memo( )} >
@@ -74,10 +74,10 @@ const Track = memo( )} > {/* Track name and number */} -
+
{/* Track number */} {isSkeleton ? ( - + ) : ( !isHighlight && (
+
{isSkeleton ? ( - + PLACEHOLDER123456789012345 ) : ( @@ -120,7 +120,7 @@ const Track = memo(
{/* Artists */} -
+
{isSkeleton ? ( PLACEHOLDER1234 ) : ( @@ -136,7 +136,7 @@ const Track = memo(
{/* Actions & Track duration */} -
+
{/* Like button */} {!isSkeleton && ( )} @@ -209,15 +209,15 @@ const TracksAlbum = ({ ) return ( -
+
{/* Tracks table header */} -
-
+
+
#
TITLE
-
ARTIST
-
TIME
+
ARTIST
+
TIME
{/* Tracks */} diff --git a/packages/renderer/src/components/TracksGrid.tsx b/packages/renderer/src/components/TracksGrid.tsx index e4e20c0..ea72635 100644 --- a/packages/renderer/src/components/TracksGrid.tsx +++ b/packages/renderer/src/components/TracksGrid.tsx @@ -16,38 +16,38 @@ const TrackListGrid = ({ 'grid-cols-1 py-1.5 px-2' )} > -
+
{/* Cover */}
{!isSkeleton && ( )} {isSkeleton && ( - + )}
{/* Track name & Artists */} -
+
{!isSkeleton && (
{track.name}
)} {isSkeleton && ( - PLACEHOLDER12345 + PLACEHOLDER12345 )} -
+
{!isSkeleton && } {isSkeleton && ( - PLACE + PLACE )}
diff --git a/packages/renderer/src/components/TracksList.tsx b/packages/renderer/src/components/TracksList.tsx index bed7e83..1a332f1 100644 --- a/packages/renderer/src/components/TracksList.tsx +++ b/packages/renderer/src/components/TracksList.tsx @@ -33,32 +33,32 @@ const Track = memo( )} > {/* Track info */} -
+
{/* Cover */}
{isSkeleton ? ( - + ) : ( )}
{/* Track name & Artists */} -
+
{isSkeleton ? ( - PLACEHOLDER12345 + PLACEHOLDER12345 ) : ( -
+
{track.name}
)} -
+
{isSkeleton ? ( - PLACE + PLACE ) : ( )} @@ -67,7 +67,7 @@ const Track = memo(
{/* Album name */} -
+
{isSkeleton ? ( PLACEHOLDER1234567890 ) : ( @@ -75,17 +75,17 @@ const Track = memo( prefetchAlbum({ id: track.al.id })} - className="hover:underline" + className='hover:underline' > {track.al.name} - + )}
{/* Actions & Track duration */} -
+
{/* Like button */} {!isSkeleton && ( )} @@ -107,7 +107,7 @@ const Track = memo( {isSkeleton ? ( 0:00 ) : ( -
+
{formatDuration(track.dt, 'en', 'hh:mm:ss')}
)} @@ -146,16 +146,16 @@ const TracksList = memo( return ( {/* Tracks table header */} -
-
+
+
TITLE
-
ALBUM
-
TIME
+
ALBUM
+
TIME
-
+
{/* Tracks */} {!isSkeleton && tracks.map(track => ( diff --git a/packages/renderer/src/pages/Album.tsx b/packages/renderer/src/pages/Album.tsx index 25aaa7c..135865b 100644 --- a/packages/renderer/src/pages/Album.tsx +++ b/packages/renderer/src/pages/Album.tsx @@ -10,7 +10,12 @@ import useAlbum from '@/hooks/useAlbum' import useArtistAlbums from '@/hooks/useArtistAlbums' import { player } from '@/store' import { State as PlayerState } from '@/utils/player' -import { formatDate, formatDuration, resizeImage } from '@/utils/common' +import { + formatDate, + formatDuration, + resizeImage, + scrollToTop, +} from '@/utils/common' const PlayButton = ({ album, @@ -49,7 +54,7 @@ const PlayButton = ({ @@ -77,29 +82,29 @@ const Header = ({ return ( {/* Header background */} -
+
{coverUrl && !isCoverError && ( )} -
+
-
+
{/* Cover */} -
+
{/* Neon shadow */} {!isLoading && coverUrl && !isCoverError && (
- +
+
) : ( coverUrl && ( setCoverError(true)} /> ) )} - {isLoading && } + {isLoading && }
{/* Info */} -
+
{/* Name */} {isLoading ? ( - PLACEHOLDER + PLACEHOLDER ) : ( -
+
{album?.name}
)} {/* Artist */} {isLoading ? ( - PLACEHOLDER + PLACEHOLDER ) : ( -
+
Album by{' '} {album?.artist.name} @@ -151,11 +156,11 @@ const Header = ({ {/* Release date & track count & album duration */} {isLoading ? ( - + PLACEHOLDER ) : ( -
+
{dayjs(album?.publishTime || 0).year()} · {album?.size} Songs,{' '} {albumDuration}
@@ -163,17 +168,17 @@ const Header = ({ {/* Description */} {isLoading ? ( - + PLACEHOLDER ) : ( -
+
{album?.description}
)} {/* Buttons */} -
+
@@ -208,7 +213,6 @@ const MoreAlbum = ({ album }: { album: Album | undefined }) => { const filteredAlbums = useMemo((): Album[] => { if (!albums) return [] - const albumID = album?.id const allReleases = albums?.hotAlbums || [] const filteredAlbums = allReleases.filter( album => @@ -218,19 +222,18 @@ const MoreAlbum = ({ album }: { album: Album | undefined }) => { const qualifiedAlbums = [...filteredAlbums, ...singles] + const formatName = (name: string) => + name.toLowerCase().replace(/(\s|deluxe|edition|\(|\))/g, '') + const uniqueAlbums: Album[] = [] qualifiedAlbums.forEach(a => { // 去除当前页面的专辑 - if (Number(a.id) === Number(albumID) || album?.name === a.name) return + if (formatName(a.name) === formatName(album?.name ?? '')) return // 去除重复的专辑(包含 deluxe edition 的专辑会视为重复) if ( uniqueAlbums.findIndex(aa => { - return ( - a.name === aa.name || - a.name.toLowerCase().replace(/(\s|deluxe|edition|\(|\))/g, '') === - aa.name.toLowerCase().replace(/(\s|deluxe|edition|\(|\))/g, '') - ) + return formatName(a.name) === formatName(aa.name) }) !== -1 ) { return @@ -248,25 +251,27 @@ const MoreAlbum = ({ album }: { album: Album | undefined }) => { }) return uniqueAlbums.slice(0, 5) - }, [album?.id, album?.name, albums]) + }, [album?.name, albums]) return (
-
-
+
+
More by{' '} {album?.artist.name}
-
+
@@ -289,7 +294,7 @@ const Album = () => { } return ( -
+
{ isSkeleton={isLoading} /> {album?.album && ( -
+
Released {formatDate(album.album.publishTime || 0, 'en')}
{album.album.company && ( -
© {album.album.company}
+
© {album.album.company}
)}
)} diff --git a/packages/renderer/src/pages/Home.tsx b/packages/renderer/src/pages/Home.tsx index f492c87..363ed8e 100644 --- a/packages/renderer/src/pages/Home.tsx +++ b/packages/renderer/src/pages/Home.tsx @@ -14,15 +14,15 @@ export default function Home() { return (
-
+
For You
-
+
diff --git a/packages/renderer/src/pages/Login.tsx b/packages/renderer/src/pages/Login.tsx index befe4b0..6dfdcd7 100644 --- a/packages/renderer/src/pages/Login.tsx +++ b/packages/renderer/src/pages/Login.tsx @@ -21,13 +21,13 @@ const EmailInput = ({ setEmail: (email: string) => void }) => { return ( -
-
Email
+
+
Email
setEmail(e.target.value)} - className="w-full rounded-md border border-gray-300 px-2 py-2" - type="email" + className='w-full rounded-md border border-gray-300 px-2 py-2' + type='email' />
) @@ -45,9 +45,9 @@ const PhoneInput = ({ setPhone: (phone: string) => void }) => { return ( -
-
Phone
-
+
+
Phone
+
= 5 && 'w-20' )} - type="text" - placeholder="+86" + type='text' + placeholder='+86' value={countryCode} onChange={e => setCountryCode(e.target.value)} /> setPhone(e.target.value)} /> @@ -80,22 +80,22 @@ const PasswordInput = ({ }) => { const [showPassword, setShowPassword] = useState(false) return ( -
-
Password
-
+
+
Password
+
setPassword(e.target.value)} - className="w-full rounded-md rounded-r-none border border-r-0 border-gray-300 px-2 py-2" + className='w-full rounded-md rounded-r-none border border-r-0 border-gray-300 px-2 py-2' type={showPassword ? 'text' : 'password'} /> -
+
@@ -153,21 +153,21 @@ const OtherLoginMethods = ({ ] return ( -
- - or - +
+ + or +
-
+
{otherLoginMethods.map( ({ id, name }) => method !== id && ( ) @@ -274,11 +274,11 @@ const LoginWithQRCode = () => { }, [qrCodeUrl]) const qrCodeMessage = 'test' return ( -
-
- QR Code +
+
+ QR Code
-
{qrCodeMessage}
+
{qrCodeMessage}
) } @@ -287,8 +287,8 @@ export default function Login() { const [method, setMethod] = useState(Method.PHONE) return ( -
-
+
+
{method === Method.EMAIL && } {method === Method.PHONE && } {method === Method.QRCODE && } diff --git a/packages/renderer/src/pages/Playlist.tsx b/packages/renderer/src/pages/Playlist.tsx index b233d16..e0732eb 100644 --- a/packages/renderer/src/pages/Playlist.tsx +++ b/packages/renderer/src/pages/Playlist.tsx @@ -27,18 +27,18 @@ const Header = memo( return ( {/* Header background */} -
- - -
+
+ + +
-
+
{/* Cover */} -
+
{!isLoading && (
)} {isLoading && ( - + )}
{/* */} -
+
{/* */} {!isLoading && ( -
+
{playlist?.name}
)} {isLoading && ( - + PLACEHOLDER )} {/* */} {!isLoading && ( -
+
Playlist by {playlist?.creator?.nickname}
)} {isLoading && ( - + PLACEHOLDER )} {/* */} {!isLoading && ( -
+
Updated at {formatDate(playlist?.updateTime || 0, 'en')} · {playlist?.trackCount} Songs
)} {isLoading && ( - + PLACEHOLDER )} {/* */} {!isLoading && ( -
+
{playlist?.description}
)} {isLoading && ( - + PLACEHOLDER )} {/* */} -
+
@@ -120,7 +120,7 @@ const Header = memo( isSkelton={isLoading} onClick={() => toast('Work in progress')} > - +
@@ -225,7 +225,7 @@ const Playlist = () => { ) return ( -
+
setTimeout(resolve, time)) } + +export function scrollToTop(smooth = false) { + const main = document.getElementById('mainContainer') + if (!main) return + main.scrollTo({ top: 0, behavior: smooth ? 'smooth' : 'auto' }) +} diff --git a/prettier.config.js b/prettier.config.js index 0dda108..a48fdbf 100644 --- a/prettier.config.js +++ b/prettier.config.js @@ -9,6 +9,7 @@ module.exports = { bracketSpacing: true, htmlWhitespaceSensitivity: 'strict', singleQuote: true, + jsxSingleQuote: true, // Tailwind CSS plugins: [require('prettier-plugin-tailwindcss')],