From f54d2ded5cfb85aa015532b9d8bf0bbd400068a6 Mon Sep 17 00:00:00 2001 From: qier222 Date: Thu, 17 Mar 2022 14:45:04 +0800 Subject: [PATCH] feat: updates --- .electron-builder.config.js | 17 +- .gitignore | 3 + package.json | 7 +- packages/main/database.ts | 82 ++++++++++ packages/main/index.ts | 12 +- packages/main/server.ts | 32 ++-- packages/main/vite.config.ts | 8 + packages/preload/vite.config.ts | 8 + packages/renderer/src/api/track.ts | 2 +- packages/renderer/src/api/user.ts | 154 +++++++++--------- packages/renderer/src/components/Button.tsx | 4 +- packages/renderer/src/components/CoverRow.tsx | 24 +-- .../renderer/src/components/IconButton.tsx | 2 +- packages/renderer/src/components/Player.tsx | 16 +- packages/renderer/src/components/Sidebar.tsx | 6 +- packages/renderer/src/components/Topbar.tsx | 2 +- packages/renderer/src/hooks/useScroll.ts | 6 +- packages/renderer/src/pages/Album.tsx | 97 ++++++++--- packages/renderer/src/pages/Login.tsx | 2 +- packages/renderer/src/pages/Playlist.tsx | 7 +- packages/renderer/src/utils/player.ts | 2 +- packages/renderer/vite.config.ts | 15 +- prettier.config.js | 2 +- scripts/watch.mjs | 5 + tailwind.config.js | 1 + yarn.lock | 11 +- 26 files changed, 361 insertions(+), 166 deletions(-) create mode 100644 packages/main/database.ts diff --git a/.electron-builder.config.js b/.electron-builder.config.js index 99fb05f..c7a86c7 100644 --- a/.electron-builder.config.js +++ b/.electron-builder.config.js @@ -3,7 +3,7 @@ * @see https://www.electron.build/configuration/configuration */ module.exports = { - appId: 'yesplaymusic', + appId: 'com.qier222.yesplaymusic', productName: 'YesPlayMusic', copyright: 'Copyright © 2022 ${author}', asar: true, @@ -43,4 +43,19 @@ module.exports = { target: ['AppImage'], artifactName: '${productName}-${version}-Installer.${ext}', }, + files: [ + '**/*', + '!**/node_modules/*/{CHANGELOG.md,README.md,README,readme.md,readme}', + '!**/node_modules/*/{test,__tests__,tests,powered-test,example,examples}', + '!**/node_modules/*.d.ts', + '!**/node_modules/.bin', + '!**/*.{iml,o,hprof,orig,pyc,pyo,rbc,swp,csproj,sln,xproj}', + '!.editorconfig', + '!**/._*', + '!**/{.DS_Store,.git,.hg,.svn,CVS,RCS,SCCS,.gitignore,.gitattributes}', + '!**/{__pycache__,thumbs.db,.flowconfig,.idea,.vs,.nyc_output}', + '!**/{appveyor.yml,.travis.yml,circle.yml}', + '!**/{npm-debug.log,yarn.lock,.yarn-integrity,.yarn-metadata.json}', + '!**/node_modules/realm/react-native/**/*', + ], } diff --git a/.gitignore b/.gitignore index 7fd4188..631832f 100644 --- a/.gitignore +++ b/.gitignore @@ -86,3 +86,6 @@ release dist-ssr *.local .vscode/settings.json +bundle-stats-main.html +bundle-stats-preload.html +bundle-stats-renderer.html diff --git a/package.json b/package.json index 7b6631e..658fd8a 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "main": "dist/main/index.cjs", "scripts": { "dev": "node scripts/watch.mjs", - "build": "npm run typecheck && node scripts/build.mjs && electron-builder --config .electron-builder.config.js", + "build": "npm run typecheck && node scripts/build.mjs && electron-builder --config .electron-builder.config.js --dir", "typecheck": "tsc --noEmit --project packages/renderer/tsconfig.json", "debug": "cross-env-shell NODE_ENV=debug \"npm run typecheck && node scripts/build.mjs && vite ./packages/renderer\"", "eslint": "eslint --ext .ts,.js ./", @@ -26,11 +26,12 @@ "cookie-parser": "^1.4.6", "electron-log": "^4.4.6", "electron-store": "^8.0.1", - "express": "^4.17.3", - "realm": "^10.13.0" + "realm": "^10.13.0", + "express": "^4.17.3" }, "devDependencies": { "@trivago/prettier-plugin-sort-imports": "^3.2.0", + "@types/cookie-parser": "^1.4.2", "@types/express": "^4.17.13", "@types/howler": "^2.2.6", "@types/js-cookie": "^3.0.1", diff --git a/packages/main/database.ts b/packages/main/database.ts new file mode 100644 index 0000000..606b2d7 --- /dev/null +++ b/packages/main/database.ts @@ -0,0 +1,82 @@ +import Realm from 'realm' +import type { FetchTracksResponse } from '../renderer/src/api/track' + +enum ModelNames { + TRACK = 'Track', +} + +const TrackSchema = { + name: ModelNames.TRACK, + properties: { + id: 'int', + json: 'string', + updateAt: 'int', + }, + primaryKey: 'id', +} + +const realm = new Realm({ + path: './dist/db.realm', + schema: [TrackSchema], +}) + +export const database = { + get: (model: ModelNames, key: number) => { + return realm.objectForPrimaryKey(model, key) + }, + set: (model: ModelNames, key: number, value: any) => { + realm.create( + model, + { + id: key, + updateAt: ~~(Date.now() / 1000), + json: JSON.stringify(value), + }, + 'modified' + ) + }, + delete: (model: ModelNames, key: number) => { + realm.delete(realm.objectForPrimaryKey(model, key)) + }, +} + +export function setTracks(data: FetchTracksResponse) { + const tracks = data.songs + if (!data.songs) return + const write = async () => + 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': + setTracks(data) + } +} + +export function getCache(api: string, query: any) { + switch (api) { + case 'song_detail': { + const ids: string[] = query.ids.split(',') + const idsQuery = ids.map(id => `id = ${id}`).join(' OR ') + const tracksRaw = realm + .objects(ModelNames.TRACK) + .filtered(`(${idsQuery})`) + if (tracksRaw.length !== ids.length) { + return + } + const tracks = tracksRaw.map(track => JSON.parse(track.json)) + + return { + code: 200, + songs: tracks, + privileges: {}, + } + } + } +} diff --git a/packages/main/index.ts b/packages/main/index.ts index 2771f09..2dd6f61 100644 --- a/packages/main/index.ts +++ b/packages/main/index.ts @@ -4,16 +4,12 @@ import { app, shell, } from 'electron' -import installExtension, { - REACT_DEVELOPER_TOOLS, - REDUX_DEVTOOLS, -} from 'electron-devtools-installer' import Store from 'electron-store' import { release } from 'os' import { join } from 'path' -import Realm from 'realm' import logger from './logger' import './server' +import './database' const isWindows = process.platform === 'win32' const isMac = process.platform === 'darwin' @@ -104,6 +100,12 @@ app.whenReady().then(async () => { // Install devtool extension if (isDev) { + const { + default: installExtension, + REACT_DEVELOPER_TOOLS, + REDUX_DEVTOOLS, + // eslint-disable-next-line @typescript-eslint/no-var-requires + } = require('electron-devtools-installer') installExtension(REACT_DEVELOPER_TOOLS.id).catch(err => console.log('An error occurred: ', err) ) diff --git a/packages/main/server.ts b/packages/main/server.ts index 5a44289..71435f1 100644 --- a/packages/main/server.ts +++ b/packages/main/server.ts @@ -2,42 +2,44 @@ import { pathCase } from 'change-case' import cookieParser from 'cookie-parser' import express, { Request, Response } from 'express' import logger from './logger' +import { getCache, setCache } from './database' -const neteaseApi = require('NeteaseCloudMusicApi') +// eslint-disable-next-line @typescript-eslint/no-var-requires +const neteaseApi = require('NeteaseCloudMusicApi') as (params: any) => any[] const app = express() app.use(cookieParser()) const port = Number(process.env['ELECTRON_DEV_NETEASE_API_PORT'] ?? 3000) Object.entries(neteaseApi).forEach(([name, handler]) => { - if (['serveNcmApi', 'getModulesDefinitions'].includes(name)) { - return - } + if (['serveNcmApi', 'getModulesDefinitions'].includes(name)) return const wrappedHandler = async (req: Request, res: Response) => { logger.info(`[server] Handling request: ${req.path}`) + + // Get from cache + const cacheResult = getCache(name, req.query) + if (cacheResult) { + logger.info(`[server] Cache hit for ${req.path}`) + return res.json(cacheResult) + } + + // Request netease api try { const result = await handler({ ...req.query, - // cookie: - // 'MUSIC_U=1239b6c1217d8cd240df9c8fa15e99a62f9aaac86baa7a8aa3166acbad267cd8a237494327fc3ec043124f3fcebe94e446b14e3f0c3f8af9fe5c85647582a507', - // cookie: req.headers.cookie, cookie: `MUSIC_U=${req.cookies['MUSIC_U']}`, }) res.send(result.body) + setCache(name, result.body) } catch (error) { res.status(500).send(error) } } - app.get( - `/netease/${pathCase(name)}`, - async (req: Request, res: Response) => await wrappedHandler(req, res) - ) - app.post( - `/netease/${pathCase(name)}`, - async (req: Request, res: Response) => await wrappedHandler(req, res) - ) + const neteasePath = `/netease/${pathCase(name)}` + app.get(neteasePath, wrappedHandler) + app.post(neteasePath, wrappedHandler) }) app.listen(port, () => { diff --git a/packages/main/vite.config.ts b/packages/main/vite.config.ts index 3441c12..e0a4bd6 100644 --- a/packages/main/vite.config.ts +++ b/packages/main/vite.config.ts @@ -1,6 +1,7 @@ import dotenv from 'dotenv' import { builtinModules } from 'module' import path from 'path' +import { visualizer } from 'rollup-plugin-visualizer' import { defineConfig } from 'vite' import pkg from '../../package.json' import esm2cjs from '../../scripts/vite-plugin-esm2cjs' @@ -27,6 +28,13 @@ export default defineConfig({ ...builtinModules, ...Object.keys(pkg.dependencies || {}), ], + plugins: [ + visualizer({ + filename: './bundle-stats-main.html', + gzipSize: true, + projectRoot: 'packages/main', + }), + ], }, }, }) diff --git a/packages/preload/vite.config.ts b/packages/preload/vite.config.ts index d2cc802..58a821a 100644 --- a/packages/preload/vite.config.ts +++ b/packages/preload/vite.config.ts @@ -3,6 +3,7 @@ import { builtinModules } from 'module' import path from 'path' import { defineConfig } from 'vite' import pkg from '../../package.json' +import { visualizer } from 'rollup-plugin-visualizer' dotenv.config({ path: path.resolve(process.cwd(), '.env'), @@ -25,6 +26,13 @@ export default defineConfig({ ...builtinModules, ...Object.keys(pkg.dependencies || {}), ], + plugins: [ + visualizer({ + filename: './bundle-stats-preload.html', + gzipSize: true, + projectRoot: 'packages/preload', + }), + ], }, }, }) diff --git a/packages/renderer/src/api/track.ts b/packages/renderer/src/api/track.ts index 5f16f7d..a8bf001 100644 --- a/packages/renderer/src/api/track.ts +++ b/packages/renderer/src/api/track.ts @@ -9,7 +9,7 @@ export enum TrackApiNames { export interface FetchTracksParams { ids: number[] } -interface FetchTracksResponse { +export interface FetchTracksResponse { code: number songs: Track[] privileges: { diff --git a/packages/renderer/src/api/user.ts b/packages/renderer/src/api/user.ts index 82f0116..2f22e73 100644 --- a/packages/renderer/src/api/user.ts +++ b/packages/renderer/src/api/user.ts @@ -12,7 +12,7 @@ export enum UserApiNames { * - uid : 用户 id * @param {number} uid */ -export function userDetail(uid) { +export function userDetail(uid: number) { return request({ url: '/user/detail', method: 'get', @@ -160,68 +160,68 @@ export function dailySignin(type = 0) { * @param {number} params.limit * @param {number=} params.offset */ -export function likedAlbums(params) { - return request({ - url: '/album/sublist', - method: 'get', - params: { - limit: params.limit, - timestamp: new Date().getTime(), - }, - }) -} +// export function likedAlbums(params) { +// return request({ +// url: '/album/sublist', +// method: 'get', +// params: { +// limit: params.limit, +// timestamp: new Date().getTime(), +// }, +// }) +// } /** * 获取收藏的歌手(需要登录) * 说明 : 调用此接口可获取到用户收藏的歌手 */ -export function likedArtists(params) { - return request({ - url: '/artist/sublist', - method: 'get', - params: { - limit: params.limit, - timestamp: new Date().getTime(), - }, - }) -} +// export function likedArtists(params) { +// return request({ +// url: '/artist/sublist', +// method: 'get', +// params: { +// limit: params.limit, +// timestamp: new Date().getTime(), +// }, +// }) +// } /** * 获取收藏的MV(需要登录) * 说明 : 调用此接口可获取到用户收藏的MV */ -export function likedMVs(params) { - return request({ - url: '/mv/sublist', - method: 'get', - params: { - limit: params.limit, - timestamp: new Date().getTime(), - }, - }) -} +// export function likedMVs(params) { +// return request({ +// url: '/mv/sublist', +// method: 'get', +// params: { +// limit: params.limit, +// timestamp: new Date().getTime(), +// }, +// }) +// } /** * 上传歌曲到云盘(需要登录) */ -export function uploadSong(file) { - let formData = new FormData() - formData.append('songFile', file) - return request({ - url: '/cloud', - method: 'post', - params: { - timestamp: new Date().getTime(), - }, - data: formData, - headers: { - 'Content-Type': 'multipart/form-data', - }, - timeout: 200000, - }).catch(error => { - alert(`上传失败,Error: ${error}`) - }) -} +// export function uploadSong(file) { +// let formData = new FormData() +// formData.append('songFile', file) +// return request({ +// url: '/cloud', +// method: 'post', +// params: { +// timestamp: new Date().getTime(), +// }, +// data: formData, +// headers: { +// 'Content-Type': 'multipart/form-data', +// }, +// timeout: 200000, +// }).catch(error => { +// alert(`上传失败,Error: ${error}`) +// }) +// } /** * 获取云盘歌曲(需要登录) @@ -232,40 +232,40 @@ export function uploadSong(file) { * @param {number} params.limit * @param {number=} params.offset */ -export function cloudDisk(params = {}) { - params.timestamp = new Date().getTime() - return request({ - url: '/user/cloud', - method: 'get', - params, - }) -} +// export function cloudDisk(params = {}) { +// params.timestamp = new Date().getTime() +// return request({ +// url: '/user/cloud', +// method: 'get', +// params, +// }) +// } /** * 获取云盘歌曲详情(需要登录) */ -export function cloudDiskTrackDetail(id) { - return request({ - url: '/user/cloud/detail', - method: 'get', - params: { - timestamp: new Date().getTime(), - id, - }, - }) -} +// export function cloudDiskTrackDetail(id) { +// return request({ +// url: '/user/cloud/detail', +// method: 'get', +// params: { +// timestamp: new Date().getTime(), +// id, +// }, +// }) +// } /** * 删除云盘歌曲(需要登录) * @param {Array} id */ -export function cloudDiskTrackDelete(id) { - return request({ - url: '/user/cloud/del', - method: 'get', - params: { - timestamp: new Date().getTime(), - id, - }, - }) -} +// export function cloudDiskTrackDelete(id) { +// return request({ +// url: '/user/cloud/del', +// method: 'get', +// params: { +// timestamp: new Date().getTime(), +// id, +// }, +// }) +// } diff --git a/packages/renderer/src/components/Button.tsx b/packages/renderer/src/components/Button.tsx index 85071bd..3212e12 100644 --- a/packages/renderer/src/components/Button.tsx +++ b/packages/renderer/src/components/Button.tsx @@ -36,11 +36,11 @@ const Button = ({ { 'px-4 py-1.5': shape === Shape.Default, 'px-3 py-1.5': shape === Shape.Square, - 'bg-brand-100 dark:bg-brand-700': color === Color.Primary, + 'bg-brand-100 dark:bg-brand-600': color === Color.Primary, 'text-brand-500 dark:text-white': iconColor === Color.Primary, 'bg-gray-100 dark:bg-gray-700': color === Color.Gray, 'text-gray-900 dark:text-gray-400': iconColor === Color.Gray, - 'animate-pulse bg-gray-100 text-transparent dark:bg-gray-800': + 'animate-pulse bg-gray-100 !text-transparent dark:bg-gray-800': isSkelton, } )} diff --git a/packages/renderer/src/components/CoverRow.tsx b/packages/renderer/src/components/CoverRow.tsx index e452bc0..0ce2f2e 100644 --- a/packages/renderer/src/components/CoverRow.tsx +++ b/packages/renderer/src/components/CoverRow.tsx @@ -38,11 +38,14 @@ const getSubtitleText = ( subtitle: Subtitle ) => { const nickname = 'creator' in item ? item.creator.nickname : 'someone' + const artist = 'artist' in item ? item.artist.name : 'unknown' + const copywriter = 'copywriter' in item ? item.copywriter : 'unknown' const releaseYear = - 'publishTime' in item - ? formatDate(item.publishTime ?? 0, 'en', 'YYYY') - : 'unknown' - const types = { + ('publishTime' in item && + formatDate(item.publishTime ?? 0, 'en', 'YYYY')) || + 'unknown' + + const type = { playlist: 'playlist', album: 'Album', 专辑: 'Album', @@ -50,16 +53,17 @@ const getSubtitleText = ( 'EP/Single': 'EP', EP: 'EP', unknown: 'unknown', - } - const type = 'type' in item ? item.type || 'unknown' : 'unknown' - const artist = 'artist' in item ? item.artist.name : 'unknown' + 精选集: 'Collection', + }[('type' in item && typeof item.type !== 'number' && item.type) || 'unknown'] const table = { [Subtitle.CREATOR]: `by ${nickname}`, - [Subtitle.TYPE_RELEASE_YEAR]: `${types[type]} · ${releaseYear}`, + [Subtitle.TYPE_RELEASE_YEAR]: `${type} · ${releaseYear}`, [Subtitle.ARTIST]: artist, + [Subtitle.COPYWRITER]: copywriter, } - return table[subtitle] ?? item[subtitle] + + return table[subtitle] } const getImageUrl = (item: Album | Playlist | Artist) => { @@ -162,7 +166,7 @@ const CoverRow = ({ )} goTo(item.id)} - className="decoration-gray-600 decoration-2 hover:underline dark:text-white" + className="decoration-gray-600 decoration-2 hover:underline dark:text-white dark:decoration-gray-200" > {item.name} diff --git a/packages/renderer/src/components/IconButton.tsx b/packages/renderer/src/components/IconButton.tsx index caa35ef..e1ad68f 100644 --- a/packages/renderer/src/components/IconButton.tsx +++ b/packages/renderer/src/components/IconButton.tsx @@ -18,7 +18,7 @@ const IconButton = ({ className, 'relative transform cursor-default p-2 transition duration-200', !disabled && - 'btn-pressed-animation btn-hover-animation after:bg-black/[.06]', + 'btn-pressed-animation btn-hover-animation after:bg-black/[.06] dark:after:bg-white/10', disabled && 'opacity-30' )} > diff --git a/packages/renderer/src/components/Player.tsx b/packages/renderer/src/components/Player.tsx index 507e7e3..d29f734 100644 --- a/packages/renderer/src/components/Player.tsx +++ b/packages/renderer/src/components/Player.tsx @@ -49,7 +49,7 @@ const PlayingTrack = () => {
{track?.name}
@@ -58,7 +58,7 @@ const PlayingTrack = () => {
- + toast('Work in progress')}> { const Others = () => { return (
- + toast('Work in progress')}> - + toast('Work in progress')}> - + toast('Work in progress')}> - + toast('Work in progress')}> - + toast('Work in progress')}>
@@ -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 69eaa20..a896bc4 100644 --- a/packages/renderer/src/components/Sidebar.tsx +++ b/packages/renderer/src/components/Sidebar.tsx @@ -52,7 +52,7 @@ const PrimaryTabs = () => { ))} -
+
) } @@ -65,7 +65,7 @@ const Playlists = () => { }) return ( -
+
{playlists?.playlist?.map(playlist => ( { return (