mirror of
https://github.com/qier222/YesPlayMusic.git
synced 2024-11-25 05:43:01 +08:00
update
This commit is contained in:
parent
32050e4553
commit
6aee8ae38e
|
@ -25,10 +25,10 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^8.31.0",
|
"eslint": "^8.31.0",
|
||||||
"prettier": "^2.8.1",
|
"prettier": "^2.8.8",
|
||||||
"turbo": "^1.8.3",
|
"turbo": "^1.9.3",
|
||||||
"typescript": "^4.9.5",
|
"typescript": "^4.9.5",
|
||||||
"tsx": "^3.12.1",
|
"tsx": "^3.12.7",
|
||||||
"prettier-plugin-tailwindcss": "^0.2.1"
|
"prettier-plugin-tailwindcss": "^0.2.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 80 KiB |
Binary file not shown.
|
@ -13,6 +13,7 @@ import fastFolderSize from 'fast-folder-size'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import prettyBytes from 'pretty-bytes'
|
import prettyBytes from 'pretty-bytes'
|
||||||
import { db, Tables } from './db'
|
import { db, Tables } from './db'
|
||||||
|
import { promisify } from 'util'
|
||||||
|
|
||||||
log.info('[electron] ipcMain.ts')
|
log.info('[electron] ipcMain.ts')
|
||||||
|
|
||||||
|
@ -137,15 +138,15 @@ function initOtherIpcMain() {
|
||||||
/**
|
/**
|
||||||
* 清除API缓存
|
* 清除API缓存
|
||||||
*/
|
*/
|
||||||
on(IpcChannels.ClearAPICache, () => {
|
handle(IpcChannels.ClearAPICache, async () => {
|
||||||
// db.truncate(Tables.Track)
|
db.truncate(Tables.Track)
|
||||||
// db.truncate(Tables.Album)
|
db.truncate(Tables.Album)
|
||||||
// db.truncate(Tables.Artist)
|
db.truncate(Tables.Artist)
|
||||||
// db.truncate(Tables.Playlist)
|
db.truncate(Tables.Playlist)
|
||||||
// db.truncate(Tables.ArtistAlbum)
|
db.truncate(Tables.ArtistAlbum)
|
||||||
// db.truncate(Tables.AccountData)
|
db.truncate(Tables.AccountData)
|
||||||
// db.truncate(Tables.Audio)
|
db.truncate(Tables.Audio)
|
||||||
// db.vacuum()
|
db.vacuum()
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -170,6 +171,31 @@ function initOtherIpcMain() {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 获取缓存位置
|
||||||
|
handle(IpcChannels.GetCachePath, async () => {
|
||||||
|
return path.join(app.getPath('userData'), './audio_cache')
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取音频缓存文件夹大小
|
||||||
|
*/
|
||||||
|
handle(IpcChannels.GetAudioCacheSize, async () => {
|
||||||
|
const fastFolderSizeAsync = promisify(fastFolderSize)
|
||||||
|
const bytes = await fastFolderSizeAsync(path.join(app.getPath('userData'), './audio_cache'))
|
||||||
|
return prettyBytes(bytes ?? 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
handle(IpcChannels.ClearAudioCache, async () => {
|
||||||
|
try {
|
||||||
|
const audioCachePath = path.join(app.getPath('userData'), './audio_cache')
|
||||||
|
fs.rmdirSync(audioCachePath, { recursive: true })
|
||||||
|
fs.mkdirSync(audioCachePath)
|
||||||
|
return true
|
||||||
|
} catch (e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 缓存封面颜色
|
* 缓存封面颜色
|
||||||
*/
|
*/
|
||||||
|
@ -178,17 +204,6 @@ function initOtherIpcMain() {
|
||||||
cache.set(CacheAPIs.CoverColor, { id, color })
|
cache.set(CacheAPIs.CoverColor, { id, color })
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取音频缓存文件夹大小
|
|
||||||
*/
|
|
||||||
on(IpcChannels.GetAudioCacheSize, event => {
|
|
||||||
fastFolderSize(path.join(app.getPath('userData'), './audio_cache'), (error, bytes) => {
|
|
||||||
if (error) throw error
|
|
||||||
|
|
||||||
event.returnValue = prettyBytes(bytes ?? 0)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从Apple Music获取专辑信息
|
* 从Apple Music获取专辑信息
|
||||||
*/
|
*/
|
||||||
|
@ -235,30 +250,26 @@ function initOtherIpcMain() {
|
||||||
* 导出tables到json文件,方便查看table大小(dev环境)
|
* 导出tables到json文件,方便查看table大小(dev环境)
|
||||||
*/
|
*/
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
// on(IpcChannels.DevDbExportJson, () => {
|
on(IpcChannels.DevDbExportJson, () => {
|
||||||
// const tables = [
|
const tables = [
|
||||||
// Tables.ArtistAlbum,
|
Tables.ArtistAlbum,
|
||||||
// Tables.Playlist,
|
Tables.Playlist,
|
||||||
// Tables.Album,
|
Tables.Album,
|
||||||
// Tables.Track,
|
Tables.Track,
|
||||||
// Tables.Artist,
|
Tables.Artist,
|
||||||
// Tables.Audio,
|
Tables.Audio,
|
||||||
// Tables.AccountData,
|
Tables.AccountData,
|
||||||
// Tables.Lyric,
|
Tables.Lyrics,
|
||||||
// ]
|
]
|
||||||
// tables.forEach(table => {
|
tables.forEach(table => {
|
||||||
// const data = db.findAll(table)
|
const data = db.findAll(table)
|
||||||
// fs.writeFile(
|
fs.writeFile(`./tmp/${table}.json`, JSON.stringify(data), function (err) {
|
||||||
// `./tmp/${table}.json`,
|
if (err) {
|
||||||
// JSON.stringify(data),
|
return console.log(err)
|
||||||
// function (err) {
|
}
|
||||||
// if (err) {
|
console.log('The file was saved!')
|
||||||
// return console.log(err)
|
})
|
||||||
// }
|
})
|
||||||
// console.log('The file was saved!')
|
})
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
"@fastify/static": "^6.6.1",
|
"@fastify/static": "^6.6.1",
|
||||||
"@sentry/electron": "^3.0.7",
|
"@sentry/electron": "^3.0.7",
|
||||||
"NeteaseCloudMusicApi": "^4.8.9",
|
"NeteaseCloudMusicApi": "^4.8.9",
|
||||||
"better-sqlite3": "8.1.0",
|
"better-sqlite3": "8.3.0",
|
||||||
"change-case": "^4.1.2",
|
"change-case": "^4.1.2",
|
||||||
"compare-versions": "^4.1.3",
|
"compare-versions": "^4.1.3",
|
||||||
"electron-log": "^4.4.8",
|
"electron-log": "^4.4.8",
|
||||||
|
@ -39,12 +39,12 @@
|
||||||
"ytdl-core": "^4.11.2"
|
"ytdl-core": "^4.11.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/better-sqlite3": "^7.6.3",
|
"@types/better-sqlite3": "^7.6.4",
|
||||||
"@vitest/ui": "^0.20.3",
|
"@vitest/ui": "^0.20.3",
|
||||||
"axios": "^1.3.4",
|
"axios": "^1.3.4",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"electron": "^23.1.4",
|
"electron": "^24.1.3",
|
||||||
"electron-builder": "23.6.0",
|
"electron-builder": "23.6.0",
|
||||||
"electron-devtools-installer": "^3.2.0",
|
"electron-devtools-installer": "^3.2.0",
|
||||||
"electron-rebuild": "^3.2.9",
|
"electron-rebuild": "^3.2.9",
|
||||||
|
|
|
@ -27,6 +27,8 @@ export const enum IpcChannels {
|
||||||
GetAlbumFromAppleMusic = 'GetAlbumFromAppleMusic',
|
GetAlbumFromAppleMusic = 'GetAlbumFromAppleMusic',
|
||||||
GetArtistFromAppleMusic = 'GetArtistFromAppleMusic',
|
GetArtistFromAppleMusic = 'GetArtistFromAppleMusic',
|
||||||
Logout = 'Logout',
|
Logout = 'Logout',
|
||||||
|
GetCachePath = 'GetCachePath',
|
||||||
|
ClearAudioCache = 'ClearAudioCache',
|
||||||
}
|
}
|
||||||
|
|
||||||
// ipcMain.on params
|
// ipcMain.on params
|
||||||
|
@ -70,6 +72,8 @@ export interface IpcChannelsParams {
|
||||||
}
|
}
|
||||||
[IpcChannels.GetArtistFromAppleMusic]: { id: number; name: string }
|
[IpcChannels.GetArtistFromAppleMusic]: { id: number; name: string }
|
||||||
[IpcChannels.Logout]: void
|
[IpcChannels.Logout]: void
|
||||||
|
[IpcChannels.GetCachePath]: void
|
||||||
|
[IpcChannels.ClearAudioCache]: void
|
||||||
}
|
}
|
||||||
|
|
||||||
// ipcRenderer.on params
|
// ipcRenderer.on params
|
||||||
|
@ -95,4 +99,6 @@ export interface IpcChannelsReturns {
|
||||||
[IpcChannels.GetAlbumFromAppleMusic]: AppleMusicAlbum | undefined
|
[IpcChannels.GetAlbumFromAppleMusic]: AppleMusicAlbum | undefined
|
||||||
[IpcChannels.GetArtistFromAppleMusic]: AppleMusicArtist | undefined
|
[IpcChannels.GetArtistFromAppleMusic]: AppleMusicArtist | undefined
|
||||||
[IpcChannels.Logout]: void
|
[IpcChannels.Logout]: void
|
||||||
|
[IpcChannels.GetCachePath]: string
|
||||||
|
[IpcChannels.ClearAudioCache]: boolean
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { fetchAudioSource, fetchTracks } from '@/web/api/track'
|
import { fetchAudioSource, fetchTracks } from '@/web/api/track'
|
||||||
import type { } from '@/web/api/track'
|
import type {} from '@/web/api/track'
|
||||||
import reactQueryClient from '@/web/utils/reactQueryClient'
|
import reactQueryClient from '@/web/utils/reactQueryClient'
|
||||||
import { IpcChannels } from '@/shared/IpcChannels'
|
import { IpcChannels } from '@/shared/IpcChannels'
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -26,7 +26,7 @@ const Artist = ({ artist }: { artist: Artist }) => {
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
onClick={to}
|
onClick={to}
|
||||||
className='line-clamp-1 mt-2.5 text-12 font-medium text-neutral-700 dark:text-neutral-600 lg:text-14 lg:font-bold'
|
className='mt-2.5 line-clamp-1 text-12 font-medium text-neutral-700 dark:text-neutral-600 lg:text-14 lg:font-bold'
|
||||||
>
|
>
|
||||||
{artist.name}
|
{artist.name}
|
||||||
</div>
|
</div>
|
||||||
|
@ -46,7 +46,7 @@ const Placeholder = ({ row }: { row: number }) => {
|
||||||
minWidth: '96px',
|
minWidth: '96px',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className='line-clamp-1 mt-2.5 w-1/2 rounded-full text-12 font-medium text-transparent dark:bg-neutral-800 lg:text-14 lg:font-bold'>
|
<div className='mt-2.5 line-clamp-1 w-1/2 rounded-full text-12 font-medium text-transparent dark:bg-neutral-800 lg:text-14 lg:font-bold'>
|
||||||
NAME
|
NAME
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -98,7 +98,7 @@ const MenuItem = ({
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
{/* 增加三角形,避免斜着移动到submenu时意外关闭菜单 */}
|
{/* 增加三角形,避免斜着移动到submenu时意外关闭菜单 */}
|
||||||
<div className='absolute -right-8 -bottom-6 h-12 w-12 rotate-45'></div>
|
<div className='absolute -bottom-6 -right-8 h-12 w-12 rotate-45'></div>
|
||||||
<div className='absolute -right-8 -top-6 h-12 w-12 rotate-45'></div>
|
<div className='absolute -right-8 -top-6 h-12 w-12 rotate-45'></div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -56,7 +56,7 @@ const Album = ({
|
||||||
onMouseOver={prefetch}
|
onMouseOver={prefetch}
|
||||||
/>
|
/>
|
||||||
{title && (
|
{title && (
|
||||||
<div className='line-clamp-2 mt-2 text-14 font-medium text-neutral-300'>{title}</div>
|
<div className='mt-2 line-clamp-2 text-14 font-medium text-neutral-300'>{title}</div>
|
||||||
)}
|
)}
|
||||||
{subtitle && <div className='mt-1 text-14 font-medium text-neutral-700'>{subtitle}</div>}
|
{subtitle && <div className='mt-1 text-14 font-medium text-neutral-700'>{subtitle}</div>}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -48,7 +48,7 @@ function DescriptionViewer({
|
||||||
>
|
>
|
||||||
<div className='relative'>
|
<div className='relative'>
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
<div className='line-clamp-1 absolute -top-8 mx-44 max-w-2xl select-none text-32 font-extrabold text-neutral-100'>
|
<div className='absolute -top-8 mx-44 line-clamp-1 max-w-2xl select-none text-32 font-extrabold text-neutral-100'>
|
||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ const Layout = () => {
|
||||||
{showPlayer && <Player />}
|
{showPlayer && <Player />}
|
||||||
|
|
||||||
{window.env?.isMac && (
|
{window.env?.isMac && (
|
||||||
<div className='fixed top-6 left-6 z-30 translate-y-0.5'>
|
<div className='fixed left-6 top-6 z-30 translate-y-0.5'>
|
||||||
<TrafficLight />
|
<TrafficLight />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -53,7 +53,7 @@ const TabName = () => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
'absolute bottom-8 right-0 left-0 z-10 flex rotate-180 select-none items-center font-bold text-brand-600 dark:text-brand-700',
|
'absolute bottom-8 left-0 right-0 z-10 flex rotate-180 select-none items-center font-bold text-brand-600 dark:text-brand-700',
|
||||||
css`
|
css`
|
||||||
writing-mode: vertical-rl;
|
writing-mode: vertical-rl;
|
||||||
text-orientation: mixed;
|
text-orientation: mixed;
|
||||||
|
@ -144,7 +144,7 @@ const MenuBar = () => {
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
'app-region-drag relative flex h-full w-full flex-col justify-center',
|
'app-region-drag relative flex h-full w-full flex-col justify-center',
|
||||||
'lg:fixed lg:left-0 lg:top-0 lg:bottom-0',
|
'lg:fixed lg:bottom-0 lg:left-0 lg:top-0',
|
||||||
css`
|
css`
|
||||||
${bp.lg} {
|
${bp.lg} {
|
||||||
width: 104px;
|
width: 104px;
|
||||||
|
|
|
@ -7,7 +7,7 @@ const Progress = () => {
|
||||||
const { track, progress } = useSnapshot(player)
|
const { track, progress } = useSnapshot(player)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-9 mb-4 flex w-full flex-col'>
|
<div className='mb-4 mt-9 flex w-full flex-col'>
|
||||||
<Slider
|
<Slider
|
||||||
min={0}
|
min={0}
|
||||||
max={(track?.dt ?? 100000) / 1000}
|
max={(track?.dt ?? 100000) / 1000}
|
||||||
|
|
|
@ -13,7 +13,7 @@ const Player = () => {
|
||||||
<MotionConfig transition={{ duration: 0.6 }}>
|
<MotionConfig transition={{ duration: 0.6 }}>
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
'fixed right-6 bottom-6 flex w-full flex-col justify-between overflow-hidden',
|
'fixed bottom-6 right-6 flex w-full flex-col justify-between overflow-hidden',
|
||||||
css`
|
css`
|
||||||
width: 318px;
|
width: 318px;
|
||||||
`
|
`
|
||||||
|
|
|
@ -64,7 +64,7 @@ const PlayerMobile = () => {
|
||||||
uiStates.mobileShowPlayingNext = true
|
uiStates.mobileShowPlayingNext = true
|
||||||
}}
|
}}
|
||||||
className={cx(
|
className={cx(
|
||||||
'absolute right-0 left-0 flex justify-center',
|
'absolute left-0 right-0 flex justify-center',
|
||||||
css`
|
css`
|
||||||
--height: 20px;
|
--height: 20px;
|
||||||
height: var(--height);
|
height: var(--height);
|
||||||
|
@ -100,7 +100,7 @@ const PlayerMobile = () => {
|
||||||
>
|
>
|
||||||
<div className='flex-shrink-0'>
|
<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'>
|
<div className='mt-1 line-clamp-1 text-12 font-bold text-white/60'>
|
||||||
{track?.ar?.map(a => a.name).join(', ')}
|
{track?.ar?.map(a => a.name).join(', ')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -109,7 +109,7 @@ const PlayerMobile = () => {
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
'absolute left-0 top-0 bottom-0 w-3 ',
|
'absolute bottom-0 left-0 top-0 w-3 ',
|
||||||
css`
|
css`
|
||||||
background: linear-gradient(to right, ${bgColor.to}, transparent);
|
background: linear-gradient(to right, ${bgColor.to}, transparent);
|
||||||
`
|
`
|
||||||
|
@ -117,7 +117,7 @@ const PlayerMobile = () => {
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
'absolute right-0 top-0 bottom-0 w-3 bg-red-200',
|
'absolute bottom-0 right-0 top-0 w-3 bg-red-200',
|
||||||
css`
|
css`
|
||||||
background: linear-gradient(to left, ${bgColor.to}, transparent);
|
background: linear-gradient(to left, ${bgColor.to}, transparent);
|
||||||
`
|
`
|
||||||
|
|
|
@ -36,7 +36,7 @@ const RepeatButton = () => {
|
||||||
)}
|
)}
|
||||||
style={buttonStyle}
|
style={buttonStyle}
|
||||||
>
|
>
|
||||||
<div className='absolute top-1/2 left-1/2 h-2 w-2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-white opacity-0 blur group-hover:opacity-100'></div>
|
<div className='absolute left-1/2 top-1/2 h-2 w-2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-white opacity-0 blur group-hover:opacity-100'></div>
|
||||||
<Icon name='repeat-1' className='h-7 w-7' />
|
<Icon name='repeat-1' className='h-7 w-7' />
|
||||||
</motion.button>
|
</motion.button>
|
||||||
)
|
)
|
||||||
|
@ -61,7 +61,7 @@ const ShuffleButton = () => {
|
||||||
style={buttonStyle}
|
style={buttonStyle}
|
||||||
>
|
>
|
||||||
<Icon name='shuffle' className='h-7 w-7' />
|
<Icon name='shuffle' className='h-7 w-7' />
|
||||||
<div className='absolute top-1/2 left-1/2 h-2 w-2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-white opacity-0 blur group-hover:opacity-100'></div>
|
<div className='absolute left-1/2 top-1/2 h-2 w-2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-white opacity-0 blur group-hover:opacity-100'></div>
|
||||||
</motion.button>
|
</motion.button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@ const Header = () => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
'absolute top-0 left-0 z-20 flex w-full items-center justify-between bg-contain bg-repeat-x px-7 pb-6 text-14 font-bold lg:px-0'
|
'absolute left-0 top-0 z-20 flex w-full items-center justify-between bg-contain bg-repeat-x px-7 pb-6 text-14 font-bold lg:px-0'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className='flex text-neutral-300'>
|
<div className='flex text-neutral-300'>
|
||||||
|
@ -134,7 +134,7 @@ const Track = ({
|
||||||
>
|
>
|
||||||
{track?.name}
|
{track?.name}
|
||||||
</div>
|
</div>
|
||||||
<div className='line-clamp-1 mt-1 text-14 font-bold text-neutral-200 dark:text-white/25'>
|
<div className='mt-1 line-clamp-1 text-14 font-bold text-neutral-200 dark:text-white/25'>
|
||||||
{track?.ar.map(a => a.name).join(', ')}
|
{track?.ar.map(a => a.name).join(', ')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -22,7 +22,7 @@ function Tabs<T>({
|
||||||
<div
|
<div
|
||||||
key={tab.id as string}
|
key={tab.id as string}
|
||||||
className={cx(
|
className={cx(
|
||||||
'mr-2.5 rounded-12 py-3 px-6 text-16 font-medium backdrop-blur transition duration-500',
|
'mr-2.5 rounded-12 px-6 py-3 text-16 font-medium backdrop-blur transition duration-500',
|
||||||
value === tab.id
|
value === tab.id
|
||||||
? 'bg-brand-700 text-white'
|
? 'bg-brand-700 text-white'
|
||||||
: 'dark:bg-white/10 dark:text-white/20 hover:dark:bg-white/20 hover:dark:text-white/40'
|
: 'dark:bg-white/10 dark:text-white/20 hover:dark:bg-white/20 hover:dark:text-white/40'
|
||||||
|
|
|
@ -44,7 +44,7 @@ const TopbarDesktop = () => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
'app-region-drag fixed top-0 left-0 right-0 z-20 flex items-center justify-between bg-contain pt-11 pb-10 pr-6',
|
'app-region-drag fixed left-0 right-0 top-0 z-20 flex items-center justify-between bg-contain pb-10 pr-6 pt-11',
|
||||||
css`
|
css`
|
||||||
padding-left: 144px;
|
padding-left: 144px;
|
||||||
`
|
`
|
||||||
|
|
|
@ -74,7 +74,7 @@ const Info = ({
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
transition={{ duration: 0.3 }}
|
transition={{ duration: 0.3 }}
|
||||||
className='line-clamp-3 mt-6 whitespace-pre-wrap text-14 font-bold transition-colors duration-300 dark:text-white/40 dark:hover:text-white/60'
|
className='mt-6 line-clamp-3 whitespace-pre-wrap text-14 font-bold transition-colors duration-300 dark:text-white/40 dark:hover:text-white/60'
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: description,
|
__html: description,
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -212,7 +212,7 @@ const Controls = ({
|
||||||
visible: { y: 0, opacity: 1 },
|
visible: { y: 0, opacity: 1 },
|
||||||
}}
|
}}
|
||||||
transition={animationTransition}
|
transition={animationTransition}
|
||||||
className='absolute bottom-5 left-5 flex rounded-20 bg-black/70 py-3 px-5 backdrop-blur-3xl'
|
className='absolute bottom-5 left-5 flex rounded-20 bg-black/70 px-5 py-3 backdrop-blur-3xl'
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
onClick={togglePlay}
|
onClick={togglePlay}
|
||||||
|
|
|
@ -11,7 +11,7 @@ const VideoRow = ({ videos }: { videos: Video[] }) => {
|
||||||
src={video.coverUrl}
|
src={video.coverUrl}
|
||||||
className='aspect-video w-full rounded-24 border border-white/5 object-contain'
|
className='aspect-video w-full rounded-24 border border-white/5 object-contain'
|
||||||
/>
|
/>
|
||||||
<div className='line-clamp-2 mt-2 text-12 font-medium text-neutral-600'>
|
<div className='mt-2 line-clamp-2 text-12 font-medium text-neutral-600'>
|
||||||
{video.creator?.at(0)?.userName} - {video.title}
|
{video.creator?.at(0)?.userName} - {video.title}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -51,7 +51,7 @@ const useHoverLightSpot = (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
className={cx(
|
className={cx(
|
||||||
'pointer-events-none absolute top-0 left-0 rounded-full transition-opacity duration-400',
|
'pointer-events-none absolute left-0 top-0 rounded-full transition-opacity duration-400',
|
||||||
css`
|
css`
|
||||||
filter: blur(16px);
|
filter: blur(16px);
|
||||||
background: rgb(255, 255, 255);
|
background: rgb(255, 255, 255);
|
||||||
|
|
|
@ -4,7 +4,7 @@ import zhCN from './locales/zh-cn.json'
|
||||||
import enUS from './locales/en-us.json'
|
import enUS from './locales/en-us.json'
|
||||||
|
|
||||||
export const supportedLanguages = ['zh-CN', 'en-US'] as const
|
export const supportedLanguages = ['zh-CN', 'en-US'] as const
|
||||||
export type SupportedLanguage = typeof supportedLanguages[number]
|
export type SupportedLanguage = (typeof supportedLanguages)[number]
|
||||||
|
|
||||||
declare module 'react-i18next' {
|
declare module 'react-i18next' {
|
||||||
interface CustomTypeOptions {
|
interface CustomTypeOptions {
|
||||||
|
|
|
@ -40,14 +40,14 @@
|
||||||
"qrcode": "^1.5.1",
|
"qrcode": "^1.5.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-ga4": "^1.4.1",
|
"react-ga4": "^2.1.0",
|
||||||
"react-hot-toast": "^2.4.0",
|
"react-hot-toast": "^2.4.0",
|
||||||
"react-i18next": "^12.1.5",
|
"react-i18next": "^12.1.5",
|
||||||
"react-router-dom": "^6.6.1",
|
"react-router-dom": "^6.6.1",
|
||||||
"react-use": "^17.4.0",
|
"react-use": "^17.4.0",
|
||||||
"react-use-measure": "^2.1.1",
|
"react-use-measure": "^2.1.1",
|
||||||
"react-virtuoso": "^2.16.6",
|
"react-virtuoso": "^2.16.6",
|
||||||
"valtio": "^1.8.0"
|
"valtio": "^1.10.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/react": "^13.3.0",
|
"@testing-library/react": "^13.3.0",
|
||||||
|
@ -58,7 +58,7 @@
|
||||||
"@types/qrcode": "^1.4.2",
|
"@types/qrcode": "^1.4.2",
|
||||||
"@types/react": "^18.0.15",
|
"@types/react": "^18.0.15",
|
||||||
"@types/react-dom": "^18.0.6",
|
"@types/react-dom": "^18.0.6",
|
||||||
"@vitejs/plugin-react-swc": "^3.2.0",
|
"@vitejs/plugin-react-swc": "^3.3.0",
|
||||||
"@vitest/ui": "^0.26.3",
|
"@vitest/ui": "^0.26.3",
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.13",
|
||||||
"c8": "^7.12.0",
|
"c8": "^7.12.0",
|
||||||
|
@ -69,10 +69,10 @@
|
||||||
"prettier": "*",
|
"prettier": "*",
|
||||||
"prettier-plugin-tailwindcss": "*",
|
"prettier-plugin-tailwindcss": "*",
|
||||||
"rollup-plugin-visualizer": "^5.9.0",
|
"rollup-plugin-visualizer": "^5.9.0",
|
||||||
"tailwindcss": "^3.2.4",
|
"tailwindcss": "^3.3.2",
|
||||||
"typescript": "*",
|
"typescript": "*",
|
||||||
"vite": "^4.2.0",
|
"vite": "^4.3.3",
|
||||||
"vite-plugin-pwa": "^0.14.4",
|
"vite-plugin-pwa": "^0.14.7",
|
||||||
"vite-plugin-svg-icons": "^2.0.1",
|
"vite-plugin-svg-icons": "^2.0.1",
|
||||||
"vitest": "^0.26.3"
|
"vitest": "^0.26.3"
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ const Artist = () => {
|
||||||
<div>
|
<div>
|
||||||
<Header />
|
<Header />
|
||||||
{/* Dividing line */}
|
{/* Dividing line */}
|
||||||
<div className='mt-10 mb-7.5 h-px w-full bg-white/20'></div>
|
<div className='mb-7.5 mt-10 h-px w-full bg-white/20'></div>
|
||||||
<Popular />
|
<Popular />
|
||||||
<ArtistAlbum />
|
<ArtistAlbum />
|
||||||
<ArtistVideos />
|
<ArtistVideos />
|
||||||
|
|
|
@ -57,7 +57,7 @@ const ArtistInfo = ({ artist, isLoading }: { artist?: Artist; isLoading: boolean
|
||||||
(isLoading || isLoadingArtistFromApple ? (
|
(isLoading || isLoadingArtistFromApple ? (
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
'line-clamp-5 mt-6 text-14 font-bold text-transparent',
|
'mt-6 line-clamp-5 text-14 font-bold text-transparent',
|
||||||
css`
|
css`
|
||||||
min-height: 85px;
|
min-height: 85px;
|
||||||
`
|
`
|
||||||
|
@ -68,7 +68,7 @@ const ArtistInfo = ({ artist, isLoading }: { artist?: Artist; isLoading: boolean
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
'line-clamp-5 mt-6 overflow-hidden whitespace-pre-wrap text-14 font-bold text-white/40 transition-colors duration-500 hover:text-white/60',
|
'mt-6 line-clamp-5 overflow-hidden whitespace-pre-wrap text-14 font-bold text-white/40 transition-colors duration-500 hover:text-white/60',
|
||||||
css`
|
css`
|
||||||
height: 85px;
|
height: 85px;
|
||||||
`
|
`
|
||||||
|
|
|
@ -42,7 +42,7 @@ const Track = ({
|
||||||
>
|
>
|
||||||
{track?.name}
|
{track?.name}
|
||||||
</div>
|
</div>
|
||||||
<div className='line-clamp-1 mt-1 text-14 font-bold text-neutral-200 dark:text-neutral-700'>
|
<div className='mt-1 line-clamp-1 text-14 font-bold text-neutral-200 dark:text-neutral-700'>
|
||||||
{track?.ar.map(a => a.name).join(', ')}
|
{track?.ar.map(a => a.name).join(', ')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -49,7 +49,7 @@ const categories = [
|
||||||
{ id: 'charts', name: 'Charts', component: <Recommend /> },
|
{ id: 'charts', name: 'Charts', component: <Recommend /> },
|
||||||
]
|
]
|
||||||
const categoriesKeys = categories.map(c => c.id)
|
const categoriesKeys = categories.map(c => c.id)
|
||||||
type Key = typeof categoriesKeys[number]
|
type Key = (typeof categoriesKeys)[number]
|
||||||
|
|
||||||
const Browse = () => {
|
const Browse = () => {
|
||||||
const [active, setActive] = useState<Key>('recommend')
|
const [active, setActive] = useState<Key>('recommend')
|
||||||
|
@ -60,7 +60,7 @@ const Browse = () => {
|
||||||
{/* Topbar background */}
|
{/* Topbar background */}
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
'pointer-events-none fixed top-0 left-10 z-10 hidden lg:block',
|
'pointer-events-none fixed left-10 top-0 z-10 hidden lg:block',
|
||||||
css`
|
css`
|
||||||
height: 230px;
|
height: 230px;
|
||||||
`
|
`
|
||||||
|
|
|
@ -22,7 +22,7 @@ import settings from '@/web/states/settings'
|
||||||
import useUser from '@/web/api/hooks/useUser'
|
import useUser from '@/web/api/hooks/useUser'
|
||||||
|
|
||||||
const collections = ['playlists', 'albums', 'artists', 'videos'] as const
|
const collections = ['playlists', 'albums', 'artists', 'videos'] as const
|
||||||
type Collection = typeof collections[number]
|
type Collection = (typeof collections)[number]
|
||||||
|
|
||||||
const Albums = () => {
|
const Albums = () => {
|
||||||
const { data: albums } = useUserAlbums()
|
const { data: albums } = useUserAlbums()
|
||||||
|
@ -114,7 +114,7 @@ const CollectionTabs = ({ showBg }: { showBg: boolean }) => {
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
className={cx(
|
className={cx(
|
||||||
'pointer-events-none absolute right-0 left-0 z-10',
|
'pointer-events-none absolute left-0 right-0 z-10',
|
||||||
css`
|
css`
|
||||||
height: 230px;
|
height: 230px;
|
||||||
background-repeat: repeat;
|
background-repeat: repeat;
|
||||||
|
@ -164,7 +164,7 @@ const Collections = () => {
|
||||||
<motion.div layout>
|
<motion.div layout>
|
||||||
<CollectionTabs showBg={isScrollReachBottom} />
|
<CollectionTabs showBg={isScrollReachBottom} />
|
||||||
<div
|
<div
|
||||||
className={cx('no-scrollbar overflow-y-auto px-2.5 pt-16 pb-16 lg:px-0')}
|
className={cx('no-scrollbar overflow-y-auto px-2.5 pb-16 pt-16 lg:px-0')}
|
||||||
onScroll={onScroll}
|
onScroll={onScroll}
|
||||||
style={{
|
style={{
|
||||||
height: `calc(100vh - ${topbarHeight}px)`,
|
height: `calc(100vh - ${topbarHeight}px)`,
|
||||||
|
|
|
@ -63,7 +63,7 @@ const Lyrics = ({ tracksIDs }: { tracksIDs: number[] }) => {
|
||||||
const Covers = memo(({ tracks }: { tracks: Track[] }) => {
|
const Covers = memo(({ tracks }: { tracks: Track[] }) => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
return (
|
return (
|
||||||
<div className='mt-6 grid w-full flex-shrink-0 grid-cols-3 gap-2.5 lg:mt-0 lg:ml-8 lg:w-auto'>
|
<div className='mt-6 grid w-full flex-shrink-0 grid-cols-3 gap-2.5 lg:ml-8 lg:mt-0 lg:w-auto'>
|
||||||
{tracks.map(track => (
|
{tracks.map(track => (
|
||||||
<Image
|
<Image
|
||||||
src={resizeImage(track.al.picUrl || '', 'md')}
|
src={resizeImage(track.al.picUrl || '', 'md')}
|
||||||
|
@ -127,7 +127,7 @@ const PlayLikedSongsCard = () => {
|
||||||
<div className='flex justify-between'>
|
<div className='flex justify-between'>
|
||||||
<button
|
<button
|
||||||
onClick={handlePlay}
|
onClick={handlePlay}
|
||||||
className='rounded-full bg-brand-700 py-5 px-6 text-16 font-medium text-white'
|
className='rounded-full bg-brand-700 px-6 py-5 text-16 font-medium text-white'
|
||||||
>
|
>
|
||||||
{t`my.playNow`}
|
{t`my.playNow`}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -55,10 +55,10 @@ const Track = ({
|
||||||
{track?.name}
|
{track?.name}
|
||||||
|
|
||||||
{[1318912, 1310848].includes(track?.mark || 0) && (
|
{[1318912, 1310848].includes(track?.mark || 0) && (
|
||||||
<Icon name='explicit' className='ml-2 mt-px mr-4 h-3.5 w-3.5 text-white/20' />
|
<Icon name='explicit' className='ml-2 mr-4 mt-px h-3.5 w-3.5 text-white/20' />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className='line-clamp-1 mt-1 text-14 font-bold text-white/30'>
|
<div className='mt-1 line-clamp-1 text-14 font-bold text-white/30'>
|
||||||
{track?.ar.map((a, index) => (
|
{track?.ar.map((a, index) => (
|
||||||
<Fragment key={a.id}>
|
<Fragment key={a.id}>
|
||||||
{index > 0 && ', '}
|
{index > 0 && ', '}
|
||||||
|
|
|
@ -88,7 +88,7 @@ const Track = ({
|
||||||
>
|
>
|
||||||
{track?.name}
|
{track?.name}
|
||||||
</div>
|
</div>
|
||||||
<div className='line-clamp-1 mt-1 text-14 font-bold text-neutral-200 text-white/30'>
|
<div className='mt-1 line-clamp-1 text-14 font-bold text-neutral-200 text-white/30'>
|
||||||
{track?.ar.map(a => a.name).join(', ')}
|
{track?.ar.map(a => a.name).join(', ')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -151,7 +151,7 @@ const Search = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className='mt-6 mb-8 text-4xl font-semibold dark:text-white'>
|
<div className='mb-8 mt-6 text-4xl font-semibold dark:text-white'>
|
||||||
<span className='text-white/40'>搜索</span> "{keywords}"
|
<span className='text-white/40'>搜索</span> "{keywords}"
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ export function Select<T extends string>({
|
||||||
<select
|
<select
|
||||||
onChange={e => onChange(e.target.value as T)}
|
onChange={e => onChange(e.target.value as T)}
|
||||||
value={value}
|
value={value}
|
||||||
className='h-full w-full appearance-none bg-transparent py-1 pr-7 pl-3 focus:outline-none'
|
className='h-full w-full appearance-none bg-transparent py-1 pl-3 pr-7 focus:outline-none'
|
||||||
>
|
>
|
||||||
{options.map(option => (
|
{options.map(option => (
|
||||||
<option key={option.value} value={option.value}>
|
<option key={option.value} value={option.value}>
|
||||||
|
@ -71,7 +71,7 @@ export function Input({
|
||||||
<div className='mb-1 text-14 font-medium text-white/30'>Host</div>
|
<div className='mb-1 text-14 font-medium text-white/30'>Host</div>
|
||||||
<div className='inline-block rounded-md bg-neutral-800 font-medium text-neutral-400'>
|
<div className='inline-block rounded-md bg-neutral-800 font-medium text-neutral-400'>
|
||||||
<input
|
<input
|
||||||
className='appearance-none bg-transparent py-1 px-3'
|
className='appearance-none bg-transparent px-3 py-1'
|
||||||
onChange={e => onChange(e.target.value)}
|
onChange={e => onChange(e.target.value)}
|
||||||
{...{ type, value }}
|
{...{ type, value }}
|
||||||
/>
|
/>
|
||||||
|
@ -80,11 +80,20 @@ export function Input({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Button({ children, onClick }: { children: React.ReactNode; onClick: () => void }) {
|
export function Button({
|
||||||
|
disalbed: disabled,
|
||||||
|
children,
|
||||||
|
onClick,
|
||||||
|
}: {
|
||||||
|
disalbed?: boolean
|
||||||
|
children: React.ReactNode
|
||||||
|
onClick: () => void
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className='rounded-md bg-neutral-800 py-1 px-3 font-medium text-neutral-400 transition-colors duration-300 hover:bg-neutral-700 hover:text-neutral-300'
|
disabled={disabled}
|
||||||
|
className='rounded-md bg-neutral-800 px-3 py-1 font-medium text-neutral-400 transition-colors duration-300 hover:bg-neutral-700 hover:text-neutral-300 disabled:opacity-10'
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
|
@ -104,5 +113,9 @@ export function Option({ children }: { children: React.ReactNode }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function OptionText({ children }: { children: React.ReactNode }) {
|
export function OptionText({ children }: { children: React.ReactNode }) {
|
||||||
return <div className='text-16 font-medium text-neutral-400'>{children}</div>
|
return (
|
||||||
|
<div className='line-clamp-1 flex-shrink-0 text-16 font-medium text-neutral-400'>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,14 @@ import Slider from '@/web/components/Slider'
|
||||||
import { cx } from '@emotion/css'
|
import { cx } from '@emotion/css'
|
||||||
import player from '@/web/states/player'
|
import player from '@/web/states/player'
|
||||||
import { ceil } from 'lodash'
|
import { ceil } from 'lodash'
|
||||||
|
import { useMutation, useQuery } from '@tanstack/react-query'
|
||||||
|
import { IpcChannels } from '@/shared/IpcChannels'
|
||||||
|
import { useCopyToClipboard } from 'react-use'
|
||||||
|
|
||||||
function Player() {
|
function Player() {
|
||||||
return (
|
return (
|
||||||
<div className={cx(`space-y-7`)}>
|
<div className={cx(`space-y-7`)}>
|
||||||
<FindTrackOnYouTube />
|
<FindTrackOnYouTube />
|
||||||
<VolumeSlider />
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -59,31 +61,4 @@ function FindTrackOnYouTube() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function VolumeSlider() {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
const { volume } = useSnapshot(player)
|
|
||||||
const onChange = (volume: number) => {
|
|
||||||
player.volume = volume
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<BlockTitle>{t(`settings.volume-control`)}</BlockTitle>
|
|
||||||
<div className='pt-2 pr-1'>
|
|
||||||
<Slider
|
|
||||||
value={volume}
|
|
||||||
min={0}
|
|
||||||
max={1}
|
|
||||||
onChange={onChange}
|
|
||||||
alwaysShowTrack
|
|
||||||
alwaysShowThumb
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='mt-1 flex justify-between text-14 font-bold text-neutral-100'>
|
|
||||||
<span>0</span>
|
|
||||||
<span>{ceil(volume * 100)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Player
|
export default Player
|
||||||
|
|
|
@ -11,7 +11,7 @@ import PageTransition from '@/web/components/PageTransition'
|
||||||
import { ease } from '@/web/utils/const'
|
import { ease } from '@/web/utils/const'
|
||||||
|
|
||||||
export const categoryIds = ['general', 'appearance', 'player', 'lab', 'about'] as const
|
export const categoryIds = ['general', 'appearance', 'player', 'lab', 'about'] as const
|
||||||
export type Category = typeof categoryIds[number]
|
export type Category = (typeof categoryIds)[number]
|
||||||
|
|
||||||
const Sidebar = ({
|
const Sidebar = ({
|
||||||
activeCategory,
|
activeCategory,
|
||||||
|
@ -42,7 +42,7 @@ const Sidebar = ({
|
||||||
initial={{ y: 11.5 }}
|
initial={{ y: 11.5 }}
|
||||||
animate={indicatorAnimation}
|
animate={indicatorAnimation}
|
||||||
transition={{ type: 'spring', duration: 0.6, bounce: 0.36 }}
|
transition={{ type: 'spring', duration: 0.6, bounce: 0.36 }}
|
||||||
className='absolute top-0 left-3 mr-2 h-4 w-1 rounded-full bg-brand-700'
|
className='absolute left-3 top-0 mr-2 h-4 w-1 rounded-full bg-brand-700'
|
||||||
></motion.div>
|
></motion.div>
|
||||||
|
|
||||||
{categories.map(category => (
|
{categories.map(category => (
|
||||||
|
|
|
@ -16,42 +16,6 @@
|
||||||
.no-drag {
|
.no-drag {
|
||||||
-webkit-user-drag: none;
|
-webkit-user-drag: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.line-clamp-1 {
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
word-break: break-all;
|
|
||||||
-webkit-line-clamp: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.line-clamp-2 {
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.line-clamp-3 {
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
-webkit-line-clamp: 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.line-clamp-4 {
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
-webkit-line-clamp: 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.line-clamp-5 {
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
-webkit-line-clamp: 4;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
|
@ -96,6 +60,9 @@ input {
|
||||||
font-family: Roboto, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Helvetica Neue,
|
font-family: Roboto, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Helvetica Neue,
|
||||||
PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, WenQuanYi Micro Hei,
|
PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, WenQuanYi Micro Hei,
|
||||||
microsoft uighur, sans-serif;
|
microsoft uighur, sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
text-rendering: optimizelegibility;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
// import colors from "tailwindcss/colors"
|
||||||
const colors = require('tailwindcss/colors')
|
// import pickedColors = "./scripts/pickedColors"
|
||||||
// const pickedColors = require('./scripts/pickedColors.js')
|
import type { Config } from 'tailwindcss'
|
||||||
|
import containerQueryPlugin from '@tailwindcss/container-queries'
|
||||||
|
|
||||||
const fontSizeDefault = {
|
const fontSizeDefault = {
|
||||||
lineHeight: '1.2',
|
lineHeight: '1.2',
|
||||||
letterSpacing: '0.02em',
|
letterSpacing: '0.02em',
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export default {
|
||||||
content: ['./index.html', './**/*.{vue,js,ts,jsx,tsx}', '!./node_modules/**/*'],
|
content: ['./index.html', './**/*.{vue,js,ts,jsx,tsx}', '!./node_modules/**/*'],
|
||||||
darkMode: 'class',
|
darkMode: 'class',
|
||||||
theme: {
|
theme: {
|
||||||
|
@ -121,5 +122,5 @@ module.exports = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [require('@tailwindcss/container-queries')],
|
plugins: [containerQueryPlugin],
|
||||||
}
|
} satisfies Config
|
|
@ -167,8 +167,8 @@ export async function calcCoverColor(coverUrl: string) {
|
||||||
|
|
||||||
export const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent)
|
export const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent)
|
||||||
export const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
|
export const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
|
||||||
export const isPWA =
|
export const isPWA = () =>
|
||||||
() => (navigator as any).standalone || window.matchMedia('(display-mode: standalone)').matches
|
(navigator as any).standalone || window.matchMedia('(display-mode: standalone)').matches
|
||||||
export const isIosPwa = isIOS && isPWA() && isSafari
|
export const isIosPwa = isIOS && isPWA() && isSafari
|
||||||
|
|
||||||
export const sleep = (ms: number) => new Promise(r => setTimeout(r, ms))
|
export const sleep = (ms: number) => new Promise(r => setTimeout(r, ms))
|
||||||
|
|
607
pnpm-lock.yaml
607
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -14,5 +14,5 @@ module.exports = {
|
||||||
|
|
||||||
// Tailwind CSS
|
// Tailwind CSS
|
||||||
plugins: [require('prettier-plugin-tailwindcss')],
|
plugins: [require('prettier-plugin-tailwindcss')],
|
||||||
tailwindConfig: './packages/web/tailwind.config.js',
|
tailwindConfig: './packages/web/tailwind.config.ts',
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user