From e3cd56735bd3b689eb7ef440a994d4d84a23a77f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= <31655147+maotoumao@users.noreply.github.com> Date: Mon, 6 Nov 2023 10:31:12 +0000 Subject: [PATCH 01/50] =?UTF-8?q?feat:=20=E7=94=A8=E6=88=B7=E5=8F=98?= =?UTF-8?q?=E9=87=8F=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 4 ++-- src/types/plugin.d.ts | 12 ++++++++---- src/types/user-perference.d.ts | 2 ++ 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 55a822bb..816e6fb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "musicfree-desktop", - "version": "0.0.1-alpha.0", + "version": "0.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "musicfree-desktop", - "version": "0.0.1-alpha.0", + "version": "0.0.2", "license": "GPL", "dependencies": { "@headlessui/react": "^1.7.15", diff --git a/src/types/plugin.d.ts b/src/types/plugin.d.ts index 62d2dcdd..11559142 100644 --- a/src/types/plugin.d.ts +++ b/src/types/plugin.d.ts @@ -28,9 +28,11 @@ declare namespace IPlugin { type: T, ) => Promise>; - interface IUserEnv { + interface IUserVariables { + /** 变量键名 */ key: string; - name: string; + /** 变量名 */ + name?: string; } interface IAlbumInfoResult { @@ -69,7 +71,7 @@ declare namespace IPlugin { /** 插件缓存控制 */ cacheControl?: 'cache' | 'no-cache' | 'no-store'; /** 用户自定义输入 */ - userEnv?: IUserEnv[]; + userVariables?: IUserVariables[]; /** 提示文本 */ hints?: Record; /** 搜索 */ @@ -122,6 +124,8 @@ declare namespace IPlugin { tag: ICommon.IUnique, page?: number, ) => Promise>; + /** 获取用户变量 */ + getUserVariables?: () => Record; } export interface IPluginInstance extends IPluginDefine { @@ -138,7 +142,7 @@ declare namespace IPlugin { /** 插件其他属性 */ export type IPluginMeta = { order: number; - userEnv: Record; + userVariables: Record; }; export type IPluginDelegate = { diff --git a/src/types/user-perference.d.ts b/src/types/user-perference.d.ts index 3134cc4f..783ee407 100644 --- a/src/types/user-perference.d.ts +++ b/src/types/user-perference.d.ts @@ -32,5 +32,7 @@ declare namespace IUserPerference { starredMusicSheets: IMedia.IMediaBase[] /** 搜索历史 */ searchHistory: string[]; + /** 插件数据 */ + pluginMeta: Record } } \ No newline at end of file From 787dade3201000322cb3c2a32f8e3687429a8041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= <31655147+maotoumao@users.noreply.github.com> Date: Mon, 6 Nov 2023 10:37:47 +0000 Subject: [PATCH 02/50] =?UTF-8?q?feat:=20=E7=A7=BB=E9=99=A4=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E5=8F=98=E9=87=8F=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/types/plugin.d.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/types/plugin.d.ts b/src/types/plugin.d.ts index 11559142..c73eac92 100644 --- a/src/types/plugin.d.ts +++ b/src/types/plugin.d.ts @@ -124,8 +124,6 @@ declare namespace IPlugin { tag: ICommon.IUnique, page?: number, ) => Promise>; - /** 获取用户变量 */ - getUserVariables?: () => Record; } export interface IPluginInstance extends IPluginDefine { From 571d2c687156911d251d547356795de5671d5780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= <31655147+maotoumao@users.noreply.github.com> Date: Tue, 7 Nov 2023 04:47:02 +0000 Subject: [PATCH 03/50] fix: #40 --- package-lock.json | 4 ++-- src/preload/extension.ts | 4 +++- src/preload/index.ts | 2 ++ src/preload/internal/utils.ts | 13 +++++++++++++ src/renderer/core/track-player/player.ts | 2 +- src/types/preload.d.ts | 1 + 6 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 src/preload/internal/utils.ts diff --git a/package-lock.json b/package-lock.json index 55a822bb..816e6fb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "musicfree-desktop", - "version": "0.0.1-alpha.0", + "version": "0.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "musicfree-desktop", - "version": "0.0.1-alpha.0", + "version": "0.0.2", "license": "GPL", "dependencies": { "@headlessui/react": "^1.7.15", diff --git a/src/preload/extension.ts b/src/preload/extension.ts index 67c27511..01917b3f 100644 --- a/src/preload/extension.ts +++ b/src/preload/extension.ts @@ -1,12 +1,13 @@ // See the Electron documentation for details on how to use preload scripts: -import { contextBridge, ipcRenderer } from "electron"; +import { contextBridge } from "electron"; import ipcRendererDelegate from "./internal/ipc-renderer-delegate"; import fsDelegate from "./internal/fs-delegate"; import themepack from "./internal/themepack"; import path from 'path'; import { rimraf } from "rimraf"; import extPort from "./internal/ext-port"; +import utils from './internal/utils'; // https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts @@ -18,3 +19,4 @@ contextBridge.exposeInMainWorld('themepack', themepack); contextBridge.exposeInMainWorld('path', path); contextBridge.exposeInMainWorld('rimraf', rimraf) contextBridge.exposeInMainWorld('extPort', extPort); +contextBridge.exposeInMainWorld('utils', utils); diff --git a/src/preload/index.ts b/src/preload/index.ts index 7f650f82..43fbe4ff 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -7,6 +7,7 @@ import themepack from "./internal/themepack"; import path from "path"; import { rimraf } from "rimraf"; import mainPort from "./internal/main-port"; +import utils from './internal/utils'; // https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts contextBridge.exposeInMainWorld("ipcRenderer", ipcRendererDelegate); @@ -15,3 +16,4 @@ contextBridge.exposeInMainWorld("themepack", themepack); contextBridge.exposeInMainWorld("path", path); contextBridge.exposeInMainWorld("rimraf", rimraf); contextBridge.exposeInMainWorld('mainPort', mainPort); +contextBridge.exposeInMainWorld('utils', utils); diff --git a/src/preload/internal/utils.ts b/src/preload/internal/utils.ts new file mode 100644 index 00000000..c0ff9ffd --- /dev/null +++ b/src/preload/internal/utils.ts @@ -0,0 +1,13 @@ +import url from 'url'; + +function addFileScheme(filePath: string) { + return filePath.startsWith("file:") + ? filePath + : url.pathToFileURL(filePath).toString(); +} + + + +export default { + addFileScheme +} \ No newline at end of file diff --git a/src/renderer/core/track-player/player.ts b/src/renderer/core/track-player/player.ts index a104ba4d..e8badf73 100644 --- a/src/renderer/core/track-player/player.ts +++ b/src/renderer/core/track-player/player.ts @@ -490,7 +490,7 @@ async function getMediaSource( return { quality, mediaSource: { - url: _path, + url: window.utils.addFileScheme(_path), }, }; } else { diff --git a/src/types/preload.d.ts b/src/types/preload.d.ts index b6cac003..88c7118e 100644 --- a/src/types/preload.d.ts +++ b/src/types/preload.d.ts @@ -4,6 +4,7 @@ interface Window { globalData: IGlobalData, path: typeof import("node:path"); rimraf: typeof import('rimraf').rimraf; + utils: typeof import('../preload/internal/utils').default; /** 向拓展窗口广播数据 */ mainPort: typeof import('../preload/internal/main-port').default; extPort: typeof import('../preload/internal/ext-port').default; From 8e17c5272496572d801366a070ee0bdbdf918fbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Sun, 19 Nov 2023 20:36:51 +0800 Subject: [PATCH 04/50] =?UTF-8?q?feat:=20Panel=E5=AE=9E=E7=8E=B0&=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E5=8F=98=E9=87=8F=E5=BC=B9=E7=AA=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/app.scss | 2 +- src/renderer/app.tsx | 2 + src/renderer/components/Header/index.scss | 4 +- src/renderer/components/Header/index.tsx | 1 - .../Modal/templates/Base/index.scss | 2 +- src/renderer/components/MusicBar/index.tsx | 10 +- .../MusicBar/widgets/Extra/index.tsx | 11 +- .../MusicBar/widgets/PlayList/index.scss | 101 -------------- src/renderer/components/Panel/index.tsx | 52 ++++++++ .../Panel/templates/Base/index.scss | 52 ++++++++ .../components/Panel/templates/Base/index.tsx | 109 +++++++++++++++ .../Panel/templates/PlayList/index.scss | 84 ++++++++++++ .../templates}/PlayList/index.tsx | 124 +++++++----------- .../Panel/templates/UserVariables/index.scss | 28 ++++ .../Panel/templates/UserVariables/index.tsx | 31 +++++ .../components/Panel/templates/index.ts | 9 ++ src/renderer/core/events/types/player.d.ts | 4 - src/renderer/document/index.tsx | 1 + .../components/plugin-table/index.tsx | 16 +++ .../views/plugin-manager-view/index.tsx | 2 +- src/types/plugin.d.ts | 4 +- 21 files changed, 452 insertions(+), 197 deletions(-) delete mode 100644 src/renderer/components/MusicBar/widgets/PlayList/index.scss create mode 100644 src/renderer/components/Panel/index.tsx create mode 100644 src/renderer/components/Panel/templates/Base/index.scss create mode 100644 src/renderer/components/Panel/templates/Base/index.tsx create mode 100644 src/renderer/components/Panel/templates/PlayList/index.scss rename src/renderer/components/{MusicBar/widgets => Panel/templates}/PlayList/index.tsx (53%) create mode 100644 src/renderer/components/Panel/templates/UserVariables/index.scss create mode 100644 src/renderer/components/Panel/templates/UserVariables/index.tsx create mode 100644 src/renderer/components/Panel/templates/index.ts diff --git a/src/renderer/app.scss b/src/renderer/app.scss index 6ad0aa3f..2bdd8952 100644 --- a/src/renderer/app.scss +++ b/src/renderer/app.scss @@ -74,7 +74,7 @@ --listHoverColor: rgba(0, 0, 0, 0.05); // 列表悬浮颜色 --listActiveColor: rgba(0, 0, 0, 0.1); // 列表选中颜色 --textColor: #333333; // 主文本颜色 - --maskColor: rgba(51, 51, 51, 0.2); // 遮罩层颜色 + --maskColor: rgba(51, 51, 51, 0.5); // 遮罩层颜色 /* --backdropColor: #fdfdfd; // 弹窗等地方的背景颜色,默认和背景色一致*/ --shadowColor: rgba(0, 0, 0, 0.2); /** --shadow: // 全部的shadow属性 */ diff --git a/src/renderer/app.tsx b/src/renderer/app.tsx index dcbd5a20..3dc9f998 100644 --- a/src/renderer/app.tsx +++ b/src/renderer/app.tsx @@ -3,6 +3,7 @@ import AppHeader from "./components/Header"; import "./app.scss"; import MusicBar from "./components/MusicBar"; import { Outlet } from "react-router"; +import PanelComponent from "./components/Panel"; export default function App() { return ( @@ -10,6 +11,7 @@ export default function App() {
+
diff --git a/src/renderer/components/Header/index.scss b/src/renderer/components/Header/index.scss index 6402420d..b8c47069 100644 --- a/src/renderer/components/Header/index.scss +++ b/src/renderer/components/Header/index.scss @@ -1,6 +1,6 @@ .header-container { width: 100vw; - height: 54px; + height: var(--appHeaderHeight, 54px); background-color: var(--primaryColor); display: flex; box-sizing: border-box; @@ -11,7 +11,7 @@ flex-shrink: 0; font-size: 1rem; position: relative; - z-index: 200; + z-index: 20000; & .left-part { display: flex; diff --git a/src/renderer/components/Header/index.tsx b/src/renderer/components/Header/index.tsx index 534196a6..b7dc855f 100644 --- a/src/renderer/components/Header/index.tsx +++ b/src/renderer/components/Header/index.tsx @@ -81,7 +81,6 @@ export default function AppHeader() { }} onHistoryPanelFocus={() => { isHistoryFocusRef.current = true; - console.log("FOCUS"); setShowSearchHistory(true); }} > diff --git a/src/renderer/components/Modal/templates/Base/index.scss b/src/renderer/components/Modal/templates/Base/index.scss index b4fe18cc..6eea0807 100644 --- a/src/renderer/components/Modal/templates/Base/index.scss +++ b/src/renderer/components/Modal/templates/Base/index.scss @@ -1,6 +1,6 @@ .components--modal-base { position: fixed; - z-index: 1000; + z-index: 10000; top: 0; left: 0; right: 0; diff --git a/src/renderer/components/MusicBar/index.tsx b/src/renderer/components/MusicBar/index.tsx index 27ba93a7..aeb3b2df 100644 --- a/src/renderer/components/MusicBar/index.tsx +++ b/src/renderer/components/MusicBar/index.tsx @@ -1,16 +1,9 @@ -import "./index.scss"; -import SvgAsset from "../SvgAsset"; -import PlayList from "./widgets/PlayList"; -import { useState } from "react"; -import Evt from "@renderer/core/events"; -import trackPlayer from "@/renderer/core/track-player/internal"; -import { ipcRendererInvoke } from "@/common/ipc-util/renderer"; -import { setFallbackAlbum } from "@/renderer/utils/img-on-error"; import Slider from "./widgets/Slider"; import MusicInfo from "./widgets/MusicInfo"; import Controller from "./widgets/Controller"; import Extra from "./widgets/Extra"; +import "./index.scss"; export default function MusicBar() { return ( @@ -19,7 +12,6 @@ export default function MusicBar() { - ); } diff --git a/src/renderer/components/MusicBar/widgets/Extra/index.tsx b/src/renderer/components/MusicBar/widgets/Extra/index.tsx index dab70f49..17203b4d 100644 --- a/src/renderer/components/MusicBar/widgets/Extra/index.tsx +++ b/src/renderer/components/MusicBar/widgets/Extra/index.tsx @@ -18,6 +18,11 @@ import { } from "@/renderer/utils/lrc-window-message-channel"; import classNames from "@/renderer/utils/classnames"; import { useLyric } from "@/renderer/core/track-player/player"; +import { + getCurrentPanel, + hidePanel, + showPanel, +} from "@/renderer/components/Panel"; export default function Extra() { const repeatMode = trackPlayer.useRepeatMode(); @@ -58,7 +63,11 @@ export default function Extra() { title="播放列表" role="button" onClick={() => { - Evt.emit("SWITCH_PLAY_LIST"); + if (getCurrentPanel()?.type === "PlayList") { + hidePanel(); + } else { + showPanel("PlayList"); + } }} > diff --git a/src/renderer/components/MusicBar/widgets/PlayList/index.scss b/src/renderer/components/MusicBar/widgets/PlayList/index.scss deleted file mode 100644 index 777db9e8..00000000 --- a/src/renderer/components/MusicBar/widgets/PlayList/index.scss +++ /dev/null @@ -1,101 +0,0 @@ -.music-bar--play-list-container { - position: absolute; - bottom: 64px; - right: 0; - width: 100vw; - height: 100vh; - z-index: 100; - - & .content-container { - position: absolute; - z-index: 1; - right: 0; - bottom: 0; - width: 460px; - height: 540px; - border-top-left-radius: 8px; - display: flex; - flex-direction: column; - padding-top: 14px; - - & .header { - box-sizing: border-box; - display: flex; - justify-content: space-between; - align-items: center; - padding-left: 14px; - padding-right: 14px; - - & .title { - font-size: 1.2rem; - font-weight: 600; - } - } - - & .playlist--music-list-container { - flex: 1; - overflow-y: auto; - overflow-x: hidden; - } - - & .playlist--music-list-scroll { - position: relative; - width: 100%; - } - - & .divider { - margin-bottom: 0; - } - } -} - -.play-list--music-item-container { - width: 460px; - height: 2.6rem; - display: flex; - align-items: center; - padding-left: 14px; - padding-right: 14px; - box-sizing: border-box; - - & .playlist--options { - display: flex; - gap: 4px; - } - - & .playlist--title { - margin-left: 8px; - width: 180px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - flex-shrink: 0; - } - - & .playlist--artist { - margin-left: 8px; - width: 120px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - flex-shrink: 0; - } - - & .playlist--platform { - margin-left: 8px; - flex: 1; - } - - & .playlist--remove { - flex-shrink: 0; - height: 16px; - width: 16px; - } - - &:nth-child(even) { - background-color: rgba($color: #000000, $alpha: 0.05); - } - &:hover { - background: var(--listHoverColor); - } -} diff --git a/src/renderer/components/Panel/index.tsx b/src/renderer/components/Panel/index.tsx new file mode 100644 index 00000000..ddf94613 --- /dev/null +++ b/src/renderer/components/Panel/index.tsx @@ -0,0 +1,52 @@ +import Store from "@/common/store"; +import templates from "./templates"; +import { useMemo } from "react"; + +type ITemplate = typeof templates; +type IPanelType = keyof ITemplate; + +interface IPanelInfo { + type: IPanelType | null; + payload: any; +} + +const panelStore = new Store({ + type: null, + payload: null, +}); + +export default function PanelComponent() { + const modalState = panelStore.useValue(); + + const component = useMemo(() => { + if (modalState.type) { + const Component = templates[modalState.type]; + return ; + } + return null; + }, [modalState]); + + return component; +} + +export function showPanel( + type: T, + payload?: Parameters[0] +) { + panelStore.setValue({ + type, + payload, + }); +} + +export function hidePanel() { + panelStore.setValue({ + type: null, + payload: null, + }); +} + + +export function getCurrentPanel(){ + return panelStore.getValue(); +} \ No newline at end of file diff --git a/src/renderer/components/Panel/templates/Base/index.scss b/src/renderer/components/Panel/templates/Base/index.scss new file mode 100644 index 00000000..a5e0e4d2 --- /dev/null +++ b/src/renderer/components/Panel/templates/Base/index.scss @@ -0,0 +1,52 @@ +.components--panel-base { + position: absolute; + z-index: 999; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + align-items: center; + justify-content: flex-end; + background-color: var(--maskColor); + cursor: default !important; + + & .components--panel-base-content { + border-top-left-radius: 8px; + background-color: var(--backgroundColor); + overflow-y: auto; + width: 40vw; + height: 100%; + box-shadow: var(--shadow, var(--shadowColor) -2px 0px 2px); + display: flex; + flex-direction: column; + + & .components--panel-base-header { + width: 100%; + display: flex; + height: 3rem; + box-sizing: border-box; + padding-left: 1rem; + padding-right: 1rem; + align-items: center; + justify-content: space-between; + font-size: 1.2rem; + font-weight: 600; + user-select: none; + /* background-color: rgba($color: #000000, $alpha: 0.1); */ + border-bottom: 1px solid var(--dividerColor); + flex-shrink: 0; + + & .components--panel-base-header-close { + $size: 18px; + width: $size; + height: $size; + + & svg { + width: $size; + height: $size; + } + } + } + } +} diff --git a/src/renderer/components/Panel/templates/Base/index.tsx b/src/renderer/components/Panel/templates/Base/index.tsx new file mode 100644 index 00000000..e8a1fdd6 --- /dev/null +++ b/src/renderer/components/Panel/templates/Base/index.tsx @@ -0,0 +1,109 @@ +import { ReactNode, useEffect, useRef } from "react"; +import "./index.scss"; +import SvgAsset from "@/renderer/components/SvgAsset"; +import { hidePanel } from "../.."; + +interface IBaseModalProps { + // 默认区域 + onDefaultClick?: () => void; + // 点击默认区域时关闭 + defaultClose?: boolean; + // 模糊 + withBlur?: boolean; + /** mask区域颜色 */ + maskColor?: string; + /** 标题 */ + title?: ReactNode; + width?: string | number; + scrollable?: boolean; + children: ReactNode; +} + +const baseId = "components--panel-base-container"; + +function Base(props: IBaseModalProps) { + const { + onDefaultClick, + defaultClose = true, + maskColor, + children, + withBlur = false, + width, + scrollable = true, + } = props; + + const trapCloseRef = useRef(false); + + return ( +
{ + if ((e.target as HTMLElement)?.id === baseId) { + trapCloseRef.current = true; + } else { + trapCloseRef.current = false; + } + }} + onMouseUp={(e) => { + if ((e.target as HTMLElement)?.id === baseId && trapCloseRef.current) { + if (defaultClose) { + hidePanel(); + } else { + onDefaultClick?.(); + } + } + }} + onMouseLeave={() => { + trapCloseRef.current = false; + }} + onMouseOut={() => { + trapCloseRef.current = false; + }} + > +
+ {children} +
+
+ ); +} + +interface IHeaderProps { + children: ReactNode; + right?: ReactNode; +} +function Header(props: IHeaderProps) { + const { children, right } = props; + + return ( +
+ {children} + {right ?? ( +
{ + hidePanel(); + }} + > + +
+ )} +
+ ); +} + +Base.Header = Header; +export default Base; diff --git a/src/renderer/components/Panel/templates/PlayList/index.scss b/src/renderer/components/Panel/templates/PlayList/index.scss new file mode 100644 index 00000000..e7798e5b --- /dev/null +++ b/src/renderer/components/Panel/templates/PlayList/index.scss @@ -0,0 +1,84 @@ +.playlist--header { + box-sizing: border-box; + display: flex; + justify-content: space-between; + align-items: center; + padding-left: 14px; + padding-right: 14px; + margin-top: 12px; + + & .playlist--title { + font-size: 1.2rem; + font-weight: 600; + } +} + +.playlist--music-list-container { + flex: 1; + overflow-y: auto; + overflow-x: hidden; +} + +.playlist--music-list-scroll { + position: relative; + width: 100%; +} + +.playlist--divider { + width: 100%; + height: 1px; + background-color: var(--dividerColor); + margin-top: 12px; + margin-bottom: 0; +} + +.play-list--music-item-container { + width: 460px; + height: 2.6rem; + display: flex; + align-items: center; + padding-left: 14px; + padding-right: 14px; + box-sizing: border-box; + + & .playlist--options { + display: flex; + gap: 4px; + } + + & .playlist--title { + margin-left: 8px; + width: 180px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + flex-shrink: 0; + } + + & .playlist--artist { + margin-left: 8px; + width: 120px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + flex-shrink: 0; + } + + & .playlist--platform { + margin-left: 8px; + flex: 1; + } + + & .playlist--remove { + flex-shrink: 0; + height: 16px; + width: 16px; + } + + &:nth-child(even) { + background-color: rgba($color: #000000, $alpha: 0.05); + } + &:hover { + background: var(--listHoverColor); + } +} diff --git a/src/renderer/components/MusicBar/widgets/PlayList/index.tsx b/src/renderer/components/Panel/templates/PlayList/index.tsx similarity index 53% rename from src/renderer/components/MusicBar/widgets/PlayList/index.tsx rename to src/renderer/components/Panel/templates/PlayList/index.tsx index de3e1208..c5f7c92a 100644 --- a/src/renderer/components/MusicBar/widgets/PlayList/index.tsx +++ b/src/renderer/components/Panel/templates/PlayList/index.tsx @@ -12,24 +12,15 @@ import useVirtualList from "@/renderer/hooks/useVirtualList"; import { rem } from "@/common/constant"; import { showMusicContextMenu } from "@/renderer/components/MusicList"; import MusicDownloaded from "@/renderer/components/MusicDownloaded"; +import Base from "../Base"; -const baseId = "music-bar--play-list"; const estimizeItemHeight = 2.6 * rem; export default function PlayList() { - const [show, setShow] = useState(false); const musicQueue = trackPlayer.useMusicQueue(); const currentMusic = trackPlayer.useCurrentMusic(); const scrollElementRef = useRef(); - Evt.use("SWITCH_PLAY_LIST", (payload) => { - if (!payload) { - setShow((_) => !_); - } else { - setShow((_) => payload.show); - } - }); - const virtualController = useVirtualList({ estimizeItemHeight, data: musicQueue, @@ -40,78 +31,63 @@ export default function PlayList() { }); useEffect(() => { - if (show) { - virtualController.setScrollElement(scrollElementRef.current); - const currentMusic = trackPlayer.getCurrentMusic(); - if (currentMusic) { - const queue = trackPlayer.getMusicQueue(); - const index = queue.findIndex((it) => isSameMedia(it, currentMusic)); - if (index > 4) { - virtualController.scrollToIndex(index - 4); - } + virtualController.setScrollElement(scrollElementRef.current); + const currentMusic = trackPlayer.getCurrentMusic(); + if (currentMusic) { + const queue = trackPlayer.getMusicQueue(); + const index = queue.findIndex((it) => isSameMedia(it, currentMusic)); + if (index > 4) { + virtualController.scrollToIndex(index - 4); } } - }, [show]); + }, []); - return show ? ( -
{ - if ((e.target as HTMLElement)?.id === baseId) { - setShow(false); - } - }} - > -
-
-
播放列表({musicQueue.length}首)
+ return ( + +
+
播放列表({musicQueue.length}首)
+
{ + trackPlayer.clearQueue(); + }} + > + 清空 +
+
+
+
+ }>
{ - trackPlayer.clearQueue(); + className="playlist--music-list-scroll" + style={{ + height: virtualController.totalHeight, }} > - 清空 + {virtualController.virtualItems.map((virtualItem) => { + const item = virtualItem.dataItem; + return ( +
+ +
+ ); + })}
-
-
-
- } - > -
- {virtualController.virtualItems.map((virtualItem) => { - const item = virtualItem.dataItem; - return ( -
- -
- ); - })} -
-
-
+
-
- ) : null; + + ); } interface IPlayListMusicItemProps { diff --git a/src/renderer/components/Panel/templates/UserVariables/index.scss b/src/renderer/components/Panel/templates/UserVariables/index.scss new file mode 100644 index 00000000..0d2e7e66 --- /dev/null +++ b/src/renderer/components/Panel/templates/UserVariables/index.scss @@ -0,0 +1,28 @@ +.panel--user-variables-container { + height: 100%; + width: 100%; + overflow-y: auto; + + + & .panel--user-variable-item { + width: 100%; + height: 3rem; + display: flex; + align-items: center; + padding: 0 12px; + box-sizing: border-box; + + & span { + width: 6rem; + margin-right: 12px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + flex-shrink: 0; + } + + & input { + flex: 1; + } + } +} \ No newline at end of file diff --git a/src/renderer/components/Panel/templates/UserVariables/index.tsx b/src/renderer/components/Panel/templates/UserVariables/index.tsx new file mode 100644 index 00000000..570441a9 --- /dev/null +++ b/src/renderer/components/Panel/templates/UserVariables/index.tsx @@ -0,0 +1,31 @@ +import { useRef } from "react"; +import Base from "../Base"; +import "./index.scss"; + +interface IUserVariablesProps { + variables: IPlugin.IUserVariable[]; + initValues?: Record; +} + +export default function (props: IUserVariablesProps) { + const { variables = [], initValues = {} } = props; + + const valueRef = useRef>({...(initValues ?? {})}); + + + return ( + + 用户变量 +
+ {variables.map((variable) => ( +
+ {variable.name ?? variable.key} + { + valueRef.current[variable.key] = (e.target as HTMLInputElement).value; + }}> +
+ ))} +
+ + ); +} diff --git a/src/renderer/components/Panel/templates/index.ts b/src/renderer/components/Panel/templates/index.ts new file mode 100644 index 00000000..77fa5b0a --- /dev/null +++ b/src/renderer/components/Panel/templates/index.ts @@ -0,0 +1,9 @@ +import Base from "./Base"; +import PlayList from "./PlayList"; +import UserVariables from "./UserVariables"; + +export default { + Base, + UserVariables, + PlayList +} \ No newline at end of file diff --git a/src/renderer/core/events/types/player.d.ts b/src/renderer/core/events/types/player.d.ts index ff8f24ab..b69167e7 100644 --- a/src/renderer/core/events/types/player.d.ts +++ b/src/renderer/core/events/types/player.d.ts @@ -1,10 +1,6 @@ // 播放控制 declare namespace IEventType { interface IEvents { - /** 展示播放列表 */ - SWITCH_PLAY_LIST: { - show: boolean; // 是否展示 - }; /** 展示音乐详情 */ SHOW_MUSIC_DETAIL: undefined; /** 隐藏音乐详情 */ diff --git a/src/renderer/document/index.tsx b/src/renderer/document/index.tsx index f1c2fd6a..41f38bd8 100644 --- a/src/renderer/document/index.tsx +++ b/src/renderer/document/index.tsx @@ -16,6 +16,7 @@ import "./index.css"; // 全局样式 import "./index.scss"; import { toastDuration } from "@/common/constant"; import useBootstrap from "./useBootstrap"; +import PanelComponent from "../components/Panel"; bootstrap().then(() => { ReactDOM.createRoot(document.getElementById("root")).render(); diff --git a/src/renderer/pages/main-page/views/plugin-manager-view/components/plugin-table/index.tsx b/src/renderer/pages/main-page/views/plugin-manager-view/components/plugin-table/index.tsx index 45a2ea2d..425813e0 100644 --- a/src/renderer/pages/main-page/views/plugin-manager-view/components/plugin-table/index.tsx +++ b/src/renderer/pages/main-page/views/plugin-manager-view/components/plugin-table/index.tsx @@ -16,9 +16,11 @@ import { hideModal, showModal } from "@/renderer/components/Modal"; import Empty from "@/renderer/components/Empty"; import { ipcRendererInvoke } from "@/common/ipc-util/renderer"; import { toast } from "react-toastify"; +import { showPanel } from "@/renderer/components/Panel"; function renderOptions(info: any) { const row = info.row.original as IPlugin.IPluginDelegate; + return (
+ + { + showPanel('UserVariables', { + variables: row.userVariables + }); + }} + > + 用户变量 + +
); } diff --git a/src/renderer/pages/main-page/views/plugin-manager-view/index.tsx b/src/renderer/pages/main-page/views/plugin-manager-view/index.tsx index fb2ea57c..d3d3d99e 100644 --- a/src/renderer/pages/main-page/views/plugin-manager-view/index.tsx +++ b/src/renderer/pages/main-page/views/plugin-manager-view/index.tsx @@ -128,7 +128,7 @@ export default function PluginManagerView() { onClick={async () => { const subscription = getUserPerference("subscription"); - if (subscription.length) { + if (subscription?.length) { for (let i = 0; i < subscription.length; ++i) { await ipcRendererInvoke( "install-plugin-remote", diff --git a/src/types/plugin.d.ts b/src/types/plugin.d.ts index c73eac92..4f8ed688 100644 --- a/src/types/plugin.d.ts +++ b/src/types/plugin.d.ts @@ -28,7 +28,7 @@ declare namespace IPlugin { type: T, ) => Promise>; - interface IUserVariables { + interface IUserVariable { /** 变量键名 */ key: string; /** 变量名 */ @@ -71,7 +71,7 @@ declare namespace IPlugin { /** 插件缓存控制 */ cacheControl?: 'cache' | 'no-cache' | 'no-store'; /** 用户自定义输入 */ - userVariables?: IUserVariables[]; + userVariables?: IUserVariable[]; /** 提示文本 */ hints?: Record; /** 搜索 */ From bce3db17061f9b5a98fc6271236227b1b3afa01e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Wed, 22 Nov 2023 09:43:02 +0800 Subject: [PATCH 05/50] =?UTF-8?q?feat:=20=E7=94=A8=E6=88=B7=E5=8F=98?= =?UTF-8?q?=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/app-config/type.d.ts | 1 + .../Panel/templates/UserVariables/index.scss | 10 ++++++++++ .../components/Panel/templates/UserVariables/index.tsx | 8 ++++++-- .../components/plugin-table/index.tsx | 3 ++- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/common/app-config/type.d.ts b/src/common/app-config/type.d.ts index 23a69e27..d2d7d8c5 100644 --- a/src/common/app-config/type.d.ts +++ b/src/common/app-config/type.d.ts @@ -82,6 +82,7 @@ interface IConfig { x: number; y: number; }; + pluginMeta: Record }; } diff --git a/src/renderer/components/Panel/templates/UserVariables/index.scss b/src/renderer/components/Panel/templates/UserVariables/index.scss index 0d2e7e66..93546aba 100644 --- a/src/renderer/components/Panel/templates/UserVariables/index.scss +++ b/src/renderer/components/Panel/templates/UserVariables/index.scss @@ -1,7 +1,17 @@ +.panel--user-variables-submit { + font-weight: 400; + padding: 0.4rem 0.6rem; + font-size: 1rem; + border-radius: 8px; + color: var(--infoColor, #0A95C8); + border: 1px solid currentColor; +} + .panel--user-variables-container { height: 100%; width: 100%; overflow-y: auto; + & .panel--user-variable-item { diff --git a/src/renderer/components/Panel/templates/UserVariables/index.tsx b/src/renderer/components/Panel/templates/UserVariables/index.tsx index 570441a9..236275f0 100644 --- a/src/renderer/components/Panel/templates/UserVariables/index.tsx +++ b/src/renderer/components/Panel/templates/UserVariables/index.tsx @@ -1,21 +1,25 @@ import { useRef } from "react"; import Base from "../Base"; import "./index.scss"; +import rendererAppConfig from "@/common/app-config/renderer"; interface IUserVariablesProps { + plugin: IPlugin.IPluginDelegate, variables: IPlugin.IUserVariable[]; initValues?: Record; } export default function (props: IUserVariablesProps) { - const { variables = [], initValues = {} } = props; + const { variables = [], initValues = {}, plugin } = props; const valueRef = useRef>({...(initValues ?? {})}); return ( - 用户变量 + { + rendererAppConfig.setAppConfigPath(`private.pluginMeta.${plugin.platform}.userVariables`, valueRef.current) + }}>确认
}>{plugin.platform ?? ''} 用户变量
{variables.map((variable) => (
diff --git a/src/renderer/pages/main-page/views/plugin-manager-view/components/plugin-table/index.tsx b/src/renderer/pages/main-page/views/plugin-manager-view/components/plugin-table/index.tsx index 425813e0..a1df642c 100644 --- a/src/renderer/pages/main-page/views/plugin-manager-view/components/plugin-table/index.tsx +++ b/src/renderer/pages/main-page/views/plugin-manager-view/components/plugin-table/index.tsx @@ -139,7 +139,8 @@ function renderOptions(info: any) { }} onClick={() => { showPanel('UserVariables', { - variables: row.userVariables + variables: row.userVariables, + plugin: row }); }} > From fec0a367a12c36c47029be4e501b708265d6cd16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Thu, 23 Nov 2023 09:11:40 +0800 Subject: [PATCH 06/50] =?UTF-8?q?feat:=20=E7=94=A8=E6=88=B7=E5=8F=98?= =?UTF-8?q?=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/core/plugin-manager/index.ts | 1 - src/main/core/plugin-manager/plugin.ts | 14 ++++++- .../Panel/templates/UserVariables/index.tsx | 41 +++++++++++++++---- .../components/plugin-table/index.tsx | 4 +- 4 files changed, 47 insertions(+), 13 deletions(-) diff --git a/src/main/core/plugin-manager/index.ts b/src/main/core/plugin-manager/index.ts index 844b97e9..f7b0d05e 100644 --- a/src/main/core/plugin-manager/index.ts +++ b/src/main/core/plugin-manager/index.ts @@ -7,7 +7,6 @@ import { ipcMainOn, ipcMainSendMainWindow, } from "@/common/ipc-util/main"; -import { getMainWindow } from "@/main/window"; import { localPluginHash, localPluginName } from "@/common/constant"; import localPlugin from "./local-plugin"; import { rimraf } from "rimraf"; diff --git a/src/main/core/plugin-manager/plugin.ts b/src/main/core/plugin-manager/plugin.ts index 0380e964..e7059f94 100644 --- a/src/main/core/plugin-manager/plugin.ts +++ b/src/main/core/plugin-manager/plugin.ts @@ -8,6 +8,7 @@ import * as cheerio from 'cheerio'; import he from 'he'; import PluginMethods from './plugin-methods'; import reactNativeCookies from './polyfill/react-native-cookies'; +import { getAppConfigPathSync } from '@/common/app-config/main'; axios.defaults.timeout = 15000; @@ -80,13 +81,22 @@ export class Plugin { const _module: any = {exports: {}}; try { if (typeof funcCode === 'string') { + // 插件的环境变量 + const env = { + getUserVariables: () => { + return ( + getAppConfigPathSync('private.pluginMeta')?.[this.name]?.userVariables ?? {} + ); + }, + os: process.platform, + }; // eslint-disable-next-line no-new-func _instance = Function(` 'use strict'; - return function(require, __musicfree_require, module, exports, console) { + return function(require, __musicfree_require, module, exports, console, env) { ${funcCode} } - `)()(_require, _require, _module, _module.exports, console); + `)()(_require, _require, _module, _module.exports, console, env); if (_module.exports.default) { _instance = _module.exports .default as IPlugin.IPluginInstance; diff --git a/src/renderer/components/Panel/templates/UserVariables/index.tsx b/src/renderer/components/Panel/templates/UserVariables/index.tsx index 236275f0..f9bbb8d2 100644 --- a/src/renderer/components/Panel/templates/UserVariables/index.tsx +++ b/src/renderer/components/Panel/templates/UserVariables/index.tsx @@ -2,9 +2,11 @@ import { useRef } from "react"; import Base from "../Base"; import "./index.scss"; import rendererAppConfig from "@/common/app-config/renderer"; +import { hidePanel } from "../.."; +import { toast } from "react-toastify"; interface IUserVariablesProps { - plugin: IPlugin.IPluginDelegate, + plugin: IPlugin.IPluginDelegate; variables: IPlugin.IUserVariable[]; initValues?: Record; } @@ -12,21 +14,42 @@ interface IUserVariablesProps { export default function (props: IUserVariablesProps) { const { variables = [], initValues = {}, plugin } = props; - const valueRef = useRef>({...(initValues ?? {})}); - + const valueRef = useRef>({ ...(initValues ?? {}) }); return ( - { - rendererAppConfig.setAppConfigPath(`private.pluginMeta.${plugin.platform}.userVariables`, valueRef.current) - }}>确认
}>{plugin.platform ?? ''} 用户变量 + { + rendererAppConfig.setAppConfigPath( + `private.pluginMeta.${plugin.platform}.userVariables`, + valueRef.current + ); + hidePanel(); + toast.success("设置成功~"); + }} + > + 确认 +
+ } + > + {plugin.platform ?? ""} 用户变量 +
{variables.map((variable) => (
{variable.name ?? variable.key} - { - valueRef.current[variable.key] = (e.target as HTMLInputElement).value; - }}> + { + valueRef.current[variable.key] = ( + e.target as HTMLInputElement + ).value; + }} + >
))}
diff --git a/src/renderer/pages/main-page/views/plugin-manager-view/components/plugin-table/index.tsx b/src/renderer/pages/main-page/views/plugin-manager-view/components/plugin-table/index.tsx index a1df642c..f20ef285 100644 --- a/src/renderer/pages/main-page/views/plugin-manager-view/components/plugin-table/index.tsx +++ b/src/renderer/pages/main-page/views/plugin-manager-view/components/plugin-table/index.tsx @@ -17,6 +17,7 @@ import Empty from "@/renderer/components/Empty"; import { ipcRendererInvoke } from "@/common/ipc-util/renderer"; import { toast } from "react-toastify"; import { showPanel } from "@/renderer/components/Panel"; +import rendererAppConfig from "@/common/app-config/renderer"; function renderOptions(info: any) { const row = info.row.original as IPlugin.IPluginDelegate; @@ -140,7 +141,8 @@ function renderOptions(info: any) { onClick={() => { showPanel('UserVariables', { variables: row.userVariables, - plugin: row + plugin: row, + initValues: rendererAppConfig.getAppConfigPath('private.pluginMeta')?.[row.platform]?.userVariables }); }} > From a4c432912f781803943049caea4f20291eda271e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Fri, 24 Nov 2023 09:31:10 +0800 Subject: [PATCH 07/50] =?UTF-8?q?feat:=20=E8=B0=83=E6=95=B4=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/Panel/templates/PlayList/index.scss | 7 +++++-- src/renderer/components/Panel/templates/PlayList/index.tsx | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/renderer/components/Panel/templates/PlayList/index.scss b/src/renderer/components/Panel/templates/PlayList/index.scss index e7798e5b..6df66e1f 100644 --- a/src/renderer/components/Panel/templates/PlayList/index.scss +++ b/src/renderer/components/Panel/templates/PlayList/index.scss @@ -57,7 +57,7 @@ & .playlist--artist { margin-left: 8px; - width: 120px; + width: 106px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; @@ -66,11 +66,14 @@ & .playlist--platform { margin-left: 8px; - flex: 1; + flex-basis: 0; + flex-grow: 1; + width: 0; } & .playlist--remove { flex-shrink: 0; + margin-left: 8px; height: 16px; width: 16px; } diff --git a/src/renderer/components/Panel/templates/PlayList/index.tsx b/src/renderer/components/Panel/templates/PlayList/index.tsx index c5f7c92a..943e26c3 100644 --- a/src/renderer/components/Panel/templates/PlayList/index.tsx +++ b/src/renderer/components/Panel/templates/PlayList/index.tsx @@ -121,7 +121,9 @@ function _PlayListMusicItem(props: IPlayListMusicItemProps) { {musicItem.artist ?? "-"}
- {musicItem.platform} + {musicItem.platform}
Date: Fri, 24 Nov 2023 09:39:52 +0800 Subject: [PATCH 08/50] =?UTF-8?q?feat:=20=E6=92=AD=E6=94=BE=E6=8E=92?= =?UTF-8?q?=E5=BA=8F/=E8=BF=87=E6=BB=A4=E5=90=8E=E7=9A=84=E6=AD=8C?= =?UTF-8?q?=E6=9B=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/components/MusicList/index.tsx | 5 ++--- .../components/MusicSheetlikeView/components/Body/index.tsx | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/renderer/components/MusicList/index.tsx b/src/renderer/components/MusicList/index.tsx index 40d4b9f2..eb075476 100644 --- a/src/renderer/components/MusicList/index.tsx +++ b/src/renderer/components/MusicList/index.tsx @@ -276,7 +276,7 @@ function _MusicList(props: IMusicListProps) { onPageChange, musicSheet, virtualProps, - getAllMusicItems, + // getAllMusicItems, doubleClickBehavior, containerStyle, hideRows, @@ -483,9 +483,8 @@ function _MusicList(props: IMusicListProps) { "playMusic.clickMusicList" ); if (config === "replace") { - // TODO: 排序后的 trackPlayer.playMusicWithReplaceQueue( - getAllMusicItems?.() ?? musicList, + table.getRowModel().rows.map(it => it.original), row.original ); } else { diff --git a/src/renderer/components/MusicSheetlikeView/components/Body/index.tsx b/src/renderer/components/MusicSheetlikeView/components/Body/index.tsx index 57220371..298f0560 100644 --- a/src/renderer/components/MusicSheetlikeView/components/Body/index.tsx +++ b/src/renderer/components/MusicSheetlikeView/components/Body/index.tsx @@ -123,7 +123,7 @@ export default function Body(props: IProps) { > musicList} + // getAllMusicItems={() => musicList} // TODO: 过滤歌曲 musicSheet={musicSheet} state={state} onPageChange={onLoadMore} From a4cbfeb63443a9687ee1061d04cce412c5d93bd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Fri, 24 Nov 2023 09:42:47 +0800 Subject: [PATCH 09/50] =?UTF-8?q?fix:=20=E8=BF=87=E6=BB=A4=E6=97=A0?= =?UTF-8?q?=E6=95=88=E7=9A=84=E7=94=A8=E6=88=B7=E5=8F=98=E9=87=8F=E5=AE=9A?= =?UTF-8?q?=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/core/plugin-manager/plugin.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/core/plugin-manager/plugin.ts b/src/main/core/plugin-manager/plugin.ts index e7059f94..6eb98fd2 100644 --- a/src/main/core/plugin-manager/plugin.ts +++ b/src/main/core/plugin-manager/plugin.ts @@ -106,6 +106,12 @@ export class Plugin { } else { _instance = funcCode(); } + // 插件初始化后的一些操作 + if (Array.isArray(_instance.userVariables)) { + _instance.userVariables = _instance.userVariables.filter( + it => it?.key, + ); + } this.checkValid(_instance); } catch (e: any) { console.log(e); From c2abb789b7d83b34e689c57062305862dfe050ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= <31655147+maotoumao@users.noreply.github.com> Date: Fri, 24 Nov 2023 04:20:50 +0000 Subject: [PATCH 10/50] =?UTF-8?q?fix:=20=E6=96=87=E6=A1=88=E9=94=99?= =?UTF-8?q?=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/components/MusicList/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/components/MusicList/index.tsx b/src/renderer/components/MusicList/index.tsx index eb075476..ab5092e6 100644 --- a/src/renderer/components/MusicList/index.tsx +++ b/src/renderer/components/MusicList/index.tsx @@ -256,7 +256,7 @@ export function showMusicContextMenu( ); } } catch (e) { - toast.error(`删除失败: ${e?.message ?? ""}`); + toast.error(`打开失败: ${e?.message ?? ""}`); } }, } From 1fd372287412e01981884cfa9da28da13b164aaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= <31655147+maotoumao@users.noreply.github.com> Date: Fri, 24 Nov 2023 04:27:36 +0000 Subject: [PATCH 11/50] =?UTF-8?q?fix:=20=E6=97=A0=E6=B3=95=E5=8F=B3?= =?UTF-8?q?=E9=94=AE=E6=89=93=E5=BC=80=E4=B8=8B=E8=BD=BD=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=A4=B9=20#49?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/components/MusicList/index.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/renderer/components/MusicList/index.tsx b/src/renderer/components/MusicList/index.tsx index ab5092e6..ff3aaa62 100644 --- a/src/renderer/components/MusicList/index.tsx +++ b/src/renderer/components/MusicList/index.tsx @@ -39,6 +39,7 @@ import classNames from "@/renderer/utils/classnames"; import SwitchCase from "../SwitchCase"; import SvgAsset from "../SvgAsset"; import { getAppConfigPath } from "@/common/app-config/main"; +import musicSheetDB from "@/renderer/core/db/music-sheet-db"; interface IMusicListProps { /** 展示的播放列表 */ @@ -245,11 +246,15 @@ export function showMusicContextMenu( async onClick() { try { if (!isArray) { + let realTimeMusicItem = musicItems; + if (musicItems.platform !== localPluginName) { + realTimeMusicItem = await musicSheetDB.musicStore.get([musicItems.platform, musicItems.id]); + } ipcRendererSend( "open-path", window.path.dirname( getInternalData( - musicItems, + realTimeMusicItem, "downloadData" )?.path ) From 80c8fec9744d0d4274cac151bc8b7b3b6df6cbb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Mon, 27 Nov 2023 22:42:21 +0800 Subject: [PATCH 12/50] =?UTF-8?q?fix:=20macos=E8=BE=93=E5=85=A5=E6=A1=86?= =?UTF-8?q?=E6=97=A0=E6=B3=95=E7=B2=98=E8=B4=B4=20#47?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/core/plugin-manager/plugin.ts | 2 - src/main/tray/index.ts | 52 +++++++++++++++++++++++--- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/main/core/plugin-manager/plugin.ts b/src/main/core/plugin-manager/plugin.ts index 6eb98fd2..d74ea4d2 100644 --- a/src/main/core/plugin-manager/plugin.ts +++ b/src/main/core/plugin-manager/plugin.ts @@ -70,8 +70,6 @@ export class Plugin { public path: string; /** 插件方法 */ public methods: PluginMethods; - // /** TODO 用户输入 */ - // public userEnv?: Record; constructor( funcCode: string | (() => IPlugin.IPluginInstance), diff --git a/src/main/tray/index.ts b/src/main/tray/index.ts index 050f0bf4..b3263e0c 100644 --- a/src/main/tray/index.ts +++ b/src/main/tray/index.ts @@ -15,13 +15,55 @@ import { getAppConfigPath } from "@/common/app-config/main"; import { setDesktopLyricLock, setLyricWindow } from "../ipc"; let tray: Tray | null = null; -Menu.setApplicationMenu(null); + +if (process.platform === "darwin") { + Menu.setApplicationMenu( + Menu.buildFromTemplate([ + { + label: app.getName(), + submenu: [ + { + label: "关于", + role: "about", + }, + { + label: "退出", + click() { + app.quit(); + }, + }, + ], + }, + { + label: "编辑", + submenu: [ + { + label: "撤销", + accelerator: "Command+Z", + role: "undo", + }, + { label: "恢复", accelerator: "Shift+Command+Z", role: "redo" }, + { type: "separator" }, + { label: "剪切", accelerator: "Command+X", role: "cut" }, + { label: "复制", accelerator: "Command+C", role: "copy" }, + { label: "粘贴", accelerator: "Command+V", role: "paste" }, + { type: "separator" }, + { label: "全选", accelerator: "Command+A", role: "selectAll" }, + ], + }, + ]) + ); +} else { + Menu.setApplicationMenu(null); +} export function setupTray() { - tray = new Tray(nativeImage.createFromPath(getResPath('logo.png')).resize({ - width: 32, - height: 32 - })); + tray = new Tray( + nativeImage.createFromPath(getResPath("logo.png")).resize({ + width: 32, + height: 32, + }) + ); tray.on("double-click", () => { showMainWindow(); From de9887b696cdbae3645698696d3e617ff1b56dc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= <31655147+maotoumao@users.noreply.github.com> Date: Fri, 1 Dec 2023 05:49:35 +0000 Subject: [PATCH 13/50] =?UTF-8?q?feat:=20macos=20=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E6=A0=8F=E6=AD=8C=E8=AF=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/ipc-util/eventType/renderer-events.d.ts | 1 + src/main/ipc/index.ts | 6 +++++- src/main/tray/index.ts | 12 ++++++++++++ src/renderer/core/message-manager/index.ts | 4 ++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/common/ipc-util/eventType/renderer-events.d.ts b/src/common/ipc-util/eventType/renderer-events.d.ts index 32307cbd..80eda1f1 100644 --- a/src/common/ipc-util/eventType/renderer-events.d.ts +++ b/src/common/ipc-util/eventType/renderer-events.d.ts @@ -19,6 +19,7 @@ declare namespace IpcEvents { "sync-current-music": IMusic.IMusicItem; "sync-current-playing-state": import("@/renderer/core/track-player/enum").PlayerState; "sync-current-repeat-mode": import("@/renderer/core/track-player/enum").RepeatMode; + 'sync-current-lyric': string; 'send-to-lyric-window': { diff --git a/src/main/ipc/index.ts b/src/main/ipc/index.ts index a46629eb..1daf586d 100644 --- a/src/main/ipc/index.ts +++ b/src/main/ipc/index.ts @@ -20,7 +20,7 @@ import { } from "electron"; import { currentMusicInfoStore } from "../store/current-music"; import { PlayerState } from "@/renderer/core/track-player/enum"; -import { setupTrayMenu } from "../tray"; +import { setTrayTitle, setupTrayMenu } from "../tray"; import axios from "axios"; import { compare } from "compare-versions"; import { getPluginByMedia } from "../core/plugin-manager"; @@ -126,6 +126,10 @@ export default function setupIpcMain() { setupTrayMenu(); }); + ipcMainOn('sync-current-lyric', (lrc) => { + setTrayTitle(lrc); + }) + ipcMainHandle("app-get-path", (pathName) => { return app.getPath(pathName as any); }); diff --git a/src/main/tray/index.ts b/src/main/tray/index.ts index b3263e0c..5ba6fbd6 100644 --- a/src/main/tray/index.ts +++ b/src/main/tray/index.ts @@ -259,3 +259,15 @@ export async function setupTrayMenu() { tray.setContextMenu(Menu.buildFromTemplate(ctxMenu)); } + + +export function setTrayTitle(str: string) { + if(!str || !str.length) { + tray.setTitle(""); + } + if (str.length > 7) { + tray?.setTitle(" " + str.slice(0, ) + '...') + } else { + tray?.setTitle(" " + str); + } +} \ No newline at end of file diff --git a/src/renderer/core/message-manager/index.ts b/src/renderer/core/message-manager/index.ts index 037b8e79..843db864 100644 --- a/src/renderer/core/message-manager/index.ts +++ b/src/renderer/core/message-manager/index.ts @@ -137,6 +137,10 @@ async function setupMessageManager() { }); trackPlayerEventsEmitter.on(TrackPlayerEvent.CurrentLyricChanged, (lyric) => { + if (window.globalData.platform === 'darwin') { + // 只有macos需要同步歌词,用来设置状态栏歌词 + ipcRendererSend('sync-current-lyric', lyric?.lrc?.lrc ?? ''); + } window.mainPort.broadcast({ data: lyric, type: "sync-current-lyric", From 7d680a55f7c45eb3390935aee4bf049a129f0079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Sat, 2 Dec 2023 14:28:59 +0800 Subject: [PATCH 14/50] =?UTF-8?q?feat:=20=E6=8F=92=E4=BB=B6=E6=8B=96?= =?UTF-8?q?=E6=8B=BD=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/media-util.ts | 17 ++-- .../components/DragReceiver/index.scss | 31 +++++++ .../components/DragReceiver/index.tsx | 73 +++++++++++++++ src/renderer/components/MusicList/index.scss | 30 ++++++- src/renderer/components/MusicList/index.tsx | 88 ++++++++++++++++--- .../components/Body/index.tsx | 16 +++- .../music-sheet/internal/sheets-method.ts | 4 +- .../core/plugin-delegate/internal/events.ts | 1 + .../core/plugin-delegate/internal/methods.ts | 63 ++++++++++++- .../components/plugin-table/index.scss | 9 +- .../components/plugin-table/index.tsx | 71 ++++++++++++--- .../views/recommend-sheets-view/index.tsx | 4 +- .../main-page/views/search-view/index.tsx | 5 +- .../main-page/views/toplist-view/index.tsx | 4 +- src/types/plugin.d.ts | 4 +- 15 files changed, 368 insertions(+), 52 deletions(-) create mode 100644 src/renderer/components/DragReceiver/index.scss create mode 100644 src/renderer/components/DragReceiver/index.tsx diff --git a/src/common/media-util.ts b/src/common/media-util.ts index 23e41e59..80155d4b 100644 --- a/src/common/media-util.ts +++ b/src/common/media-util.ts @@ -39,10 +39,10 @@ export function resetMediaItem( } export function getMediaPrimaryKey(mediaItem: IMedia.IUnique) { - if(mediaItem) { + if (mediaItem) { return `${mediaItem.platform}@${mediaItem.id}`; } - return 'invalid@invalid' + return "invalid@invalid"; } export function sortByTimestampAndIndex(array: any[], newArray = false) { @@ -110,12 +110,7 @@ export function setInternalData< T extends Record, K extends keyof T = keyof T, R extends IMedia.IMediaBase = IMedia.IMediaBase ->( - mediaItem: R, - internalProp: K, - value: T[K] | null, - newObj = false -): R { +>(mediaItem: R, internalProp: K, value: T[K] | null, newObj = false): R { if (newObj) { return { ...mediaItem, @@ -131,3 +126,9 @@ export function setInternalData< return mediaItem; } +export function toMediaBase(media: IMedia.IMediaBase) { + return { + platform: media.platform, + id: media.id, + }; +} diff --git a/src/renderer/components/DragReceiver/index.scss b/src/renderer/components/DragReceiver/index.scss new file mode 100644 index 00000000..bd9779e9 --- /dev/null +++ b/src/renderer/components/DragReceiver/index.scss @@ -0,0 +1,31 @@ + +.components--drag-receiver { + position: absolute; + left: 0; + height: 12px; + width: 100%; + display: flex; + align-items: center; + + & .components--drag-receiver-content { + width: 100%; + height: 2px; + background-color: var(--primaryColor); + pointer-events: none; + } + } + + .components--drag-receiver-top { + top: -6px; + } + + .components--drag-receiver-bottom { + bottom: -6px; + } + + .components--drag-receiver-table-container { + width: 0; + max-width: 0; + flex-basis: 0; + flex-grow: 0; + } \ No newline at end of file diff --git a/src/renderer/components/DragReceiver/index.tsx b/src/renderer/components/DragReceiver/index.tsx new file mode 100644 index 00000000..a7322fed --- /dev/null +++ b/src/renderer/components/DragReceiver/index.tsx @@ -0,0 +1,73 @@ +import { useCallback, useState, DragEvent } from "react"; +import { IfTruthy } from "../Condition"; +import "./index.scss"; + +interface IDragReceiverProps { + // 位置:顶部/底部 + position: "top" | "bottom"; + // 当前响应器的下标 + rowIndex: number; + // 释放事件 + onDrop?: (from: number, to: number) => void; + /** 用来匹配拖拽源的tag */ + tag?: string; + /** 是否需要td标签包裹 */ + insideTable?: boolean; +} + +export default function DragReceiver(props: IDragReceiverProps) { + const { position, rowIndex, onDrop, tag, insideTable } = props; + const [draggingOver, setDraggingOver] = useState(false); + + const onDragOver = useCallback(() => { + setDraggingOver(true); + }, []); + + const onDragLeave = useCallback(() => { + setDraggingOver(false); + }, []); + + const contentComponent = ( +
{ + const itemIndex = +e.dataTransfer.getData("itemIndex"); + const itemTag = e.dataTransfer.getData("itemTag"); + setDraggingOver(false); + + const _itemTag = (itemTag === 'null' || itemTag === 'undefined') ? null : `${itemTag}`; + const _tag = tag ? `${tag}` : null; + if (_itemTag !== _tag) { + // tag 不一致 忽略 + return; + } + if (itemIndex >= 0) { + onDrop?.(itemIndex, rowIndex); + } + }} + > + +
+
+
+ ); + + return insideTable ? ( + {contentComponent} + ) : ( + contentComponent + ); +} + +export function startDrag( + e: DragEvent, + itemIndex: number | string, + tag?: string +) { + e.dataTransfer.setData("itemIndex", `${itemIndex}`); + e.dataTransfer.setData("itemTag", tag ?? null); +} diff --git a/src/renderer/components/MusicList/index.scss b/src/renderer/components/MusicList/index.scss index b3d6fc30..52a6ce87 100644 --- a/src/renderer/components/MusicList/index.scss +++ b/src/renderer/components/MusicList/index.scss @@ -5,7 +5,7 @@ & th { position: relative; - &:not([data-id=like]):hover { + &:not([data-id="like"]):hover { background-color: var(--listHoverColor); & .sort-container[data-sorting="false"] { @@ -40,6 +40,10 @@ } } + & tr { + position: relative; + } + & td { white-space: nowrap; overflow: hidden; @@ -76,3 +80,27 @@ // opacity: 1; // } } + +.music-list-drag-receiver { + position: absolute; + left: 0; + height: 12px; + width: 100%; + display: flex; + align-items: center; + + & .music-list-drag-receiver-content { + width: 100%; + height: 2px; + background-color: var(--primaryColor); + pointer-events: none; + } +} + +.music-list-drag-receiver-top { + top: -6px; +} + +.music-list-drag-receiver-bottom { + bottom: -6px; +} diff --git a/src/renderer/components/MusicList/index.tsx b/src/renderer/components/MusicList/index.tsx index ff3aaa62..fef90aa9 100644 --- a/src/renderer/components/MusicList/index.tsx +++ b/src/renderer/components/MusicList/index.tsx @@ -14,7 +14,7 @@ import Tag from "../Tag"; import { secondsToDuration } from "@/common/time-util"; import MusicSheet from "@/renderer/core/music-sheet"; import trackPlayer from "@/renderer/core/track-player"; -import Condition from "../Condition"; +import Condition, { IfTruthy } from "../Condition"; import Empty from "../Empty"; import MusicFavorite from "../MusicFavorite"; import MusicDownloaded from "../MusicDownloaded"; @@ -26,7 +26,15 @@ import { getMediaPrimaryKey, isSameMedia, } from "@/common/media-util"; -import { CSSProperties, memo, useEffect, useRef, useState } from "react"; +import { + CSSProperties, + memo, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; import { showModal } from "../Modal"; import useVirtualList from "@/renderer/hooks/useVirtualList"; import rendererAppConfig from "@/common/app-config/renderer"; @@ -40,6 +48,7 @@ import SwitchCase from "../SwitchCase"; import SvgAsset from "../SvgAsset"; import { getAppConfigPath } from "@/common/app-config/main"; import musicSheetDB from "@/renderer/core/db/music-sheet-db"; +import DragReceiver, { startDrag } from "../DragReceiver"; interface IMusicListProps { /** 展示的播放列表 */ @@ -49,11 +58,9 @@ interface IMusicListProps { /** 音乐列表所属的歌单信息 */ musicSheet?: IMusic.IMusicSheetItem; // enablePagination?: boolean; // 分页/虚拟长列表 - enableSort?: boolean; // 拖拽排序 - onSortEnd?: () => void; // 排序结束 - state?: RequestStateCode; - doubleClickBehavior?: "replace" | "normal"; - onPageChange?: (page?: number) => void; + state?: RequestStateCode; // 网络状态 + doubleClickBehavior?: "replace" | "normal"; // 双击行为 + onPageChange?: (page?: number) => void; // 分页 /** 虚拟滚动参数 */ virtualProps?: { offsetHeight?: number | (() => number); // 距离顶部的高度 @@ -64,6 +71,10 @@ interface IMusicListProps { hideRows?: Array< "like" | "index" | "title" | "artist" | "album" | "duration" | "platform" >; + /** 允许拖拽 */ + enableDrag?: boolean; + /** 拖拽结束 */ + onDragEnd?: (newMusicList: IMusic.IMusicItem[]) => void; } const columnHelper = createColumnHelper(); @@ -248,7 +259,10 @@ export function showMusicContextMenu( if (!isArray) { let realTimeMusicItem = musicItems; if (musicItems.platform !== localPluginName) { - realTimeMusicItem = await musicSheetDB.musicStore.get([musicItems.platform, musicItems.id]); + realTimeMusicItem = await musicSheetDB.musicStore.get([ + musicItems.platform, + musicItems.id, + ]); } ipcRendererSend( "open-path", @@ -285,6 +299,8 @@ function _MusicList(props: IMusicListProps) { doubleClickBehavior, containerStyle, hideRows, + enableDrag, + onDragEnd, } = props; const [sorting, setSorting] = useState([]); @@ -300,7 +316,6 @@ function _MusicList(props: IMusicListProps) { ) ); - const table = useReactTable({ debugAll: false, data: musicList, @@ -308,7 +323,9 @@ function _MusicList(props: IMusicListProps) { state: { sorting: sorting, columnVisibility: hideRows - ? hideRows.reduce((prev, curr) => ({ ...prev, [curr]: false }), {...columnShownRef.current}) + ? hideRows.reduce((prev, curr) => ({ ...prev, [curr]: false }), { + ...columnShownRef.current, + }) : columnShownRef.current, }, onSortingChange: setSorting, @@ -351,6 +368,21 @@ function _MusicList(props: IMusicListProps) { }; }, []); + const _onDrop = useCallback( + (fromIndex: number, toIndex: number) => { + if (!onDragEnd || fromIndex === toIndex) { + // 没有移动 + return; + } + const newData = musicList + .slice(0, fromIndex) + .concat(musicList.slice(fromIndex + 1)); + newData.splice(toIndex, 0, musicList[fromIndex]); + onDragEnd?.(newData); + }, + [onDragEnd, musicList] + ); + return (
- {virtualController.virtualItems.map((virtualItem) => { + {virtualController.virtualItems.map((virtualItem, index) => { const row = virtualItem.dataItem; + if (!row.original) { return null; } @@ -489,13 +522,22 @@ function _MusicList(props: IMusicListProps) { ); if (config === "replace") { trackPlayer.playMusicWithReplaceQueue( - table.getRowModel().rows.map(it => it.original), + table.getRowModel().rows.map((it) => it.original), row.original ); } else { trackPlayer.playMusic(row.original); } }} + draggable={enableDrag} + onDragStart={(e) => { + // TODO + // if(activeItems) { + + // } + startDrag(e, virtualItem.rowIndex, "musiclist"); + + }} > {row.getVisibleCells().map((cell) => ( ))} + + + + + + ); })} @@ -542,10 +602,10 @@ export default memo( _MusicList, (prev, curr) => prev.state === curr.state && - prev.enableSort === curr.enableSort && + prev.enableDrag === curr.enableDrag && prev.musicList === curr.musicList && prev.onPageChange === curr.onPageChange && - prev.onSortEnd === curr.onSortEnd && + prev.onDragEnd === curr.onDragEnd && prev.musicSheet && curr.musicSheet && isSameMedia(prev.musicSheet, curr.musicSheet) diff --git a/src/renderer/components/MusicSheetlikeView/components/Body/index.tsx b/src/renderer/components/MusicSheetlikeView/components/Body/index.tsx index 298f0560..32607e0d 100644 --- a/src/renderer/components/MusicSheetlikeView/components/Body/index.tsx +++ b/src/renderer/components/MusicSheetlikeView/components/Body/index.tsx @@ -13,16 +13,18 @@ import Condition from "@/renderer/components/Condition"; import Loading from "@/renderer/components/Loading"; import trackPlayer from "@/renderer/core/track-player"; import { showModal } from "@/renderer/components/Modal"; -import { RequestStateCode, rem } from "@/common/constant"; +import { RequestStateCode, localPluginName, rem } from "@/common/constant"; import { offsetHeightStore } from "../../store"; import rendererAppConfig from "@/common/app-config/renderer"; +import MusicSheet from "@/renderer/core/music-sheet"; +import { toMediaBase } from "@/common/media-util"; interface IProps { musicSheet: IMusic.IMusicSheetItem; musicList: IMusic.IMusicItem[]; state?: RequestStateCode; onLoadMore?: () => void; - options?: ReactNode + options?: ReactNode; } export default function Body(props: IProps) { const { musicList = [], musicSheet, state, onLoadMore, options } = props; @@ -133,6 +135,16 @@ export default function Body(props: IProps) { }, offsetHeight: () => offsetHeightStore.getValue(), }} + // enableDrag={musicSheet?.platform === localPluginName} + onDragEnd={(newData) => { + if (musicSheet?.platform === localPluginName) { + // @ts-ignore + MusicSheet.updateSheet(musicSheet.id, { + // @ts-ignore + musicList: newData.map(toMediaBase), + }); + } + }} >
diff --git a/src/renderer/core/music-sheet/internal/sheets-method.ts b/src/renderer/core/music-sheet/internal/sheets-method.ts index 1328be91..ee91ae44 100644 --- a/src/renderer/core/music-sheet/internal/sheets-method.ts +++ b/src/renderer/core/music-sheet/internal/sheets-method.ts @@ -96,7 +96,7 @@ export const useAllSheets = musicSheetsStore.useValue; /** 更新歌单信息 */ export async function updateSheet( sheetId: string, - newData: IMusic.IMusicSheetItem + newData: Partial ) { try { if (!newData) { @@ -115,7 +115,7 @@ export async function updateSheet( .getValue() .findIndex((_) => _.id === sheetId); if (currentIndex === -1) { - draft.push(newData); + draft.push(newData as IMusic.IDBMusicSheetItem); } else { draft[currentIndex] = { ...draft[currentIndex], diff --git a/src/renderer/core/plugin-delegate/internal/events.ts b/src/renderer/core/plugin-delegate/internal/events.ts index 3069f59a..7d891472 100644 --- a/src/renderer/core/plugin-delegate/internal/events.ts +++ b/src/renderer/core/plugin-delegate/internal/events.ts @@ -1,6 +1,7 @@ import { ipcRendererOn } from "@/common/ipc-util/renderer"; import delegatePluginsStore from "./store"; import { refreshPlugins } from "./methods"; +import rendererAppConfig from "@/common/app-config/renderer"; function onPluginLoaded(){ ipcRendererOn('plugin-loaded', (plugins) => { diff --git a/src/renderer/core/plugin-delegate/internal/methods.ts b/src/renderer/core/plugin-delegate/internal/methods.ts index 74baef70..e5495b1a 100644 --- a/src/renderer/core/plugin-delegate/internal/methods.ts +++ b/src/renderer/core/plugin-delegate/internal/methods.ts @@ -1,5 +1,7 @@ import { ipcRendererInvoke, ipcRendererSend } from "@/common/ipc-util/renderer"; import delegatePluginsStore from "./store"; +import rendererAppConfig from "@/common/app-config/renderer"; +import { useMemo } from "react"; /** 刷新插件 */ export function refreshPlugins() { @@ -14,6 +16,22 @@ export function getSupportedPlugin( .filter((_) => _.supportedMethod.includes(featureMethod)); } +export function getSortedSupportedPlugin( + featureMethod: keyof IPlugin.IPluginInstanceMethods +) { + const meta = rendererAppConfig.getAppConfigPath("private.pluginMeta"); + return delegatePluginsStore + .getValue() + .filter((_) => _.supportedMethod.includes(featureMethod)) + .sort((a, b) => { + return (meta[a.platform]?.order ?? Infinity) - + (meta[b?.platform]?.order ?? Infinity) < + 0 + ? -1 + : 1; + }); +} + export function useSupportedPlugin( featureMethod: keyof IPlugin.IPluginInstanceMethods ) { @@ -22,6 +40,22 @@ export function useSupportedPlugin( .filter((_) => _.supportedMethod.includes(featureMethod)); } +export function useSortedSupportedPlugin( + featureMethod: keyof IPlugin.IPluginInstanceMethods +) { + const meta = rendererAppConfig.getAppConfigPath("private.pluginMeta"); + return delegatePluginsStore + .useValue() + .filter((_) => _.supportedMethod.includes(featureMethod)) + .sort((a, b) => { + return (meta[a.platform]?.order ?? Infinity) - + (meta[b?.platform]?.order ?? Infinity) < + 0 + ? -1 + : 1; + }); +} + export function getSearchablePlugins( supportedSearchType?: IMedia.SupportMediaType ) { @@ -57,5 +91,30 @@ export async function callPluginDelegateMethod< } export function getPluginPrimaryKey(pluginItem: IPluginDelegateLike) { - return delegatePluginsStore.getValue().find(it => it.platform === pluginItem.platform)?.primaryKey ?? []; -} \ No newline at end of file + return ( + delegatePluginsStore + .getValue() + .find((it) => it.platform === pluginItem.platform)?.primaryKey ?? [] + ); +} + +export function useSortedPlugins() { + const plugins = delegatePluginsStore.useValue(); + const configs = rendererAppConfig.useAppConfig(); + + const meta = useMemo(() => { + return configs?.private?.pluginMeta ?? {}; + }, [configs]); + + const sortedPlugins = useMemo(() => { + return [...plugins].sort((a, b) => { + return (meta[a.platform]?.order ?? Infinity) - + (meta[b?.platform]?.order ?? Infinity) < + 0 + ? -1 + : 1; + }); + }, [plugins, meta]); + + return sortedPlugins; +} diff --git a/src/renderer/pages/main-page/views/plugin-manager-view/components/plugin-table/index.scss b/src/renderer/pages/main-page/views/plugin-manager-view/components/plugin-table/index.scss index 8725a38f..6962be7d 100644 --- a/src/renderer/pages/main-page/views/plugin-manager-view/components/plugin-table/index.scss +++ b/src/renderer/pages/main-page/views/plugin-manager-view/components/plugin-table/index.scss @@ -1,16 +1,19 @@ -.plugin-table-wrapper { +.plugin-table--container { width: calc(100% - 1rem); flex: 1; - & .action-button { cursor: pointer; margin-right: 0.8rem; - + &:hover { font-weight: 500; color: var(--primaryColor) !important; border-bottom: 1px solid currentColor; } } + + & tr { + position: relative; + } } diff --git a/src/renderer/pages/main-page/views/plugin-manager-view/components/plugin-table/index.tsx b/src/renderer/pages/main-page/views/plugin-manager-view/components/plugin-table/index.tsx index f20ef285..ea8fb940 100644 --- a/src/renderer/pages/main-page/views/plugin-manager-view/components/plugin-table/index.tsx +++ b/src/renderer/pages/main-page/views/plugin-manager-view/components/plugin-table/index.tsx @@ -1,6 +1,6 @@ import { callPluginDelegateMethod, - pluginsStore, + useSortedPlugins, } from "@/renderer/core/plugin-delegate"; import { @@ -11,17 +11,19 @@ import { } from "@tanstack/react-table"; import "./index.scss"; import { CSSProperties, ReactNode } from "react"; -import Condition from "@/renderer/components/Condition"; +import Condition, { IfTruthy } from "@/renderer/components/Condition"; import { hideModal, showModal } from "@/renderer/components/Modal"; import Empty from "@/renderer/components/Empty"; import { ipcRendererInvoke } from "@/common/ipc-util/renderer"; import { toast } from "react-toastify"; import { showPanel } from "@/renderer/components/Panel"; import rendererAppConfig from "@/common/app-config/renderer"; +import DragReceiver, { startDrag } from "@/renderer/components/DragReceiver"; +import { produce } from "immer"; function renderOptions(info: any) { const row = info.row.original as IPlugin.IPluginDelegate; - + return (
{ - showPanel('UserVariables', { + showPanel("UserVariables", { variables: row.userVariables, plugin: row, - initValues: rendererAppConfig.getAppConfigPath('private.pluginMeta')?.[row.platform]?.userVariables + initValues: + rendererAppConfig.getAppConfigPath("private.pluginMeta")?.[ + row.platform + ]?.userVariables, }); }} > @@ -186,15 +191,39 @@ const columnDef = [ ]; export default function PluginTable() { - const plugins = pluginsStore.useValue(); + const plugins = useSortedPlugins(); const table = useReactTable({ data: plugins, columns: columnDef, getCoreRowModel: getCoreRowModel(), }); + function onDrop(fromIndex: number, toIndex: number) { + const meta = rendererAppConfig.getAppConfigPath("private.pluginMeta") ?? {}; + + const newPlugins = plugins + .slice(0, fromIndex) + .concat(plugins.slice(fromIndex + 1)); + newPlugins.splice( + fromIndex < toIndex ? toIndex - 1 : toIndex, + 0, + plugins[fromIndex] + ); + + const newMeta = produce(meta, (draft) => { + newPlugins.forEach((plugin, index) => { + if (!draft[plugin.platform]) { + draft[plugin.platform] = {}; + } + draft[plugin.platform].order = index; + }); + }); + + rendererAppConfig.setAppConfigPath("private.pluginMeta", newMeta); + } + return ( -
+
} @@ -206,7 +235,7 @@ export default function PluginTable() { {flexRender( @@ -218,8 +247,14 @@ export default function PluginTable() { - {table.getRowModel().rows.map((row) => ( - + {table.getRowModel().rows.map((row, index) => ( + { + startDrag(e, index); + }} + > {row.getAllCells().map((cell) => ( ))} + + + + ))} diff --git a/src/renderer/pages/main-page/views/recommend-sheets-view/index.tsx b/src/renderer/pages/main-page/views/recommend-sheets-view/index.tsx index 44db18e9..41c3dd44 100644 --- a/src/renderer/pages/main-page/views/recommend-sheets-view/index.tsx +++ b/src/renderer/pages/main-page/views/recommend-sheets-view/index.tsx @@ -1,12 +1,12 @@ import Condition from "@/renderer/components/Condition"; import NoPlugin from "@/renderer/components/NoPlugin"; import { Tab } from "@headlessui/react"; -import { getSupportedPlugin } from "@/renderer/core/plugin-delegate"; +import { getSortedSupportedPlugin } from "@/renderer/core/plugin-delegate"; import { useNavigate } from "react-router-dom"; import Body from "./components/Body"; export default function RecommendSheetsView() { - const availablePlugins = getSupportedPlugin("getRecommendSheetsByTag"); + const availablePlugins = getSortedSupportedPlugin("getRecommendSheetsByTag"); const navigate = useNavigate(); return ( diff --git a/src/renderer/pages/main-page/views/search-view/index.tsx b/src/renderer/pages/main-page/views/search-view/index.tsx index 2f603b3e..da42ec5b 100644 --- a/src/renderer/pages/main-page/views/search-view/index.tsx +++ b/src/renderer/pages/main-page/views/search-view/index.tsx @@ -1,7 +1,6 @@ import { getSearchablePlugins, - getSupportedPlugin, - useSupportedPlugin, + useSortedSupportedPlugin, } from "@/renderer/core/plugin-delegate"; import { useEffect } from "react"; import { useMatch, useNavigate } from "react-router-dom"; @@ -18,7 +17,7 @@ export default function SearchView() { const match = useMatch("/main/search/:query"); const query = match?.params?.query; - const plugins = useSupportedPlugin("search"); + const plugins = useSortedSupportedPlugin("search"); const { t } = useTranslation(); const search = useSearch(); diff --git a/src/renderer/pages/main-page/views/toplist-view/index.tsx b/src/renderer/pages/main-page/views/toplist-view/index.tsx index daaaacd9..a53c1ac3 100644 --- a/src/renderer/pages/main-page/views/toplist-view/index.tsx +++ b/src/renderer/pages/main-page/views/toplist-view/index.tsx @@ -2,7 +2,7 @@ import Condition from "@/renderer/components/Condition"; import "./index.scss"; import MusicSheetlikeItem from "@/renderer/components/MusicSheetlikeItem"; import { Tab } from "@headlessui/react"; -import { getSupportedPlugin } from "@/renderer/core/plugin-delegate"; +import { getSortedSupportedPlugin } from "@/renderer/core/plugin-delegate"; import { pluginsTopListStore } from "./store"; import { RequestStateCode } from "@/common/constant"; import Loading from "@/renderer/components/Loading"; @@ -13,7 +13,7 @@ import NoPlugin from "@/renderer/components/NoPlugin"; import Empty from "@/renderer/components/Empty"; export default function ToplistView() { - const availablePlugins = getSupportedPlugin("getTopLists"); + const availablePlugins = getSortedSupportedPlugin("getTopLists"); const navigate = useNavigate(); return ( diff --git a/src/types/plugin.d.ts b/src/types/plugin.d.ts index 4f8ed688..c198423b 100644 --- a/src/types/plugin.d.ts +++ b/src/types/plugin.d.ts @@ -139,8 +139,8 @@ declare namespace IPlugin { /** 插件其他属性 */ export type IPluginMeta = { - order: number; - userVariables: Record; + order?: number; + userVariables?: Record; }; export type IPluginDelegate = { From 4ef0bf534c469b8f187dfada3e0500d5ac4ce537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Sat, 2 Dec 2023 14:35:40 +0800 Subject: [PATCH 15/50] =?UTF-8?q?feat:=20=E8=AE=BE=E7=BD=AE=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E6=A0=8F=E6=AD=8C=E8=AF=8D=E5=BC=80=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/app-config/type.d.ts | 2 ++ src/main/ipc/index.ts | 12 +++++++----- src/renderer/core/message-manager/index.ts | 3 ++- .../views/setting-view/routers/Lyric/index.tsx | 8 ++++++++ 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/common/app-config/type.d.ts b/src/common/app-config/type.d.ts index d2d7d8c5..b1c01f67 100644 --- a/src/common/app-config/type.d.ts +++ b/src/common/app-config/type.d.ts @@ -21,6 +21,8 @@ interface IConfig { audioOutputDevice: MediaDeviceInfo | null; }; lyric: { + /** [darwin only] 显示状态栏歌词 */ + enableStatusBarLyric: boolean; /** 显示桌面歌词 */ enableDesktopLyric: boolean; /** 桌面歌词置顶 */ diff --git a/src/main/ipc/index.ts b/src/main/ipc/index.ts index 1daf586d..25b764a7 100644 --- a/src/main/ipc/index.ts +++ b/src/main/ipc/index.ts @@ -35,7 +35,6 @@ import { import setThumbImg from "../utils/set-thumb-img"; import setThumbbarBtns from "../utils/set-thumbbar-btns"; - export default function setupIpcMain() { ipcMainOn("min-window", ({ skipTaskBar }) => { const mainWindow = getMainWindow(); @@ -126,9 +125,13 @@ export default function setupIpcMain() { setupTrayMenu(); }); - ipcMainOn('sync-current-lyric', (lrc) => { - setTrayTitle(lrc); - }) + ipcMainOn("sync-current-lyric", (lrc) => { + if (getAppConfigPathSync("lyric.enableStatusBarLyric")) { + setTrayTitle(lrc); + } else { + setTrayTitle(''); + } + }); ipcMainHandle("app-get-path", (pathName) => { return app.getPath(pathName as any); @@ -190,7 +193,6 @@ export default function setupIpcMain() { forward: true, }); }); - } export async function setLyricWindow(enabled: boolean) { diff --git a/src/renderer/core/message-manager/index.ts b/src/renderer/core/message-manager/index.ts index 843db864..eff13c5b 100644 --- a/src/renderer/core/message-manager/index.ts +++ b/src/renderer/core/message-manager/index.ts @@ -6,6 +6,7 @@ import { TrackPlayerEvent, } from "../track-player/enum"; import trackPlayerEventsEmitter from "../track-player/event"; +import rendererAppConfig from "@/common/app-config/renderer"; function getCurrentMusicData() { const currentMusic = trackPlayer.getCurrentMusic(); @@ -137,7 +138,7 @@ async function setupMessageManager() { }); trackPlayerEventsEmitter.on(TrackPlayerEvent.CurrentLyricChanged, (lyric) => { - if (window.globalData.platform === 'darwin') { + if (window.globalData.platform === 'darwin' && rendererAppConfig.getAppConfigPath('lyric.enableStatusBarLyric')) { // 只有macos需要同步歌词,用来设置状态栏歌词 ipcRendererSend('sync-current-lyric', lyric?.lrc?.lrc ?? ''); } diff --git a/src/renderer/pages/main-page/views/setting-view/routers/Lyric/index.tsx b/src/renderer/pages/main-page/views/setting-view/routers/Lyric/index.tsx index e048d865..cbefe5b0 100644 --- a/src/renderer/pages/main-page/views/setting-view/routers/Lyric/index.tsx +++ b/src/renderer/pages/main-page/views/setting-view/routers/Lyric/index.tsx @@ -5,6 +5,7 @@ import ColorPickerSettingItem from "../../components/ColorPickerSettingItem"; import { ipcRendererInvoke, ipcRendererSend } from "@/common/ipc-util/renderer"; import ListBoxSettingItem from "../../components/ListBoxSettingItem"; import FontPickerSettingItem from "../../components/FontPickerSettingItem"; +import { IfTruthy } from "@/renderer/components/Condition"; interface IProps { data: IAppConfig["lyric"]; @@ -19,6 +20,13 @@ export default function Lyric(props: IProps) { return (
+ + + Date: Sat, 2 Dec 2023 14:59:06 +0800 Subject: [PATCH 16/50] =?UTF-8?q?feat:=20=E8=87=AA=E5=8A=A8=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/app-config/type.d.ts | 4 ++ .../ipc-util/eventType/renderer-events.d.ts | 43 ++++++++++--------- src/main/core/plugin-manager/index.ts | 19 +++++++- src/renderer/document/bootstrap.ts | 14 +++++- .../setting-view/routers/Plugin/index.scss | 4 ++ .../setting-view/routers/Plugin/index.tsx | 26 +++++++++++ .../views/setting-view/routers/index.ts | 6 +++ 7 files changed, 91 insertions(+), 25 deletions(-) create mode 100644 src/renderer/pages/main-page/views/setting-view/routers/Plugin/index.scss create mode 100644 src/renderer/pages/main-page/views/setting-view/routers/Plugin/index.tsx diff --git a/src/common/app-config/type.d.ts b/src/common/app-config/type.d.ts index b1c01f67..c0dfcb0f 100644 --- a/src/common/app-config/type.d.ts +++ b/src/common/app-config/type.d.ts @@ -65,6 +65,10 @@ interface IConfig { /** 最多同时下载 */ concurrency: number }; + plugin: { + autoUpdatePlugin: boolean; // 是否自动升级插件 + notCheckPluginVersion: boolean; // 是否不检测插件版本 + } backup: { test: never; diff --git a/src/common/ipc-util/eventType/renderer-events.d.ts b/src/common/ipc-util/eventType/renderer-events.d.ts index 80eda1f1..a64aaa9d 100644 --- a/src/common/ipc-util/eventType/renderer-events.d.ts +++ b/src/common/ipc-util/eventType/renderer-events.d.ts @@ -12,39 +12,40 @@ declare namespace IpcEvents { /** 刷新插件 */ "refresh-plugins": undefined; + /** 更新所有插件 */ + "update-all-plugins": undefined; "open-url": string; - 'open-path': string; + "open-path": string; "sync-current-music": IMusic.IMusicItem; "sync-current-playing-state": import("@/renderer/core/track-player/enum").PlayerState; "sync-current-repeat-mode": import("@/renderer/core/track-player/enum").RepeatMode; - 'sync-current-lyric': string; + "sync-current-lyric": string; - - 'send-to-lyric-window': { + "send-to-lyric-window": { // 时序 timeStamp: number; - lrc: ILyric.IParsedLrcItem[] + lrc: ILyric.IParsedLrcItem[]; }; - 'set-desktop-lyric-lock': boolean; - 'ignore-mouse-event': { - ignore: boolean, - window: 'main' | 'lyric' + "set-desktop-lyric-lock": boolean; + "ignore-mouse-event": { + ignore: boolean; + window: "main" | "lyric"; }; /** 设置歌词窗口位置 */ - 'set-lyric-window-pos': ICommon.IPoint; + "set-lyric-window-pos": ICommon.IPoint; /** 快捷键 */ - 'enable-global-short-cut': boolean; - 'bind-global-short-cut': { - key: keyof import('../../app-config/type').IAppConfig["shortCut"]["shortcuts"], - shortCut: string[] - } - 'unbind-global-short-cut': { - key: keyof import('../../app-config/type').IAppConfig["shortCut"]["shortcuts"], - shortCut: string[] - } + "enable-global-short-cut": boolean; + "bind-global-short-cut": { + key: keyof import("../../app-config/type").IAppConfig["shortCut"]["shortcuts"]; + shortCut: string[]; + }; + "unbind-global-short-cut": { + key: keyof import("../../app-config/type").IAppConfig["shortCut"]["shortcuts"]; + shortCut: string[]; + }; } } @@ -86,8 +87,8 @@ declare namespace IpcInvoke { ) => Electron.SaveDialogReturnValue; "check-update": () => ICommon.IUpdateInfo; - "set-lyric-window": (show: boolean) => void; + "set-lyric-window": (show: boolean) => void; /** 主窗口和歌词窗口之间 */ - 'app-get-path': (pathName: string) => string; + "app-get-path": (pathName: string) => string; } } diff --git a/src/main/core/plugin-manager/index.ts b/src/main/core/plugin-manager/index.ts index f7b0d05e..824744d6 100644 --- a/src/main/core/plugin-manager/index.ts +++ b/src/main/core/plugin-manager/index.ts @@ -14,13 +14,14 @@ import axios from "axios"; import { compare } from "compare-versions"; import { nanoid } from "nanoid"; import { addRandomHash } from "@/common/normalize-util"; +import { getAppConfigPathSync } from "@/common/app-config/main"; let plugins: Plugin[] = []; let clonedPlugins: IPlugin.IPluginDelegate[] = []; let _pluginBasePath: string; function getPluginBasePath() { - if(_pluginBasePath) { + if (_pluginBasePath) { return _pluginBasePath; } _pluginBasePath = path.resolve( @@ -91,6 +92,8 @@ function registerEvents() { /** 刷新插件 */ ipcMainOn("refresh-plugins", loadAllPlugins); + /** 更新所有插件 */ + ipcMainOn("update-all-plugins", updateAllPlugins); ipcMainHandle("install-plugin-remote", async (urlLike: string) => { try { @@ -217,7 +220,10 @@ async function installPluginFromRawCode(funcCode: string) { return; } const oldVersionPlugin = plugins.find((p) => p.name === plugin.name); - if (oldVersionPlugin) { + if ( + oldVersionPlugin && + !getAppConfigPathSync("plugin.notCheckPluginVersion") + ) { if ( compare( oldVersionPlugin.instance.version ?? "", @@ -258,3 +264,12 @@ async function uninstallPlugin(hash: string) { } catch {} } } + +/** 更新所有插件 */ +async function updateAllPlugins() { + return Promise.allSettled( + plugins.map((plg) => + plg.instance.srcUrl ? installPluginFromUrl(plg.instance.srcUrl) : null + ) + ); +} diff --git a/src/renderer/document/bootstrap.ts b/src/renderer/document/bootstrap.ts index 8b4025c0..1e410dee 100644 --- a/src/renderer/document/bootstrap.ts +++ b/src/renderer/document/bootstrap.ts @@ -10,7 +10,7 @@ import localMusic from "../core/local-music"; import { setupLocalShortCut } from "../core/shortcut"; import { setAutoFreeze } from "immer"; import Evt from "../core/events"; -import { ipcRendererInvoke } from "@/common/ipc-util/renderer"; +import { ipcRendererInvoke, ipcRendererSend } from "@/common/ipc-util/renderer"; import * as Comlink from "comlink"; import Downloader from "../core/downloader"; @@ -33,6 +33,16 @@ export default async function () { clearDefaultBehavior(); setupEvents(); await Downloader.setupDownloader(); + + // 自动更新插件 + if (rendererAppConfig.getAppConfigPath("plugin.autoUpdatePlugin")) { + const lastUpdated = +(localStorage.getItem("pluginLastupdatedTime") || 0); + const now = Date.now(); + if (Math.abs(now - lastUpdated) > 86400000) { + localStorage.setItem("pluginLastupdatedTime", `${now}`); + ipcRendererSend("update-all-plugins"); + } + } } function dropHandler() { @@ -120,7 +130,7 @@ function setupEvents() { !enableDesktopLyric ); }); - + Evt.on("TOGGLE_LIKE", async (item) => { // 如果没有传入,就是当前播放的歌曲 const realItem = item || trackPlayer.getCurrentMusic(); diff --git a/src/renderer/pages/main-page/views/setting-view/routers/Plugin/index.scss b/src/renderer/pages/main-page/views/setting-view/routers/Plugin/index.scss new file mode 100644 index 00000000..95392bd7 --- /dev/null +++ b/src/renderer/pages/main-page/views/setting-view/routers/Plugin/index.scss @@ -0,0 +1,4 @@ +.setting-view--plugin-container { + width: 100%; + +} diff --git a/src/renderer/pages/main-page/views/setting-view/routers/Plugin/index.tsx b/src/renderer/pages/main-page/views/setting-view/routers/Plugin/index.tsx new file mode 100644 index 00000000..9ef93acf --- /dev/null +++ b/src/renderer/pages/main-page/views/setting-view/routers/Plugin/index.tsx @@ -0,0 +1,26 @@ +import { IAppConfig } from "@/common/app-config/type"; +import "./index.scss"; +import CheckBoxSettingItem from "../../components/CheckBoxSettingItem"; + +interface IProps { + data: IAppConfig["plugin"]; +} + +export default function Plugin(props: IProps) { + const { data = {} as IAppConfig["plugin"] } = props; + + return ( +
+ + +
+ ); +} diff --git a/src/renderer/pages/main-page/views/setting-view/routers/index.ts b/src/renderer/pages/main-page/views/setting-view/routers/index.ts index 85990fe5..038f1bad 100644 --- a/src/renderer/pages/main-page/views/setting-view/routers/index.ts +++ b/src/renderer/pages/main-page/views/setting-view/routers/index.ts @@ -6,6 +6,7 @@ import Download from "./Download"; import Lyric from "./Lyric"; import Normal from "./Normal"; import PlayMusic from "./PlayMusic"; +import Plugin from "./Plugin"; import ShortCut from "./ShortCut"; import Theme from "./Theme"; @@ -25,6 +26,11 @@ export default [ title: "歌词", component: Lyric, }, + { + id: "plugin", + title: "插件", + component: Plugin, + }, { id: "theme", title: "主题", From fe4398a086b4020de2bd02ce3365f1e697311909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Sat, 2 Dec 2023 22:03:37 +0800 Subject: [PATCH 17/50] =?UTF-8?q?fix:=20=E5=BF=AB=E6=8D=B7=E9=94=AE?= =?UTF-8?q?=E6=97=A0=E6=B3=95=E5=88=A0=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/core/shortcut/index.ts | 12 +- .../setting-view/routers/ShortCut/index.tsx | 219 ++++++++++-------- .../views/setting-view/routers/index.ts | 12 +- 3 files changed, 139 insertions(+), 104 deletions(-) diff --git a/src/renderer/core/shortcut/index.ts b/src/renderer/core/shortcut/index.ts index 4a77b6df..d2a2672c 100644 --- a/src/renderer/core/shortcut/index.ts +++ b/src/renderer/core/shortcut/index.ts @@ -56,13 +56,16 @@ export function bindShortCut( // return; // } unbindShortCut(key, global); + if (!shortCut?.length) { + return; + } if (global) { ipcRendererSend("bind-global-short-cut", { key: key, shortCut: shortCut, }); } else { - hotkeys(shortCut.join("+"), localShortCutKeyFuncs[mapKey]); + hotkeys(shortCut.join("+"), "all", localShortCutKeyFuncs[mapKey]); } boundKeyMap.set(mapKey, shortCut); @@ -73,6 +76,7 @@ export function unbindShortCut(eventType: IShortCutKeys, global = false) { const mapKey = `${eventType}${global ? "-g" : ""}`; const originalHotKey = boundKeyMap.get(mapKey); + if (originalHotKey) { if (global) { ipcRendererSend("unbind-global-short-cut", { @@ -80,7 +84,11 @@ export function unbindShortCut(eventType: IShortCutKeys, global = false) { shortCut: originalHotKey, }); } else { - hotkeys.unbind(originalHotKey.join("+"), localShortCutKeyFuncs[mapKey]); + hotkeys.unbind( + originalHotKey.join("+"), + "all", + localShortCutKeyFuncs[mapKey] + ); } boundKeyMap.delete(mapKey); } diff --git a/src/renderer/pages/main-page/views/setting-view/routers/ShortCut/index.tsx b/src/renderer/pages/main-page/views/setting-view/routers/ShortCut/index.tsx index 163e5a86..02a9c0be 100644 --- a/src/renderer/pages/main-page/views/setting-view/routers/ShortCut/index.tsx +++ b/src/renderer/pages/main-page/views/setting-view/routers/ShortCut/index.tsx @@ -7,63 +7,13 @@ import hotkeys from "hotkeys-js"; import rendererAppConfig from "@/common/app-config/renderer"; import { bindShortCut } from "@/renderer/core/shortcut"; import { ipcRendererSend } from "@/common/ipc-util/renderer"; -import raf2 from "@/renderer/utils/raf2"; + interface IProps { data: IAppConfig["shortCut"]; } -let recordShortCutKey: string[] = []; -let isAllModifierKey = false; - export default function ShortCut(props: IProps) { const { data = {} as IAppConfig["shortCut"] } = props; - useEffect(() => { - hotkeys( - "*", - { - capture: true, - }, - (evt, detail) => { - const target = evt.target as HTMLElement; - if ( - target.tagName === "INPUT" && - target.dataset["capture"] === "true" - ) { - const pressedKeys = hotkeys.getPressedKeyString(); - const _recordShortCutKey = []; - isAllModifierKey = false; - if (hotkeys.ctrl) { - _recordShortCutKey.push("Ctrl"); - isAllModifierKey = true; - } - if (hotkeys.shift) { - _recordShortCutKey.push("Shift"); - isAllModifierKey = true; - } - if (hotkeys.alt) { - _recordShortCutKey.push("Alt"); - isAllModifierKey = true; - } - // 跟一个普通键 - for (let i = pressedKeys.length - 1; i >= 0; --i) { - if (!hotkeys.modifier[pressedKeys[i]]) { - _recordShortCutKey.push( - pressedKeys[i].replace(/^(.)/, (_, $1: string) => - $1.toUpperCase() - ) - ); - isAllModifierKey = false; - break; - } - } - recordShortCutKey = _recordShortCutKey; - } - } - ); - return () => { - hotkeys.unbind("*"); - }; - }, []); return (
@@ -140,7 +90,6 @@ function ShortCutTable(props: IShortCutTableProps) { enabled={enableGlobal} value={shortCuts[it]?.global} onChange={(val) => { - console.log(it, val); bindShortCut(it as IShortCutKeys, val, true); }} > @@ -153,6 +102,7 @@ function ShortCutTable(props: IShortCutTableProps) { interface IShortCutItemProps { enabled?: boolean; + isGlobal?: boolean; value?: string[]; onChange?: (sc?: string[]) => void; } @@ -161,65 +111,142 @@ function formatValue(val: string[]) { return val.join(" + "); } +function keyCodeMap(code: string) { + switch (code) { + case "arrowup": + return "Up"; + case "arrowdown": + return "Down"; + case "arrowleft": + return "Left"; + case "arrowright": + return "Right"; + default: + return code; + } +} + function ShortCutItem(props: IShortCutItemProps) { - const { value, onChange, enabled } = props; - const [realValue, setRealValue] = useState(formatValue(value ?? [])); + const { value, onChange, enabled, isGlobal } = props; + const [tmpValue, setTmpValue] = useState(); + const realValue = formatValue(tmpValue ?? value ?? []); const isRecordingRef = useRef(false); - const inputRef = useRef(); - // todo 写的很奇怪 - const resultRef = useRef([]); - const isAllModifierKeyRef = useRef(false); - - const keyupHandler = () => - raf2(() => { - const isAllModifierKey = isAllModifierKeyRef.current; - const recordShortCutKey = resultRef.current; - if (isRecordingRef.current) { - if (isAllModifierKey || !recordShortCutKey.length) { - setRealValue(formatValue(value ?? [])); - } else if ( - recordShortCutKey.includes("Backspace") - ) { - setRealValue(""); - onChange?.([]); - } else { - setRealValue(formatValue(recordShortCutKey)); - onChange?.(recordShortCutKey); + const scopeRef = useRef(Math.random().toString().slice(2)); + const recordedKeysRef = useRef(new Set()); + + useEffect(() => { + hotkeys( + "*", + { + scope: scopeRef.current, + keyup: true, + }, + (evt) => { + console.log(evt); + const type = evt.type; + let key = evt.key.toLowerCase(); + if (evt.code === "Space") { + key = "Space"; } - } - isAllModifierKeyRef.current = false; - resultRef.current = []; + if (type === "keydown") { + isRecordingRef.current = true; + if (key === "backspace") { + // 删除 + setTmpValue(null); + isRecordingRef.current = false; + recordedKeysRef.current.clear(); + // 新的快捷键为空 + onChange?.([]); + } else if (key === "meta") { + setTmpValue(null); + isRecordingRef.current = false; + recordedKeysRef.current.clear(); + } else { + if (!recordedKeysRef.current.has(key)) { + recordedKeysRef.current.add(key); + setTmpValue( + [...recordedKeysRef.current].map((it) => + it.replace(/^(.)/, (_, $1: string) => $1.toUpperCase()) + ) + ); + } + } + } else if (type === "keyup" && isRecordingRef.current) { + isRecordingRef.current = false; + // 开始结算 + const recordedSet = recordedKeysRef.current; + const _recordShortCutKey = []; - isRecordingRef.current = false; - }); + let statusCode = 0; + if (recordedSet.has("ctrl") || recordedSet.has("control")) { + _recordShortCutKey.push("Ctrl"); + recordedSet.delete("ctrl"); + recordedSet.delete("control"); + statusCode |= 1; + } + if (recordedSet.has("command")) { + _recordShortCutKey.push("Command"); + recordedSet.delete("command"); + statusCode |= 1; + } + if (recordedSet.has("option")) { + _recordShortCutKey.push("Option"); + recordedSet.delete("option"); + statusCode |= 1; + } + if (recordedSet.has("shift")) { + _recordShortCutKey.push("Shift"); + recordedSet.delete("shift"); + statusCode |= 1; + } + + if (recordedSet.has("alt")) { + _recordShortCutKey.push("Alt"); + recordedSet.delete("alt"); + statusCode |= 1; + } + + if (recordedSet.size === 1 && (isGlobal ? statusCode : true)) { + _recordShortCutKey.push( + keyCodeMap([...recordedSet.values()][0]).replace( + /^(.)/, + (_, $1: string) => $1.toUpperCase() + ) + ); + setTmpValue(_recordShortCutKey); + onChange?.(_recordShortCutKey); + } else { + setTmpValue(null); + } + + recordedKeysRef.current.clear(); + } + } + ); + }, []); return ( { e.preventDefault(); - raf2(() => { - resultRef.current = recordShortCutKey.filter( - (it) => it !== "Backspace" - ); - isAllModifierKeyRef.current = isAllModifierKey; - setRealValue( - `${resultRef.current.join(" + ")}${isAllModifierKey ? " +" : ""}` - ); - if (isRecordingRef.current) { - return; - } else { - isRecordingRef.current = true; - } - }); }} - onKeyUp={keyupHandler} - onBlur={keyupHandler} + onFocus={() => { + hotkeys.setScope(scopeRef.current); + }} + onBlur={() => { + hotkeys.setScope("all"); + setTmpValue(null); + recordedKeysRef.current.clear(); + }} > ); } diff --git a/src/renderer/pages/main-page/views/setting-view/routers/index.ts b/src/renderer/pages/main-page/views/setting-view/routers/index.ts index 038f1bad..fc82e523 100644 --- a/src/renderer/pages/main-page/views/setting-view/routers/index.ts +++ b/src/renderer/pages/main-page/views/setting-view/routers/index.ts @@ -11,7 +11,7 @@ import ShortCut from "./ShortCut"; import Theme from "./Theme"; export default [ - { + { id: "normal", title: "常规", component: Normal, @@ -21,6 +21,11 @@ export default [ title: "播放", component: PlayMusic, }, + { + id: "download", + title: "下载", + component: Download, + }, { id: "lyric", title: "歌词", @@ -36,11 +41,6 @@ export default [ title: "主题", component: Theme, }, - { - id: "download", - title: "下载", - component: Download, - }, { id: "shortCut", title: "快捷键", From f59924ea9729704ef9db8dc1c8f72e6a593c9c26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Sun, 3 Dec 2023 12:24:05 +0800 Subject: [PATCH 18/50] =?UTF-8?q?fix:=20=E5=88=A0=E9=99=A4=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E6=97=B6=E4=B8=8D=E9=98=BB=E5=A1=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/components/MusicList/index.tsx | 12 +++--- .../core/downloader/downloaded-sheet.ts | 37 ++++++++++++++----- src/types/common.d.ts | 13 +++++-- 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/src/renderer/components/MusicList/index.tsx b/src/renderer/components/MusicList/index.tsx index fef90aa9..42283ebd 100644 --- a/src/renderer/components/MusicList/index.tsx +++ b/src/renderer/components/MusicList/index.tsx @@ -233,8 +233,11 @@ export function showMusicContextMenu( (isArray && musicItems.every((it) => Downloader.isDownloaded(it))) || (!isArray && Downloader.isDownloaded(musicItems)), async onClick() { - try { - await Downloader.removeDownloadedMusic(musicItems, true); + const [isSuccess, info] = await Downloader.removeDownloadedMusic( + musicItems, + true + ); + if (isSuccess) { if (isArray) { toast.success(`已删除 ${musicItems.length} 首本地歌曲`); } else { @@ -242,8 +245,8 @@ export function showMusicContextMenu( `已删除本地歌曲 [${(musicItems as IMusic.IMusicItem).title}]` ); } - } catch (e) { - toast.error(`删除失败: ${e?.message ?? ""}`); + } else if (info?.msg) { + toast.error(info.msg); } }, }, @@ -536,7 +539,6 @@ function _MusicList(props: IMusicListProps) { // } startDrag(e, virtualItem.rowIndex, "musiclist"); - }} > {row.getVisibleCells().map((cell) => ( diff --git a/src/renderer/core/downloader/downloaded-sheet.ts b/src/renderer/core/downloader/downloaded-sheet.ts index 832aed17..12f0c170 100644 --- a/src/renderer/core/downloader/downloaded-sheet.ts +++ b/src/renderer/core/downloader/downloaded-sheet.ts @@ -99,9 +99,11 @@ export async function addDownloadedMusicToList( export async function removeDownloadedMusic( musicItems: IMusic.IMusicItem | IMusic.IMusicItem[], removeFile = false -) { +): Promise { const _musicItems = Array.isArray(musicItems) ? musicItems : [musicItems]; + let message: string | null = null; + try { // 1. 获取全部详细信息 const toBeRemovedMusicDetail = await musicSheetDB.transaction( @@ -117,12 +119,18 @@ export async function removeDownloadedMusic( let removeResults: boolean[] = []; if (removeFile) { removeResults = await Promise.all( - toBeRemovedMusicDetail.map((it) => - window.rimraf( - getInternalData(it, "downloadData") - ?.path - ) - ) + toBeRemovedMusicDetail.map((it) => { + try { + return window.rimraf( + getInternalData(it, "downloadData") + ?.path + ); + } catch (e) { + // 删除失败 + message = "部分歌曲删除失败 " + (e?.message ?? ""); + return false; + } + }) ); } // 3. 修改数据库 @@ -173,8 +181,17 @@ export async function removeDownloadedMusic( ); }); } catch (e) { - console.log(e); - throw e; + message = "删除失败 " + e?.message ?? ""; + } + if (message) { + return [ + false, + { + msg: message, + }, + ]; + } else { + return [true]; } } @@ -212,7 +229,7 @@ export function useDownloaded(musicItem: IMedia.IMediaBase) { } }; - if(musicItem) { + if (musicItem) { setDownloaded(isDownloaded(musicItem)); } diff --git a/src/types/common.d.ts b/src/types/common.d.ts index 4b1df430..cdc79fa7 100644 --- a/src/types/common.d.ts +++ b/src/types/common.d.ts @@ -14,12 +14,12 @@ declare namespace ICommon { version: string; changeLog: string[]; download: string[]; - } + }; } interface IPoint { x: number; - y: number + y: number; } interface IThemePack { @@ -44,5 +44,12 @@ declare namespace ICommon { /** 总大小 */ totalSize?: number; } - + + type ICommonReturnType = [ + boolean, + { + msg?: string; + [k: string]: any; + }? + ]; } From 87afa5366eab2157dd852d527391740f0561b5a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Sun, 3 Dec 2023 14:29:48 +0800 Subject: [PATCH 19/50] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E8=B6=85=E9=95=BF=E6=AD=8C=E5=8D=95=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/constant.ts | 1 + .../music-sheet/internal/sheets-method.ts | 76 ++++++++++++------- .../SideBar/widgets/ListItem/index.scss | 9 +-- .../music-sheet-view/local-sheet/index.tsx | 4 +- 4 files changed, 54 insertions(+), 36 deletions(-) diff --git a/src/common/constant.ts b/src/common/constant.ts index 95cbab17..2a43c68f 100644 --- a/src/common/constant.ts +++ b/src/common/constant.ts @@ -27,6 +27,7 @@ export enum RequestStateCode { /** 空闲 */ IDLE = 0b00000000, PENDING_FIRST_PAGE = 0b00000010, + LOADING = 0b00000010, /** 检索中 */ PENDING_REST_PAGE = 0b00000011, /** 部分结束 */ diff --git a/src/renderer/core/music-sheet/internal/sheets-method.ts b/src/renderer/core/music-sheet/internal/sheets-method.ts index ee91ae44..a43b1652 100644 --- a/src/renderer/core/music-sheet/internal/sheets-method.ts +++ b/src/renderer/core/music-sheet/internal/sheets-method.ts @@ -10,7 +10,7 @@ import { musicSheetsStore, starredSheetsStore } from "./store"; import { produce } from "immer"; import defaultSheet from "./default-sheet"; import { getMediaPrimaryKey, isSameMedia } from "@/common/media-util"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { getUserPerferenceIDB, setUserPerferenceIDB, @@ -241,28 +241,39 @@ export async function addMusicToSheet( export async function getSheetDetail( sheetId: string ): Promise { - return await musicSheetDB.transaction( - "readonly", - musicSheetDB.musicStore, - async () => { - const targetSheet = musicSheetsStore - .getValue() - .find((item) => item.id === sheetId); - if (!targetSheet) { - return null; + // 取太多歌曲时会卡顿, 1000首歌大约100ms + const targetSheet = musicSheetsStore + .getValue() + .find((item) => item.id === sheetId); + if (!targetSheet) { + return null; + } + const tmpResult = []; + const musicList = targetSheet.musicList ?? []; + // 一组800个 + const groupSize = 800; + const groupNum = Math.ceil(musicList.length / groupSize); + + for (let i = 0; i < groupNum; ++i) { + const sliceResult = await musicSheetDB.transaction( + "readonly", + musicSheetDB.musicStore, + async () => { + return await musicSheetDB.musicStore.bulkGet( + musicList + .slice(i * groupSize, (i + 1) * groupSize) + .map((item) => [item.platform, item.id]) + ); } - const musicList = targetSheet.musicList ?? []; - const musicDetailList = await musicSheetDB.musicStore.bulkGet( - musicList.map((item) => [item.platform, item.id]) - ); - const _targetSheet = { ...targetSheet }; - _targetSheet.musicList = musicList.map((mi, index) => ({ - ...mi, - ...musicDetailList[index], - })); - return _targetSheet as IMusic.IMusicSheetItem; - } - ); + ); + + tmpResult.push(...(sliceResult ?? [])); + } + + return { + ...targetSheet, + musicList: tmpResult, + } as IMusic.IMusicSheetItem; } /** 获取所有歌单信息 */ @@ -431,10 +442,19 @@ export function useMusicSheet(sheetId: string) { const [musicSheet, setMusicSheet] = useState( null ); + const [loading, setLoading] = useState(true); + + const realTimeSheetIdRef = useRef(sheetId); + realTimeSheetIdRef.current = sheetId; useEffect(() => { - const updateSheet = () => { - getSheetDetail(sheetId).then(setMusicSheet); + const updateSheet = async () => { + setLoading(true); + const sheetDetails = await getSheetDetail(sheetId); + setLoading(false); + if (realTimeSheetIdRef.current === sheetId) { + setMusicSheet(sheetDetails); + } }; const cbs = updateSheetCbs.get(sheetId) ?? new Set(); cbs.add(updateSheet); @@ -451,16 +471,14 @@ export function useMusicSheet(sheetId: string) { }); } - // TODO 长列表会有短暂卡顿 - setTimeout(() => { - updateSheet(); - }, 0); + updateSheet(); + return () => { cbs?.delete(updateSheet); }; }, [sheetId]); - return musicSheet; + return [musicSheet, loading] as const; } export async function starMusicSheet(sheet: IMedia.IMediaBase) { diff --git a/src/renderer/pages/main-page/components/SideBar/widgets/ListItem/index.scss b/src/renderer/pages/main-page/components/SideBar/widgets/ListItem/index.scss index 9cec787d..761e8793 100644 --- a/src/renderer/pages/main-page/components/SideBar/widgets/ListItem/index.scss +++ b/src/renderer/pages/main-page/components/SideBar/widgets/ListItem/index.scss @@ -32,6 +32,9 @@ } &[data-selected="true"] { + color: var(--primaryColor); + background: var(--listActiveColor); + &::before { content: ""; position: absolute; @@ -41,11 +44,5 @@ height: $height; background-color: var(--primaryColor); } - - & { - color: var(--primaryColor); - background: var(--listActiveColor); - - } } } diff --git a/src/renderer/pages/main-page/views/music-sheet-view/local-sheet/index.tsx b/src/renderer/pages/main-page/views/music-sheet-view/local-sheet/index.tsx index a36a9de1..03a2ba34 100644 --- a/src/renderer/pages/main-page/views/music-sheet-view/local-sheet/index.tsx +++ b/src/renderer/pages/main-page/views/music-sheet-view/local-sheet/index.tsx @@ -1,14 +1,16 @@ import { useParams } from "react-router-dom"; import { useMusicSheet } from "@/renderer/core/music-sheet/internal/sheets-method"; import MusicSheetlikeView from "@/renderer/components/MusicSheetlikeView"; +import { RequestStateCode } from "@/common/constant"; export default function LocalSheet() { const { id } = useParams() ?? {}; - const musicSheet = useMusicSheet(id); + const [musicSheet, loading] = useMusicSheet(id); return ( ); From 39cf0dfef4762e0c29b995af89727ad57bfdb874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Sun, 3 Dec 2023 14:49:16 +0800 Subject: [PATCH 20/50] =?UTF-8?q?feat:=20=E7=8E=AF=E5=A2=83=E5=8F=98?= =?UTF-8?q?=E9=87=8F=EF=BC=9B=E6=9B=B4=E6=96=B0=E5=A4=B1=E8=B4=A5=E4=BF=A1?= =?UTF-8?q?=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/core/plugin-manager/plugin.ts | 2 ++ .../plugin-manager-view/components/plugin-table/index.tsx | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/core/plugin-manager/plugin.ts b/src/main/core/plugin-manager/plugin.ts index d74ea4d2..cc04bf28 100644 --- a/src/main/core/plugin-manager/plugin.ts +++ b/src/main/core/plugin-manager/plugin.ts @@ -9,6 +9,7 @@ import he from 'he'; import PluginMethods from './plugin-methods'; import reactNativeCookies from './polyfill/react-native-cookies'; import { getAppConfigPathSync } from '@/common/app-config/main'; +import { app } from 'electron'; axios.defaults.timeout = 15000; @@ -87,6 +88,7 @@ export class Plugin { ); }, os: process.platform, + appVersion: app.getVersion() }; // eslint-disable-next-line no-new-func _instance = Function(` diff --git a/src/renderer/pages/main-page/views/plugin-manager-view/components/plugin-table/index.tsx b/src/renderer/pages/main-page/views/plugin-manager-view/components/plugin-table/index.tsx index ea8fb940..908ed392 100644 --- a/src/renderer/pages/main-page/views/plugin-manager-view/components/plugin-table/index.tsx +++ b/src/renderer/pages/main-page/views/plugin-manager-view/components/plugin-table/index.tsx @@ -57,7 +57,9 @@ function renderOptions(info: any) { try { await ipcRendererInvoke("install-plugin-remote", row.srcUrl); toast.success(`插件「${row.platform}」已更新至最新版本`); - } catch {} + } catch (e) { + toast.error(e?.message ?? "更新失败"); + } }} > 更新 From 03254106f2867d0074c8d1e31bc4cf3c56ff6418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Fri, 8 Dec 2023 09:25:18 +0800 Subject: [PATCH 21/50] =?UTF-8?q?feat:=20=E7=BD=91=E7=BB=9C=E4=BB=A3?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 215 +++++++++++++++++- package.json | 2 + src/common/app-config/type.d.ts | 24 +- .../ipc-util/eventType/renderer-events.d.ts | 7 + src/main/index.ts | 20 +- src/main/ipc/index.ts | 53 +++-- src/renderer/core/track-player/player.ts | 32 +-- src/renderer/document/index.tsx | 1 - .../components/InputSettingItem/index.scss | 19 ++ .../components/InputSettingItem/index.tsx | 71 ++++++ .../setting-view/routers/Network/index.scss | 17 ++ .../setting-view/routers/Network/index.tsx | 87 +++++++ .../views/setting-view/routers/index.ts | 7 +- 13 files changed, 495 insertions(+), 60 deletions(-) create mode 100644 src/renderer/pages/main-page/views/setting-view/components/InputSettingItem/index.scss create mode 100644 src/renderer/pages/main-page/views/setting-view/components/InputSettingItem/index.tsx create mode 100644 src/renderer/pages/main-page/views/setting-view/routers/Network/index.scss create mode 100644 src/renderer/pages/main-page/views/setting-view/routers/Network/index.tsx diff --git a/package-lock.json b/package-lock.json index 816e6fb4..b8a5896d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "he": "^1.2.0", "hls.js": "^1.4.12", "hotkeys-js": "^3.12.0", + "https-proxy-agent": "^7.0.2", "i18next": "^22.5.1", "immer": "^10.0.2", "lodash.shuffle": "^4.2.0", @@ -46,6 +47,7 @@ "react-toastify": "^9.1.3", "rimraf": "^5.0.1", "sharp": "^0.32.6", + "socket.io": "^4.7.2", "unzipper": "^0.10.14", "watcher": "^2.3.0" }, @@ -3338,6 +3340,11 @@ "node": ">=10" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, "node_modules/@svgr/babel-plugin-add-jsx-attribute": { "version": "8.0.0", "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", @@ -3725,6 +3732,19 @@ "@types/node": "*" } }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/crypto-js": { "version": "4.1.1", "resolved": "https://registry.npmmirror.com/@types/crypto-js/-/crypto-js-4.1.1.tgz", @@ -3911,8 +3931,7 @@ "node_modules/@types/node": { "version": "20.5.9", "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.5.9.tgz", - "integrity": "sha512-PcGNd//40kHAS3sTlzKB9C9XL4K0sTup8nbG5lC14kzEteTNuAFh9u5nA0o5TWnSG2r/JNPRXFVcHJIIeRlmqQ==", - "dev": true + "integrity": "sha512-PcGNd//40kHAS3sTlzKB9C9XL4K0sTup8nbG5lC14kzEteTNuAFh9u5nA0o5TWnSG2r/JNPRXFVcHJIIeRlmqQ==" }, "node_modules/@types/normalize-package-data": { "version": "2.4.1", @@ -4505,7 +4524,6 @@ "version": "1.3.8", "resolved": "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz", "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dev": true, "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" @@ -5060,6 +5078,14 @@ "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmmirror.com/batch/-/batch-0.6.1.tgz", @@ -5965,6 +5991,18 @@ "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cosmiconfig": { "version": "8.3.3", "resolved": "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-8.3.3.tgz", @@ -7168,6 +7206,62 @@ "once": "^1.4.0" } }, + "node_modules/engine.io": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", + "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", + "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/enhanced-resolve": { "version": "5.15.0", "resolved": "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", @@ -9451,16 +9545,26 @@ } }, "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", "dependencies": { - "agent-base": "6", + "agent-base": "^7.0.2", "debug": "4" }, "engines": { - "node": ">= 6" + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent/node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" } }, "node_modules/human-signals": { @@ -10967,6 +11071,19 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/make-fetch-happen/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/make-fetch-happen/node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-7.18.3.tgz", @@ -11648,7 +11765,6 @@ "version": "0.6.3", "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -12058,6 +12174,19 @@ "node": ">=8" } }, + "node_modules/node-sass/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/node-sass/node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-7.18.3.tgz", @@ -12558,6 +12687,14 @@ "boolbase": "^1.0.0" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.12.3.tgz", @@ -14884,6 +15021,63 @@ "tslib": "^2.0.3" } }, + "node_modules/socket.io": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.2.tgz", + "integrity": "sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", + "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "dependencies": { + "ws": "~8.11.0" + } + }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmmirror.com/sockjs/-/sockjs-0.3.24.tgz", @@ -16310,7 +16504,6 @@ "version": "1.1.2", "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true, "engines": { "node": ">= 0.8" } diff --git a/package.json b/package.json index 87a057c6..35f80cf4 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "he": "^1.2.0", "hls.js": "^1.4.12", "hotkeys-js": "^3.12.0", + "https-proxy-agent": "^7.0.2", "i18next": "^22.5.1", "immer": "^10.0.2", "lodash.shuffle": "^4.2.0", @@ -105,6 +106,7 @@ "react-toastify": "^9.1.3", "rimraf": "^5.0.1", "sharp": "^0.32.6", + "socket.io": "^4.7.2", "unzipper": "^0.10.14", "watcher": "^2.3.0" } diff --git a/src/common/app-config/type.d.ts b/src/common/app-config/type.d.ts index c0dfcb0f..ecff6611 100644 --- a/src/common/app-config/type.d.ts +++ b/src/common/app-config/type.d.ts @@ -3,8 +3,8 @@ interface IConfig { closeBehavior: "exit" | "minimize"; maxHistoryLength: number; checkUpdate: boolean; - taskbarThumb: 'window' | 'artwork', - musicListColumnsShown: Array<'duration' | 'platform'> + taskbarThumb: "window" | "artwork"; + musicListColumnsShown: Array<"duration" | "platform">; }; playMusic: { /** 歌单内搜索区分大小写 */ @@ -63,12 +63,26 @@ interface IConfig { /** 默认下载音质缺失时 */ whenQualityMissing: "higher" | "lower"; /** 最多同时下载 */ - concurrency: number + concurrency: number; }; plugin: { autoUpdatePlugin: boolean; // 是否自动升级插件 notCheckPluginVersion: boolean; // 是否不检测插件版本 - } + }; + network: { + proxy: { + /** 是否启用代理 */ + enabled: boolean; + /** 主机 */ + host?: string; + /** 端口 */ + port?: string; + /** 账号 */ + username?: string; + /** 密码 */ + password?: string; + }; + }; backup: { test: never; @@ -88,7 +102,7 @@ interface IConfig { x: number; y: number; }; - pluginMeta: Record + pluginMeta: Record; }; } diff --git a/src/common/ipc-util/eventType/renderer-events.d.ts b/src/common/ipc-util/eventType/renderer-events.d.ts index a64aaa9d..3c84fdf5 100644 --- a/src/common/ipc-util/eventType/renderer-events.d.ts +++ b/src/common/ipc-util/eventType/renderer-events.d.ts @@ -46,6 +46,13 @@ declare namespace IpcEvents { key: keyof import("../../app-config/type").IAppConfig["shortCut"]["shortcuts"]; shortCut: string[]; }; + "set-proxy": { + enabled: boolean; + host?: string; + port?: string; + username?: string; + password?: string; + } } } diff --git a/src/main/index.ts b/src/main/index.ts index 6f092cda..b7869bc9 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -6,7 +6,7 @@ import { getMainWindow, showMainWindow, } from "./window"; -import setupIpcMain from "./ipc"; +import setupIpcMain, { handleProxy } from "./ipc"; import { setupPluginManager } from "./core/plugin-manager"; import { getAppConfigPath, @@ -24,26 +24,22 @@ import path from "path"; // } // portable -if(process.platform === 'win32') { +if (process.platform === "win32") { try { - const appPath = app.getPath('exe'); + const appPath = app.getPath("exe"); const portablePath = path.resolve(appPath, "../portable"); const portableFolderStat = fs.statSync(portablePath); if (portableFolderStat.isDirectory()) { - const appPathNames = [ - "appData", - "userData", - ]; + const appPathNames = ["appData", "userData"]; appPathNames.forEach((it) => { app.setPath(it, path.resolve(portablePath, it)); }); } - } catch (e){ + } catch (e) { // console.log(e) } } - // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. @@ -106,4 +102,10 @@ async function bootstrap() { } } }); + + getAppConfigPath("network.proxy").then((result) => { + if (result) { + handleProxy(result); + } + }); } diff --git a/src/main/ipc/index.ts b/src/main/ipc/index.ts index 25b764a7..ba1def0f 100644 --- a/src/main/ipc/index.ts +++ b/src/main/ipc/index.ts @@ -1,39 +1,25 @@ -import { - ipcMainHandle, - ipcMainOn, - ipcMainSendMainWindow, -} from "@/common/ipc-util/main"; +import { ipcMainHandle, ipcMainOn } from "@/common/ipc-util/main"; import { closeLyricWindow, createLyricWindow, getLyricWindow, getMainWindow, } from "../window"; -import { - BrowserWindow, - MessageChannelMain, - app, - dialog, - ipcRenderer, - net, - shell, -} from "electron"; +import { app, dialog, shell } from "electron"; import { currentMusicInfoStore } from "../store/current-music"; import { PlayerState } from "@/renderer/core/track-player/enum"; import { setTrayTitle, setupTrayMenu } from "../tray"; import axios from "axios"; import { compare } from "compare-versions"; -import { getPluginByMedia } from "../core/plugin-manager"; -import { encodeUrlHeaders } from "@/common/normalize-util"; -import { getQualityOrder } from "@/common/media-util"; import { - getAppConfigPath, getAppConfigPathSync, setAppConfigPath, } from "@/common/app-config/main"; // import { getExtensionWindow, syncExtensionData } from "../core/extensions"; import setThumbImg from "../utils/set-thumb-img"; import setThumbbarBtns from "../utils/set-thumbbar-btns"; +import { HttpsProxyAgent } from "https-proxy-agent"; +import { IAppConfig } from "@/common/app-config/type"; export default function setupIpcMain() { ipcMainOn("min-window", ({ skipTaskBar }) => { @@ -129,7 +115,7 @@ export default function setupIpcMain() { if (getAppConfigPathSync("lyric.enableStatusBarLyric")) { setTrayTitle(lrc); } else { - setTrayTitle(''); + setTrayTitle(""); } }); @@ -193,6 +179,35 @@ export default function setupIpcMain() { forward: true, }); }); + + /** 设置代理 */ + ipcMainOn("set-proxy", async (data) => { + handleProxy(data); + setAppConfigPath("network.proxy", data); + }); +} + +export async function handleProxy(data: IAppConfig["network"]["proxy"]) { + try { + if (!data.enabled) { + axios.defaults.httpAgent = undefined; + axios.defaults.httpsAgent = undefined; + } else if (data.host) { + const proxyUrl = new URL(data.host); + proxyUrl.port = data.port; + proxyUrl.username = data.username; + proxyUrl.password = data.password; + const agent = new HttpsProxyAgent(proxyUrl); + + axios.defaults.httpAgent = agent; + axios.defaults.httpsAgent = agent; + } else { + throw new Error("Unknown Host"); + } + } catch (e) { + axios.defaults.httpAgent = undefined; + axios.defaults.httpsAgent = undefined; + } } export async function setLyricWindow(enabled: boolean) { diff --git a/src/renderer/core/track-player/player.ts b/src/renderer/core/track-player/player.ts index e8badf73..2a8750a1 100644 --- a/src/renderer/core/track-player/player.ts +++ b/src/renderer/core/track-player/player.ts @@ -102,20 +102,24 @@ export async function setupPlayer() { } trackPlayerEventsEmitter.emit(TrackPlayerEvent.NeedRefreshLyric); - try { - const { mediaSource, quality } = await getMediaSource(currentMusic, { - quality: - getUserPerference("currentQuality") || - rendererAppConfig.getAppConfigPath("playMusic.defaultQuality"), - }); - - setTrackAndPlay(mediaSource, currentMusic, { - seekTo: currentProgress, - autoPlay: false, - }); - - setCurrentQuality(quality); - } catch {} + // 不能阻塞加载 + getMediaSource(currentMusic, { + quality: + getUserPerference("currentQuality") || + rendererAppConfig.getAppConfigPath("playMusic.defaultQuality"), + }) + .then(({ mediaSource, quality }) => { + if (isSameMedia(currentMusic, currentMusicStore.getValue())) { + setTrackAndPlay(mediaSource, currentMusic, { + seekTo: currentProgress, + autoPlay: false, + }); + + setCurrentQuality(quality); + } + }) + .catch(() => {}); + currentIndex = findMusicIndex(currentMusic); } diff --git a/src/renderer/document/index.tsx b/src/renderer/document/index.tsx index 41f38bd8..f1c2fd6a 100644 --- a/src/renderer/document/index.tsx +++ b/src/renderer/document/index.tsx @@ -16,7 +16,6 @@ import "./index.css"; // 全局样式 import "./index.scss"; import { toastDuration } from "@/common/constant"; import useBootstrap from "./useBootstrap"; -import PanelComponent from "../components/Panel"; bootstrap().then(() => { ReactDOM.createRoot(document.getElementById("root")).render(); diff --git a/src/renderer/pages/main-page/views/setting-view/components/InputSettingItem/index.scss b/src/renderer/pages/main-page/views/setting-view/components/InputSettingItem/index.scss new file mode 100644 index 00000000..0f1f17cd --- /dev/null +++ b/src/renderer/pages/main-page/views/setting-view/components/InputSettingItem/index.scss @@ -0,0 +1,19 @@ +.setting-view--input-setting-item-container { + display: flex; + align-items: center; + width: 200px; + column-gap: 12px; + + & .input-label { + width: 48px; + white-space: nowrap; + } + + & input { + flex: 1; + } + + & input:disabled { + opacity: 0.5; + } +} diff --git a/src/renderer/pages/main-page/views/setting-view/components/InputSettingItem/index.tsx b/src/renderer/pages/main-page/views/setting-view/components/InputSettingItem/index.tsx new file mode 100644 index 00000000..51e547e4 --- /dev/null +++ b/src/renderer/pages/main-page/views/setting-view/components/InputSettingItem/index.tsx @@ -0,0 +1,71 @@ +import rendererAppConfig from "@/common/app-config/renderer"; +import { + IAppConfigKeyPath, + IAppConfigKeyPathValue, +} from "@/common/app-config/type"; +import { Listbox } from "@headlessui/react"; +import "./index.scss"; +import defaultAppConfig from "@/common/app-config/default-app-config"; +import Condition from "@/renderer/components/Condition"; +import Loading from "@/renderer/components/Loading"; +import { isBasicType } from "@/common/normalize-util"; +import useVirtualList from "@/renderer/hooks/useVirtualList"; +import { rem } from "@/common/constant"; +import { Fragment, useEffect, useRef, useState } from "react"; + +interface InputSettingItemProps { + keyPath: T; + label?: string; + value?: IAppConfigKeyPathValue; + onChange?: (val: IAppConfigKeyPathValue) => void; + width?: number | string; + /** 是否过滤首尾空格 */ + trim?: boolean; + disabled?: boolean; +} + +export default function InputSettingItem( + props: InputSettingItemProps +) { + const { + keyPath, + label, + value = defaultAppConfig[keyPath], + onChange, + width, + disabled + } = props; + + const [tmpValue, setTmpValue] = useState(null); + + return ( +
+ {label ?
{label}
: null} + { + setTmpValue(e.target.value ?? null); + }} + onBlur={() => { + if (tmpValue === null) { + return; + } + if (onChange) { + onChange(tmpValue as any); + } else { + rendererAppConfig.setAppConfigPath(keyPath, tmpValue.trim() as any); + } + setTmpValue(null); + }} + value={(tmpValue || value || "") as string} + > +
+ ); +} + diff --git a/src/renderer/pages/main-page/views/setting-view/routers/Network/index.scss b/src/renderer/pages/main-page/views/setting-view/routers/Network/index.scss new file mode 100644 index 00000000..bb55304c --- /dev/null +++ b/src/renderer/pages/main-page/views/setting-view/routers/Network/index.scss @@ -0,0 +1,17 @@ +.setting-view--network-container { + width: 100%; + + & .proxy-container { + display: grid; + grid-template-columns: 33% 33%; + gap: 18px; + + & .proxy-item { + display: flex; + + & input { + flex: 1; + } + } + } +} diff --git a/src/renderer/pages/main-page/views/setting-view/routers/Network/index.tsx b/src/renderer/pages/main-page/views/setting-view/routers/Network/index.tsx new file mode 100644 index 00000000..afc5f530 --- /dev/null +++ b/src/renderer/pages/main-page/views/setting-view/routers/Network/index.tsx @@ -0,0 +1,87 @@ +import { IAppConfig } from "@/common/app-config/type"; +import "./index.scss"; +import CheckBoxSettingItem from "../../components/CheckBoxSettingItem"; +import InputSettingItem from "../../components/InputSettingItem"; +import { ipcRendererSend } from "@/common/ipc-util/renderer"; + +interface IProps { + data: IAppConfig["network"]; +} + +export default function Network(props: IProps) { + const { data = {} as IAppConfig["network"] } = props; + + const proxyEnabled = !!data.proxy?.enabled; + + return ( +
+ { + ipcRendererSend("set-proxy", { + ...(data.proxy ?? {}), + enabled: checked, + }); + }} + > + +
+ { + console.log(val); + ipcRendererSend("set-proxy", { + ...(data.proxy ?? { enabled: false }), + host: val.trim(), + }); + }} + > + { + ipcRendererSend("set-proxy", { + ...(data.proxy ?? { enabled: false }), + port: val.trim(), + }); + }} + > + { + ipcRendererSend("set-proxy", { + ...(data.proxy ?? { enabled: false }), + username: val.trim(), + }); + }} + > + { + ipcRendererSend("set-proxy", { + ...(data.proxy ?? { enabled: false }), + password: val.trim(), + }); + }} + > +
+
+ ); +} diff --git a/src/renderer/pages/main-page/views/setting-view/routers/index.ts b/src/renderer/pages/main-page/views/setting-view/routers/index.ts index fc82e523..f289c80a 100644 --- a/src/renderer/pages/main-page/views/setting-view/routers/index.ts +++ b/src/renderer/pages/main-page/views/setting-view/routers/index.ts @@ -1,9 +1,9 @@ /** 配置 */ - import About from "./About"; import Backup from "./Backup"; import Download from "./Download"; import Lyric from "./Lyric"; +import Network from "./Network"; import Normal from "./Normal"; import PlayMusic from "./PlayMusic"; import Plugin from "./Plugin"; @@ -46,6 +46,11 @@ export default [ title: "快捷键", component: ShortCut, }, + { + id: "network", + title: "网络", + component: Network, + }, { id: "backup", title: "备份与恢复", From fd21a9dacf7a021cd22401c6c2ac2cc3cf3f2b06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Fri, 8 Dec 2023 09:37:10 +0800 Subject: [PATCH 22/50] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E7=AA=97?= =?UTF-8?q?=E5=8F=A3=E6=98=BE=E7=A4=BA#59?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/tray/index.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/tray/index.ts b/src/main/tray/index.ts index 5ba6fbd6..941885ec 100644 --- a/src/main/tray/index.ts +++ b/src/main/tray/index.ts @@ -98,11 +98,12 @@ export async function setupTrayMenu() { tray.setToolTip("MusicFree"); } if (currentMusic) { + const fullName = `${currentMusic.title ?? "未知音乐"}${ + currentMusic.artist ? ` - ${currentMusic.artist}` : "" + }`; ctxMenu.push( { - label: `${currentMusic.title ?? "未知音乐"}${ - currentMusic.artist ? ` - ${currentMusic.artist}` : "" - }`, + label: fullName.length > 12 ? fullName.slice(0, 12) + "..." : fullName, click: openMusicDetail, }, { @@ -260,14 +261,13 @@ export async function setupTrayMenu() { tray.setContextMenu(Menu.buildFromTemplate(ctxMenu)); } - export function setTrayTitle(str: string) { - if(!str || !str.length) { + if (!str || !str.length) { tray.setTitle(""); } if (str.length > 7) { - tray?.setTitle(" " + str.slice(0, ) + '...') + tray?.setTitle(" " + str.slice(0) + "..."); } else { tray?.setTitle(" " + str); } -} \ No newline at end of file +} From 878924f4d42c40698825e68df87e8dc0c2b796e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Thu, 14 Dec 2023 20:21:46 +0800 Subject: [PATCH 23/50] =?UTF-8?q?fix:=20albumDetail=20=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E9=A1=B5=E7=A0=81=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/media-util.ts | 3 ++- src/main/core/plugin-manager/plugin-methods.ts | 5 +++-- src/main/index.ts | 2 ++ .../main-page/views/album-view/hooks/useAlbumDetail.ts | 10 ++++++---- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/common/media-util.ts b/src/common/media-util.ts index 80155d4b..9262b204 100644 --- a/src/common/media-util.ts +++ b/src/common/media-util.ts @@ -1,4 +1,4 @@ -import { produce } from "immer"; +import { produce, setAutoFreeze } from "immer"; import { internalDataKey, localPluginName, @@ -6,6 +6,7 @@ import { sortIndexSymbol, timeStampSymbol, } from "./constant"; +setAutoFreeze(false); export function isSameMedia( a?: IMedia.IMediaBase | null, diff --git a/src/main/core/plugin-manager/plugin-methods.ts b/src/main/core/plugin-manager/plugin-methods.ts index 929e4832..a38a3466 100644 --- a/src/main/core/plugin-manager/plugin-methods.ts +++ b/src/main/core/plugin-manager/plugin-methods.ts @@ -78,6 +78,7 @@ export default class PluginMethods implements IPlugin.IPluginInstanceMethods { return result; } catch (e: any) { + console.log(e); if (retryCount > 0 && e?.message !== "NOT RETRY") { await delay(150); return this.plugin.methods.getMediaSource( @@ -213,7 +214,7 @@ export default class PluginMethods implements IPlugin.IPluginInstanceMethods { if (!this.plugin.instance.getAlbumInfo) { return { albumItem, - musicList: albumItem?.musicList ?? [], + musicList: (albumItem?.musicList ?? []).map(it => resetMediaItem(it, this.plugin.name)), isEnd: true, }; } @@ -256,7 +257,7 @@ export default class PluginMethods implements IPlugin.IPluginInstanceMethods { sheetItem: IMusic.IMusicSheetItem, page = 1 ): Promise { - if (!this.plugin.instance.getAlbumInfo) { + if (!this.plugin.instance.getMusicSheetInfo) { return { sheetItem, musicList: sheetItem?.musicList ?? [], diff --git a/src/main/index.ts b/src/main/index.ts index b7869bc9..2aa62b54 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -17,6 +17,7 @@ import { setupTray } from "./tray"; import { setupGlobalShortCut } from "./core/global-short-cut"; import fs from "fs"; import path from "path"; +import { setAutoFreeze } from "immer"; // Handle creating/removing shortcuts on Windows when installing/uninstalling. // if (require("electron-squirrel-startup")) { @@ -40,6 +41,7 @@ if (process.platform === "win32") { } } +setAutoFreeze(false); // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. diff --git a/src/renderer/pages/main-page/views/album-view/hooks/useAlbumDetail.ts b/src/renderer/pages/main-page/views/album-view/hooks/useAlbumDetail.ts index 518cac02..afe0187a 100644 --- a/src/renderer/pages/main-page/views/album-view/hooks/useAlbumDetail.ts +++ b/src/renderer/pages/main-page/views/album-view/hooks/useAlbumDetail.ts @@ -5,7 +5,7 @@ import { useCallback, useEffect, useRef, useState } from "react"; const idleCode = [ RequestStateCode.IDLE, RequestStateCode.FINISHED, - RequestStateCode.PARTLY_DONE + RequestStateCode.PARTLY_DONE, ]; export default function useAlbumDetail( @@ -24,7 +24,7 @@ export default function useAlbumDetail( const getAlbumDetail = useCallback( async function () { - if (originalAlbumItem === null || !(idleCode.includes(requestState))) { + if (originalAlbumItem === null || !idleCode.includes(requestState)) { return; } @@ -52,8 +52,9 @@ export default function useAlbumDetail( })); } if (result?.musicList) { + const currentPage = currentPageRef.current; setMusicList((prev) => { - if (currentPageRef.current === 1) { + if (currentPage === 1) { return result?.musicList ?? prev; } else { return [...prev, ...(result.musicList ?? [])]; @@ -66,7 +67,7 @@ export default function useAlbumDetail( : RequestStateCode.PARTLY_DONE ); currentPageRef.current += 1; - } catch(e) { + } catch (e) { setRequestState(RequestStateCode.IDLE); } }, @@ -76,6 +77,7 @@ export default function useAlbumDetail( useEffect(() => { getAlbumDetail(); }, []); + console.log(musicList); return [requestState, albumItem, musicList, getAlbumDetail] as const; } From 21b7a0be55285d7d243cacc5d2cd2f17360014af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Sat, 16 Dec 2023 15:36:56 +0800 Subject: [PATCH 24/50] =?UTF-8?q?feat:=20=E9=87=8D=E5=86=99=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E6=AD=8C=E5=8D=95=20&=20=E4=BF=AE=E5=A4=8D=E6=94=B6?= =?UTF-8?q?=E8=97=8F=E6=AD=8C=E5=8D=95=E9=83=A8=E5=88=86=E6=83=85=E5=86=B5?= =?UTF-8?q?=E4=B8=8B=E6=97=A0=E6=B3=95=E7=82=B9=E5=87=BB=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Modal/templates/AddMusicToSheet/index.tsx | 4 +- .../Modal/templates/AddNewSheet/index.tsx | 4 +- .../components/MusicFavorite/index.tsx | 9 +- src/renderer/components/MusicList/index.tsx | 2 +- .../components/Body/index.tsx | 11 +- .../sheets-method.ts => backend/index.ts} | 437 +++++++++--------- .../{internal => common}/default-sheet.ts | 0 .../core/music-sheet/frontend/index.ts | 247 ++++++++++ src/renderer/core/music-sheet/index.ts | 10 +- .../core/music-sheet/internal/store.ts | 5 - .../core/plugin-delegate/internal/methods.ts | 10 + src/renderer/document/bootstrap.ts | 9 +- .../SideBar/widgets/MySheets/index.tsx | 10 +- .../SideBar/widgets/StarredSheets/index.tsx | 19 +- .../music-sheet-view/local-sheet/index.tsx | 6 +- .../hooks/usePluginSheetMusicList.ts | 144 +++--- .../music-sheet-view/remote-sheet/index.tsx | 21 +- .../main-page/views/search-view/index.tsx | 3 +- .../setting-view/routers/Backup/index.tsx | 6 +- 19 files changed, 608 insertions(+), 349 deletions(-) rename src/renderer/core/music-sheet/{internal/sheets-method.ts => backend/index.ts} (62%) rename src/renderer/core/music-sheet/{internal => common}/default-sheet.ts (100%) create mode 100644 src/renderer/core/music-sheet/frontend/index.ts delete mode 100644 src/renderer/core/music-sheet/internal/store.ts diff --git a/src/renderer/components/Modal/templates/AddMusicToSheet/index.tsx b/src/renderer/components/Modal/templates/AddMusicToSheet/index.tsx index b593723d..5fd727bb 100644 --- a/src/renderer/components/Modal/templates/AddMusicToSheet/index.tsx +++ b/src/renderer/components/Modal/templates/AddMusicToSheet/index.tsx @@ -13,7 +13,7 @@ interface IAddMusicToSheetProps { export default function AddMusicToSheet(props: IAddMusicToSheetProps) { const { musicItems } = props; - const allSheets = MusicSheet.useAllSheets(); + const allSheets = MusicSheet.frontend.useAllSheets(); return (
@@ -44,7 +44,7 @@ export default function AddMusicToSheet(props: IAddMusicToSheetProps) { key={sheet.id} role="button" onClick={() => { - MusicSheet.addMusicToSheet(musicItems, sheet.id); + MusicSheet.frontend.addMusicToSheet(musicItems, sheet.id); hideModal(); }} > diff --git a/src/renderer/components/Modal/templates/AddNewSheet/index.tsx b/src/renderer/components/Modal/templates/AddNewSheet/index.tsx index 9a634898..2098ad55 100644 --- a/src/renderer/components/Modal/templates/AddNewSheet/index.tsx +++ b/src/renderer/components/Modal/templates/AddNewSheet/index.tsx @@ -12,9 +12,9 @@ export default function AddNewSheet(props: IProps) { const onCreateNewSheetClick = useCallback( debounce(async (newSheetName) => { try { - const id = await MusicSheet.addSheet(newSheetName); + const newSheet = await MusicSheet.frontend.addSheet(newSheetName); if (props?.initMusicItems) { - await MusicSheet.addMusicToSheet(props.initMusicItems, id); + await MusicSheet.frontend.addMusicToSheet(props.initMusicItems, newSheet.id); } hideModal(); } catch { diff --git a/src/renderer/components/MusicFavorite/index.tsx b/src/renderer/components/MusicFavorite/index.tsx index 1d13098d..7dc1d129 100644 --- a/src/renderer/components/MusicFavorite/index.tsx +++ b/src/renderer/components/MusicFavorite/index.tsx @@ -1,4 +1,3 @@ -import { useMusicIsFavorite } from "@/renderer/core/music-sheet/internal/sheets-method"; import SvgAsset from "../SvgAsset"; import MusicSheet from "@/renderer/core/music-sheet"; @@ -9,17 +8,17 @@ interface IMusicFavoriteProps { export default function MusicFavorite(props: IMusicFavoriteProps) { const { musicItem, size } = props; - const isFav = useMusicIsFavorite(musicItem); - + const isFav = MusicSheet.frontend.useMusicIsFavorite(musicItem); + return (
{ e.stopPropagation(); if (isFav) { - MusicSheet.removeMusicFromFavorite(musicItem); + MusicSheet.frontend.removeMusicFromFavorite(musicItem); } else { - MusicSheet.addMusicToFavorite(musicItem); + MusicSheet.frontend.addMusicToFavorite(musicItem); } }} onDoubleClick={(e) => { diff --git a/src/renderer/components/MusicList/index.tsx b/src/renderer/components/MusicList/index.tsx index 42283ebd..b8ee7f28 100644 --- a/src/renderer/components/MusicList/index.tsx +++ b/src/renderer/components/MusicList/index.tsx @@ -206,7 +206,7 @@ export function showMusicContextMenu( icon: "trash", show: !!localMusicSheetId, onClick() { - MusicSheet.removeMusicFromSheet(musicItems, localMusicSheetId); + MusicSheet.frontend.removeMusicFromSheet(musicItems, localMusicSheetId); }, } ); diff --git a/src/renderer/components/MusicSheetlikeView/components/Body/index.tsx b/src/renderer/components/MusicSheetlikeView/components/Body/index.tsx index 32607e0d..973e8e07 100644 --- a/src/renderer/components/MusicSheetlikeView/components/Body/index.tsx +++ b/src/renderer/components/MusicSheetlikeView/components/Body/index.tsx @@ -135,14 +135,13 @@ export default function Body(props: IProps) { }, offsetHeight: () => offsetHeightStore.getValue(), }} - // enableDrag={musicSheet?.platform === localPluginName} + enableDrag={musicSheet?.platform === localPluginName} onDragEnd={(newData) => { if (musicSheet?.platform === localPluginName) { - // @ts-ignore - MusicSheet.updateSheet(musicSheet.id, { - // @ts-ignore - musicList: newData.map(toMediaBase), - }); + // MusicSheet.updateSheetMusicList(musicSheet.id, { + // // @ts-ignore + // musicList: newData.map(toMediaBase), + // }); } }} > diff --git a/src/renderer/core/music-sheet/internal/sheets-method.ts b/src/renderer/core/music-sheet/backend/index.ts similarity index 62% rename from src/renderer/core/music-sheet/internal/sheets-method.ts rename to src/renderer/core/music-sheet/backend/index.ts index a43b1652..f53ac199 100644 --- a/src/renderer/core/music-sheet/internal/sheets-method.ts +++ b/src/renderer/core/music-sheet/backend/index.ts @@ -1,3 +1,9 @@ +/** + * 这里不应该写任何和UI有关的逻辑,只是简单的数据库操作 + * + * 除了frontend文件夹外,其他任何地方不应该直接调用此处定义的函数 + */ + import { localPluginName, musicRefSymbol, @@ -6,22 +12,45 @@ import { } from "@/common/constant"; import { nanoid } from "nanoid"; import musicSheetDB from "../../db/music-sheet-db"; -import { musicSheetsStore, starredSheetsStore } from "./store"; import { produce } from "immer"; -import defaultSheet from "./default-sheet"; +import defaultSheet from "../common/default-sheet"; import { getMediaPrimaryKey, isSameMedia } from "@/common/media-util"; -import { useEffect, useRef, useState } from "react"; import { getUserPerferenceIDB, setUserPerferenceIDB, } from "@/renderer/utils/user-perference"; +/******************** 内存缓存 ***********************/ // 默认歌单,快速判定是否在列表中 const favoriteMusicListIds = new Set(); +// 全部的歌单列表(无详情,只有ID) +let musicSheets: IMusic.IDBMusicSheetItem[] = []; +// 星标的歌单信息 +let starredMusicSheets: IMedia.IMediaBase[] = []; + +/******************** 方法 ***********************/ + +/** + * 获取全部音乐信息 + * @returns + */ +export function getAllSheets() { + return musicSheets; +} -/** 初始化 */ -export async function setupSheets() { +export function getAllStarredSheets() { + return starredMusicSheets; +} + +/** + * + * 查询所有歌单信息(无详情) + * + * @returns 全部歌单信息 + */ +export async function queryAllSheets() { try { + // 读取全部歌单 const allSheets = await musicSheetDB.sheets .orderBy("$$sortIndex") .toArray(); @@ -36,35 +65,48 @@ export async function setupSheets() { musicSheetDB.sheets.put(defaultSheet); } ); - musicSheetsStore.setValue([defaultSheet, ...allSheets]); + musicSheets = [defaultSheet, ...allSheets]; } else { dbDefaultSheet.musicList.forEach((mi) => { favoriteMusicListIds.add(getMediaPrimaryKey(mi)); }); - musicSheetsStore.setValue(allSheets); + musicSheets = allSheets; } // 收藏歌单 - const starredSheets = await getUserPerferenceIDB("starredMusicSheets"); - starredSheetsStore.setValue(starredSheets ?? []); + return musicSheets; } catch (e) { console.log(e); + return musicSheets; } } -function forceUpdateMusicSheets() { - musicSheetsStore.setValue((prev) => [...prev]); +/** + * 查询所有收藏歌单 + * @returns 收藏歌单信息 + */ +export async function queryAllStarredSheets() { + try { + starredMusicSheets = await getUserPerferenceIDB("starredMusicSheets"); + return starredMusicSheets; + } catch { + return []; + } } -/** 新建歌单 */ + +/** + * 新建歌单 + * @param sheetName 歌单名 + * @returns 新建的歌单信息 + */ export async function addSheet(sheetName: string) { const id = nanoid(); - const allSheets = musicSheetsStore.getValue(); const newSheet: IMusic.IMusicSheetItem = { id, title: sheetName, createAt: Date.now(), platform: localPluginName, musicList: [], - $$sortIndex: allSheets[allSheets.length - 1].$$sortIndex + 1, + $$sortIndex: musicSheets[musicSheets.length - 1].$$sortIndex + 1, }; try { await musicSheetDB.transaction( @@ -74,26 +116,19 @@ export async function addSheet(sheetName: string) { musicSheetDB.sheets.put(newSheet); } ); - musicSheetsStore.setValue((prev) => [...prev, newSheet]); + musicSheets = [...musicSheets, newSheet]; + return newSheet; } catch { throw new Error("新建失败"); } - return id; -} - -/** 获取所有歌单简略信息(不包含音乐列表详情) */ -export async function getAllSheets() { - try { - const allSheets = await musicSheetDB.sheets.toArray(); - musicSheetsStore.setValue(allSheets); - } catch (e) { - console.log(e); - } } -export const useAllSheets = musicSheetsStore.useValue; - -/** 更新歌单信息 */ +/** + * 更新歌单信息 + * @param sheetId 歌单ID + * @param newData 最新的歌单信息 + * @returns + */ export async function updateSheet( sheetId: string, newData: Partial @@ -109,30 +144,33 @@ export async function updateSheet( musicSheetDB.sheets.update(sheetId, newData); } ); - musicSheetsStore.setValue( - produce((draft) => { - const currentIndex = musicSheetsStore - .getValue() - .findIndex((_) => _.id === sheetId); - if (currentIndex === -1) { - draft.push(newData as IMusic.IDBMusicSheetItem); - } else { - draft[currentIndex] = { - ...draft[currentIndex], - ...newData, - }; - } - }) - ); + + musicSheets = produce(musicSheets, (draft) => { + const currentIndex = draft.findIndex((_) => _.id === sheetId); + if (currentIndex === -1) { + draft.push(newData as IMusic.IDBMusicSheetItem); + } else { + draft[currentIndex] = { + ...draft[currentIndex], + ...newData, + }; + } + }); } catch (e) { + // 更新歌单信息失败 console.log(e); } } -/** 移除歌单 */ +/** + * 移除歌单 + * @param sheetId 歌单ID + * @returns 删除后的ID + */ export async function removeSheet(sheetId: string) { try { if (sheetId === defaultSheet.id) { + // 默认歌单不可删除 return; } await musicSheetDB.transaction( @@ -140,10 +178,8 @@ export async function removeSheet(sheetId: string) { musicSheetDB.sheets, musicSheetDB.musicStore, async () => { - const targetSheet = musicSheetsStore - .getValue() - .find((item) => item.id === sheetId); - console.log(targetSheet); + const targetSheet = musicSheets.find((item) => item.id === sheetId); + await removeMusicFromSheet( targetSheet.musicList ?? ([] as any), sheetId @@ -151,17 +187,52 @@ export async function removeSheet(sheetId: string) { musicSheetDB.sheets.delete(sheetId); } ); - musicSheetsStore.setValue((prev) => - prev.filter((item) => item.id !== sheetId) - ); + musicSheets = musicSheets.filter((it) => it.id !== sheetId); + return musicSheets; } catch (e) { console.log(e); } } -/************* 歌曲 ************/ +/** + * 收藏歌单 + * @param sheet + */ +export async function starMusicSheet(sheet: IMedia.IMediaBase) { + const newSheets = [...starredMusicSheets, sheet]; + await setUserPerferenceIDB("starredMusicSheets", newSheets); + starredMusicSheets = newSheets; +} + +/** + * 取消收藏歌单 + * @param sheet + */ +export async function unstarMusicSheet(sheet: IMedia.IMediaBase) { + const newSheets = starredMusicSheets.filter( + (item) => !isSameMedia(item, sheet) + ); + await setUserPerferenceIDB("starredMusicSheets", newSheets); + starredMusicSheets = newSheets; +} + +/** + * 收藏歌单排序 + */ -/** 添加歌曲到歌单 */ +export async function setStarredMusicSheets(sheets: IMedia.IMediaBase[]) { + await setUserPerferenceIDB("starredMusicSheets", sheets); + starredMusicSheets = sheets; +} + +/**************************** 歌曲相关方法 ************************/ + +/** + * 添加歌曲到歌单 + * @param musicItems + * @param sheetId + * @returns + */ export async function addMusicToSheet( musicItems: IMusic.IMusicItem | IMusic.IMusicItem[], sheetId: string @@ -169,14 +240,13 @@ export async function addMusicToSheet( const _musicItems = Array.isArray(musicItems) ? musicItems : [musicItems]; try { // 当前的列表 - const targetSheet = musicSheetsStore - .getValue() - .find((item) => item.id === sheetId); + const targetSheet = musicSheets.find((item) => item.id === sheetId); if (!targetSheet) { return; } // 筛选出不在列表中的项目 const targetMusicList = targetSheet.musicList; + // 要添加到音乐列表中的项目 const validMusicItems = _musicItems.filter( (item) => -1 === targetMusicList.findIndex((mi) => isSameMedia(mi, item)) ); @@ -220,7 +290,7 @@ export async function addMusicToSheet( ]; targetSheet.artwork = obj.artwork; targetSheet.musicList = obj.musicList; - forceUpdateMusicSheets(); + musicSheets = [...musicSheets]; }); } ); @@ -229,100 +299,40 @@ export async function addMusicToSheet( _musicItems.forEach((mi) => { favoriteMusicListIds.add(getMediaPrimaryKey(mi)); }); - refreshFavoriteState(); } - refreshSheetState(sheetId); + + return musicSheets; } catch { console.log("error!!"); } } -/** 获取歌单内的歌曲详细信息 */ -export async function getSheetDetail( - sheetId: string -): Promise { - // 取太多歌曲时会卡顿, 1000首歌大约100ms - const targetSheet = musicSheetsStore - .getValue() - .find((item) => item.id === sheetId); - if (!targetSheet) { - return null; - } - const tmpResult = []; - const musicList = targetSheet.musicList ?? []; - // 一组800个 - const groupSize = 800; - const groupNum = Math.ceil(musicList.length / groupSize); - - for (let i = 0; i < groupNum; ++i) { - const sliceResult = await musicSheetDB.transaction( - "readonly", - musicSheetDB.musicStore, - async () => { - return await musicSheetDB.musicStore.bulkGet( - musicList - .slice(i * groupSize, (i + 1) * groupSize) - .map((item) => [item.platform, item.id]) - ); - } - ); - - tmpResult.push(...(sliceResult ?? [])); - } - - return { - ...targetSheet, - musicList: tmpResult, - } as IMusic.IMusicSheetItem; -} - -/** 获取所有歌单信息 */ -export async function getAllSheetDetails() { - return await musicSheetDB.transaction( - "readonly", - musicSheetDB.musicStore, - async () => { - const allSheets = musicSheetsStore.getValue(); - if (!allSheets) { - return []; - } - const musicLists = await Promise.all( - allSheets.map((sheet) => - musicSheetDB.musicStore.bulkGet( - (sheet.musicList ?? []).map((item) => [item.platform, item.id]) - ) - ) - ); - - allSheets.forEach((sheet, index) => { - sheet.musicList = musicLists[index]; - }); - - return allSheets; - } - ); -} - -/** 从歌单内移除歌曲 */ +/** + * 从歌单内移除歌曲 + * @param musicItems 要移除的歌曲 + * @param sheetId 歌单ID + * @returns + */ export async function removeMusicFromSheet( musicItems: IMusic.IMusicItem | IMusic.IMusicItem[], sheetId: string ) { - const _musicItems = Array.isArray(musicItems) ? musicItems : [musicItems]; - const targetSheet = musicSheetsStore - .getValue() - .find((item) => item.id === sheetId); + const targetSheet = musicSheets.find((item) => item.id === sheetId); if (!targetSheet) { return; } + // 重新组装 + const _musicItems = Array.isArray(musicItems) ? musicItems : [musicItems]; const targetMusicList = targetSheet.musicList ?? []; const toBeRemovedMusic: IMedia.IMediaBase[] = []; const restMusic: IMedia.IMediaBase[] = []; for (const mi of targetMusicList) { // 用map会更快吧 if (_musicItems.findIndex((item) => isSameMedia(mi, item)) === -1) { + // 剩余的音乐 restMusic.push(mi); } else { + // 将要删除的音乐 toBeRemovedMusic.push(mi); } } @@ -337,7 +347,9 @@ export async function removeMusicFromSheet( const toBeRemovedMusicDetail = await musicSheetDB.musicStore.bulkGet( toBeRemovedMusic.map((item) => [item.platform, item.id]) ); + // 如果引用计数为0,进入删除队列 const needDelete: any[] = []; + // 如果不为0,进入更新队列 const needUpdate: any[] = []; toBeRemovedMusicDetail.forEach((musicItem) => { if (!musicItem) { @@ -353,7 +365,9 @@ export async function removeMusicFromSheet( await musicSheetDB.musicStore.bulkDelete(needDelete); await musicSheetDB.musicStore.bulkPut(needUpdate); + // 当前的最后一首歌 const lastMusic = restMusic[restMusic.length - 1]; + // 更新当前歌单的封面 let newArtwork: string; if (lastMusic) { newArtwork = ( @@ -370,128 +384,97 @@ export async function removeMusicFromSheet( .modify((obj) => { obj.artwork = newArtwork; obj.musicList = restMusic; + // 修改 MusicSheets targetSheet.artwork = newArtwork; targetSheet.musicList = obj.musicList; - forceUpdateMusicSheets(); + musicSheets = [...musicSheets]; }); } ); if (sheetId === defaultSheet.id) { + // 从默认歌单里删除 toBeRemovedMusic.forEach((mi) => { favoriteMusicListIds.delete(getMediaPrimaryKey(mi)); }); - refreshFavoriteState(); } - refreshSheetState(sheetId); } catch (e) { console.log(e); throw e; } } -const refreshFavCbs = new Set<() => void>(); -function refreshFavoriteState() { - refreshFavCbs.forEach((cb) => cb?.()); -} - -/** hook 某首歌曲是否被标记成喜欢 */ -export function useMusicIsFavorite(musicItem: IMusic.IMusicItem) { - const [isFav, setIsFav] = useState( - favoriteMusicListIds.has(getMediaPrimaryKey(musicItem)) - ); +/** 获取歌单内的歌曲详细信息 */ +export async function getSheetItemDetail( + sheetId: string +): Promise { + // 取太多歌曲时会卡顿, 1000首歌大约100ms + const targetSheet = musicSheets.find((item) => item.id === sheetId); + if (!targetSheet) { + return null; + } + const tmpResult = []; + const musicList = targetSheet.musicList ?? []; + // 一组800个 + const groupSize = 800; + const groupNum = Math.ceil(musicList.length / groupSize); - useEffect(() => { - const cb = () => { - setIsFav(favoriteMusicListIds.has(getMediaPrimaryKey(musicItem))); - }; - cb(); - refreshFavCbs.add(cb); - return () => { - refreshFavCbs.delete(cb); - }; - }, [musicItem]); - - return isFav; -} + for (let i = 0; i < groupNum; ++i) { + const sliceResult = await musicSheetDB.transaction( + "readonly", + musicSheetDB.musicStore, + async () => { + return await musicSheetDB.musicStore.bulkGet( + musicList + .slice(i * groupSize, (i + 1) * groupSize) + .map((item) => [item.platform, item.id]) + ); + } + ); -/** 添加到默认歌单 */ -export async function addMusicToFavorite( - musicItems: IMusic.IMusicItem | IMusic.IMusicItem[] -) { - return addMusicToSheet(musicItems, defaultSheet.id); -} + tmpResult.push(...(sliceResult ?? [])); + } -/** 从默认歌单中移除 */ -export async function removeMusicFromFavorite( - musicItems: IMusic.IMusicItem | IMusic.IMusicItem[] -) { - return removeMusicFromSheet(musicItems, defaultSheet.id); + return { + ...targetSheet, + musicList: tmpResult, + } as IMusic.IMusicSheetItem; } -export async function isFavoriteMusic(musicItem: IMusic.IMusicItem) { +/** + * 某首歌是否被标记为喜欢 + * @param musicItem + * @returns + */ +export function isFavoriteMusic(musicItem: IMusic.IMusicItem) { return favoriteMusicListIds.has(getMediaPrimaryKey(musicItem)); } -const updateSheetCbs: Map void>> = new Map(); -function refreshSheetState(sheetId: string) { - updateSheetCbs.get(sheetId)?.forEach((cb) => cb?.()); -} - -export function useMusicSheet(sheetId: string) { - const [musicSheet, setMusicSheet] = useState( - null - ); - const [loading, setLoading] = useState(true); - - const realTimeSheetIdRef = useRef(sheetId); - realTimeSheetIdRef.current = sheetId; - - useEffect(() => { - const updateSheet = async () => { - setLoading(true); - const sheetDetails = await getSheetDetail(sheetId); - setLoading(false); - if (realTimeSheetIdRef.current === sheetId) { - setMusicSheet(sheetDetails); +/** 导出所有歌单信息 */ +export async function exportAllSheetDetails() { + return await musicSheetDB.transaction( + "readonly", + musicSheetDB.musicStore, + async () => { + const allSheets = musicSheets; + if (!allSheets) { + return []; } - }; - const cbs = updateSheetCbs.get(sheetId) ?? new Set(); - cbs.add(updateSheet); - updateSheetCbs.set(sheetId, cbs); - - const targetSheet = musicSheetsStore - .getValue() - .find((item) => item.id === sheetId); - - if (targetSheet) { - setMusicSheet({ - ...targetSheet, - musicList: [], - }); - } - - updateSheet(); - - return () => { - cbs?.delete(updateSheet); - }; - }, [sheetId]); - - return [musicSheet, loading] as const; -} + const musicLists = await Promise.all( + allSheets.map((sheet) => + musicSheetDB.musicStore.bulkGet( + (sheet.musicList ?? []).map((item) => [item.platform, item.id]) + ) + ) + ); -export async function starMusicSheet(sheet: IMedia.IMediaBase) { - const currentStarredSheets = starredSheetsStore.getValue(); - const newSheets = [...currentStarredSheets, sheet]; - await setUserPerferenceIDB("starredMusicSheets", newSheets); - starredSheetsStore.setValue(newSheets); -} + const allSheetDetails = produce(allSheets, (draft) => { + draft.forEach((sheet, index) => { + sheet.musicList = musicLists[index]; + }); + }); -export async function unstarMusicSheet(sheet: IMedia.IMediaBase) { - const newSheets = starredSheetsStore - .getValue() - .filter((item) => !isSameMedia(item, sheet)); - await setUserPerferenceIDB("starredMusicSheets", newSheets); - starredSheetsStore.setValue(newSheets); + return allSheetDetails; + } + ); } diff --git a/src/renderer/core/music-sheet/internal/default-sheet.ts b/src/renderer/core/music-sheet/common/default-sheet.ts similarity index 100% rename from src/renderer/core/music-sheet/internal/default-sheet.ts rename to src/renderer/core/music-sheet/common/default-sheet.ts diff --git a/src/renderer/core/music-sheet/frontend/index.ts b/src/renderer/core/music-sheet/frontend/index.ts new file mode 100644 index 00000000..a39de24b --- /dev/null +++ b/src/renderer/core/music-sheet/frontend/index.ts @@ -0,0 +1,247 @@ +import Store from "@/common/store"; +import * as backend from "../backend"; +import defaultSheet from "../common/default-sheet"; +import { useEffect, useRef, useState } from "react"; +import { RequestStateCode } from "@/common/constant"; + +const musicSheetsStore = new Store([]); +const starredSheetsStore = new Store([]); + +export const useAllSheets = musicSheetsStore.useValue; +export const useAllStarredSheets = starredSheetsStore.useValue; + +/** 更新默认歌单变化 */ +const refreshFavCbs = new Set<() => void>(); +function refreshFavoriteState() { + refreshFavCbs.forEach((cb) => cb?.()); +} + +/** + * 初始化 + */ +export async function setupMusicSheets() { + const [musicSheets, starredSheets] = await Promise.all([ + backend.queryAllSheets(), + backend.queryAllStarredSheets(), + ]); + musicSheetsStore.setValue(musicSheets); + starredSheetsStore.setValue(starredSheets); +} + +/** + * 新建歌单 + * @param sheetName 歌单名 + * @returns 新建的歌单信息 + */ +export async function addSheet(sheetName: string) { + try { + const newSheetDetail = await backend.addSheet(sheetName); + musicSheetsStore.setValue(backend.getAllSheets()); + return newSheetDetail; + } catch {} +} + +/** + * 更新歌单信息 + * @param sheetId 歌单ID + * @param newData 最新的歌单信息 + * @returns + */ +export async function updateSheet( + sheetId: string, + newData: Partial +) { + try { + await backend.updateSheet(sheetId, newData); + musicSheetsStore.setValue(backend.getAllSheets()); + } catch {} +} + +/** + * 移除歌单 + * @param sheetId 歌单ID + * @returns 删除后的ID + */ +export async function removeSheet(sheetId: string) { + try { + await backend.removeSheet(sheetId); + } catch {} +} + +/** + * 收藏歌单 + * @param sheet + */ +export async function starMusicSheet(sheet: IMedia.IMediaBase) { + await backend.starMusicSheet(sheet); + starredSheetsStore.setValue(backend.getAllStarredSheets()); +} + +/** + * 取消收藏歌单 + * @param sheet + */ +export async function unstarMusicSheet(sheet: IMedia.IMediaBase) { + await backend.unstarMusicSheet(sheet); + starredSheetsStore.setValue(backend.getAllStarredSheets()); +} + +/** + * 收藏歌单排序 + */ +export async function setStarredMusicSheets(sheets: IMedia.IMediaBase[]) { + await backend.setStarredMusicSheets(sheets); + starredSheetsStore.setValue(backend.getAllStarredSheets()); +} + +/**************************** 歌曲相关方法 ************************/ + +/** + * 添加歌曲到歌单 + * @param musicItems + * @param sheetId + * @returns + */ +export async function addMusicToSheet( + musicItems: IMusic.IMusicItem | IMusic.IMusicItem[], + sheetId: string +) { + await backend.addMusicToSheet(musicItems, sheetId); + musicSheetsStore.setValue(backend.getAllSheets()); + if (sheetId === defaultSheet.id) { + // 更新默认列表的状态 + refreshFavoriteState(); + } + refreshSheetDetailState(sheetId); +} + +/** 添加到默认歌单 */ +export async function addMusicToFavorite( + musicItems: IMusic.IMusicItem | IMusic.IMusicItem[] +) { + return addMusicToSheet(musicItems, defaultSheet.id); +} + +/** + * 从歌单内移除歌曲 + * @param musicItems 要移除的歌曲 + * @param sheetId 歌单ID + * @returns + */ +export async function removeMusicFromSheet( + musicItems: IMusic.IMusicItem | IMusic.IMusicItem[], + sheetId: string +) { + await backend.removeMusicFromSheet(musicItems, sheetId); + musicSheetsStore.setValue(backend.getAllSheets()); + if (sheetId === defaultSheet.id) { + // 更新默认列表的状态 + refreshFavoriteState(); + } + refreshSheetDetailState(sheetId); +} + +/** 从默认歌单中移除 */ +export async function removeMusicFromFavorite( + musicItems: IMusic.IMusicItem | IMusic.IMusicItem[] +) { + return removeMusicFromSheet(musicItems, defaultSheet.id); +} + +/** 是否是我喜欢的歌单 */ +export function isFavoriteMusic(musicItem: IMusic.IMusicItem) { + return backend.isFavoriteMusic(musicItem); +} + +/** hook 某首歌曲是否被标记成喜欢 */ +export function useMusicIsFavorite(musicItem: IMusic.IMusicItem) { + const [isFav, setIsFav] = useState(backend.isFavoriteMusic(musicItem)); + + useEffect(() => { + const cb = () => { + setIsFav(backend.isFavoriteMusic(musicItem)); + }; + cb(); + refreshFavCbs.add(cb); + return () => { + refreshFavCbs.delete(cb); + }; + }, [musicItem]); + + return isFav; +} + +const updateSheetCbs: Map void>> = new Map(); +function refreshSheetDetailState(sheetId: string) { + updateSheetCbs.get(sheetId)?.forEach((cb) => cb?.()); +} + +/** + * 监听当前某个歌单 + * @param sheetId 歌单ID + * @param initQuery 是否重新查询 + */ +export function useMusicSheet(sheetId: string) { + const [pendingState, setPendingState] = useState( + RequestStateCode.PENDING_FIRST_PAGE + ); + const [sheetItem, setSheetItem] = useState( + null + ); + + // 实时的sheetId + const realTimeSheetIdRef = useRef(sheetId); + realTimeSheetIdRef.current = sheetId; + + const pendingStateRef = useRef(pendingState); + pendingStateRef.current = pendingState; + + useEffect(() => { + const updateSheet = async () => { + const sheetDetail = await backend.getSheetItemDetail(sheetId); + console.log("歌单详情", sheetDetail); + if (realTimeSheetIdRef.current === sheetId) { + console.log("歌单详情", sheetId); + + setSheetItem(sheetDetail); + setPendingState(RequestStateCode.FINISHED); + } + }; + + const updateSheetCallback = async () => { + console.log("Update", pendingStateRef.current); + if (!(pendingStateRef.current & RequestStateCode.LOADING)) { + setPendingState(RequestStateCode.PENDING_REST_PAGE); + await updateSheet(); + } + }; + + const cbs = updateSheetCbs.get(sheetId) ?? new Set(); + cbs.add(updateSheetCallback); + updateSheetCbs.set(sheetId, cbs); + + const targetSheet = musicSheetsStore + .getValue() + .find((item) => item.id === sheetId); + + if (targetSheet) { + setSheetItem({ + ...targetSheet, + musicList: [], + }); + } + + setPendingState(RequestStateCode.PENDING_FIRST_PAGE); + updateSheet(); + + return () => { + cbs?.delete(updateSheet); + }; + }, [sheetId]); + + return [sheetItem, pendingState] as const; +} + +export async function exportAllSheetDetails() { + return await backend.exportAllSheetDetails(); +} diff --git a/src/renderer/core/music-sheet/index.ts b/src/renderer/core/music-sheet/index.ts index 9e5f3d85..f6109831 100644 --- a/src/renderer/core/music-sheet/index.ts +++ b/src/renderer/core/music-sheet/index.ts @@ -1,9 +1,11 @@ -import * as sheetsMethod from "./internal/sheets-method"; +import * as frontend from './frontend'; +import defaultSheet from "./common/default-sheet"; const MusicSheet = { - ...sheetsMethod, + // ...sheetsMethod, + defaultSheet, + frontend }; export default MusicSheet; -export { musicSheetsStore, starredSheetsStore } from "./internal/store"; -export { default as defaultSheet } from "./internal/default-sheet"; +export { defaultSheet }; diff --git a/src/renderer/core/music-sheet/internal/store.ts b/src/renderer/core/music-sheet/internal/store.ts deleted file mode 100644 index 9ca177ee..00000000 --- a/src/renderer/core/music-sheet/internal/store.ts +++ /dev/null @@ -1,5 +0,0 @@ -import Store from "@/common/store"; -import defaultSheet from "./default-sheet"; - -export const musicSheetsStore = new Store([defaultSheet]); -export const starredSheetsStore = new Store([]); \ No newline at end of file diff --git a/src/renderer/core/plugin-delegate/internal/methods.ts b/src/renderer/core/plugin-delegate/internal/methods.ts index e5495b1a..ef281014 100644 --- a/src/renderer/core/plugin-delegate/internal/methods.ts +++ b/src/renderer/core/plugin-delegate/internal/methods.ts @@ -66,6 +66,16 @@ export function getSearchablePlugins( ); } +export function getSortedSearchablePlugins( + supportedSearchType?: IMedia.SupportMediaType +) { + return getSortedSupportedPlugin("search").filter((_) => + supportedSearchType && _.supportedSearchType + ? _.supportedSearchType.includes(supportedSearchType) + : true + ); +} + export function getPluginByHash(hash: string) { return delegatePluginsStore.getValue().find((item) => item.hash === hash); } diff --git a/src/renderer/document/bootstrap.ts b/src/renderer/document/bootstrap.ts index 1e410dee..51b376e0 100644 --- a/src/renderer/document/bootstrap.ts +++ b/src/renderer/document/bootstrap.ts @@ -12,7 +12,6 @@ import { setAutoFreeze } from "immer"; import Evt from "../core/events"; import { ipcRendererInvoke, ipcRendererSend } from "@/common/ipc-util/renderer"; -import * as Comlink from "comlink"; import Downloader from "../core/downloader"; import MessageManager from "../core/message-manager"; @@ -22,7 +21,7 @@ export default async function () { await Promise.all([ rendererAppConfig.setupRendererAppConfig(), registerPluginEvents(), - MusicSheet.setupSheets(), + MusicSheet.frontend.setupMusicSheets(), trackPlayer.setupPlayer(), localMusic.setupLocalMusic(), ]); @@ -134,10 +133,10 @@ function setupEvents() { Evt.on("TOGGLE_LIKE", async (item) => { // 如果没有传入,就是当前播放的歌曲 const realItem = item || trackPlayer.getCurrentMusic(); - if (await MusicSheet.isFavoriteMusic(realItem)) { - MusicSheet.removeMusicFromFavorite(realItem); + if (MusicSheet.frontend.isFavoriteMusic(realItem)) { + MusicSheet.frontend.removeMusicFromFavorite(realItem); } else { - MusicSheet.addMusicToFavorite(realItem); + MusicSheet.frontend.addMusicToFavorite(realItem); } }); } diff --git a/src/renderer/pages/main-page/components/SideBar/widgets/MySheets/index.tsx b/src/renderer/pages/main-page/components/SideBar/widgets/MySheets/index.tsx index 3ca03cc8..150f5725 100644 --- a/src/renderer/pages/main-page/components/SideBar/widgets/MySheets/index.tsx +++ b/src/renderer/pages/main-page/components/SideBar/widgets/MySheets/index.tsx @@ -2,10 +2,7 @@ import "./index.scss"; import ListItem from "../ListItem"; import { useMatch, useNavigate } from "react-router-dom"; import { Disclosure } from "@headlessui/react"; -import MusicSheet, { - defaultSheet, - musicSheetsStore, -} from "@/renderer/core/music-sheet"; +import MusicSheet, { defaultSheet } from "@/renderer/core/music-sheet"; import SvgAsset from "@/renderer/components/SvgAsset"; import { showModal } from "@/renderer/components/Modal"; import { localPluginName } from "@/common/constant"; @@ -16,8 +13,7 @@ export default function MySheets() { `/main/musicsheet/${encodeURIComponent(localPluginName)}/:sheetId` ); const currentSheetId = sheetIdMatch?.params?.sheetId; - - const musicSheets = musicSheetsStore.useValue(); + const musicSheets = MusicSheet.frontend.useAllSheets(); const navigate = useNavigate(); return ( @@ -62,7 +58,7 @@ export default function MySheets() { icon: "trash", show: item.id !== defaultSheet.id, onClick() { - MusicSheet.removeSheet(item.id).then(() => { + MusicSheet.frontend.removeSheet(item.id).then(() => { if (currentSheetId === item.id) { navigate( `/main/musicsheet/${localPluginName}/${defaultSheet.id}`, diff --git a/src/renderer/pages/main-page/components/SideBar/widgets/StarredSheets/index.tsx b/src/renderer/pages/main-page/components/SideBar/widgets/StarredSheets/index.tsx index 033300a4..78dd57a6 100644 --- a/src/renderer/pages/main-page/components/SideBar/widgets/StarredSheets/index.tsx +++ b/src/renderer/pages/main-page/components/SideBar/widgets/StarredSheets/index.tsx @@ -2,10 +2,7 @@ import "./index.scss"; import ListItem from "../ListItem"; import { useMatch, useNavigate } from "react-router-dom"; import { Disclosure } from "@headlessui/react"; -import MusicSheet, { - defaultSheet, - starredSheetsStore, -} from "@/renderer/core/music-sheet"; +import MusicSheet, { defaultSheet } from "@/renderer/core/music-sheet"; import { localPluginName } from "@/common/constant"; import { showContextMenu } from "@/renderer/components/ContextMenu"; @@ -15,7 +12,7 @@ export default function StarredSheets() { const currentPlatform = sheetIdMatch?.params?.platform; const currentSheetId = sheetIdMatch?.params?.sheetId; - const starredSheets = starredSheetsStore.useValue(); + const starredSheets = MusicSheet.frontend.useAllStarredSheets(); const navigate = useNavigate(); @@ -31,13 +28,19 @@ export default function StarredSheets() { key={item.id} iconName={"musical-note"} onClick={() => { - currentSheetId !== item.id && - currentPlatform !== item.platform && + if ( + !( + currentSheetId === item.id && + currentPlatform === item.platform + ) + ) { + // 如果不是相同歌单 navigate(`/main/musicsheet/${item.platform}/${item.id}`, { state: { sheetItem: item, }, }); + } }} onContextMenu={(e) => { showContextMenu({ @@ -48,7 +51,7 @@ export default function StarredSheets() { title: "取消收藏", icon: "trash", onClick() { - MusicSheet.unstarMusicSheet(item).then(() => { + MusicSheet.frontend.unstarMusicSheet(item).then(() => { if ( currentSheetId === item.id && currentPlatform === item.platform diff --git a/src/renderer/pages/main-page/views/music-sheet-view/local-sheet/index.tsx b/src/renderer/pages/main-page/views/music-sheet-view/local-sheet/index.tsx index 03a2ba34..87c7ba2b 100644 --- a/src/renderer/pages/main-page/views/music-sheet-view/local-sheet/index.tsx +++ b/src/renderer/pages/main-page/views/music-sheet-view/local-sheet/index.tsx @@ -1,16 +1,16 @@ import { useParams } from "react-router-dom"; -import { useMusicSheet } from "@/renderer/core/music-sheet/internal/sheets-method"; import MusicSheetlikeView from "@/renderer/components/MusicSheetlikeView"; import { RequestStateCode } from "@/common/constant"; +import MusicSheet from "@/renderer/core/music-sheet"; export default function LocalSheet() { const { id } = useParams() ?? {}; - const [musicSheet, loading] = useMusicSheet(id); + const [musicSheet, loading] = MusicSheet.frontend.useMusicSheet(id); return ( ); diff --git a/src/renderer/pages/main-page/views/music-sheet-view/remote-sheet/hooks/usePluginSheetMusicList.ts b/src/renderer/pages/main-page/views/music-sheet-view/remote-sheet/hooks/usePluginSheetMusicList.ts index bcca4a1d..56c2dbd9 100644 --- a/src/renderer/pages/main-page/views/music-sheet-view/remote-sheet/hooks/usePluginSheetMusicList.ts +++ b/src/renderer/pages/main-page/views/music-sheet-view/remote-sheet/hooks/usePluginSheetMusicList.ts @@ -1,78 +1,106 @@ import { RequestStateCode } from "@/common/constant"; +import { isSameMedia } from "@/common/media-util"; import { callPluginDelegateMethod } from "@/renderer/core/plugin-delegate"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; export default function usePluginSheetMusicList( - originalSheetItem: IMusic.IMusicSheetItem | null + platform: string, + id: string, + originalSheetItem?: IMusic.IMusicSheetItem | null // 额外的输入 ) { - const currentPageRef = useRef(1); const [requestState, setRequestState] = useState( RequestStateCode.IDLE ); - const [sheetItem, setSheetItem] = useState( - originalSheetItem - ); + const [sheetItem, setSheetItem] = useState({ + ...originalSheetItem, + platform, + id, + }); const [musicList, setMusicList] = useState( originalSheetItem?.musicList ?? [] ); - const getSheetDetail = useCallback( - async function () { - if ( - originalSheetItem === null || - requestState & RequestStateCode.PENDING_FIRST_PAGE - ) { - return; - } + // 当前正在搜索的信息 + const currentSheetItemRef = useRef(null); + // 页码 + const currentPageRef = useRef(1); + + const getSheetDetail = async () => { + if (!isSameMedia(currentSheetItemRef.current, originalSheetItem)) { + // 1.1 如果是切换了新的歌单 + // 恢复初始状态 并设置当前的歌曲项 + currentSheetItemRef.current = { + ...originalSheetItem, + platform, + id, + }; + setSheetItem(currentSheetItemRef.current); + setMusicList(originalSheetItem?.musicList ?? []); + currentPageRef.current = 1; + } else if (requestState & RequestStateCode.PENDING_FIRST_PAGE) { + // 1.2 如果是原有歌单,并且在loading中,返回 + return; + } - try { - setRequestState( - currentPageRef.current === 1 - ? RequestStateCode.PENDING_FIRST_PAGE - : RequestStateCode.PENDING_REST_PAGE - ); - const result = await callPluginDelegateMethod( - originalSheetItem, - "getMusicSheetInfo", - originalSheetItem, - currentPageRef.current - ); + try { + // 2. 设置初始状态 + setRequestState( + currentPageRef.current === 1 + ? RequestStateCode.PENDING_FIRST_PAGE + : RequestStateCode.PENDING_REST_PAGE + ); + // 3. 调用获取音乐详情接口 + const sheetItem = currentSheetItemRef.current; + const result = await callPluginDelegateMethod( + sheetItem, + "getMusicSheetInfo", + sheetItem, + currentPageRef.current + ); - if (result === null || result === undefined) { - throw new Error(); - } - if (result?.sheetItem) { - setSheetItem((prev) => ({ - ...(prev ?? {}), - ...(result.sheetItem as IMusic.IMusicSheetItem), - platform: originalSheetItem.platform, - })); - } - if (result?.musicList) { - setMusicList((prev) => { - if (currentPageRef.current === 1) { - return result?.musicList ?? prev; - } else { - return [...prev, ...(result.musicList ?? [])]; - } - }); - } - setRequestState( - result.isEnd - ? RequestStateCode.FINISHED - : RequestStateCode.PARTLY_DONE - ); - currentPageRef.current += 1; - } catch { - setRequestState(currentPageRef.current === 1 ? RequestStateCode.FINISHED: RequestStateCode.PARTLY_DONE); + if (!isSameMedia(currentSheetItemRef.current, sheetItem)) { + // 出现竞态 结果直接舍弃 + return; } - }, - [requestState] - ); + if (result === null || result === undefined) { + throw new Error(); + } + // 3. 如果在页码为1的时候返回了sheetItem,重新设置下sheetItem + if (result?.sheetItem && currentPageRef.current <= 1) { + setSheetItem((prev) => ({ + ...(prev ?? {}), + ...(result.sheetItem as IMusic.IMusicSheetItem), + platform: originalSheetItem.platform, + })); + } + // 4. 如果返回了音乐列表 + if (result?.musicList) { + setMusicList((prev) => { + if (currentPageRef.current === 1) { + return result?.musicList ?? prev; + } else { + return [...prev, ...(result.musicList ?? [])]; + } + }); + } + setRequestState( + result.isEnd ? RequestStateCode.FINISHED : RequestStateCode.PARTLY_DONE + ); + currentPageRef.current += 1; + } catch { + setRequestState( + currentPageRef.current === 1 + ? RequestStateCode.FINISHED + : RequestStateCode.PARTLY_DONE + ); + } + }; useEffect(() => { - getSheetDetail(); - }, []); + if (platform && id) { + getSheetDetail(); + } + }, [platform, id]); return [requestState, sheetItem, musicList, getSheetDetail] as const; } diff --git a/src/renderer/pages/main-page/views/music-sheet-view/remote-sheet/index.tsx b/src/renderer/pages/main-page/views/music-sheet-view/remote-sheet/index.tsx index 1afa5e64..87a3607a 100644 --- a/src/renderer/pages/main-page/views/music-sheet-view/remote-sheet/index.tsx +++ b/src/renderer/pages/main-page/views/music-sheet-view/remote-sheet/index.tsx @@ -2,22 +2,17 @@ import React from "react"; import { useParams } from "react-router-dom"; import usePluginSheetMusicList from "./hooks/usePluginSheetMusicList"; import MusicSheetlikeView from "@/renderer/components/MusicSheetlikeView"; -import { starredSheetsStore } from "@/renderer/core/music-sheet"; import { isSameMedia } from "@/common/media-util"; -import { - starMusicSheet, - unstarMusicSheet, -} from "@/renderer/core/music-sheet/internal/sheets-method"; + +import MusicSheet from "@/renderer/core/music-sheet"; export default function RemoteSheet() { const { platform, id } = useParams() ?? {}; const [state, sheetItem, musicList, getSheetDetail] = usePluginSheetMusicList( - { - ...(history.state?.usr?.sheetItem ?? {}), - platform, - id, - } as IMusic.IMusicSheetItem + platform, + id, + history.state?.usr?.sheetItem ); return ( isSameMedia(sheetItem, item) @@ -49,7 +44,9 @@ function RemoteSheetOptions(props: IProps) { role="button" data-type="normalButton" onClick={() => { - isStarred ? unstarMusicSheet(sheetItem) : starMusicSheet(sheetItem); + isStarred + ? MusicSheet.frontend.unstarMusicSheet(sheetItem) + : MusicSheet.frontend.starMusicSheet(sheetItem); }} > {isStarred ? "取消收藏" : "收藏歌单"} diff --git a/src/renderer/pages/main-page/views/search-view/index.tsx b/src/renderer/pages/main-page/views/search-view/index.tsx index da42ec5b..9596cc7f 100644 --- a/src/renderer/pages/main-page/views/search-view/index.tsx +++ b/src/renderer/pages/main-page/views/search-view/index.tsx @@ -1,5 +1,6 @@ import { getSearchablePlugins, + getSortedSearchablePlugins, useSortedSupportedPlugin, } from "@/renderer/core/plugin-delegate"; import { useEffect } from "react"; @@ -68,7 +69,7 @@ export default function SearchView() { diff --git a/src/renderer/pages/main-page/views/setting-view/routers/Backup/index.tsx b/src/renderer/pages/main-page/views/setting-view/routers/Backup/index.tsx index a872625e..f3495fb4 100644 --- a/src/renderer/pages/main-page/views/setting-view/routers/Backup/index.tsx +++ b/src/renderer/pages/main-page/views/setting-view/routers/Backup/index.tsx @@ -22,7 +22,7 @@ export default function Backup() { title: "备份到...", }); if (!result.canceled && result.filePath) { - const sheetDetails = await MusicSheet.getAllSheetDetails(); + const sheetDetails = await MusicSheet.frontend.exportAllSheetDetails(); const backUp = JSON.stringify({ musicSheets: sheetDetails, }); @@ -56,8 +56,8 @@ export default function Backup() { const parsedSheets = JSON.parse(rawSheets); const allSheets = parsedSheets.musicSheets; for (const sheet of allSheets) { - const sheetId = await MusicSheet.addSheet(sheet.title); - await MusicSheet.addMusicToSheet(sheet.musicList, sheetId); + const newSheet = await MusicSheet.frontend.addSheet(sheet.title); + await MusicSheet.frontend.addMusicToSheet(sheet.musicList, newSheet.id); } toast.success("恢复成功~"); } catch (e) { From b72903af7a110365f7b940c7dcc2ed9fadfbbacd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Sat, 16 Dec 2023 17:24:02 +0800 Subject: [PATCH 25/50] =?UTF-8?q?feat:=20=E6=92=AD=E6=94=BE=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E5=8F=AF=E5=A4=9A=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/components/MusicList/index.scss | 5 + src/renderer/components/MusicList/index.tsx | 22 +++- .../Panel/templates/PlayList/index.scss | 5 + .../Panel/templates/PlayList/index.tsx | 113 +++++++++++++++--- .../core/music-sheet/frontend/index.ts | 13 +- src/renderer/core/track-player/player.ts | 64 +++++++--- src/renderer/hooks/useStateRef.ts | 10 ++ .../views/local-music-view/index.tsx | 1 + 8 files changed, 191 insertions(+), 42 deletions(-) create mode 100644 src/renderer/hooks/useStateRef.ts diff --git a/src/renderer/components/MusicList/index.scss b/src/renderer/components/MusicList/index.scss index 52a6ce87..f0f12eae 100644 --- a/src/renderer/components/MusicList/index.scss +++ b/src/renderer/components/MusicList/index.scss @@ -1,6 +1,11 @@ .music-list-container { width: 100%; min-height: 300px; + + + &:focus-visible { + outline: none; + } & th { position: relative; diff --git a/src/renderer/components/MusicList/index.tsx b/src/renderer/components/MusicList/index.tsx index b8ee7f28..98420e9c 100644 --- a/src/renderer/components/MusicList/index.tsx +++ b/src/renderer/components/MusicList/index.tsx @@ -204,10 +204,18 @@ export function showMusicContextMenu( { title: "从歌单内删除", icon: "trash", - show: !!localMusicSheetId, + show: !!localMusicSheetId && localMusicSheetId !== 'play-list', onClick() { MusicSheet.frontend.removeMusicFromSheet(musicItems, localMusicSheetId); }, + }, + { + title: "删除", + icon: "trash", + show: localMusicSheetId === 'play-list', + onClick() { + trackPlayer.removeFromQueue(musicItems); + }, } ); @@ -357,16 +365,13 @@ function _MusicList(props: IMusicListProps) { }, [musicList]); useEffect(() => { - const musiclistScope = "ml" + Math.random().toString().slice(2); - hotkeys("Shift", musiclistScope, () => {}); const ctrlAHandler = (evt: Event) => { evt.preventDefault(); setActiveItems([0, musicListRef.current.length - 1]); }; - hotkeys("Ctrl+A", ctrlAHandler); + hotkeys("Ctrl+A", 'music-list', ctrlAHandler); return () => { - hotkeys.unbind("Shift", musiclistScope); hotkeys.unbind("Ctrl+A", ctrlAHandler); }; }, []); @@ -391,6 +396,13 @@ function _MusicList(props: IMusicListProps) { className="music-list-container" style={containerStyle} ref={tableContainerRef} + tabIndex={-1} + onFocus={() => { + hotkeys.setScope('music-list'); + }} + onBlur={() => { + hotkeys.setScope('all'); + }} > (); + const [activeItems, setActiveItems] = useState([]); const virtualController = useVirtualList({ estimizeItemHeight, @@ -40,8 +44,24 @@ export default function PlayList() { virtualController.scrollToIndex(index - 4); } } + + const ctrlAHandler = (evt: Event) => { + evt.preventDefault(); + const queue = trackPlayer.getMusicQueue(); + setActiveItems([0, queue.length - 1]); + }; + hotkeys("Ctrl+A", "play-list", ctrlAHandler); + + return () => { + hotkeys.unbind("Ctrl+A", ctrlAHandler); + }; }, []); + + useEffect(() => { + setActiveItems([]); + }, [musicQueue]); + return (
@@ -63,9 +83,16 @@ export default function PlayList() { style={{ height: virtualController.totalHeight, }} + tabIndex={-1} + onFocus={() => { + hotkeys.setScope("play-list"); + }} + onBlur={() => { + hotkeys.setScope("all"); + }} > {virtualController.virtualItems.map((virtualItem) => { - const item = virtualItem.dataItem; + const musicItem = virtualItem.dataItem; return (
{ + trackPlayer.playMusic(musicItem); + }} + onContextMenu={(e) => { + if ( + activeItems.length === 2 && + isBetween( + virtualItem.rowIndex, + activeItems[0], + activeItems[1] + ) && + activeItems[0] !== activeItems[1] + ) { + let [start, end] = activeItems; + if (start > end) { + [start, end] = [end, start]; + } + + showMusicContextMenu( + musicQueue.slice(start, end + 1), + e.clientX, + e.clientY, + 'play-list' + ); + } else { + setActiveItems([virtualItem.rowIndex]); + showMusicContextMenu( + musicItem, + e.clientX, + e.clientY, + 'play-list' + ); + } + }} + onClick={() => { + // 如果点击的时候按下shift + if (hotkeys.shift) { + setActiveItems([ + activeItems[0] ?? 0, + virtualItem.rowIndex, + ]); + } else { + setActiveItems([virtualItem.rowIndex]); + } + }} >
); @@ -93,9 +174,10 @@ export default function PlayList() { interface IPlayListMusicItemProps { isPlaying: boolean; musicItem: IMusic.IMusicItem; + isActive?: boolean; } function _PlayListMusicItem(props: IPlayListMusicItemProps) { - const { isPlaying, musicItem } = props; + const { isPlaying, musicItem, isActive } = props; return (
{ - trackPlayer.playMusic(musicItem); - }} - onContextMenu={(e) => { - showMusicContextMenu(musicItem, e.clientX, e.clientY); - }} + data-active={isActive} >
@@ -121,9 +198,13 @@ function _PlayListMusicItem(props: IPlayListMusicItemProps) { {musicItem.artist ?? "-"}
- {musicItem.platform} + + {musicItem.platform} +
- prev.isPlaying === curr.isPlaying && prev.musicItem === curr.musicItem + prev.isPlaying === curr.isPlaying && + prev.musicItem === curr.musicItem && + prev.isActive === curr.isActive ); diff --git a/src/renderer/core/music-sheet/frontend/index.ts b/src/renderer/core/music-sheet/frontend/index.ts index a39de24b..9e078b12 100644 --- a/src/renderer/core/music-sheet/frontend/index.ts +++ b/src/renderer/core/music-sheet/frontend/index.ts @@ -106,7 +106,10 @@ export async function addMusicToSheet( musicItems: IMusic.IMusicItem | IMusic.IMusicItem[], sheetId: string ) { + const start = Date.now(); await backend.addMusicToSheet(musicItems, sheetId); + console.log("添加音乐", Date.now() - start, "ms"); + musicSheetsStore.setValue(backend.getAllSheets()); if (sheetId === defaultSheet.id) { // 更新默认列表的状态 @@ -132,7 +135,10 @@ export async function removeMusicFromSheet( musicItems: IMusic.IMusicItem | IMusic.IMusicItem[], sheetId: string ) { + const start = Date.now(); await backend.removeMusicFromSheet(musicItems, sheetId); + console.log("删除音乐", Date.now() - start, "ms"); + musicSheetsStore.setValue(backend.getAllSheets()); if (sheetId === defaultSheet.id) { // 更新默认列表的状态 @@ -198,18 +204,17 @@ export function useMusicSheet(sheetId: string) { useEffect(() => { const updateSheet = async () => { + const start = Date.now(); const sheetDetail = await backend.getSheetItemDetail(sheetId); - console.log("歌单详情", sheetDetail); + console.log("歌单详情", Date.now() - start, "ms"); if (realTimeSheetIdRef.current === sheetId) { console.log("歌单详情", sheetId); - setSheetItem(sheetDetail); setPendingState(RequestStateCode.FINISHED); } }; const updateSheetCallback = async () => { - console.log("Update", pendingStateRef.current); if (!(pendingStateRef.current & RequestStateCode.LOADING)) { setPendingState(RequestStateCode.PENDING_REST_PAGE); await updateSheet(); @@ -235,7 +240,7 @@ export function useMusicSheet(sheetId: string) { updateSheet(); return () => { - cbs?.delete(updateSheet); + cbs?.delete(updateSheetCallback); }; }, [sheetId]); diff --git a/src/renderer/core/track-player/player.ts b/src/renderer/core/track-player/player.ts index 2a8750a1..2317a795 100644 --- a/src/renderer/core/track-player/player.ts +++ b/src/renderer/core/track-player/player.ts @@ -663,28 +663,56 @@ export function clearQueue() { currentIndex = -1; } -export function removeFromQueue(musicItem: IMusic.IMusicItem | number) { - let musicIndex: number; - if (typeof musicItem !== "number") { - musicIndex = findMusicIndex(musicItem); +export function removeFromQueue( + musicItem: IMusic.IMusicItem | number | IMusic.IMusicItem[] +) { + if (Array.isArray(musicItem)) { + const mapObj: Record> = {}; + for (const mi of musicItem) { + mapObj[mi.platform] = { ...mapObj[mi.platform], [mi.id]: true }; + } + const musicQueue = musicQueueStore.getValue(); + const result: IMusic.IMusicItem[] = []; + for (let i = 0; i < musicQueue.length; ++i) { + const loopMusicItem = musicQueue[i]; + // 命中即将删除的逻辑 + if (mapObj?.[loopMusicItem.platform]?.[loopMusicItem.id]) { + if (currentIndex === i) { + trackPlayer.clear(); + currentIndex = -1; + setCurrentMusic(null); + } + } else { + result.push(loopMusicItem); + if (currentIndex === i) { + currentIndex = result.length - 1; + } + } + } + setMusicQueue(result); } else { - musicIndex = musicItem; - } - if (musicIndex === -1) { - return; - } + let musicIndex: number; + if (typeof musicItem !== "number") { + musicIndex = findMusicIndex(musicItem); + } else { + musicIndex = musicItem; + } + if (musicIndex === -1) { + return; + } - if (musicIndex === currentIndex) { - trackPlayer.clear(); - currentIndex = -1; - setCurrentMusic(null); - } + if (musicIndex === currentIndex) { + trackPlayer.clear(); + currentIndex = -1; + setCurrentMusic(null); + } - const newQueue = [...musicQueueStore.getValue()]; - newQueue.splice(musicIndex, 1); + const newQueue = [...musicQueueStore.getValue()]; + newQueue.splice(musicIndex, 1); - setMusicQueue(newQueue); - currentIndex = findMusicIndex(currentMusicStore.getValue()); + setMusicQueue(newQueue); + currentIndex = findMusicIndex(currentMusicStore.getValue()); + } } export function seekTo(position: number) { diff --git a/src/renderer/hooks/useStateRef.ts b/src/renderer/hooks/useStateRef.ts new file mode 100644 index 00000000..83b598aa --- /dev/null +++ b/src/renderer/hooks/useStateRef.ts @@ -0,0 +1,10 @@ +import { useRef, useState } from "react"; + +export default function useStateRef(initValue: T) { + const [state, setState] = useState(initValue); + const ref = useRef(initValue); + + ref.current = state; + + return [state, setState, ref] as const; +} diff --git a/src/renderer/pages/main-page/views/local-music-view/index.tsx b/src/renderer/pages/main-page/views/local-music-view/index.tsx index a255dfd7..581e3454 100644 --- a/src/renderer/pages/main-page/views/local-music-view/index.tsx +++ b/src/renderer/pages/main-page/views/local-music-view/index.tsx @@ -39,6 +39,7 @@ export default function LocalMusicView() { 自动扫描
+
搜索
Date: Sat, 16 Dec 2023 22:12:58 +0800 Subject: [PATCH 26/50] =?UTF-8?q?feat:=20=E6=8B=96=E6=8B=BD=E6=8E=92?= =?UTF-8?q?=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/components/MusicList/index.tsx | 16 +- .../components/Body/index.tsx | 7 +- .../core/music-sheet/frontend/index.ts | 150 +++++++++++++++--- .../views/local-music-view/index.tsx | 1 - 4 files changed, 142 insertions(+), 32 deletions(-) diff --git a/src/renderer/components/MusicList/index.tsx b/src/renderer/components/MusicList/index.tsx index 98420e9c..0f2d479d 100644 --- a/src/renderer/components/MusicList/index.tsx +++ b/src/renderer/components/MusicList/index.tsx @@ -204,7 +204,7 @@ export function showMusicContextMenu( { title: "从歌单内删除", icon: "trash", - show: !!localMusicSheetId && localMusicSheetId !== 'play-list', + show: !!localMusicSheetId && localMusicSheetId !== "play-list", onClick() { MusicSheet.frontend.removeMusicFromSheet(musicItems, localMusicSheetId); }, @@ -212,7 +212,7 @@ export function showMusicContextMenu( { title: "删除", icon: "trash", - show: localMusicSheetId === 'play-list', + show: localMusicSheetId === "play-list", onClick() { trackPlayer.removeFromQueue(musicItems); }, @@ -369,7 +369,7 @@ function _MusicList(props: IMusicListProps) { evt.preventDefault(); setActiveItems([0, musicListRef.current.length - 1]); }; - hotkeys("Ctrl+A", 'music-list', ctrlAHandler); + hotkeys("Ctrl+A", "music-list", ctrlAHandler); return () => { hotkeys.unbind("Ctrl+A", ctrlAHandler); @@ -385,7 +385,11 @@ function _MusicList(props: IMusicListProps) { const newData = musicList .slice(0, fromIndex) .concat(musicList.slice(fromIndex + 1)); - newData.splice(toIndex, 0, musicList[fromIndex]); + newData.splice( + fromIndex > toIndex ? toIndex : toIndex - 1, + 0, + musicList[fromIndex] + ); onDragEnd?.(newData); }, [onDragEnd, musicList] @@ -398,10 +402,10 @@ function _MusicList(props: IMusicListProps) { ref={tableContainerRef} tabIndex={-1} onFocus={() => { - hotkeys.setScope('music-list'); + hotkeys.setScope("music-list"); }} onBlur={() => { - hotkeys.setScope('all'); + hotkeys.setScope("all"); }} >
{ - if (musicSheet?.platform === localPluginName) { - // MusicSheet.updateSheetMusicList(musicSheet.id, { - // // @ts-ignore - // musicList: newData.map(toMediaBase), - // }); + if (musicSheet?.platform === localPluginName && musicSheet?.id) { + MusicSheet.frontend.updateSheetMusicOrder(musicSheet.id, newData); } }} > diff --git a/src/renderer/core/music-sheet/frontend/index.ts b/src/renderer/core/music-sheet/frontend/index.ts index 9e078b12..a723dbbc 100644 --- a/src/renderer/core/music-sheet/frontend/index.ts +++ b/src/renderer/core/music-sheet/frontend/index.ts @@ -2,7 +2,8 @@ import Store from "@/common/store"; import * as backend from "../backend"; import defaultSheet from "../common/default-sheet"; import { useEffect, useRef, useState } from "react"; -import { RequestStateCode } from "@/common/constant"; +import { RequestStateCode, localPluginName } from "@/common/constant"; +import { toMediaBase } from "@/common/media-util"; const musicSheetsStore = new Store([]); const starredSheetsStore = new Store([]); @@ -57,6 +58,30 @@ export async function updateSheet( } catch {} } +/** + * 更新歌单中的歌曲顺序 + * @param sheetId + * @param musicList + */ +export async function updateSheetMusicOrder( + sheetId: string, + musicList: IMusic.IMusicItem[] +) { + try { + const targetSheet = musicSheetsStore + .getValue() + .find((it) => it.id === sheetId); + updateSheetDetail({ + ...targetSheet, + musicList, + }); + await backend.updateSheet(sheetId, { + musicList: musicList.map(toMediaBase) as any, + }); + musicSheetsStore.setValue(backend.getAllSheets()); + } catch {} +} + /** * 移除歌单 * @param sheetId 歌单ID @@ -65,6 +90,7 @@ export async function updateSheet( export async function removeSheet(sheetId: string) { try { await backend.removeSheet(sheetId); + musicSheetsStore.setValue(backend.getAllSheets()); } catch {} } @@ -178,10 +204,39 @@ export function useMusicIsFavorite(musicItem: IMusic.IMusicItem) { } const updateSheetCbs: Map void>> = new Map(); +/** 更新最新的歌单状态 */ function refreshSheetDetailState(sheetId: string) { updateSheetCbs.get(sheetId)?.forEach((cb) => cb?.()); } +const updateSheetDetailCallbacks: Map< + string, + Set<(newSheet: IMusic.IMusicSheetItem) => void> +> = new Map(); + +function updateSheetDetail(newSheet: IMusic.IMusicSheetItem) { + updateSheetDetailCallbacks.get(newSheet?.id)?.forEach((cb) => cb?.(newSheet)); +} + +/** + * 重新取歌单状态 + * @param sheetId + */ +async function refetchSheetDetail(sheetId: string) { + let sheetDetail = await backend.getSheetItemDetail(sheetId); + if (!sheetDetail) { + // 可能已经被删除了 + sheetDetail = { + id: sheetId, + title: "已删除歌单", + artist: "未知作者", + platform: localPluginName, + }; + } + + updateSheetDetail(sheetDetail); +} + /** * 监听当前某个歌单 * @param sheetId 歌单ID @@ -203,27 +258,17 @@ export function useMusicSheet(sheetId: string) { pendingStateRef.current = pendingState; useEffect(() => { - const updateSheet = async () => { - const start = Date.now(); - const sheetDetail = await backend.getSheetItemDetail(sheetId); - console.log("歌单详情", Date.now() - start, "ms"); - if (realTimeSheetIdRef.current === sheetId) { - console.log("歌单详情", sheetId); - setSheetItem(sheetDetail); + const updateSheet = async (newSheet: IMusic.IMusicSheetItem) => { + // 如果更新的是当前歌单,则设置 + if (realTimeSheetIdRef.current === newSheet.id) { + setSheetItem(newSheet); setPendingState(RequestStateCode.FINISHED); } }; - const updateSheetCallback = async () => { - if (!(pendingStateRef.current & RequestStateCode.LOADING)) { - setPendingState(RequestStateCode.PENDING_REST_PAGE); - await updateSheet(); - } - }; - - const cbs = updateSheetCbs.get(sheetId) ?? new Set(); - cbs.add(updateSheetCallback); - updateSheetCbs.set(sheetId, cbs); + const cbs = updateSheetDetailCallbacks.get(sheetId) ?? new Set(); + cbs.add(updateSheet); + updateSheetDetailCallbacks.set(sheetId, cbs); const targetSheet = musicSheetsStore .getValue() @@ -237,16 +282,81 @@ export function useMusicSheet(sheetId: string) { } setPendingState(RequestStateCode.PENDING_FIRST_PAGE); - updateSheet(); + refetchSheetDetail(sheetId); return () => { - cbs?.delete(updateSheetCallback); + cbs?.delete(updateSheet); }; }, [sheetId]); return [sheetItem, pendingState] as const; } +/** + * 监听当前某个歌单 + * @param sheetId 歌单ID + * @param initQuery 是否重新查询 + */ +// export function useMusicSheet(sheetId: string) { +// const [pendingState, setPendingState] = useState( +// RequestStateCode.PENDING_FIRST_PAGE +// ); +// const [sheetItem, setSheetItem] = useState( +// null +// ); + +// // 实时的sheetId +// const realTimeSheetIdRef = useRef(sheetId); +// realTimeSheetIdRef.current = sheetId; + +// const pendingStateRef = useRef(pendingState); +// pendingStateRef.current = pendingState; + +// useEffect(() => { +// const updateSheet = async () => { +// const start = Date.now(); +// const sheetDetail = await backend.getSheetItemDetail(sheetId); +// console.log("歌单详情", Date.now() - start, "ms"); +// if (realTimeSheetIdRef.current === sheetId) { +// console.log("歌单详情", sheetId); +// setSheetItem(sheetDetail); +// setPendingState(RequestStateCode.FINISHED); +// } +// }; + +// const updateSheetCallback = async () => { +// if (!(pendingStateRef.current & RequestStateCode.LOADING)) { +// setPendingState(RequestStateCode.PENDING_REST_PAGE); +// await updateSheet(); +// } +// }; + +// const cbs = updateSheetCbs.get(sheetId) ?? new Set(); +// cbs.add(updateSheetCallback); +// updateSheetCbs.set(sheetId, cbs); + +// const targetSheet = musicSheetsStore +// .getValue() +// .find((item) => item.id === sheetId); + +// if (targetSheet) { +// setSheetItem({ +// ...targetSheet, +// musicList: [], +// }); +// } + +// setPendingState(RequestStateCode.PENDING_FIRST_PAGE); +// updateSheet(); + +// return () => { +// cbs?.delete(updateSheetCallback); +// }; +// }, [sheetId]); + +// return [sheetItem, pendingState] as const; +// } + export async function exportAllSheetDetails() { return await backend.exportAllSheetDetails(); } diff --git a/src/renderer/pages/main-page/views/local-music-view/index.tsx b/src/renderer/pages/main-page/views/local-music-view/index.tsx index 581e3454..a255dfd7 100644 --- a/src/renderer/pages/main-page/views/local-music-view/index.tsx +++ b/src/renderer/pages/main-page/views/local-music-view/index.tsx @@ -39,7 +39,6 @@ export default function LocalMusicView() { 自动扫描
-
搜索
Date: Sat, 16 Dec 2023 22:21:30 +0800 Subject: [PATCH 27/50] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 38b8600a..48537e99 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ +## 打包分发请遵守 GPL3.0 协议,并保留此 Github 仓库地址作为出处。 + ### 下载地址 [蓝奏云下载地址](https://wwzb.lanzoue.com/b042da1xe) From 4c379709e37e2c86348ebd0e590ae391819dce93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Sun, 17 Dec 2023 09:43:58 +0800 Subject: [PATCH 28/50] =?UTF-8?q?feat:=20=E6=A6=9C=E5=8D=95=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E6=94=AF=E6=8C=81=E5=88=86=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/plugin-manager/plugin-methods.ts | 14 ++-- src/renderer/components/MusicDetail/index.tsx | 2 +- .../hooks/useTopListDetail.ts | 66 +++++++++++-------- .../views/toplist-detail-view/index.tsx | 9 +-- src/types/plugin.d.ts | 10 ++- 5 files changed, 60 insertions(+), 41 deletions(-) diff --git a/src/main/core/plugin-manager/plugin-methods.ts b/src/main/core/plugin-manager/plugin-methods.ts index a38a3466..480d5d40 100644 --- a/src/main/core/plugin-manager/plugin-methods.ts +++ b/src/main/core/plugin-manager/plugin-methods.ts @@ -378,11 +378,13 @@ export default class PluginMethods implements IPlugin.IPluginInstanceMethods { } /** 获取榜单详情 */ async getTopListDetail( - topListItem: IMusic.IMusicSheetItem - ): Promise> { + topListItem: IMusic.IMusicSheetItem, + page: number + ): Promise { try { const result = await this.plugin.instance?.getTopListDetail?.( - topListItem + topListItem, + page ); if (!result) { throw new Error(); @@ -390,11 +392,15 @@ export default class PluginMethods implements IPlugin.IPluginInstanceMethods { if (result.musicList) { result.musicList.forEach((_) => resetMediaItem(_, this.plugin.name)); } + if (result.isEnd !== false) { + result.isEnd = true; + } return result; } catch (e: any) { // devLog('error', '获取榜单详情失败', e, e?.message); return { - ...topListItem, + isEnd: true, + topListItem, musicList: [], }; } diff --git a/src/renderer/components/MusicDetail/index.tsx b/src/renderer/components/MusicDetail/index.tsx index ed270d23..6714792c 100644 --- a/src/renderer/components/MusicDetail/index.tsx +++ b/src/renderer/components/MusicDetail/index.tsx @@ -68,7 +68,7 @@ export default function () {
diff --git a/src/renderer/pages/main-page/views/toplist-detail-view/hooks/useTopListDetail.ts b/src/renderer/pages/main-page/views/toplist-detail-view/hooks/useTopListDetail.ts index 78bd6ae3..945b88b4 100644 --- a/src/renderer/pages/main-page/views/toplist-detail-view/hooks/useTopListDetail.ts +++ b/src/renderer/pages/main-page/views/toplist-detail-view/hooks/useTopListDetail.ts @@ -1,5 +1,6 @@ +import { RequestStateCode } from "@/common/constant"; import { callPluginDelegateMethod } from "@/renderer/core/plugin-delegate"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; export default function useTopListDetail( topListItem: IMusic.IMusicSheetItem | null, @@ -7,37 +8,46 @@ export default function useTopListDetail( ) { const [mergedTopListItem, setMergedTopListItem] = useState | null>(topListItem); + const pageRef = useRef(1); + const [requestState, setRequestState] = useState(RequestStateCode.IDLE); + + async function loadMore(){ + if (!topListItem) { + return; + } + try { + if (pageRef.current === 1) { + setRequestState(RequestStateCode.PENDING_FIRST_PAGE); + } else { + setRequestState(RequestStateCode.PENDING_REST_PAGE); + } + const result = await callPluginDelegateMethod({platform}, 'getTopListDetail', topListItem, pageRef.current); + if (!result) { + throw new Error(); + } + const currentPage = pageRef.current; + setMergedTopListItem((prev) => ({ + ...prev, + ...(result.topListItem), + musicList: currentPage === 1 ? (result.musicList ?? []): [...prev.musicList, ...result.musicList] + })) + + if (!result.isEnd) { + setRequestState(RequestStateCode.IDLE); + } else { + setRequestState(RequestStateCode.FINISHED); + } + pageRef.current++; + } catch { + setRequestState(RequestStateCode.FINISHED); + } + } useEffect(() => { if (topListItem === null) { return; } - console.log("here", topListItem, platform); - callPluginDelegateMethod( - { - platform, - }, - "getTopListDetail", - topListItem - ) - .then((_) => { - if (_) { - setMergedTopListItem( - (prev) => - ({ - ...(prev ?? {}), - ...(_ ?? {}), - } as any) - ); - } - }) - .catch((e) => { - console.log("catch", e); - setMergedTopListItem((prev) => ({ - ...prev, - musicList: [], - })); - }); + loadMore(); }, []); - return mergedTopListItem; + return [mergedTopListItem, requestState, loadMore] as const; } diff --git a/src/renderer/pages/main-page/views/toplist-detail-view/index.tsx b/src/renderer/pages/main-page/views/toplist-detail-view/index.tsx index 90871c49..fb47aded 100644 --- a/src/renderer/pages/main-page/views/toplist-detail-view/index.tsx +++ b/src/renderer/pages/main-page/views/toplist-detail-view/index.tsx @@ -6,7 +6,7 @@ import { RequestStateCode } from "@/common/constant"; export default function TopListDetailView() { const params = useParams(); - const topListDetail = useTopListDetail( + const [topListDetail, state, loadMore] = useTopListDetail( history.state?.usr?.toplist, params?.platform ); @@ -15,11 +15,8 @@ export default function TopListDetailView() { ); } diff --git a/src/types/plugin.d.ts b/src/types/plugin.d.ts index c198423b..bed98d87 100644 --- a/src/types/plugin.d.ts +++ b/src/types/plugin.d.ts @@ -47,6 +47,12 @@ declare namespace IPlugin { musicList?: IMusic.IMusicItem[]; } + interface ITopListInfoResult { + isEnd?: boolean; + topListItem?: IMusic.IMusicSheetItem, + musicList?: IMusic.IMusicItem[]; + } + interface IGetRecommendSheetTagsResult { // 固定的tag pinned?: IMusic.IMusicSheetItem[]; @@ -112,11 +118,11 @@ declare namespace IPlugin { ) => Promise; /** 获取榜单 */ getTopLists?: () => Promise; - // todo:分页 /** 获取榜单详情 */ getTopListDetail?: ( topListItem: IMusic.IMusicSheetItem, - ) => Promise>; + page: number + ) => Promise; /** 获取热门歌单tag */ getRecommendSheetTags?: () => Promise; /** 歌单列表 */ From 823c1bda5d5eda717ecaa0759d9989992cbb5e48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Sun, 17 Dec 2023 09:54:14 +0800 Subject: [PATCH 29/50] =?UTF-8?q?feat:=20=E5=88=A0=E9=99=A4=E5=A4=9A?= =?UTF-8?q?=E4=BD=99=E7=9A=84=E5=90=8C=E6=AD=A5=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ipc-util/eventType/renderer-events.d.ts | 10 ++-- src/main/ipc/index.ts | 26 ++++---- .../MusicBar/widgets/Extra/index.tsx | 60 ++++++++----------- .../views/toplist-detail-view/index.tsx | 2 - 4 files changed, 44 insertions(+), 54 deletions(-) diff --git a/src/common/ipc-util/eventType/renderer-events.d.ts b/src/common/ipc-util/eventType/renderer-events.d.ts index 3c84fdf5..730c7e39 100644 --- a/src/common/ipc-util/eventType/renderer-events.d.ts +++ b/src/common/ipc-util/eventType/renderer-events.d.ts @@ -23,11 +23,11 @@ declare namespace IpcEvents { "sync-current-repeat-mode": import("@/renderer/core/track-player/enum").RepeatMode; "sync-current-lyric": string; - "send-to-lyric-window": { - // 时序 - timeStamp: number; - lrc: ILyric.IParsedLrcItem[]; - }; + // "send-to-lyric-window": { + // // 时序 + // timeStamp: number; + // lrc: ILyric.IParsedLrcItem[]; + // }; "set-desktop-lyric-lock": boolean; "ignore-mouse-event": { ignore: boolean; diff --git a/src/main/ipc/index.ts b/src/main/ipc/index.ts index ba1def0f..e17711e9 100644 --- a/src/main/ipc/index.ts +++ b/src/main/ipc/index.ts @@ -151,19 +151,19 @@ export default function setupIpcMain() { setLyricWindow(enabled); }); - ipcMainOn("send-to-lyric-window", (data) => { - const lyricWindow = getLyricWindow(); - if (!lyricWindow) { - return; - } - currentMusicInfoStore.setValue((prev) => ({ - ...prev, - lrc: data.lrc, - })); - // syncExtensionData({ - // lrc: data.lrc, - // }); - }); + // ipcMainOn("send-to-lyric-window", (data) => { + // const lyricWindow = getLyricWindow(); + // if (!lyricWindow) { + // return; + // } + // currentMusicInfoStore.setValue((prev) => ({ + // ...prev, + // lrc: data.lrc, + // })); + // // syncExtensionData({ + // // lrc: data.lrc, + // // }); + // }); ipcMainOn("set-desktop-lyric-lock", (lockState) => { setDesktopLyricLock(lockState); diff --git a/src/renderer/components/MusicBar/widgets/Extra/index.tsx b/src/renderer/components/MusicBar/widgets/Extra/index.tsx index 17203b4d..039c842c 100644 --- a/src/renderer/components/MusicBar/widgets/Extra/index.tsx +++ b/src/renderer/components/MusicBar/widgets/Extra/index.tsx @@ -1,23 +1,15 @@ import SvgAsset from "@/renderer/components/SvgAsset"; -import Evt from "@/renderer/core/events"; import "./index.scss"; import SwitchCase from "@/renderer/components/SwitchCase"; import trackPlayer from "@/renderer/core/track-player"; import { RepeatMode } from "@/renderer/core/track-player/enum"; -import { useEffect, useRef, useState } from "react"; +import { useRef, useState } from "react"; import Condition from "@/renderer/components/Condition"; import Slider from "rc-slider"; -import { showContextMenu } from "@/renderer/components/ContextMenu"; -import { toast } from "react-toastify"; import { showModal } from "@/renderer/components/Modal"; import rendererAppConfig from "@/common/app-config/renderer"; -import { ipcRendererInvoke, ipcRendererSend } from "@/common/ipc-util/renderer"; -import { - sendMessageToLyricWindow, - setMainWindowMessagePort, -} from "@/renderer/utils/lrc-window-message-channel"; +import { ipcRendererInvoke } from "@/common/ipc-util/renderer"; import classNames from "@/renderer/utils/classnames"; -import { useLyric } from "@/renderer/core/track-player/player"; import { getCurrentPanel, hidePanel, @@ -274,30 +266,30 @@ function QualityBtn() { function LyricBtn() { const rendererConfig = rendererAppConfig.useAppConfig(); const enableDesktopLyric = rendererConfig?.lyric?.enableDesktopLyric ?? false; - const lyric = useLyric(); + // const lyric = useLyric(); - useEffect(() => { - // 同步歌词 这样写貌似不好 应该用回调 - // TODO: 挪到bootstrap中 - if (enableDesktopLyric) { - // 同步歌词 - if (lyric?.currentLrc) { - const currentLrc = lyric?.currentLrc; - // 同步两句歌词 - ipcRendererSend("send-to-lyric-window", { - timeStamp: Date.now(), - lrc: currentLrc - ? [currentLrc.lrc, lyric.parser.getLyric()[currentLrc.index + 1]] - : [], - }); - } else { - ipcRendererSend("send-to-lyric-window", { - timeStamp: Date.now(), - lrc: [], - }); - } - } - }, [lyric, enableDesktopLyric]); + // useEffect(() => { + // // 同步歌词 这样写貌似不好 应该用回调 + // // TODO: 挪到bootstrap中 + // if (enableDesktopLyric) { + // // 同步歌词 + // if (lyric?.currentLrc) { + // const currentLrc = lyric?.currentLrc; + // // 同步两句歌词 + // ipcRendererSend("send-to-lyric-window", { + // timeStamp: Date.now(), + // lrc: currentLrc + // ? [currentLrc.lrc, lyric.parser.getLyric()[currentLrc.index + 1]] + // : [], + // }); + // } else { + // ipcRendererSend("send-to-lyric-window", { + // timeStamp: Date.now(), + // lrc: [], + // }); + // } + // } + // }, [lyric, enableDesktopLyric]); return (
- +
); } diff --git a/src/renderer/pages/main-page/views/toplist-detail-view/index.tsx b/src/renderer/pages/main-page/views/toplist-detail-view/index.tsx index fb47aded..b8f42b7e 100644 --- a/src/renderer/pages/main-page/views/toplist-detail-view/index.tsx +++ b/src/renderer/pages/main-page/views/toplist-detail-view/index.tsx @@ -1,8 +1,6 @@ -import React from "react"; import useTopListDetail from "./hooks/useTopListDetail"; import { useParams } from "react-router-dom"; import MusicSheetlikeView from "@/renderer/components/MusicSheetlikeView"; -import { RequestStateCode } from "@/common/constant"; export default function TopListDetailView() { const params = useParams(); From 98ffeb31f4adda21d7d2957932bf51bcdd971dd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Sun, 17 Dec 2023 11:08:13 +0800 Subject: [PATCH 30/50] =?UTF-8?q?fix:=20=E6=9C=AC=E5=9C=B0=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E7=BC=96=E7=A0=81=E8=BD=AC=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 61 +++++++++++++++++++++++++++-------------- package.json | 2 ++ src/common/file-util.ts | 51 ++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index b8a5896d..5cc3497b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,9 @@ "hotkeys-js": "^3.12.0", "https-proxy-agent": "^7.0.2", "i18next": "^22.5.1", + "iconv-lite": "^0.6.3", "immer": "^10.0.2", + "jschardet": "^3.0.0", "lodash.shuffle": "^4.2.0", "lodash.throttle": "^4.1.1", "lru-cache": "^10.0.1", @@ -5178,6 +5180,18 @@ "ms": "2.0.0" } }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", @@ -7177,7 +7191,7 @@ }, "node_modules/encoding": { "version": "0.1.13", - "resolved": "https://registry.npmmirror.com/encoding/-/encoding-0.1.13.tgz", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "dev": true, "optional": true, @@ -7185,19 +7199,6 @@ "iconv-lite": "^0.6.2" } }, - "node_modules/encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -9606,12 +9607,11 @@ } }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" @@ -10193,6 +10193,14 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jschardet": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/jschardet/-/jschardet-3.0.0.tgz", + "integrity": "sha512-lJH6tJ77V8Nzd5QWRkFYCLc13a3vADkh3r/Fi8HupZGWk2OVVDfnZP8V/VgQgZ+lzW0kG2UGb5hFgt3V3ndotQ==", + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-2.5.2.tgz", @@ -13686,6 +13694,18 @@ "node": ">= 0.8" } }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmmirror.com/rc/-/rc-1.2.8.tgz", @@ -14471,8 +14491,7 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sass-graph": { "version": "4.0.1", diff --git a/package.json b/package.json index 35f80cf4..cf2726ec 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,9 @@ "hotkeys-js": "^3.12.0", "https-proxy-agent": "^7.0.2", "i18next": "^22.5.1", + "iconv-lite": "^0.6.3", "immer": "^10.0.2", + "jschardet": "^3.0.0", "lodash.shuffle": "^4.2.0", "lodash.throttle": "^4.1.1", "lru-cache": "^10.0.1", diff --git a/src/common/file-util.ts b/src/common/file-util.ts index 24459d0f..67967c24 100644 --- a/src/common/file-util.ts +++ b/src/common/file-util.ts @@ -9,6 +9,8 @@ function getB64Picture(picture: IPicture) { return `data:${picture.format};base64,${picture.data.toString("base64")}`; } +const specialEncoding = ["GB2312"]; + export async function parseLocalMusicItem( filePath: string ): Promise { @@ -16,6 +18,55 @@ export async function parseLocalMusicItem( try { const { common = {} as ICommonTagsResult } = await parseFile(filePath); + const jschardet = await import("jschardet"); + + // 检测编码 + let encoding: string | null = null; + let conf = 0; + const testItems = [common.title, common.artist, common.album]; + + for (const testItem of testItems) { + const testResult = jschardet.detect(testItem, { + minimumThreshold: 0.4, + }); + if (testResult.confidence > conf) { + conf = testResult.confidence; + encoding = testResult.encoding; + } + + if (conf > 0.9) { + break; + } + } + + if (specialEncoding.includes(encoding)) { + const iconv = await import("iconv-lite"); + + if (common.title) { + common.title = iconv.decode( + common.title as unknown as Buffer, + encoding + ); + } + if (common.artist) { + common.artist = iconv.decode( + common.artist as unknown as Buffer, + encoding + ); + } + if (common.artist) { + common.album = iconv.decode( + common.album as unknown as Buffer, + encoding + ); + } + if (common.lyrics) { + common.lyrics = common.lyrics.map((it) => + it ? iconv.decode(it as unknown as Buffer, encoding) : "" + ); + } + } + return { title: common.title ?? path.basename(filePath), artist: common.artist ?? "未知作者", From 40be59bcf1d1182ec41ed9abbe26179e74bb531c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Sun, 17 Dec 2023 11:17:28 +0800 Subject: [PATCH 31/50] =?UTF-8?q?feat:=20=E6=97=A0=E6=B3=95=E6=A3=80?= =?UTF-8?q?=E6=B5=8B=E5=88=B0=E6=A0=87=E7=AD=BE=E7=9A=84=E6=96=87=E4=BB=B6?= =?UTF-8?q?=EF=BC=8C=E6=96=87=E4=BB=B6=E5=90=8D=E4=B8=8D=E5=B8=A6=E5=90=8E?= =?UTF-8?q?=E7=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/file-util.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/common/file-util.ts b/src/common/file-util.ts index 67967c24..532badec 100644 --- a/src/common/file-util.ts +++ b/src/common/file-util.ts @@ -26,6 +26,9 @@ export async function parseLocalMusicItem( const testItems = [common.title, common.artist, common.album]; for (const testItem of testItems) { + if (!testItem) { + continue; + } const testResult = jschardet.detect(testItem, { minimumThreshold: 0.4, }); @@ -68,7 +71,7 @@ export async function parseLocalMusicItem( } return { - title: common.title ?? path.basename(filePath), + title: common.title ?? path.parse(filePath).name, artist: common.artist ?? "未知作者", artwork: common.picture?.[0] ? getB64Picture(common.picture[0]) @@ -80,14 +83,15 @@ export async function parseLocalMusicItem( id: hash, rawLrc: common.lyrics?.join(""), }; - } catch { + } catch (e) { return { - title: path.basename(filePath) || filePath, + title: path.parse(filePath).name || filePath, id: hash, platform: localPluginName, localPath: filePath, url: addFileScheme(filePath), - artist: "-", + artist: "未知作者", + album: '未知专辑' }; } } From ed983e537f9d74774b10e9946130a46e35550b85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Sun, 17 Dec 2023 15:41:10 +0800 Subject: [PATCH 32/50] =?UTF-8?q?feat:=20=E6=90=9C=E7=B4=A2=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Panel/templates/UserVariables/index.tsx | 2 + .../views/local-music-view/index.scss | 4 ++ .../views/local-music-view/index.tsx | 70 +++++++++++++++++-- .../local-music-view/views/album/index.tsx | 19 +++-- .../local-music-view/views/artist/index.tsx | 9 ++- .../local-music-view/views/folder/index.tsx | 14 ++-- .../local-music-view/views/list/index.tsx | 14 ++-- src/types/plugin.d.ts | 2 + 8 files changed, 111 insertions(+), 23 deletions(-) diff --git a/src/renderer/components/Panel/templates/UserVariables/index.tsx b/src/renderer/components/Panel/templates/UserVariables/index.tsx index f9bbb8d2..d460283b 100644 --- a/src/renderer/components/Panel/templates/UserVariables/index.tsx +++ b/src/renderer/components/Panel/templates/UserVariables/index.tsx @@ -43,12 +43,14 @@ export default function (props: IUserVariablesProps) {
{variable.name ?? variable.key} { valueRef.current[variable.key] = ( e.target as HTMLInputElement ).value; }} + placeholder={variable.hint} >
))} diff --git a/src/renderer/pages/main-page/views/local-music-view/index.scss b/src/renderer/pages/main-page/views/local-music-view/index.scss index 26b8f1f5..809ede02 100644 --- a/src/renderer/pages/main-page/views/local-music-view/index.scss +++ b/src/renderer/pages/main-page/views/local-music-view/index.scss @@ -26,6 +26,10 @@ display: flex; align-items: center; + & .search-local-music { + margin-right: 12px; + } + & .list-view-action { width: 2.4rem; height: 2rem; diff --git a/src/renderer/pages/main-page/views/local-music-view/index.tsx b/src/renderer/pages/main-page/views/local-music-view/index.tsx index a255dfd7..83ef7e2f 100644 --- a/src/renderer/pages/main-page/views/local-music-view/index.tsx +++ b/src/renderer/pages/main-page/views/local-music-view/index.tsx @@ -7,12 +7,13 @@ import MusicList from "@/renderer/components/MusicList"; import { showModal } from "@/renderer/components/Modal"; import SvgAsset from "@/renderer/components/SvgAsset"; import { useUserPerference } from "@/renderer/utils/user-perference"; -import { useState } from "react"; +import { useEffect, useState, useTransition } from "react"; import SwitchCase from "@/renderer/components/SwitchCase"; import ListView from "./views/list"; import ArtistView from "./views/artist"; import AlbumView from "./views/album"; import FolderView from "./views/folder"; +import rendererAppConfig from "@/common/app-config/renderer"; enum DisplayView { LIST, @@ -25,8 +26,57 @@ export default function LocalMusicView() { const { t } = useTranslation(); const [displayView, setDisplayView] = useState(DisplayView.LIST); + const localMusicList = localMusicListStore.useValue(); + const [inputSearch, setInputSearch] = useState(""); + const [filterMusicList, setFilterMusicList] = useState< + IMusic.IMusicItem[] | null + >(null); + + const [isPending, startTransition] = useTransition(); + + useEffect(() => { + if (inputSearch.trim() === "") { + setFilterMusicList(null); + } else { + startTransition(() => { + const caseSensitive = rendererAppConfig.getAppConfigPath( + "playMusic.caseSensitiveInSearch" + ); + if (caseSensitive) { + setFilterMusicList( + localMusicListStore + .getValue() + .filter( + (item) => + item.title?.includes(inputSearch) || + item.artist?.includes(inputSearch) || + item.album?.includes(inputSearch) + ) + ); + } else { + const searchText = inputSearch.toLocaleLowerCase(); + setFilterMusicList( + localMusicListStore + .getValue() + .filter( + (item) => + item.title?.toLocaleLowerCase()?.includes(searchText) || + item.artist?.toLocaleLowerCase()?.includes(searchText) || + item.album?.toLocaleLowerCase()?.includes(searchText) + ) + ); + } + }); + } + }, [inputSearch]); + + const finalMusicList = filterMusicList ?? localMusicList; + return ( -
+
本地音乐
+ { + setInputSearch(evt.target.value); + }} + placeholder="搜索本地音乐" + >
- + - + - + - +
diff --git a/src/renderer/pages/main-page/views/local-music-view/views/album/index.tsx b/src/renderer/pages/main-page/views/local-music-view/views/album/index.tsx index 451433e4..87702553 100644 --- a/src/renderer/pages/main-page/views/local-music-view/views/album/index.tsx +++ b/src/renderer/pages/main-page/views/local-music-view/views/album/index.tsx @@ -4,11 +4,18 @@ import { useMemo, useState } from "react"; import groupBy from "@/renderer/utils/groupBy"; import MusicList from "@/renderer/components/MusicList"; -export default function AlbumView() { - const localMusicList = localMusicListStore.useValue(); +interface IProps { + localMusicList: IMusic.IMusicItem[]; +} + +export default function AlbumView(props: IProps) { + const { localMusicList } = props; const [keys, allMusic] = useMemo(() => { - const grouped = groupBy(localMusicList ?? [], (it) => `${it.album} - ${it.artist}`); + const grouped = groupBy( + localMusicList ?? [], + (it) => `${it.album} - ${it.artist}` + ); return [Object.keys(grouped).sort((a, b) => a.localeCompare(b)), grouped]; }, [localMusicList]); @@ -28,15 +35,15 @@ export default function AlbumView() { setSelectedKey(it); }} > - {it.split(' - ')[0]} - {it.split(' - ')[1]} + {it.split(" - ")[0]} + {it.split(" - ")[1]}
))}
{ const grouped = groupBy(localMusicList ?? [], (it) => it.artist); diff --git a/src/renderer/pages/main-page/views/local-music-view/views/folder/index.tsx b/src/renderer/pages/main-page/views/local-music-view/views/folder/index.tsx index 873edb1b..6a267a0c 100644 --- a/src/renderer/pages/main-page/views/local-music-view/views/folder/index.tsx +++ b/src/renderer/pages/main-page/views/local-music-view/views/folder/index.tsx @@ -4,11 +4,17 @@ import { useMemo, useState } from "react"; import groupBy from "@/renderer/utils/groupBy"; import MusicList from "@/renderer/components/MusicList"; -export default function FolderView() { - const localMusicList = localMusicListStore.useValue(); +interface IProps { + localMusicList: IMusic.IMusicItem[]; +} + +export default function FolderView(props: IProps) { + const { localMusicList } = props; const [keys, allMusic] = useMemo(() => { - const grouped = groupBy(localMusicList ?? [], (it) => window.path.dirname(it.$$localPath)); + const grouped = groupBy(localMusicList ?? [], (it) => + window.path.dirname(it.$$localPath) + ); return [Object.keys(grouped).sort((a, b) => a.localeCompare(b)), grouped]; }, [localMusicList]); @@ -36,7 +42,7 @@ export default function FolderView() {
Date: Sun, 17 Dec 2023 16:05:17 +0800 Subject: [PATCH 33/50] =?UTF-8?q?feat:=20=E7=BC=93=E5=AD=98=E7=AE=A1?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ipc-util/eventType/renderer-events.d.ts | 6 +++- src/main/ipc/index.ts | 16 +++++++++++ .../setting-view/routers/Network/index.scss | 8 +++++- .../setting-view/routers/Network/index.tsx | 28 +++++++++++++++++-- 4 files changed, 54 insertions(+), 4 deletions(-) diff --git a/src/common/ipc-util/eventType/renderer-events.d.ts b/src/common/ipc-util/eventType/renderer-events.d.ts index 730c7e39..e9bde830 100644 --- a/src/common/ipc-util/eventType/renderer-events.d.ts +++ b/src/common/ipc-util/eventType/renderer-events.d.ts @@ -46,13 +46,16 @@ declare namespace IpcEvents { key: keyof import("../../app-config/type").IAppConfig["shortCut"]["shortcuts"]; shortCut: string[]; }; + /** 设置代理 */ "set-proxy": { enabled: boolean; host?: string; port?: string; username?: string; password?: string; - } + }; + /** 清空缓存 */ + "clear-cache": undefined; } } @@ -97,5 +100,6 @@ declare namespace IpcInvoke { "set-lyric-window": (show: boolean) => void; /** 主窗口和歌词窗口之间 */ "app-get-path": (pathName: string) => string; + "get-cache-size": () => number; } } diff --git a/src/main/ipc/index.ts b/src/main/ipc/index.ts index e17711e9..98f05f72 100644 --- a/src/main/ipc/index.ts +++ b/src/main/ipc/index.ts @@ -151,6 +151,22 @@ export default function setupIpcMain() { setLyricWindow(enabled); }); + ipcMainOn("clear-cache", () => { + const mainWindow = getMainWindow(); + if (mainWindow) { + mainWindow.webContents.session.clearCache?.(); + } + }); + + ipcMainHandle("get-cache-size", async () => { + const mainWindow = getMainWindow(); + if (mainWindow) { + return await mainWindow.webContents.session.getCacheSize(); + } + + return NaN; + }); + // ipcMainOn("send-to-lyric-window", (data) => { // const lyricWindow = getLyricWindow(); // if (!lyricWindow) { diff --git a/src/renderer/pages/main-page/views/setting-view/routers/Network/index.scss b/src/renderer/pages/main-page/views/setting-view/routers/Network/index.scss index bb55304c..5d472480 100644 --- a/src/renderer/pages/main-page/views/setting-view/routers/Network/index.scss +++ b/src/renderer/pages/main-page/views/setting-view/routers/Network/index.scss @@ -8,10 +8,16 @@ & .proxy-item { display: flex; - + & input { flex: 1; } } } + + & .network-cache-container { + display: flex; + align-items: center; + gap: 24px; + } } diff --git a/src/renderer/pages/main-page/views/setting-view/routers/Network/index.tsx b/src/renderer/pages/main-page/views/setting-view/routers/Network/index.tsx index afc5f530..6bfd0675 100644 --- a/src/renderer/pages/main-page/views/setting-view/routers/Network/index.tsx +++ b/src/renderer/pages/main-page/views/setting-view/routers/Network/index.tsx @@ -2,7 +2,9 @@ import { IAppConfig } from "@/common/app-config/type"; import "./index.scss"; import CheckBoxSettingItem from "../../components/CheckBoxSettingItem"; import InputSettingItem from "../../components/InputSettingItem"; -import { ipcRendererSend } from "@/common/ipc-util/renderer"; +import { ipcRendererInvoke, ipcRendererSend } from "@/common/ipc-util/renderer"; +import { useEffect, useState } from "react"; +import { normalizeFileSize } from "@/common/normalize-util"; interface IProps { data: IAppConfig["network"]; @@ -11,7 +13,15 @@ interface IProps { export default function Network(props: IProps) { const { data = {} as IAppConfig["network"] } = props; - const proxyEnabled = !!data.proxy?.enabled; + const proxyEnabled = !!data.proxy?.enabled; + + const [cacheSize, setCacheSize] = useState(NaN); + + useEffect(() => { + ipcRendererInvoke("get-cache-size").then((res) => { + setCacheSize(res); + }); + }, []); return (
@@ -82,6 +92,20 @@ export default function Network(props: IProps) { }} >
+ +
+ 本地缓存: {isNaN(cacheSize) ? "-" : normalizeFileSize(cacheSize)} +
{ + setCacheSize(0); + ipcRendererSend("clear-cache"); + }} + > + 清空缓存 +
+
); } From 1a2ba7f9516989a8931c355deb6bf82f95952637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Sun, 17 Dec 2023 16:28:19 +0800 Subject: [PATCH 34/50] =?UTF-8?q?feat:=20=E5=A4=96=E9=93=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/Modal/templates/SimpleInputWithState/index.tsx | 4 ++-- .../pages/main-page/views/plugin-manager-view/index.tsx | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/renderer/components/Modal/templates/SimpleInputWithState/index.tsx b/src/renderer/components/Modal/templates/SimpleInputWithState/index.tsx index 6eaa9051..e251a000 100644 --- a/src/renderer/components/Modal/templates/SimpleInputWithState/index.tsx +++ b/src/renderer/components/Modal/templates/SimpleInputWithState/index.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { ReactNode, useState } from "react"; import "./index.scss"; import Base from "../Base"; import useMounted from "@/renderer/hooks/useMounted"; @@ -8,7 +8,7 @@ import Loading from "@/renderer/components/Loading"; interface ISimpleInputWithStateProps { title: string; placeholder?: string; - hints?: string[]; + hints?: ReactNode[]; maxLength?: number; withLoading?: boolean; // 是否需要中间状态 okText?: string; diff --git a/src/renderer/pages/main-page/views/plugin-manager-view/index.tsx b/src/renderer/pages/main-page/views/plugin-manager-view/index.tsx index d3d3d99e..606114fa 100644 --- a/src/renderer/pages/main-page/views/plugin-manager-view/index.tsx +++ b/src/renderer/pages/main-page/views/plugin-manager-view/index.tsx @@ -4,6 +4,7 @@ import "./index.scss"; import { getUserPerference } from "@/renderer/utils/user-perference"; import { ipcRendererInvoke } from "@/common/ipc-util/renderer"; import { toast } from "react-toastify"; +import A from "@/renderer/components/A"; export default function PluginManagerView() { return ( @@ -69,7 +70,7 @@ export default function PluginManagerView() { toast.warn(`安装失败: ${e.message ?? "无效插件"}`); }, hints: [ - "插件需要满足 MusicFree 特定的插件协议,具体可在官方网站中查看", + <>插件需要满足 MusicFree 特定的插件协议,具体可在官方网站中查看 ], }); }} From 5799e94ded4d49ebaf4577c39fa672c5643010ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Sun, 17 Dec 2023 18:09:53 +0800 Subject: [PATCH 35/50] =?UTF-8?q?feat:=20webdav=E5=A4=87=E4=BB=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 258 +++++++++++++++++- package.json | 3 +- src/common/app-config/default-app-config.ts | 1 + src/common/app-config/type.d.ts | 8 +- src/common/constant.ts | 5 +- src/preload/index.ts | 8 +- src/renderer/core/backup-resume/index.ts | 41 +++ .../core/music-sheet/backend/index.ts | 26 ++ .../core/music-sheet/frontend/index.ts | 19 +- .../components/InputSettingItem/index.tsx | 14 +- .../setting-view/routers/Backup/index.scss | 18 +- .../setting-view/routers/Backup/index.tsx | 162 ++++++++++- .../setting-view/routers/Cache/index.scss | 5 - .../setting-view/routers/Cache/index.tsx | 7 - .../setting-view/routers/Network/index.tsx | 1 + src/renderer/utils/create-tmp-file.ts | 21 ++ 16 files changed, 556 insertions(+), 41 deletions(-) create mode 100644 src/renderer/core/backup-resume/index.ts delete mode 100644 src/renderer/pages/main-page/views/setting-view/routers/Cache/index.scss delete mode 100644 src/renderer/pages/main-page/views/setting-view/routers/Cache/index.tsx create mode 100644 src/renderer/utils/create-tmp-file.ts diff --git a/package-lock.json b/package-lock.json index 5cc3497b..15ae9903 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,7 +51,8 @@ "sharp": "^0.32.6", "socket.io": "^4.7.2", "unzipper": "^0.10.14", - "watcher": "^2.3.0" + "watcher": "^2.3.0", + "webdav": "^5.3.1" }, "devDependencies": { "@babel/core": "^7.22.1", @@ -1926,6 +1927,32 @@ "node": ">=6.9.0" } }, + "node_modules/@buttercup/fetch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@buttercup/fetch/-/fetch-0.1.2.tgz", + "integrity": "sha512-mDBtsysQ0Gnrp4FamlRJGpu7HUHwbyLC4uUav1I7QAqThFAa/4d1cdZCxrV5gKvh6zO1fu95bILNJi4Y2hALhQ==", + "optionalDependencies": { + "node-fetch": "^3.3.0" + } + }, + "node_modules/@buttercup/fetch/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "optional": true, + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmmirror.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -5065,6 +5092,11 @@ "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base-64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", + "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==" + }, "node_modules/base32-encode": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/base32-encode/-/base32-encode-1.2.0.tgz", @@ -5337,6 +5369,11 @@ "node": ">=0.2.0" } }, + "node_modules/byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/byte-length/-/byte-length-1.0.2.tgz", + "integrity": "sha512-ovBpjmsgd/teRmgcPh23d4gJvxDoXtAzEL9xTfMU8Yc2kqCDb7L9jAG0XHl1nzuGl+h3ebCIF1i62UFyA9V/2Q==" + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", @@ -5566,6 +5603,14 @@ "node": ">=4" } }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "engines": { + "node": "*" + } + }, "node_modules/cheerio": { "version": "1.0.0-rc.12", "resolved": "https://registry.npmmirror.com/cheerio/-/cheerio-1.0.0-rc.12.tgz", @@ -6094,6 +6139,14 @@ "node": ">=12.10" } }, + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "engines": { + "node": "*" + } + }, "node_modules/crypto-js": { "version": "4.1.1", "resolved": "https://registry.npmmirror.com/crypto-js/-/crypto-js-4.1.1.tgz", @@ -6238,6 +6291,15 @@ "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", "dev": true }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "optional": true, + "engines": { + "node": ">= 12" + } + }, "node_modules/dayjs": { "version": "1.11.9", "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.9.tgz", @@ -8094,6 +8156,27 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-xml-parser": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.2.tgz", + "integrity": "sha512-rmrXUXwbJedoXkStenj1kkljNF7ugn5ZjR9FJcwmCfcCbtOMDghPajbc+Tck6vE6F5XsDmx+Pr2le9fw8+pXBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastq": { "version": "1.15.0", "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.15.0.tgz", @@ -8124,6 +8207,29 @@ "pend": "~1.2.0" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "optional": true, + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -8552,6 +8658,18 @@ "node": ">= 6" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "optional": true, + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", @@ -9304,6 +9422,11 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/hot-patcher": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hot-patcher/-/hot-patcher-2.0.1.tgz", + "integrity": "sha512-ECg1JFG0YzehicQaogenlcs2qg6WsXQsxtnbr1i696u5tLUjtJdQAh0u2g0Q5YV45f263Ta1GnUJsc8WIfJf4Q==" + }, "node_modules/hotkeys-js": { "version": "3.12.0", "resolved": "https://registry.npmmirror.com/hotkeys-js/-/hotkeys-js-3.12.0.tgz", @@ -9815,6 +9938,11 @@ "node": ">= 0.4" } }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz", @@ -10315,6 +10443,11 @@ "shell-quote": "^1.7.3" } }, + "node_modules/layerr": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layerr/-/layerr-2.0.1.tgz", + "integrity": "sha512-z0730CwG/JO24evdORnyDkwG1Q7b7mF2Tp1qRQ0YvrMMARbt1DFG694SOv439Gm7hYKolyZyaB49YIrYIfZBdg==" + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz", @@ -11145,6 +11278,16 @@ "node": ">=10" } }, + "node_modules/md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "dependencies": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, "node_modules/mdn-data": { "version": "2.0.30", "resolved": "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.30.tgz", @@ -11783,6 +11926,11 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/nested-property": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nested-property/-/nested-property-4.0.0.tgz", + "integrity": "sha512-yFehXNWRs4cM0+dz7QxCd06hTbWbSkV0ISsqBfkntU6TOY4Qm3Q88fRRLOddkGh2Qq6dZvnKVAahfhjcUvLnyA==" + }, "node_modules/nice-try": { "version": "1.0.5", "resolved": "https://registry.npmmirror.com/nice-try/-/nice-try-1.0.5.tgz", @@ -11893,6 +12041,25 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "optional": true, + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz", @@ -13207,6 +13374,11 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-posix": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/path-posix/-/path-posix-1.0.0.tgz", + "integrity": "sha512-1gJ0WpNIiYcQydgg3Ed8KzvIqTsDpNwq+cjBCssvBtuTWjEqY1AW+i+OepiEMqDCzyro9B2sLAe4RBPajMYFiA==" + }, "node_modules/path-scurry": { "version": "1.10.1", "resolved": "https://registry.npmmirror.com/path-scurry/-/path-scurry-1.10.1.tgz", @@ -13630,6 +13802,11 @@ "node": ">=0.6" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -14265,8 +14442,7 @@ "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, "node_modules/resolve": { "version": "1.22.4", @@ -15490,6 +15666,11 @@ "node": ">=0.10.0" } }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + }, "node_modules/strtok3": { "version": "7.0.0", "resolved": "https://registry.npmmirror.com/strtok3/-/strtok3-7.0.0.tgz", @@ -16461,6 +16642,23 @@ "punycode": "^2.1.0" } }, + "node_modules/url-join": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz", + "integrity": "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/username": { "version": "5.1.0", "resolved": "https://registry.npmmirror.com/username/-/username-5.1.0.tgz", @@ -16576,6 +16774,60 @@ "defaults": "^1.0.3" } }, + "node_modules/web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "optional": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/webdav": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/webdav/-/webdav-5.3.1.tgz", + "integrity": "sha512-wzZdTHtMuSIXqHGBznc8FM2L94Mc/17Tbn9ppoMybRO0bjWOSIeScdVXWX5qqHsg00EjfiOcwMqGFx6ghIhccQ==", + "dependencies": { + "@buttercup/fetch": "^0.1.1", + "base-64": "^1.0.0", + "byte-length": "^1.0.2", + "fast-xml-parser": "^4.2.4", + "he": "^1.2.0", + "hot-patcher": "^2.0.0", + "layerr": "^2.0.1", + "md5": "^2.3.0", + "minimatch": "^7.4.6", + "nested-property": "^4.0.0", + "path-posix": "^1.0.0", + "url-join": "^5.0.0", + "url-parse": "^1.5.10" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/webdav/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/webdav/node_modules/minimatch": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz", + "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/package.json b/package.json index cf2726ec..8ebd3e77 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,7 @@ "sharp": "^0.32.6", "socket.io": "^4.7.2", "unzipper": "^0.10.14", - "watcher": "^2.3.0" + "watcher": "^2.3.0", + "webdav": "^5.3.1" } } diff --git a/src/common/app-config/default-app-config.ts b/src/common/app-config/default-app-config.ts index ada12b1d..bd98d028 100644 --- a/src/common/app-config/default-app-config.ts +++ b/src/common/app-config/default-app-config.ts @@ -28,6 +28,7 @@ const defaultAppConfig: IDefaultAppConfig = { "shortCut.enableGlobal": false, "download.concurrency": 5, "normal.musicListColumnsShown": [] as any, + "backup.resumeBehavior": "append", } as const; export default defaultAppConfig; diff --git a/src/common/app-config/type.d.ts b/src/common/app-config/type.d.ts index ecff6611..e966c361 100644 --- a/src/common/app-config/type.d.ts +++ b/src/common/app-config/type.d.ts @@ -85,7 +85,13 @@ interface IConfig { }; backup: { - test: never; + /** 恢复歌单时行为 */ + resumeBehavior: 'append' | 'overwrite', + webdav: { + url: string; + username: string; + password: string + } }; /** 本地音乐配置 */ localMusic: { diff --git a/src/common/constant.ts b/src/common/constant.ts index 2a43c68f..d23f1201 100644 --- a/src/common/constant.ts +++ b/src/common/constant.ts @@ -6,7 +6,10 @@ export const internalDataSymbol = Symbol.for("internal"); export const timeStampSymbol = Symbol.for("time-stamp"); // 加入播放列表的辅助顺序 export const sortIndexSymbol = Symbol.for("sort-index"); -/** 歌曲引用次数 */ +/** + * 歌曲引用次数 + * TODO: 没必要算引用 如果真有需要直接取异或就可以了 + */ export const musicRefSymbol = "$$ref"; /** 本地存储路径 */ diff --git a/src/preload/index.ts b/src/preload/index.ts index 43fbe4ff..f100a412 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -1,13 +1,13 @@ // See the Electron documentation for details on how to use preload scripts: -import { contextBridge } from "electron"; +import { app, contextBridge } from "electron"; import ipcRendererDelegate from "./internal/ipc-renderer-delegate"; import fsDelegate from "./internal/fs-delegate"; import themepack from "./internal/themepack"; import path from "path"; import { rimraf } from "rimraf"; import mainPort from "./internal/main-port"; -import utils from './internal/utils'; +import utils from "./internal/utils"; // https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts contextBridge.exposeInMainWorld("ipcRenderer", ipcRendererDelegate); @@ -15,5 +15,5 @@ contextBridge.exposeInMainWorld("fs", fsDelegate); contextBridge.exposeInMainWorld("themepack", themepack); contextBridge.exposeInMainWorld("path", path); contextBridge.exposeInMainWorld("rimraf", rimraf); -contextBridge.exposeInMainWorld('mainPort', mainPort); -contextBridge.exposeInMainWorld('utils', utils); +contextBridge.exposeInMainWorld("mainPort", mainPort); +contextBridge.exposeInMainWorld("utils", utils); diff --git a/src/renderer/core/backup-resume/index.ts b/src/renderer/core/backup-resume/index.ts new file mode 100644 index 00000000..be8aec7b --- /dev/null +++ b/src/renderer/core/backup-resume/index.ts @@ -0,0 +1,41 @@ +import MusicSheet from "../music-sheet"; + +/** + * 恢复 + * @param data 数据 + * @param overwrite 是否覆写歌单 + */ +async function resume(data: string | Record, overwrite?: boolean) { + const dataObj = typeof data === "string" ? JSON.parse(data) : data; + + const currentSheets = MusicSheet.frontend.getAllSheets(); + const allSheets: IMusic.IMusicSheetItem[] = dataObj.musicSheets; + + let importedDefaultSheet; + for (const sheet of allSheets) { + if (overwrite && sheet.id === MusicSheet.defaultSheet.id) { + importedDefaultSheet = sheet; + continue; + } + const newSheet = await MusicSheet.frontend.addSheet(sheet.title); + await MusicSheet.frontend.addMusicToSheet(sheet.musicList, newSheet.id); + } + if (overwrite) { + for (const sheet of currentSheets) { + if (sheet.id === MusicSheet.defaultSheet.id) { + if (importedDefaultSheet) { + await MusicSheet.frontend.clearSheet(MusicSheet.defaultSheet.id); + await MusicSheet.frontend.addMusicToFavorite( + importedDefaultSheet.musicList + ); + } + } + await MusicSheet.frontend.removeSheet(sheet.id); + } + } +} + +const BackupResume = { + resume, +}; +export default BackupResume; diff --git a/src/renderer/core/music-sheet/backend/index.ts b/src/renderer/core/music-sheet/backend/index.ts index f53ac199..82ffe98d 100644 --- a/src/renderer/core/music-sheet/backend/index.ts +++ b/src/renderer/core/music-sheet/backend/index.ts @@ -194,6 +194,32 @@ export async function removeSheet(sheetId: string) { } } +/** + * 清空所有音乐 + * @param sheetId 歌单ID + * @returns 删除后的ID + */ +export async function clearSheet(sheetId: string) { + try { + await musicSheetDB.transaction( + "readwrite", + musicSheetDB.sheets, + musicSheetDB.musicStore, + async () => { + const targetSheet = musicSheets.find((item) => item.id === sheetId); + await removeMusicFromSheet( + targetSheet.musicList ?? ([] as any), + sheetId + ); + targetSheet.musicList = []; + } + ); + return [...musicSheets]; + } catch (e) { + console.log(e); + } +} + /** * 收藏歌单 * @param sheet diff --git a/src/renderer/core/music-sheet/frontend/index.ts b/src/renderer/core/music-sheet/frontend/index.ts index a723dbbc..2648c541 100644 --- a/src/renderer/core/music-sheet/frontend/index.ts +++ b/src/renderer/core/music-sheet/frontend/index.ts @@ -11,6 +11,8 @@ const starredSheetsStore = new Store([]); export const useAllSheets = musicSheetsStore.useValue; export const useAllStarredSheets = starredSheetsStore.useValue; +export const getAllSheets = musicSheetsStore.getValue; + /** 更新默认歌单变化 */ const refreshFavCbs = new Set<() => void>(); function refreshFavoriteState() { @@ -60,8 +62,8 @@ export async function updateSheet( /** * 更新歌单中的歌曲顺序 - * @param sheetId - * @param musicList + * @param sheetId + * @param musicList */ export async function updateSheetMusicOrder( sheetId: string, @@ -94,6 +96,19 @@ export async function removeSheet(sheetId: string) { } catch {} } +/** + * 清空所有音乐 + * @param sheetId 歌单ID + * @returns 删除后的ID + */ +export async function clearSheet(sheetId: string) { + try { + await backend.clearSheet(sheetId); + musicSheetsStore.setValue(backend.getAllSheets()); + refetchSheetDetail(sheetId); + } catch {} +} + /** * 收藏歌单 * @param sheet diff --git a/src/renderer/pages/main-page/views/setting-view/components/InputSettingItem/index.tsx b/src/renderer/pages/main-page/views/setting-view/components/InputSettingItem/index.tsx index 51e547e4..adcadb7f 100644 --- a/src/renderer/pages/main-page/views/setting-view/components/InputSettingItem/index.tsx +++ b/src/renderer/pages/main-page/views/setting-view/components/InputSettingItem/index.tsx @@ -11,7 +11,13 @@ import Loading from "@/renderer/components/Loading"; import { isBasicType } from "@/common/normalize-util"; import useVirtualList from "@/renderer/hooks/useVirtualList"; import { rem } from "@/common/constant"; -import { Fragment, useEffect, useRef, useState } from "react"; +import { + Fragment, + HTMLInputTypeAttribute, + useEffect, + useRef, + useState, +} from "react"; interface InputSettingItemProps { keyPath: T; @@ -22,6 +28,7 @@ interface InputSettingItemProps { /** 是否过滤首尾空格 */ trim?: boolean; disabled?: boolean; + type?: HTMLInputTypeAttribute; } export default function InputSettingItem( @@ -33,7 +40,8 @@ export default function InputSettingItem( value = defaultAppConfig[keyPath], onChange, width, - disabled + type, + disabled, } = props; const [tmpValue, setTmpValue] = useState(null); @@ -52,6 +60,7 @@ export default function InputSettingItem( onChange={(e) => { setTmpValue(e.target.value ?? null); }} + type={type} onBlur={() => { if (tmpValue === null) { return; @@ -68,4 +77,3 @@ export default function InputSettingItem(
); } - diff --git a/src/renderer/pages/main-page/views/setting-view/routers/Backup/index.scss b/src/renderer/pages/main-page/views/setting-view/routers/Backup/index.scss index 1f76e09a..cd9386ee 100644 --- a/src/renderer/pages/main-page/views/setting-view/routers/Backup/index.scss +++ b/src/renderer/pages/main-page/views/setting-view/routers/Backup/index.scss @@ -1,8 +1,14 @@ .setting-view--backup-container { - width: 100%; + width: 100%; - & .backup-row { - display: flex; - gap: 12px; - } -} \ No newline at end of file + & .backup-row { + display: flex; + gap: 12px; + } + + & .webdav-backup-container { + display: grid; + grid-template-columns: 33% 33%; + gap: 18px; + } +} diff --git a/src/renderer/pages/main-page/views/setting-view/routers/Backup/index.tsx b/src/renderer/pages/main-page/views/setting-view/routers/Backup/index.tsx index f3495fb4..b8ebaad8 100644 --- a/src/renderer/pages/main-page/views/setting-view/routers/Backup/index.tsx +++ b/src/renderer/pages/main-page/views/setting-view/routers/Backup/index.tsx @@ -2,10 +2,38 @@ import { ipcRendererInvoke } from "@/common/ipc-util/renderer"; import "./index.scss"; import MusicSheet from "@/renderer/core/music-sheet"; import { toast } from "react-toastify"; +import { IAppConfig } from "@/common/app-config/type"; +import RadioGroupSettingItem from "../../components/RadioGroupSettingItem"; +import rendererAppConfig from "@/common/app-config/renderer"; +import InputSettingItem from "../../components/InputSettingItem"; +import { AuthType, createClient } from "webdav"; +import { createTmpFile } from "@/renderer/utils/create-tmp-file"; +import BackupResume from "@/renderer/core/backup-resume"; + +interface IProps { + data: IAppConfig["backup"]; +} + +export default function Backup(props: IProps) { + const { data } = props; -export default function Backup() { return (
+ +
文件备份
+
WebDAV 备份
+
+ + + +
+
+
{ + try { + if ( + data?.webdav?.url && + data?.webdav?.username && + data?.webdav?.password + ) { + const client = createClient(data.webdav.url, { + authType: AuthType.Password, + username: data.webdav.username, + password: data.webdav.password, + }); + const sheetDetails = + await MusicSheet.frontend.exportAllSheetDetails(); + const backUp = JSON.stringify( + { + musicSheets: sheetDetails, + }, + undefined, + 0 + ); + if (!(await client.exists("/MusicFree"))) { + await client.createDirectory("/MusicFree"); + } + // 临时文件 + await client.putFileContents( + `/MusicFree/MusicFreeBackup.json`, + backUp, + { + overwrite: true, + } + ); + toast.success("备份成功"); + } else { + toast.error("URL、账号、密码不可为空"); + } + } catch (e) { + toast.error(`备份失败: ${e.message}`); + } + }} + > + 备份歌单 +
+
{ + try { + if ( + data?.webdav?.url && + data?.webdav?.username && + data?.webdav?.password + ) { + const client = createClient(data.webdav.url, { + authType: AuthType.Password, + username: data.webdav.username, + password: data.webdav.password, + }); + + if (!(await client.exists("/MusicFree/MusicFreeBackup.json"))) { + throw new Error("备份文件不存在"); + } + const resumeData = await client.getFileContents( + "/MusicFree/MusicFreeBackup.json", + { + format: "text", + } + ); + await BackupResume.resume( + resumeData, + rendererAppConfig.getAppConfigPath( + "backup.resumeBehavior" + ) === "overwrite" + ); + toast.success("恢复成功"); + } else { + toast.error("URL、账号、密码不可为空"); + } + } catch (e) { + toast.error(`恢复失败: ${e.message}`); + } + }} + > + 恢复歌单 +
+
); } diff --git a/src/renderer/pages/main-page/views/setting-view/routers/Cache/index.scss b/src/renderer/pages/main-page/views/setting-view/routers/Cache/index.scss deleted file mode 100644 index c42359a8..00000000 --- a/src/renderer/pages/main-page/views/setting-view/routers/Cache/index.scss +++ /dev/null @@ -1,5 +0,0 @@ -.setting-view--cache-container { - height: 60vh; - width: 50vw; - background-color: goldenrod; -} \ No newline at end of file diff --git a/src/renderer/pages/main-page/views/setting-view/routers/Cache/index.tsx b/src/renderer/pages/main-page/views/setting-view/routers/Cache/index.tsx deleted file mode 100644 index b6b97119..00000000 --- a/src/renderer/pages/main-page/views/setting-view/routers/Cache/index.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import './index.scss'; - -export default function Cache() { - return ( -
PlayMusic
- ) -} diff --git a/src/renderer/pages/main-page/views/setting-view/routers/Network/index.tsx b/src/renderer/pages/main-page/views/setting-view/routers/Network/index.tsx index 6bfd0675..f4ee3ec7 100644 --- a/src/renderer/pages/main-page/views/setting-view/routers/Network/index.tsx +++ b/src/renderer/pages/main-page/views/setting-view/routers/Network/index.tsx @@ -81,6 +81,7 @@ export default function Network(props: IProps) { Date: Sun, 17 Dec 2023 18:12:10 +0800 Subject: [PATCH 36/50] =?UTF-8?q?feat:=20=E9=A2=84=E7=BD=AEwebdav=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/core/plugin-manager/plugin.ts | 257 +++++++++++++------------ 1 file changed, 131 insertions(+), 126 deletions(-) diff --git a/src/main/core/plugin-manager/plugin.ts b/src/main/core/plugin-manager/plugin.ts index cc04bf28..c315bcd3 100644 --- a/src/main/core/plugin-manager/plugin.ts +++ b/src/main/core/plugin-manager/plugin.ts @@ -1,42 +1,43 @@ - -import CryptoJs from 'crypto-js'; -import dayjs from 'dayjs'; -import axios from 'axios'; -import bigInt from 'big-integer'; -import qs from 'qs'; -import * as cheerio from 'cheerio'; -import he from 'he'; -import PluginMethods from './plugin-methods'; -import reactNativeCookies from './polyfill/react-native-cookies'; -import { getAppConfigPathSync } from '@/common/app-config/main'; -import { app } from 'electron'; +import CryptoJs from "crypto-js"; +import dayjs from "dayjs"; +import axios from "axios"; +import bigInt from "big-integer"; +import qs from "qs"; +import * as cheerio from "cheerio"; +import he from "he"; +import PluginMethods from "./plugin-methods"; +import reactNativeCookies from "./polyfill/react-native-cookies"; +import { getAppConfigPathSync } from "@/common/app-config/main"; +import { app } from "electron"; +import * as webdav from "webdav"; axios.defaults.timeout = 15000; const sha256 = CryptoJs.SHA256; export enum PluginStateCode { - /** 版本不匹配 */ - VersionNotMatch = 'VERSION NOT MATCH', - /** 无法解析 */ - CannotParse = 'CANNOT PARSE', + /** 版本不匹配 */ + VersionNotMatch = "VERSION NOT MATCH", + /** 无法解析 */ + CannotParse = "CANNOT PARSE", } const packages: Record = { - cheerio, - 'crypto-js': CryptoJs, - axios, - dayjs, - 'big-integer': bigInt, - qs, - he, - '@react-native-cookies/cookies': reactNativeCookies, + cheerio, + "crypto-js": CryptoJs, + axios, + dayjs, + "big-integer": bigInt, + qs, + he, + "@react-native-cookies/cookies": reactNativeCookies, + webdav, }; const _require = (packageName: string) => { - const pkg = packages[packageName]; - pkg.default = pkg; - return pkg; + const pkg = packages[packageName]; + pkg.default = pkg; + return pkg; }; // const _consoleBind = function ( @@ -59,114 +60,118 @@ const _require = (packageName: string) => { //#region 插件类 export class Plugin { - /** 插件名 */ - public name: string; - /** 插件的hash,作为唯一id */ - public hash: string; - /** 插件状态信息 */ - public stateCode?: PluginStateCode; - /** 插件的实例 */ - public instance: IPlugin.IPluginInstance; - /** 插件路径 */ - public path: string; - /** 插件方法 */ - public methods: PluginMethods; + /** 插件名 */ + public name: string; + /** 插件的hash,作为唯一id */ + public hash: string; + /** 插件状态信息 */ + public stateCode?: PluginStateCode; + /** 插件的实例 */ + public instance: IPlugin.IPluginInstance; + /** 插件路径 */ + public path: string; + /** 插件方法 */ + public methods: PluginMethods; - constructor( - funcCode: string | (() => IPlugin.IPluginInstance), - pluginPath: string, - ) { - let _instance: IPlugin.IPluginInstance; - const _module: any = {exports: {}}; - try { - if (typeof funcCode === 'string') { - // 插件的环境变量 - const env = { - getUserVariables: () => { - return ( - getAppConfigPathSync('private.pluginMeta')?.[this.name]?.userVariables ?? {} - ); - }, - os: process.platform, - appVersion: app.getVersion() - }; - // eslint-disable-next-line no-new-func - _instance = Function(` + constructor( + funcCode: string | (() => IPlugin.IPluginInstance), + pluginPath: string + ) { + let _instance: IPlugin.IPluginInstance; + const _module: any = { exports: {} }; + try { + if (typeof funcCode === "string") { + // 插件的环境变量 + const env = { + getUserVariables: () => { + return ( + getAppConfigPathSync("private.pluginMeta")?.[this.name] + ?.userVariables ?? {} + ); + }, + os: process.platform, + appVersion: app.getVersion(), + }; + // eslint-disable-next-line no-new-func + _instance = Function(` 'use strict'; return function(require, __musicfree_require, module, exports, console, env) { ${funcCode} } - `)()(_require, _require, _module, _module.exports, console, env); - if (_module.exports.default) { - _instance = _module.exports - .default as IPlugin.IPluginInstance; - } else { - _instance = _module.exports as IPlugin.IPluginInstance; - } - } else { - _instance = funcCode(); - } - // 插件初始化后的一些操作 - if (Array.isArray(_instance.userVariables)) { - _instance.userVariables = _instance.userVariables.filter( - it => it?.key, - ); - } - this.checkValid(_instance); - } catch (e: any) { - console.log(e); - this.stateCode = PluginStateCode.CannotParse; - if (e?.stateCode) { - this.stateCode = e.stateCode; - } - - _instance = e?.instance ?? { - _path: '', - platform: '', - appVersion: '', - async getMediaSource() { - return null; - }, - async search() { - return {}; - }, - async getAlbumInfo() { - return null; - }, - }; - } - this.instance = _instance; - this.path = pluginPath; - this.name = _instance.platform; - if ( - this.instance.platform === '' || - this.instance.platform === undefined - ) { - this.hash = ''; + `)()( + _require, + _require, + _module, + _module.exports, + console, + env + ); + if (_module.exports.default) { + _instance = _module.exports.default as IPlugin.IPluginInstance; } else { - if (typeof funcCode === 'string') { - this.hash = sha256(funcCode).toString(); - } else { - this.hash = sha256(funcCode.toString()).toString(); - } + _instance = _module.exports as IPlugin.IPluginInstance; } + } else { + _instance = funcCode(); + } + // 插件初始化后的一些操作 + if (Array.isArray(_instance.userVariables)) { + _instance.userVariables = _instance.userVariables.filter( + (it) => it?.key + ); + } + this.checkValid(_instance); + } catch (e: any) { + console.log(e); + this.stateCode = PluginStateCode.CannotParse; + if (e?.stateCode) { + this.stateCode = e.stateCode; + } - // 放在最后 - this.methods = new PluginMethods(this); + _instance = e?.instance ?? { + _path: "", + platform: "", + appVersion: "", + async getMediaSource() { + return null; + }, + async search() { + return {}; + }, + async getAlbumInfo() { + return null; + }, + }; } - - private checkValid(_instance: IPlugin.IPluginInstance) { - /** 版本号校验 */ - // if ( - // _instance.appVersion && - // !satisfies(DeviceInfo.getVersion(), _instance.appVersion) - // ) { - // throw { - // instance: _instance, - // stateCode: PluginStateCode.VersionNotMatch, - // }; - // } - return true; + this.instance = _instance; + this.path = pluginPath; + this.name = _instance.platform; + if (this.instance.platform === "" || this.instance.platform === undefined) { + this.hash = ""; + } else { + if (typeof funcCode === "string") { + this.hash = sha256(funcCode).toString(); + } else { + this.hash = sha256(funcCode.toString()).toString(); + } } + + // 放在最后 + this.methods = new PluginMethods(this); + } + + private checkValid(_instance: IPlugin.IPluginInstance) { + /** 版本号校验 */ + // if ( + // _instance.appVersion && + // !satisfies(DeviceInfo.getVersion(), _instance.appVersion) + // ) { + // throw { + // instance: _instance, + // stateCode: PluginStateCode.VersionNotMatch, + // }; + // } + return true; + } } //#endregion From dcb85a42bf9521cf39fb5c98036e1744f66a0f8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Sun, 17 Dec 2023 21:48:48 +0800 Subject: [PATCH 37/50] =?UTF-8?q?feat:=20=E6=8F=92=E4=BB=B6=E4=BD=9C?= =?UTF-8?q?=E8=80=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/plugin-table/index.tsx | 10 + src/types/plugin.d.ts | 320 +++++++++--------- 2 files changed, 168 insertions(+), 162 deletions(-) diff --git a/src/renderer/pages/main-page/views/plugin-manager-view/components/plugin-table/index.tsx b/src/renderer/pages/main-page/views/plugin-manager-view/components/plugin-table/index.tsx index 908ed392..24f87588 100644 --- a/src/renderer/pages/main-page/views/plugin-manager-view/components/plugin-table/index.tsx +++ b/src/renderer/pages/main-page/views/plugin-manager-view/components/plugin-table/index.tsx @@ -185,6 +185,13 @@ const columnDef = [ maxSize: 100, size: 100, }), + columnHelper.accessor("author", { + cell: (info) => info.getValue() ?? '未知作者', + header: () => "作者", + maxSize: 100, + minSize: 100, + size: 100, + }), columnHelper.accessor(() => 0, { id: "extra", cell: renderOptions, @@ -262,6 +269,9 @@ export default function PluginTable() { key={cell.id} style={{ width: cell.column.getSize(), + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis' }} > {flexRender(cell.column.columnDef.cell, cell.getContext())} diff --git a/src/types/plugin.d.ts b/src/types/plugin.d.ts index 6b1ba52c..c869c913 100644 --- a/src/types/plugin.d.ts +++ b/src/types/plugin.d.ts @@ -1,164 +1,160 @@ declare namespace IPlugin { - export interface IMediaSourceResult { - headers?: Record; - /** 兜底播放 */ - url?: string; - /** UA */ - userAgent?: string; - /** 音质 */ - quality?: IMusic.IQualityKey; - } - - export interface ISearchResult { - isEnd?: boolean; - data: IMedia.SupportMediaItem[T][]; - } - - export type ISearchResultType = IMedia.SupportMediaType; - - type ISearchFunc = ( - query: string, - page: number, - type: T, - ) => Promise>; - - type IGetArtistWorksFunc = ( - artistItem: IArtist.IArtistItem, - page: number, - type: T, - ) => Promise>; - - interface IUserVariable { - /** 变量键名 */ - key: string; - /** 变量名 */ - name?: string; - /** 提示文案 */ - hint?: string; - } - - interface IAlbumInfoResult { - isEnd?: boolean; - albumItem?: IAlbum.IAlbumItem; - musicList?: IMusic.IMusicItem[]; - } - - interface ISheetInfoResult { - isEnd?: boolean; - sheetItem?: IMusic.IMusicSheetItem; - musicList?: IMusic.IMusicItem[]; - } - - interface ITopListInfoResult { - isEnd?: boolean; - topListItem?: IMusic.IMusicSheetItem, - musicList?: IMusic.IMusicItem[]; - } - - interface IGetRecommendSheetTagsResult { - // 固定的tag - pinned?: IMusic.IMusicSheetItem[]; - data?: IMusic.IMusicSheetGroupItem[]; - } - - interface IPluginDefine { - /** 来源名 */ - platform: string; - /** 匹配的版本号 */ - appVersion?: string; - /** 插件版本 */ - version?: string; - /** 远程更新的url */ - srcUrl?: string; - /** 主键,会被存储到mediameta中 */ - primaryKey?: string[]; - /** 默认搜索类型 */ - defaultSearchType?: IMedia.SupportMediaType; - /** 有效搜索类型 */ - supportedSearchType?: ICommon.SupportMediaType[]; - /** 插件缓存控制 */ - cacheControl?: 'cache' | 'no-cache' | 'no-store'; - /** 用户自定义输入 */ - userVariables?: IUserVariable[]; - /** 提示文本 */ - hints?: Record; - /** 搜索 */ - search?: ISearchFunc; - /** 获取根据音乐信息获取url */ - getMediaSource?: ( - musicItem: IMusic.IMusicItemPartial, - quality: IMusic.IQualityKey, - ) => Promise; - /** 根据主键去查询歌曲信息 */ - getMusicInfo?: ( - musicBase: IMedia.IMediaBase, - ) => Promise | null>; - /** 获取歌词 */ - getLyric?: ( - musicItem: IMusic.IMusicItemPartial, - ) => Promise; - /** 获取专辑信息,里面的歌曲分页 */ - getAlbumInfo?: ( - albumItem: IAlbum.IAlbumItem, - page: number, - ) => Promise; - /** 获取歌单信息,有分页 */ - getMusicSheetInfo?: ( - sheetItem: IMusic.IMusicSheetItem, - page: number, - ) => Promise; - /** 获取作品,有分页 */ - getArtistWorks?: IGetArtistWorksFunc; - /** 导入歌单 */ - // todo: 数据结构应该是IMusicSheetItem - importMusicSheet?: ( - urlLike: string, - ) => Promise; - /** 导入单曲 */ - importMusicItem?: ( - urlLike: string, - ) => Promise; - /** 获取榜单 */ - getTopLists?: () => Promise; - /** 获取榜单详情 */ - getTopListDetail?: ( - topListItem: IMusic.IMusicSheetItem, - page: number - ) => Promise; - /** 获取热门歌单tag */ - getRecommendSheetTags?: () => Promise; - /** 歌单列表 */ - getRecommendSheetsByTag?: ( - tag: ICommon.IUnique, - page?: number, - ) => Promise>; - } - - export interface IPluginInstance extends IPluginDefine { - /** 内部属性 */ - /** 插件路径 */ - _path: string; - } - - type R = Required; - export type IPluginInstanceMethods = { - [K in keyof R as R[K] extends (...args: any) => any ? K : never]: R[K]; - }; - - /** 插件其他属性 */ - export type IPluginMeta = { - order?: number; - userVariables?: Record; - }; - - export type IPluginDelegate = { - // 除去函数 - [K in keyof R as R[K] extends (...args: any) => any ? never : K]: R[K]; - } & { - supportedMethod: string[]; - hash: string; - path: string; - } - - + export interface IMediaSourceResult { + headers?: Record; + /** 兜底播放 */ + url?: string; + /** UA */ + userAgent?: string; + /** 音质 */ + quality?: IMusic.IQualityKey; + } + + export interface ISearchResult { + isEnd?: boolean; + data: IMedia.SupportMediaItem[T][]; + } + + export type ISearchResultType = IMedia.SupportMediaType; + + type ISearchFunc = ( + query: string, + page: number, + type: T + ) => Promise>; + + type IGetArtistWorksFunc = ( + artistItem: IArtist.IArtistItem, + page: number, + type: T + ) => Promise>; + + interface IUserVariable { + /** 变量键名 */ + key: string; + /** 变量名 */ + name?: string; + /** 提示文案 */ + hint?: string; + } + + interface IAlbumInfoResult { + isEnd?: boolean; + albumItem?: IAlbum.IAlbumItem; + musicList?: IMusic.IMusicItem[]; + } + + interface ISheetInfoResult { + isEnd?: boolean; + sheetItem?: IMusic.IMusicSheetItem; + musicList?: IMusic.IMusicItem[]; + } + + interface ITopListInfoResult { + isEnd?: boolean; + topListItem?: IMusic.IMusicSheetItem; + musicList?: IMusic.IMusicItem[]; + } + + interface IGetRecommendSheetTagsResult { + // 固定的tag + pinned?: IMusic.IMusicSheetItem[]; + data?: IMusic.IMusicSheetGroupItem[]; + } + + interface IPluginDefine { + /** 来源名 */ + platform: string; + /** 匹配的版本号 */ + appVersion?: string; + /** 插件版本 */ + version?: string; + /** 远程更新的url */ + srcUrl?: string; + /** 主键,会被存储到mediameta中 */ + primaryKey?: string[]; + /** 默认搜索类型 */ + defaultSearchType?: IMedia.SupportMediaType; + /** 有效搜索类型 */ + supportedSearchType?: ICommon.SupportMediaType[]; + /** 插件缓存控制 */ + cacheControl?: "cache" | "no-cache" | "no-store"; + /** 插件作者 */ + author?: string; + /** 用户自定义输入 */ + userVariables?: IUserVariable[]; + /** 提示文本 */ + hints?: Record; + /** 搜索 */ + search?: ISearchFunc; + /** 获取根据音乐信息获取url */ + getMediaSource?: ( + musicItem: IMusic.IMusicItemPartial, + quality: IMusic.IQualityKey + ) => Promise; + /** 根据主键去查询歌曲信息 */ + getMusicInfo?: ( + musicBase: IMedia.IMediaBase + ) => Promise | null>; + /** 获取歌词 */ + getLyric?: ( + musicItem: IMusic.IMusicItemPartial + ) => Promise; + /** 获取专辑信息,里面的歌曲分页 */ + getAlbumInfo?: ( + albumItem: IAlbum.IAlbumItem, + page: number + ) => Promise; + /** 获取歌单信息,有分页 */ + getMusicSheetInfo?: ( + sheetItem: IMusic.IMusicSheetItem, + page: number + ) => Promise; + /** 获取作品,有分页 */ + getArtistWorks?: IGetArtistWorksFunc; + /** 导入歌单 */ + // todo: 数据结构应该是IMusicSheetItem + importMusicSheet?: (urlLike: string) => Promise; + /** 导入单曲 */ + importMusicItem?: (urlLike: string) => Promise; + /** 获取榜单 */ + getTopLists?: () => Promise; + /** 获取榜单详情 */ + getTopListDetail?: ( + topListItem: IMusic.IMusicSheetItem, + page: number + ) => Promise; + /** 获取热门歌单tag */ + getRecommendSheetTags?: () => Promise; + /** 歌单列表 */ + getRecommendSheetsByTag?: ( + tag: ICommon.IUnique, + page?: number + ) => Promise>; + } + + export interface IPluginInstance extends IPluginDefine { + /** 内部属性 */ + /** 插件路径 */ + _path: string; + } + + type R = Required; + export type IPluginInstanceMethods = { + [K in keyof R as R[K] extends (...args: any) => any ? K : never]: R[K]; + }; + + /** 插件其他属性 */ + export type IPluginMeta = { + order?: number; + userVariables?: Record; + }; + + export type IPluginDelegate = { + // 除去函数 + [K in keyof R as R[K] extends (...args: any) => any ? never : K]: R[K]; + } & { + supportedMethod: string[]; + hash: string; + path: string; + }; } From 8d8e6ca31cfab2a5766f308750f3d37e84d41191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Tue, 19 Dec 2023 09:39:45 +0800 Subject: [PATCH 38/50] =?UTF-8?q?feat:=20=E7=94=A8=E6=88=B7=E5=8F=98?= =?UTF-8?q?=E9=87=8F=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/Panel/templates/UserVariables/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/renderer/components/Panel/templates/UserVariables/index.tsx b/src/renderer/components/Panel/templates/UserVariables/index.tsx index d460283b..412da167 100644 --- a/src/renderer/components/Panel/templates/UserVariables/index.tsx +++ b/src/renderer/components/Panel/templates/UserVariables/index.tsx @@ -41,7 +41,9 @@ export default function (props: IUserVariablesProps) {
{variables.map((variable) => (
- {variable.name ?? variable.key} + + {variable.name ?? variable.key} + Date: Tue, 19 Dec 2023 23:42:34 +0800 Subject: [PATCH 39/50] =?UTF-8?q?fix:=20header=E7=BC=BA=E5=A4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/normalize-util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/normalize-util.ts b/src/common/normalize-util.ts index 2256ddc3..8ad71345 100644 --- a/src/common/normalize-util.ts +++ b/src/common/normalize-util.ts @@ -27,7 +27,7 @@ export function encodeUrlHeaders( for (const key in headers) { formalizedKey = key.toLowerCase(); - _setHeaders[formalizedKey] = headers[formalizedKey]; + _setHeaders[formalizedKey] = headers[key]; } const encodedUrl = new URL(originalUrl); encodedUrl.searchParams.set( From a36dbfff6c5bcf91305c0bd0167f78e405fbe06b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Wed, 20 Dec 2023 01:03:44 +0800 Subject: [PATCH 40/50] =?UTF-8?q?feat:=20=E7=A7=81=E6=9C=89=E6=BA=90?= =?UTF-8?q?=E6=92=AD=E6=94=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/core/track-player/internal.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/renderer/core/track-player/internal.ts b/src/renderer/core/track-player/internal.ts index cab1f778..2cc885e3 100644 --- a/src/renderer/core/track-player/internal.ts +++ b/src/renderer/core/track-player/internal.ts @@ -11,6 +11,7 @@ import Hls from "hls.js"; class TrackPlayerInternal { private audioContext: AudioContext; private audio: HTMLAudioElement; + private hls: Hls; private playerState: PlayerState; private currentMusic: IMusic.IMusicItem; @@ -19,6 +20,8 @@ class TrackPlayerInternal { this.audio = new Audio(); this.audio.preload = "auto"; this.audio.controls = false; + this.hls = new Hls(); + this.hls.attachMedia(this.audio); this.registerEvents(); } @@ -111,9 +114,7 @@ class TrackPlayerInternal { }); // 拓展播放功能 if (getUrlExt(url) === ".m3u8" && Hls.isSupported()) { - const hls = new Hls(); - hls.loadSource(url); - hls.attachMedia(this.audio); + this.hls.loadSource(url); } else { this.audio.src = url; } From c523271e9f0bc25d13f93f934fe79b80b314ffb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Wed, 20 Dec 2023 09:10:11 +0800 Subject: [PATCH 41/50] =?UTF-8?q?feat:=20=E4=B8=8B=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/webworkers/downloader.ts | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/webworkers/downloader.ts b/src/webworkers/downloader.ts index 201b03fd..d4ab0542 100644 --- a/src/webworkers/downloader.ts +++ b/src/webworkers/downloader.ts @@ -71,13 +71,29 @@ async function downloadFile( return; } } catch (e) {} - const _headers = { + const _headers: Record = { ...(mediaSource.headers ?? {}), "user-agent": mediaSource.userAgent, }; try { - const res = await fetch(encodeUrlHeaders(mediaSource.url, _headers)); + const urlObj = new URL(mediaSource.url); + let res: Response; + if (urlObj.username && urlObj.password) { + _headers["Authorization"] = `Basic ${btoa( + `${decodeURIComponent(urlObj.username)}:${decodeURIComponent( + urlObj.password + )}` + )}`; + urlObj.username = ""; + urlObj.password = ""; + res = await fetch(urlObj.toString(), { + headers: _headers, + }); + } else { + res = await fetch(encodeUrlHeaders(mediaSource.url, _headers)); + } + const totalSize = +res.headers.get("content-length"); onStateChange({ state, From 5e1ef1f16d76c1728d9f2374062823d9c8b87d5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Fri, 22 Dec 2023 21:08:11 +0800 Subject: [PATCH 42/50] =?UTF-8?q?fix:=20=E8=AE=BE=E7=BD=AE=E6=88=91?= =?UTF-8?q?=E5=96=9C=E6=AC=A2=E5=90=8E=E4=B8=8D=E6=9B=B4=E6=96=B0=E6=AD=8C?= =?UTF-8?q?=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/core/music-sheet/frontend/index.ts | 9 ++------- src/renderer/core/track-player/internal.ts | 5 +++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/renderer/core/music-sheet/frontend/index.ts b/src/renderer/core/music-sheet/frontend/index.ts index 2648c541..ac77f0f9 100644 --- a/src/renderer/core/music-sheet/frontend/index.ts +++ b/src/renderer/core/music-sheet/frontend/index.ts @@ -156,7 +156,7 @@ export async function addMusicToSheet( // 更新默认列表的状态 refreshFavoriteState(); } - refreshSheetDetailState(sheetId); + refetchSheetDetail(sheetId); } /** 添加到默认歌单 */ @@ -185,7 +185,7 @@ export async function removeMusicFromSheet( // 更新默认列表的状态 refreshFavoriteState(); } - refreshSheetDetailState(sheetId); + refetchSheetDetail(sheetId); } /** 从默认歌单中移除 */ @@ -218,11 +218,6 @@ export function useMusicIsFavorite(musicItem: IMusic.IMusicItem) { return isFav; } -const updateSheetCbs: Map void>> = new Map(); -/** 更新最新的歌单状态 */ -function refreshSheetDetailState(sheetId: string) { - updateSheetCbs.get(sheetId)?.forEach((cb) => cb?.()); -} const updateSheetDetailCallbacks: Map< string, diff --git a/src/renderer/core/track-player/internal.ts b/src/renderer/core/track-player/internal.ts index 2cc885e3..6af4d2d6 100644 --- a/src/renderer/core/track-player/internal.ts +++ b/src/renderer/core/track-player/internal.ts @@ -22,6 +22,11 @@ class TrackPlayerInternal { this.audio.controls = false; this.hls = new Hls(); this.hls.attachMedia(this.audio); + // @ts-ignore + this.hls.on('hlsError', () => { + console.log("???") + this.throwError(ErrorReason.EmptyResource) + }) this.registerEvents(); } From b53a873a8b9efd4424e12e3a99b0089b2f2d6e48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Fri, 22 Dec 2023 21:45:57 +0800 Subject: [PATCH 43/50] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E7=A7=81?= =?UTF-8?q?=E6=9C=89=E6=B5=81=E6=92=AD=E6=94=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/core/track-player/internal.ts | 44 +++++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/src/renderer/core/track-player/internal.ts b/src/renderer/core/track-player/internal.ts index 6af4d2d6..f76a6cc7 100644 --- a/src/renderer/core/track-player/internal.ts +++ b/src/renderer/core/track-player/internal.ts @@ -23,10 +23,10 @@ class TrackPlayerInternal { this.hls = new Hls(); this.hls.attachMedia(this.audio); // @ts-ignore - this.hls.on('hlsError', () => { - console.log("???") - this.throwError(ErrorReason.EmptyResource) - }) + this.hls.on("hlsError", () => { + console.log("???"); + this.throwError(ErrorReason.EmptyResource); + }); this.registerEvents(); } @@ -121,7 +121,41 @@ class TrackPlayerInternal { if (getUrlExt(url) === ".m3u8" && Hls.isSupported()) { this.hls.loadSource(url); } else { - this.audio.src = url; + const urlObj = new URL(trackSource.url); + if (urlObj.username && urlObj.password) { + // TODO: 这部分逻辑需要抽离出来 特殊逻辑 + const mediaSource = new MediaSource(); + this.audio.src = URL.createObjectURL(mediaSource); + mediaSource.addEventListener("sourceopen", () => { + const sourceBuffer = mediaSource.addSourceBuffer("audio/mpeg"); + + const authHeader = `Basic ${btoa( + `${decodeURIComponent(urlObj.username)}:${decodeURIComponent( + urlObj.password + )}` + )}`; + urlObj.username = ""; + urlObj.password = ""; + fetch(urlObj.toString(), { + method: "GET", + headers: { + ...trackSource.headers, + Authorization: authHeader, + }, + }) + .then((res) => res.arrayBuffer()) + .then((buf) => { + sourceBuffer.addEventListener('updateend', () => { + mediaSource.endOfStream(); + }) + sourceBuffer.appendBuffer(buf); + + }); + }); + + } else { + this.audio.src = url; + } } } From 5c5c153731601006210c57b46faa7ac1c7469493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Sat, 23 Dec 2023 11:36:43 +0800 Subject: [PATCH 44/50] =?UTF-8?q?fix:=20=E5=85=9C=E5=BA=95=E6=96=87?= =?UTF-8?q?=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/components/MusicList/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/components/MusicList/index.tsx b/src/renderer/components/MusicList/index.tsx index 0f2d479d..7306367e 100644 --- a/src/renderer/components/MusicList/index.tsx +++ b/src/renderer/components/MusicList/index.tsx @@ -171,11 +171,11 @@ export function showMusicContextMenu( icon: "identification", }, { - title: `作者: ${musicItems.artist}`, + title: `作者: ${musicItems.artist ?? '未知作者'}`, icon: "user", }, { - title: `专辑: ${musicItems.album}`, + title: `专辑: ${musicItems.album ?? '未知专辑'}`, icon: "album", show: !!musicItems.album, }, From 0b8f7566b1f8435241e95981dab50374f9c45d75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Sat, 23 Dec 2023 15:36:54 +0800 Subject: [PATCH 45/50] =?UTF-8?q?feat:=20=E7=A7=BB=E5=8A=A8=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E6=A0=B7=E5=BC=8F=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/renderer/document/index.css | 81 ------------------------------- src/renderer/document/index.scss | 82 ++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 82 deletions(-) diff --git a/package.json b/package.json index 8ebd3e77..2c69010a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "musicfree-desktop", "productName": "MusicFree", - "version": "0.0.2", + "version": "0.0.3", "description": "一个插件化的音乐播放器", "main": ".webpack/main", "scripts": { diff --git a/src/renderer/document/index.css b/src/renderer/document/index.css index babab9ac..551bb1b6 100644 --- a/src/renderer/document/index.css +++ b/src/renderer/document/index.css @@ -33,87 +33,6 @@ body { backdrop-filter: blur(10px); } -.opacity-button { - opacity: 0.6; - - &:hover { - opacity: 1; - } -} - -.highlight { - color: var(--primaryColor) !important; -} - -iframe { - width: 100%; - height: 100%; - border: none; - position: absolute; - top: 0; - left: 0; - overflow: hidden; - pointer-events: none; -} - -input { - outline: none; - font-size: 1rem; - padding: 0.4rem 0.6rem; - border-radius: 2px; - border: 1px solid var(--dividerColor); - box-sizing: border-box; - background: var(--placeholderColor); - color: var(--textColor); - - &::placeholder { - opacity: 0.6; - color: var(--textColor); - } -} - -div[role="button"] { - cursor: pointer; - user-select: none; - - &[data-disabled]:not([data-disabled="false"]) { - cursor: default; - opacity: 0.5; - pointer-events: none; - } - - &[data-type="primaryButton"] { - background-color: var(--primaryColor); - font-size: 1em; - padding: 0.4em 1em; - border-radius: 1.6em; - color: white; - width: fit-content; - } - &[data-type="normalButton"] { - font-size: 1em; - padding: 0.4em 1em; - border-radius: 1.6em; - color: var(--textColor); - border: 1px solid currentColor; - width: fit-content; - } - - &[data-type="dangerButton"] { - font-size: 1em; - padding: 0.4em 1em; - border-radius: 1.6em; - color: var(--dangerColor); - border: 1px solid currentColor; - width: fit-content; - - &[data-fill="true"] { - color: white; - background: var(--dangerColor); - } - } -} - .divider { width: 100%; height: 1px; diff --git a/src/renderer/document/index.scss b/src/renderer/document/index.scss index 422478a4..e290e05f 100644 --- a/src/renderer/document/index.scss +++ b/src/renderer/document/index.scss @@ -47,3 +47,85 @@ table { .Toastify__toast-container--top-right { top: calc(54px + 1rem); } + + +.opacity-button { + opacity: 0.6; + + &:hover { + opacity: 1; + } +} + +.highlight { + color: var(--primaryColor) !important; +} + +iframe { + width: 100%; + height: 100%; + border: none; + position: absolute; + top: 0; + left: 0; + overflow: hidden; + pointer-events: none; +} + +input { + outline: none; + font-size: 1rem; + padding: 0.4rem 0.6rem; + border-radius: 2px; + border: 1px solid var(--dividerColor); + box-sizing: border-box; + background: var(--placeholderColor); + color: var(--textColor); + + &::placeholder { + opacity: 0.6; + color: var(--textColor); + } +} + +div[role="button"] { + cursor: pointer; + user-select: none; + + &[data-disabled]:not([data-disabled="false"]) { + cursor: default; + opacity: 0.5; + pointer-events: none; + } + + &[data-type="primaryButton"] { + background-color: var(--primaryColor); + font-size: 1em; + padding: 0.4em 1em; + border-radius: 1.6em; + color: white; + width: fit-content; + } + &[data-type="normalButton"] { + font-size: 1em; + padding: 0.4em 1em; + border-radius: 1.6em; + color: var(--textColor); + border: 1px solid currentColor; + width: fit-content; + } + + &[data-type="dangerButton"] { + font-size: 1em; + padding: 0.4em 1em; + border-radius: 1.6em; + color: var(--dangerColor); + border: 1px solid currentColor; + width: fit-content; + + &[data-fill="true"] { + color: white; + background: var(--dangerColor); + } + } +} \ No newline at end of file From df5f2a8b2d3b499778e25b1afe944a365280f80a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Sat, 23 Dec 2023 16:26:36 +0800 Subject: [PATCH 46/50] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E6=AD=8C=E6=9B=B2=E8=BF=87=E5=A4=9A=E6=97=B6=E7=9A=84?= =?UTF-8?q?=E6=8B=96=E6=8B=BD=E8=A1=A8=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release/version.json | 42 ++++++++++--------- .../local-music-view/views/list/index.tsx | 6 ++- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/release/version.json b/release/version.json index 95950733..8b701b77 100644 --- a/release/version.json +++ b/release/version.json @@ -1,25 +1,27 @@ { - "version": "0.0.2", + "version": "0.0.3", "changeLog": [ - "1. 【功能】支持播放 .m3u8 源", - "2. 【功能】打开播放列表时锚定到当前正在播放的歌曲", - "3. 【功能】新增搜索歌词功能,你可以在歌曲详情页右键点击,并单击【搜索歌词】功能唤起搜索弹窗", - "4. 【功能】重写了本地歌曲的导入机制,新增本地音乐视图(列表、作者、专辑、文件夹)", - "5. 【功能】新增支持隐藏歌曲列表的部分列", - "6. 【功能】新增快捷键:喜欢/不喜欢歌曲", - "7. 【功能】已经下载的歌曲/本地歌曲支持右键打开", - "8. 【功能, windows】新增缩略图配置,你可以选择在任务栏悬浮图标时展示原窗口或专辑封面", - "9. 【功能, windows】新增任务栏播放控制按钮", - "10. 【优化】优化主题包安装机制:取消原本安装文件夹的机制,修改为安装 .mftheme 或 .zip 的文件,支持批量安装", - "11. 【优化】优化了从热门歌单页详情页返回时的表现", - "12. 【优化】优化了歌曲详情页右键按钮的表现", - "13. 【优化】优化了主窗口和歌词窗口的通信机制", - "14. 【修复】修复作者页歌曲显示不全的问题", - "15. 【修复】修复包含特殊字符时下载失败的问题", - "16. 【修复, macos】修复 macos 图标显示异常的问题", - "17. 【修复, linux】修复 linux 无法最小化的问题", - "18. 【打包】新增 windows 免安装版、mac m1/m2版、linux版", - "19. 【版本号】桌面版后缀取消 -alpha 后缀,以正式版本号发布。" + "1. 【功能】插件支持拖拽排序,该排序会影响到搜索结果、排行榜、热门歌单的展示顺序", + "2. 【功能】播放列表支持多选快捷键(Ctrl + A 全选、按住 Shift 批量选择)", + "3. 【功能】本地音乐新增搜索功能", + "4. 【功能】歌单内歌曲支持拖拽排序", + "5. 【功能】新增了一些插件设置,比如启动软件时自动更新插件", + "6. 【功能】新增缓存设置,可以在设置中清空软件缓存", + "7. 【功能】新增网络代理设置", + "8. 【功能】插件协议更新:新增支持「用户变量」。", + "9. 【功能】插件协议更新:榜单列表支持分页", + "10. 【功能】歌单支持 WebDAV 备份;插件预置的 npm 包新增 webdav,配合 WebDAV 插件即可播放 WebDAV 源", + "11. 【优化】排序/过滤后,点击歌单列表会播放排序/过滤后的歌曲,而非全部歌曲", + "12. 【优化】优化批量删除歌曲失败时的表现", + "13. 【优化】优化歌单内歌曲较多时的体验", + "14. 【优化】优化歌曲名称超长时右下角菜单的显示", + "15. 【优化】加载本地歌曲时会自动识别歌曲元信息的编码,减少出现乱码的可能性", + "16. 【优化】优化 windows7/8 部分按钮的表现", + "17. 【修复】修复 macos、linux 本地歌曲无法播放的问题", + "18. 【修复】修复部分情况下无法右键打开下载文件夹的问题", + "19. 【修复】修复 macos 输入框无法粘贴的问题", + "20. 【修复】修复部分情况下快捷键无法删除的问题", + "21. 【修复】重写了本地歌单逻辑,修复收藏歌单部分情况下无法点击的问题" ], "download": ["https://wwzb.lanzoue.com/b042x0z9e"] } diff --git a/src/renderer/pages/main-page/views/local-music-view/views/list/index.tsx b/src/renderer/pages/main-page/views/local-music-view/views/list/index.tsx index c9aaf8a3..d40a3e2b 100644 --- a/src/renderer/pages/main-page/views/local-music-view/views/list/index.tsx +++ b/src/renderer/pages/main-page/views/local-music-view/views/list/index.tsx @@ -15,7 +15,11 @@ export default function ListView(props: IProps) { }} musicList={localMusicList} virtualProps={{ - fallbackRenderCount: -1, + fallbackRenderCount: 40, + getScrollElement() { + return document.querySelector('#page-container'); + }, + offsetHeight: 102 }} > ); From dd5c5ede649d7bb306a3578bc91e7d1f48b575a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Sat, 23 Dec 2023 16:27:26 +0800 Subject: [PATCH 47/50] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0changelog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- release/version.json | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/release/version.json b/release/version.json index 8b701b77..6f7f7f39 100644 --- a/release/version.json +++ b/release/version.json @@ -16,12 +16,13 @@ "13. 【优化】优化歌单内歌曲较多时的体验", "14. 【优化】优化歌曲名称超长时右下角菜单的显示", "15. 【优化】加载本地歌曲时会自动识别歌曲元信息的编码,减少出现乱码的可能性", - "16. 【优化】优化 windows7/8 部分按钮的表现", - "17. 【修复】修复 macos、linux 本地歌曲无法播放的问题", - "18. 【修复】修复部分情况下无法右键打开下载文件夹的问题", - "19. 【修复】修复 macos 输入框无法粘贴的问题", - "20. 【修复】修复部分情况下快捷键无法删除的问题", - "21. 【修复】重写了本地歌单逻辑,修复收藏歌单部分情况下无法点击的问题" + "16. 【优化】优化本地歌曲过多时的拖拽表现", + "17. 【优化】优化 windows7/8 部分按钮的表现", + "18. 【修复】修复 macos、linux 本地歌曲无法播放的问题", + "19. 【修复】修复部分情况下无法右键打开下载文件夹的问题", + "20. 【修复】修复 macos 输入框无法粘贴的问题", + "21. 【修复】修复部分情况下快捷键无法删除的问题", + "22. 【修复】重写了本地歌单逻辑,修复收藏歌单部分情况下无法点击的问题" ], "download": ["https://wwzb.lanzoue.com/b042x0z9e"] } From e6e1c2655b0f547b176f26b6b09b53fa6c1a69d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Sat, 23 Dec 2023 19:59:51 +0800 Subject: [PATCH 48/50] =?UTF-8?q?fix:=20=E7=99=BD=E5=B1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/core/music-sheet/backend/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/core/music-sheet/backend/index.ts b/src/renderer/core/music-sheet/backend/index.ts index 82ffe98d..4d703f84 100644 --- a/src/renderer/core/music-sheet/backend/index.ts +++ b/src/renderer/core/music-sheet/backend/index.ts @@ -87,7 +87,7 @@ export async function queryAllSheets() { export async function queryAllStarredSheets() { try { starredMusicSheets = await getUserPerferenceIDB("starredMusicSheets"); - return starredMusicSheets; + return starredMusicSheets ?? []; } catch { return []; } From 1459afd9e62a3d49c857e1b59b0b8634c2f0c12b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Sat, 23 Dec 2023 20:03:16 +0800 Subject: [PATCH 49/50] =?UTF-8?q?fix:=20=E6=8E=92=E5=BA=8F=E5=85=9C?= =?UTF-8?q?=E5=BA=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/core/plugin-delegate/internal/methods.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/core/plugin-delegate/internal/methods.ts b/src/renderer/core/plugin-delegate/internal/methods.ts index ef281014..2c971f80 100644 --- a/src/renderer/core/plugin-delegate/internal/methods.ts +++ b/src/renderer/core/plugin-delegate/internal/methods.ts @@ -19,7 +19,7 @@ export function getSupportedPlugin( export function getSortedSupportedPlugin( featureMethod: keyof IPlugin.IPluginInstanceMethods ) { - const meta = rendererAppConfig.getAppConfigPath("private.pluginMeta"); + const meta = rendererAppConfig.getAppConfigPath("private.pluginMeta") ?? {}; return delegatePluginsStore .getValue() .filter((_) => _.supportedMethod.includes(featureMethod)) @@ -43,7 +43,7 @@ export function useSupportedPlugin( export function useSortedSupportedPlugin( featureMethod: keyof IPlugin.IPluginInstanceMethods ) { - const meta = rendererAppConfig.getAppConfigPath("private.pluginMeta"); + const meta = rendererAppConfig.getAppConfigPath("private.pluginMeta") ?? {}; return delegatePluginsStore .useValue() .filter((_) => _.supportedMethod.includes(featureMethod)) From 586201e4116f8ef6ed067ad6a28bf1fb703e0545 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=A4=B4=E7=8C=AB?= Date: Sat, 23 Dec 2023 21:51:02 +0800 Subject: [PATCH 50/50] feat: release 0.0.3 --- changelog.md | 24 ++++++++++++++++++++++++ release/version.json | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index df6ed3d7..f9ce24a7 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,27 @@ +`2023-12.23 v0.0.3` +1. 【功能】插件支持拖拽排序,该排序会影响到搜索结果、排行榜、热门歌单的展示顺序 +2. 【功能】播放列表支持多选快捷键(Ctrl + A 全选、按住 Shift 批量选择) +3. 【功能】本地音乐新增搜索功能 +4. 【功能】歌单内歌曲支持拖拽排序 +5. 【功能】新增了一些插件设置,比如启动软件时自动更新插件 +6. 【功能】新增缓存设置,可以在设置中清空软件缓存 +7. 【功能】新增网络代理设置 +8. 【功能】插件协议更新:新增支持「用户变量」。 +9. 【功能】插件协议更新:榜单列表支持分页 +10. 【功能】歌单支持 WebDAV 备份;插件预置的 npm 包新增 webdav,配合 WebDAV 插件即可播放 WebDAV 源 +11. 【优化】排序/过滤后,点击歌单列表会播放排序/过滤后的歌曲,而非全部歌曲 +12. 【优化】优化批量删除歌曲失败时的表现 +13. 【优化】优化歌单内歌曲较多时的体验 +14. 【优化】优化歌曲名称超长时右下角菜单的显示 +15. 【优化】加载本地歌曲时会自动识别歌曲元信息的编码,减少出现乱码的可能性 +16. 【优化】优化本地歌曲过多时的拖拽表现 +17. 【优化】优化 windows7/8 部分按钮的表现 +18. 【修复】修复 macos、linux 本地歌曲无法播放的问题 +19. 【修复】修复部分情况下无法右键打开下载文件夹的问题 +20. 【修复】修复 macos 输入框无法粘贴的问题 +21. 【修复】修复部分情况下快捷键无法删除的问题 +22. 【修复】重写了本地歌单逻辑,修复收藏歌单部分情况下无法点击的问题 + `2023-11.5 v0.0.2` 1. 【功能】支持播放 .m3u8 源 2. 【功能】打开播放列表时锚定到当前正在播放的歌曲 diff --git a/release/version.json b/release/version.json index 6f7f7f39..da45aea8 100644 --- a/release/version.json +++ b/release/version.json @@ -24,5 +24,5 @@ "21. 【修复】修复部分情况下快捷键无法删除的问题", "22. 【修复】重写了本地歌单逻辑,修复收藏歌单部分情况下无法点击的问题" ], - "download": ["https://wwzb.lanzoue.com/b042x0z9e"] + "download": ["https://wwzb.lanzouu.com/b0432vfef"] }