diff --git a/.babelrc b/.babelrc index ec3e7f01..158e6f87 100644 --- a/.babelrc +++ b/.babelrc @@ -1,5 +1,6 @@ { "presets": [ + "@babel/preset-typescript", [ "@babel/preset-env", { diff --git a/.eslintrc b/.eslintrc index ebbbce6c..24e309d5 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,6 +1,6 @@ { + "root": true, "extends": [ - // "plugin:vue/vue3-recommended", "standard" ], "plugins": [ @@ -8,7 +8,7 @@ ], "parser": "@babel/eslint-parser", "parserOptions": { - "requireConfigFile": false + // "requireConfigFile": false }, "rules": { "no-new": "off", @@ -24,11 +24,94 @@ "standard/no-callback-literal": "off", "prefer-const": "off", "no-labels": "off", - "node/no-callback-literal": "off", - "vue/multi-word-component-names": "off" + "node/no-callback-literal": "off" }, - "settings": { - "html/html-extensions": [".html", ".vue"] - }, - "ignorePatterns": ["vendors", "*.min.js", "dist", "node_modules"] + "ignorePatterns": ["vendors", "*.min.js", "dist"], + "overrides": [ + { + "files": [ "*.vue" ], + "rules": { + "no-new": "off", + "camelcase": "off", + "no-return-assign": "off", + "space-before-function-paren": ["error", "never"], + "no-var": "error", + "no-fallthrough": "off", + "prefer-promise-reject-errors": "off", + "eqeqeq": "off", + "no-multiple-empty-lines": [1, {"max": 2}], + "comma-dangle": [2, "always-multiline"], + "standard/no-callback-literal": "off", + "prefer-const": "off", + "no-labels": "off", + "node/no-callback-literal": "off", + "vue/multi-word-component-names": "off", + "@typescript-eslint/strict-boolean-expressions": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/space-before-function-paren": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/naming-convention": "off", + "vue/max-attributes-per-line": "off", + "vue/singleline-html-element-content-newline": "off", + "vue/use-v-on-exact": "off", + "@typescript-eslint/restrict-template-expressions": "off", + // "no-undef": "off" + }, + "parser": "vue-eslint-parser", + "extends": [ + "plugin:vue/base", + // "plugin:vue/strongly-recommended" + "plugin:vue/vue3-recommended", + "standard-with-typescript", + ], + "parserOptions": { + "sourceType": "module", + "parser": { + // Script parser for ``)) - const randomNum = Math.random().toString().substring(2, 10) - global.modules.userApiWindow.loadURL( - 'data:text/html;charset=UTF-8,' + encodeURIComponent(html - .replace('', - ``) - .replace('', ``))) - - // global.modules.userApiWindow.loadFile(path.join(dir, 'renderer/user-api.html')) - // global.modules.userApiWindow.webContents.openDevTools() -} - -exports.closeWindow = async() => { - if (!global.modules.userApiWindow) return - await Promise.all([ - global.modules.userApiWindow.webContents.session.clearAuthCache(), - global.modules.userApiWindow.webContents.session.clearStorageData(), - global.modules.userApiWindow.webContents.session.clearCache(), - ]) - global.modules.userApiWindow.destroy() - global.modules.userApiWindow = null -} diff --git a/src/main/modules/userApi/main.ts b/src/main/modules/userApi/main.ts new file mode 100644 index 00000000..60d3d79c --- /dev/null +++ b/src/main/modules/userApi/main.ts @@ -0,0 +1,116 @@ +import { mainSend } from '@common/mainIpc' +import { BrowserWindow } from 'electron' +import fs from 'fs' +import { join } from 'path' +import { openDevTools as handleOpenDevTools } from '@main/utils' +import { encodePath, isWin } from '@common/utils' + +let browserWindow: Electron.BrowserWindow | null = null + +let html: string | null = null +let dir: string | null = null + +const denyEvents = [ + 'will-navigate', + 'will-redirect', + 'will-attach-webview', + 'will-prevent-unload', + 'media-started-playing', +] as const + +const winEvent = () => { + if (!browserWindow) return + browserWindow.on('closed', () => { + browserWindow = null + }) +} + +export const createWindow = async(userApi: LX.UserApi.UserApiInfo) => { + await closeWindow() + if (!dir) dir = global.isDev ? webpackUserApiPath : join(encodePath(isWin ? __dirname.replaceAll('\\', '/') : __dirname), 'userApi') + + if (!html) { + html = await fs.promises.readFile(join(dir, 'renderer/user-api.html'), 'utf8') + } + const preloadUrl = global.isDev + ? `${join(encodePath(isWin ? __dirname.replaceAll('\\', '/') : __dirname), '../dist/user-api-preload.js')}` + : `${join(encodePath(isWin ? __dirname.replaceAll('\\', '/') : __dirname), 'user-api-preload.js')}` + // console.log(preloadUrl) + + /** + * Initial window options + */ + browserWindow = new BrowserWindow({ + // enableRemoteModule: false, + resizable: false, + minimizable: false, + maximizable: false, + fullscreenable: false, + show: false, + webPreferences: { + contextIsolation: true, + // worldSafeExecuteJavaScript: true, + nodeIntegration: false, + nodeIntegrationInWorker: false, + + spellcheck: false, + autoplayPolicy: 'document-user-activation-required', + enableWebSQL: false, + disableDialogs: true, + // nativeWindowOpen: false, + webgl: false, + images: false, + + preload: preloadUrl, + }, + }) + + for (const eventName of denyEvents) { + // @ts-expect-error + browserWindow.webContents.on(eventName, (event: Electron.Event) => { + event.preventDefault() + }) + } + browserWindow.webContents.session.setPermissionRequestHandler((webContents, permission, resolve) => { + if (webContents === browserWindow?.webContents) return resolve(true) + + resolve(false) + }) + browserWindow.webContents.setWindowOpenHandler(() => { + return { action: 'deny' } + }) + + winEvent() + + // console.log(html.replace('', ``)) + const randomNum = Math.random().toString().substring(2, 10) + await browserWindow.loadURL( + 'data:text/html;charset=UTF-8,' + encodeURIComponent(html + .replace('', + ``) + .replace('', ``))) + + // global.modules.userApiWindow.loadFile(join(dir, 'renderer/user-api.html')) + // global.modules.userApiWindow.webContents.openDevTools() +} + +export const closeWindow = async() => { + if (!browserWindow) return + await Promise.all([ + browserWindow.webContents.session.clearAuthCache(), + browserWindow.webContents.session.clearStorageData(), + browserWindow.webContents.session.clearCache(), + ]) + browserWindow.destroy() + browserWindow = null +} + +export const sendEvent = (name: string, params?: T) => { + if (!browserWindow) return + mainSend(browserWindow, name, params) +} + +export const openDevTools = () => { + if (!browserWindow) return + handleOpenDevTools(browserWindow.webContents) +} diff --git a/src/main/modules/userApi/rendererEvent/rendererEvent.js b/src/main/modules/userApi/rendererEvent/rendererEvent.js deleted file mode 100644 index e57088cc..00000000 --- a/src/main/modules/userApi/rendererEvent/rendererEvent.js +++ /dev/null @@ -1,98 +0,0 @@ -const { mainOn, mainSend } = require('@common/ipc') - -const USER_API_RENDERER_EVENT_NAME = require('../rendererEvent/name') -const { createWindow } = require('../main') -const { getUserApis } = require('../utils') -const { openDevTools } = require('@main/utils') - -let userApi -let status = { status: true } -const requestQueue = new Map() -const timeouts = {} - -const handleInit = (event, { status, message, data: apiInfo }) => { - // console.log('inited') - if (!status) { - console.log('init failed:', message) - global.lx_event.userApi.status(status = { status: false, apiInfo: userApi, message }) - return - } - global.lx_event.userApi.status(status = { status: true, apiInfo: { ...userApi, sources: apiInfo.sources } }) -} -const handleResponse = (event, { status, data: { requestKey, result }, message }) => { - const request = requestQueue.get(requestKey) - if (!request) return - requestQueue.delete(requestKey) - clearTimeout(timeouts[requestKey]) - delete timeouts[requestKey] - if (status) { - request[0](result) - } else { - request[1](new Error(message)) - } -} -const handleOpenDevTools = () => { - if (global.modules.userApiWindow) { - openDevTools(global.modules.userApiWindow.webContents) - } -} -const handleShowUpdateAlert = (event, { data }) => { - if (!userApi.allowShowUpdateAlert) return - global.lx_event.userApi.showUpdateAlert({ - name: userApi.name, - description: userApi.description, - log: data.log, - updateUrl: data.updateUrl, - }) -} -mainOn(USER_API_RENDERER_EVENT_NAME.init, handleInit) -mainOn(USER_API_RENDERER_EVENT_NAME.response, handleResponse) -mainOn(USER_API_RENDERER_EVENT_NAME.openDevTools, handleOpenDevTools) -mainOn(USER_API_RENDERER_EVENT_NAME.showUpdateAlert, handleShowUpdateAlert) - -exports.loadApi = async apiId => { - if (!apiId) return global.lx_event.userApi.status(status = { status: false, message: 'api id is null' }) - userApi = getUserApis().find(api => api.id == apiId) - console.log('load api', userApi.name) - await createWindow(userApi) - // if (!userApi) return global.lx_event.userApi.status(status = { status: false, message: 'api script is not found' }) - // if (!global.modules.userApiWindow) { - // global.lx_event.userApi.status(status = { status: false, message: 'user api runtime is not defined' }) - // throw new Error('user api window is not defined') - // } - - // // const path = require('path') - // // // eslint-disable-next-line no-undef - // // userApi.script = require('fs').readFileSync(path.join(global.isDev ? __userApi : __dirname, 'renderer/test-api.js')).toString() - // console.log('load api', userApi.name) - // mainSend(global.modules.userApiWindow, USER_API_RENDERER_EVENT_NAME.init, { userApi }) -} - -exports.cancelRequest = requestKey => { - if (!requestQueue.has(requestKey)) return - const request = requestQueue.get(requestKey) - request[1](new Error('Cancel request')) - requestQueue.delete(requestKey) - clearTimeout(timeouts[requestKey]) - delete timeouts[requestKey] -} - -exports.request = ({ requestKey, data }) => new Promise((resolve, reject) => { - if (!userApi) return reject(new Error('user api is not load')) - - // const requestKey = `request__${Math.random().toString().substring(2)}` - - timeouts[requestKey] = setTimeout(() => { - exports.cancelRequest(requestKey) - }, 20000) - - requestQueue.set(requestKey, [resolve, reject, data]) - mainSend(global.modules.userApiWindow, USER_API_RENDERER_EVENT_NAME.request, { requestKey, data }) -}) - -exports.getStatus = () => status - -exports.setAllowShowUpdateAlert = (id, enable) => { - if (!userApi || userApi.id != id) return - userApi.allowShowUpdateAlert = enable -} diff --git a/src/main/modules/userApi/rendererEvent/rendererEvent.ts b/src/main/modules/userApi/rendererEvent/rendererEvent.ts new file mode 100644 index 00000000..b87eba5b --- /dev/null +++ b/src/main/modules/userApi/rendererEvent/rendererEvent.ts @@ -0,0 +1,148 @@ +import { mainOn } from '@common/mainIpc' + +import USER_API_RENDERER_EVENT_NAME from './name' +import { createWindow, openDevTools, sendEvent } from '../main' +import { getUserApis } from '../utils' +import { sendShowUpdateAlert, sendStatusChange } from '@main/modules/winMain' + +let userApi: LX.UserApi.UserApiInfo +let apiStatus: LX.UserApi.UserApiStatus = { status: true } +const requestQueue = new Map() +const timeouts: Map = new Map() +interface InitParams { + params: { + status: boolean + message: string + data: LX.UserApi.UserApiInfo + } +} +interface ResponseParams { + params: { + status: boolean + message: string + data: { + requestKey: string + result: any + } + } +} +interface UpdateInfoParams { + params: { + data: { + log: string + updateUrl: string + } + } +} + +export const init = () => { + const handleInit = ({ params: { status, message, data: apiInfo } }: InitParams) => { + // console.log('inited') + // if (!status) { + // console.log('init failed:', message) + // global.lx_event.userApi.status(status = { status: true, apiInfo: { ...userApi, sources: apiInfo.sources } }) + // return + // } + apiStatus = status + ? { status: true, apiInfo: { ...userApi, sources: apiInfo.sources } } + : { status: false, apiInfo: userApi, message } + sendStatusChange(apiStatus) + } + const handleResponse = ({ params: { status, data: { requestKey, result }, message } }: ResponseParams) => { + const request = requestQueue.get(requestKey) + if (!request) return + requestQueue.delete(requestKey) + clearRequestTimeout(requestKey) + if (status) { + request[0](result) + } else { + request[1](new Error(message)) + } + } + const handleOpenDevTools = () => { + openDevTools() + } + const handleShowUpdateAlert = ({ params: { data } }: UpdateInfoParams) => { + if (!userApi.allowShowUpdateAlert) return + sendShowUpdateAlert({ + name: userApi.name, + description: userApi.description, + log: data.log, + updateUrl: data.updateUrl, + }) + } + mainOn(USER_API_RENDERER_EVENT_NAME.init, handleInit) + mainOn(USER_API_RENDERER_EVENT_NAME.response, handleResponse) + mainOn(USER_API_RENDERER_EVENT_NAME.openDevTools, handleOpenDevTools) + mainOn(USER_API_RENDERER_EVENT_NAME.showUpdateAlert, handleShowUpdateAlert) +} + +export const clearRequestTimeout = (requestKey: string) => { + const timeout = timeouts.get(requestKey) + if (timeout) { + clearTimeout(timeout) + timeouts.delete(requestKey) + } +} + +export const loadApi = async(apiId: string) => { + if (!apiId) { + apiStatus = { status: false, message: 'api id is null' } + sendStatusChange(apiStatus) + return + } + const targetApi = getUserApis().find(api => api.id == apiId) + if (!targetApi) throw new Error('api not found') + userApi = targetApi + console.log('load api', userApi.name) + await createWindow(userApi) + // if (!userApi) return global.lx_event.userApi.status(status = { status: false, message: 'api script is not found' }) + // if (!global.modules.userApiWindow) { + // global.lx_event.userApi.status(status = { status: false, message: 'user api runtime is not defined' }) + // throw new Error('user api window is not defined') + // } + + // // const path = require('path') + // // // eslint-disable-next-line no-undef + // // userApi.script = require('fs').readFileSync(join(global.isDev ? __userApi : __dirname, 'renderer/test-api.js')).toString() + // console.log('load api', userApi.name) + // mainSend(global.modules.userApiWindow, USER_API_RENDERER_EVENT_NAME.init, { userApi }) +} + +export const cancelRequest = (requestKey: string) => { + if (!requestQueue.has(requestKey)) return + const request = requestQueue.get(requestKey) + request[1](new Error('Cancel request')) + requestQueue.delete(requestKey) + clearRequestTimeout(requestKey) +} + +export const request = async({ requestKey, data }: LX.UserApi.UserApiRequestParams): Promise => await new Promise((resolve, reject) => { + if (!userApi) return reject(new Error('user api is not load')) + + // const requestKey = `request__${Math.random().toString().substring(2)}` + const timeout = timeouts.get(requestKey) + if (timeout) { + clearTimeout(timeout) + timeouts.delete(requestKey) + cancelRequest(requestKey) + } + + timeouts.set(requestKey, setTimeout(() => { + cancelRequest(requestKey) + }, 20000)) + + requestQueue.set(requestKey, [resolve, reject, data]) + sendRequest({ requestKey, data }) +}) + +export const getStatus = (): LX.UserApi.UserApiStatus => apiStatus + +export const setAllowShowUpdateAlert = (id: string, enable: boolean) => { + if (!userApi || userApi.id != id) return + userApi.allowShowUpdateAlert = enable +} + +export const sendRequest = (reqData: { requestKey: string, data: any }) => { + sendEvent(USER_API_RENDERER_EVENT_NAME.request, reqData) +} diff --git a/src/main/modules/userApi/utils/index.js b/src/main/modules/userApi/utils.ts similarity index 58% rename from src/main/modules/userApi/utils/index.js rename to src/main/modules/userApi/utils.ts index 7d8de509..c07af513 100644 --- a/src/main/modules/userApi/utils/index.js +++ b/src/main/modules/userApi/utils.ts @@ -1,12 +1,13 @@ -const { userApis: defaultUserApis } = require('../config') -const getStore = require('@common/store') +import { userApis as defaultUserApis } from './config' +import { STORE_NAMES } from '@common/constants' +import getStore from '@main/utils/store' -let userApis +let userApis: LX.UserApi.UserApiInfo[] | null -exports.getUserApis = () => { - const electronStore_userApi = getStore('userApi') +export const getUserApis = (): LX.UserApi.UserApiInfo[] => { + const electronStore_userApi = getStore(STORE_NAMES.USER_API) if (userApis) return userApis - userApis = electronStore_userApi.get('userApis') + userApis = electronStore_userApi.get('userApis') as LX.UserApi.UserApiInfo[] if (!userApis) { userApis = defaultUserApis electronStore_userApi.set('userApis', userApis) @@ -17,7 +18,7 @@ exports.getUserApis = () => { return userApis } -exports.importApi = script => { +export const importApi = (script: string): LX.UserApi.UserApiInfo => { let scriptInfo = script.split(/\r?\n/) let name = scriptInfo[1] || '' let description = scriptInfo[2] || '' @@ -32,24 +33,26 @@ exports.importApi = script => { script, allowShowUpdateAlert: true, } + if (!userApis) userApis = [] userApis.push(apiInfo) - getStore('userApi').set('userApis', userApis) + getStore(STORE_NAMES.USER_API).set('userApis', userApis) return apiInfo } -exports.removeApi = ids => { +export const removeApi = (ids: string[]) => { + if (!userApis) return for (let index = userApis.length - 1; index > -1; index--) { if (ids.includes(userApis[index].id)) { userApis.splice(index, 1) ids.splice(index, 1) } } - getStore('userApi').set('userApis', userApis) + getStore(STORE_NAMES.USER_API).set('userApis', userApis) } -exports.setAllowShowUpdateAlert = (id, enable) => { - const targetApi = userApis.find(api => api.id == id) +export const setAllowShowUpdateAlert = (id: string, enable: boolean) => { + const targetApi = userApis?.find(api => api.id == id) if (!targetApi) return targetApi.allowShowUpdateAlert = enable - getStore('userApi').set('userApis', userApis) + getStore(STORE_NAMES.USER_API).set('userApis', userApis) } diff --git a/src/main/modules/winLyric/config.ts b/src/main/modules/winLyric/config.ts new file mode 100644 index 00000000..47c03134 --- /dev/null +++ b/src/main/modules/winLyric/config.ts @@ -0,0 +1,70 @@ +import { isLinux } from '@common/utils' +import { closeWindow, createWindow, getBounds, isExistWindow, alwaysOnTopTools, setBounds, setIgnoreMouseEvents } from './main' +import { sendConfigChange } from './rendererEvent' +import { buildLyricConfig, getLyricWindowBounds, watchConfigKeys } from './utils' + +let isLock: boolean +let isEnable: boolean +let isAlwaysOnTop: boolean +let isAlwaysOnTopLoop: boolean +let isLockScreen: boolean +let isHoverHide: boolean + + +export const setLrcConfig = (keys: Array, setting: Partial) => { + if (!watchConfigKeys.some(key => keys.includes(key))) return + + if (isExistWindow()) { + sendConfigChange(buildLyricConfig(setting)) + if (keys.includes('desktopLyric.isLock') && isLock != global.lx.appSetting['desktopLyric.isLock']) { + isLock = global.lx.appSetting['desktopLyric.isLock'] + if (global.lx.appSetting['desktopLyric.isLock']) { + setIgnoreMouseEvents(true, { forward: !isLinux && global.lx.appSetting['desktopLyric.isHoverHide'] }) + } else { + setIgnoreMouseEvents(false, { forward: !isLinux && global.lx.appSetting['desktopLyric.isHoverHide'] }) + } + } + if (keys.includes('desktopLyric.isHoverHide') && isHoverHide != global.lx.appSetting['desktopLyric.isHoverHide']) { + isHoverHide = global.lx.appSetting['desktopLyric.isHoverHide'] + if (!isLinux) { + setIgnoreMouseEvents(global.lx.appSetting['desktopLyric.isLock'], { forward: global.lx.appSetting['desktopLyric.isHoverHide'] }) + } + } + if (keys.includes('desktopLyric.isAlwaysOnTop') && isAlwaysOnTop != global.lx.appSetting['desktopLyric.isAlwaysOnTop']) { + isAlwaysOnTop = global.lx.appSetting['desktopLyric.isAlwaysOnTop'] + alwaysOnTopTools.setAlwaysOnTop(global.lx.appSetting['desktopLyric.isAlwaysOnTopLoop']) + if (isAlwaysOnTop && global.lx.appSetting['desktopLyric.isAlwaysOnTopLoop']) { + alwaysOnTopTools.startLoop() + } else alwaysOnTopTools.clearLoop() + } + if (keys.includes('desktopLyric.isAlwaysOnTopLoop') && isAlwaysOnTopLoop != global.lx.appSetting['desktopLyric.isAlwaysOnTopLoop']) { + isAlwaysOnTopLoop = global.lx.appSetting['desktopLyric.isAlwaysOnTopLoop'] + if (!global.lx.appSetting['desktopLyric.isAlwaysOnTop']) return + if (isAlwaysOnTopLoop) { + alwaysOnTopTools.startLoop() + } else { + alwaysOnTopTools.clearLoop() + } + } + if (keys.includes('desktopLyric.isLockScreen') && isLockScreen != global.lx.appSetting['desktopLyric.isLockScreen']) { + isLockScreen = global.lx.appSetting['desktopLyric.isLockScreen'] + if (global.lx.appSetting['desktopLyric.isLockScreen']) { + setBounds(getLyricWindowBounds(getBounds(), { + x: null, + y: 0, + w: global.lx.appSetting['desktopLyric.width'], + h: global.lx.appSetting['desktopLyric.height'], + })) + } + } + } + if (keys.includes('desktopLyric.enable') && isEnable != global.lx.appSetting['desktopLyric.enable']) { + isEnable = global.lx.appSetting['desktopLyric.enable'] + if (global.lx.appSetting['desktopLyric.enable']) { + createWindow() + } else { + alwaysOnTopTools.clearLoop() + closeWindow() + } + } +} diff --git a/src/main/modules/winLyric/event.js b/src/main/modules/winLyric/event.js deleted file mode 100644 index 09e6bcc3..00000000 --- a/src/main/modules/winLyric/event.js +++ /dev/null @@ -1,126 +0,0 @@ -const { common: COMMON_EVENT_NAME, winLyric: WIN_LYRIC_EVENT_NAME, hotKey: HOT_KEY_EVENT_NAME, mainWindow: MAIN_WINDOW_EVENT_NAME } = require('../../events/_name') -const { mainSend, NAMES: { winLyric: ipcWinLyricNames } } = require('../../../common/ipc') -const { desktop_lyric } = require('../../../common/hotKey') -const { getLyricWindowBounds } = require('./utils') -const { isLinux } = require('@common/utils') - -let isLock = null -let isEnable = null -let isAlwaysOnTop = null -let isAlwaysOnTopLoop = null -let isLockScreen = null -let isHoverHide = null - -const alwaysOnTopTools = { - timeout: null, - alwaysOnTop: false, - setAlwaysOnTop(flag, isLoop) { - this.alwaysOnTop = flag - this.clearLoop() - global.modules.lyricWindow.setAlwaysOnTop(flag, 'screen-saver') - console.log(isLoop) - if (flag && isLoop) this.startLoop() - }, - startLoop() { - if (!this.alwaysOnTop) return - this.timeout = setInterval(() => { - if (!global.modules.lyricWindow) return this.clearLoop() - global.modules.lyricWindow.setAlwaysOnTop(true, 'screen-saver') - }, 1000) - }, - clearLoop() { - if (!this.timeout) return - clearInterval(this.timeout) - this.timeout = null - }, -} - - -const setLrcConfig = () => { - let desktopLyric = global.appSetting.desktopLyric - if (global.modules.lyricWindow) { - mainSend(global.modules.lyricWindow, ipcWinLyricNames.set_lyric_config, { - config: desktopLyric, - languageId: global.appSetting.langId, - isShowLyricTranslation: global.appSetting.player.isShowLyricTranslation, - isShowLyricRoma: global.appSetting.player.isShowLyricRoma, - isPlayLxlrc: global.appSetting.player.isPlayLxlrc, - }) - if (isLock != desktopLyric.isLock) { - isLock = desktopLyric.isLock - if (desktopLyric.isLock) { - global.modules.lyricWindow.setIgnoreMouseEvents(true, { forward: !isLinux && global.appSetting.desktopLyric.isHoverHide }) - } else { - global.modules.lyricWindow.setIgnoreMouseEvents(false, { forward: !isLinux && global.appSetting.desktopLyric.isHoverHide }) - } - } - if (isHoverHide != desktopLyric.isHoverHide) { - isHoverHide = desktopLyric.isHoverHide - if (!isLinux) { - global.modules.lyricWindow.setIgnoreMouseEvents(desktopLyric.isLock, { forward: global.appSetting.desktopLyric.isHoverHide }) - } - } - if (isAlwaysOnTop != desktopLyric.isAlwaysOnTop) { - isAlwaysOnTop = desktopLyric.isAlwaysOnTop - alwaysOnTopTools.setAlwaysOnTop(desktopLyric.isAlwaysOnTop, desktopLyric.isAlwaysOnTopLoop) - } - if (isAlwaysOnTopLoop != desktopLyric.isAlwaysOnTopLoop) { - isAlwaysOnTopLoop = desktopLyric.isAlwaysOnTopLoop - if (isAlwaysOnTopLoop) { - alwaysOnTopTools.startLoop() - } else { - alwaysOnTopTools.clearLoop() - } - } - if (isLockScreen != desktopLyric.isLockScreen) { - isLockScreen = desktopLyric.isLockScreen - if (desktopLyric.isLockScreen) { - global.modules.lyricWindow.setBounds(getLyricWindowBounds(global.modules.lyricWindow.getBounds(), { - x: null, - y: null, - w: desktopLyric.width, - h: desktopLyric.height, - })) - } - } - } - if (isEnable != desktopLyric.enable) { - isEnable = desktopLyric.enable - if (desktopLyric.enable) { - global.lx_event.winLyric.create() - } else { - alwaysOnTopTools.clearLoop() - global.lx_event.winLyric.close() - } - } -} -global.lx_event.common.on(COMMON_EVENT_NAME.configStatus, name => { - if (WIN_LYRIC_EVENT_NAME.name === name) return - setLrcConfig() -}) - -global.lx_event.mainWindow.on(MAIN_WINDOW_EVENT_NAME.setLyricInfo, info => { - if (!global.modules.lyricWindow) return - mainSend(global.modules.lyricWindow, ipcWinLyricNames.set_lyric_info, info) -}) - -global.lx_event.hotKey.on(HOT_KEY_EVENT_NAME.keyDown, ({ type, key }) => { - let info = global.appHotKey.config.global.keys[key] - if (!info || info.type != WIN_LYRIC_EVENT_NAME.name) return - let desktopLyricSetting = JSON.parse(JSON.stringify(global.appSetting.desktopLyric)) - let settingKey - switch (info.action) { - case desktop_lyric.toggle_visible.action: - settingKey = 'enable' - break - case desktop_lyric.toggle_lock.action: - settingKey = 'isLock' - break - case desktop_lyric.toggle_always_top.action: - settingKey = 'isAlwaysOnTop' - break - } - desktopLyricSetting[settingKey] = !desktopLyricSetting[settingKey] - - global.lx_core.setAppConfig({ desktopLyric: desktopLyricSetting }, null) -}) diff --git a/src/main/modules/winLyric/index.js b/src/main/modules/winLyric/index.js deleted file mode 100644 index 45057b32..00000000 --- a/src/main/modules/winLyric/index.js +++ /dev/null @@ -1,132 +0,0 @@ -const path = require('path') -const { BrowserWindow } = require('electron') -const { winLyric: WIN_LYRIC_EVENT_NAME } = require('../../events/_name') -const { debounce, isLinux } = require('../../../common/utils') -const { getLyricWindowBounds } = require('./utils') - -require('./event') -require('./rendererEvent') - -global.lx_event.winLyric.on(WIN_LYRIC_EVENT_NAME.create, () => { - createWindow() -}) -global.lx_event.winLyric.on(WIN_LYRIC_EVENT_NAME.close, () => { - closeWindow() -}) - -let winURL = global.isDev ? 'http://localhost:9081/lyric.html' : `file://${path.join(__dirname, 'lyric.html')}` - -const setLyricsConfig = debounce(config => { - // if (x != null) bounds.x = x - // if (y != null) bounds.y = y - // if (width != null) bounds.width = width - // if (height != null) bounds.height = height - global.lx_core.setAppConfig({ desktopLyric: config }, WIN_LYRIC_EVENT_NAME.name) -}, 500) - -const winEvent = lyricWindow => { - // let bounds - // lyricWindow.on('close', event => { - // if (global.isQuitting || !global.appSetting.tray.isShow || (!isWin && !global.isTrafficLightClose)) { - // lyricWindow.setProgressBar(-1) - // return - // } - - // if (global.isTrafficLightClose) global.isTrafficLightClose = false - // event.preventDefault() - // lyricWindow.hide() - // }) - - lyricWindow.on('closed', () => { - lyricWindow = global.modules.lyricWindow = null - }) - - - lyricWindow.on('move', event => { - // bounds = lyricWindow.getBounds() - // console.log(bounds) - setLyricsConfig(lyricWindow.getBounds()) - }) - - lyricWindow.on('resize', event => { - // bounds = lyricWindow.getBounds() - // console.log(bounds) - setLyricsConfig(lyricWindow.getBounds()) - }) - - // lyricWindow.on('restore', () => { - // lyricWindow.webContents.send('restore') - // }) - // lyricWindow.on('focus', () => { - // lyricWindow.webContents.send('focus') - // }) - - lyricWindow.once('ready-to-show', () => { - lyricWindow.show() - if (global.appSetting.desktopLyric.isLock) { - global.modules.lyricWindow.setIgnoreMouseEvents(true, { forward: !isLinux && global.appSetting.desktopLyric.isHoverHide }) - } - // linux下每次重开时貌似要重新设置置顶 - if (isLinux && global.appSetting.desktopLyric.isAlwaysOnTop) { - global.modules.lyricWindow.setAlwaysOnTop(global.appSetting.desktopLyric.isAlwaysOnTop, 'screen-saver') - } - }) -} - -const createWindow = () => { - if (global.modules.lyricWindow) return - if (!global.appSetting.desktopLyric.enable) return - // const windowSizeInfo = getWindowSizeInfo(global.appSetting) - let { x, y, width, height, isAlwaysOnTop } = global.appSetting.desktopLyric - let { width: screenWidth, height: screenHeight } = global.envParams.workAreaSize - if (x == null) { - x = screenWidth - width - y = screenHeight - height - } - if (global.appSetting.desktopLyric.isLockScreen) { - let bounds = getLyricWindowBounds({ x, y, width, height }, { x: null, y: null, w: width, h: height }) - x = bounds.x - y = bounds.y - width = bounds.width - height = bounds.height - } - /** - * Initial window options - */ - global.modules.lyricWindow = new BrowserWindow({ - height, - width, - x, - y, - minWidth: 380, - minHeight: 80, - useContentSize: true, - frame: false, - transparent: true, - enableRemoteModule: false, - // icon: path.join(global.__static, isWin ? 'icons/256x256.ico' : 'icons/512x512.png'), - resizable: false, - minimizable: false, - maximizable: false, - fullscreenable: false, - show: false, - alwaysOnTop: isAlwaysOnTop, - skipTaskbar: true, - webPreferences: { - contextIsolation: false, - webSecurity: !global.isDev, - nodeIntegration: true, - spellcheck: false, // 禁用拼写检查器 - }, - }) - - global.modules.lyricWindow.loadURL(winURL) - - winEvent(global.modules.lyricWindow) - // mainWindow.webContents.openDevTools() -} - -const closeWindow = () => { - if (!global.modules.lyricWindow) return - global.modules.lyricWindow.close() -} diff --git a/src/main/modules/winLyric/index.ts b/src/main/modules/winLyric/index.ts new file mode 100644 index 00000000..9f636522 --- /dev/null +++ b/src/main/modules/winLyric/index.ts @@ -0,0 +1,61 @@ +import { APP_EVENT_NAMES } from '@common/constants' +import initRendererEvent, { sendMainWindowInitedEvent } from './rendererEvent' +import { setLrcConfig } from './config' +import { HOTKEY_DESKTOP_LYRIC } from '@common/hotKey' +import { closeWindow, createWindow, isExistWindow } from './main' +// import main from './main' +// import { Event, EVENT_NAMES } from './event' + + +export default () => { + initRendererEvent() + // global.lx.event_app.winLyric = new Event() + // global.app_event.winMain. + + global.lx.event_app.on('main_window_inited', () => { + if (global.lx.appSetting['desktopLyric.enable']) { + if (isExistWindow()) sendMainWindowInitedEvent() + else createWindow() + } + }) + global.lx.event_app.on('updated_config', (keys, setting) => { + setLrcConfig(keys, setting) + }) + global.lx.event_app.on('main_window_close', () => { + closeWindow() + }) + + + // global.lx_event.mainWindow.on(MAIN_WINDOW_EVENT_NAME.setLyricInfo, info => { + // if (!global.modules.lyricWindow) return + // mainSend(global.modules.lyricWindow, ipcWinLyricNames.set_lyric_info, info) + // }) + + global.lx.event_app.on('hot_key_down', ({ type, key }) => { + let info = global.lx.hotKey.config.global.keys[key] + if (!info || info.type != APP_EVENT_NAMES.winLyricName) return + let newSetting: Partial = {} + let settingKey: keyof LX.AppSetting + switch (info.action) { + case HOTKEY_DESKTOP_LYRIC.toggle_visible.action: + settingKey = 'desktopLyric.enable' + break + case HOTKEY_DESKTOP_LYRIC.toggle_lock.action: + settingKey = 'desktopLyric.isLock' + break + case HOTKEY_DESKTOP_LYRIC.toggle_always_top.action: + settingKey = 'desktopLyric.isAlwaysOnTop' + break + default: return + } + newSetting[settingKey] = !global.lx.appSetting[settingKey] + + global.lx.event_app.update_config(newSetting) + }) +} +export * from './main' +export * from './rendererEvent' + +// export { +// EVENT_NAMES, +// } diff --git a/src/main/modules/winLyric/main.ts b/src/main/modules/winLyric/main.ts new file mode 100644 index 00000000..b283a04c --- /dev/null +++ b/src/main/modules/winLyric/main.ts @@ -0,0 +1,208 @@ +import { join } from 'path' +import { BrowserWindow } from 'electron' +import { debounce, encodePath, isLinux, isWin } from '@common/utils' +import { getLyricWindowBounds } from './utils' +import { mainSend } from '@common/mainIpc' + +// require('./event') +// require('./rendererEvent') + +let browserWindow: Electron.BrowserWindow | null = null + + +const setLyricsConfig = debounce((config: Partial) => { + global.lx.event_app.update_config(config) +}, 500) + +const winEvent = () => { + if (!browserWindow) return + // let bounds + // lyricWindow.on('close', event => { + // if (global.isQuitting || !global.appSetting.tray.enable || (!isWin && !global.isTrafficLightClose)) { + // lyricWindow.setProgressBar(-1) + // return + // } + + // if (global.isTrafficLightClose) global.isTrafficLightClose = false + // event.preventDefault() + // lyricWindow.hide() + // }) + + browserWindow.on('closed', () => { + browserWindow = null + }) + + browserWindow.on('move', () => { + // bounds = browserWindow.getBounds() + // console.log(bounds) + const bounds = browserWindow!.getBounds() + setLyricsConfig({ + 'desktopLyric.x': bounds.x, + 'desktopLyric.y': bounds.y, + 'desktopLyric.width': bounds.width, + 'desktopLyric.height': bounds.height, + }) + }) + + browserWindow.on('resize', () => { + // bounds = browserWindow.getBounds() + // console.log(bounds) + const bounds = browserWindow!.getBounds() + setLyricsConfig({ + 'desktopLyric.x': bounds.x, + 'desktopLyric.y': bounds.y, + 'desktopLyric.width': bounds.width, + 'desktopLyric.height': bounds.height, + }) + }) + + // browserWindow.on('restore', () => { + // browserWindow.webContents.send('restore') + // }) + // browserWindow.on('focus', () => { + // browserWindow.webContents.send('focus') + // }) + + browserWindow.once('ready-to-show', () => { + showWindow() + if (global.lx.appSetting['desktopLyric.isLock']) { + browserWindow!.setIgnoreMouseEvents(true, { forward: !isLinux && global.lx.appSetting['desktopLyric.isHoverHide'] }) + } + // linux下每次重开时貌似要重新设置置顶 + if (isLinux && global.lx.appSetting['desktopLyric.isAlwaysOnTop']) { + browserWindow!.setAlwaysOnTop(global.lx.appSetting['desktopLyric.isAlwaysOnTop'], 'screen-saver') + } + if (global.lx.appSetting['desktopLyric.isAlwaysOnTop'] && global.lx.appSetting['desktopLyric.isAlwaysOnTopLoop']) alwaysOnTopTools.startLoop() + }) +} + +export const createWindow = () => { + closeWindow() + if (!global.envParams.workAreaSize) return + let x = global.lx.appSetting['desktopLyric.x'] + let y = global.lx.appSetting['desktopLyric.y'] + let width = global.lx.appSetting['desktopLyric.width'] + let height = global.lx.appSetting['desktopLyric.height'] + let isAlwaysOnTop = global.lx.appSetting['desktopLyric.isAlwaysOnTop'] + let isLockScreen = global.lx.appSetting['desktopLyric.isLockScreen'] + let { width: screenWidth, height: screenHeight } = global.envParams.workAreaSize + if (x == null || y == null) { + x = screenWidth - width + y = screenHeight - height + } + if (isLockScreen) { + let bounds = getLyricWindowBounds({ x, y, width, height }, { x: null, y: 0, w: width, h: height }) + x = bounds.x + y = bounds.y + width = bounds.width + height = bounds.height + } + + const { shouldUseDarkColors, theme } = global.lx.theme + + /** + * Initial window options + */ + browserWindow = new BrowserWindow({ + height, + width, + x, + y, + minWidth: 380, + minHeight: 80, + useContentSize: true, + frame: false, + transparent: true, + // enableRemoteModule: false, + // icon: join(global.__static, isWin ? 'icons/256x256.ico' : 'icons/512x512.png'), + resizable: false, + minimizable: false, + maximizable: false, + fullscreenable: false, + show: false, + alwaysOnTop: isAlwaysOnTop, + skipTaskbar: true, + webPreferences: { + contextIsolation: false, + webSecurity: !global.isDev, + nodeIntegration: true, + spellcheck: false, // 禁用拼写检查器 + }, + }) + + const winURL = global.isDev ? 'http://localhost:9081/lyric.html' : `file://${join(encodePath(isWin ? __dirname.replaceAll('\\', '/') : __dirname), 'lyric.html')}` + void browserWindow.loadURL(winURL + `?dark=${shouldUseDarkColors}&theme=${encodeURIComponent(JSON.stringify(theme))}`) + + winEvent() + // browserWindow.webContents.openDevTools() +} +export const isExistWindow = (): boolean => !!browserWindow + +export const closeWindow = () => { + if (!browserWindow) return + browserWindow.close() +} + +export const showWindow = () => { + if (!browserWindow) return + browserWindow.show() +} + +export const sendEvent = (name: string, params?: T) => { + if (!browserWindow) return + mainSend(browserWindow, name, params) +} + +export const getBounds = (): Electron.Rectangle => { + if (!browserWindow) throw new Error('window is not available') + return browserWindow.getBounds() +} + +export const setBounds = (bounds: Electron.Rectangle) => { + if (!browserWindow) return + browserWindow.setBounds(bounds) +} + + +export const setIgnoreMouseEvents = (ignore: boolean, options?: Electron.IgnoreMouseEventsOptions) => { + if (!browserWindow) return + browserWindow.setIgnoreMouseEvents(ignore, options) +} + +export const setAlwaysOnTop = (flag: boolean, level?: 'normal' | 'floating' | 'torn-off-menu' | 'modal-panel' | 'main-menu' | 'status' | 'pop-up-menu' | 'screen-saver' | undefined, relativeLevel?: number | undefined) => { + if (!browserWindow) return + browserWindow.setAlwaysOnTop(flag, level, relativeLevel) +} + +export const getMainFrame = (): Electron.WebFrameMain | null => { + if (!browserWindow) return null + return browserWindow.webContents.mainFrame +} + +interface AlwaysOnTopTools { + timeout: NodeJS.Timeout | null + setAlwaysOnTop: (isLoop: boolean) => void + startLoop: () => void + clearLoop: () => void +} +export const alwaysOnTopTools: AlwaysOnTopTools = { + timeout: null, + setAlwaysOnTop(isLoop) { + this.clearLoop() + setAlwaysOnTop(global.lx.appSetting['desktopLyric.isAlwaysOnTop'], 'screen-saver') + // console.log(isLoop) + if (isLoop) this.startLoop() + }, + startLoop() { + this.clearLoop() + this.timeout = setInterval(() => { + if (!isExistWindow()) return this.clearLoop() + setAlwaysOnTop(true, 'screen-saver') + }, 1000) + }, + clearLoop() { + if (!this.timeout) return + clearInterval(this.timeout) + this.timeout = null + }, +} diff --git a/src/main/modules/winLyric/rendererEvent.js b/src/main/modules/winLyric/rendererEvent.js deleted file mode 100644 index 082aa574..00000000 --- a/src/main/modules/winLyric/rendererEvent.js +++ /dev/null @@ -1,38 +0,0 @@ -const { - mainOn, - mainHandle, - mainSend, - NAMES: { - mainWindow: ipcMainWindowNames, - winLyric: ipcWinLyricNames, - }, -} = require('../../../common/ipc') -const { winLyric: WIN_LYRIC_EVENT_NAME } = require('../../events/_name') -const { getLyricWindowBounds } = require('./utils') - -mainOn(ipcWinLyricNames.get_lyric_info, (event, action) => { - if (!global.modules.mainWindow) return - mainSend(global.modules.mainWindow, ipcMainWindowNames.get_lyric_info, { - name: ipcWinLyricNames.set_lyric_info, - modal: 'lyricWindow', - action, - }) -}) - -mainOn(ipcWinLyricNames.set_lyric_config, (event, config) => { - global.lx_core.setAppConfig({ desktopLyric: config }, WIN_LYRIC_EVENT_NAME.name) -}) - -mainHandle(ipcWinLyricNames.get_lyric_config, async() => { - return { - config: global.appSetting.desktopLyric, - languageId: global.appSetting.langId, - isShowLyricTranslation: global.appSetting.player.isShowLyricTranslation, - isShowLyricRoma: global.appSetting.player.isShowLyricRoma, - isPlayLxlrc: global.appSetting.player.isPlayLxlrc, - } -}) - -mainOn(ipcWinLyricNames.set_win_bounds, (event, options) => { - global.modules.lyricWindow.setBounds(getLyricWindowBounds(global.modules.lyricWindow.getBounds(), options)) -}) diff --git a/src/main/modules/winLyric/rendererEvent.ts b/src/main/modules/winLyric/rendererEvent.ts new file mode 100644 index 00000000..e08daddc --- /dev/null +++ b/src/main/modules/winLyric/rendererEvent.ts @@ -0,0 +1,53 @@ +import { registerRendererEvents as common } from '@main/modules/commonRenderers/common' +import { mainOn, mainHandle } from '@common/mainIpc' +import { WIN_LYRIC_RENDERER_EVENT_NAME } from '@common/ipcNames' +import { buildLyricConfig, getLyricWindowBounds } from './utils' +import { sendNewDesktopLyricClient } from '@main/modules/winMain' +import { getBounds, getMainFrame, sendEvent, setBounds } from './main' +import { MessageChannelMain } from 'electron' + + +export default () => { + // mainOn(WIN_LYRIC_RENDERER_EVENT_NAME.get_lyric_info, ({ params: action }) => { + // sendMainEvent(WIN_MAIN_RENDERER_EVENT_NAME.get_lyric_info, { + // name: WIN_LYRIC_RENDERER_EVENT_NAME.set_lyric_info, + // modal: 'lyricWindow', + // action, + // }) + // }) + common(sendEvent) + + mainHandle>(WIN_LYRIC_RENDERER_EVENT_NAME.set_config, async({ params: config }) => { + global.lx.event_app.update_config(config) + }) + + mainHandle(WIN_LYRIC_RENDERER_EVENT_NAME.get_config, async() => { + return buildLyricConfig(global.lx.appSetting) as LX.DesktopLyric.Config + }) + + mainOn(WIN_LYRIC_RENDERER_EVENT_NAME.set_win_bounds, ({ params: options }) => { + setBounds(getLyricWindowBounds(getBounds(), options)) + }) + + mainOn(WIN_LYRIC_RENDERER_EVENT_NAME.request_main_window_channel, ({ event }) => { + if (event.senderFrame !== getMainFrame()) return + // Create a new channel ... + const { port1, port2 } = new MessageChannelMain() + // ... send one end to the worker ... + sendNewDesktopLyricClient(port1) + // ... and the other end to the main window. + event.senderFrame.postMessage(WIN_LYRIC_RENDERER_EVENT_NAME.provide_main_window_channel, null, [port2]) + // Now the main window and the worker can communicate with each other + // without going through the main process! + console.log('request_main_window_channel') + }) +} + +export const sendConfigChange = (setting: Partial) => { + sendEvent(WIN_LYRIC_RENDERER_EVENT_NAME.on_config_change, setting) +} + +export const sendMainWindowInitedEvent = () => { + sendEvent(WIN_LYRIC_RENDERER_EVENT_NAME.main_window_inited) +} + diff --git a/src/main/modules/winLyric/utils.js b/src/main/modules/winLyric/utils.js deleted file mode 100644 index a82c1a23..00000000 --- a/src/main/modules/winLyric/utils.js +++ /dev/null @@ -1,68 +0,0 @@ -// 设置窗口位置、大小 -let winX -let winY -let wasW -let wasH -let offset = 8 -let minWidth = 380 -let minHeight = 80 -exports.getLyricWindowBounds = (bounds, { x = 0, y = 0, w = 0, h = 0 }) => { - if (w < minWidth) w = minWidth - if (h < minHeight) h = minHeight - - if (global.appSetting.desktopLyric.isLockScreen) { - wasW = global.envParams.workAreaSize.width + offset - wasH = global.envParams.workAreaSize.height + offset - if (w > wasW + offset) w = wasW + offset - if (h > wasH + offset) h = wasH + offset - if (x == null) { - if (bounds.x > wasW - w) { - x = wasW - w - bounds.x - } else if (bounds.x < -offset) { - x = bounds.x + offset - } else { - x = 0 - } - if (bounds.y > wasH - h) { - y = wasH - h - bounds.y - } else if (bounds.y < -offset) { - y = bounds.y + offset - } else { - y = 0 - } - } - winX = bounds.x + x - winY = bounds.y + y - - if (x != 0) { - if (winX < -offset) { - winX = -offset - } else if (winX + w > wasW) { - winX = wasW - w - } - } - if (y != 0) { - if (winY < -offset) { - winY = -offset - } else if (winY + h > wasH) { - winY = wasH - h - } - } - - x = winX - y = winY - - if (x + w > wasW) w = wasW - x - if (y + h > wasH) h = wasH - y - } else { - y += bounds.y - x += bounds.x - } - - bounds.width = w - bounds.height = h - bounds.x = x - bounds.y = y - // console.log('util bounds', bounds) - return bounds -} diff --git a/src/main/modules/winLyric/utils.ts b/src/main/modules/winLyric/utils.ts new file mode 100644 index 00000000..95e107cb --- /dev/null +++ b/src/main/modules/winLyric/utils.ts @@ -0,0 +1,115 @@ +// 设置窗口位置、大小 +let winX +let winY +let wasW +let wasH +let offset = 8 +let minWidth = 80 +let minHeight = 50 + +export const getLyricWindowBounds = (bounds: Electron.Rectangle, { x = 0, y = 0, w = 0, h = 0 }: LX.DesktopLyric.NewBounds): Electron.Rectangle => { + if (w < minWidth) w = minWidth + if (h < minHeight) h = minHeight + + if (global.lx.appSetting['desktopLyric.isLockScreen']) { + if (!global.envParams.workAreaSize) return bounds + wasW = (global.envParams.workAreaSize.width ?? 0) + offset + wasH = (global.envParams.workAreaSize.height ?? 0) + offset + + if (w > wasW + offset) w = wasW + offset + if (h > wasH + offset) h = wasH + offset + if (x == null) { + if (bounds.x > wasW - w) { + x = wasW - w - bounds.x + } else if (bounds.x < -offset) { + x = bounds.x + offset + } else { + x = 0 + } + if (bounds.y > wasH - h) { + y = wasH - h - bounds.y + } else if (bounds.y < -offset) { + y = bounds.y + offset + } else { + y = 0 + } + } + winX = bounds.x + x + winY = bounds.y + y + + if (x != 0) { + if (winX < -offset) { + winX = -offset + } else if (winX + w > wasW) { + winX = wasW - w + } + } + if (y != 0) { + if (winY < -offset) { + winY = -offset + } else if (winY + h > wasH) { + winY = wasH - h + } + } + + x = winX + y = winY + + if (x + w > wasW) w = wasW - x + if (y + h > wasH) h = wasH - y + } else { + if (x == null) { + x = 0 + y = 0 + } + y += bounds.y + x += bounds.x + } + + bounds.width = w + bounds.height = h + bounds.x = x + bounds.y = y + // console.log('util bounds', bounds) + return bounds +} + + +export const watchConfigKeys = [ + 'desktopLyric.enable', + 'desktopLyric.isLock', + 'desktopLyric.isAlwaysOnTop', + 'desktopLyric.isAlwaysOnTopLoop', + 'desktopLyric.audioVisualization', + 'desktopLyric.width', + 'desktopLyric.height', + 'desktopLyric.x', + 'desktopLyric.y', + 'desktopLyric.isLockScreen', + 'desktopLyric.isDelayScroll', + 'desktopLyric.isHoverHide', + 'desktopLyric.direction', + 'desktopLyric.style.align', + 'desktopLyric.style.lyricUnplayColor', + 'desktopLyric.style.lyricPlayedColor', + 'desktopLyric.style.lyricShadowColor', + 'desktopLyric.style.font', + 'desktopLyric.style.fontSize', + // 'desktopLyric.style.fontWeight', + 'desktopLyric.style.opacity', + 'desktopLyric.style.ellipsis', + 'desktopLyric.style.isZoomActiveLrc', + 'common.langId', + 'player.isShowLyricTranslation', + 'player.isShowLyricRoma', + 'player.isPlayLxlrc', +] as const + +export const buildLyricConfig = (appSetting: Partial): Partial => { + const setting: Partial = {} + for (const key of watchConfigKeys) { + // @ts-expect-error + if (key in appSetting) setting[key] = appSetting[key] + } + return setting +} diff --git a/src/main/utils/autoUpdate.js b/src/main/modules/winMain/autoUpdate.ts similarity index 65% rename from src/main/utils/autoUpdate.js rename to src/main/modules/winMain/autoUpdate.ts index 3051574a..7273c900 100644 --- a/src/main/utils/autoUpdate.js +++ b/src/main/modules/winMain/autoUpdate.ts @@ -1,10 +1,11 @@ -const { log, isWin } = require('../../common/utils') -const { autoUpdater } = require('electron-updater') -const { mainOn, mainSend, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc') +import { autoUpdater } from 'electron-updater' +import { log, isWin } from '@common/utils' +import { mainOn } from '@common/mainIpc' +import { isExistWindow, sendEvent } from './index' +import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames' autoUpdater.logger = log // autoUpdater.autoDownload = false -autoUpdater.logger.transports.file.level = 'info' let isFirstCheckedUpdate = true @@ -22,7 +23,7 @@ log.info('App starting...') // ------------------------------------------------------------------- // let win -function sendStatusToWindow(text) { +function sendStatusToWindow(text: string) { log.info(text) // ipcMain.send('message', text) } @@ -59,25 +60,69 @@ function sendStatusToWindow(text) { // }) -let waitEvent = [] -const handleSendEvent = action => { - if (global.modules.mainWindow) { +interface WaitEvent { + type: string + info: any +} + +let waitEvent: WaitEvent[] = [] +const handleSendEvent = (action: WaitEvent) => { + if (isExistWindow()) { setTimeout(() => { // 延迟发送事件,过早发送可能渲染进程还没启动完成 - if (!global.modules.mainWindow) return - mainSend(global.modules.mainWindow, action.type, action.info) + sendEvent(action.type, action.info) }, 2000) } else { waitEvent.push(action) } } -module.exports = () => { +export default () => { + autoUpdater.on('checking-for-update', () => { + sendStatusToWindow('Checking for update...') + }) + autoUpdater.on('update-available', info => { + sendStatusToWindow('Update available.') + handleSendEvent({ type: WIN_MAIN_RENDERER_EVENT_NAME.update_available, info }) + }) + autoUpdater.on('update-not-available', info => { + sendStatusToWindow('Update not available.') + handleSendEvent({ type: WIN_MAIN_RENDERER_EVENT_NAME.update_not_available, info }) + }) + autoUpdater.on('error', err => { + sendStatusToWindow('Error in auto-updater.') + handleSendEvent({ type: WIN_MAIN_RENDERER_EVENT_NAME.update_error, info: err.message }) + }) + autoUpdater.on('download-progress', progressObj => { + let log_message = `Download speed: ${progressObj.bytesPerSecond}` + log_message = `${log_message} - Downloaded ${progressObj.percent}%` + log_message = `${log_message} (progressObj.transferred/${progressObj.total})` + sendStatusToWindow(log_message) + handleSendEvent({ type: WIN_MAIN_RENDERER_EVENT_NAME.update_progress, info: progressObj }) + }) + autoUpdater.on('update-downloaded', info => { + sendStatusToWindow('Update downloaded.') + handleSendEvent({ type: WIN_MAIN_RENDERER_EVENT_NAME.update_downloaded, info }) + }) + + mainOn(WIN_MAIN_RENDERER_EVENT_NAME.update_check, () => { + checkUpdate() + }) + + mainOn(WIN_MAIN_RENDERER_EVENT_NAME.quit_update, () => { + global.lx.isQuitting = true + + setTimeout(() => { + autoUpdater.quitAndInstall(true, true) + }, 1000) + }) +} + +export const checkUpdate = () => { if (!isFirstCheckedUpdate) { if (waitEvent.length) { waitEvent.forEach((event, index) => { setTimeout(() => { // 延迟发送事件,过早发送可能渲染进程还没启动完成 - if (!global.modules.mainWindow) return - mainSend(global.modules.mainWindow, event.type, event.info) + sendEvent(event.type, event.info) }, 2000 * (index + 1)) }) waitEvent = [] @@ -86,45 +131,10 @@ module.exports = () => { } isFirstCheckedUpdate = false - autoUpdater.on('checking-for-update', () => { - sendStatusToWindow('Checking for update...') - }) - autoUpdater.on('update-available', info => { - sendStatusToWindow('Update available.') - handleSendEvent({ type: ipcMainWindowNames.update_available, info }) - }) - autoUpdater.on('update-not-available', info => { - sendStatusToWindow('Update not available.') - handleSendEvent({ type: ipcMainWindowNames.update_not_available, info }) - }) - autoUpdater.on('error', err => { - sendStatusToWindow('Error in auto-updater.') - handleSendEvent({ type: ipcMainWindowNames.update_error, info: err.message }) - }) - autoUpdater.on('download-progress', progressObj => { - let log_message = 'Download speed: ' + progressObj.bytesPerSecond - log_message = log_message + ' - Downloaded ' + progressObj.percent + '%' - log_message = log_message + ' (' + progressObj.transferred + '/' + progressObj.total + ')' - sendStatusToWindow(log_message) - handleSendEvent({ type: ipcMainWindowNames.update_progress, info: progressObj }) - }) - autoUpdater.on('update-downloaded', info => { - sendStatusToWindow('Update downloaded.') - handleSendEvent({ type: ipcMainWindowNames.update_downloaded, info }) - }) - mainOn(ipcMainWindowNames.quit_update, () => { - global.isQuitting = true - - setTimeout(() => { - autoUpdater.quitAndInstall(true, true) - }, 1000) - }) - // 由于集合安装包中不包含win arm版,这将会导致arm版更新失败 if (isWin && process.arch.includes('arm')) { - handleSendEvent({ type: ipcMainWindowNames.update_error, info: 'failed' }) + handleSendEvent({ type: WIN_MAIN_RENDERER_EVENT_NAME.update_error, info: 'failed' }) } else { - autoUpdater.checkForUpdates() + void autoUpdater.checkForUpdates() } } - diff --git a/src/main/modules/winMain/index.ts b/src/main/modules/winMain/index.ts new file mode 100644 index 00000000..933a4008 --- /dev/null +++ b/src/main/modules/winMain/index.ts @@ -0,0 +1,45 @@ +import initRendererEvent, { handleKeyDown, hotKeyConfigUpdate } from './rendererEvent' + +import { APP_EVENT_NAMES } from '@common/constants' +import { createWindow, minimize, toggleHide, toggleMinimize } from './main' +import initUpdate from './autoUpdate' +import { HOTKEY_COMMON } from '@common/hotKey' +import { quitApp } from '@main/app' + +export default () => { + initRendererEvent() + initUpdate() + + global.lx.event_app.on('hot_key_down', ({ type, key }) => { + let info = global.lx.hotKey.config.global.keys[key] + if (info?.type != APP_EVENT_NAMES.winMainName) return + switch (info.action) { + case HOTKEY_COMMON.close.action: + quitApp() + break + case HOTKEY_COMMON.hide_toggle.action: + toggleHide() + break + case HOTKEY_COMMON.min.action: + minimize() + break + case HOTKEY_COMMON.min_toggle.action: + toggleMinimize() + break + default: + handleKeyDown(type, key) + break + } + }) + global.lx.event_app.on('hot_key_config_update', (config) => { + hotKeyConfigUpdate(config) + }) + + global.lx.event_app.on('app_inited', () => { + createWindow() + }) +} + +export * from './main' +export * from './rendererEvent' + diff --git a/src/main/modules/winMain/main.ts b/src/main/modules/winMain/main.ts new file mode 100644 index 00000000..c957572e --- /dev/null +++ b/src/main/modules/winMain/main.ts @@ -0,0 +1,254 @@ +import { BrowserWindow, dialog } from 'electron' +import { join } from 'path' +import { createTaskBarButtons, getWindowSizeInfo } from './utils' +import { encodePath, isLinux, isWin } from '@common/utils' +import { openDevTools as handleOpenDevTools } from '@main/utils' +import { mainSend } from '@common/mainIpc' +import { sendFocus, sendTaskbarButtonClick } from './rendererEvent' + +let browserWindow: Electron.BrowserWindow | null = null + +const winEvent = () => { + if (!browserWindow) return + + browserWindow.on('close', event => { + if (global.lx.isQuitting || !global.lx.appSetting['tray.enable'] || (!isWin && !global.lx.isTrafficLightClose)) { + browserWindow!.setProgressBar(-1) + global.lx.event_app.main_window_close() + return + } + + if (global.lx.isTrafficLightClose) global.lx.isTrafficLightClose = false + event.preventDefault() + browserWindow!.hide() + }) + + browserWindow.on('closed', () => { + browserWindow = null + }) + + // browserWindow.on('restore', () => { + // browserWindow.webContents.send('restore') + // }) + browserWindow.on('focus', () => { + sendFocus() + global.lx.event_app.main_window_focus() + }) + + browserWindow.on('blur', () => { + global.lx.event_app.main_window_blur() + }) + + browserWindow.once('ready-to-show', () => { + showWindow() + setThumbarButtons() + global.lx.event_app.main_window_ready_to_show() + }) + + browserWindow.on('show', () => { + global.lx.event_app.main_window_show() + }) + browserWindow.on('hide', () => { + global.lx.event_app.main_window_hide() + }) +} + + +export const createWindow = () => { + closeWindow() + const windowSizeInfo = getWindowSizeInfo(global.lx.appSetting['common.windowSizeId']) + + const { shouldUseDarkColors, theme } = global.lx.theme + + /** + * Initial window options + */ + const options: Electron.BrowserWindowConstructorOptions = { + height: windowSizeInfo.height, + useContentSize: true, + width: windowSizeInfo.width, + frame: false, + transparent: !global.envParams.cmdParams.dt, + // enableRemoteModule: false, + // icon: join(global.__static, isWin ? 'icons/256x256.ico' : 'icons/512x512.png'), + resizable: false, + maximizable: false, + fullscreenable: true, + show: false, + webPreferences: { + nodeIntegrationInWorker: true, + contextIsolation: false, + webSecurity: !global.isDev, + nodeIntegration: true, + spellcheck: false, // 禁用拼写检查器 + }, + } + if (global.envParams.cmdParams.dt) options.backgroundColor = theme.colors['--color-primary-light-1000'] + if (global.lx.appSetting['common.startInFullscreen']) { + options.fullscreen = true + if (isLinux) options.resizable = true + } + browserWindow = new BrowserWindow(options) + + const winURL = global.isDev ? 'http://localhost:9080' : `file://${join(encodePath(isWin ? __dirname.replaceAll('\\', '/') : __dirname), 'index.html')}` + void browserWindow.loadURL(winURL + `?dt=${!!global.envParams.cmdParams.dt}&dark=${shouldUseDarkColors}&theme=${encodeURIComponent(JSON.stringify(theme))}`) + + winEvent() + + if (global.envParams.cmdParams.odt) handleOpenDevTools(browserWindow.webContents) + // browserWindow.webContents.openDevTools() +} + +export const isExistWindow = (): boolean => !!browserWindow +export const isShowWindow = (): boolean => { + if (!browserWindow) return false + return browserWindow.isVisible() && (isWin ? true : browserWindow.isFocused()) +} + +export const closeWindow = () => { + if (!browserWindow) return + browserWindow.close() +} + +export const sendEvent = (name: string, params?: T) => { + if (!browserWindow) return + mainSend(browserWindow, name, params) +} + +export const showSelectDialog = async(options: Electron.OpenDialogOptions) => { + if (!browserWindow) throw new Error('main window is undefined') + return await dialog.showOpenDialog(browserWindow, options) +} +export const showDialog = ({ type, message, detail }: Electron.MessageBoxSyncOptions) => { + if (!browserWindow) return + dialog.showMessageBoxSync(browserWindow, { + type, + message, + detail, + }) +} +export const showSaveDialog = async(options: Electron.SaveDialogOptions) => { + if (!browserWindow) throw new Error('main window is undefined') + return await dialog.showSaveDialog(browserWindow, options) +} +export const minimize = () => { + if (!browserWindow) return + browserWindow.minimize() +} +export const maximize = () => { + if (!browserWindow) return + browserWindow.maximize() +} +export const unmaximize = () => { + if (!browserWindow) return + browserWindow.unmaximize() +} +export const toggleHide = () => { + if (!browserWindow) return + browserWindow.isVisible() + ? browserWindow.hide() + : browserWindow.show() +} +export const toggleMinimize = () => { + if (!browserWindow) return + if (browserWindow.isMinimized()) { + if (!browserWindow.isVisible()) { + browserWindow.show() + } + browserWindow.restore() + browserWindow.focus() + } else { + browserWindow.minimize() + } +} +export const showWindow = () => { + if (!browserWindow) return + if (browserWindow.isMinimized()) { + browserWindow.restore() + } + if (browserWindow.isVisible()) { + browserWindow.focus() + } else { + browserWindow.show() + } +} +export const hideWindow = () => { + if (!browserWindow) return + browserWindow.hide() +} +export const setWindowBounds = (options: Partial) => { + if (!browserWindow) return + browserWindow.setBounds(options) +} +export const setProgressBar = (progress: number, options?: Electron.ProgressBarOptions) => { + if (!browserWindow) return + browserWindow.setProgressBar(progress, options) +} +export const setIgnoreMouseEvents = (ignore: boolean, options?: Electron.IgnoreMouseEventsOptions) => { + if (!browserWindow) return + browserWindow.setIgnoreMouseEvents(ignore, options) +} +export const toggleDevTools = () => { + if (!browserWindow) return + if (browserWindow.webContents.isDevToolsOpened()) { + browserWindow.webContents.closeDevTools() + } else { + handleOpenDevTools(browserWindow.webContents) + } +} + +export const setFullScreen = (isFullscreen: boolean): boolean => { + if (!browserWindow) return false + if (isLinux) { // linux 需要先设置为可调整窗口大小才能全屏 + if (isFullscreen) { + browserWindow.setResizable(isFullscreen) + browserWindow.setFullScreen(isFullscreen) + } else { + browserWindow.setFullScreen(isFullscreen) + browserWindow.setResizable(isFullscreen) + } + } else { + browserWindow.setFullScreen(isFullscreen) + } + return isFullscreen +} + +const taskBarButtonFlags: LX.TaskBarButtonFlags = { + empty: true, + collect: false, + play: false, + next: true, + prev: true, +} +export const setThumbarButtons = ({ empty, collect, play, next, prev }: LX.TaskBarButtonFlags = taskBarButtonFlags) => { + if (!isWin || !browserWindow) return + taskBarButtonFlags.empty = empty + taskBarButtonFlags.collect = collect + taskBarButtonFlags.play = play + taskBarButtonFlags.next = next + taskBarButtonFlags.prev = prev + browserWindow.setThumbarButtons(createTaskBarButtons(taskBarButtonFlags, action => { + sendTaskbarButtonClick(action) + })) +} + +export const setThumbnailClip = (region: Electron.Rectangle) => { + if (!browserWindow) return + return browserWindow.setThumbnailClip(region) +} + + +export const clearCache = async() => { + if (!browserWindow) throw new Error('main window is undefined') + return await browserWindow.webContents.session.clearCache() +} + +export const getCacheSize = async() => { + if (!browserWindow) throw new Error('main window is undefined') + return await browserWindow.webContents.session.getCacheSize() +} + +export const getWebContents = (): Electron.WebContents => { + if (!browserWindow) throw new Error('main window is undefined') + return browserWindow.webContents +} diff --git a/src/main/modules/winMain/rendererEvent/app.ts b/src/main/modules/winMain/rendererEvent/app.ts new file mode 100644 index 00000000..627580cd --- /dev/null +++ b/src/main/modules/winMain/rendererEvent/app.ts @@ -0,0 +1,142 @@ +// const path = require('path') +import { app } from 'electron' +import { mainHandle, mainOn } from '@common/mainIpc' +import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames' +// import { name as defaultName } from '../../../../../package.json' +import { + minimize, + maximize, + closeWindow, + showWindow, + setFullScreen, + sendEvent, + clearCache, + getCacheSize, + toggleDevTools, + setWindowBounds, + setProgressBar, + setIgnoreMouseEvents, + // setThumbnailClip, + setThumbarButtons, + toggleMinimize, + toggleHide, + showSelectDialog, + showDialog, + showSaveDialog, +} from '@main/modules/winMain' +import { quitApp } from '@main/app' +import { getAllThemes, removeTheme, saveTheme } from '@main/utils' + +export default () => { + // 设置应用名称 + // mainOn(WIN_MAIN_RENDERER_EVENT_NAME.set_app_name, ({ params: name }) => { + // if (name == null) { + // app.setName(defaultName) + // } else { + // app.setName(name) + // } + // }) + mainOn(WIN_MAIN_RENDERER_EVENT_NAME.quit, () => { + quitApp() + }) + mainOn(WIN_MAIN_RENDERER_EVENT_NAME.min_toggle, () => { + toggleMinimize() + }) + mainOn(WIN_MAIN_RENDERER_EVENT_NAME.hide_toggle, () => { + toggleHide() + }) + mainOn(WIN_MAIN_RENDERER_EVENT_NAME.min, () => { + minimize() + }) + mainOn(WIN_MAIN_RENDERER_EVENT_NAME.max, () => { + maximize() + }) + mainOn(WIN_MAIN_RENDERER_EVENT_NAME.focus, () => { + showWindow() + }) + mainOn(WIN_MAIN_RENDERER_EVENT_NAME.close, ({ params: isForce }) => { + if (isForce) return app.exit(0) + global.lx.isTrafficLightClose = true + closeWindow() + }) + // 全屏 + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.fullscreen, async({ params: isFullscreen }) => { + return setFullScreen(isFullscreen) + }) + + // 选择目录 + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.show_select_dialog, async({ params: options }) => { + return await showSelectDialog(options) + }) + // 显示弹窗信息 + mainOn(WIN_MAIN_RENDERER_EVENT_NAME.show_dialog, ({ params }) => { + showDialog(params) + }) + // 显示保存弹窗 + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.show_save_dialog, async({ params }) => { + return await showSaveDialog(params) + }) + + + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.clear_cache, async() => { + return await clearCache() + }) + + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.get_cache_size, async() => { + return await getCacheSize() + }) + + mainOn(WIN_MAIN_RENDERER_EVENT_NAME.open_dev_tools, () => { + toggleDevTools() + }) + + mainOn>(WIN_MAIN_RENDERER_EVENT_NAME.set_window_size, ({ params }) => { + setWindowBounds(params) + }) + + mainOn(WIN_MAIN_RENDERER_EVENT_NAME.progress, ({ params }) => { + // console.log(params) + setProgressBar(params.progress, { + mode: params.mode ?? 'normal', + }) + }) + + mainOn(WIN_MAIN_RENDERER_EVENT_NAME.set_ignore_mouse_events, ({ params: isIgnored }) => { + isIgnored + ? setIgnoreMouseEvents(isIgnored, { forward: true }) + : setIgnoreMouseEvents(false) + }) + + // mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.taskbar_set_thumbnail_clip, async({ params }) => { + // return setThumbnailClip(params) + // }) + + mainOn(WIN_MAIN_RENDERER_EVENT_NAME.player_action_set_buttons, ({ params }) => { + setThumbarButtons(params) + }) + + mainOn(WIN_MAIN_RENDERER_EVENT_NAME.inited, () => { + global.lx.event_app.main_window_inited() + }) + + mainHandle<{ themes: LX.Theme[], userThemes: LX.Theme[] }>(WIN_MAIN_RENDERER_EVENT_NAME.get_themes, async() => { + return getAllThemes() + }) + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.save_theme, async({ params: theme }) => { + saveTheme(theme) + }) + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.remove_theme, async({ params: id }) => { + removeTheme(id) + }) +} + +export const sendFocus = () => { + sendEvent(WIN_MAIN_RENDERER_EVENT_NAME.focus) +} + +export const sendTaskbarButtonClick = (action: LX.Player.StatusButtonActions) => { + sendEvent(WIN_MAIN_RENDERER_EVENT_NAME.player_action_on_button_click, action) +} +export const sendConfigChange = (setting: Partial) => { + sendEvent(WIN_MAIN_RENDERER_EVENT_NAME.on_config_change, setting) +} diff --git a/src/main/modules/winMain/rendererEvent/data.ts b/src/main/modules/winMain/rendererEvent/data.ts new file mode 100644 index 00000000..fcbdd5d3 --- /dev/null +++ b/src/main/modules/winMain/rendererEvent/data.ts @@ -0,0 +1,17 @@ +import { STORE_NAMES } from '@common/constants' +import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames' +import { mainOn, mainHandle } from '@common/mainIpc' +import getStore from '@main/utils/store' + +export default () => { + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.get_data, ({ params: path }) => { + return getStore(STORE_NAMES.DATA).get(path) as any + }) + + mainOn<{ + path: string + data: any + }>(WIN_MAIN_RENDERER_EVENT_NAME.save_data, ({ params: { path, data } }) => { + getStore(STORE_NAMES.DATA).set(path, data) + }) +} diff --git a/src/main/modules/winMain/rendererEvent/download.ts b/src/main/modules/winMain/rendererEvent/download.ts new file mode 100644 index 00000000..dfba2bb4 --- /dev/null +++ b/src/main/modules/winMain/rendererEvent/download.ts @@ -0,0 +1,21 @@ +import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames' +import { mainHandle } from '@common/mainIpc' + + +export default () => { + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.download_list_get, async() => { + return await global.lx.worker.dbService.getDownloadList() + }) + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.download_list_add, async({ params: { list, addMusicLocationType } }) => { + await global.lx.worker.dbService.downloadInfoSave(list, addMusicLocationType) + }) + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.download_list_update, async({ params: list }) => { + await global.lx.worker.dbService.downloadInfoUpdate(list) + }) + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.download_list_remove, async({ params: ids }) => { + return await global.lx.worker.dbService.downloadInfoRemove(ids) + }) + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.download_list_clear, async() => { + await global.lx.worker.dbService.downloadInfoClear() + }) +} diff --git a/src/main/modules/winMain/rendererEvent/hotKey.ts b/src/main/modules/winMain/rendererEvent/hotKey.ts new file mode 100644 index 00000000..84ffbe01 --- /dev/null +++ b/src/main/modules/winMain/rendererEvent/hotKey.ts @@ -0,0 +1,42 @@ +import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames' +import { mainHandle } from '@common/mainIpc' +import { sendEvent } from '../main' +// import getStore from '@common/store' + + +// const { registerHotkey, unRegisterHotkey } = require('../modules/hotKey/utils') + +// mainHandle(ipcMainWindowNames.set_hot_key_config, async(event, { action, data }) => { +// switch (action) { +// case 'config': +// global.lx_event.hotKey.saveConfig(data.data, MAIN_WINDOW_EVENT_NAME.source) +// return +// case 'register': +// return registerHotkey(data) +// case 'unregister': +// return unRegisterHotkey(data) +// } +// }) + +export default () => { + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.get_hot_key, async() => { + // const electronStore_hotKey = getStore('hotKey') + return { + local: global.lx.hotKey?.config.local, + global: global.lx.hotKey?.config.global, + } + }) + + // global.lx.event_app.on(APP_EVENT_NAMES.hotKeyConfig, (config, source) => { + // if (!global.modules.mainWindow || source === MAIN_WINDOW_EVENT_NAME.name) return + // mainSend(global.modules.mainWindow, WIN_MAIN_RENDERER_EVENT_NAME.set_hot_key_config, { config, source }) + // }) +} + +export const handleKeyDown = (type: string, key: string) => { + sendEvent(WIN_MAIN_RENDERER_EVENT_NAME.key_down, { type, key }) +} + +export const hotKeyConfigUpdate = (config: LX.HotKeyConfigAll) => { + sendEvent(WIN_MAIN_RENDERER_EVENT_NAME.set_hot_key_config, config) +} diff --git a/src/main/modules/winMain/rendererEvent/index.ts b/src/main/modules/winMain/rendererEvent/index.ts new file mode 100644 index 00000000..d52915ba --- /dev/null +++ b/src/main/modules/winMain/rendererEvent/index.ts @@ -0,0 +1,39 @@ +import { registerRendererEvents as common } from '@main/modules/commonRenderers/common' +import { registerRendererEvents as list } from '@main/modules/commonRenderers/list' +import app, { sendConfigChange } from './app' +import hotKey from './hotKey' +import kw_decodeLyric from './kw_decodeLyric' +import userApi from './userApi' +import sync from './sync' +import data from './data' +import music from './music' +import download from './download' +import { sendEvent } from '../main' + +export * from './app' +export * from './hotKey' +export * from './userApi' +export * from './sync' +export * from './process' + +let isInitialized = false +export default () => { + if (isInitialized) return + isInitialized = true + + common(sendEvent) + list(sendEvent) + app() + hotKey() + kw_decodeLyric() + userApi() + sync() + data() + music() + download() + + global.lx.event_app.on('updated_config', (keys, setting) => { + sendConfigChange(setting) + }) +} + diff --git a/src/main/rendererEvents/kw_decodeLyric.js b/src/main/modules/winMain/rendererEvent/kw_decodeLyric.ts similarity index 52% rename from src/main/rendererEvents/kw_decodeLyric.js rename to src/main/modules/winMain/rendererEvent/kw_decodeLyric.ts index fad5c853..70c6a5f9 100644 --- a/src/main/rendererEvents/kw_decodeLyric.js +++ b/src/main/modules/winMain/rendererEvent/kw_decodeLyric.ts @@ -1,18 +1,21 @@ -const { inflate } = require('zlib') -const iconv = require('iconv-lite') -const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc') +import { inflate } from 'zlib' +import iconv from 'iconv-lite' +import { mainHandle } from '@common/mainIpc' +import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames' -const handleInflate = data => new Promise((resolve, reject) => { - inflate(data, (err, result) => { - if (err) return reject(err) - resolve(result) +const handleInflate = async(data: Buffer) => { + return await new Promise((resolve: (result: Buffer) => void, reject) => { + inflate(data, (err, result) => { + if (err) return reject(err) + resolve(result) + }) }) -}) +} const buf_key = Buffer.from('yeelion') const buf_key_len = buf_key.length -const decodeLyric = async(buf, isGetLyricx) => { +const decodeLyric = async(buf: Buffer, isGetLyricx: boolean) => { // const info = buf.slice(0, index).toString() // if (!info.startsWith('tp=content')) return null // const isLyric = info.includes('\r\nlrcx=0\r\n') @@ -38,8 +41,10 @@ const decodeLyric = async(buf, isGetLyricx) => { return iconv.decode(Buffer.from(output), 'gb18030') } -mainHandle(ipcMainWindowNames.handle_kw_decode_lyric, async(event, { lrcBase64, isGetLyricx }) => { - if (!global.modules.mainWindow) throw new Error('mainWindow is undefined') - const lrc = await decodeLyric(Buffer.from(lrcBase64, 'base64'), isGetLyricx) - return Buffer.from(lrc).toString('base64') -}) + +export default () => { + mainHandle<{ lrcBase64: string, isGetLyricx: boolean }, string>(WIN_MAIN_RENDERER_EVENT_NAME.handle_kw_decode_lyric, async({ params: { lrcBase64, isGetLyricx } }) => { + const lrc = await decodeLyric(Buffer.from(lrcBase64, 'base64'), isGetLyricx) + return Buffer.from(lrc).toString('base64') + }) +} diff --git a/src/main/modules/winMain/rendererEvent/music.ts b/src/main/modules/winMain/rendererEvent/music.ts new file mode 100644 index 00000000..112ef347 --- /dev/null +++ b/src/main/modules/winMain/rendererEvent/music.ts @@ -0,0 +1,106 @@ +import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames' +import { mainHandle } from '@common/mainIpc' + + +export default () => { + // =========================歌词========================= + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.get_palyer_lyric, async({ params: id }) => { + // return (getStore(LRC_EDITED, true, false).get(id) as LX.Music.LyricInfo | undefined) ?? + // getStore(LRC_RAW, true, false).get(id, {}) as LX.Music.LyricInfo + return await global.lx.worker.dbService.getPlayerLyric(id) + }) + + // 原始歌词 + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.get_lyric_raw, async({ params: id }) => { + return await global.lx.worker.dbService.getRawLyric(id) + }) + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.save_lyric_raw, async({ params: { id, lyrics } }) => { + await global.lx.worker.dbService.rawLyricAdd(id, lyrics) + }) + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.clear_lyric_raw, async() => { + await global.lx.worker.dbService.rawLyricClear() + }) + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.get_lyric_raw_count, async() => { + return await global.lx.worker.dbService.rawLyricCount() + }) + + // 已编辑的歌词 + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.get_lyric_edited, async({ params: id }) => { + return await global.lx.worker.dbService.getEditedLyric(id) + }) + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.save_lyric_edited, async({ params: { id, lyrics } }) => { + await global.lx.worker.dbService.editedLyricUpdateAddAndUpdate(id, lyrics) + }) + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.remove_lyric_edited, async({ params: id }) => { + await global.lx.worker.dbService.editedLyricRemove([id]) + }) + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.clear_lyric_edited, async() => { + await global.lx.worker.dbService.editedLyricClear() + }) + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.get_lyric_edited_count, async() => { + return await global.lx.worker.dbService.editedLyricCount() + }) + + + // =========================歌曲URL========================= + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.get_music_url, async({ params: id }) => { + return await global.lx.worker.dbService.getMusicUrl(id) ?? '' + }) + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.save_music_url, async({ params: { id, url } }) => { + await global.lx.worker.dbService.musicUrlSave([{ id, url }]) + }) + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.clear_music_url, async() => { + await global.lx.worker.dbService.musicUrlClear() + }) + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.get_music_url_count, async() => { + return await global.lx.worker.dbService.musicUrlCount() + }) + + // =========================换源歌曲========================= + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.get_other_source, async({ params: id }) => { + return await global.lx.worker.dbService.getMusicInfoOtherSource(id) + }) + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.save_other_source, async({ params: { id, list } }) => { + await global.lx.worker.dbService.musicInfoOtherSourceAdd(id, list) + }) + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.clear_other_source, async() => { + await global.lx.worker.dbService.musicInfoOtherSourceClear() + }) + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.get_other_source_count, async() => { + return await global.lx.worker.dbService.musicInfoOtherSourceCount() + }) + + + // =========================我的列表========================= + // mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.get_playlist, async({ params: isIgnoredError = false }) => { + // const electronStore_list = getStore('playList', isIgnoredError, false) + + // return { + // defaultList: electronStore_list.get('defaultList'), + // loveList: electronStore_list.get('loveList'), + // tempList: electronStore_list.get('tempList'), + // userList: electronStore_list.get('userList'), + // downloadList: getStore('downloadList').get('list'), + // } + // }) + + // const handleSaveList = ({ defaultList, loveList, userList, tempList }: Partial) => { + // let data: Partial = {} + // if (defaultList != null) data.defaultList = defaultList + // if (loveList != null) data.loveList = loveList + // if (userList != null) data.userList = userList + // if (tempList != null) data.tempList = tempList + // getStore('playList').set(data) + // } + // mainOn(WIN_MAIN_RENDERER_EVENT_NAME.save_playlist, ({ params }) => { + // switch (params.type) { + // case 'myList': + // handleSaveList(params.data) + // global.lx.event_app.save_my_list(params.data) + // break + // case 'downloadList': + // getStore('downloadList').set('list', params.data) + // break + // } + // }) +} diff --git a/src/main/modules/winMain/rendererEvent/process.ts b/src/main/modules/winMain/rendererEvent/process.ts new file mode 100644 index 00000000..0c28efa7 --- /dev/null +++ b/src/main/modules/winMain/rendererEvent/process.ts @@ -0,0 +1,18 @@ +import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames' +import { getWebContents } from '../main' + + +// export default () => { + + +// } + +/** + * 发送桌面歌词进程创建事件 + * @param port 端口 + */ +export const sendNewDesktopLyricClient = (port: Electron.MessagePortMain) => { + getWebContents().postMessage(WIN_MAIN_RENDERER_EVENT_NAME.process_new_desktop_lyric_client, null, [port]) +} + + diff --git a/src/main/modules/winMain/rendererEvent/sync.ts b/src/main/modules/winMain/rendererEvent/sync.ts new file mode 100644 index 00000000..b172f5f4 --- /dev/null +++ b/src/main/modules/winMain/rendererEvent/sync.ts @@ -0,0 +1,46 @@ +import { mainHandle } from '@common/mainIpc' +import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames' +import { startServer, stopServer, getStatus, generateCode } from '@main/modules/sync' +import { sendEvent } from '../main' + +let selectModeListenr: ((mode: LX.Sync.Mode) => void) | null = null + +export default () => { + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.sync_action, async({ params: data }) => { + switch (data.action) { + case 'enable': + return data.data.enable ? await startServer(parseInt(data.data.port)) : await stopServer() + case 'get_status': + return getStatus() + case 'generate_code': + return await generateCode() + case 'select_mode': + if (selectModeListenr) selectModeListenr(data.data) + break + default: + break + } + }) +} + + +export const sendSyncAction = (data: LX.Sync.SyncMainWindowActions) => { + sendEvent(WIN_MAIN_RENDERER_EVENT_NAME.sync_action, data) +} + +export const sendStatus = (status: LX.Sync.Status) => { + sendSyncAction({ + action: 'status', + data: status, + }) +} +export const sendSelectMode = (keyInfo: LX.Sync.KeyInfo, listener: (mode: LX.Sync.Mode) => void) => { + selectModeListenr = listener + sendSyncAction({ action: 'select_mode', data: keyInfo }) + return () => { + selectModeListenr = null + } +} +export const sendCloseSelectMode = () => { + sendSyncAction({ action: 'close_select_mode' }) +} diff --git a/src/main/modules/winMain/rendererEvent/userApi.ts b/src/main/modules/winMain/rendererEvent/userApi.ts new file mode 100644 index 00000000..d8cbece7 --- /dev/null +++ b/src/main/modules/winMain/rendererEvent/userApi.ts @@ -0,0 +1,54 @@ +import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames' +import { mainHandle } from '@common/mainIpc' +import { + getApiList, + importApi, + removeApi, + setApi, + getStatus, + request, + cancelRequest, + setAllowShowUpdateAlert, +} from '@main/modules/userApi' +import { sendEvent } from '@main/modules/winMain/main' + +export default () => { + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.import_user_api, async({ params: script }) => { + return importApi(script) + }) + + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.remove_user_api, async({ params: apiIds }) => { + return await removeApi(apiIds) + }) + + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.set_user_api, async({ params: apiId }) => { + return await setApi(apiId) + }) + + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.get_user_api_list, async() => { + return getApiList() + }) + + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.get_user_api_status, async() => { + return getStatus() + }) + + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.user_api_set_allow_update_alert, async({ params: { id, enable } }) => { + return setAllowShowUpdateAlert(id, enable) + }) + + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.request_user_api, async({ params }) => { + return await request(params) + }) + mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.request_user_api_cancel, async({ params: requestKey }) => { + return cancelRequest(requestKey) + }) +} + +export const sendStatusChange = (status: LX.UserApi.UserApiStatus) => { + sendEvent(WIN_MAIN_RENDERER_EVENT_NAME.user_api_status, status) +} +export const sendShowUpdateAlert = (info: LX.UserApi.UserApiUpdateInfo) => { + sendEvent(WIN_MAIN_RENDERER_EVENT_NAME.user_api_show_update_alert, info) +} + diff --git a/src/main/modules/winMain/utils.ts b/src/main/modules/winMain/utils.ts new file mode 100644 index 00000000..50495e7d --- /dev/null +++ b/src/main/modules/winMain/utils.ts @@ -0,0 +1,79 @@ +// import fs from 'fs' +import { join } from 'path' +import { WindowSize, windowSizeList } from '@common/config' +import { nativeImage } from 'electron' + +export const getWindowSizeInfo = (windowSizeId: number | string): WindowSize => { + return windowSizeList.find(i => i.id == windowSizeId) ?? windowSizeList[0] +} + +const getIconPath = (name: string): Electron.NativeImage => { + return nativeImage.createFromPath(join(global.staticPath, 'images/taskbar', name + '.png')) +} + +export const createTaskBarButtons = ({ + empty = false, + collect = false, + play = false, + next = true, + prev = true, +}: LX.TaskBarButtonFlags, onClick: (action: LX.Player.StatusButtonActions) => void): Electron.ThumbarButton[] => { + const buttons: Electron.ThumbarButton[] = [ + collect + ? { + icon: getIconPath('collected'), + click() { + onClick('unCollect') + }, + tooltip: '取消收藏', + flags: ['nobackground'], + } + : { + icon: getIconPath('collect'), + click() { + onClick('collect') + }, + tooltip: '收藏', + flags: ['nobackground'], + }, + { + icon: getIconPath('prev'), + click() { + onClick('prev') + }, + tooltip: '上一曲', + flags: prev ? ['nobackground'] : ['nobackground', 'disabled'], + }, + play + ? { + icon: getIconPath('pause'), + click() { + onClick('pause') + }, + tooltip: '暂停', + flags: ['nobackground'], + } + : { + icon: getIconPath('play'), + click() { + onClick('play') + }, + tooltip: '播放', + flags: ['nobackground'], + }, + { + icon: getIconPath('next'), + click() { + onClick('next') + }, + tooltip: '下一曲', + flags: next ? ['nobackground'] : ['nobackground', 'disabled'], + }, + ] + if (empty) { + for (const button of buttons) { + button.flags = ['nobackground', 'disabled'] + } + } + return buttons +} diff --git a/src/main/rendererEvents/appName.js b/src/main/rendererEvents/appName.js deleted file mode 100644 index 22d2744b..00000000 --- a/src/main/rendererEvents/appName.js +++ /dev/null @@ -1,13 +0,0 @@ -const { mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc') -const { app } = require('electron') -const { name: defaultName } = require('../../../package.json') - - -mainOn(ipcMainWindowNames.set_app_name, (event, params) => { - if (params == null) { - app.setName(defaultName) - } else { - app.setName(params.name) - } -}) - diff --git a/src/main/rendererEvents/appSetting.js b/src/main/rendererEvents/appSetting.js deleted file mode 100644 index 2198a0c7..00000000 --- a/src/main/rendererEvents/appSetting.js +++ /dev/null @@ -1,11 +0,0 @@ -const { mainWindow: MAIN_WINDOW_EVENT_NAME } = require('../events/_name') -const { NAMES: { mainWindow: ipcMainWindowNames }, mainHandle } = require('../../common/ipc') - - -mainHandle(ipcMainWindowNames.set_app_setting, (event, config) => { - if (!config) return - global.lx_core.setAppConfig(config, MAIN_WINDOW_EVENT_NAME.name) - return global.appSetting -}) - -mainHandle(ipcMainWindowNames.get_setting, async() => ({ setting: global.appSetting, version: global.appSettingVersion })) diff --git a/src/main/rendererEvents/cache.js b/src/main/rendererEvents/cache.js deleted file mode 100644 index ef6441eb..00000000 --- a/src/main/rendererEvents/cache.js +++ /dev/null @@ -1,11 +0,0 @@ -const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc') - -mainHandle(ipcMainWindowNames.clear_cache, async(event, options) => { - if (!global.modules.mainWindow) throw new Error('mainWindow is undefined') - return global.modules.mainWindow.webContents.session.clearCache() -}) - -mainHandle(ipcMainWindowNames.get_cache_size, async(event, options) => { - if (!global.modules.mainWindow) throw new Error('mainWindow is undefined') - return global.modules.mainWindow.webContents.session.getCacheSize() -}) diff --git a/src/main/rendererEvents/data.js b/src/main/rendererEvents/data.js deleted file mode 100644 index ef25b573..00000000 --- a/src/main/rendererEvents/data.js +++ /dev/null @@ -1,8 +0,0 @@ -const { mainOn, NAMES: { mainWindow: ipcMainWindowNames }, mainHandle } = require('../../common/ipc') -const getStore = require('@common/store') - - -mainHandle(ipcMainWindowNames.get_data, async(event, path) => getStore('data').get(path)) - - -mainOn(ipcMainWindowNames.save_data, (event, { path, data }) => getStore('data').set(path, data)) diff --git a/src/main/rendererEvents/getDataPath.js b/src/main/rendererEvents/getDataPath.js deleted file mode 100644 index 2348d642..00000000 --- a/src/main/rendererEvents/getDataPath.js +++ /dev/null @@ -1,5 +0,0 @@ -const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc') -const { app } = require('electron') - -mainHandle(ipcMainWindowNames.get_data_path, async() => app.getPath('userData')) - diff --git a/src/main/rendererEvents/getEnvParams.js b/src/main/rendererEvents/getEnvParams.js deleted file mode 100644 index b6278769..00000000 --- a/src/main/rendererEvents/getEnvParams.js +++ /dev/null @@ -1,9 +0,0 @@ -const { mainHandle, mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc') - -mainHandle(ipcMainWindowNames.get_env_params, async(event, options) => { - return global.envParams -}) - -mainOn(ipcMainWindowNames.clear_env_params_deeplink, () => { - global.envParams.deeplink = null -}) diff --git a/src/main/rendererEvents/hotKey.js b/src/main/rendererEvents/hotKey.js deleted file mode 100644 index 49e0bd12..00000000 --- a/src/main/rendererEvents/hotKey.js +++ /dev/null @@ -1,42 +0,0 @@ -const { mainSend, NAMES: { mainWindow: ipcMainWindowNames }, mainOn, mainHandle } = require('../../common/ipc') -const { mainWindow: MAIN_WINDOW_EVENT_NAME, hotKey: HOT_KEY_EVENT_NAME } = require('../events/_name') -const getStore = require('@common/store') - - -// const { registerHotkey, unRegisterHotkey } = require('../modules/hotKey/utils') - -// mainHandle(ipcMainWindowNames.set_hot_key_config, async(event, { action, data }) => { -// switch (action) { -// case 'config': -// global.lx_event.hotKey.saveConfig(data.data, MAIN_WINDOW_EVENT_NAME.source) -// return -// case 'register': -// return registerHotkey(data) -// case 'unregister': -// return unRegisterHotkey(data) -// } -// }) - -mainHandle(ipcMainWindowNames.get_hot_key, async() => { - const electronStore_hotKey = getStore('hotKey') - return { - local: electronStore_hotKey.get('local'), - global: electronStore_hotKey.get('global'), - } -}) - -mainOn(ipcMainWindowNames.quit, () => global.lx_event.mainWindow.quit()) -mainOn(ipcMainWindowNames.min_toggle, () => global.lx_event.mainWindow.toggleMinimize()) -mainOn(ipcMainWindowNames.hide_toggle, () => global.lx_event.mainWindow.toggleHide()) - - -global.lx_event.hotKey.on(HOT_KEY_EVENT_NAME.config, (config, source) => { - if (!global.modules.mainWindow || source === MAIN_WINDOW_EVENT_NAME.name) return - mainSend(global.modules.mainWindow, ipcMainWindowNames.set_hot_key_config, { config, source }) -}) - -global.lx_event.hotKey.on(HOT_KEY_EVENT_NAME.keyDown, ({ type, key }) => { - let info = global.appHotKey.config.global.keys[key] - if (!info || info.type != MAIN_WINDOW_EVENT_NAME.name) return - mainSend(global.modules.mainWindow, ipcMainWindowNames.key_down, { type, key }) -}) diff --git a/src/main/rendererEvents/index.js b/src/main/rendererEvents/index.js deleted file mode 100644 index 0ead3981..00000000 --- a/src/main/rendererEvents/index.js +++ /dev/null @@ -1,34 +0,0 @@ -const { isWin } = require('@common/utils') -// require('./request') -// require('./appName') -require('./progressBar') -require('./trafficLight') -require('./musicMeta') -require('./selectDir') -require('./setWindowSize') -require('./showSaveDialog') -require('./cache') -require('./setIgnoreMouseEvent') -require('./getEnvParams') -require('./appSetting') -require('./setLyricInfo') -require('./hotKey') -require('./getDataPath') -require('./showDialog') -require('./playList') -require('./data') -require('./lyric') -require('./musicUrl') -require('./systemFonts') -require('./wait') -require('./openDevtools') -require('./nativeTheme') - -if (isWin) require('./taskbar') - -require('./kw_decodeLyric') - -require('./userApi') -require('./sync') -require('./s2t') - diff --git a/src/main/rendererEvents/lyric.js b/src/main/rendererEvents/lyric.js deleted file mode 100644 index a96b2580..00000000 --- a/src/main/rendererEvents/lyric.js +++ /dev/null @@ -1,20 +0,0 @@ -const { mainOn, NAMES: { mainWindow: ipcMainWindowNames }, mainHandle } = require('../../common/ipc') -const getStore = require('@common/store') - -const LRC_RAW = 'lyrics' -const LRC_EDITED = 'lyrics_edited' - -mainHandle(ipcMainWindowNames.get_lyric, async(event, id) => { - return getStore(LRC_EDITED, true, false).get(id) || getStore(LRC_RAW, true, false).get(id) || {} -}) - - -// 原始歌词 -mainHandle(ipcMainWindowNames.get_lyric_raw, async(event, id) => getStore(LRC_RAW, true, false).get(id) || {}) -mainOn(ipcMainWindowNames.save_lyric_raw, (event, { id, lyrics }) => getStore(LRC_RAW, true, false).set(id, lyrics)) -mainOn(ipcMainWindowNames.clear_lyric_raw, () => getStore(LRC_RAW, true, false).clear()) - -// 已编辑的歌词 -mainHandle(ipcMainWindowNames.get_lyric_edited, async(event, id) => getStore(LRC_EDITED, true, false).get(id) || {}) -mainOn(ipcMainWindowNames.save_lyric_edited, (event, { id, lyrics }) => getStore(LRC_EDITED, true, false).set(id, lyrics)) -mainOn(ipcMainWindowNames.remove_lyric_edited, async(event, id) => getStore(LRC_EDITED, true, false).delete(id)) diff --git a/src/main/rendererEvents/musicMeta.js b/src/main/rendererEvents/musicMeta.js deleted file mode 100644 index 7c0bdbab..00000000 --- a/src/main/rendererEvents/musicMeta.js +++ /dev/null @@ -1,6 +0,0 @@ -const { mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc') -const { setMeta } = require('../utils/musicMeta') - -mainOn(ipcMainWindowNames.set_music_meta, (event, { filePath, meta }) => { - setMeta(filePath, meta) -}) diff --git a/src/main/rendererEvents/musicUrl.js b/src/main/rendererEvents/musicUrl.js deleted file mode 100644 index 5bac64b8..00000000 --- a/src/main/rendererEvents/musicUrl.js +++ /dev/null @@ -1,10 +0,0 @@ -const { mainOn, NAMES: { mainWindow: ipcMainWindowNames }, mainHandle } = require('../../common/ipc') -const getStore = require('@common/store') - - -mainHandle(ipcMainWindowNames.get_music_url, async(event, id) => getStore('musicUrls', true, false).get(id) || '') - - -mainOn(ipcMainWindowNames.save_music_url, (event, { id, url }) => getStore('musicUrls', true, false).set(id, url)) - -mainOn(ipcMainWindowNames.clear_music_url, () => getStore('musicUrls', true, false).clear()) diff --git a/src/main/rendererEvents/nativeTheme.js b/src/main/rendererEvents/nativeTheme.js deleted file mode 100644 index 1d3880b8..00000000 --- a/src/main/rendererEvents/nativeTheme.js +++ /dev/null @@ -1,11 +0,0 @@ -const { nativeTheme } = require('electron') - -const { NAMES: { mainWindow: ipcMainWindowNames }, mainSend } = require('@common/ipc') - -nativeTheme.addListener('updated', (event) => { - // console.log(event.sender.shouldUseDarkColors) - if (!global.modules.mainWindow) return - mainSend(global.modules.mainWindow, ipcMainWindowNames.system_theme_change, event.sender.shouldUseDarkColors) - // console.log(nativeTheme.themeSource) -}) - diff --git a/src/main/rendererEvents/openDevtools.js b/src/main/rendererEvents/openDevtools.js deleted file mode 100644 index cb8bee00..00000000 --- a/src/main/rendererEvents/openDevtools.js +++ /dev/null @@ -1,12 +0,0 @@ -const { mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc') -const { openDevTools } = require('@main/utils') - -mainOn(ipcMainWindowNames.open_dev_tools, event => { - if (global.modules.mainWindow) { - if (global.modules.mainWindow.isDevToolsOpened()) { - global.modules.mainWindow.webContents.closeDevTools() - } else { - openDevTools(global.modules.mainWindow.webContents) - } - } -}) diff --git a/src/main/rendererEvents/playList.js b/src/main/rendererEvents/playList.js deleted file mode 100644 index 476a4d59..00000000 --- a/src/main/rendererEvents/playList.js +++ /dev/null @@ -1,36 +0,0 @@ -const { mainOn, NAMES: { mainWindow: ipcMainWindowNames }, mainHandle } = require('../../common/ipc') -const getStore = require('@common/store') - - -mainHandle(ipcMainWindowNames.get_playlist, async(event, isIgnoredError = false) => { - const electronStore_list = getStore('playList', isIgnoredError, false) - - return { - defaultList: electronStore_list.get('defaultList'), - loveList: electronStore_list.get('loveList'), - tempList: electronStore_list.get('tempList'), - userList: electronStore_list.get('userList'), - downloadList: getStore('downloadList').get('list'), - } -}) - -const handleSaveList = ({ defaultList, loveList, userList, tempList }) => { - let data = {} - if (defaultList != null) data.defaultList = defaultList - if (loveList != null) data.loveList = loveList - if (userList != null) data.userList = userList - if (tempList != null) data.tempList = tempList - getStore('playList').set(data) -} -mainOn(ipcMainWindowNames.save_playlist, (event, { type, data }) => { - data = JSON.parse(data) - switch (type) { - case 'myList': - handleSaveList(data) - global.lx_event.common.saveMyList(data) - break - case 'downloadList': - getStore('downloadList').set('list', data) - break - } -}) diff --git a/src/main/rendererEvents/progressBar.js b/src/main/rendererEvents/progressBar.js deleted file mode 100644 index 672e461c..00000000 --- a/src/main/rendererEvents/progressBar.js +++ /dev/null @@ -1,10 +0,0 @@ -const { mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc') - - -mainOn(ipcMainWindowNames.progress, (event, params) => { - // console.log(params) - global.modules.mainWindow && global.modules.mainWindow.setProgressBar(params.status, { - mode: params.mode || 'normal', - }) -}) - diff --git a/src/main/rendererEvents/request.js b/src/main/rendererEvents/request.js deleted file mode 100644 index f64407cb..00000000 --- a/src/main/rendererEvents/request.js +++ /dev/null @@ -1,45 +0,0 @@ -const request = require('request') - -const { mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc') - -const tasks = [] - -mainOn(ipcMainWindowNames.handle_request, (event, options) => { - // console.log(args) - if (!options) return - let index = fetchData(options, (err, resp) => { - tasks[index] = null - if (err) { - console.log(err) - event.sender.send('response', err.message, null) - return - } - event.sender.send('response', null, resp.body) - }) - event.returnValue = index -}) - -mainOn(ipcMainWindowNames.cancel_request, (event, index) => { - if (index == null) return - let r = tasks[index] - if (r == null) return - r.abort() - tasks[index] = null -}) - -const fetchData = (options, callback) => pushTask(tasks, request(options.url, { - method: options.method, - headers: options.headers, - Origin: options.origin, -}, (err, resp) => { - if (err) return callback(err, null) - callback(null, resp) -})) - -const pushTask = (tasks, newTask) => { - for (const [index, task] of tasks.entries()) { - if (task == null) { - return tasks[index].push(newTask) - } - } -} diff --git a/src/main/rendererEvents/restartWindow.js b/src/main/rendererEvents/restartWindow.js deleted file mode 100644 index a296a8dd..00000000 --- a/src/main/rendererEvents/restartWindow.js +++ /dev/null @@ -1,13 +0,0 @@ -const { mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc') - - -mainOn(ipcMainWindowNames.restart_window, (event, name) => { - console.log(name) - switch (name) { - case 'main': - default: - - break - } -}) - diff --git a/src/main/rendererEvents/s2t.js b/src/main/rendererEvents/s2t.js deleted file mode 100644 index b13124ad..00000000 --- a/src/main/rendererEvents/s2t.js +++ /dev/null @@ -1,9 +0,0 @@ -const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc') -const { tranditionalize } = require('../utils/simplify-chinese-main') - - -mainHandle(ipcMainWindowNames.lang_s2t, async(event, textBase64) => { - if (!global.modules.mainWindow) throw new Error('mainWindow is undefined') - const text = tranditionalize(Buffer.from(textBase64, 'base64').toString()) - return Buffer.from(text).toString('base64') -}) diff --git a/src/main/rendererEvents/selectDir.js b/src/main/rendererEvents/selectDir.js deleted file mode 100644 index 2f43af87..00000000 --- a/src/main/rendererEvents/selectDir.js +++ /dev/null @@ -1,8 +0,0 @@ -const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc') -const { dialog } = require('electron') - -mainHandle(ipcMainWindowNames.select_dir, async(event, options) => { - if (!global.modules.mainWindow) throw new Error('mainWindow is undefined') - return dialog.showOpenDialog(global.modules.mainWindow, options) -}) - diff --git a/src/main/rendererEvents/setIgnoreMouseEvent.js b/src/main/rendererEvents/setIgnoreMouseEvent.js deleted file mode 100644 index 39dd288f..00000000 --- a/src/main/rendererEvents/setIgnoreMouseEvent.js +++ /dev/null @@ -1,8 +0,0 @@ -const { mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc') - -mainOn(ipcMainWindowNames.set_ignore_mouse_events, (event, isIgnored) => { - if (!global.modules.mainWindow) return - isIgnored - ? global.modules.mainWindow.setIgnoreMouseEvents(true, { forward: true }) - : global.modules.mainWindow.setIgnoreMouseEvents(false) -}) diff --git a/src/main/rendererEvents/setLyricInfo.js b/src/main/rendererEvents/setLyricInfo.js deleted file mode 100644 index 642bd8aa..00000000 --- a/src/main/rendererEvents/setLyricInfo.js +++ /dev/null @@ -1,10 +0,0 @@ -const { mainOn, mainSend, NAMES: { mainWindow: ipcMainWindowNames } } = require('@common/ipc') - - -mainOn(ipcMainWindowNames.set_lyric_info, (event, info) => { - if (info.info == null) { - global.lx_event.mainWindow.setLyricInfo(info) - return - } - mainSend(global.modules[info.info.modal], info.info.name, info) -}) diff --git a/src/main/rendererEvents/setWindowSize.js b/src/main/rendererEvents/setWindowSize.js deleted file mode 100644 index 599f9c14..00000000 --- a/src/main/rendererEvents/setWindowSize.js +++ /dev/null @@ -1,7 +0,0 @@ -const { mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc') - -mainOn(ipcMainWindowNames.set_window_size, (event, options) => { - if (!global.modules.mainWindow) return - global.modules.mainWindow.setBounds(options) -}) - diff --git a/src/main/rendererEvents/showDialog.js b/src/main/rendererEvents/showDialog.js deleted file mode 100644 index 171fcaa1..00000000 --- a/src/main/rendererEvents/showDialog.js +++ /dev/null @@ -1,12 +0,0 @@ -const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc') -const { dialog } = require('electron') - - -mainHandle(ipcMainWindowNames.show_dialog, async(event, { type, message, detail }) => { - if (!global.modules.mainWindow) throw new Error('mainWindow is undefined') - dialog.showMessageBoxSync(global.modules.mainWindow, { - type, - message, - detail, - }) -}) diff --git a/src/main/rendererEvents/showSaveDialog.js b/src/main/rendererEvents/showSaveDialog.js deleted file mode 100644 index bed3383b..00000000 --- a/src/main/rendererEvents/showSaveDialog.js +++ /dev/null @@ -1,8 +0,0 @@ -const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc') -const { dialog } = require('electron') - -mainHandle(ipcMainWindowNames.show_save_dialog, async(event, options) => { - if (!global.modules.mainWindow) throw new Error('mainWindow is undefined') - return dialog.showSaveDialog(global.modules.mainWindow, options) -}) - diff --git a/src/main/rendererEvents/sync.js b/src/main/rendererEvents/sync.js deleted file mode 100644 index 55569f84..00000000 --- a/src/main/rendererEvents/sync.js +++ /dev/null @@ -1,33 +0,0 @@ -const { mainSend, NAMES: { mainWindow: ipcMainWindowNames }, mainOn, mainHandle } = require('@common/ipc') -const { eventNames, modules, startServer, stopServer, getStatus, generateCode } = require('../modules/sync') - - -mainOn(ipcMainWindowNames.sync_action_list, (event, { action, data }) => { - modules.list.sendListAction(action, data) -}) - -mainHandle(ipcMainWindowNames.sync_enable, (event, { enable, port }) => { - return enable ? startServer(port) : stopServer() -}) - -mainHandle(ipcMainWindowNames.sync_get_status, () => { - return getStatus() -}) - -mainHandle(ipcMainWindowNames.sync_generate_code, () => { - return generateCode() -}) - -mainOn(ipcMainWindowNames.sync_list, (event, { action, data }) => { - global.lx_event.sync.sync_handle_list({ action, data }) -}) - -global.lx_event.sync.on(eventNames.sync_action_list, ({ action, data }) => { - mainSend(global.modules.mainWindow, ipcMainWindowNames.sync_action_list, { action, data }) -}) -global.lx_event.sync.on(eventNames.status, status => { - mainSend(global.modules.mainWindow, ipcMainWindowNames.sync_status, status) -}) -global.lx_event.sync.on(eventNames.sync_list, ({ action, data }) => { - mainSend(global.modules.mainWindow, ipcMainWindowNames.sync_list, { action, data }) -}) diff --git a/src/main/rendererEvents/systemFonts.js b/src/main/rendererEvents/systemFonts.js deleted file mode 100644 index cfd7f088..00000000 --- a/src/main/rendererEvents/systemFonts.js +++ /dev/null @@ -1,5 +0,0 @@ -const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('@common/ipc') -const { getFonts } = require('@main/utils/fontManage') - -mainHandle(ipcMainWindowNames.get_system_fonts, getFonts) - diff --git a/src/main/rendererEvents/taskbar.js b/src/main/rendererEvents/taskbar.js deleted file mode 100644 index c128a3ec..00000000 --- a/src/main/rendererEvents/taskbar.js +++ /dev/null @@ -1,18 +0,0 @@ -const { mainSend, mainOn, NAMES: { mainWindow: ipcMainWindowNames }, mainHandle } = require('@common/ipc') -const { setThumbnailClip, setThumbarButtons, eventNames } = require('../modules/taskbar') -const { mainWindow: MAIN_WINDOW_EVENT_NAME } = require('@main/events/_name') - -const handleThumbarButtonClick = action => { - mainSend(global.modules.mainWindow, ipcMainWindowNames.taskbar_on_thumbar_button_click, action) -} - -global.lx_event.taskbar.on(eventNames.thumbarButtonClick, handleThumbarButtonClick) -global.lx_event.mainWindow.on(MAIN_WINDOW_EVENT_NAME.show, setThumbarButtons) - -mainHandle(ipcMainWindowNames.taskbar_set_thumbnail_clip, (event, clip) => { - return setThumbnailClip(clip) -}) - -mainOn(ipcMainWindowNames.taskbar_set_thumbar_buttons, (event, buttons) => { - setThumbarButtons(buttons) -}) diff --git a/src/main/rendererEvents/trafficLight.js b/src/main/rendererEvents/trafficLight.js deleted file mode 100644 index 2e43f281..00000000 --- a/src/main/rendererEvents/trafficLight.js +++ /dev/null @@ -1,34 +0,0 @@ -const { app } = require('electron') -const { mainOn, mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc') -const { isLinux } = require('@common/utils') - -mainOn(ipcMainWindowNames.min, event => { - if (global.modules.mainWindow) { - global.modules.mainWindow.minimize() - } -}) -mainOn(ipcMainWindowNames.max, event => { - if (global.modules.mainWindow) { - global.modules.mainWindow.maximize() - } -}) -mainOn(ipcMainWindowNames.close, (event, isForce) => { - if (isForce) return app.exit(0) - global.isTrafficLightClose = true - if (global.modules.mainWindow) global.modules.mainWindow.close() -}) -mainHandle(ipcMainWindowNames.fullscreen, async(event, isFullscreen) => { - if (!global.modules.mainWindow) return false - if (isLinux) { // linux 需要先设置为可调整窗口大小才能全屏 - if (isFullscreen) { - await global.modules.mainWindow.setResizable(isFullscreen) - await global.modules.mainWindow.setFullScreen(isFullscreen) - } else { - await global.modules.mainWindow.setFullScreen(isFullscreen) - await global.modules.mainWindow.setResizable(isFullscreen) - } - } else { - await global.modules.mainWindow.setFullScreen(isFullscreen) - } - return isFullscreen -}) diff --git a/src/main/rendererEvents/userApi.js b/src/main/rendererEvents/userApi.js deleted file mode 100644 index 559386c0..00000000 --- a/src/main/rendererEvents/userApi.js +++ /dev/null @@ -1,43 +0,0 @@ -const { mainSend, mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('@common/ipc') -const { getApiList, importApi, removeApi, setApi, getStatus, request, cancelRequest, eventNames, setAllowShowUpdateAlert } = require('../modules/userApi') - -const handleStatusChange = status => { - mainSend(global.modules.mainWindow, ipcMainWindowNames.user_api_status, status) -} -const handleShowUpdateAlert = info => { - mainSend(global.modules.mainWindow, ipcMainWindowNames.user_api_show_update_alert, info) -} - -global.lx_event.userApi.on(eventNames.status, handleStatusChange) -global.lx_event.userApi.on(eventNames.showUpdateAlert, handleShowUpdateAlert) - -mainHandle(ipcMainWindowNames.import_user_api, async(event, script) => { - return importApi(script) -}) - -mainHandle(ipcMainWindowNames.remove_user_api, (event, apiIds) => { - return removeApi(apiIds) -}) - -mainHandle(ipcMainWindowNames.set_user_api, (event, apiId) => { - return setApi(apiId) -}) - -mainHandle(ipcMainWindowNames.get_user_api_list, event => { - return getApiList() -}) - -mainHandle(ipcMainWindowNames.get_user_api_status, event => { - return getStatus() -}) - -mainHandle(ipcMainWindowNames.user_api_set_allow_update_alert, (event, { id, enable }) => { - return setAllowShowUpdateAlert(id, enable) -}) - -mainHandle(ipcMainWindowNames.request_user_api, (event, musicInfo) => { - return request(musicInfo) -}) -mainHandle(ipcMainWindowNames.request_user_api_cancel, (event, requestKey) => { - return cancelRequest(requestKey) -}) diff --git a/src/main/rendererEvents/wait.js b/src/main/rendererEvents/wait.js deleted file mode 100644 index ca8d842d..00000000 --- a/src/main/rendererEvents/wait.js +++ /dev/null @@ -1,46 +0,0 @@ -const { mainOn, mainHandle, mainSend, NAMES: { mainWindow: ipcMainWindowNames } } = require('@common/ipc') - -const timeoutMap = new Map() - -mainHandle(ipcMainWindowNames.wait, (event, { time, id }) => { - return new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - if (timeoutMap.has(id)) timeoutMap.delete(id) - resolve() - }, time) - timeoutMap.set(id, { - timeout, - resolve, - reject, - }) - }) -}) - -mainOn(ipcMainWindowNames.wait_cancel, (event, id) => { - if (!timeoutMap.has(id)) return - const timeout = timeoutMap.get(id) - timeoutMap.delete(id) - clearTimeout(timeout.timeout) - timeout.reject('cancelled') -}) - -mainOn(ipcMainWindowNames.interval, (event, { time, id }) => { - if (timeoutMap.has(id)) return - const timeout = setInterval(() => { - if (global.modules.mainWindow) mainSend(global.modules.mainWindow, ipcMainWindowNames.interval_callback, id) - }, time) - - timeoutMap.set(id, { - timeout, - type: 'interval', - time, - }) -}) - -mainOn(ipcMainWindowNames.interval_cancel, (event, id) => { - if (!timeoutMap.has(id)) return - const timeout = timeoutMap.get(id) - timeoutMap.delete(id) - if (timeout.type != 'interval') return - clearInterval(timeout.timeout) -}) diff --git a/src/main/rendererEvents/winEvent.js b/src/main/rendererEvents/winEvent.js deleted file mode 100644 index 205d82fe..00000000 --- a/src/main/rendererEvents/winEvent.js +++ /dev/null @@ -1,48 +0,0 @@ -const { isWin } = require('../../common/utils') -const { openDevTools } = require('@main/utils') -const { mainSend, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc') -global.isQuitting = false -global.isTrafficLightClose = false // 是否点击软件上的关闭按钮关闭 - -module.exports = mainWindow => { - mainWindow.on('close', event => { - if (global.isQuitting || !global.appSetting.tray.isShow || (!isWin && !global.isTrafficLightClose)) { - mainWindow.setProgressBar(-1) - global.lx_event.winLyric.close() - return - } - - if (global.isTrafficLightClose) global.isTrafficLightClose = false - event.preventDefault() - mainWindow.hide() - }) - - mainWindow.on('closed', () => { - mainWindow = global.modules.mainWindow = null - }) - - // mainWindow.on('restore', () => { - // mainWindow.webContents.send('restore') - // }) - mainWindow.on('focus', () => { - mainSend(mainWindow, ipcMainWindowNames.focus) - global.lx_event.mainWindow.focus() - }) - - mainWindow.on('blur', () => { - global.lx_event.mainWindow.blur() - }) - - mainWindow.once('ready-to-show', () => { - mainWindow.show() - global.lx_event.mainWindow.readyToShow() - if (global.envParams.cmdParams.debug) openDevTools(global.modules.mainWindow.webContents) - }) - - mainWindow.on('show', () => { - global.lx_event.mainWindow.show() - }) - mainWindow.on('hide', () => { - global.lx_event.mainWindow.hide() - }) -} diff --git a/src/main/tsconfig.json b/src/main/tsconfig.json new file mode 100644 index 00000000..6555b209 --- /dev/null +++ b/src/main/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "module": "esnext", /* Specify what module code is generated. */ + "moduleResolution": "nodenext", /* Specify how TypeScript looks up a file from a given module specifier. */ + "paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */ + "@common/*": ["common/*"], + "@main/*": ["main/*"], + "@static/*": ["static/*"], + "@/*": ["./*"], + }, + "typeRoots": [ /* Specify multiple folders that act like './node_modules/@types'. */ + "./types", + ], + }, + // "include": [ + // "**/*.ts", + // "**/*.js", + // "**/*.vue", + // "**/*.json", + // ], + // "exclude": ["dbService"] +} diff --git a/src/main/types/app.d.ts b/src/main/types/app.d.ts new file mode 100644 index 00000000..122f5c34 --- /dev/null +++ b/src/main/types/app.d.ts @@ -0,0 +1,44 @@ +/* eslint-disable no-var */ +// import { Event as WinMainEvent } from '@main/modules/winMain/event' +// import { Event as WinLyricEvent } from '@main/modules/winLyric/event' +import { AppType, ListType } from '@main/event' +import { DBSeriveTypes } from '@main/worker/utils' + +declare global { + // declare module NodeJS { + // export interface Global { + // lx: { + // app_event: { + // winMain: WinMainEvent + // winLyric: WinLyricEvent + // } + // } + // } + // } + interface Lx { + appSetting: LX.AppSetting + hotKey: { + enable: boolean + config: LX.HotKeyConfigAll + state: LX.HotKeyState + } + isTrafficLightClose: boolean + isQuitting: boolean + event_app: AppType + event_list: ListType + worker: { + dbService: DBSeriveTypes + } + theme: LX.ThemeSetting + } + + var isDev: boolean + var envParams: LX.EnvParams + var staticPath: string + var lxDataPath: string + var lxOldDataPath: string + var lx: Lx + var appWorder: AppWorder +} + + diff --git a/src/main/types/common.d.ts b/src/main/types/common.d.ts new file mode 100644 index 00000000..68cafe1b --- /dev/null +++ b/src/main/types/common.d.ts @@ -0,0 +1,12 @@ +import '@common/types/utils' +import '@common/types/app_setting' +import '@common/types/common' +import '@common/types/user_api' +import '@common/types/sync' +import '@common/types/list' +import '@common/types/download_list' +import '@common/types/music' +import '@common/types/player' +import '@common/types/desktop_lyric' +import '@common/types/theme' +import '@common/types/ipc_main' diff --git a/src/main/types/db_service.d.ts b/src/main/types/db_service.d.ts new file mode 100644 index 00000000..6ab1fd2f --- /dev/null +++ b/src/main/types/db_service.d.ts @@ -0,0 +1,82 @@ +declare namespace LX { + namespace DBService { + + interface MusicInfo { + id: string + listId: string + name: string + singer: string + interval: string | null + source: LX.Music.MusicInfo['source'] + meta: string + order: number + } + + interface MusicInfoOrder { + listId: string + musicInfoId: string + order: number + } + + interface MusicInfoQuery { + listId: string + } + + interface MusicInfoRemove { + listId: string + id: string + } + + interface ListMusicInfoQuery { + listId: string + musicInfoId: string + } + + interface UserListInfo { + id: string + name: string + source?: LX.Source + sourceListId?: string + position: number + locationUpdateTime: number | null + } + + type Lyricnfo = { + id: string + type: 'lyric' + text: string + source: 'raw' | 'edited' + } | { + id: string + type: keyof Omit + text: string | null + source: 'raw' | 'edited' + } + + interface MusicUrlInfo { + id: string + url: string + } + + interface DownloadMusicInfo { + id: string + isComplate: 0 | 1 + status: LX.Download.DownloadTaskStatus + statusText: string + progress_downloaded: number + progress_total: number + url: string | null + quality: LX.Quality + ext: LX.Download.FileExt + fileName: string + filePath: string + musicInfo: string + position: number + } + + interface MusicInfoOtherSource extends Omit { + source_id: string + } + + } +} diff --git a/src/main/types/global.d.ts b/src/main/types/global.d.ts new file mode 100644 index 00000000..177ed414 --- /dev/null +++ b/src/main/types/global.d.ts @@ -0,0 +1,25 @@ + + +// interface Lx { +// appSetting: LX.AppSetting +// hotKey: { +// enable: boolean +// config: LX.HotKeyConfigAll +// state: LX.HotKeyState +// } +// isTrafficLightClose: boolean +// isQuitting: boolean +// } + +// // declare module NodeJS { +// // interface Global { +// // isDev: boolean +// // envParams: LX.EnvParams +// // staticPath: string +// // lx: Lx +// // } +// // } + +declare const webpackStaticPath: string +declare const webpackUserApiPath: string + diff --git a/src/main/types/sync.d.ts b/src/main/types/sync.d.ts new file mode 100644 index 00000000..364a8728 --- /dev/null +++ b/src/main/types/sync.d.ts @@ -0,0 +1,41 @@ +import { Socket as _Socket, RemoteSocket as _RemoteSocket } from 'socket.io' + +interface DefaultEventsMap { + [event: string]: (...args: any[]) => void +} + + +declare global { + namespace LX { + namespace Sync { + class Socket extends _Socket { + data: SocketData + } + class RemoteSocket extends _RemoteSocket { + readonly data: SyncSocketData + } + interface Data { + action: string + data: any + } + interface SocketData { + snapshotFilePath: string + isCreatedSnapshot: boolean + keyInfo: KeyInfo + } + type Action = 'list:sync' + type ListAction = 'getData' | 'finished' + } + } + + // interface SyncListActionData_none { + // action: 'finished' + // } + // interface SyncListActionData_getData { + // action: 'getData' + // data: 'all' + // } + + // type SyncListActionData = SyncListActionData_none | SyncListActionData_getData +} + diff --git a/src/main/types/worker.d.ts b/src/main/types/worker.d.ts new file mode 100644 index 00000000..6eff5d11 --- /dev/null +++ b/src/main/types/worker.d.ts @@ -0,0 +1,10 @@ +import { workerDBSeriveTypes } from '@main/worker/dbService' + +declare global { + // interface WorkerDBSeriveTypes { + // list: typeof list + // } + namespace LX { + type WorkerDBSeriveListTypes = workerDBSeriveTypes + } +} diff --git a/src/main/utils/fontManage.js b/src/main/utils/fontManage.ts similarity index 65% rename from src/main/utils/fontManage.js rename to src/main/utils/fontManage.ts index 80208476..9c12d9aa 100644 --- a/src/main/utils/fontManage.js +++ b/src/main/utils/fontManage.ts @@ -4,6 +4,8 @@ // exports.getAvailableFontFamilies = getAvailableFontFamilies -const fontList = require('font-list') +import { getFonts } from 'font-list' -exports.getFonts = fontList.getFonts +export { + getFonts, +} diff --git a/src/main/utils/index.js b/src/main/utils/index.js deleted file mode 100644 index ac83ce72..00000000 --- a/src/main/utils/index.js +++ /dev/null @@ -1,49 +0,0 @@ -const { windowSizeList } = require('../../common/config') -const { objectDeepMerge, throttle, initSetting } = require('../../common/utils') -const getStore = require('@common/store') - -exports.getWindowSizeInfo = ({ windowSizeId = 1 } = {}) => { - return windowSizeList.find(i => i.id === windowSizeId) || windowSizeList[0] -} - -exports.getAppSetting = () => { - return getStore('config').get('setting') -} - -exports.getAppHotKeyConfig = () => { - const electronStore_hotKey = getStore('hotKey') - - return { - global: electronStore_hotKey.get('global'), - local: electronStore_hotKey.get('local'), - } -} -const saveHotKeyConfig = throttle(config => { - for (const key of Object.keys(config)) { - global.appHotKey.config[key] = config[key] - getStore('hotKey').set(key, config[key]) - } -}) -exports.saveAppHotKeyConfig = config => { - saveHotKeyConfig(config) -} - -// const saveSetting = throttle(n => { -// electronStore_config.set('setting', n) -// }) -exports.updateSetting = (settings) => { - objectDeepMerge(global.appSetting, settings) - getStore('config').set('setting', global.appSetting) - exports.initSetting(false) -} -exports.initSetting = (isShowErrorAlert = true) => { - const info = initSetting(isShowErrorAlert) - global.appSetting = info.setting - global.appSettingVersion = info.version -} - -exports.openDevTools = webContents => { - webContents.openDevTools({ - mode: 'undocked', - }) -} diff --git a/src/main/utils/index.ts b/src/main/utils/index.ts new file mode 100644 index 00000000..21a768e3 --- /dev/null +++ b/src/main/utils/index.ts @@ -0,0 +1,277 @@ +import { throttle } from '@common/utils' +import migrateSetting from '@common/utils/migrateSetting' +import getStore from '@main/utils/store' +import { STORE_NAMES, URL_SCHEME_RXP } from '@common/constants' +import defaultSetting from '@common/defaultSetting' +import defaultHotKey from '@common/defaultHotKey' +import { migrateDataJson, migrateHotKey, migrateUserApi, parseDataFile } from './migrate' +import { commonColorNames, commonLightColorValues, commonDarkColorValues } from '@common/config' +import { nativeTheme } from 'electron' +import { joinPath } from '@common/utils/nodejs' +import themes from '@common/theme/index.json' + +export const parseEnvParams = (): { cmdParams: LX.CmdParams, deeplink: string | null } => { + const cmdParams: LX.CmdParams = {} + let deeplink = null + const rx = /^-\w+/ + for (let param of process.argv) { + if (URL_SCHEME_RXP.test(param)) { + deeplink = param + } + + if (!rx.test(param)) continue + param = param.substring(1) + let index = param.indexOf('=') + if (index < 0) { + cmdParams[param] = true + } else { + cmdParams[param.substring(0, index)] = param.substring(index + 1) + } + } + return { + cmdParams, + deeplink, + } +} + +const primitiveType = ['string', 'boolean', 'number'] +const checkPrimitiveType = (val: any): boolean => val === null || primitiveType.includes(typeof val) +// const handleMergeSetting = (defaultSetting: LX.AppSetting, currentSetting: Partial) => { +// const updatedSettingKeys: Array = [] +// for (const key of Object.keys(defaultSetting) as Array) { +// const currentValue: any = currentSetting[key] +// const isPrimitive = checkPrimitiveType(currentValue) +// // if (checkPrimitiveType(value)) { +// if (!isPrimitive) continue +// updatedSettingKeys.push(key) +// // @ts-expect-error +// defaultSetting[key] = currentValue +// // } else { +// // if (!isPrimitive && currentValue != undefined) handleMergeSetting(value, currentValue) +// // } +// } +// return { +// setting: defaultSetting, +// updatedSettingKeys, +// } +// } + +export const mergeSetting = (originSetting: LX.AppSetting, targetSetting?: Partial | null): { + setting: LX.AppSetting + updatedSettingKeys: Array + updatedSetting: Partial +} => { + let originSettingCopy: LX.AppSetting = JSON.parse(JSON.stringify(originSetting)) + // const defaultVersion = targetSettingCopy.version + const updatedSettingKeys: Array = [] + const updatedSetting: Partial = {} + + if (targetSetting) { + const originSettingKeys = Object.keys(originSettingCopy) + const targetSettingKeys = Object.keys(targetSetting) + + if (originSettingKeys.length > targetSettingKeys.length) { + for (const key of targetSettingKeys as Array) { + const targetValue: any = targetSetting[key] + const isPrimitive = checkPrimitiveType(targetValue) + // if (checkPrimitiveType(value)) { + if (!isPrimitive || targetValue == originSettingCopy[key] || originSettingCopy[key] === undefined) continue + updatedSettingKeys.push(key) + updatedSetting[key] = targetValue + // @ts-expect-error + originSettingCopy[key] = targetValue + // } else { + // if (!isPrimitive && currentValue != undefined) handleMergeSetting(value, currentValue) + // } + } + } else { + for (const key of originSettingKeys as Array) { + const targetValue: any = targetSetting[key] + const isPrimitive = checkPrimitiveType(targetValue) + // if (checkPrimitiveType(value)) { + if (!isPrimitive || targetValue == originSettingCopy[key]) continue + updatedSettingKeys.push(key) + updatedSetting[key] = targetValue + // @ts-expect-error + originSettingCopy[key] = targetValue + // } else { + // if (!isPrimitive && currentValue != undefined) handleMergeSetting(value, currentValue) + // } + } + } + } + + return { + setting: originSettingCopy, + updatedSettingKeys, + updatedSetting, + } +} + + +export const updateSetting = (setting?: Partial, isInit: boolean = false) => { + const electronStore_config = getStore(STORE_NAMES.APP_SETTINGS) + + let originSetting: LX.AppSetting + if (isInit) { + if (setting) setting = migrateSetting(setting) + originSetting = defaultSetting + } else originSetting = global.lx.appSetting + + const result = mergeSetting(originSetting, setting) + + result.setting.version = defaultSetting.version + + electronStore_config.set({ version: result.setting.version, setting: result.setting }) + return result +} + +/** + * 初始化设置 + */ +export const initSetting = async() => { + const electronStore_config = getStore(STORE_NAMES.APP_SETTINGS) + + let setting = electronStore_config.get('setting') as LX.AppSetting | undefined + + // migrate setting + if (!setting) { + const config = await parseDataFile<{ setting?: any }>('config.json') + if (config?.setting) setting = config.setting as LX.AppSetting + await migrateUserApi() + await migrateDataJson() + } + + // console.log(setting) + return updateSetting(setting, true) +} + +/** + * 初始化快捷键设置 + */ +export const initHotKey = async() => { + const electronStore_hotKey = getStore(STORE_NAMES.HOTKEY) + + let localConfig = electronStore_hotKey.get('local') as LX.HotKeyConfig | null + let globalConfig = electronStore_hotKey.get('global') as LX.HotKeyConfig | null + + if (!localConfig) { + // migrate hotKey + const config = await migrateHotKey() + if (config) { + localConfig = config.local + globalConfig = config.global + } else { + localConfig = JSON.parse(JSON.stringify(defaultHotKey.global)) + globalConfig = JSON.parse(JSON.stringify(defaultHotKey.local)) + } + + electronStore_hotKey.set('local', localConfig) + electronStore_hotKey.set('global', globalConfig) + } + + return { + local: localConfig as LX.HotKeyConfig, + global: globalConfig as LX.HotKeyConfig, + } +} + +type HotKeyType = 'local' | 'global' + +const saveHotKeyConfig = throttle<[LX.HotKeyConfigAll]>((config: LX.HotKeyConfigAll) => { + for (const key of Object.keys(config) as HotKeyType[]) { + global.lx.hotKey.config[key] = config[key] + getStore(STORE_NAMES.HOTKEY).set(key, config[key]) + } +}) +export const saveAppHotKeyConfig = (config: LX.HotKeyConfigAll) => { + saveHotKeyConfig(config) +} + +export const openDevTools = (webContents: Electron.WebContents) => { + webContents.openDevTools({ + mode: 'undocked', + }) +} + + +let userThemes: LX.Theme[] +export const getAllThemes = () => { + if (!userThemes) userThemes = getStore(STORE_NAMES.THEME).get('themes') as LX.Theme[] | null ?? [] + return { + themes, + userThemes, + dataPath: joinPath(global.lxDataPath, 'theme_images'), + } +} + +export const saveTheme = (theme: LX.Theme) => { + const targetTheme = userThemes.find(t => t.id === theme.id) + if (targetTheme) Object.assign(targetTheme, theme) + else userThemes.push(theme) + getStore(STORE_NAMES.THEME).set('themes', userThemes) +} + +export const removeTheme = (id: string) => { + const index = userThemes.findIndex(t => t.id === id) + if (index < 0) return + userThemes.splice(index, 1) + getStore(STORE_NAMES.THEME).set('themes', userThemes) +} + +const copyTheme = (theme: LX.Theme): LX.Theme => { + return { + ...theme, + config: { + ...theme.config, + extInfo: { ...theme.config.extInfo }, + themeColors: { ...theme.config.themeColors }, + }, + } +} +export const getTheme = () => { + // fs.promises.readdir() + const shouldUseDarkColors = nativeTheme.shouldUseDarkColors + let themeId = global.lx.appSetting['theme.id'] == 'auto' + ? shouldUseDarkColors + ? global.lx.appSetting['theme.darkId'] + : global.lx.appSetting['theme.lightId'] + : global.lx.appSetting['theme.id'] + // themeId = 'naruto' + // themeId = 'pink' + // themeId = 'black' + let theme = themes.find(theme => theme.id == themeId) + if (!theme) { + userThemes = getStore(STORE_NAMES.THEME).get('themes') as LX.Theme[] | null ?? [] + theme = userThemes.find(theme => theme.id == themeId) + if (theme) { + if (theme.config.extInfo['--background-image'] != 'none') { + theme = copyTheme(theme) + theme.config.extInfo['--background-image'] = `url(file:///${joinPath(global.lxDataPath, 'theme_images', theme.config.extInfo['--background-image']).replaceAll('\\', '/')})` + } + } else { + themeId = global.lx.appSetting['theme.id'] == 'auto' && shouldUseDarkColors ? 'black' : 'green' + theme = themes.find(theme => theme.id == themeId) as LX.Theme + } + } + + const colors: Record = { + ...theme.config.themeColors, + ...theme.config.extInfo, + } + + const vals = theme.isDark ? commonDarkColorValues : commonLightColorValues + for (let i = commonColorNames.length; i--;) { + colors[commonColorNames[i]] = vals[i] + } + + return { + shouldUseDarkColors, + theme: { + id: global.lx.appSetting['theme.id'], + name: theme.name, + isDark: theme.isDark, + colors, + }, + } +} diff --git a/src/main/utils/migrate.ts b/src/main/utils/migrate.ts new file mode 100644 index 00000000..56dd127f --- /dev/null +++ b/src/main/utils/migrate.ts @@ -0,0 +1,156 @@ +import fs from 'node:fs' +import { checkPath, joinPath } from '@common/utils/nodejs' +import { log } from '@common/utils' +import { toNewMusicInfo } from '@common/utils/tools' +import { APP_EVENT_NAMES, STORE_NAMES } from '@common/constants' + +/** + * 读取配置文件 + * @returns + */ +export const parseDataFile = async(name: string): Promise => { + const path = joinPath(global.lxOldDataPath, name) + if (await checkPath(path)) { + try { + return JSON.parse((await fs.promises.readFile(path)).toString()) + } catch (err) { + log.error(err) + } + } + return null +} + +interface OldUserListInfo { + name: string + id: string + source?: LX.OnlineSource + sourceListId?: string + locationUpdateTime?: number + list: any[] +} +/** + * 迁移 v2.0.0 之前的 list data + * @returns + */ +export const migrateDBData = async() => { + let playList = await parseDataFile<{ defaultList?: { list: any[] }, loveList?: { list: any[] }, tempList?: { list: any[] }, userList?: OldUserListInfo[] }>('playList.json') + let listDataAll: LX.List.ListDataFull = { + defaultList: [], + loveList: [], + userList: [], + tempList: [], + } + let isRequiredSave = false + if (playList) { + if (playList.defaultList) listDataAll.defaultList = playList.defaultList.list.map(m => toNewMusicInfo(m)) + if (playList.loveList) listDataAll.loveList = playList.loveList.list.map(m => toNewMusicInfo(m)) + if (playList.tempList) listDataAll.tempList = playList.tempList.list.map(m => toNewMusicInfo(m)) + if (playList.userList) { + listDataAll.userList = playList.userList.map(l => { + return { + ...l, + locationUpdateTime: l.locationUpdateTime ?? null, + list: l.list.map(m => toNewMusicInfo(m)), + } + }) + } + isRequiredSave = true + } else { + const config = await parseDataFile<{ list?: { defaultList?: any[], loveList?: any[] } }>('config.json') + if (config?.list) { + const list = config.list + if (list.defaultList) listDataAll.defaultList = list.defaultList.map(m => toNewMusicInfo(m)) + if (list.loveList) listDataAll.loveList = list.loveList.map(m => toNewMusicInfo(m)) + isRequiredSave = true + } + } + if (isRequiredSave) await global.lx.worker.dbService.listDataOverwrite(listDataAll) + + const lyricData = await parseDataFile>('lyrics_edited.json') + if (lyricData) { + for await (const [id, info] of Object.entries(lyricData)) { + await global.lx.worker.dbService.editedLyricAdd(id, info) + } + } +} + +// 迁移文件 +const migrateFile = async(name: string, targetName: string) => { + let path = joinPath(global.lxDataPath, targetName) + let oldPath = joinPath(global.lxOldDataPath, name) + if (!await checkPath(path) && await checkPath(oldPath)) { + await fs.promises.copyFile(oldPath, path).catch(err => { + log.error(err) + }).catch(err => log.error(err)) + } +} + +/** + * 迁移 v2.0.0 之前的 data.json + * @returns + */ +export const migrateDataJson = async() => { + const path = joinPath(global.lxDataPath, 'data.json') + if (await checkPath(path)) return + const oldDataFile = await parseDataFile<{ + searchHistoryList?: string[] + playInfo?: any + listPrevSelectId?: any + listPosition?: any + listUpdateInfo?: any + }>('data.json') + if (!oldDataFile) return + const newData: any = {} + if (oldDataFile.searchHistoryList) newData.searchHistoryList = oldDataFile.searchHistoryList + if (oldDataFile.playInfo) newData.playInfo = oldDataFile.playInfo + if (oldDataFile.listPrevSelectId) newData.listPrevSelectId = oldDataFile.listPrevSelectId + if (oldDataFile.listPosition) newData.listScrollPosition = oldDataFile.listPosition + if (oldDataFile.listUpdateInfo) newData.listUpdateInfo = oldDataFile.listUpdateInfo + + await fs.promises.writeFile(path, JSON.stringify(newData)).catch(err => log.error(err)) +} + + +const hotKeyNameMap = { + mainWindow: APP_EVENT_NAMES.winMainName, + winLyric: APP_EVENT_NAMES.winLyricName, +} as const +const updateHotKeyTypeName = (config: LX.HotKeyConfig) => { + for (const keyConfig of Object.values(config.keys)) { + if (hotKeyNameMap[keyConfig.type as keyof typeof hotKeyNameMap]) keyConfig.type = hotKeyNameMap[keyConfig.type as keyof typeof hotKeyNameMap] + } +} +/** + * 迁移 v2.0.0 之前的 hotkey + * @returns + */ +export const migrateHotKey = async() => { + const oldConfig = await parseDataFile('hotKey.json') + if (oldConfig) { + let localConfig: LX.HotKeyConfig + let globalConfig: LX.HotKeyConfig + updateHotKeyTypeName(oldConfig.local) + updateHotKeyTypeName(oldConfig.global) + + localConfig = oldConfig.local + globalConfig = oldConfig.global + + // 移除v1.0.1及之前设置的全局声音媒体快捷键接管 + if (globalConfig.keys.VolumeUp) { + delete globalConfig.keys.VolumeUp + delete globalConfig.keys.VolumeDown + delete globalConfig.keys.VolumeMute + } + return { + local: localConfig, + global: globalConfig, + } + } + return null +} + +/** + * 迁移 v2.0.0 之前的user api + * @returns + */ +export const migrateUserApi = async() => migrateFile('userApi.json', STORE_NAMES.USER_API + '.json') diff --git a/src/main/utils/mp3Meta.js b/src/main/utils/mp3Meta.js deleted file mode 100644 index 80b16b71..00000000 --- a/src/main/utils/mp3Meta.js +++ /dev/null @@ -1,60 +0,0 @@ -const NodeID3 = require('node-id3') -const path = require('path') -const fs = require('fs') -const request = require('request') -const extReg = /^(\.(?:jpe?g|png)).*$/ - -const handleWriteMeta = (meta, filePath) => { - if (meta.lyrics) { - meta.unsynchronisedLyrics = { - language: 'zho', - text: meta.lyrics, - } - delete meta.lyrics - } - NodeID3.write(meta, filePath) -} - -module.exports = (filePath, meta) => { - if (!meta.APIC) return handleWriteMeta(meta, filePath) - if (!/^http/.test(meta.APIC)) { - delete meta.APIC - return handleWriteMeta(meta, filePath) - } - let ext = path.extname(meta.APIC) - let picPath = filePath.replace(/\.mp3$/, '') + (ext ? ext.replace(extReg, '$1') : '.jpg') - request(meta.APIC) - .on('response', respones => { - if (respones.statusCode !== 200 && respones.statusCode != 206) { - delete meta.APIC - handleWriteMeta(meta, filePath) - return - } - respones - .pipe(fs.createWriteStream(picPath)) - .on('finish', () => { - if (respones.complete) { - meta.APIC = picPath - handleWriteMeta(meta, filePath) - } else { - delete meta.APIC - handleWriteMeta(meta, filePath) - } - fs.unlink(picPath, err => { - if (err) console.log(err.message) - }) - }).on('error', err => { - if (err) console.log(err.message) - delete meta.APIC - handleWriteMeta(meta, filePath) - fs.unlink(picPath, err => { - if (err) console.log(err.message) - }) - }) - }) - .on('error', err => { - if (err) console.log(err.message) - delete meta.APIC - handleWriteMeta(meta, filePath) - }) -} diff --git a/src/main/utils/simplify-chinese-main/.gitignore b/src/main/utils/simplify-chinese-main/.gitignore deleted file mode 100644 index 771f1208..00000000 --- a/src/main/utils/simplify-chinese-main/.gitignore +++ /dev/null @@ -1,18 +0,0 @@ -todo.md -node_modules/ -npm-debug.log -yarn-debug.log -yarn-error.log -package-lock.json -tsconfig.tsbuildinfo -report.*.json - -.eslintcache -.DS_Store -.idea -.vscode -.yarn -*.suo -*.ntvs* -*.njsproj -*.sln diff --git a/src/main/utils/simplify-chinese-main/chinese.js b/src/main/utils/simplify-chinese-main/chinese.js deleted file mode 100644 index 412fa491..00000000 --- a/src/main/utils/simplify-chinese-main/chinese.js +++ /dev/null @@ -1,2 +0,0 @@ -exports.simplified = '万与丑专业丛东丝丢两严丧个丬丰临为丽举么义乌乐乔习乡书买乱争于亏云亘亚产亩亲亵亸亿仅从仑仓仪们价众优伙会伛伞伟传伤伥伦伧伪伫体余佣佥侠侣侥侦侧侨侩侪侬俣俦俨俩俪俭债倾偬偻偾偿傥傧储傩儿兑兖党兰关兴兹养兽冁内冈册写军农冢冯冲决况冻净凄凉凌减凑凛几凤凫凭凯击凼凿刍划刘则刚创删别刬刭刽刿剀剂剐剑剥剧劝办务劢动励劲劳势勋勐勚匀匦匮区医华协单卖卢卤卧卫却卺厂厅历厉压厌厍厕厢厣厦厨厩厮县参叆叇双发变叙叠叶号叹叽吁后吓吕吗吣吨听启吴呒呓呕呖呗员呙呛呜咏咔咙咛咝咤咴咸哌响哑哒哓哔哕哗哙哜哝哟唛唝唠唡唢唣唤呼啧啬啭啮啰啴啸喷喽喾嗫嗳嘘嘤嘱噜噼嚣嚯团园囱围囵国图圆圣圹场坂坏块坚坛坜坝坞坟坠垄垅垆垒垦垧垩垫垭垯垱垲垴埘埙埚埝埯堑堕塆墙壮声壳壶壸处备复够头夸夹夺奁奂奋奖奥妆妇妈妩妪妫姗姜娄娅娆娇娈娱娲娴婳婴婵婶媪嫒嫔嫱嬷孙学孪宁宝实宠审宪宫宽宾寝对寻导寿将尔尘尧尴尸尽层屃屉届属屡屦屿岁岂岖岗岘岙岚岛岭岳岽岿峃峄峡峣峤峥峦崂崃崄崭嵘嵚嵛嵝嵴巅巩巯币帅师帏帐帘帜带帧帮帱帻帼幂幞干并广庄庆庐庑库应庙庞废庼廪开异弃张弥弪弯弹强归当录彟彦彻径徕御忆忏忧忾怀态怂怃怄怅怆怜总怼怿恋恳恶恸恹恺恻恼恽悦悫悬悭悯惊惧惨惩惫惬惭惮惯愍愠愤愦愿慑慭憷懑懒懔戆戋戏戗战戬户扎扑扦执扩扪扫扬扰抚抛抟抠抡抢护报担拟拢拣拥拦拧拨择挂挚挛挜挝挞挟挠挡挢挣挤挥挦捞损捡换捣据捻掳掴掷掸掺掼揸揽揿搀搁搂搅携摄摅摆摇摈摊撄撑撵撷撸撺擞攒敌敛数斋斓斗斩断无旧时旷旸昙昼昽显晋晒晓晔晕晖暂暧札术朴机杀杂权条来杨杩杰极构枞枢枣枥枧枨枪枫枭柜柠柽栀栅标栈栉栊栋栌栎栏树栖样栾桊桠桡桢档桤桥桦桧桨桩梦梼梾检棂椁椟椠椤椭楼榄榇榈榉槚槛槟槠横樯樱橥橱橹橼檐檩欢欤欧歼殁殇残殒殓殚殡殴毁毂毕毙毡毵氇气氢氩氲汇汉污汤汹沓沟没沣沤沥沦沧沨沩沪沵泞泪泶泷泸泺泻泼泽泾洁洒洼浃浅浆浇浈浉浊测浍济浏浐浑浒浓浔浕涂涌涛涝涞涟涠涡涢涣涤润涧涨涩淀渊渌渍渎渐渑渔渖渗温游湾湿溃溅溆溇滗滚滞滟滠满滢滤滥滦滨滩滪漤潆潇潋潍潜潴澜濑濒灏灭灯灵灾灿炀炉炖炜炝点炼炽烁烂烃烛烟烦烧烨烩烫烬热焕焖焘煅煳爱爷牍牦牵牺犊犟状犷犸犹狈狍狝狞独狭狮狯狰狱狲猃猎猕猡猪猫猬献獭玑玙玚玛玮环现玱玺珉珏珐珑珰珲琎琏琐琼瑶瑷璇璎瓒瓮瓯电画畅畲畴疖疗疟疠疡疬疮疯疱疴痈痉痒痖痨痪痫痴瘅瘆瘗瘘瘪瘫瘾瘿癞癣癫癯皑皱皲盏盐监盖盗盘眍眦眬睁睐睑瞒瞩矫矶矾矿砀码砖砗砚砜砺砻砾础硁硅硕硖硗硙硚确硷碍碛碜碱碹磙礼祎祢祯祷祸禀禄禅离秃秆种积称秽秾稆税稣稳穑穷窃窍窑窜窝窥窦窭竖竞笃笋笔笕笺笼笾筑筚筛筜筝筹签简箓箦箧箨箩箪箫篑篓篮篱簖籁籴类籼粜粝粤粪粮糁糇紧絷纟纠纡红纣纤纥约级纨纩纪纫纬纭纮纯纰纱纲纳纴纵纶纷纸纹纺纻纼纽纾线绀绁绂练组绅细织终绉绊绋绌绍绎经绐绑绒结绔绕绖绗绘给绚绛络绝绞统绠绡绢绣绤绥绦继绨绩绪绫绬续绮绯绰绱绲绳维绵绶绷绸绹绺绻综绽绾绿缀缁缂缃缄缅缆缇缈缉缊缋缌缍缎缏缐缑缒缓缔缕编缗缘缙缚缛缜缝缞缟缠缡缢缣缤缥缦缧缨缩缪缫缬缭缮缯缰缱缲缳缴缵罂网罗罚罢罴羁羟羡翘翙翚耢耧耸耻聂聋职聍联聩聪肃肠肤肷肾肿胀胁胆胜胧胨胪胫胶脉脍脏脐脑脓脔脚脱脶脸腊腌腘腭腻腼腽腾膑臜舆舣舰舱舻艰艳艹艺节芈芗芜芦苁苇苈苋苌苍苎苏苘苹茎茏茑茔茕茧荆荐荙荚荛荜荞荟荠荡荣荤荥荦荧荨荩荪荫荬荭荮药莅莜莱莲莳莴莶获莸莹莺莼萚萝萤营萦萧萨葱蒇蒉蒋蒌蓝蓟蓠蓣蓥蓦蔷蔹蔺蔼蕲蕴薮藁藓虏虑虚虫虬虮虽虾虿蚀蚁蚂蚕蚝蚬蛊蛎蛏蛮蛰蛱蛲蛳蛴蜕蜗蜡蝇蝈蝉蝎蝼蝾螀螨蟏衅衔补衬衮袄袅袆袜袭袯装裆裈裢裣裤裥褛褴襁襕见观觃规觅视觇览觉觊觋觌觍觎觏觐觑觞触觯詟誉誊讠计订讣认讥讦讧讨让讪讫训议讯记讱讲讳讴讵讶讷许讹论讻讼讽设访诀证诂诃评诅识诇诈诉诊诋诌词诎诏诐译诒诓诔试诖诗诘诙诚诛诜话诞诟诠诡询诣诤该详诧诨诩诪诫诬语诮误诰诱诲诳说诵诶请诸诹诺读诼诽课诿谀谁谂调谄谅谆谇谈谊谋谌谍谎谏谐谑谒谓谔谕谖谗谘谙谚谛谜谝谞谟谠谡谢谣谤谥谦谧谨谩谪谫谬谭谮谯谰谱谲谳谴谵谶谷豮贝贞负贠贡财责贤败账货质贩贪贫贬购贮贯贰贱贲贳贴贵贶贷贸费贺贻贼贽贾贿赀赁赂赃资赅赆赇赈赉赊赋赌赍赎赏赐赑赒赓赔赕赖赗赘赙赚赛赜赝赞赟赠赡赢赣赪赵赶趋趱趸跃跄跖跞践跶跷跸跹跻踊踌踪踬踯蹑蹒蹰蹿躏躜躯车轧轨轩轪轫转轭轮软轰轱轲轳轴轵轶轷轸轹轺轻轼载轾轿辀辁辂较辄辅辆辇辈辉辊辋辌辍辎辏辐辑辒输辔辕辖辗辘辙辚辞辩辫边辽达迁过迈运还这进远违连迟迩迳迹适选逊递逦逻遗遥邓邝邬邮邹邺邻郁郄郏郐郑郓郦郧郸酝酦酱酽酾酿释里鉅鉴銮錾钆钇针钉钊钋钌钍钎钏钐钑钒钓钔钕钖钗钘钙钚钛钝钞钟钠钡钢钣钤钥钦钧钨钩钪钫钬钭钮钯钰钱钲钳钴钵钶钷钸钹钺钻钼钽钾钿铀铁铂铃铄铅铆铈铉铊铋铍铎铏铐铑铒铕铗铘铙铚铛铜铝铞铟铠铡铢铣铤铥铦铧铨铪铫铬铭铮铯铰铱铲铳铴铵银铷铸铹铺铻铼铽链铿销锁锂锃锄锅锆锇锈锉锊锋锌锍锎锏锐锑锒锓锔锕锖锗错锚锜锞锟锠锡锢锣锤锥锦锨锩锫锬锭键锯锰锱锲锳锴锵锶锷锸锹锺锻锼锽锾锿镀镁镂镃镆镇镈镉镊镌镍镎镏镐镑镒镕镖镗镙镚镛镜镝镞镟镠镡镢镣镤镥镦镧镨镩镪镫镬镭镮镯镰镱镲镳镴镶长门闩闪闫闬闭问闯闰闱闳间闵闶闷闸闹闺闻闼闽闾闿阀阁阂阃阄阅阆阇阈阉阊阋阌阍阎阏阐阑阒阓阔阕阖阗阘阙阚阛队阳阴阵阶际陆陇陈陉陕陧陨险随隐隶隽难雏雠雳雾霁霉霭靓静靥鞑鞒鞯鞴韦韧韨韩韪韫韬韵页顶顷顸项顺须顼顽顾顿颀颁颂颃预颅领颇颈颉颊颋颌颍颎颏颐频颒颓颔颕颖颗题颙颚颛颜额颞颟颠颡颢颣颤颥颦颧风飏飐飑飒飓飔飕飖飗飘飙飚飞飨餍饤饥饦饧饨饩饪饫饬饭饮饯饰饱饲饳饴饵饶饷饸饹饺饻饼饽饾饿馀馁馂馃馄馅馆馇馈馉馊馋馌馍馎馏馐馑馒馓馔馕马驭驮驯驰驱驲驳驴驵驶驷驸驹驺驻驼驽驾驿骀骁骂骃骄骅骆骇骈骉骊骋验骍骎骏骐骑骒骓骔骕骖骗骘骙骚骛骜骝骞骟骠骡骢骣骤骥骦骧髅髋髌鬓魇魉鱼鱽鱾鱿鲀鲁鲂鲄鲅鲆鲇鲈鲉鲊鲋鲌鲍鲎鲏鲐鲑鲒鲓鲔鲕鲖鲗鲘鲙鲚鲛鲜鲝鲞鲟鲠鲡鲢鲣鲤鲥鲦鲧鲨鲩鲪鲫鲬鲭鲮鲯鲰鲱鲲鲳鲴鲵鲶鲷鲸鲹鲺鲻鲼鲽鲾鲿鳀鳁鳂鳃鳄鳅鳆鳇鳈鳉鳊鳋鳌鳍鳎鳏鳐鳑鳒鳓鳔鳕鳖鳗鳘鳙鳛鳜鳝鳞鳟鳠鳡鳢鳣鸟鸠鸡鸢鸣鸤鸥鸦鸧鸨鸩鸪鸫鸬鸭鸮鸯鸰鸱鸲鸳鸴鸵鸶鸷鸸鸹鸺鸻鸼鸽鸾鸿鹀鹁鹂鹃鹄鹅鹆鹇鹈鹉鹊鹋鹌鹍鹎鹏鹐鹑鹒鹓鹔鹕鹖鹗鹘鹚鹛鹜鹝鹞鹟鹠鹡鹢鹣鹤鹥鹦鹧鹨鹩鹪鹫鹬鹭鹯鹰鹱鹲鹳鹴鹾麦麸黄黉黡黩黪黾鼋鼌鼍鼗鼹齄齐齑齿龀龁龂龃龄龅龆龇龈龉龊龋龌龙龚龛龟志制咨系范尝准闲拼' -exports.traditional = '萬與醜專業叢東絲丟兩嚴喪個爿豐臨為麗舉麼義烏樂喬習鄉書買亂爭於虧雲亙亞產畝親褻嚲億僅從侖倉儀們價眾優夥會傴傘偉傳傷倀倫傖偽佇體餘傭僉俠侶僥偵側僑儈儕儂俁儔儼倆儷儉債傾傯僂僨償儻儐儲儺兒兌兗黨蘭關興茲養獸囅內岡冊寫軍農塚馮衝決況凍淨淒涼淩減湊凜幾鳳鳧憑凱擊氹鑿芻劃劉則剛創刪別剗剄劊劌剴劑剮劍剝劇勸辦務勱動勵勁勞勢勳猛勩勻匭匱區醫華協單賣盧鹵臥衛卻巹廠廳曆厲壓厭厙廁廂厴廈廚廄廝縣參靉靆雙發變敘疊葉號歎嘰籲後嚇呂嗎唚噸聽啟吳嘸囈嘔嚦唄員咼嗆嗚詠哢嚨嚀噝吒噅鹹呱響啞噠嘵嗶噦嘩噲嚌噥喲嘜嗊嘮啢嗩唕喚唿嘖嗇囀齧囉嘽嘯噴嘍嚳囁噯噓嚶囑嚕劈囂謔團園囪圍圇國圖圓聖壙場阪壞塊堅壇壢壩塢墳墜壟壟壚壘墾坰堊墊埡墶壋塏堖塒塤堝墊垵塹墮壪牆壯聲殼壺壼處備複夠頭誇夾奪奩奐奮獎奧妝婦媽嫵嫗媯姍薑婁婭嬈嬌孌娛媧嫻嫿嬰嬋嬸媼嬡嬪嬙嬤孫學孿寧寶實寵審憲宮寬賓寢對尋導壽將爾塵堯尷屍盡層屭屜屆屬屢屨嶼歲豈嶇崗峴嶴嵐島嶺嶽崠巋嶨嶧峽嶢嶠崢巒嶗崍嶮嶄嶸嶔崳嶁脊巔鞏巰幣帥師幃帳簾幟帶幀幫幬幘幗冪襆幹並廣莊慶廬廡庫應廟龐廢廎廩開異棄張彌弳彎彈強歸當錄彠彥徹徑徠禦憶懺憂愾懷態慫憮慪悵愴憐總懟懌戀懇惡慟懨愷惻惱惲悅愨懸慳憫驚懼慘懲憊愜慚憚慣湣慍憤憒願懾憖怵懣懶懍戇戔戲戧戰戩戶紮撲扡執擴捫掃揚擾撫拋摶摳掄搶護報擔擬攏揀擁攔擰撥擇掛摯攣掗撾撻挾撓擋撟掙擠揮撏撈損撿換搗據撚擄摑擲撣摻摜摣攬撳攙擱摟攪攜攝攄擺搖擯攤攖撐攆擷擼攛擻攢敵斂數齋斕鬥斬斷無舊時曠暘曇晝曨顯晉曬曉曄暈暉暫曖劄術樸機殺雜權條來楊榪傑極構樅樞棗櫪梘棖槍楓梟櫃檸檉梔柵標棧櫛櫳棟櫨櫟欄樹棲樣欒棬椏橈楨檔榿橋樺檜槳樁夢檮棶檢欞槨櫝槧欏橢樓欖櫬櫚櫸檟檻檳櫧橫檣櫻櫫櫥櫓櫞簷檁歡歟歐殲歿殤殘殞殮殫殯毆毀轂畢斃氈毿氌氣氫氬氳彙漢汙湯洶遝溝沒灃漚瀝淪滄渢溈滬濔濘淚澩瀧瀘濼瀉潑澤涇潔灑窪浹淺漿澆湞溮濁測澮濟瀏滻渾滸濃潯濜塗湧濤澇淶漣潿渦溳渙滌潤澗漲澀澱淵淥漬瀆漸澠漁瀋滲溫遊灣濕潰濺漵漊潷滾滯灩灄滿瀅濾濫灤濱灘澦濫瀠瀟瀲濰潛瀦瀾瀨瀕灝滅燈靈災燦煬爐燉煒熗點煉熾爍爛烴燭煙煩燒燁燴燙燼熱煥燜燾煆糊愛爺牘犛牽犧犢強狀獷獁猶狽麅獮獰獨狹獅獪猙獄猻獫獵獼玀豬貓蝟獻獺璣璵瑒瑪瑋環現瑲璽瑉玨琺瓏璫琿璡璉瑣瓊瑤璦璿瓔瓚甕甌電畫暢佘疇癤療瘧癘瘍鬁瘡瘋皰屙癰痙癢瘂癆瘓癇癡癉瘮瘞瘺癟癱癮癭癩癬癲臒皚皺皸盞鹽監蓋盜盤瞘眥矓睜睞瞼瞞矚矯磯礬礦碭碼磚硨硯碸礪礱礫礎硜矽碩硤磽磑礄確鹼礙磧磣堿镟滾禮禕禰禎禱禍稟祿禪離禿稈種積稱穢穠穭稅穌穩穡窮竊竅窯竄窩窺竇窶豎競篤筍筆筧箋籠籩築篳篩簹箏籌簽簡籙簀篋籜籮簞簫簣簍籃籬籪籟糴類秈糶糲粵糞糧糝餱緊縶糸糾紆紅紂纖紇約級紈纊紀紉緯紜紘純紕紗綱納紝縱綸紛紙紋紡紵紖紐紓線紺絏紱練組紳細織終縐絆紼絀紹繹經紿綁絨結絝繞絰絎繪給絢絳絡絕絞統綆綃絹繡綌綏絛繼綈績緒綾緓續綺緋綽緔緄繩維綿綬繃綢綯綹綣綜綻綰綠綴緇緙緗緘緬纜緹緲緝縕繢緦綞緞緶線緱縋緩締縷編緡緣縉縛縟縝縫縗縞纏縭縊縑繽縹縵縲纓縮繆繅纈繚繕繒韁繾繰繯繳纘罌網羅罰罷羆羈羥羨翹翽翬耮耬聳恥聶聾職聹聯聵聰肅腸膚膁腎腫脹脅膽勝朧腖臚脛膠脈膾髒臍腦膿臠腳脫腡臉臘醃膕齶膩靦膃騰臏臢輿艤艦艙艫艱豔艸藝節羋薌蕪蘆蓯葦藶莧萇蒼苧蘇檾蘋莖蘢蔦塋煢繭荊薦薘莢蕘蓽蕎薈薺蕩榮葷滎犖熒蕁藎蓀蔭蕒葒葤藥蒞蓧萊蓮蒔萵薟獲蕕瑩鶯蓴蘀蘿螢營縈蕭薩蔥蕆蕢蔣蔞藍薊蘺蕷鎣驀薔蘞藺藹蘄蘊藪槁蘚虜慮虛蟲虯蟣雖蝦蠆蝕蟻螞蠶蠔蜆蠱蠣蟶蠻蟄蛺蟯螄蠐蛻蝸蠟蠅蟈蟬蠍螻蠑螿蟎蠨釁銜補襯袞襖嫋褘襪襲襏裝襠褌褳襝褲襇褸襤繈襴見觀覎規覓視覘覽覺覬覡覿覥覦覯覲覷觴觸觶讋譽謄訁計訂訃認譏訐訌討讓訕訖訓議訊記訒講諱謳詎訝訥許訛論訩訟諷設訪訣證詁訶評詛識詗詐訴診詆謅詞詘詔詖譯詒誆誄試詿詩詰詼誠誅詵話誕詬詮詭詢詣諍該詳詫諢詡譸誡誣語誚誤誥誘誨誑說誦誒請諸諏諾讀諑誹課諉諛誰諗調諂諒諄誶談誼謀諶諜謊諫諧謔謁謂諤諭諼讒諮諳諺諦謎諞諝謨讜謖謝謠謗諡謙謐謹謾謫譾謬譚譖譙讕譜譎讞譴譫讖穀豶貝貞負貟貢財責賢敗賬貨質販貪貧貶購貯貫貳賤賁貰貼貴貺貸貿費賀貽賊贄賈賄貲賃賂贓資賅贐賕賑賚賒賦賭齎贖賞賜贔賙賡賠賧賴賵贅賻賺賽賾贗讚贇贈贍贏贛赬趙趕趨趲躉躍蹌蹠躒踐躂蹺蹕躚躋踴躊蹤躓躑躡蹣躕躥躪躦軀車軋軌軒軑軔轉軛輪軟轟軲軻轤軸軹軼軤軫轢軺輕軾載輊轎輈輇輅較輒輔輛輦輩輝輥輞輬輟輜輳輻輯轀輸轡轅轄輾轆轍轔辭辯辮邊遼達遷過邁運還這進遠違連遲邇逕跡適選遜遞邐邏遺遙鄧鄺鄔郵鄒鄴鄰鬱郤郟鄶鄭鄆酈鄖鄲醞醱醬釅釃釀釋裏钜鑒鑾鏨釓釔針釘釗釙釕釷釺釧釤鈒釩釣鍆釹鍚釵鈃鈣鈈鈦鈍鈔鍾鈉鋇鋼鈑鈐鑰欽鈞鎢鉤鈧鈁鈥鈄鈕鈀鈺錢鉦鉗鈷缽鈳鉕鈽鈸鉞鑽鉬鉭鉀鈿鈾鐵鉑鈴鑠鉛鉚鈰鉉鉈鉍鈹鐸鉶銬銠鉺銪鋏鋣鐃銍鐺銅鋁銱銦鎧鍘銖銑鋌銩銛鏵銓鉿銚鉻銘錚銫鉸銥鏟銃鐋銨銀銣鑄鐒鋪鋙錸鋱鏈鏗銷鎖鋰鋥鋤鍋鋯鋨鏽銼鋝鋒鋅鋶鐦鐧銳銻鋃鋟鋦錒錆鍺錯錨錡錁錕錩錫錮鑼錘錐錦鍁錈錇錟錠鍵鋸錳錙鍥鍈鍇鏘鍶鍔鍤鍬鍾鍛鎪鍠鍰鎄鍍鎂鏤鎡鏌鎮鎛鎘鑷鐫鎳鎿鎦鎬鎊鎰鎔鏢鏜鏍鏰鏞鏡鏑鏃鏇鏐鐔钁鐐鏷鑥鐓鑭鐠鑹鏹鐙鑊鐳鐶鐲鐮鐿鑔鑣鑞鑲長門閂閃閆閈閉問闖閏闈閎間閔閌悶閘鬧閨聞闥閩閭闓閥閣閡閫鬮閱閬闍閾閹閶鬩閿閽閻閼闡闌闃闠闊闋闔闐闒闕闞闤隊陽陰陣階際陸隴陳陘陝隉隕險隨隱隸雋難雛讎靂霧霽黴靄靚靜靨韃鞽韉韝韋韌韍韓韙韞韜韻頁頂頃頇項順須頊頑顧頓頎頒頌頏預顱領頗頸頡頰頲頜潁熲頦頤頻頮頹頷頴穎顆題顒顎顓顏額顳顢顛顙顥纇顫顬顰顴風颺颭颮颯颶颸颼颻飀飄飆飆飛饗饜飣饑飥餳飩餼飪飫飭飯飲餞飾飽飼飿飴餌饒餉餄餎餃餏餅餑餖餓餘餒餕餜餛餡館餷饋餶餿饞饁饃餺餾饈饉饅饊饌饢馬馭馱馴馳驅馹駁驢駔駛駟駙駒騶駐駝駑駕驛駘驍罵駰驕驊駱駭駢驫驪騁驗騂駸駿騏騎騍騅騌驌驂騙騭騤騷騖驁騮騫騸驃騾驄驏驟驥驦驤髏髖髕鬢魘魎魚魛魢魷魨魯魴魺鮁鮃鯰鱸鮋鮓鮒鮊鮑鱟鮍鮐鮭鮚鮳鮪鮞鮦鰂鮜鱠鱭鮫鮮鮺鯗鱘鯁鱺鰱鰹鯉鰣鰷鯀鯊鯇鮶鯽鯒鯖鯪鯕鯫鯡鯤鯧鯝鯢鯰鯛鯨鯵鯴鯔鱝鰈鰏鱨鯷鰮鰃鰓鱷鰍鰒鰉鰁鱂鯿鰠鼇鰭鰨鰥鰩鰟鰜鰳鰾鱈鱉鰻鰵鱅鰼鱖鱔鱗鱒鱯鱤鱧鱣鳥鳩雞鳶鳴鳲鷗鴉鶬鴇鴆鴣鶇鸕鴨鴞鴦鴒鴟鴝鴛鴬鴕鷥鷙鴯鴰鵂鴴鵃鴿鸞鴻鵐鵓鸝鵑鵠鵝鵒鷳鵜鵡鵲鶓鵪鶤鵯鵬鵮鶉鶊鵷鷫鶘鶡鶚鶻鶿鶥鶩鷊鷂鶲鶹鶺鷁鶼鶴鷖鸚鷓鷚鷯鷦鷲鷸鷺鸇鷹鸌鸏鸛鸘鹺麥麩黃黌黶黷黲黽黿鼂鼉鞀鼴齇齊齏齒齔齕齗齟齡齙齠齜齦齬齪齲齷龍龔龕龜誌製谘係範嘗準閒拚' diff --git a/src/main/utils/simplify-chinese-main/package.json b/src/main/utils/simplify-chinese-main/package.json deleted file mode 100644 index 6c67d23e..00000000 --- a/src/main/utils/simplify-chinese-main/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "simplify-chinese", - "description": "Convert chinese characters between simplified form and tranditional form 汉字简繁体转换工具", - "version": "1.1.0", - "main": "index.js", - "typings": "index.d.ts", - "repository": "https://github.com/koishijs/simplify-chinese.git", - "author": "Shigma <1700011071@pku.edu.cn>", - "license": "MIT" -} diff --git a/src/main/utils/store.ts b/src/main/utils/store.ts new file mode 100644 index 00000000..07fc933e --- /dev/null +++ b/src/main/utils/store.ts @@ -0,0 +1,51 @@ +import Store from 'electron-store' +import { dialog, shell } from 'electron' +import { join } from 'path' +import fs from 'fs' +import log from 'electron-log' + +interface Stores { + [key: string]: Store +} + +const stores: Stores = {} + +/** + * 获取 Store 对象 + * @param {string} name store 名 + * @param {boolean|undefined} isIgnoredError 是否忽略错误 + * @param {boolean|undefined} isShowErrorAlert=true 是否显示错误弹窗 + * @returns Store + */ +export default (name: string, isIgnoredError = true, isShowErrorAlert = true): Store => { + if (stores[name]) return stores[name] + let store: Store + try { + store = stores[name] = new Store({ name, clearInvalidConfig: false, cwd: global.lxDataPath }) + } catch (err: any) { + const error = err as Error + log.error(error) + + if (!isIgnoredError) throw error + + + const backPath = join(global.lxDataPath, name + '.json.bak') + fs.copyFileSync(join(global.lxDataPath, name + '.json'), backPath) + if (isShowErrorAlert) { + dialog.showMessageBoxSync({ + type: 'error', + message: name + ' data load error', + detail: `We have helped you back up the old ${name} file to: ${backPath}\nYou can try to repair and restore it manually\n\nError detail: ${error.message}`, + }) + shell.showItemInFolder(backPath) + } + + + store = new Store({ name, clearInvalidConfig: true }) + } + return store +} + +export { + Store, +} diff --git a/src/main/worker/dbService/db.ts b/src/main/worker/dbService/db.ts new file mode 100644 index 00000000..026ebeab --- /dev/null +++ b/src/main/worker/dbService/db.ts @@ -0,0 +1,158 @@ +import Database from 'better-sqlite3' +import path from 'path' +// import migrateData from './migrate' + +let db: Database.Database + + +const initTables = (db: Database.Database) => { + db.exec(` + CREATE TABLE "db_info" ( + "id" INTEGER NOT NULL UNIQUE, + "field_name" TEXT, + "field_value" TEXT, + PRIMARY KEY("id" AUTOINCREMENT) + )`) + + db.prepare(`INSERT INTO "main"."db_info" ("field_name", "field_value") VALUES + ('version', '1') + `).run() + + db.exec(` + CREATE TABLE "my_list" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "source" TEXT, + "sourceListId" TEXT, + "position" INTEGER NOT NULL, + "locationUpdateTime" INTEGER, + PRIMARY KEY("id") + )`) + // const insert = db.prepare(`INSERT INTO "main"."my_list" ("id", "name", "source", "sourceListId", "locationUpdateTime") VALUES + // (@id, @name, @source, @sourceListId, @locationUpdateTime)`) + // db.transaction((cats) => { + // for (const cat of cats) insert.run(cat) + // })([ + // { id: 'default', name: '默认列表', source: null, sourceListId: null, locationUpdateTime: null }, + // { id: 'love', name: '收藏列表', source: null, sourceListId: null, locationUpdateTime: null }, + // { id: 'temp', name: '临时列表', source: null, sourceListId: null, locationUpdateTime: null }, + // ]) + db.exec(` + CREATE TABLE "my_list_music_info" ( + "id" TEXT NOT NULL, + "listId" TEXT NOT NULL, + "name" TEXT NOT NULL, + "singer" TEXT NOT NULL, + "source" TEXT NOT NULL, + "interval" TEXT, + "meta" TEXT NOT NULL, + UNIQUE("id","listId") + )`) + db.exec(` + CREATE INDEX "index_my_list_music_info" ON "my_list_music_info" ( + "id", + "listId" + )`) + + db.exec(` + CREATE TABLE "my_list_music_info_order" ( + "listId" TEXT NOT NULL, + "musicInfoId" TEXT NOT NULL, + "order" INTEGER NOT NULL + )`) + db.exec(` + CREATE INDEX "index_my_list_music_info_order" ON "my_list_music_info_order" ( + "listId", + "musicInfoId" + )`) + + db.exec(` + CREATE TABLE "music_info_other_source" ( + "source_id" TEXT NOT NULL, + "id" TEXT NOT NULL, + "source" TEXT NOT NULL, + "name" TEXT NOT NULL, + "singer" TEXT NOT NULL, + "meta" TEXT NOT NULL, + "order" INTEGER NOT NULL, + UNIQUE("source_id","id") + )`) + db.exec(` + CREATE INDEX "index_music_info_other_source" ON "music_info_other_source" ( + "source_id", + "id" + )`) + + // TODO "meta" TEXT NOT NULL, + db.exec(` + CREATE TABLE "lyric" ( + "id" TEXT NOT NULL, + "source" TEXT NOT NULL, + "type" TEXT NOT NULL, + "text" TEXT NOT NULL + )`) + + db.exec(` + CREATE TABLE "music_url" ( + "id" TEXT NOT NULL, + "url" TEXT NOT NULL + )`) + + db.exec(` + CREATE TABLE "download_list" ( + "id" TEXT NOT NULL, + "isComplate" INTEGER NOT NULL, + "status" TEXT NOT NULL, + "statusText" TEXT NOT NULL, + "progress_downloaded" INTEGER NOT NULL, + "progress_total" INTEGER NOT NULL, + "url" TEXT, + "quality" TEXT NOT NULL, + "ext" TEXT NOT NULL, + "fileName" TEXT NOT NULL, + "filePath" TEXT NOT NULL, + "musicInfo" TEXT NOT NULL, + "position" INTEGER NOT NULL, + PRIMARY KEY("id") + )`) +} + + +// 打开、初始化数据库 +export const init = (lxDataPath: string): boolean => { + const databasePath = path.join(lxDataPath, 'lx.data.db') + const nativeBinding = path.join(__dirname, '../node_modules/better-sqlite3/build/Release/better_sqlite3.node') + let dbFileExists = true + + try { + db = new Database(databasePath, { + fileMustExist: true, + nativeBinding, + verbose: global.isDev ? console.log : undefined, + }) + } catch (error) { + console.log(error) + db = new Database(databasePath, { + nativeBinding, + verbose: global.isDev ? console.log : undefined, + }) + initTables(db) + dbFileExists = false + } + + // if (dbFileExists) migrateData(db) + + // https://www.sqlite.org/pragma.html#pragma_optimize + db.exec('PRAGMA optimize;') + + // https://www.sqlite.org/lang_vacuum.html + // db.exec('VACUUM "main"') + + process.on('exit', () => db.close()) + console.log('db inited') + // require('./test') + return dbFileExists +} + +// 获取数据库实例 +export const getDB = (): Database.Database => db diff --git a/src/main/worker/dbService/index.ts b/src/main/worker/dbService/index.ts new file mode 100644 index 00000000..927c7e6a --- /dev/null +++ b/src/main/worker/dbService/index.ts @@ -0,0 +1,17 @@ +import { init } from './db' +import { exposeWorker } from '../utils/worker' +import { list, lyric, music_url, music_other_source, download } from './modules/index' + + +const common = { + init, +} + +exposeWorker(Object.assign(common, list, lyric, music_url, music_other_source, download)) + +export declare type workerDBSeriveTypes = typeof common + & typeof list + & typeof lyric + & typeof music_url + & typeof music_other_source + & typeof download diff --git a/src/main/worker/dbService/migrate.ts b/src/main/worker/dbService/migrate.ts new file mode 100644 index 00000000..c01bb54b --- /dev/null +++ b/src/main/worker/dbService/migrate.ts @@ -0,0 +1,32 @@ +import Database from 'better-sqlite3' + +const migrateV1 = (db: Database.Database) => { + db.exec('DROP TABLE "main"."download_list"') + db.exec(` + CREATE TABLE "download_list" ( + "id" TEXT NOT NULL, + "isComplate" INTEGER NOT NULL, + "status" TEXT NOT NULL, + "statusText" TEXT NOT NULL, + "progress_downloaded" INTEGER NOT NULL, + "progress_total" INTEGER NOT NULL, + "url" TEXT, + "quality" TEXT NOT NULL, + "ext" TEXT NOT NULL, + "fileName" TEXT NOT NULL, + "filePath" TEXT NOT NULL, + "musicInfo" TEXT NOT NULL, + "position" INTEGER NOT NULL, + PRIMARY KEY("id") + )`) + db.prepare('UPDATE "main"."db_info" SET "field_value"=@value WHERE "field_name"=@name').run({ name: 'version', value: '2' }) +} + +export default (db: Database.Database) => { + const version = db.prepare('SELECT "field_value" FROM "main"."db_info" WHERE "field_name" = ?').get('version').field_value + switch (version) { + case '1': + migrateV1(db) + break + } +} diff --git a/src/main/worker/dbService/modules/download/dbHelper.ts b/src/main/worker/dbService/modules/download/dbHelper.ts new file mode 100644 index 00000000..13de16c4 --- /dev/null +++ b/src/main/worker/dbService/modules/download/dbHelper.ts @@ -0,0 +1,64 @@ +import { getDB } from '../../db' +import { + createQueryStatement, + createInsertStatement, + createDeleteStatement, + createUpdateStatement, + createUpdatePositionStatement, + createClearStatement, +} from './statements' + +/** + * 查询下载歌曲列表 + */ +export const queryDownloadList = (): LX.DBService.DownloadMusicInfo[] => { + const queryStatement = createQueryStatement() + return queryStatement.all() +} + +/** + * 批量插入下载歌曲并刷新顺序 + * @param mInfos 列表 + */ +export const inertDownloadList = (mInfos: LX.DBService.DownloadMusicInfo[], listPositions: Array<{ id: string, position: number }>) => { + const db = getDB() + const insertStatement = createInsertStatement() + const updatePositionStatement = createUpdatePositionStatement() + db.transaction((mInfos: LX.DBService.DownloadMusicInfo[]) => { + for (const info of mInfos) insertStatement.run(info) + for (const info of listPositions) updatePositionStatement.run(info) + })(mInfos) +} + +/** + * 批量删除下载歌曲 + * @param ids 列表 + */ +export const deleteDownloadList = (ids: string[]) => { + const db = getDB() + const deleteStatement = createDeleteStatement() + db.transaction((ids: string[]) => { + for (const id of ids) deleteStatement.run(id) + })(ids) +} + +/** + * 批量更新下载歌曲 + * @param urlInfo 列表 + */ +export const updateDownloadList = (urlInfo: LX.DBService.DownloadMusicInfo[]) => { + const db = getDB() + const updateStatement = createUpdateStatement() + db.transaction((urlInfo: LX.DBService.DownloadMusicInfo[]) => { + for (const info of urlInfo) updateStatement.run(info) + })(urlInfo) +} + +/** + * 清空下载歌曲列表 + */ +export const clearDownloadList = () => { + const clearStatement = createClearStatement() + clearStatement.run() +} + diff --git a/src/main/worker/dbService/modules/download/index.ts b/src/main/worker/dbService/modules/download/index.ts new file mode 100644 index 00000000..5ec966dd --- /dev/null +++ b/src/main/worker/dbService/modules/download/index.ts @@ -0,0 +1,121 @@ +import { + queryDownloadList, + inertDownloadList, + updateDownloadList, + deleteDownloadList, + clearDownloadList, +} from './dbHelper' + +let list: LX.Download.ListItem[] + +const toDBDownloadInfo = (musicInfos: LX.Download.ListItem[], offset: number = 0): LX.DBService.DownloadMusicInfo[] => { + return musicInfos.map((info, index) => { + return { + id: info.id, + isComplate: info.isComplate ? 1 : 0, + status: info.status, + statusText: info.statusText, + progress_downloaded: info.downloaded, + progress_total: info.total, + url: info.metadata.url, + quality: info.metadata.quality, + ext: info.metadata.ext, + fileName: info.metadata.fileName, + filePath: info.metadata.filePath, + musicInfo: JSON.stringify(info.metadata.musicInfo), + position: offset + index, + } + }) +} + +const initDownloadList = () => { + list = queryDownloadList().map(item => { + const musicInfo = JSON.parse(item.musicInfo) as LX.Music.MusicInfoOnline + return { + id: item.id, + isComplate: item.isComplate == 1, + status: item.status, + statusText: item.statusText, + downloaded: item.progress_downloaded, + total: item.progress_total, + progress: item.progress_total ? parseInt((item.progress_downloaded / item.progress_total).toFixed(2)) * 100 : 0, + speed: '', + metadata: { + musicInfo, + url: item.url, + quality: item.quality, + ext: item.ext, + fileName: item.fileName, + filePath: item.filePath, + }, + } + }) +} + +/** + * 获取下载列表 + * @returns 下载列表 + */ +export const getDownloadList = (): LX.Download.ListItem[] => { + if (!list) initDownloadList() + return list +} + +/** + * 添加下载歌曲信息 + * @param downloadInfos url信息 + */ +export const downloadInfoSave = (downloadInfos: LX.Download.ListItem[], addMusicLocationType: LX.AddMusicLocationType) => { + if (!list) initDownloadList() + if (addMusicLocationType == 'top') { + let newList = [...list] + newList.unshift(...downloadInfos) + inertDownloadList(toDBDownloadInfo(downloadInfos), newList.slice(downloadInfos.length - 1).map((info, index) => { + return { id: info.id, position: index } + })) + list = newList + } else { + inertDownloadList(toDBDownloadInfo(downloadInfos, list.length), []) + list.push(...downloadInfos) + } +} + +/** + * 批量更新列表信息 + * @param lists 列表信息 + */ +export const downloadInfoUpdate = (lists: LX.Download.ListItem[]) => { + updateDownloadList(toDBDownloadInfo(lists)) + if (list) { + for (const item of lists) { + const index = list.findIndex(info => info.id === item.id) + if (index < 0) continue + list.splice(index, 1, item) + } + } +} + + +/** + * 删除下载列表 + * @param ids 歌曲id + */ +export const downloadInfoRemove = (ids: string[]) => { + deleteDownloadList(ids) + if (list) { + for (let i = list.length; i--;) { + let idx = ids.indexOf(list[i].id) + if (idx < 0) continue + list.splice(i, 1) + ids.splice(idx, 1) + } + } +} + +/** + * 清空下载列表 + */ +export const downloadInfoClear = () => { + clearDownloadList() +} + diff --git a/src/main/worker/dbService/modules/download/statements.ts b/src/main/worker/dbService/modules/download/statements.ts new file mode 100644 index 00000000..d3a89f6a --- /dev/null +++ b/src/main/worker/dbService/modules/download/statements.ts @@ -0,0 +1,72 @@ +import { getDB } from '../../db' + +/** + * 创建下载列表查询语句 + * @returns 查询语句 + */ +export const createQueryStatement = () => { + const db = getDB() + return db.prepare(` + SELECT "id", "isComplate", "status", "statusText", "progress_downloaded", "progress_total", "url", "quality", "ext", "fileName", "filePath", "musicInfo", "position" + FROM download_list + ORDER BY "position" ASC + `) +} + +/** + * 创建下载记录插入语句 + * @returns 插入语句 + */ +export const createInsertStatement = () => { + const db = getDB() + return db.prepare<[LX.DBService.DownloadMusicInfo]>(` + INSERT INTO "main"."download_list" ("id", "isComplate", "status", "statusText", "progress_downloaded", "progress_total", "url", "quality", "ext", "fileName", "filePath", "musicInfo", "position") + VALUES (@id, @isComplate, @status, @statusText, @progress_downloaded, @progress_total, @url, @quality, @ext, @fileName, @filePath, @musicInfo, @position)`) +} + +/** + * 创建下载记录清空语句 + * @returns 清空语句 + */ +export const createClearStatement = () => { + const db = getDB() + return db.prepare(` + DELETE FROM "main"."download_list" + `) +} + +/** + * 创建下载记录删除语句 + * @returns 删除语句 + */ +export const createDeleteStatement = () => { + const db = getDB() + return db.prepare<[string]>(` + DELETE FROM "main"."download_list" + WHERE "id"=? + `) +} + +/** + * 创建下载记录更新语句 + * @returns 更新语句 + */ +export const createUpdateStatement = () => { + const db = getDB() + return db.prepare<[LX.DBService.DownloadMusicInfo]>(` + UPDATE "main"."download_list" + SET "isComplate"=@isComplate, "status"=@status, "statusText"=@statusText, "progress_downloaded"=@progress_downloaded, "progress_total"=@progress_total, "url"=@url, "filePath"=@filePath + WHERE "id"=@id`) +} + +/** + * 创建下载记录顺序更新语句 + * @returns 更新语句 + */ +export const createUpdatePositionStatement = () => { + const db = getDB() + return db.prepare<[{ id: string, position: number }]>(` + UPDATE "main"."download_list" + SET "position"=@position + WHERE "id"=@id`) +} diff --git a/src/main/worker/dbService/modules/index.ts b/src/main/worker/dbService/modules/index.ts new file mode 100644 index 00000000..4903bb9c --- /dev/null +++ b/src/main/worker/dbService/modules/index.ts @@ -0,0 +1,6 @@ + +export * as list from './list' +export * as lyric from './lyric' +export * as music_url from './music_url' +export * as music_other_source from './music_other_source' +export * as download from './download' diff --git a/src/main/worker/dbService/modules/list/dbHelper.ts b/src/main/worker/dbService/modules/list/dbHelper.ts new file mode 100644 index 00000000..48a1239b --- /dev/null +++ b/src/main/worker/dbService/modules/list/dbHelper.ts @@ -0,0 +1,361 @@ +import { getDB } from '../../db' +import { + createListQueryStatement, + createListInsertStatement, + createListDeleteStatement, + createListClearStatement, + createListUpdateStatement, + createMusicInfoQueryStatement, + createMusicInfoInsertStatement, + createMusicInfoUpdateStatement, + createMusicInfoDeleteStatement, + createMusicInfoDeleteByListIdStatement, + createMusicInfoOrderInsertStatement, + createMusicInfoOrderDeleteStatement, + createMusicInfoOrderDeleteByListIdStatement, + createMusicInfoClearStatement, + createMusicInfoOrderClearStatement, + createMusicInfoByListAndMusicInfoIdQueryStatement, + createMusicInfoByMusicInfoIdQueryStatement, +} from './statements' + + +/** + * 获取用户列表 + * @returns + */ +export const queryAllUserList = (): LX.DBService.UserListInfo[] => { + return createListQueryStatement().all() +} + +/** + * 批量插入用户列表 + * @param lists 列表 + * @param isClear 是否清空列表 + */ +export const inertUserLists = (lists: LX.DBService.UserListInfo[], isClear: boolean = false) => { + const db = getDB() + const listClearStatement = createListClearStatement() + const listInsertStatement = createListInsertStatement() + db.transaction((lists: LX.DBService.UserListInfo[]) => { + if (isClear) listClearStatement.run() + for (const list of lists) { + listInsertStatement.run({ + id: list.id, + name: list.name, + source: list.source, + sourceListId: list.sourceListId, + locationUpdateTime: list.locationUpdateTime, + position: list.position, + }) + } + })(lists) +} + +/** + * 批量删除用户列表及列表内歌曲 + * @param listIds 列表id + */ +export const deleteUserLists = (listIds: string[]) => { + const db = getDB() + const listDeleteStatement = createListDeleteStatement() + const musicInfoDeleteByListIdStatement = createMusicInfoDeleteByListIdStatement() + const musicInfoOrderDeleteByListIdStatement = createMusicInfoOrderDeleteByListIdStatement() + db.transaction((listIds: string[]) => { + for (const id of listIds) { + listDeleteStatement.run(id) + musicInfoDeleteByListIdStatement.run(id) + musicInfoOrderDeleteByListIdStatement.run(id) + } + })(listIds) +} + +/** + * 批量更新用户列表 + * @param lists 列表 + */ +export const updateUserLists = (lists: LX.DBService.UserListInfo[]) => { + const db = getDB() + const listUpdateStatement = createListUpdateStatement() + db.transaction((lists: LX.DBService.UserListInfo[]) => { + for (const list of lists) listUpdateStatement.run(list) + })(lists) +} + + +/** + * 批量添加歌曲 + * @param list + */ +export const insertMusicInfoList = (list: LX.DBService.MusicInfo[]) => { + const musicInfoInsertStatement = createMusicInfoInsertStatement() + const musicInfoOrderInsertStatement = createMusicInfoOrderInsertStatement() + const db = getDB() + return db.transaction((musics: LX.DBService.MusicInfo[]) => { + for (const music of musics) { + musicInfoInsertStatement.run(music) + musicInfoOrderInsertStatement.run({ + listId: music.listId, + musicInfoId: music.id, + order: music.order, + }) + } + })(list) +} + +/** + * 批量添加歌曲并刷新排序 + * @param list 新增歌曲 + * @param listId 列表Id + * @param listAll 原始列表歌曲,列表去重后 + */ +export const insertMusicInfoListAndRefreshOrder = (list: LX.DBService.MusicInfo[], listId: string, listAll: LX.DBService.MusicInfo[]) => { + const musicInfoInsertStatement = createMusicInfoInsertStatement() + const musicInfoOrderInsertStatement = createMusicInfoOrderInsertStatement() + const musicInfoOrderDeleteByListIdStatement = createMusicInfoOrderDeleteByListIdStatement() + + const db = getDB() + db.transaction((list: LX.DBService.MusicInfo[], listId: string, listAll: LX.DBService.MusicInfo[]) => { + musicInfoOrderDeleteByListIdStatement.run(listId) + for (const music of list) { + musicInfoInsertStatement.run(music) + musicInfoOrderInsertStatement.run({ + listId: music.listId, + musicInfoId: music.id, + order: music.order, + }) + } + for (const music of listAll) { + musicInfoOrderInsertStatement.run({ + listId: music.listId, + musicInfoId: music.id, + order: music.order, + }) + } + })(list, listId, listAll) +} + +/** + * 批量更新歌曲 + * @param list + */ +export const updateMusicInfos = (list: LX.DBService.MusicInfo[]) => { + const musicInfoUpdateStatement = createMusicInfoUpdateStatement() + const db = getDB() + db.transaction((musics: LX.DBService.MusicInfo[]) => { + for (const music of musics) { + musicInfoUpdateStatement.run(music) + } + })(list) +} + +/** + * 获取列表内的歌曲 + * @param listId 列表Id + * @returns 列表歌曲 + */ +export const queryMusicInfoByListId = (listId: string): LX.DBService.MusicInfo[] => { + const musicInfoQueryStatement = createMusicInfoQueryStatement() + return musicInfoQueryStatement.all({ listId }) +} + +/** + * 批量移动歌曲 + * @param fromId 源列表Id + * @param ids 要移动的歌曲 + * @param musicInfos 音乐信息 + */ +export const moveMusicInfo = (fromId: string, ids: string[], musicInfos: LX.DBService.MusicInfo[]) => { + const musicInfoInsertStatement = createMusicInfoInsertStatement() + const musicInfoOrderInsertStatement = createMusicInfoOrderInsertStatement() + const musicInfoDeleteStatement = createMusicInfoDeleteStatement() + const musicInfoOrderDeleteStatement = createMusicInfoOrderDeleteStatement() + // const musicInfoOrderDeleteByListIdStatement = createMusicInfoOrderDeleteByListIdStatement() + + const db = getDB() + db.transaction((fromId: string, ids: string[], musicInfos: LX.DBService.MusicInfo[]) => { + // musicInfoOrderDeleteByListIdStatement.run(fromId) + for (const id of ids) { + musicInfoDeleteStatement.run({ listId: fromId, id }) + musicInfoOrderDeleteStatement.run({ listId: fromId, id }) + } + for (const music of musicInfos) { + musicInfoInsertStatement.run(music) + musicInfoOrderInsertStatement.run({ + listId: music.listId, + musicInfoId: music.id, + order: music.order, + }) + } + })(fromId, ids, musicInfos) +} + +/** + * 批量移动歌曲并刷新排序 + * @param fromId 源列表Id + * @param ids 要移动的歌曲id,原始选择的歌曲 + * @param musicInfos 要移动的歌曲,目标列表去重后 + * @param toListAll 目标列表歌曲 + */ +export const moveMusicInfoAndRefreshOrder = (fromId: string, ids: string[], toId: string, musicInfos: LX.DBService.MusicInfo[], toListAll: LX.DBService.MusicInfo[]) => { + const musicInfoInsertStatement = createMusicInfoInsertStatement() + const musicInfoDeleteStatement = createMusicInfoDeleteStatement() + const musicInfoOrderDeleteStatement = createMusicInfoOrderDeleteStatement() + const musicInfoOrderInsertStatement = createMusicInfoOrderInsertStatement() + const musicInfoOrderDeleteByListIdStatement = createMusicInfoOrderDeleteByListIdStatement() + + const db = getDB() + db.transaction((fromId: string, ids: string[], musicInfos: LX.DBService.MusicInfo[], toListAll: LX.DBService.MusicInfo[]) => { + for (const id of ids) { + musicInfoDeleteStatement.run({ listId: fromId, id }) + musicInfoOrderDeleteStatement.run({ listId: fromId, id }) + } + musicInfoOrderDeleteByListIdStatement.run(toId) + for (const music of musicInfos) { + musicInfoInsertStatement.run(music) + musicInfoOrderInsertStatement.run({ + listId: music.listId, + musicInfoId: music.id, + order: music.order, + }) + } + for (const music of toListAll) { + musicInfoOrderInsertStatement.run({ + listId: music.listId, + musicInfoId: music.id, + order: music.order, + }) + } + })(fromId, ids, musicInfos, toListAll) +} + +/** + * 批量移除列表内音乐 + * @param listId 列表id + * @param ids 音乐id + */ +export const removeMusicInfos = (listId: string, ids: string[]) => { + const musicInfoDeleteStatement = createMusicInfoDeleteStatement() + const musicInfoOrderDeleteStatement = createMusicInfoOrderDeleteStatement() + const db = getDB() + return db.transaction((listId: string, ids: string[]) => { + for (const id of ids) { + musicInfoDeleteStatement.run({ listId, id }) + musicInfoOrderDeleteStatement.run({ listId, id }) + } + })(listId, ids) +} + +/** + * 清空列表内歌曲 + * @param listId 列表id + */ +export const removeMusicInfoByListId = (listId: string) => { + const db = getDB() + const musicInfoDeleteByListIdStatement = createMusicInfoDeleteByListIdStatement() + const musicInfoOrderDeleteByListIdStatement = createMusicInfoOrderDeleteByListIdStatement() + db.transaction((listId: string) => { + musicInfoDeleteByListIdStatement.run(listId) + musicInfoOrderDeleteByListIdStatement.run(listId) + })(listId) +} + +/** + * 创建根据列表Id与音乐id查询音乐信息 + * @param listId 列表id + * @param musicInfoId 音乐id + * @returns + */ +export const queryMusicInfoByListIdAndMusicInfoId = (listId: string, musicInfoId: string): LX.DBService.MusicInfo | null => { + const musicInfoByListAndMusicInfoIdQueryStatement = createMusicInfoByListAndMusicInfoIdQueryStatement() + return musicInfoByListAndMusicInfoIdQueryStatement.get({ listId, musicInfoId }) +} + +/** + * 创建根据音乐id查询所有列表的音乐信息 + * @param id 音乐id + * @returns + */ +export const queryMusicInfoByMusicInfoId = (id: string): LX.DBService.MusicInfo[] => { + const musicInfoByMusicInfoIdQueryStatement = createMusicInfoByMusicInfoIdQueryStatement() + return musicInfoByMusicInfoIdQueryStatement.all(id) +} + +/** + * 批量更新歌曲位置 + * @param listId 列表id + * @param musicInfoOrders 音乐顺序 + */ +export const updateMusicInfoOrder = (listId: string, musicInfoOrders: LX.DBService.MusicInfoOrder[]) => { + const db = getDB() + const musicInfoOrderInsertStatement = createMusicInfoOrderInsertStatement() + const musicInfoOrderDeleteByListIdStatement = createMusicInfoOrderDeleteByListIdStatement() + db.transaction((listId: string, musicInfoOrders: LX.DBService.MusicInfoOrder[]) => { + musicInfoOrderDeleteByListIdStatement.run(listId) + for (const orderInfo of musicInfoOrders) musicInfoOrderInsertStatement.run(orderInfo) + })(listId, musicInfoOrders) +} + +/** + * 覆盖列表内的歌曲 + * @param listId 列表id + * @param musicInfos 歌曲列表 + */ +export const overwriteMusicInfo = (listId: string, musicInfos: LX.DBService.MusicInfo[]) => { + const db = getDB() + const musicInfoDeleteByListIdStatement = createMusicInfoDeleteByListIdStatement() + const musicInfoOrderDeleteByListIdStatement = createMusicInfoOrderDeleteByListIdStatement() + const musicInfoInsertStatement = createMusicInfoInsertStatement() + const musicInfoOrderInsertStatement = createMusicInfoOrderInsertStatement() + db.transaction((listId: string, musicInfos: LX.DBService.MusicInfo[]) => { + musicInfoDeleteByListIdStatement.run(listId) + musicInfoOrderDeleteByListIdStatement.run(listId) + for (const musicInfo of musicInfos) { + musicInfoInsertStatement.run(musicInfo) + musicInfoOrderInsertStatement.run({ + listId: musicInfo.listId, + musicInfoId: musicInfo.id, + order: musicInfo.order, + }) + } + })(listId, musicInfos) +} + +/** + * 覆盖整个列表 + * @param lists 列表 + * @param musicInfos 歌曲列表 + */ +export const overwriteListData = (lists: LX.DBService.UserListInfo[], musicInfos: LX.DBService.MusicInfo[]) => { + const db = getDB() + const listClearStatement = createListClearStatement() + const listInsertStatement = createListInsertStatement() + const musicInfoClearStatement = createMusicInfoClearStatement() + const musicInfoInsertStatement = createMusicInfoInsertStatement() + const musicInfoOrderClearStatement = createMusicInfoOrderClearStatement() + const musicInfoOrderInsertStatement = createMusicInfoOrderInsertStatement() + db.transaction((lists: LX.DBService.UserListInfo[], musicInfos: LX.DBService.MusicInfo[]) => { + listClearStatement.run() + for (const list of lists) { + listInsertStatement.run({ + id: list.id, + name: list.name, + source: list.source, + sourceListId: list.sourceListId, + locationUpdateTime: list.locationUpdateTime, + position: list.position, + }) + } + musicInfoClearStatement.run() + musicInfoOrderClearStatement.run() + for (const musicInfo of musicInfos) { + musicInfoInsertStatement.run(musicInfo) + musicInfoOrderInsertStatement.run({ + listId: musicInfo.listId, + musicInfoId: musicInfo.id, + order: musicInfo.order, + }) + } + })(lists, musicInfos) +} + diff --git a/src/main/worker/dbService/modules/list/index.ts b/src/main/worker/dbService/modules/list/index.ts new file mode 100644 index 00000000..301864d3 --- /dev/null +++ b/src/main/worker/dbService/modules/list/index.ts @@ -0,0 +1,407 @@ +import { LIST_IDS } from '@common/constants' +import { + deleteUserLists, + inertUserLists, + insertMusicInfoList, + insertMusicInfoListAndRefreshOrder, + moveMusicInfo, + moveMusicInfoAndRefreshOrder, + overwriteListData, + overwriteMusicInfo, + queryAllUserList, + queryMusicInfoByListId, + queryMusicInfoByListIdAndMusicInfoId, + queryMusicInfoByMusicInfoId, + removeMusicInfoByListId, + removeMusicInfos, + updateMusicInfoOrder, + updateMusicInfos, + updateUserLists as updateUserListsFromDB, +} from './dbHelper' + +let userLists: LX.DBService.UserListInfo[] +let musicLists = new Map() + +const toDBMusicInfo = (musicInfos: LX.Music.MusicInfo[], listId: string, offset: number = 0): LX.DBService.MusicInfo[] => { + return musicInfos.map((info, index) => { + return { + ...info, + listId, + meta: JSON.stringify(info.meta), + order: offset + index, + } + }) +} + +/** + * 获取所有用户列表 + * @returns + */ +export const getAllUserList = (): LX.List.UserListInfo[] => { + if (userLists == null) userLists = queryAllUserList() + + return userLists.map(list => { + const { position, ...newList } = list + return newList + }) +} + +/** + * 批量创建列表 + * @param position 列表位置 + * @param lists 列表信息 + */ +export const createUserLists = (position: number, lists: LX.List.UserListInfo[]) => { + if (userLists == null) userLists = queryAllUserList() + if (position < 0 || position >= userLists.length) { + const newLists: LX.DBService.UserListInfo[] = lists.map((list, index) => { + return { + ...list, + position: position + index, + } + }) + inertUserLists(newLists) + userLists = [...userLists, ...newLists] + } else { + const newUserLists = [...userLists] + // @ts-expect-error + newUserLists.splice(position, 0, ...lists) + newUserLists.forEach((list, index) => { + list.position = index + }) + inertUserLists(newUserLists, true) + userLists = newUserLists + } +} + +/** + * 覆盖列表 + * @param lists 列表信息 + */ +export const setUserLists = (lists: LX.List.UserListInfo[]) => { + const newUserLists: LX.DBService.UserListInfo[] = lists.map((list, index) => { + return { + ...list, + position: index, + } + }) + inertUserLists(newUserLists, true) + userLists = newUserLists +} + +/** + * 批量删除列表 + * @param ids 列表ids + */ +export const removeUserLists = (ids: string[]) => { + deleteUserLists(ids) + if (userLists) userLists = queryAllUserList() +} + +/** + * 批量更新列表信息 + * @param lists 列表信息 + */ +export const updateUserLists = (lists: LX.List.UserListInfo[]) => { + const positionMap = new Map() + for (const list of userLists) { + positionMap.set(list.id, list.position) + } + const dbList: LX.DBService.UserListInfo[] = lists.map(list => { + const position = positionMap.get(list.id) + if (position == null) return null + return { + ...list, + position, + } + }).filter(Boolean) as LX.DBService.UserListInfo[] + updateUserListsFromDB(dbList) + if (userLists) userLists = queryAllUserList() +} + +/** + * 批量更新列表位置 + * @param position 列表位置 + * @param ids 列表ids + */ +export const updateUserListsPosition = (position: number, ids: string[]) => { + if (userLists == null) userLists = queryAllUserList() + + const newUserLists = [...userLists] + + const updateLists: LX.DBService.UserListInfo[] = [] + + for (let i = newUserLists.length - 1; i >= 0; i--) { + if (ids.includes(newUserLists[i].id)) { + const list = newUserLists.splice(i, 1)[0] + list.locationUpdateTime = Date.now() + updateLists.push(list) + } + } + position = Math.min(newUserLists.length, position) + + newUserLists.splice(position, 0, ...updateLists) + newUserLists.forEach((list, index) => { + list.position = index + }) + inertUserLists(newUserLists, true) + userLists = newUserLists +} + +/** + * 根据列表ID获取列表内歌曲 + * @param listId 列表ID + * @returns 列表内歌曲 + */ +export const getListMusics = (listId: string): LX.Music.MusicInfo[] => { + let targetList: LX.Music.MusicInfo[] | undefined = musicLists.get(listId) + if (targetList == null) { + targetList = queryMusicInfoByListId(listId).map(info => { + return { + id: info.id, + name: info.name, + singer: info.singer, + source: info.source, + interval: info.interval, + meta: JSON.parse(info.meta), + } + }) + musicLists.set(listId, targetList) + } + + return targetList +} + +/** + * 覆盖列表内的歌曲 + * @param listId 列表id + * @param musicInfos 歌曲列表 + */ +export const musicOverwrite = (listId: string, musicInfos: LX.Music.MusicInfo[]) => { + let targetList = getListMusics(listId) + overwriteMusicInfo(listId, toDBMusicInfo(musicInfos, listId)) + if (targetList) targetList.splice(0, targetList.length, ...musicInfos) +} + +/** + * 批量添加歌曲 + * @param listId 列表id + * @param musicInfos 添加的歌曲信息 + * @param addMusicLocationType 添加在到列表的位置 + */ +export const musicsAdd = (listId: string, musicInfos: LX.Music.MusicInfo[], addMusicLocationType: LX.AddMusicLocationType) => { + let targetList = getListMusics(listId) + + const set = new Set() + for (const item of targetList) set.add(item.id) + musicInfos = musicInfos.filter(item => { + if (set.has(item.id)) return false + set.add(item.id) + return true + }) + + switch (addMusicLocationType) { + case 'top': + insertMusicInfoListAndRefreshOrder(toDBMusicInfo(musicInfos, listId), listId, toDBMusicInfo(targetList, listId, musicInfos.length)) + targetList.unshift(...musicInfos) + break + case 'bottom': + default: + insertMusicInfoList(toDBMusicInfo(musicInfos, listId, targetList.length)) + targetList.push(...musicInfos) + break + } +} + +/** + * 批量删除歌曲 + * @param listId 列表Id + * @param ids 要删除歌曲的id + */ +export const musicsRemove = (listId: string, ids: string[]) => { + let targetList = getListMusics(listId) + if (!targetList.length) return + ids = [...ids] + removeMusicInfos(listId, ids) + for (let i = targetList.length - 1; i > -1; i--) { + const item = targetList[i] + const index = ids.indexOf(item.id) + if (index < 0) continue + ids.splice(index, 1) + targetList.splice(i, 1) + } +} + +/** + * 批量移动歌曲 + * @param fromId 源列表id + * @param toId 目标列表id + * @param musicInfos 添加的歌曲信息 + * @param addMusicLocationType 添加在到列表的位置 + */ +export const musicsMove = (fromId: string, toId: string, musicInfos: LX.Music.MusicInfo[], addMusicLocationType: LX.AddMusicLocationType) => { + const ids = musicInfos.map(musicInfo => musicInfo.id) + let toList = getListMusics(toId) + switch (addMusicLocationType) { + case 'top': + moveMusicInfoAndRefreshOrder(fromId, ids, toId, toDBMusicInfo(musicInfos, toId), toDBMusicInfo(toList, toId, musicInfos.length)) + break + case 'bottom': + default: + moveMusicInfo(fromId, ids, toDBMusicInfo(musicInfos, toId, toList.length)) + break + } + musicsRemove(fromId, ids) + musicsAdd(toId, musicInfos, addMusicLocationType) + // let fromList = getListMusics(fromId) + // let toList = getListMusics(toId) + + // const ids = musicInfos.map(musicInfo => musicInfo.id) + + // const map = new Map() + // for (const item of toList) map.set(item.id, item) + // musicInfos = musicInfos.filter(item => { + // if (map.has(item.id)) return false + // map.set(item.id, item) + // return true + // }) + + // switch (addMusicLocationType) { + // case 'top': + // moveMusicInfoAndRefreshOrder(fromId, ids, toId, toDBMusicInfo(musicInfos, toId), toDBMusicInfo(toList, toId, musicInfos.length)) + // toList.unshift(...musicInfos) + // break + // case 'bottom': + // default: + // moveMusicInfo(fromId, ids, toDBMusicInfo(musicInfos, toId, toList.length)) + // toList.push(...musicInfos) + // break + // } + // for (let i = fromList.length - 1; i > -1; i--) { + // const item = fromList[i] + // const index = ids.indexOf(item.id) + // if (index < 0) continue + // ids.splice(index, 1) + // fromList.splice(i, 1) + // } +} + +/** + * 批量更新歌曲信息 + * @param musicInfos 歌曲&列表信息 + */ +export const musicsUpdate = (musicInfos: LX.List.ListActionMusicUpdate) => { + updateMusicInfos(musicInfos.map(({ id, musicInfo }) => { + return { + ...musicInfo, + listId: id, + meta: JSON.stringify(musicInfo.meta), + order: 0, + } + })) + for (const { id, musicInfo } of musicInfos) { + const targetList = musicLists.get(id) + if (targetList == null) continue + const targetMusic = targetList.find(item => item.id == musicInfo.id) + if (!targetMusic) continue + targetMusic.name = musicInfo.name + targetMusic.singer = musicInfo.singer + targetMusic.source = musicInfo.source + targetMusic.interval = musicInfo.interval + targetMusic.meta = musicInfo.meta + } +} + +/** + * 清空列表内的歌曲 + * @param listId 列表Id + */ +export const musicsClear = (listId: string) => { + removeMusicInfoByListId(listId) + const targetList = getListMusics(listId) + if (targetList == null) return + targetList.slice(0, targetList.length) +} + +/** + * 批量更新歌曲位置 + * @param listId 列表id + * @param position 新位置 + * @param ids 要更新位置的歌曲id + */ +export const musicsPositionUpdate = (listId: string, position: number, ids: string[]) => { + let targetList = getListMusics(listId) + if (!targetList.length) return + + const newTargetList = [...targetList] + const infos = Array(ids.length) + for (let i = newTargetList.length; i--;) { + const item = newTargetList[i] + const index = ids.indexOf(item.id) + if (index < 0) continue + infos.splice(index, 1, newTargetList.splice(i, 1)[0]) + } + newTargetList.splice(Math.min(position, newTargetList.length), 0, ...infos) + + updateMusicInfoOrder(listId, newTargetList.map((info, index) => { + return { + listId, + musicInfoId: info.id, + order: index, + } + })) + musicLists.set(listId, newTargetList) +} + +/** + * 覆盖所有列表数据 + * @param myListData 完整列表数据 + */ +export const listDataOverwrite = (myListData: MakeOptional) => { + const dbLists: LX.DBService.UserListInfo[] = [] + const listData: LX.List.ListDataFull = { + ...myListData, + tempList: myListData.tempList ?? getListMusics(LIST_IDS.TEMP), + } + + const dbMusicInfos: LX.DBService.MusicInfo[] = [ + ...toDBMusicInfo(listData.defaultList, LIST_IDS.DEFAULT), + ...toDBMusicInfo(listData.loveList, LIST_IDS.LOVE), + ...toDBMusicInfo(listData.tempList, LIST_IDS.TEMP), + ] + listData.userList.forEach(({ list, ...listInfo }, index) => { + dbLists.push({ ...listInfo, position: index }) + dbMusicInfos.push(...toDBMusicInfo(list, listInfo.id)) + }) + overwriteListData(dbLists, dbMusicInfos) + + if (userLists) userLists.splice(0, userLists.length, ...dbLists) + else userLists = dbLists + + musicLists.clear() + musicLists.set(LIST_IDS.DEFAULT, listData.defaultList) + musicLists.set(LIST_IDS.LOVE, listData.loveList) + musicLists.set(LIST_IDS.TEMP, listData.tempList) + for (const list of listData.userList) musicLists.set(list.id, list.list) +} + +/** + * 检查音乐是否存在列表中 + * @param listId 列表id + * @param musicInfoId 音乐id + * @returns + */ +export const checkListExistMusic = (listId: string, musicInfoId: string): boolean => { + const musicInfo = queryMusicInfoByListIdAndMusicInfoId(listId, musicInfoId) + return musicInfo != null +} + +/** + * 获取所有存在该音乐的列表id + * @param musicInfoId 音乐id + * @returns + */ +export const getMusicExistListIds = (musicInfoId: string): string[] => { + const musicInfos = queryMusicInfoByMusicInfoId(musicInfoId) + return musicInfos.map(m => m.listId) +} diff --git a/src/main/worker/dbService/modules/list/statements.ts b/src/main/worker/dbService/modules/list/statements.ts new file mode 100644 index 00000000..fa7a118b --- /dev/null +++ b/src/main/worker/dbService/modules/list/statements.ts @@ -0,0 +1,186 @@ +import { getDB } from '../../db' + + +/** + * 创建列表查询语句 + * @returns 查询语句 + */ +export const createListQueryStatement = () => { + const db = getDB() + return db.prepare(` + SELECT "id", "name", "source", "sourceListId", "position", "locationUpdateTime" + FROM "main"."my_list" + `) +} + +/** + * 创建列表插入语句 + * @returns 插入语句 + */ +export const createListInsertStatement = () => { + const db = getDB() + return db.prepare<[LX.DBService.UserListInfo]>(` + INSERT INTO "main"."my_list" ("id", "name", "source", "sourceListId", "position", "locationUpdateTime") + VALUES (@id, @name, @source, @sourceListId, @position, @locationUpdateTime)`) +} + +/** + * 创建列表清空语句 + * @returns 清空语句 + */ +export const createListClearStatement = () => { + const db = getDB() + return db.prepare('DELETE FROM "main"."my_list"') +} + +/** + * 创建列表删除语句 + * @returns 删除语句 + */ +export const createListDeleteStatement = () => { + const db = getDB() + return db.prepare<[string]>('DELETE FROM "main"."my_list" WHERE "id"=?') +} + +/** + * 创建列表更新语句 + * @returns 更新语句 + */ +export const createListUpdateStatement = () => { + const db = getDB() + return db.prepare<[LX.DBService.UserListInfo]>(` + UPDATE "main"."my_list" + SET "name"=@name, "source"=@source, "sourceListId"=@sourceListId, "locationUpdateTime"=@locationUpdateTime + WHERE "id"=@id`) +} + +/** + * 创建音乐信息查询语句 + * @returns 查询语句 + */ +export const createMusicInfoQueryStatement = () => { + const db = getDB() + return db.prepare<[LX.DBService.MusicInfoQuery]>(` + SELECT mInfo."id", mInfo."name", mInfo."singer", mInfo."source", mInfo."interval", mInfo."meta" + FROM my_list_music_info mInfo + LEFT JOIN my_list_music_info_order O + ON mInfo.id=O.musicInfoId AND O.listId=@listId + WHERE mInfo.listId=@listId + ORDER BY O."order" ASC + `) +} + +/** + * 创建音乐信息插入语句 + * @returns 插入语句 + */ +export const createMusicInfoInsertStatement = () => { + const db = getDB() + return db.prepare<[LX.DBService.MusicInfo]>(` + INSERT INTO "main"."my_list_music_info" ("id", "listId", "name", "singer", "source", "interval", "meta") + VALUES (@id, @listId, @name, @singer, @source, @interval, @meta)`) +} + +/** + * 创建音乐信息更新语句 + * @returns 更新语句 + */ +export const createMusicInfoUpdateStatement = () => { + const db = getDB() + return db.prepare<[LX.DBService.MusicInfo]>(` + UPDATE "main"."my_list_music_info" + SET "name"=@name, "singer"=@singer, "source"=@source, "interval"=@interval, "meta"=@meta + WHERE "id"=@id AND "listId"=@listId`) +} + + +/** + * 创建清空音乐信息语句 + * @returns 删除语句 + */ +export const createMusicInfoClearStatement = () => { + const db = getDB() + return db.prepare('DELETE FROM "main"."my_list_music_info"') +} + +/** + * 创建根据列表id批量删除音乐信息语句 + * @returns 删除语句 + */ +export const createMusicInfoDeleteByListIdStatement = () => { + const db = getDB() + return db.prepare<[string]>('DELETE FROM "main"."my_list_music_info" WHERE "listId"=?') +} + +/** + * 创建根据列表Id与音乐id批量删除音乐信息语句 + * @returns 删除语句 + */ +export const createMusicInfoDeleteStatement = () => { + const db = getDB() + return db.prepare<[LX.DBService.MusicInfoRemove]>('DELETE FROM "main"."my_list_music_info" WHERE "id"=@id AND "listId"=@listId') +} + +/** + * 创建根据列表Id与音乐id查询音乐信息语句 + * @returns 删除语句 + */ +export const createMusicInfoByListAndMusicInfoIdQueryStatement = () => { + const db = getDB() + return db.prepare<[LX.DBService.ListMusicInfoQuery]>(`SELECT "id", "name", "singer", "source", "interval", "meta" + FROM "main"."my_list_music_info" + WHERE "id"=@musicInfoId + AND "listId"=@listId`) +} + +/** + * 创建根据音乐id查询音乐信息语句 + * @returns 删除语句 + */ +export const createMusicInfoByMusicInfoIdQueryStatement = () => { + const db = getDB() + return db.prepare<[string]>(`SELECT "id", "name", "singer", "source", "interval", "meta", "listId" + FROM "main"."my_list_music_info" + WHERE "id"=?`) +} + + +/** + * 创建音乐信息排序插入语句 + * @returns 插入语句 + */ +export const createMusicInfoOrderInsertStatement = () => { + const db = getDB() + return db.prepare<[LX.DBService.MusicInfoOrder]>(` + INSERT INTO "main"."my_list_music_info_order" ("listId", "musicInfoId", "order") + VALUES (@listId, @musicInfoId, @order)`) +} + +/** + * 创建清空音乐排序语句 + * @returns 删除语句 + */ +export const createMusicInfoOrderClearStatement = () => { + const db = getDB() + return db.prepare('DELETE FROM "main"."my_list_music_info_order"') +} + +/** + * 创建根据列表id删除音乐排序语句 + * @returns 删除语句 + */ +export const createMusicInfoOrderDeleteByListIdStatement = () => { + const db = getDB() + return db.prepare<[string]>('DELETE FROM "main"."my_list_music_info_order" WHERE "listId"=?') +} + +/** + * 创建根据列表Id与音乐id删除音乐排序语句 + * @returns 删除语句 + */ +export const createMusicInfoOrderDeleteStatement = () => { + const db = getDB() + return db.prepare<[LX.DBService.MusicInfoRemove]>('DELETE FROM "main"."my_list_music_info_order" WHERE "musicInfoId"=@id AND "listId"=@listId') +} + + diff --git a/src/main/worker/dbService/modules/lyric/dbHelper.ts b/src/main/worker/dbService/modules/lyric/dbHelper.ts new file mode 100644 index 00000000..a5c71e6b --- /dev/null +++ b/src/main/worker/dbService/modules/lyric/dbHelper.ts @@ -0,0 +1,152 @@ +import { getDB } from '../../db' +import { + createLyricQueryStatement, + createRawLyricQueryStatement, + createRawLyricInsertStatement, + createRawLyricDeleteStatement, + createRawLyricUpdateStatement, + createRawLyricClearStatement, + createEditedLyricQueryStatement, + createEditedLyricInsertStatement, + createEditedLyricDeleteStatement, + createEditedLyricUpdateStatement, + createEditedLyricClearStatement, + createEditedLyricCountStatement, + createRawLyricCountStatement, +} from './statements' + +/** + * 查询原始歌词 + * @param id 歌曲id + * @returns 歌词信息 + */ +export const queryLyric = (id: string): LX.DBService.Lyricnfo[] => { + const lyricQueryStatement = createLyricQueryStatement() + return lyricQueryStatement.all(id) +} + +/** + * 查询原始歌词 + * @param id 歌曲id + * @returns 歌词信息 + */ +export const queryRawLyric = (id: string): LX.DBService.Lyricnfo[] => { + const rawLyricQueryStatement = createRawLyricQueryStatement() + return rawLyricQueryStatement.all(id) +} + +/** + * 批量插入原始歌词 + * @param lyrics 列表 + */ +export const inertRawLyric = (lyrics: LX.DBService.Lyricnfo[]) => { + const db = getDB() + const rawLyricInsertStatement = createRawLyricInsertStatement() + db.transaction((lyrics: LX.DBService.Lyricnfo[]) => { + for (const lyric of lyrics) rawLyricInsertStatement.run(lyric) + })(lyrics) +} + +/** + * 批量删除原始歌词 + * @param ids 列表 + */ +export const deleteRawLyric = (ids: string[]) => { + const db = getDB() + const rawLyricDeleteStatement = createRawLyricDeleteStatement() + db.transaction((ids: string[]) => { + for (const id of ids) rawLyricDeleteStatement.run(id) + })(ids) +} + +/** + * 批量更新原始歌词 + * @param lyrics 列表 + */ +export const updateRawLyric = (lyrics: LX.DBService.Lyricnfo[]) => { + const db = getDB() + const rawLyricUpdateStatement = createRawLyricUpdateStatement() + db.transaction((lyrics: LX.DBService.Lyricnfo[]) => { + for (const lyric of lyrics) rawLyricUpdateStatement.run(lyric) + })(lyrics) +} + +/** + * 清空原始歌词 + */ +export const clearRawLyric = () => { + const rawLyricClearStatement = createRawLyricClearStatement() + rawLyricClearStatement.run() +} + +/** + * 统计已编辑歌词数量 + */ +export const countRawLyric = () => { + const countStatement = createRawLyricCountStatement() + return countStatement.get().count +} + + +/** + * 查询已编辑歌词 + * @param id 歌曲id + * @returns 歌词信息 + */ +export const queryEditedLyric = (id: string): LX.DBService.Lyricnfo[] => { + const rawLyricQueryStatement = createEditedLyricQueryStatement() + return rawLyricQueryStatement.all(id) +} + +/** + * 批量插入已编辑歌词 + * @param lyrics 列表 + */ +export const inertEditedLyric = (lyrics: LX.DBService.Lyricnfo[]) => { + const db = getDB() + const rawLyricInsertStatement = createEditedLyricInsertStatement() + db.transaction((lyrics: LX.DBService.Lyricnfo[]) => { + for (const lyric of lyrics) rawLyricInsertStatement.run(lyric) + })(lyrics) +} + +/** + * 批量删除已编辑歌词 + * @param ids 列表 + */ +export const deleteEditedLyric = (ids: string[]) => { + const db = getDB() + const rawLyricDeleteStatement = createEditedLyricDeleteStatement() + db.transaction((ids: string[]) => { + for (const id of ids) rawLyricDeleteStatement.run(id) + })(ids) +} + +/** + * 批量更新已编辑歌词 + * @param lyrics 列表 + */ +export const updateEditedLyric = (lyrics: LX.DBService.Lyricnfo[]) => { + const db = getDB() + const rawLyricUpdateStatement = createEditedLyricUpdateStatement() + db.transaction((lyrics: LX.DBService.Lyricnfo[]) => { + for (const lyric of lyrics) rawLyricUpdateStatement.run(lyric) + })(lyrics) +} + +/** + * 清空已编辑歌词 + */ +export const clearEditedLyric = () => { + const rawLyricClearStatement = createEditedLyricClearStatement() + rawLyricClearStatement.run() +} + + +/** + * 统计已编辑歌词数量 + */ +export const countEditedLyric = () => { + const countStatement = createEditedLyricCountStatement() + return countStatement.get().count +} diff --git a/src/main/worker/dbService/modules/lyric/index.ts b/src/main/worker/dbService/modules/lyric/index.ts new file mode 100644 index 00000000..a1d5bca4 --- /dev/null +++ b/src/main/worker/dbService/modules/lyric/index.ts @@ -0,0 +1,197 @@ +import { + queryLyric, + queryRawLyric, + inertRawLyric, + deleteRawLyric, + updateRawLyric, + clearRawLyric, + queryEditedLyric, + inertEditedLyric, + deleteEditedLyric, + updateEditedLyric, + clearEditedLyric, + countEditedLyric, + countRawLyric, +} from './dbHelper' + +const keys = ['lyric', 'tlyric', 'rlyric', 'lxlyric'] as const + +const toDBLyric = (id: string, source: LX.DBService.Lyricnfo['source'], lyricInfo: LX.Music.LyricInfo): LX.DBService.Lyricnfo[] => { + return (keys.map(k => [k, lyricInfo[k]]) + .filter(([k, t]) => t != null) as Array<[LX.DBService.Lyricnfo['type'], string]>) + .map(([k, t]) => { + return { + id, + type: k, + text: Buffer.from(t).toString('base64'), + source, + } + }) +} + +/** + * 获取歌词 + * @param id 歌曲id + * @returns 歌词信息 + */ +export const getPlayerLyric = (id: string): LX.Player.LyricInfo => { + const lyrics = queryLyric(id) + + let lyricInfo: LX.Music.LyricInfo = { + lyric: '', + } + let rawLyricInfo: LX.Music.LyricInfo = { + lyric: '', + } + for (const lyric of lyrics) { + switch (lyric.source) { + case 'edited': + if (lyric.type == 'lyric') lyricInfo.lyric = Buffer.from(lyric.text, 'base64').toString() + else if (lyric.text != null) lyricInfo[lyric.type] = Buffer.from(lyric.text, 'base64').toString() + break + default: + if (lyric.type == 'lyric') rawLyricInfo.lyric = Buffer.from(lyric.text, 'base64').toString() + else if (lyric.text != null) rawLyricInfo[lyric.type] = Buffer.from(lyric.text, 'base64').toString() + break + } + } + + return lyricInfo.lyric ? { + ...lyricInfo, + rawlrcInfo: rawLyricInfo, + } : { + ...rawLyricInfo, + rawlrcInfo: rawLyricInfo, + } +} + +/** + * 获取原始歌词 + * @param id 歌曲id + * @returns 歌词信息 + */ +export const getRawLyric = (id: string): LX.Music.LyricInfo => { + const lyrics = queryRawLyric(id) + + let lyricInfo: LX.Music.LyricInfo = { + lyric: '', + } + for (const lyric of lyrics) { + if (lyric.type == 'lyric') lyricInfo.lyric = Buffer.from(lyric.text, 'base64').toString() + else if (lyric.text != null) lyricInfo[lyric.type] = Buffer.from(lyric.text, 'base64').toString() + } + + return lyricInfo +} + +/** + * 保存原始歌词信息 + * @param id 歌曲id + * @param lyricInfo 歌词信息 + */ +export const rawLyricAdd = (id: string, lyricInfo: LX.Music.LyricInfo) => { + inertRawLyric(toDBLyric(id, 'raw', lyricInfo)) +} + +/** + * 删除原始歌词信息 + * @param ids 歌曲id + */ +export const rawLyricRemove = (ids: string[]) => { + deleteRawLyric(ids) +} + +/** + * 更新原始歌词信息 + * @param id 歌曲id + * @param lyricInfo 歌词信息 + */ +export const rawLyricUpdate = (id: string, lyricInfo: LX.Music.LyricInfo) => { + updateRawLyric(toDBLyric(id, 'raw', lyricInfo)) +} + +/** + * 清空原始歌词信息 + */ +export const rawLyricClear = () => { + clearRawLyric() +} + +/** + * 统计原始歌词数量 + */ +export const rawLyricCount = () => { + return countRawLyric() +} + + +/** + * 获取已编辑歌词 + * @param id 歌曲id + * @returns 歌词信息 + */ +export const getEditedLyric = (id: string): LX.Music.LyricInfo => { + const lyrics = queryEditedLyric(id) + + let lyricInfo: LX.Music.LyricInfo = { + lyric: '', + } + for (const lyric of lyrics) { + if (lyric.type == 'lyric') lyricInfo.lyric = Buffer.from(lyric.text, 'base64').toString() + else if (lyric.text != null) lyricInfo[lyric.type] = Buffer.from(lyric.text, 'base64').toString() + } + + return lyricInfo +} + +/** + * 保存已编辑歌词信息 + * @param id 歌曲id + * @param lyricInfo 歌词信息 + */ +export const editedLyricAdd = (id: string, lyricInfo: LX.Music.LyricInfo) => { + inertEditedLyric(toDBLyric(id, 'edited', lyricInfo)) +} + +/** + * 删除已编辑歌词信息 + * @param ids 歌曲id + */ +export const editedLyricRemove = (ids: string[]) => { + deleteEditedLyric(ids) +} + +/** + * 更新已编辑歌词信息 + * @param id 歌曲id + * @param lyricInfo 歌词信息 + */ +export const editedLyricUpdate = (id: string, lyricInfo: LX.Music.LyricInfo) => { + updateEditedLyric(toDBLyric(id, 'edited', lyricInfo)) +} + +/** + * 清空已编辑歌词信息 + */ +export const editedLyricClear = () => { + clearEditedLyric() +} + +/** + * 新增或更新已编辑歌词信息 + * @param id 歌曲id + * @param lyricInfo 歌词信息 + */ +export const editedLyricUpdateAddAndUpdate = (id: string, lyricInfo: LX.Music.LyricInfo) => { + const lyrics = queryEditedLyric(id) + if (lyrics.length) updateEditedLyric(toDBLyric(id, 'edited', lyricInfo)) + else inertEditedLyric(toDBLyric(id, 'edited', lyricInfo)) +} + +/** + * 统计已编辑歌词数量 + */ +export const editedLyricCount = () => { + return countEditedLyric() +} + diff --git a/src/main/worker/dbService/modules/lyric/statements.ts b/src/main/worker/dbService/modules/lyric/statements.ts new file mode 100644 index 00000000..15a3f57d --- /dev/null +++ b/src/main/worker/dbService/modules/lyric/statements.ts @@ -0,0 +1,157 @@ +import { getDB } from '../../db' + +const RAW_LYRIC = 'raw' +const EDITED_LYRIC = 'edited' + +/** + * 创建歌词查询语句 + * @returns 查询语句 + */ +export const createLyricQueryStatement = () => { + const db = getDB() + return db.prepare<[string]>(` + SELECT "type", "text", "source" + FROM "main"."lyric" + WHERE "id"=? + `) +} + +/** + * 创建原始歌词查询语句 + * @returns 查询语句 + */ +export const createRawLyricQueryStatement = () => { + const db = getDB() + return db.prepare<[string]>(` + SELECT "type", "text" + FROM "main"."lyric" + WHERE "id"=? AND "source"='${RAW_LYRIC}' + `) +} + +/** + * 创建原始歌词插入语句 + * @returns 插入语句 + */ +export const createRawLyricInsertStatement = () => { + const db = getDB() + return db.prepare<[LX.DBService.Lyricnfo]>(` + INSERT INTO "main"."lyric" ("id", "type", "text", "source") + VALUES (@id, @type, @text, '${RAW_LYRIC}')`) +} + +/** + * 创建原始歌词清空语句 + * @returns 清空语句 + */ +export const createRawLyricClearStatement = () => { + const db = getDB() + return db.prepare(` + DELETE FROM "main"."lyric" + WHERE "source"='${RAW_LYRIC}' + `) +} + +/** + * 创建原始歌词删除语句 + * @returns 删除语句 + */ +export const createRawLyricDeleteStatement = () => { + const db = getDB() + return db.prepare<[string]>(` + DELETE FROM "main"."lyric" + WHERE "id"=? AND "source"='${RAW_LYRIC}' + `) +} + +/** + * 创建原始歌词更新语句 + * @returns 更新语句 + */ +export const createRawLyricUpdateStatement = () => { + const db = getDB() + return db.prepare<[LX.DBService.Lyricnfo]>(` + UPDATE "main"."lyric" + SET "text"=@text + WHERE "id"=@id AND "source"='${RAW_LYRIC}' AND "type"=@type`) +} + + +/** + * 创建原始歌词数量统计语句 + * @returns 统计语句 + */ +export const createRawLyricCountStatement = () => { + const db = getDB() + return db.prepare(`SELECT COUNT(*) as count FROM "main"."lyric" WHERE "source"='${RAW_LYRIC}'`) +} + + +/** + * 创建已编辑歌词查询语句 + * @returns 查询语句 + */ +export const createEditedLyricQueryStatement = () => { + const db = getDB() + return db.prepare<[string]>(` + SELECT "type", "text" + FROM "main"."lyric" + WHERE "id"=? AND "source"='${EDITED_LYRIC}' + `) +} + +/** + * 创建已编辑歌词插入语句 + * @returns 插入语句 + */ +export const createEditedLyricInsertStatement = () => { + const db = getDB() + return db.prepare<[LX.DBService.Lyricnfo]>(` + INSERT INTO "main"."lyric" ("id", "type", "text", "source") + VALUES (@id, @type, @text, '${EDITED_LYRIC}')`) +} + +/** + * 创建已编辑歌词清空语句 + * @returns 清空语句 + */ +export const createEditedLyricClearStatement = () => { + const db = getDB() + return db.prepare(` + DELETE FROM "main"."lyric" + WHERE "source"='${EDITED_LYRIC}' + `) +} + +/** + * 创建已编辑歌词删除语句 + * @returns 删除语句 + */ +export const createEditedLyricDeleteStatement = () => { + const db = getDB() + return db.prepare<[string]>(` + DELETE FROM "main"."lyric" + WHERE "id"=? AND "source"='${EDITED_LYRIC}' + `) +} + +/** + * 创建已编辑歌词更新语句 + * @returns 更新语句 + */ +export const createEditedLyricUpdateStatement = () => { + const db = getDB() + return db.prepare<[LX.DBService.Lyricnfo]>(` + UPDATE "main"."lyric" + SET "text"=@text + WHERE "id"=@id AND "source"='${EDITED_LYRIC}' AND "type"=@type`) +} + +/** + * 创建已编辑歌词数量统计语句 + * @returns 统计语句 + */ +export const createEditedLyricCountStatement = () => { + const db = getDB() + return db.prepare(`SELECT COUNT(*) as count FROM "main"."lyric" WHERE "source"='${EDITED_LYRIC}'`) +} diff --git a/src/main/worker/dbService/modules/music_other_source/dbHelper.ts b/src/main/worker/dbService/modules/music_other_source/dbHelper.ts new file mode 100644 index 00000000..0fd01fc7 --- /dev/null +++ b/src/main/worker/dbService/modules/music_other_source/dbHelper.ts @@ -0,0 +1,59 @@ +import { getDB } from '../../db' +import { + createMusicInfoQueryStatement, + createMusicInfoInsertStatement, + createMusicInfoDeleteStatement, + createMusicInfoClearStatement, + createCountStatement, +} from './statements' + + +/** + * 查询歌曲信息 + * @param id 歌曲id + * @returns 歌曲信息 + */ +export const queryMusicInfo = (id: string): LX.DBService.MusicInfoOtherSource[] => { + const musicInfoQueryStatement = createMusicInfoQueryStatement() + return musicInfoQueryStatement.all(id) +} + +/** + * 批量插入歌曲信息 + * @param musicInfos 列表 + */ +export const inertMusicInfo = (musicInfos: LX.DBService.MusicInfoOtherSource[]) => { + const db = getDB() + const musicInfoInsertStatement = createMusicInfoInsertStatement() + db.transaction((musicInfos: LX.DBService.MusicInfoOtherSource[]) => { + for (const info of musicInfos) musicInfoInsertStatement.run(info) + })(musicInfos) +} + +/** + * 批量删除歌曲信息 + * @param ids 列表 + */ +export const deleteMusicInfo = (ids: string[]) => { + const db = getDB() + const musicInfoDeleteStatement = createMusicInfoDeleteStatement() + db.transaction((ids: string[]) => { + for (const id of ids) musicInfoDeleteStatement.run(id) + })(ids) +} + +/** + * 清空歌曲信息 + */ +export const clearMusicInfo = () => { + const musicInfoClearStatement = createMusicInfoClearStatement() + musicInfoClearStatement.run() +} + +/** + * 统计歌曲信息数量 + */ +export const countMusicInfo = () => { + const countStatement = createCountStatement() + return countStatement.get().count +} diff --git a/src/main/worker/dbService/modules/music_other_source/index.ts b/src/main/worker/dbService/modules/music_other_source/index.ts new file mode 100644 index 00000000..19081246 --- /dev/null +++ b/src/main/worker/dbService/modules/music_other_source/index.ts @@ -0,0 +1,72 @@ +import { + queryMusicInfo, + inertMusicInfo, + deleteMusicInfo, + clearMusicInfo, + countMusicInfo, +} from './dbHelper' + + +const toDBMusicInfo = (id: string, musicInfos: LX.Music.MusicInfo[]): LX.DBService.MusicInfoOtherSource[] => { + return musicInfos.map((info, index) => { + return { + ...info, + meta: JSON.stringify(info.meta), + source_id: id, + order: index, + } + }) +} + +/** + * 获取歌曲信息 + * @param id 歌曲id + * @returns 歌词信息 + */ +export const getMusicInfoOtherSource = (id: string): LX.Music.MusicInfoOnline[] => { + const list = queryMusicInfo(id).map(info => { + return { + id: info.id, + name: info.name, + singer: info.singer, + source: info.source, + interval: info.interval, + meta: JSON.parse(info.meta), + } + }) + + return list +} + +/** + * 保存歌曲信息信息 + * @param id 歌曲id + * @param musicInfos 歌词信息 + */ +export const musicInfoOtherSourceAdd = (id: string, musicInfos: LX.Music.MusicInfoOnline[]) => { + inertMusicInfo(toDBMusicInfo(id, musicInfos)) +} + +/** + * 删除歌曲信息信息 + * @param ids 歌曲id + */ +export const musicInfoOtherSourceRemove = (ids: string[]) => { + deleteMusicInfo(ids) +} + +/** + * 清空歌曲信息信息 + */ +export const musicInfoOtherSourceClear = () => { + clearMusicInfo() +} + + +/** + * 统计歌曲信息信息数量 + */ +export const musicInfoOtherSourceCount = () => { + return countMusicInfo() +} + diff --git a/src/main/worker/dbService/modules/music_other_source/statements.ts b/src/main/worker/dbService/modules/music_other_source/statements.ts new file mode 100644 index 00000000..3eb15c99 --- /dev/null +++ b/src/main/worker/dbService/modules/music_other_source/statements.ts @@ -0,0 +1,60 @@ +import { getDB } from '../../db' + + +/** + * 创建歌曲信息查询语句 + * @returns 查询语句 + */ +export const createMusicInfoQueryStatement = () => { + const db = getDB() + return db.prepare<[string]>(` + SELECT "id", "name", "singer", "source", "meta" + FROM "main"."music_info_other_source" + WHERE source_id=? + ORDER BY "order" ASC + `) +} + +/** + * 创建歌曲信息插入语句 + * @returns 插入语句 + */ +export const createMusicInfoInsertStatement = () => { + const db = getDB() + return db.prepare<[LX.DBService.MusicInfoOtherSource]>(` + INSERT INTO "main"."music_info_other_source" ("id", "name", "singer", "source", "meta", "source_id", "order") + VALUES (@id, @name, @singer, @source, @meta, @source_id, @order) + `) +} + +/** + * 创建歌曲信息清空语句 + * @returns 清空语句 + */ +export const createMusicInfoClearStatement = () => { + const db = getDB() + return db.prepare(` + DELETE FROM "main"."music_info_other_source" + `) +} + +/** + * 创建歌曲信息删除语句 + * @returns 删除语句 + */ +export const createMusicInfoDeleteStatement = () => { + const db = getDB() + return db.prepare<[string]>(` + DELETE FROM "main"."music_info_other_source" + WHERE "source_id"=? + `) +} + +/** + * 创建数量统计语句 + * @returns 统计语句 + */ +export const createCountStatement = () => { + const db = getDB() + return db.prepare('SELECT COUNT(*) as count FROM "main"."music_info_other_source"') +} diff --git a/src/main/worker/dbService/modules/music_url/dbHelper.ts b/src/main/worker/dbService/modules/music_url/dbHelper.ts new file mode 100644 index 00000000..4502af1e --- /dev/null +++ b/src/main/worker/dbService/modules/music_url/dbHelper.ts @@ -0,0 +1,75 @@ +import { getDB } from '../../db' +import { + createQueryStatement, + createInsertStatement, + createDeleteStatement, + // createUpdateStatement, + createClearStatement, + createCountStatement, +} from './statements' + +/** + * 查询歌曲url + * @param id 歌曲id + * @returns url + */ +export const queryMusicUrl = (id: string): string | null => { + const queryStatement = createQueryStatement() + return queryStatement.get(id)?.url +} + +/** + * 批量插入歌曲url + * @param urlInfo 列表 + */ +export const inertMusicUrl = (urlInfo: LX.DBService.MusicUrlInfo[]) => { + const db = getDB() + const insertStatement = createInsertStatement() + const deleteStatement = createDeleteStatement() + db.transaction((urlInfo: LX.DBService.MusicUrlInfo[]) => { + for (const info of urlInfo) { + deleteStatement.run(info.id) + insertStatement.run(info) + } + })(urlInfo) +} + +/** + * 批量删除歌曲url + * @param ids 列表 + */ +export const deleteMusicUrl = (ids: string[]) => { + const db = getDB() + const deleteStatement = createDeleteStatement() + db.transaction((ids: string[]) => { + for (const id of ids) deleteStatement.run(id) + })(ids) +} + +/** + * 批量更新歌曲url + * @param urlInfo 列表 + */ +// export const updateMusicUrl = (urlInfo: LX.DBService.MusicUrlInfo[]) => { +// const db = getDB() +// const updateStatement = createUpdateStatement() +// db.transaction((urlInfo: LX.DBService.MusicUrlInfo[]) => { +// for (const info of urlInfo) updateStatement.run(info) +// })(urlInfo) +// } + +/** + * 清空歌曲url + */ +export const clearMusicUrl = () => { + const clearStatement = createClearStatement() + clearStatement.run() +} + +/** + * 统计歌曲信息数量 + */ +export const countMusicUrl = () => { + const countStatement = createCountStatement() + return countStatement.get().count +} diff --git a/src/main/worker/dbService/modules/music_url/index.ts b/src/main/worker/dbService/modules/music_url/index.ts new file mode 100644 index 00000000..7ffdf57e --- /dev/null +++ b/src/main/worker/dbService/modules/music_url/index.ts @@ -0,0 +1,49 @@ +import { + queryMusicUrl, + inertMusicUrl, + deleteMusicUrl, + clearMusicUrl, + countMusicUrl, +} from './dbHelper' + + +/** + * 获取歌曲url + * @param id 歌曲id + * @returns 歌曲url + */ +export const getMusicUrl = (id: string): string | null => { + const url = queryMusicUrl(id) + return url +} + +/** + * 保存歌曲url + * @param urlInfos url信息 + */ +export const musicUrlSave = (urlInfos: LX.Music.MusicUrlInfo[]) => { + inertMusicUrl(urlInfos) +} + +/** + * 删除歌曲url + * @param ids 歌曲id + */ +export const musicUrlRemove = (ids: string[]) => { + deleteMusicUrl(ids) +} + +/** + * 清空歌曲url + */ +export const musicUrlClear = () => { + clearMusicUrl() +} + +/** + * 统计歌曲url数量 + */ +export const musicUrlCount = () => { + return countMusicUrl() +} + diff --git a/src/main/worker/dbService/modules/music_url/statements.ts b/src/main/worker/dbService/modules/music_url/statements.ts new file mode 100644 index 00000000..c1825a66 --- /dev/null +++ b/src/main/worker/dbService/modules/music_url/statements.ts @@ -0,0 +1,69 @@ +import { getDB } from '../../db' + +/** + * 创建歌曲url查询语句 + * @returns 查询语句 + */ +export const createQueryStatement = () => { + const db = getDB() + return db.prepare<[string]>(` + SELECT "url" + FROM "main"."music_url" + WHERE "id"=? + `) +} + +/** + * 创建歌曲url插入语句 + * @returns 插入语句 + */ +export const createInsertStatement = () => { + const db = getDB() + return db.prepare<[LX.DBService.MusicUrlInfo]>(` + INSERT INTO "main"."music_url" ("id", "url") + VALUES (@id, @url)`) +} + +/** + * 创建歌曲url清空语句 + * @returns 清空语句 + */ +export const createClearStatement = () => { + const db = getDB() + return db.prepare(` + DELETE FROM "main"."music_url" + `) +} + +/** + * 创建歌曲url删除语句 + * @returns 删除语句 + */ +export const createDeleteStatement = () => { + const db = getDB() + return db.prepare<[string]>(` + DELETE FROM "main"."music_url" + WHERE "id"=? + `) +} + +/** + * 创建歌曲url更新语句 + * @returns 更新语句 + */ +export const createUpdateStatement = () => { + const db = getDB() + return db.prepare<[LX.DBService.MusicUrlInfo]>(` + UPDATE "main"."music_url" + SET "url"=@url + WHERE "id"=@id`) +} + +/** + * 创建数量统计语句 + * @returns 统计语句 + */ +export const createCountStatement = () => { + const db = getDB() + return db.prepare('SELECT COUNT(*) as count FROM "main"."music_url"') +} diff --git a/src/main/worker/index.ts b/src/main/worker/index.ts new file mode 100644 index 00000000..bd9aca90 --- /dev/null +++ b/src/main/worker/index.ts @@ -0,0 +1,9 @@ +import { createDBServiceWorker } from './utils' + + +export default () => { + return { + dbService: createDBServiceWorker(), + } +} + diff --git a/src/main/worker/utils/index.ts b/src/main/worker/utils/index.ts new file mode 100644 index 00000000..39657899 --- /dev/null +++ b/src/main/worker/utils/index.ts @@ -0,0 +1,15 @@ +import { Worker } from 'worker_threads' +import * as Comlink from 'comlink' +import nodeEndpoint from 'comlink/dist/esm/node-adapter' + +export declare type DBSeriveTypes = Comlink.Remote + +export const createDBServiceWorker = () => { + const worker: Worker = new Worker(new URL( + /* webpackChunkName: 'dbService.worker' */ + '../dbService', + import.meta.url, + )) + return Comlink.wrap(nodeEndpoint(worker)) +} + diff --git a/src/main/worker/utils/worker.ts b/src/main/worker/utils/worker.ts new file mode 100644 index 00000000..5e5cab0a --- /dev/null +++ b/src/main/worker/utils/worker.ts @@ -0,0 +1,9 @@ +import { parentPort } from 'worker_threads' +import * as Comlink from 'comlink' +import nodeEndpoint from 'comlink/dist/esm/node-adapter' + + +export const exposeWorker = (obj: any) => { + if (parentPort == null) return + Comlink.expose(obj, nodeEndpoint(parentPort)) +} diff --git a/src/renderer-lyric/App.vue b/src/renderer-lyric/App.vue index 61ba130b..1703b4b7 100644 --- a/src/renderer-lyric/App.vue +++ b/src/renderer-lyric/App.vue @@ -1,286 +1,51 @@ - + + + + + diff --git a/src/renderer/components/material/OnlineList/useList.js b/src/renderer/views/Download/useList.js similarity index 65% rename from src/renderer/components/material/OnlineList/useList.js rename to src/renderer/views/Download/useList.js index 28e723f8..feff1e11 100644 --- a/src/renderer/components/material/OnlineList/useList.js +++ b/src/renderer/views/Download/useList.js @@ -1,6 +1,7 @@ -import { computed, useRefGetter, watch, ref, onBeforeUnmount } from '@renderer/utils/vueTools' -import { windowSizeList, isFullscreen } from '@renderer/core/share' +import { computed, watch, ref, onBeforeUnmount } from '@common/utils/vueTools' +import { isFullscreen } from '@renderer/store' import { getFontSizeWithScreen } from '@renderer/utils' +import { appSetting } from '@renderer/store/setting' const useKeyEvent = ({ handleSelectAllData }) => { const keyEvent = { @@ -29,28 +30,27 @@ const useKeyEvent = ({ handleSelectAllData }) => { } onBeforeUnmount(() => { - window.eventHub.off('key_shift_down', handle_key_shift_down) - window.eventHub.off('key_shift_up', handle_key_shift_up) - window.eventHub.off('key_mod_down', handle_key_mod_down) - window.eventHub.off('key_mod_up', handle_key_mod_up) - window.eventHub.off('key_mod+a_down', handle_key_mod_a_down) + window.key_event.off('key_shift_down', handle_key_shift_down) + window.key_event.off('key_shift_up', handle_key_shift_up) + window.key_event.off('key_mod_down', handle_key_mod_down) + window.key_event.off('key_mod_up', handle_key_mod_up) + window.key_event.off('key_mod+a_down', handle_key_mod_a_down) }) - window.eventHub.on('key_shift_down', handle_key_shift_down) - window.eventHub.on('key_shift_up', handle_key_shift_up) - window.eventHub.on('key_mod_down', handle_key_mod_down) - window.eventHub.on('key_mod_up', handle_key_mod_up) - window.eventHub.on('key_mod+a_down', handle_key_mod_a_down) + window.key_event.on('key_shift_down', handle_key_shift_down) + window.key_event.on('key_shift_up', handle_key_shift_up) + window.key_event.on('key_mod_down', handle_key_mod_down) + window.key_event.on('key_mod_up', handle_key_mod_up) + window.key_event.on('key_mod+a_down', handle_key_mod_a_down) return keyEvent } - -export default ({ props }) => { +export default ({ list, listAll }) => { const selectedList = ref([]) - const setting = useRefGetter('setting') + let lastSelectIndex = -1 const listItemHeight = computed(() => { - return Math.ceil((isFullscreen.value ? getFontSizeWithScreen() : parseInt(windowSizeList.find(item => item.id == setting.value.windowSizeId).fontSize)) * 2.3) + return Math.ceil((isFullscreen.value ? getFontSizeWithScreen() : appSetting['common.fontSize']) * 2.3) }) const removeAllSelect = () => { @@ -58,7 +58,7 @@ export default ({ props }) => { } const handleSelectAllData = () => { removeAllSelect() - selectedList.value = [...props.list] + selectedList.value = [...list.value] } const keyEvent = useKeyEvent({ handleSelectAllData }) @@ -74,16 +74,16 @@ export default ({ props }) => { clickIndex = temp isNeedReverse = true } - selectedList.value = props.list.slice(lastSelectIndex, clickIndex + 1) + selectedList.value = list.value.slice(lastSelectIndex, clickIndex + 1) if (isNeedReverse) selectedList.value.reverse() } } else { - selectedList.value.push(props.list[clickIndex]) + selectedList.value.push(list.value[clickIndex]) lastSelectIndex = clickIndex } } else if (keyEvent.isModDown) { lastSelectIndex = clickIndex - let item = props.list[clickIndex] + let item = list.value[clickIndex] let index = selectedList.value.indexOf(item) if (index < 0) { selectedList.value.push(item) @@ -95,7 +95,7 @@ export default ({ props }) => { } } - watch(() => props.list, removeAllSelect) + watch(listAll, removeAllSelect) return { selectedList, diff --git a/src/renderer/views/Download/useListInfo.js b/src/renderer/views/Download/useListInfo.js new file mode 100644 index 00000000..f69f8850 --- /dev/null +++ b/src/renderer/views/Download/useListInfo.js @@ -0,0 +1,42 @@ +import { ref, computed } from '@common/utils/vueTools' +import { playMusicInfo, playInfo } from '@renderer/store/player/state' +import { downloadStatus } from '@renderer/store/download/state' +import { getDownloadList } from '@renderer/store/download/action' +import { LIST_IDS } from '@common/constants' + + +export default (activeTab) => { + const rightClickSelectedIndex = ref(-1) + const dom_listContent = ref(null) + + const listAll = ref([]) + getDownloadList().then(l => { + listAll.value = l + }) + + const list = computed(() => { + switch (activeTab.value) { + case 'runing': + return listAll.value.filter(i => i.status == downloadStatus.RUN || i.status == downloadStatus.WAITING) + case 'paused': + return listAll.value.filter(i => i.status == downloadStatus.PAUSE) + case 'error': + return listAll.value.filter(i => i.status == downloadStatus.ERROR) + case 'finished': + return listAll.value.filter(i => i.status == downloadStatus.COMPLETED) + default: + return listAll.value + } + }) + + const playTaskId = computed(() => playMusicInfo.listId == LIST_IDS.DOWNLOAD ? listAll.value[playInfo.playIndex]?.id : '') + + + return { + rightClickSelectedIndex, + dom_listContent, + listAll, + list, + playTaskId, + } +} diff --git a/src/renderer/views/Download/useMenu.js b/src/renderer/views/Download/useMenu.js new file mode 100644 index 00000000..0f97addb --- /dev/null +++ b/src/renderer/views/Download/useMenu.js @@ -0,0 +1,150 @@ +import { computed, ref, shallowReactive, reactive, nextTick } from '@common/utils/vueTools' +import musicSdk from '@renderer/utils/musicSdk' +import { useI18n } from '@renderer/plugins/i18n' +import { DOWNLOAD_STATUS } from '@common/constants' + +export default ({ + handleStartTask, + handlePauseTask, + handleRemoveTask, + handleOpenFile, + handlePlayMusic, + handlePlayMusicLater, + handleSearch, + handleOpenMusicDetail, +}) => { + const itemMenuControl = reactive({ + play: true, + start: true, + pause: true, + playLater: true, + file: true, + sourceDetail: true, + search: true, + remove: true, + }) + const t = useI18n() + const menuLocation = shallowReactive({ x: 0, y: 0 }) + const isShowItemMenu = ref(false) + + const menus = computed(() => { + return [ + { + name: t('list__play'), + action: 'play', + hide: !itemMenuControl.play, + }, + { + name: t('list__start'), + action: 'start', + hide: !itemMenuControl.start, + }, + { + name: t('list__pause'), + action: 'pause', + hide: !itemMenuControl.pause, + }, + { + name: t('list__play_later'), + action: 'playLater', + hide: !itemMenuControl.playLater, + }, + { + name: t('list__file'), + action: 'file', + hide: !itemMenuControl.file, + }, + { + name: t('list__source_detail'), + action: 'sourceDetail', + disabled: !itemMenuControl.sourceDetail, + }, + { + name: t('list__search'), + action: 'search', + hide: !itemMenuControl.search, + }, + { + name: t('list__remove'), + action: 'remove', + hide: !itemMenuControl.remove, + }, + ] + }) + + const showMenu = (event, taskInfo) => { + itemMenuControl.sourceDetail = !!musicSdk[taskInfo.metadata.musicInfo.source]?.getMusicDetailPageUrl + + if (taskInfo.isComplate) { + itemMenuControl.play = + itemMenuControl.playLater = + itemMenuControl.file = true + itemMenuControl.start = + itemMenuControl.pause = false + } else if (taskInfo.status === DOWNLOAD_STATUS.ERROR || taskInfo.status === DOWNLOAD_STATUS.PAUSE) { + itemMenuControl.play = + itemMenuControl.playLater = + itemMenuControl.pause = + itemMenuControl.file = false + itemMenuControl.start = true + } else { + itemMenuControl.play = + itemMenuControl.playLater = + itemMenuControl.start = + itemMenuControl.file = false + itemMenuControl.pause = true + } + + menuLocation.x = event.pageX + menuLocation.y = event.pageY + + if (isShowItemMenu.value) return + + nextTick(() => { + isShowItemMenu.value = true + }) + } + + const hideMenu = () => { + isShowItemMenu.value = false + } + + const menuClick = (action, index) => { + // console.log(action) + hideMenu() + if (!action) return + switch (action.action) { + case 'start': + handleStartTask(index) + break + case 'pause': + handlePauseTask(index) + break + case 'file': + handleOpenFile(index) + break + case 'play': + handlePlayMusic(index) + break + case 'playLater': + handlePlayMusicLater(index) + break + case 'search': + handleSearch(index) + break + case 'remove': + handleRemoveTask(index) + break + case 'sourceDetail': + handleOpenMusicDetail(index) + } + } + + return { + menus, + menuLocation, + isShowItemMenu, + showMenu, + menuClick, + } +} diff --git a/src/renderer/views/Download/usePlay.js b/src/renderer/views/Download/usePlay.js new file mode 100644 index 00000000..dd7da417 --- /dev/null +++ b/src/renderer/views/Download/usePlay.js @@ -0,0 +1,23 @@ +import { addTempPlayList } from '@renderer/store/player/action' +import { playList } from '@renderer/core/player' +import { LIST_IDS } from '@common/constants' + +export default ({ selectedList, list, listAll, removeAllSelect }) => { + const handlePlayMusic = (index) => { + playList(LIST_IDS.DOWNLOAD, listAll.value.indexOf(list.value[index])) + } + + const handlePlayMusicLater = (index, single) => { + if (selectedList.value.length && !single) { + addTempPlayList(selectedList.value.map(s => ({ listId: LIST_IDS.DOWNLOAD, musicInfo: s }))) + removeAllSelect() + } else { + addTempPlayList([{ listId: LIST_IDS.DOWNLOAD, musicInfo: list.value[index] }]) + } + } + + return { + handlePlayMusic, + handlePlayMusicLater, + } +} diff --git a/src/renderer/views/Download/useTab.js b/src/renderer/views/Download/useTab.js new file mode 100644 index 00000000..644fc9a3 --- /dev/null +++ b/src/renderer/views/Download/useTab.js @@ -0,0 +1,39 @@ +import { computed, ref } from '@common/utils/vueTools' + +export default () => { + const tabs = computed(() => { + return [ + { + label: window.i18n.t('download__all'), + id: 'all', + }, + { + label: window.i18n.t('download__runing'), + id: 'runing', + }, + { + label: window.i18n.t('download__paused'), + id: 'paused', + }, + { + label: window.i18n.t('download__error'), + id: 'error', + }, + { + label: window.i18n.t('download__finished'), + id: 'finished', + }, + ] + }) + const activeTab = ref('all') + + // const setActiveTab = (tab) => { + // activeTab.value = tab + // } + + return { + tabs, + activeTab, + // setActiveTab, + } +} diff --git a/src/renderer/views/Download/useTaskActions.js b/src/renderer/views/Download/useTaskActions.js new file mode 100644 index 00000000..f0803358 --- /dev/null +++ b/src/renderer/views/Download/useTaskActions.js @@ -0,0 +1,82 @@ +import { useRouter } from '@common/utils/vueRouter' +import musicSdk from '@renderer/utils/musicSdk' +import { openUrl, openDirInExplorer } from '@common/utils/electron' +import { checkPath } from '@common/utils/nodejs' +// import { dialog } from '@renderer/plugins/Dialog' +// import { useI18n } from '@renderer/plugins/i18n' +// import { appSetting } from '@renderer/store/setting' +import { toOldMusicInfo } from '@renderer/utils/index' +import { startDownloadTasks, pauseDownloadTasks, removeDownloadTasks } from '@renderer/store/download/action' + +export default ({ list, selectedList, removeAllSelect }) => { + const router = useRouter() + // const t = useI18n() + + const handleSearch = index => { + const info = list.value[index].metadata.musicInfo + router.push({ + path: '/search', + query: { + text: `${info.name} ${info.singer}`, + }, + }) + } + + const handleOpenMusicDetail = index => { + const task = list.value[index] + const mInfo = toOldMusicInfo(task.metadata.musicInfo) + const url = musicSdk[mInfo.source].getMusicDetailPageUrl(mInfo) + if (!url) return + openUrl(url) + } + + const handleStartTask = async(index, single) => { + if (selectedList.value.length && !single) { + startDownloadTasks([...selectedList.value]) + removeAllSelect() + } else { + startDownloadTasks([list.value[index]]) + } + } + + const handlePauseTask = async(index, single) => { + if (selectedList.value.length && !single) { + pauseDownloadTasks([...selectedList.value]) + removeAllSelect() + } else { + pauseDownloadTasks([list.value[index]]) + } + } + + const handleRemoveTask = async(index, single) => { + if (selectedList.value.length && !single) { + // const confirm = await (selectedList.value.length > 1 + // ? dialog.confirm({ + // message: t('lists__remove music_tip', { len: selectedList.value.length }), + // confirmButtonText: t('lists__remove_tip_button'), + // }) + // : Promise.resolve(true) + // ) + // if (!confirm) return + removeDownloadTasks(selectedList.value.map(m => m.id)) + removeAllSelect() + } else { + removeDownloadTasks([list.value[index].id]) + } + } + + const handleOpenFile = async(index) => { + const task = list.value[index] + if (!checkPath(task.metadata.filePath)) return + openDirInExplorer(task.metadata.filePath) + } + + return { + handleSearch, + handleOpenMusicDetail, + handleStartTask, + handlePauseTask, + handleRemoveTask, + handleOpenFile, + } +} diff --git a/src/renderer/views/Leaderboard.vue b/src/renderer/views/Leaderboard.vue deleted file mode 100644 index 7c7f2e5c..00000000 --- a/src/renderer/views/Leaderboard.vue +++ /dev/null @@ -1,446 +0,0 @@ - - - - - diff --git a/src/renderer/views/Leaderboard/BoardList/index.vue b/src/renderer/views/Leaderboard/BoardList/index.vue new file mode 100644 index 00000000..c797cf80 --- /dev/null +++ b/src/renderer/views/Leaderboard/BoardList/index.vue @@ -0,0 +1,136 @@ + + + + + + diff --git a/src/renderer/views/Leaderboard/BoardList/useMenu.js b/src/renderer/views/Leaderboard/BoardList/useMenu.js new file mode 100644 index 00000000..d4b24bff --- /dev/null +++ b/src/renderer/views/Leaderboard/BoardList/useMenu.js @@ -0,0 +1,72 @@ +import { computed, ref, reactive, nextTick } from '@common/utils/vueTools' +import { useI18n } from '@renderer/plugins/i18n' +import { addSongListDetail, playSongListDetail } from '../action' + +export default ({ + emit, + list, +}) => { + // const menuControl = reactive({ + // play: true, + // collect: true, + // }) + const t = useI18n() + const menuLocation = reactive({ x: 0, y: 0 }) + const isShowMenu = ref(false) + + const menus = computed(() => { + return [ + { + name: t('list__play'), + action: 'play', + disabled: false, + }, + { + name: t('list__collect'), + action: 'collect', + disabled: false, + }, + ] + }) + + + const showMenu = (event, index) => { + menuLocation.x = event.pageX + menuLocation.y = event.pageY + + if (isShowMenu.value) return + emit('show-menu') + nextTick(() => { + isShowMenu.value = true + }) + } + + const hideMenu = () => { + isShowMenu.value = false + } + + + const menuClick = (action, index, source) => { + // console.log(action) + hideMenu() + if (!action) return + // const id = `board__${this.source}__${board.id}` + const board = list[index] + switch (action.action) { + case 'play': + playSongListDetail(board.id) + break + case 'collect': + addSongListDetail(board.id, board.name, source) + break + } + } + + return { + menus, + menuLocation, + isShowMenu, + showMenu, + menuClick, + } +} diff --git a/src/renderer/views/Leaderboard/MusicList/index.vue b/src/renderer/views/Leaderboard/MusicList/index.vue new file mode 100644 index 00000000..626864af --- /dev/null +++ b/src/renderer/views/Leaderboard/MusicList/index.vue @@ -0,0 +1,78 @@ + + + + + + diff --git a/src/renderer/views/Leaderboard/MusicList/useList.ts b/src/renderer/views/Leaderboard/MusicList/useList.ts new file mode 100644 index 00000000..ca313369 --- /dev/null +++ b/src/renderer/views/Leaderboard/MusicList/useList.ts @@ -0,0 +1,29 @@ +import { ref } from '@common/utils/vueTools' +// import { useI18n } from '@renderer/plugins/i18n' +// import { } from '@renderer/store/search/state' +import { getAndSetListDetail } from '@renderer/store/leaderboard/action' +import { listDetailInfo } from '@renderer/store/leaderboard/state' +import { playSongListDetail } from '../action' + +export default () => { + const listRef = ref(null) + + const handlePlayList = (index: number) => { + void playSongListDetail(listDetailInfo.id, listDetailInfo.list, index) + } + + const getList = (id: string, page: number) => { + void getAndSetListDetail(id, page).then(() => { + setTimeout(() => { + if (listRef.value) listRef.value.scrollToTop() + }) + }) + } + + return { + listRef, + listDetailInfo, + getList, + handlePlayList, + } +} diff --git a/src/renderer/views/Leaderboard/action.ts b/src/renderer/views/Leaderboard/action.ts new file mode 100644 index 00000000..5c8d8d09 --- /dev/null +++ b/src/renderer/views/Leaderboard/action.ts @@ -0,0 +1,57 @@ +import { tempListMeta, userLists } from '@renderer/store/list/state' +import { dialog } from '@renderer/plugins/Dialog' +import syncSourceList from '@renderer/store/list/syncSourceList' +import { getListDetail, getListDetailAll } from '@renderer/store/leaderboard/action' +import { createUserList, setTempList } from '@renderer/store/list/action' +import { playList } from '@renderer/core/player/action' +import { LIST_IDS } from '@common/constants' + +const getListId = (id: string) => `board__${id}` + +export const addSongListDetail = async(id: string, name: string, source: LX.OnlineSource) => { + // console.log(this.listDetail.info) + // if (!this.listDetail.info.name) return + const listId = getListId(id) + const targetList = userLists.find(l => l.id == listId) + if (targetList) { + const confirm = await dialog.confirm({ + message: window.i18n.t('duplicate_list_tip', { name: targetList.name }), + cancelButtonText: window.i18n.t('lists__import_part_button_cancel'), + confirmButtonText: window.i18n.t('confirm_button_text'), + }) + if (!confirm) return + void syncSourceList(targetList) + return + } + + const list = await getListDetailAll(id) + await createUserList({ + name, + id: listId, + list, + source, + sourceListId: listId, + }) +} + +export const playSongListDetail = async(id: string, list?: LX.Music.MusicInfoOnline[], index: number = 0) => { + let isPlayingList = false + console.log(list) + const listId = getListId(id) + if (!list?.length) list = (await getListDetail(id, 1)).list + if (list?.length) { + await setTempList(listId, [...list]) + playList(LIST_IDS.TEMP, index) + isPlayingList = true + } + const fullList = await getListDetailAll(id) + if (!fullList.length) return + if (isPlayingList) { + if (tempListMeta.id == listId) { + await setTempList(listId, [...fullList]) + } + } else { + await setTempList(listId, [...fullList]) + playList(LIST_IDS.TEMP, index) + } +} diff --git a/src/renderer/views/Leaderboard/index.vue b/src/renderer/views/Leaderboard/index.vue new file mode 100644 index 00000000..a4ac54f8 --- /dev/null +++ b/src/renderer/views/Leaderboard/index.vue @@ -0,0 +1,189 @@ + + + + + diff --git a/src/renderer/views/list/components/MusicSortModal.vue b/src/renderer/views/List/MusicList/components/MusicSortModal.vue similarity index 66% rename from src/renderer/views/list/components/MusicSortModal.vue rename to src/renderer/views/List/MusicList/components/MusicSortModal.vue index 0f90f68e..13a6a04a 100644 --- a/src/renderer/views/list/components/MusicSortModal.vue +++ b/src/renderer/views/List/MusicList/components/MusicSortModal.vue @@ -1,18 +1,20 @@ + + + diff --git a/src/renderer/views/list/components/MusicList/useList.js b/src/renderer/views/List/MusicList/useList.js similarity index 73% rename from src/renderer/views/list/components/MusicList/useList.js rename to src/renderer/views/List/MusicList/useList.js index 73cd7d60..e599488e 100644 --- a/src/renderer/views/list/components/MusicList/useList.js +++ b/src/renderer/views/List/MusicList/useList.js @@ -1,6 +1,7 @@ -import { computed, watch, ref, onBeforeUnmount } from '@renderer/utils/vueTools' -import { windowSizeList, isFullscreen } from '@renderer/core/share' +import { computed, watch, ref, onBeforeUnmount } from '@common/utils/vueTools' +import { isFullscreen } from '@renderer/store' import { getFontSizeWithScreen } from '@renderer/utils' +import { appSetting } from '@renderer/store/setting' const useKeyEvent = ({ handleSelectAllData }) => { const keyEvent = { @@ -29,27 +30,27 @@ const useKeyEvent = ({ handleSelectAllData }) => { } onBeforeUnmount(() => { - window.eventHub.off('key_shift_down', handle_key_shift_down) - window.eventHub.off('key_shift_up', handle_key_shift_up) - window.eventHub.off('key_mod_down', handle_key_mod_down) - window.eventHub.off('key_mod_up', handle_key_mod_up) - window.eventHub.off('key_mod+a_down', handle_key_mod_a_down) + window.key_event.off('key_shift_down', handle_key_shift_down) + window.key_event.off('key_shift_up', handle_key_shift_up) + window.key_event.off('key_mod_down', handle_key_mod_down) + window.key_event.off('key_mod_up', handle_key_mod_up) + window.key_event.off('key_mod+a_down', handle_key_mod_a_down) }) - window.eventHub.on('key_shift_down', handle_key_shift_down) - window.eventHub.on('key_shift_up', handle_key_shift_up) - window.eventHub.on('key_mod_down', handle_key_mod_down) - window.eventHub.on('key_mod_up', handle_key_mod_up) - window.eventHub.on('key_mod+a_down', handle_key_mod_a_down) + window.key_event.on('key_shift_down', handle_key_shift_down) + window.key_event.on('key_shift_up', handle_key_shift_up) + window.key_event.on('key_mod_down', handle_key_mod_down) + window.key_event.on('key_mod_up', handle_key_mod_up) + window.key_event.on('key_mod+a_down', handle_key_mod_a_down) return keyEvent } -export default ({ list, setting }) => { +export default ({ list }) => { const selectedList = ref([]) let lastSelectIndex = -1 const listItemHeight = computed(() => { - return Math.ceil((isFullscreen.value ? getFontSizeWithScreen() : parseInt(windowSizeList.find(item => item.id == setting.value.windowSizeId).fontSize)) * 2.3) + return Math.ceil((isFullscreen.value ? getFontSizeWithScreen() : appSetting['common.fontSize']) * 2.3) }) const removeAllSelect = () => { diff --git a/src/renderer/views/list/components/MusicList/useListInfo.js b/src/renderer/views/List/MusicList/useListInfo.js similarity index 54% rename from src/renderer/views/list/components/MusicList/useListInfo.js rename to src/renderer/views/List/MusicList/useListInfo.js index b5f25b2d..1f2800ce 100644 --- a/src/renderer/views/list/components/MusicList/useListInfo.js +++ b/src/renderer/views/List/MusicList/useListInfo.js @@ -1,11 +1,10 @@ -import { ref, watch, useRefGetter, computed } from '@renderer/utils/vueTools' -import { playMusicInfo, playInfo } from '@renderer/core/share/player' -import { getList } from '@renderer/core/share/utils' +import { ref, watch, computed } from '@common/utils/vueTools' +import { playMusicInfo, playInfo } from '@renderer/store/player/state' +import { getListMusics } from '@renderer/store/list/action' +import { appSetting } from '@renderer/store/setting' -export default ({ props }) => { - const setting = useRefGetter('setting') - +export default ({ props, onLoadedList }) => { const rightClickSelectedIndex = ref(-1) const selectedIndex = ref(-1) const dom_listContent = ref(null) @@ -14,9 +13,15 @@ export default ({ props }) => { const excludeListIds = computed(() => ([props.listId])) - const list = ref(getList(props.listId)) + const list = ref([]) watch(() => props.listId, id => { - list.value = getList(id) + getListMusics(id).then(l => { + list.value = l + if (id != props.listId) return + onLoadedList() + }) + }, { + immediate: true, }) const playerInfo = computed(() => ({ @@ -28,7 +33,7 @@ export default ({ props }) => { selectedIndex.value = index } - const isShowSource = computed(() => setting.value.list.isShowSource) + const isShowSource = computed(() => appSetting['list.isShowSource']) return { rightClickSelectedIndex, @@ -38,7 +43,6 @@ export default ({ props }) => { list, playerInfo, setSelectedIndex, - setting, isShowSource, excludeListIds, } diff --git a/src/renderer/views/list/components/MusicList/useListScroll.js b/src/renderer/views/List/MusicList/useListScroll.js similarity index 56% rename from src/renderer/views/list/components/MusicList/useListScroll.js rename to src/renderer/views/List/MusicList/useListScroll.js index 0d9cd3d6..4f4e91c5 100644 --- a/src/renderer/views/list/components/MusicList/useListScroll.js +++ b/src/renderer/views/List/MusicList/useListScroll.js @@ -1,7 +1,9 @@ -import { useRoute, useRouter, onMounted, onBeforeUnmount } from '@renderer/utils/vueTools' +import { onMounted, onBeforeUnmount } from '@common/utils/vueTools' +import { useRoute, useRouter } from '@common/utils/vueRouter' import { setListPosition, getListPosition } from '@renderer/utils/data' +import { appSetting } from '@renderer/store/setting' -export default ({ props, listRef, list, setting }) => { +export default ({ props, listRef, list, handleRestoreScroll }) => { const route = useRoute() const router = useRouter() @@ -10,16 +12,16 @@ export default ({ props, listRef, list, setting }) => { } const handleScrollList = (index, isAnimation, callback = () => {}) => { - listRef.value.scrollToIndex(index, -150, isAnimation).then(callback) + listRef.value.scrollToIndex(index, -150, isAnimation, callback) } - const restoreScroll = (index, isAnimation) => { + const restoreScroll = async(index, isAnimation) => { // console.log(index, isAnimation) if (!list.value.length) return if (index == null) { - let location = getListPosition(props.listId) || 0 - if (setting.value.list.isSaveScrollLocation && location != null) { - listRef.value.scrollTo(location) + let location = await getListPosition(props.listId) || 0 + if (appSetting['list.isSaveScrollLocation'] && location != null) { + listRef.value?.scrollTo(location) } return } @@ -28,10 +30,10 @@ export default ({ props, listRef, list, setting }) => { } onMounted(() => { - restoreScroll(route.query.scrollIndex, false) + handleRestoreScroll(route.query.scrollIndex, false) if (route.query.scrollIndex != null) { router.replace({ - path: 'list', + path: '/list', query: { id: props.listId, updated: true, diff --git a/src/renderer/views/list/components/MusicList/useMenu.js b/src/renderer/views/List/MusicList/useMenu.js similarity index 79% rename from src/renderer/views/list/components/MusicList/useMenu.js rename to src/renderer/views/List/MusicList/useMenu.js index de2270c1..7562211e 100644 --- a/src/renderer/views/list/components/MusicList/useMenu.js +++ b/src/renderer/views/List/MusicList/useMenu.js @@ -1,8 +1,8 @@ -import { computed, ref, reactive, useI18n, nextTick } from '@renderer/utils/vueTools' -import musicSdk from '@renderer/utils/music' +import { computed, ref, shallowReactive, reactive, nextTick } from '@common/utils/vueTools' +import musicSdk from '@renderer/utils/musicSdk' +import { useI18n } from '@renderer/plugins/i18n' export default ({ - listRef, assertApiSupport, emit, @@ -29,8 +29,8 @@ export default ({ remove: true, sourceDetail: true, }) - const { t } = useI18n() - const menuLocation = ref({ x: 0, y: 0 }) + const t = useI18n() + const menuLocation = shallowReactive({ x: 0, y: 0 }) const isShowItemMenu = ref(false) const menus = computed(() => { @@ -89,22 +89,16 @@ export default ({ }) const showMenu = (event, musicInfo) => { - itemMenuControl.sourceDetail = !!musicSdk[musicInfo.source].getMusicDetailPageUrl + itemMenuControl.sourceDetail = !!musicSdk[musicInfo.source]?.getMusicDetailPageUrl // itemMenuControl.play = // itemMenuControl.playLater = - itemMenuControl.download = assertApiSupport(musicInfo.source) + itemMenuControl.download = assertApiSupport(musicInfo.source) && musicInfo.source != 'local' + + menuLocation.x = event.pageX + menuLocation.y = event.pageY + + if (isShowItemMenu.value) return - let dom_container = event.target.closest('#my-list') - const getOffsetValue = (target, x = 0, y = 0) => { - if (target === dom_container) return { x, y } - if (!target) return { x: 0, y: 0 } - x += target.offsetLeft - y += target.offsetTop - return getOffsetValue(target.offsetParent, x, y) - } - let { x, y } = getOffsetValue(event.target) - menuLocation.value.x = x + event.offsetX - menuLocation.value.y = y + event.offsetY - listRef.value.getScrollTop() emit('show-menu') nextTick(() => { isShowItemMenu.value = true diff --git a/src/renderer/views/list/components/MusicList/useMusicActions.js b/src/renderer/views/List/MusicList/useMusicActions.js similarity index 56% rename from src/renderer/views/list/components/MusicList/useMusicActions.js rename to src/renderer/views/List/MusicList/useMusicActions.js index 5b517456..fc2bc3e4 100644 --- a/src/renderer/views/list/components/MusicList/useMusicActions.js +++ b/src/renderer/views/List/MusicList/useMusicActions.js @@ -1,20 +1,21 @@ -import { useCommit, useRouter, useI18n } from '@renderer/utils/vueTools' -import musicSdk from '@renderer/utils/music' -import { openUrl, clipboardWriteText } from '@renderer/utils' +import { useRouter } from '@common/utils/vueRouter' +import musicSdk from '@renderer/utils/musicSdk' +import { openUrl, clipboardWriteText } from '@common/utils/electron' import { dialog } from '@renderer/plugins/Dialog' +import { useI18n } from '@renderer/plugins/i18n' +import { removeListMusics } from '@renderer/store/list/action' +import { appSetting } from '@renderer/store/setting' +import { toOldMusicInfo } from '@renderer/utils/index' -export default ({ props, list, setting, selectedList, removeAllSelect }) => { +export default ({ props, list, selectedList, removeAllSelect }) => { const router = useRouter() - const { t } = useI18n() - - const listRemove = useCommit('list', 'listRemove') - const listRemoveMultiple = useCommit('list', 'listRemoveMultiple') + const t = useI18n() const handleSearch = index => { const info = list.value[index] router.push({ - path: 'search', + path: '/search', query: { text: `${info.name} ${info.singer}`, }, @@ -23,14 +24,14 @@ export default ({ props, list, setting, selectedList, removeAllSelect }) => { const handleOpenMusicDetail = index => { const minfo = list.value[index] - const url = musicSdk[minfo.source].getMusicDetailPageUrl(minfo) + const url = musicSdk[minfo.source].getMusicDetailPageUrl(toOldMusicInfo(minfo)) if (!url) return openUrl(url) } const handleCopyName = index => { const minfo = list.value[index] - clipboardWriteText(setting.value.download.fileName.replace('歌名', minfo.name).replace('歌手', minfo.singer)) + clipboardWriteText(appSetting['download.fileName'].replace('歌名', minfo.name).replace('歌手', minfo.singer)) } const handleRemoveMusic = async(index, single) => { @@ -43,10 +44,10 @@ export default ({ props, list, setting, selectedList, removeAllSelect }) => { : Promise.resolve(true) ) if (!confirm) return - listRemoveMultiple({ listId: props.listId, ids: selectedList.value.map(m => m.songmid) }) + removeListMusics({ listId: props.listId, ids: selectedList.value.map(m => m.id) }) removeAllSelect() } else { - listRemove({ listId: props.listId, id: list.value[index].songmid }) + removeListMusics({ listId: props.listId, ids: [list.value[index].id] }) } } diff --git a/src/renderer/views/list/components/MusicList/useMusicAdd.js b/src/renderer/views/List/MusicList/useMusicAdd.js similarity index 95% rename from src/renderer/views/list/components/MusicList/useMusicAdd.js rename to src/renderer/views/List/MusicList/useMusicAdd.js index 571a98cd..eb6f2c4a 100644 --- a/src/renderer/views/list/components/MusicList/useMusicAdd.js +++ b/src/renderer/views/List/MusicList/useMusicAdd.js @@ -1,4 +1,4 @@ -import { ref, nextTick } from '@renderer/utils/vueTools' +import { ref, nextTick } from '@common/utils/vueTools' export default ({ selectedList, list }) => { const isShowListAdd = ref(false) diff --git a/src/renderer/views/list/components/MusicList/useMusicDownload.js b/src/renderer/views/List/MusicList/useMusicDownload.js similarity index 90% rename from src/renderer/views/list/components/MusicList/useMusicDownload.js rename to src/renderer/views/List/MusicList/useMusicDownload.js index 621a2292..125d2b85 100644 --- a/src/renderer/views/list/components/MusicList/useMusicDownload.js +++ b/src/renderer/views/List/MusicList/useMusicDownload.js @@ -1,4 +1,4 @@ -import { ref, nextTick } from '@renderer/utils/vueTools' +import { ref, nextTick } from '@common/utils/vueTools' export default ({ selectedList, list }) => { const isShowDownload = ref(false) diff --git a/src/renderer/views/list/components/MusicList/usePlay.js b/src/renderer/views/List/MusicList/usePlay.js similarity index 67% rename from src/renderer/views/list/components/MusicList/usePlay.js rename to src/renderer/views/List/MusicList/usePlay.js index 80e636cc..a2340004 100644 --- a/src/renderer/views/list/components/MusicList/usePlay.js +++ b/src/renderer/views/List/MusicList/usePlay.js @@ -1,25 +1,20 @@ -import { useCommit } from '@renderer/utils/vueTools' +import { addTempPlayList } from '@renderer/store/player/action' +import { playList } from '@renderer/core/player' export default ({ props, selectedList, list, removeAllSelect }) => { let clickTime = 0 let clickIndex = -1 - const setList = useCommit('player', 'setList') - const setTempPlayList = useCommit('player', 'setTempPlayList') - const handlePlayMusic = (index) => { - setList({ - listId: props.listId, - index, - }) + playList(props.listId, index) } const handlePlayMusicLater = (index, single) => { if (selectedList.value.length && !single) { - setTempPlayList(selectedList.value.map(s => ({ listId: props.listId, musicInfo: s }))) + addTempPlayList(selectedList.value.map(s => ({ listId: props.listId, musicInfo: s }))) removeAllSelect() } else { - setTempPlayList([{ listId: props.listId, musicInfo: list.value[index] }]) + addTempPlayList([{ listId: props.listId, musicInfo: list.value[index] }]) } } diff --git a/src/renderer/views/list/components/MusicList/useSearch.js b/src/renderer/views/List/MusicList/useSearch.js similarity index 73% rename from src/renderer/views/list/components/MusicList/useSearch.js rename to src/renderer/views/List/MusicList/useSearch.js index 6e9c7230..95d84dc7 100644 --- a/src/renderer/views/list/components/MusicList/useSearch.js +++ b/src/renderer/views/List/MusicList/useSearch.js @@ -1,4 +1,4 @@ -import { ref, onBeforeUnmount } from '@renderer/utils/vueTools' +import { ref, onBeforeUnmount } from '@common/utils/vueTools' export default ({ setSelectedIndex, handlePlayMusic, listRef }) => { const isShowSearchBar = ref(false) @@ -13,7 +13,7 @@ export default ({ setSelectedIndex, handlePlayMusic, listRef }) => { switch (action) { case 'listClick': if (index < 0) return - listRef.value.scrollToIndex(index, -150, true).then(() => { + listRef.value.scrollToIndex(index, -150, true, () => { setSelectedIndex(index) setTimeout(() => { setSelectedIndex(-1) @@ -24,10 +24,10 @@ export default ({ setSelectedIndex, handlePlayMusic, listRef }) => { } } - window.eventHub.on('key_mod+f_down', handleShowSearchBar) + window.key_event.on('key_mod+f_down', handleShowSearchBar) onBeforeUnmount(() => { - window.eventHub.off('key_mod+f_down', handleShowSearchBar) + window.key_event.off('key_mod+f_down', handleShowSearchBar) }) return { diff --git a/src/renderer/views/list/components/MusicList/useSort.js b/src/renderer/views/List/MusicList/useSort.js similarity index 71% rename from src/renderer/views/list/components/MusicList/useSort.js rename to src/renderer/views/List/MusicList/useSort.js index 211b8d8b..5d3186e2 100644 --- a/src/renderer/views/list/components/MusicList/useSort.js +++ b/src/renderer/views/List/MusicList/useSort.js @@ -1,12 +1,11 @@ -import { ref, nextTick, useCommit } from '@renderer/utils/vueTools' +import { updateListMusicsPosition } from '@renderer/store/list/action' +import { ref, nextTick } from '@common/utils/vueTools' export default ({ props, list, selectedList, removeAllSelect }) => { const isShowMusicSortModal = ref(false) const selectedNum = ref(0) const musicInfo = ref(null) - const setMusicPosition = useCommit('list', 'setMusicPosition') - const handleShowSortModal = (index, single) => { if (selectedList.value.length && !single) { selectedNum.value = selectedList.value.length @@ -21,10 +20,10 @@ export default ({ props, list, selectedList, removeAllSelect }) => { const sortMusic = num => { num = Math.min(num, list.value.length) - setMusicPosition({ - id: props.listId, - position: num, - list: selectedNum.value ? [...selectedList.value] : [musicInfo.value], + updateListMusicsPosition({ + listId: props.listId, + position: num - 1, + ids: (selectedNum.value ? [...selectedList.value] : [musicInfo.value]).map(m => m.id), }) removeAllSelect() isShowMusicSortModal.value = false diff --git a/src/renderer/views/List/MyList/actions.ts b/src/renderer/views/List/MyList/actions.ts new file mode 100644 index 00000000..6d0910df --- /dev/null +++ b/src/renderer/views/List/MyList/actions.ts @@ -0,0 +1,28 @@ +import { addListMusics, setFetchingListStatus } from '@renderer/store/list/action' +import { showSelectDialog } from '@renderer/utils/ipc' + + +const handleAddMusics = async(listId: string, filePaths: string[], index: number = -1) => { + // console.log(index + 1, index + 201) + const paths = filePaths.slice(index + 1, index + 201) + const musicInfos = await window.lx.worker.main.createLocalMusicInfos(paths) + if (musicInfos.length) await addListMusics(listId, musicInfos) + index += 200 + if (filePaths.length - 1 > index) await handleAddMusics(listId, filePaths, index) +} +export const addLocalFile = async(listInfo: LX.List.MyListInfo) => { + const { canceled, filePaths } = await showSelectDialog({ + title: window.i18n.t('lists__add_local_file_desc'), + properties: ['openFile', 'multiSelections'], + filters: [ + { name: 'Media File', extensions: ['mp3', 'flac', 'ogg', 'wav'] }, + // { name: 'All Files', extensions: ['*'] }, + ], + }) + if (canceled || !filePaths.length) return + + console.log(filePaths) + setFetchingListStatus(listInfo.id, true) + await handleAddMusics(listInfo.id, filePaths) + setFetchingListStatus(listInfo.id, false) +} diff --git a/src/renderer/views/List/MyList/components/DuplicateMusicModal.vue b/src/renderer/views/List/MyList/components/DuplicateMusicModal.vue new file mode 100644 index 00000000..fdfb23ff --- /dev/null +++ b/src/renderer/views/List/MyList/components/DuplicateMusicModal.vue @@ -0,0 +1,232 @@ + + + + + diff --git a/src/renderer/views/List/MyList/components/ListSortModal.vue b/src/renderer/views/List/MyList/components/ListSortModal.vue new file mode 100644 index 00000000..1d7b7bfb --- /dev/null +++ b/src/renderer/views/List/MyList/components/ListSortModal.vue @@ -0,0 +1,183 @@ + + + + + + diff --git a/src/renderer/views/List/MyList/components/ListUpdateModal.vue b/src/renderer/views/List/MyList/components/ListUpdateModal.vue new file mode 100644 index 00000000..63640490 --- /dev/null +++ b/src/renderer/views/List/MyList/components/ListUpdateModal.vue @@ -0,0 +1,231 @@ + + + + + diff --git a/src/renderer/components/core/PlayDetail/components/LyricMenu.vue b/src/renderer/views/List/MyList/components/Menu.vue similarity index 59% rename from src/renderer/components/core/PlayDetail/components/LyricMenu.vue rename to src/renderer/views/List/MyList/components/Menu.vue index 0828c321..c63f2758 100644 --- a/src/renderer/components/core/PlayDetail/components/LyricMenu.vue +++ b/src/renderer/views/List/MyList/components/Menu.vue @@ -1,51 +1,22 @@ - + + diff --git a/src/renderer/views/setting/components/SettingDownload.vue b/src/renderer/views/setting/components/SettingDownload.vue index 1e0d8ed1..a33d942f 100644 --- a/src/renderer/views/setting/components/SettingDownload.vue +++ b/src/renderer/views/setting/components/SettingDownload.vue @@ -1,13 +1,13 @@ + + diff --git a/src/renderer/views/setting/components/ThemeEditModal/useAppBgColor.ts b/src/renderer/views/setting/components/ThemeEditModal/useAppBgColor.ts new file mode 100644 index 00000000..ad583065 --- /dev/null +++ b/src/renderer/views/setting/components/ThemeEditModal/useAppBgColor.ts @@ -0,0 +1,32 @@ +import { ref } from '@common/utils/vueTools' +import { pickrTools, PickrTools } from '@renderer/utils/pickrTools' + +export default () => { + const app_bg_color_ref = ref(null) + let tools: PickrTools | null + + const initAppBgColor = (color: string, changed: (color: string) => void, reset: () => void) => { + if (!app_bg_color_ref.value) return + tools = pickrTools.create(app_bg_color_ref.value, color, [ + 'rgba(255, 255, 255, 0)', + 'rgba(255, 255, 255, 0.15)', + 'rgba(255, 255, 255, 0.15)', + 'rgba(21.34, 18.92, 44.61, 0.81)', + ], changed, reset) + } + const destroyAppBgColor = () => { + if (!tools) return + tools.destroy() + tools = null + } + const setAppBgColor = (color: string) => { + tools?.setColor(color) + } + + return { + app_bg_color_ref, + initAppBgColor, + destroyAppBgColor, + setAppBgColor, + } +} diff --git a/src/renderer/views/setting/components/ThemeEditModal/useAsideFontColor.ts b/src/renderer/views/setting/components/ThemeEditModal/useAsideFontColor.ts new file mode 100644 index 00000000..c10a3d45 --- /dev/null +++ b/src/renderer/views/setting/components/ThemeEditModal/useAsideFontColor.ts @@ -0,0 +1,27 @@ +import { ref } from '@common/utils/vueTools' +import { pickrTools, PickrTools } from '@renderer/utils/pickrTools' + +export default () => { + const aside_font_color_ref = ref(null) + let tools: PickrTools | null + + const initAsideFontColor = (color: string, changed: (color: string) => void, reset: () => void) => { + if (!aside_font_color_ref.value) return + tools = pickrTools.create(aside_font_color_ref.value, color, null, changed, reset) + } + const destroyAsideFontColor = () => { + if (!tools) return + tools.destroy() + tools = null + } + const setAsideFontColor = (color: string) => { + tools?.setColor(color) + } + + return { + aside_font_color_ref, + initAsideFontColor, + destroyAsideFontColor, + setAsideFontColor, + } +} diff --git a/src/renderer/views/setting/components/ThemeEditModal/useBadgePrimaryColor.ts b/src/renderer/views/setting/components/ThemeEditModal/useBadgePrimaryColor.ts new file mode 100644 index 00000000..7faed539 --- /dev/null +++ b/src/renderer/views/setting/components/ThemeEditModal/useBadgePrimaryColor.ts @@ -0,0 +1,27 @@ +import { ref } from '@common/utils/vueTools' +import { pickrTools, PickrTools } from '@renderer/utils/pickrTools' + +export default () => { + const badge_primary_color_ref = ref(null) + let tools: PickrTools | null + + const initBadgePrimaryColor = (color: string, changed: (color: string) => void, reset: () => void) => { + if (!badge_primary_color_ref.value) return + tools = pickrTools.create(badge_primary_color_ref.value, color, null, changed, reset) + } + const destroyBadgePrimaryColor = () => { + if (!tools) return + tools.destroy() + tools = null + } + const setBadgePrimaryColor = (color: string) => { + tools?.setColor(color) + } + + return { + badge_primary_color_ref, + initBadgePrimaryColor, + destroyBadgePrimaryColor, + setBadgePrimaryColor, + } +} diff --git a/src/renderer/views/setting/components/ThemeEditModal/useBadgeSecondaryColor.ts b/src/renderer/views/setting/components/ThemeEditModal/useBadgeSecondaryColor.ts new file mode 100644 index 00000000..e204c532 --- /dev/null +++ b/src/renderer/views/setting/components/ThemeEditModal/useBadgeSecondaryColor.ts @@ -0,0 +1,41 @@ +import { ref } from '@common/utils/vueTools' +import { pickrTools, PickrTools } from '@renderer/utils/pickrTools' + +export default () => { + const badge_secondary_color_ref = ref(null) + let tools: PickrTools | null + + const initBadgeSecondaryColor = (color: string, changed: (color: string) => void, reset: () => void) => { + if (!badge_secondary_color_ref.value) return + tools = pickrTools.create(badge_secondary_color_ref.value, color, [ + 'rgba(75, 174, 213, 1)', + 'rgba(92, 191, 155, 1)', + 'rgba(66, 6, 150, 7, 171, 1)', + 'rgba(158, 212, 88, 1)', + 'rgba(223, 187, 107, 1)', + 'rgba(245, 182, 132, 1)', + 'rgba(229, 163, 159, 1)', + 'rgba(177, 155, 159, 1)', + 'rgba(99, 118, 162, 1)', + 'rgba(176, 128, 219, 1)', + 'rgba(175, 148, 121, 1)', + 'rgba(223, 187, 107, 1)', + 'rgba(53.08, 107.67, 129.18, 1)', + ], changed, reset) + } + const destroyBadgeSecondaryColor = () => { + if (!tools) return + tools.destroy() + tools = null + } + const setBadgeSecondaryColor = (color: string) => { + tools?.setColor(color) + } + + return { + badge_secondary_color_ref, + initBadgeSecondaryColor, + destroyBadgeSecondaryColor, + setBadgeSecondaryColor, + } +} diff --git a/src/renderer/views/setting/components/ThemeEditModal/useBadgeTertiaryColor.ts b/src/renderer/views/setting/components/ThemeEditModal/useBadgeTertiaryColor.ts new file mode 100644 index 00000000..d1b5d4bb --- /dev/null +++ b/src/renderer/views/setting/components/ThemeEditModal/useBadgeTertiaryColor.ts @@ -0,0 +1,41 @@ +import { ref } from '@common/utils/vueTools' +import { pickrTools, PickrTools } from '@renderer/utils/pickrTools' + +export default () => { + const badge_tertiary_color_ref = ref(null) + let tools: PickrTools | null + + const initBadgeTertiaryColor = (color: string, changed: (color: string) => void, reset: () => void) => { + if (!badge_tertiary_color_ref.value) return + tools = pickrTools.create(badge_tertiary_color_ref.value, color, [ + 'rgba(231, 170, 54, 1)', + 'rgba(92, 191, 155, 1)', + 'rgba(54, 196, 231, 1)', + 'rgba(158, 212, 88, 1)', + 'rgba(223, 187, 107, 1)', + 'rgba(245, 182, 132, 1)', + 'rgba(229, 163, 159, 1)', + 'rgba(177, 155, 159, 1)', + 'rgba(99, 118, 162, 1)', + 'rgba(176, 128, 219, 1)', + 'rgba(175, 148, 121, 1)', + 'rgba(223, 187, 107, 1)', + 'rgba(49.11, 161.05, 135.64, 1)', + ], changed, reset) + } + const destroyBadgeTertiaryColor = () => { + if (!tools) return + tools.destroy() + tools = null + } + const setBadgeTertiaryColor = (color: string) => { + tools?.setColor(color) + } + + return { + badge_tertiary_color_ref, + initBadgeTertiaryColor, + destroyBadgeTertiaryColor, + setBadgeTertiaryColor, + } +} diff --git a/src/renderer/views/setting/components/ThemeEditModal/useCloseBtnColor.ts b/src/renderer/views/setting/components/ThemeEditModal/useCloseBtnColor.ts new file mode 100644 index 00000000..2e4f3ee4 --- /dev/null +++ b/src/renderer/views/setting/components/ThemeEditModal/useCloseBtnColor.ts @@ -0,0 +1,32 @@ +import { ref } from '@common/utils/vueTools' +import { pickrTools, PickrTools } from '@renderer/utils/pickrTools' + +export default () => { + const close_btn_color_ref = ref(null) + let tools: PickrTools | null + + const initCloseBtnColor = (color: string, changed: (color: string) => void, reset: () => void) => { + if (!close_btn_color_ref.value) return + tools = pickrTools.create(close_btn_color_ref.value, color, [ + 'rgba(59, 194, 178, 1)', + 'rgba(133, 196, 59, 1)', + 'rgba(250, 180, 160, 1)', + 'rgba(104.51, 72.55, 107.98, 1)', + ], changed, reset) + } + const destroyCloseBtnColor = () => { + if (!tools) return + tools.destroy() + tools = null + } + const setCloseBtnColor = (color: string) => { + tools?.setColor(color) + } + + return { + close_btn_color_ref, + initCloseBtnColor, + destroyCloseBtnColor, + setCloseBtnColor, + } +} diff --git a/src/renderer/views/setting/components/ThemeEditModal/useHideBtnColor.ts b/src/renderer/views/setting/components/ThemeEditModal/useHideBtnColor.ts new file mode 100644 index 00000000..30b605bb --- /dev/null +++ b/src/renderer/views/setting/components/ThemeEditModal/useHideBtnColor.ts @@ -0,0 +1,32 @@ +import { ref } from '@common/utils/vueTools' +import { pickrTools, PickrTools } from '@renderer/utils/pickrTools' + +export default () => { + const hide_btn_color_ref = ref(null) + let tools: PickrTools | null + + const initHideBtnColor = (color: string, changed: (color: string) => void, reset: () => void) => { + if (!hide_btn_color_ref.value) return + tools = pickrTools.create(hide_btn_color_ref.value, color, [ + 'rgba(59, 194, 178, 1)', + 'rgba(133, 196, 59, 1)', + 'rgba(250, 180, 160, 1)', + 'rgba(77.46, 103.73, 151.81, 1)', + ], changed, reset) + } + const destroyHideBtnColor = () => { + if (!tools) return + tools.destroy() + tools = null + } + const setHideBtnColor = (color: string) => { + tools?.setColor(color) + } + + return { + hide_btn_color_ref, + initHideBtnColor, + destroyHideBtnColor, + setHideBtnColor, + } +} diff --git a/src/renderer/views/setting/components/ThemeEditModal/useMainBgColor.ts b/src/renderer/views/setting/components/ThemeEditModal/useMainBgColor.ts new file mode 100644 index 00000000..64d31b2a --- /dev/null +++ b/src/renderer/views/setting/components/ThemeEditModal/useMainBgColor.ts @@ -0,0 +1,33 @@ +import { ref } from '@common/utils/vueTools' +import { pickrTools, PickrTools } from '@renderer/utils/pickrTools' + +export default () => { + const main_bg_color_ref = ref(null) + let tools: PickrTools | null + + const initMainBgColor = (color: string, changed: (color: string) => void, reset: () => void) => { + if (!main_bg_color_ref.value) return + tools = pickrTools.create(main_bg_color_ref.value, color, [ + 'rgba(255, 255, 255, 1)', + 'rgba(19, 19, 19, 0.9)', + 'rgba(255, 255, 255, 0.9)', + 'rgba(255, 255, 255, 0.8)', + 'rgba(25.82, 23.65, 46.6, 0.54)', + ], changed, reset) + } + const destroyMainBgColor = () => { + if (!tools) return + tools.destroy() + tools = null + } + const setMainBgColor = (color: string) => { + tools?.setColor(color) + } + + return { + main_bg_color_ref, + initMainBgColor, + destroyMainBgColor, + setMainBgColor, + } +} diff --git a/src/renderer/views/setting/components/ThemeEditModal/useMainCoolor.ts b/src/renderer/views/setting/components/ThemeEditModal/useMainCoolor.ts new file mode 100644 index 00000000..44a4ebf3 --- /dev/null +++ b/src/renderer/views/setting/components/ThemeEditModal/useMainCoolor.ts @@ -0,0 +1,39 @@ +import { ref } from '@common/utils/vueTools' +import { pickrTools, PickrTools } from '@renderer/utils/pickrTools' + +export default () => { + const primary_color_ref = ref(null) + let tools: PickrTools | null + + const initMainColor = (color: string, changed: (color: string) => void) => { + if (!primary_color_ref.value) return + tools = pickrTools.create(primary_color_ref.value, color, [ + 'rgba(77, 175, 124, 1)', + 'rgba(52, 152, 219, 1)', + 'rgba(77, 131, 175, 1)', + 'rgba(245, 171, 53, 1)', + 'rgba(214, 69, 65, 1)', + 'rgba(241, 130, 141, 1)', + 'rgba(155, 89, 182, 1)', + 'rgba(108, 122, 137, 1)', + 'rgba(51, 110, 123, 1)', + 'rgba(79, 98, 208, 1)', + 'rgba(150, 150, 150, 1)', + 'rgba(74, 55, 82, 1)', + 'rgba(87, 144, 167, 1)', + 'rgba(192, 57, 43, 1)', + 'rgba(113.52, 107.21, 166.13, 1)', + ], changed, () => {}) + } + const destroyMainColor = () => { + if (!tools) return + tools.destroy() + tools = null + } + + return { + primary_color_ref, + initMainColor, + destroyMainColor, + } +} diff --git a/src/renderer/views/setting/components/ThemeEditModal/useMinBtnColor.ts b/src/renderer/views/setting/components/ThemeEditModal/useMinBtnColor.ts new file mode 100644 index 00000000..a09c7dd2 --- /dev/null +++ b/src/renderer/views/setting/components/ThemeEditModal/useMinBtnColor.ts @@ -0,0 +1,32 @@ +import { ref } from '@common/utils/vueTools' +import { pickrTools, PickrTools } from '@renderer/utils/pickrTools' + +export default () => { + const min_btn_color_ref = ref(null) + let tools: PickrTools | null + + const initMinBtnColor = (color: string, changed: (color: string) => void, reset: () => void) => { + if (!min_btn_color_ref.value) return + tools = pickrTools.create(min_btn_color_ref.value, color, [ + 'rgba(59, 194, 178, 1)', + 'rgba(133, 196, 59, 1)', + 'rgba(250, 180, 160, 1)', + 'rgba(116, 87, 152, 1)', + ], changed, reset) + } + const destroyMinBtnColor = () => { + if (!tools) return + tools.destroy() + tools = null + } + const setMinBtnColor = (color: string) => { + tools?.setColor(color) + } + + return { + min_btn_color_ref, + initMinBtnColor, + destroyMinBtnColor, + setMinBtnColor, + } +} diff --git a/src/renderer/views/setting/components/ThemeSelectorModal.vue b/src/renderer/views/setting/components/ThemeSelectorModal.vue index 9c26a5b3..d97ae87a 100644 --- a/src/renderer/views/setting/components/ThemeSelectorModal.vue +++ b/src/renderer/views/setting/components/ThemeSelectorModal.vue @@ -1,28 +1,47 @@ -