You've already forked lx-music-desktop
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
900bdd3afe | ||
|
|
8dff457422 | ||
|
|
850a429243 | ||
|
|
d3c8f16aed | ||
|
|
a43e127dd0 | ||
|
|
84e52f5525 | ||
|
|
1dce2c1f7a | ||
|
|
1c802ad5f5 | ||
|
|
db38db9256 | ||
|
|
cb9fa62dac | ||
|
|
9cc36e2a6a | ||
|
|
bf310adc12 | ||
|
|
7273b6ad67 | ||
|
|
a02f9e1151 | ||
|
|
6ef8015179 | ||
|
|
db38b27e52 | ||
|
|
cccb5b191d | ||
|
|
3c589743b1 | ||
|
|
65270e02da | ||
|
|
27c1bf8eb6 | ||
|
|
1db54195f5 | ||
|
|
399208429d | ||
|
|
b40e57f191 | ||
|
|
be350e49f0 | ||
|
|
bae732016c | ||
|
|
e50a67f0e4 | ||
|
|
26e8e0a1b1 | ||
|
|
c8210e0516 | ||
|
|
95590dd07f | ||
|
|
7e94e96bcc | ||
|
|
5009494e89 | ||
|
|
e58c2f86aa |
45
CHANGELOG.md
45
CHANGELOG.md
@@ -6,6 +6,51 @@ Project versioning adheres to [Semantic Versioning](http://semver.org/).
|
||||
Commit convention is based on [Conventional Commits](http://conventionalcommits.org).
|
||||
Change log format is based on [Keep a Changelog](http://keepachangelog.com/).
|
||||
|
||||
## [2.0.4](https://github.com/lyswhut/lx-music-desktop/compare/v2.0.3...v2.0.4) - 2023-01-15
|
||||
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复备份文件导入指引无法识别v2配置的问题
|
||||
- 修复从搜索界面进入歌单详情后,若启用强迫症设置的清空功能会导致意外清空搜索框、搜索列表的问题
|
||||
- 修复桌面歌词在启用卡拉OK歌词后字体边缘可能被截断的问题(特别是纵向歌词某些字的边角被截断导致后面的阴影露出来或阴影不均匀的问题)
|
||||
- 修复桌面歌词启用歌词缩放后的阴影显示问题
|
||||
- 修复Linux armv7l系统(如树莓派)下无法启动的问题(与修复Linux arm64的方法一样采用内置预编译模块的方式修复)
|
||||
- 修复备份与恢复的列表导入列表信息设置逻辑问题与潜在导入问题
|
||||
|
||||
## [2.0.3](https://github.com/lyswhut/lx-music-desktop/compare/v2.0.2...v2.0.3) - 2023-01-08
|
||||
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复初始设置的桌面歌词窗口没有完全居右下角的问题
|
||||
- 修复Linux arm64系统下无法启动的问题(#1102)
|
||||
- 修复桌面歌词使用斜体出现截断的问题(#1106)
|
||||
- 修复某些情况下歌词的滚动问题
|
||||
- 修复禁用切歌时歌曲播放完毕后的歌曲信息显示问题
|
||||
- 修复修改播放设置-音频输出设置后,所做的更改没有被保存的问题
|
||||
|
||||
### 优化
|
||||
|
||||
- 点击打开歌单弹窗背景可以关闭弹窗(#1096)
|
||||
|
||||
## [2.0.2](https://github.com/lyswhut/lx-music-desktop/compare/v2.0.1...v2.0.2) - 2023-01-02
|
||||
|
||||
若你更新v2.0.0后,出现之前收藏的歌曲全部丢失或者歌曲无法添加到列表播放的问题,可以按以下方式解决:
|
||||
|
||||
1. 根据你的平台类型,进入软件数据目录
|
||||
- Windows:`%APPDATA%/lx-music-desktop`
|
||||
- Linux:`$XDG_CONFIG_HOME/lx-music-desktop` 或 `~/.config/lx-music-desktop`
|
||||
- macOS:`~/Library/Application Support/lx-music-desktop`
|
||||
|
||||
2. 进入`LxDatas`目录,退出LX,删除`lx.data.db`文件,再启动软件即可
|
||||
|
||||
若以上操作仍然不行,可以加交流群或者在GitHub开issue反馈
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复无效的歌曲信息导致我的列表数据迁移失败的问题
|
||||
|
||||
## [2.0.1](https://github.com/lyswhut/lx-music-desktop/compare/v2.0.0...v2.0.1) - 2023-01-02
|
||||
|
||||
若你更新v2.0.0后,出现之前收藏的歌曲全部丢失或者歌曲无法添加到列表播放的问题,可以按以下方式解决:
|
||||
|
||||
43
build-config/build-before-pack.js
Normal file
43
build-config/build-before-pack.js
Normal file
@@ -0,0 +1,43 @@
|
||||
const fs = require('fs')
|
||||
const fsPromises = require('fs').promises
|
||||
const path = require('path')
|
||||
const { Arch } = require('electron-builder')
|
||||
|
||||
const fileNameMap = {
|
||||
[Arch.arm64]: 'arm64',
|
||||
[Arch.armv7l]: 'armv7l',
|
||||
}
|
||||
|
||||
const replaceSqliteLib = async(arch) => {
|
||||
// console.log(await fs.readdir(path.join(context.appOutDir, './resources/')))
|
||||
// if (context.electronPlatformName != 'linux' || context.arch != Arch.arm64) return
|
||||
console.log('replace sqlite lib...')
|
||||
const filePath = path.join(__dirname, `./lib/better_sqlite3.linux.${fileNameMap[arch]}.node`)
|
||||
const targetPath = path.join(__dirname, '../node_modules/better-sqlite3/build/Release/better_sqlite3.node')
|
||||
await fsPromises.unlink(targetPath).catch(_ => _)
|
||||
await fsPromises.copyFile(filePath, targetPath)
|
||||
}
|
||||
|
||||
|
||||
module.exports = async(context) => {
|
||||
const { electronPlatformName, arch } = context
|
||||
if (electronPlatformName !== 'linux') return
|
||||
const bindingFilePath = path.join(__dirname, '../node_modules/better-sqlite3/binding.gyp')
|
||||
const bindingBakFilePath = path.join(__dirname, '../node_modules/better-sqlite3/binding.gyp.bak')
|
||||
switch (arch) {
|
||||
case Arch.arm64:
|
||||
case Arch.armv7l:
|
||||
if (fs.existsSync(bindingFilePath)) {
|
||||
console.log('rename binding file...')
|
||||
await fsPromises.rename(bindingFilePath, bindingBakFilePath)
|
||||
}
|
||||
await replaceSqliteLib(arch)
|
||||
break
|
||||
|
||||
default:
|
||||
if (fs.existsSync(bindingFilePath)) return
|
||||
console.log('restore binding file...')
|
||||
await fsPromises.rename(bindingBakFilePath, bindingFilePath)
|
||||
break
|
||||
}
|
||||
}
|
||||
BIN
build-config/lib/better_sqlite3.linux.arm64.node
Normal file
BIN
build-config/lib/better_sqlite3.linux.arm64.node
Normal file
Binary file not shown.
BIN
build-config/lib/better_sqlite3.linux.armv7l.node
Normal file
BIN
build-config/lib/better_sqlite3.linux.armv7l.node
Normal file
Binary file not shown.
1021
package-lock.json
generated
1021
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
42
package.json
42
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "lx-music-desktop",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.4",
|
||||
"description": "一个免费的音乐查找助手",
|
||||
"main": "./dist/main.js",
|
||||
"productName": "lx-music-desktop",
|
||||
@@ -64,7 +64,7 @@
|
||||
"build:renderer-lyric": "cross-env NODE_ENV=production webpack --config build-config/renderer-lyric/webpack.config.prod.js --progress",
|
||||
"build:renderer-scripts": "cross-env NODE_ENV=production webpack --config build-config/renderer-scripts/webpack.config.prod.js --progress",
|
||||
"build": "npm run clean:electron && npm run build:main && npm run build:renderer && npm run build:renderer-lyric && npm run build:renderer-scripts",
|
||||
"lint": "eslint --ext .js,.vue -f node_modules/eslint-formatter-friendly src",
|
||||
"lint": "eslint --ext .ts,.js,.vue -f node_modules/eslint-formatter-friendly src",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"lint:fix": "eslint --ext .js,.vue -f node_modules/eslint-formatter-friendly --fix src",
|
||||
"dp": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://127.0.0.1:1081 npm run pack",
|
||||
@@ -79,6 +79,7 @@
|
||||
},
|
||||
"build": {
|
||||
"appId": "cn.toside.music.desktop",
|
||||
"beforePack": "./build-config/build-before-pack.js",
|
||||
"afterPack": "./build-config/build-after-pack.js",
|
||||
"protocols": {
|
||||
"name": "lx-music-protocol",
|
||||
@@ -193,17 +194,14 @@
|
||||
"electron-app",
|
||||
"vuejs3"
|
||||
],
|
||||
"author": {
|
||||
"name": "lyswhut",
|
||||
"email": "lyswhut@qq.com"
|
||||
},
|
||||
"author": "lyswhut <lyswhut@qq.com>",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/lyswhut/lx-music-desktop/issues"
|
||||
},
|
||||
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.7",
|
||||
"@babel/core": "^7.20.12",
|
||||
"@babel/eslint-parser": "^7.19.1",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
@@ -214,10 +212,10 @@
|
||||
"@types/better-sqlite3": "^7.6.3",
|
||||
"@types/needle": "^3.2.0",
|
||||
"@types/tunnel": "^0.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.47.1",
|
||||
"@typescript-eslint/parser": "^5.47.1",
|
||||
"@volar/vue-language-plugin-pug": "^1.0.19",
|
||||
"babel-loader": "^9.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.1",
|
||||
"@typescript-eslint/parser": "^5.48.1",
|
||||
"@volar/vue-language-plugin-pug": "^1.0.24",
|
||||
"babel-loader": "^9.1.2",
|
||||
"browserslist": "^4.21.4",
|
||||
"chalk": "^4.1.2",
|
||||
"changelog-parser": "^3.0.1",
|
||||
@@ -228,32 +226,32 @@
|
||||
"css-minimizer-webpack-plugin": "^4.2.2",
|
||||
"del": "^6.1.1",
|
||||
"electron": "^19.1.9",
|
||||
"electron-builder": "^24.0.0-alpha.8",
|
||||
"electron-builder": "^24.0.0-alpha.10",
|
||||
"electron-debug": "^3.2.0",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-to-chromium": "^1.4.284",
|
||||
"electron-updater": "^6.0.0-alpha.6",
|
||||
"eslint": "^8.31.0",
|
||||
"electron-updater": "^6.0.0-alpha.7",
|
||||
"eslint": "^8.32.0",
|
||||
"eslint-config-standard": "^17.0.0",
|
||||
"eslint-config-standard-with-typescript": "^24.0.0",
|
||||
"eslint-config-standard-with-typescript": "^27.0.1",
|
||||
"eslint-formatter-friendly": "github:lyswhut/eslint-friendly-formatter#2170d1320e2fad13615a9dcf229669f0bb473a53",
|
||||
"eslint-plugin-html": "^7.1.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.6.0",
|
||||
"eslint-plugin-import": "^2.27.4",
|
||||
"eslint-plugin-n": "^15.6.1",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-vue": "^9.8.0",
|
||||
"eslint-plugin-vue": "^9.9.0",
|
||||
"eslint-webpack-plugin": "^3.2.0",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"less": "^4.1.3",
|
||||
"less-loader": "^11.1.0",
|
||||
"mini-css-extract-plugin": "^2.7.2",
|
||||
"node-loader": "^2.0.0",
|
||||
"postcss": "^8.4.20",
|
||||
"postcss": "^8.4.21",
|
||||
"postcss-loader": "^7.0.2",
|
||||
"postcss-pxtorem": "^6.0.0",
|
||||
"pug": "^3.0.2",
|
||||
"pug-plain-loader": "^1.1.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"rimraf": "^4.0.5",
|
||||
"spinnies": "github:lyswhut/spinnies#233305c58694aa3b053e3ab9af9049993f918b9d",
|
||||
"svg-sprite-loader": "^6.0.11",
|
||||
"svg-transform-loader": "^2.0.13",
|
||||
@@ -286,13 +284,13 @@
|
||||
"jschardet": "^3.0.0",
|
||||
"koa": "^2.14.1",
|
||||
"long": "^5.2.1",
|
||||
"music-metadata": "^8.1.0",
|
||||
"music-metadata": "^8.1.3",
|
||||
"needle": "github:lyswhut/needle#93299ac841b7e9a9f82ca7279b88aaaeda404060",
|
||||
"node-id3": "^0.2.5",
|
||||
"socket.io": "^4.5.4",
|
||||
"sortablejs": "^1.15.0",
|
||||
"tunnel": "^0.0.6",
|
||||
"utf-8-validate": "^5.0.10",
|
||||
"utf-8-validate": "^6.0.0",
|
||||
"vue": "^3.2.45",
|
||||
"vue-router": "^4.1.6"
|
||||
},
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
若你更新v2.0.0后,出现之前收藏的歌曲全部丢失或者歌曲无法添加到列表播放的问题,可以按以下方式解决:
|
||||
|
||||
1. 根据你的平台类型,进入软件数据目录
|
||||
- Windows:`%APPDATA%/lx-music-desktop`
|
||||
- Linux:`$XDG_CONFIG_HOME/lx-music-desktop` 或 `~/.config/lx-music-desktop`
|
||||
- macOS:`~/Library/Application Support/lx-music-desktop`
|
||||
### 修复
|
||||
|
||||
2. 进入`LxDatas`目录,退出LX,删除`lx.data.db`文件,再启动软件即可
|
||||
|
||||
若以上操作仍然不行,可以加交流群或者在GitHub开issue反馈
|
||||
|
||||
### 优化
|
||||
|
||||
- 单次执行所有sql语句,尝试解决某些情况下某些表没有成功创建的问题
|
||||
- 修复备份文件导入指引无法识别v2配置的问题
|
||||
- 修复从搜索界面进入歌单详情后,若启用强迫症设置的清空功能会导致意外清空搜索框、搜索列表的问题
|
||||
- 修复桌面歌词在启用卡拉OK歌词后字体边缘可能被截断的问题(特别是纵向歌词某些字的边角被截断导致后面的阴影露出来或阴影不均匀的问题)
|
||||
- 修复桌面歌词启用歌词缩放后的阴影显示问题
|
||||
- 修复Linux armv7l系统(如树莓派)下无法启动的问题(与修复Linux arm64的方法一样采用内置预编译模块的方式修复)
|
||||
- 修复备份与恢复的列表导入列表信息设置逻辑问题与潜在导入问题
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -23,6 +23,7 @@ export const LIST_IDS = {
|
||||
LOVE: 'love',
|
||||
TEMP: 'temp',
|
||||
DOWNLOAD: 'download',
|
||||
PLAY_LATER: null,
|
||||
} as const
|
||||
|
||||
export const DATA_KEYS = {
|
||||
|
||||
@@ -58,7 +58,7 @@ const defaultSetting: LX.AppSetting = {
|
||||
'desktopLyric.style.align': 'center',
|
||||
'desktopLyric.style.font': '',
|
||||
'desktopLyric.style.fontSize': 20,
|
||||
'desktopLyric.style.lineGap': 15,
|
||||
'desktopLyric.style.lineGap': 14,
|
||||
'desktopLyric.style.lyricUnplayColor': 'rgba(255, 255, 255, 1)',
|
||||
'desktopLyric.style.lyricPlayedColor': 'rgba(7, 197, 86, 1)',
|
||||
'desktopLyric.style.lyricShadowColor': 'rgba(0, 0, 0, 0.15)',
|
||||
|
||||
@@ -36,7 +36,7 @@ exports.createThemeColors = (rgbaColor, fontRgbaColor, isDark) => {
|
||||
const createFontColors = (rgbaColor, isDark) => {
|
||||
// rgb(238, 238, 238)
|
||||
// let prec = 'rgb(255, 255, 255)'
|
||||
if (rgbaColor == null) rgbaColor = isDark ? 'rgb(229, 229, 229)' : 'rgb(33, 33, 33)'
|
||||
rgbaColor ??= isDark ? 'rgb(229, 229, 229)' : 'rgb(33, 33, 33)'
|
||||
if (isDark) return createFontDarkColors(rgbaColor)
|
||||
|
||||
let colors = {
|
||||
|
||||
4
src/common/types/common.d.ts
vendored
4
src/common/types/common.d.ts
vendored
@@ -65,9 +65,7 @@ declare namespace LX {
|
||||
|
||||
interface HotKeyConfig {
|
||||
enable: boolean
|
||||
keys: {
|
||||
[key: string]: HotKey
|
||||
}
|
||||
keys: Record<string, HotKey>
|
||||
}
|
||||
interface HotKeyConfigAll {
|
||||
local: HotKeyConfig
|
||||
|
||||
2
src/common/types/list.d.ts
vendored
2
src/common/types/list.d.ts
vendored
@@ -4,7 +4,7 @@ declare namespace LX {
|
||||
id: string
|
||||
name: string
|
||||
// list: LX.Music.MusicInfo[]
|
||||
source?: LX.Source
|
||||
source?: LX.OnlineSource
|
||||
sourceListId?: string
|
||||
// position?: number
|
||||
locationUpdateTime: number | null
|
||||
|
||||
@@ -176,7 +176,7 @@ export const similar = (a: string, b: string) => {
|
||||
* @param arr
|
||||
* @param data
|
||||
*/
|
||||
export const sortInsert = (arr: Array<{ num: number, data: any }>, data: { num: number, data: any }) => {
|
||||
export const sortInsert = <T>(arr: Array<{ num: number, data: T }>, data: { num: number, data: T }) => {
|
||||
let key = data.num
|
||||
let left = 0
|
||||
let right = arr.length - 1
|
||||
|
||||
@@ -135,12 +135,13 @@ class Task extends EventEmitter {
|
||||
try {
|
||||
this.__initDownload(response)
|
||||
} catch (error: any) {
|
||||
return this.__handleError(error)
|
||||
this.__handleError(error)
|
||||
return
|
||||
}
|
||||
this.status = STATUS.running
|
||||
response
|
||||
.on('data', this.__handleWriteData.bind(this))
|
||||
.on('error', err => this.__handleError(err))
|
||||
.on('error', err => { this.__handleError(err) })
|
||||
.on('end', () => {
|
||||
if (response.complete) {
|
||||
this.__handleComplete()
|
||||
@@ -150,7 +151,7 @@ class Task extends EventEmitter {
|
||||
}
|
||||
})
|
||||
})
|
||||
.on('error', err => this.__handleError(err))
|
||||
.on('error', err => { this.__handleError(err) })
|
||||
.on('close', () => {
|
||||
void this.__closeWriteStream()
|
||||
})
|
||||
@@ -159,7 +160,10 @@ class Task extends EventEmitter {
|
||||
|
||||
__initDownload(response: http.IncomingMessage) {
|
||||
this.progress.total = response.headers['content-length'] ? parseInt(response.headers['content-length']) : 0
|
||||
if (!this.progress.total) return this.__handleError(new Error('Content length is 0'))
|
||||
if (!this.progress.total) {
|
||||
this.__handleError(new Error('Content length is 0'))
|
||||
return
|
||||
}
|
||||
let options: any = {}
|
||||
let isResumable = this.options.forceResume ||
|
||||
response.headers['accept-ranges'] !== 'none' ||
|
||||
@@ -170,11 +174,17 @@ class Task extends EventEmitter {
|
||||
options.flags = 'a'
|
||||
if (this.progress.downloaded) this.progress.total -= 10
|
||||
} else {
|
||||
if (this.chunkInfo.startByte != '0') return this.__handleError(new Error('The resource cannot be resumed download.'))
|
||||
if (this.chunkInfo.startByte != '0') {
|
||||
this.__handleError(new Error('The resource cannot be resumed download.'))
|
||||
return
|
||||
}
|
||||
}
|
||||
this.progress.total += this.progress.downloaded
|
||||
this.statsEstimate.prevBytes = this.progress.downloaded
|
||||
if (!this.chunkInfo.path) return this.__handleError(new Error('Chunk save Path is not set.'))
|
||||
if (!this.chunkInfo.path) {
|
||||
this.__handleError(new Error('Chunk save Path is not set.'))
|
||||
return
|
||||
}
|
||||
this.ws = fs.createWriteStream(this.chunkInfo.path, options)
|
||||
|
||||
this.ws.on('finish', () => {
|
||||
@@ -216,7 +226,10 @@ class Task extends EventEmitter {
|
||||
|
||||
async __closeWriteStream() {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (!this.ws) return resolve()
|
||||
if (!this.ws) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
// console.log('close write stream')
|
||||
this.ws.close(err => {
|
||||
if (err) {
|
||||
@@ -251,7 +264,10 @@ class Task extends EventEmitter {
|
||||
// this.__handleError(err)
|
||||
this.chunkInfo.startByte = '0'
|
||||
this.resumeLastChunk = null
|
||||
if (unlinkErr && unlinkErr.code !== 'ENOENT') return this.__handleError(unlinkErr)
|
||||
if (unlinkErr && unlinkErr.code !== 'ENOENT') {
|
||||
this.__handleError(unlinkErr)
|
||||
return
|
||||
}
|
||||
void this.start()
|
||||
})
|
||||
})
|
||||
@@ -259,7 +275,10 @@ class Task extends EventEmitter {
|
||||
}
|
||||
}
|
||||
// console.log('data', chunk)
|
||||
if (this.status == STATUS.stopped || this.ws == null) return console.log('cancel write')
|
||||
if (this.status == STATUS.stopped || this.ws == null) {
|
||||
console.log('cancel write')
|
||||
return
|
||||
}
|
||||
this.__calculateProgress(chunk.length)
|
||||
this.ws.write(chunk, err => {
|
||||
if (!err) return
|
||||
|
||||
@@ -141,7 +141,7 @@ module.exports = class FontPlayer {
|
||||
// lineText += text
|
||||
|
||||
if (this.shadowContent) {
|
||||
if (!lrcShadowContent) lrcShadowContent = document.createElement('div')
|
||||
lrcShadowContent ??= document.createElement('div')
|
||||
const shadowDom = document.createElement('span')
|
||||
shadowDom.textContent = text
|
||||
lrcShadowContent.appendChild(shadowDom)
|
||||
@@ -159,7 +159,7 @@ module.exports = class FontPlayer {
|
||||
}
|
||||
|
||||
if (this.shadowContent && lrcShadowContent) {
|
||||
lrcShadowContent.style = 'position:absolute;top:0;left:0;width:100%;z-index:-1;'
|
||||
lrcShadowContent.style = 'position:absolute;top:0;left:0;right:0;z-index:-1;'
|
||||
lrcShadowContent.className = this.shadowClassName
|
||||
this.line.appendChild(lrcShadowContent)
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ export default (setting: any): Partial<LX.AppSetting> => {
|
||||
// 迁移列表滚动位置设置 ~0.18.3
|
||||
if (setting.list?.scroll) {
|
||||
let scroll = setting.list.scroll
|
||||
if (setting.list.isSaveScrollLocation) setting.list.isSaveScrollLocation = scroll.enable
|
||||
setting.list.isSaveScrollLocation &&= scroll.enable
|
||||
delete setting.list.scroll
|
||||
}
|
||||
|
||||
|
||||
@@ -16,9 +16,15 @@ export const dirname = (p: string): string => path.dirname(p)
|
||||
*/
|
||||
export const checkPath = async(path: string): Promise<boolean> => {
|
||||
return await new Promise(resolve => {
|
||||
if (!path) return resolve(false)
|
||||
if (!path) {
|
||||
resolve(false)
|
||||
return
|
||||
}
|
||||
fs.access(path, fs.constants.F_OK, err => {
|
||||
if (err) return resolve(false)
|
||||
if (err) {
|
||||
resolve(false)
|
||||
return
|
||||
}
|
||||
resolve(true)
|
||||
})
|
||||
})
|
||||
@@ -26,9 +32,15 @@ export const checkPath = async(path: string): Promise<boolean> => {
|
||||
|
||||
export const getFileStats = async(path: string): Promise<fs.Stats | null> => {
|
||||
return await new Promise(resolve => {
|
||||
if (!path) return resolve(null)
|
||||
if (!path) {
|
||||
resolve(null)
|
||||
return
|
||||
}
|
||||
fs.stat(path, (err, stats) => {
|
||||
if (err) return resolve(null)
|
||||
if (err) {
|
||||
resolve(null)
|
||||
return
|
||||
}
|
||||
resolve(stats)
|
||||
})
|
||||
})
|
||||
@@ -39,29 +51,37 @@ export const getFileStats = async(path: string): Promise<fs.Stats | null> => {
|
||||
* @param path
|
||||
* @returns
|
||||
*/
|
||||
export const createDir = async(path: string): Promise<void> => {
|
||||
return await new Promise((resolve, reject) => {
|
||||
fs.access(path, fs.constants.F_OK | fs.constants.W_OK, err => {
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
fs.mkdir(path, { recursive: true }, err => {
|
||||
if (err) return reject(err)
|
||||
resolve()
|
||||
})
|
||||
return
|
||||
}
|
||||
return reject(err)
|
||||
export const createDir = async(path: string) => new Promise<void>((resolve, reject) => {
|
||||
fs.access(path, fs.constants.F_OK | fs.constants.W_OK, err => {
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
fs.mkdir(path, { recursive: true }, err => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
return
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
return
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
reject(err)
|
||||
return
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export const removeFile = async(path: string) => new Promise<void>((resolve, reject) => {
|
||||
fs.access(path, fs.constants.F_OK, err => {
|
||||
if (err) return err.code == 'ENOENT' ? resolve() : reject(err)
|
||||
if (err) {
|
||||
err.code == 'ENOENT' ? resolve() : reject(err)
|
||||
return
|
||||
}
|
||||
fs.unlink(path, err => {
|
||||
if (err) return reject(err)
|
||||
if (err) {
|
||||
reject(err)
|
||||
return
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
@@ -79,7 +99,10 @@ export const toMD5 = (str: string) => crypto.createHash('md5').update(str).diges
|
||||
export const gzipData = async(str: string): Promise<Buffer> => {
|
||||
return await new Promise((resolve, reject) => {
|
||||
gzip(str, (err, result) => {
|
||||
if (err) return reject(err)
|
||||
if (err) {
|
||||
reject(err)
|
||||
return
|
||||
}
|
||||
resolve(result)
|
||||
})
|
||||
})
|
||||
@@ -88,7 +111,10 @@ export const gzipData = async(str: string): Promise<Buffer> => {
|
||||
export const gunzipData = async(buf: Buffer): Promise<string> => {
|
||||
return await new Promise((resolve, reject) => {
|
||||
gunzip(buf, (err, result) => {
|
||||
if (err) return reject(err)
|
||||
if (err) {
|
||||
reject(err)
|
||||
return
|
||||
}
|
||||
resolve(result.toString())
|
||||
})
|
||||
})
|
||||
|
||||
@@ -45,7 +45,10 @@ const handleScrollY = (element: HTMLElement, to: number, duration = 300, fn = no
|
||||
element.scrollTop = val
|
||||
}
|
||||
if (currentTime < duration) {
|
||||
if (cancel) return fn()
|
||||
if (cancel) {
|
||||
fn()
|
||||
return
|
||||
}
|
||||
window.setTimeout(animateScroll, increment)
|
||||
} else {
|
||||
fn()
|
||||
@@ -117,7 +120,10 @@ const handleScrollX = (element: HTMLElement, to: number, duration = 300, fn = ()
|
||||
element.scrollLeft = val
|
||||
}
|
||||
if (currentTime < duration) {
|
||||
if (cancel) return fn()
|
||||
if (cancel) {
|
||||
fn()
|
||||
return
|
||||
}
|
||||
window.setTimeout(animateScroll, increment)
|
||||
} else {
|
||||
fn()
|
||||
@@ -192,7 +198,10 @@ const handleScrollXR = (element: HTMLElement, to: number, duration = 300, fn = (
|
||||
element.scrollLeft = val
|
||||
}
|
||||
if (currentTime < duration) {
|
||||
if (cancel) return fn()
|
||||
if (cancel) {
|
||||
fn()
|
||||
return
|
||||
}
|
||||
window.setTimeout(animateScroll, increment)
|
||||
} else {
|
||||
fn()
|
||||
@@ -235,7 +244,7 @@ export const scrollXRTo = (element: HTMLElement, to: number, duration = 300, fn
|
||||
*/
|
||||
let dom_title = document.getElementsByTagName('title')[0]
|
||||
export const setTitle = (title: string | null) => {
|
||||
if (!title) title = '洛雪音乐助手'
|
||||
title ||= '洛雪音乐助手'
|
||||
dom_title.innerText = title
|
||||
}
|
||||
|
||||
|
||||
@@ -118,3 +118,13 @@ export const fixNewMusicInfoQuality = (musicInfo: LX.Music.MusicInfo) => {
|
||||
|
||||
return musicInfo
|
||||
}
|
||||
|
||||
export const filterMusicList = <T extends LX.Music.MusicInfo>(list: T[]): T[] => {
|
||||
const ids: Set<string> = new Set()
|
||||
return list.filter(s => {
|
||||
if (!s.id || ids.has(s.id) || !s.name) return false
|
||||
if (s.singer == null) s.singer = ''
|
||||
ids.add(s.id)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
@@ -116,8 +116,14 @@ export const registerDeeplink = (startApp: () => void) => {
|
||||
export const listenerAppEvent = (startApp: () => void) => {
|
||||
app.on('web-contents-created', (event, contents) => {
|
||||
contents.on('will-navigate', (event, navigationUrl) => {
|
||||
if (global.isDev) return console.log('navigation to url:', navigationUrl)
|
||||
if (!navigationUrlWhiteList.some(url => url.test(navigationUrl))) return event.preventDefault()
|
||||
if (global.isDev) {
|
||||
console.log('navigation to url:', navigationUrl)
|
||||
return
|
||||
}
|
||||
if (!navigationUrlWhiteList.some(url => url.test(navigationUrl))) {
|
||||
event.preventDefault()
|
||||
return
|
||||
}
|
||||
console.log('navigation to url:', navigationUrl)
|
||||
})
|
||||
contents.setWindowOpenHandler(({ url }) => {
|
||||
@@ -233,7 +239,7 @@ export const initAppSetting = async() => {
|
||||
if (!isInitialized) {
|
||||
const dbFileExists = await global.lx.worker.dbService.init(global.lxDataPath)
|
||||
global.lx.appSetting = (await initSetting()).setting
|
||||
if (!dbFileExists) await migrateDBData().catch(err => log.error(err))
|
||||
if (!dbFileExists) await migrateDBData().catch(err => { log.error(err) })
|
||||
initTheme()
|
||||
}
|
||||
// global.lx.theme = getTheme()
|
||||
|
||||
@@ -17,8 +17,12 @@ electronDebug({
|
||||
// Install `vue-devtools`
|
||||
app.on('ready', () => {
|
||||
installExtension(VUEJS_DEVTOOLS)
|
||||
.then((name: string) => console.log(`Added Extension: ${name}`))
|
||||
.catch((err: Error) => console.log('An error occurred: ', err))
|
||||
.then((name: string) => {
|
||||
console.log(`Added Extension: ${name}`)
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
console.log('An error occurred: ', err)
|
||||
})
|
||||
})
|
||||
|
||||
// Require `main` process to boot app
|
||||
|
||||
@@ -30,4 +30,6 @@ listenerAppEvent(init)
|
||||
|
||||
|
||||
// https://github.com/electron/electron/issues/16809
|
||||
void app.whenReady().then(() => isLinux ? setTimeout(init, 300) : init())
|
||||
void app.whenReady().then(() => {
|
||||
isLinux ? setTimeout(init, 300) : init()
|
||||
})
|
||||
|
||||
@@ -146,7 +146,7 @@ const broadcast = async(action: listAction, data: any, excludeIds: string[] = []
|
||||
export const sendListAction = async(action: LX.Sync.ActionList) => {
|
||||
console.log('sendListAction', action.action)
|
||||
// io.sockets
|
||||
return await broadcast('list:action', JSON.stringify(action))
|
||||
await broadcast('list:action', JSON.stringify(action))
|
||||
}
|
||||
|
||||
export const registerListHandler = (_io: Server, socket: LX.Sync.Socket) => {
|
||||
|
||||
@@ -18,49 +18,54 @@ export const authCode = async(req: http.IncomingMessage, res: http.ServerRespons
|
||||
|
||||
let ip = req.socket.remoteAddress
|
||||
// console.log(req.headers)
|
||||
if (typeof req.headers.m == 'string' && ip && (requestIps.get(ip) ?? 0) < 10) {
|
||||
if (req.headers.m) {
|
||||
label:
|
||||
if (req.headers.i) { // key验证
|
||||
if (typeof req.headers.i != 'string') break label
|
||||
const keyInfo = getClientKeyInfo(req.headers.i)
|
||||
if (!keyInfo) break label
|
||||
let text
|
||||
try {
|
||||
text = aesDecrypt(req.headers.m, keyInfo.key)
|
||||
} catch (err) {
|
||||
break label
|
||||
}
|
||||
// console.log(text)
|
||||
if (text.startsWith(SYNC_CODE.authMsg)) {
|
||||
code = 200
|
||||
const deviceName = text.replace(SYNC_CODE.authMsg, '') || 'Unknown'
|
||||
if (deviceName != keyInfo.deviceName) {
|
||||
keyInfo.deviceName = deviceName
|
||||
setClientKeyInfo(keyInfo)
|
||||
if (typeof req.headers.m == 'string') {
|
||||
if (ip && (requestIps.get(ip) ?? 0) < 10) {
|
||||
if (req.headers.m) {
|
||||
label:
|
||||
if (req.headers.i) { // key验证
|
||||
if (typeof req.headers.i != 'string') break label
|
||||
const keyInfo = getClientKeyInfo(req.headers.i)
|
||||
if (!keyInfo) break label
|
||||
let text
|
||||
try {
|
||||
text = aesDecrypt(req.headers.m, keyInfo.key)
|
||||
} catch (err) {
|
||||
break label
|
||||
}
|
||||
// console.log(text)
|
||||
if (text.startsWith(SYNC_CODE.authMsg)) {
|
||||
code = 200
|
||||
const deviceName = text.replace(SYNC_CODE.authMsg, '') || 'Unknown'
|
||||
if (deviceName != keyInfo.deviceName) {
|
||||
keyInfo.deviceName = deviceName
|
||||
setClientKeyInfo(keyInfo)
|
||||
}
|
||||
msg = aesEncrypt(SYNC_CODE.helloMsg, keyInfo.key)
|
||||
}
|
||||
} else { // 连接码验证
|
||||
let key = ''.padStart(16, Buffer.from(authCode).toString('hex'))
|
||||
// const iv = Buffer.from(key.split('').reverse().join('')).toString('base64')
|
||||
key = Buffer.from(key).toString('base64')
|
||||
// console.log(req.headers.m, authCode, key)
|
||||
let text
|
||||
try {
|
||||
text = aesDecrypt(req.headers.m, key)
|
||||
} catch (err) {
|
||||
break label
|
||||
}
|
||||
// console.log(text)
|
||||
if (text.startsWith(SYNC_CODE.authMsg)) {
|
||||
code = 200
|
||||
const data = text.split('\n')
|
||||
const publicKey = `-----BEGIN PUBLIC KEY-----\n${data[1]}\n-----END PUBLIC KEY-----`
|
||||
const deviceName = data[2] || 'Unknown'
|
||||
msg = rsaEncrypt(Buffer.from(JSON.stringify(createClientKeyInfo(deviceName))), publicKey)
|
||||
}
|
||||
msg = aesEncrypt(SYNC_CODE.helloMsg, keyInfo.key)
|
||||
}
|
||||
} else { // 连接码验证
|
||||
let key = ''.padStart(16, Buffer.from(authCode).toString('hex'))
|
||||
// const iv = Buffer.from(key.split('').reverse().join('')).toString('base64')
|
||||
key = Buffer.from(key).toString('base64')
|
||||
// console.log(req.headers.m, authCode, key)
|
||||
let text
|
||||
try {
|
||||
text = aesDecrypt(req.headers.m, key)
|
||||
} catch (err) {
|
||||
break label
|
||||
}
|
||||
// console.log(text)
|
||||
if (text.startsWith(SYNC_CODE.authMsg)) {
|
||||
code = 200
|
||||
const data = text.split('\n')
|
||||
const publicKey = `-----BEGIN PUBLIC KEY-----\n${data[1]}\n-----END PUBLIC KEY-----`
|
||||
const deviceName = data[2] || 'Unknown'
|
||||
msg = rsaEncrypt(Buffer.from(JSON.stringify(createClientKeyInfo(deviceName))), publicKey)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
code = 403
|
||||
msg = SYNC_CODE.msgBlockedIp
|
||||
}
|
||||
}
|
||||
res.writeHead(code)
|
||||
|
||||
@@ -3,5 +3,6 @@ export const SYNC_CODE = {
|
||||
idPrefix: 'OjppZDo6',
|
||||
authMsg: 'lx-music auth::',
|
||||
msgAuthFailed: 'Auth failed',
|
||||
msgBlockedIp: 'Blocked IP',
|
||||
msgConnect: 'lx-music connect',
|
||||
} as const
|
||||
|
||||
@@ -142,7 +142,10 @@ const handleStartServer = async(port = 9527) => await new Promise((resolve, reje
|
||||
|
||||
httpServer.on('listening', () => {
|
||||
const addr = httpServer.address()
|
||||
if (!addr) return reject(new Error('address is null'))
|
||||
if (!addr) {
|
||||
reject(new Error('address is null'))
|
||||
return
|
||||
}
|
||||
const bind = typeof addr == 'string' ? `pipe ${addr}` : `port ${addr.port}`
|
||||
console.info(`Listening on ${bind}`)
|
||||
resolve(null)
|
||||
@@ -171,7 +174,7 @@ export const stopServer = async() => {
|
||||
return
|
||||
}
|
||||
console.log('stoping sync server...')
|
||||
return await handleStopServer().then(() => {
|
||||
await handleStopServer().then(() => {
|
||||
console.log('sync server stoped')
|
||||
status.status = false
|
||||
status.message = ''
|
||||
@@ -189,7 +192,7 @@ export const startServer = async(port: number) => {
|
||||
if (status.status) await handleStopServer()
|
||||
|
||||
console.log('starting sync server...')
|
||||
return await handleStartServer(port).then(() => {
|
||||
await handleStartServer(port).then(() => {
|
||||
console.log('sync server started')
|
||||
status.status = true
|
||||
status.message = ''
|
||||
|
||||
@@ -31,7 +31,10 @@ const getRemoteListData = async(socket: LX.Sync.Socket): Promise<LX.Sync.ListDat
|
||||
socket.removeListener('list:sync', handleSuccess)
|
||||
console.log('getRemoteListData', 'handleSuccess')
|
||||
const data: LX.Sync.Data | null = JSON.parse(decryptMsg(socket.data.keyInfo, enData))
|
||||
if (!data) return reject(new Error('Get remote list data failed'))
|
||||
if (!data) {
|
||||
reject(new Error('Get remote list data failed'))
|
||||
return
|
||||
}
|
||||
if (data.action != 'getData') return
|
||||
resolve(patchListData(data.data))
|
||||
}
|
||||
@@ -101,10 +104,10 @@ const updateSnapshot = async(path: string, data: string) => {
|
||||
let writeFilePromise = writeFilePromises.get(path) ?? Promise.resolve()
|
||||
writeFilePromise = writeFilePromise.then(async() => {
|
||||
if (writeFilePromise !== writeFilePromises.get(path)) return
|
||||
return await fsPromises.writeFile(path, data)
|
||||
await fsPromises.writeFile(path, data)
|
||||
})
|
||||
writeFilePromises.set(path, writeFilePromise)
|
||||
return await writeFilePromise.finally(() => {
|
||||
await writeFilePromise.finally(() => {
|
||||
if (writeFilePromise !== writeFilePromises.get(path)) return
|
||||
writeFilePromises.delete(path)
|
||||
})
|
||||
@@ -453,7 +456,7 @@ const checkSyncQueue = async(): Promise<void> => {
|
||||
if (!syncingId) return
|
||||
console.log('sync queue...')
|
||||
await wait()
|
||||
return await checkSyncQueue()
|
||||
await checkSyncQueue()
|
||||
}
|
||||
|
||||
// export {
|
||||
@@ -484,7 +487,7 @@ const _syncList = async(_io: Server, socket: LX.Sync.Socket) => {
|
||||
|
||||
const removeSnapshot = async(keyInfo: LX.Sync.KeyInfo) => {
|
||||
const filePath = getSnapshotFilePath(keyInfo)
|
||||
return await fsPromises.unlink(filePath)
|
||||
await fsPromises.unlink(filePath)
|
||||
}
|
||||
|
||||
export {
|
||||
|
||||
@@ -44,7 +44,7 @@ export const createClientKeyInfo = (deviceName: string): LX.Sync.KeyInfo => {
|
||||
deviceName,
|
||||
}
|
||||
const store = getStore(STORE_NAME)
|
||||
if (!keyInfos) keyInfos = store.get('keys') as KeyInfos || {}
|
||||
keyInfos ??= store.get('keys') as KeyInfos || {}
|
||||
if (Object.keys(keyInfos).length > 101) throw new Error('max keys')
|
||||
|
||||
keyInfos[keyInfo.clientId] = keyInfo
|
||||
|
||||
@@ -27,7 +27,7 @@ const winEvent = () => {
|
||||
|
||||
export const createWindow = async(userApi: LX.UserApi.UserApiInfo) => {
|
||||
await closeWindow()
|
||||
if (!dir) dir = global.isDev ? webpackUserApiPath : join(encodePath(__dirname), 'userApi')
|
||||
dir ??= global.isDev ? webpackUserApiPath : join(encodePath(__dirname), 'userApi')
|
||||
|
||||
if (!html) {
|
||||
html = await fs.promises.readFile(join(dir, 'renderer/user-api.html'), 'utf8')
|
||||
@@ -72,7 +72,10 @@ export const createWindow = async(userApi: LX.UserApi.UserApiInfo) => {
|
||||
})
|
||||
}
|
||||
browserWindow.webContents.session.setPermissionRequestHandler((webContents, permission, resolve) => {
|
||||
if (webContents === browserWindow?.webContents) return resolve(false)
|
||||
if (webContents === browserWindow?.webContents) {
|
||||
resolve(false)
|
||||
return
|
||||
}
|
||||
resolve(true)
|
||||
})
|
||||
browserWindow.webContents.setWindowOpenHandler(() => {
|
||||
|
||||
@@ -118,7 +118,9 @@ export const cancelRequest = (requestKey: string) => {
|
||||
}
|
||||
|
||||
export const request = async({ requestKey, data }: LX.UserApi.UserApiRequestParams): Promise<any> => await new Promise((resolve, reject) => {
|
||||
if (!userApi) return reject(new Error('user api is not load'))
|
||||
if (!userApi) {
|
||||
reject(new Error('user api is not load'))
|
||||
}
|
||||
|
||||
// const requestKey = `request__${Math.random().toString().substring(2)}`
|
||||
const timeout = timeouts.get(requestKey)
|
||||
|
||||
@@ -33,7 +33,7 @@ export const importApi = (script: string): LX.UserApi.UserApiInfo => {
|
||||
script,
|
||||
allowShowUpdateAlert: true,
|
||||
}
|
||||
if (!userApis) userApis = []
|
||||
userApis ??= []
|
||||
userApis.push(apiInfo)
|
||||
getStore(STORE_NAMES.USER_API).set('userApis', userApis)
|
||||
return apiInfo
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { join } from 'path'
|
||||
import { BrowserWindow } from 'electron'
|
||||
import { debounce, isLinux } from '@common/utils'
|
||||
import { getLyricWindowBounds } from './utils'
|
||||
import { getLyricWindowBounds, offset } from './utils'
|
||||
import { mainSend } from '@common/mainIpc'
|
||||
import { encodePath } from '@common/utils/electron'
|
||||
|
||||
@@ -85,8 +85,8 @@ export const createWindow = () => {
|
||||
let isShowTaskbar = global.lx.appSetting['desktopLyric.isShowTaskbar']
|
||||
let { width: screenWidth, height: screenHeight } = global.envParams.workAreaSize
|
||||
if (x == null || y == null) {
|
||||
x = screenWidth - width
|
||||
y = screenHeight - height
|
||||
x = screenWidth - width + offset
|
||||
y = screenHeight - height + offset
|
||||
}
|
||||
if (isLockScreen) {
|
||||
let bounds = getLyricWindowBounds({ x, y, width, height }, { x: null, y: 0, w: width, h: height })
|
||||
@@ -202,7 +202,10 @@ export const alwaysOnTopTools: AlwaysOnTopTools = {
|
||||
startLoop() {
|
||||
this.clearLoop()
|
||||
this.timeout = setInterval(() => {
|
||||
if (!isExistWindow()) return this.clearLoop()
|
||||
if (!isExistWindow()) {
|
||||
this.clearLoop()
|
||||
return
|
||||
}
|
||||
setAlwaysOnTop(true, 'screen-saver')
|
||||
}, 1000)
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@ let winX
|
||||
let winY
|
||||
let wasW
|
||||
let wasH
|
||||
let offset = 8
|
||||
export const offset = 8
|
||||
let minWidth = 80
|
||||
let minHeight = 50
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ const winEvent = () => {
|
||||
return
|
||||
}
|
||||
|
||||
if (global.lx.isTrafficLightClose) global.lx.isTrafficLightClose = false
|
||||
global.lx.isTrafficLightClose &&= false
|
||||
event.preventDefault()
|
||||
browserWindow!.hide()
|
||||
})
|
||||
@@ -242,13 +242,13 @@ export const setThumbarButtons = ({ empty, collect, play, next, prev }: LX.TaskB
|
||||
|
||||
export const setThumbnailClip = (region: Electron.Rectangle) => {
|
||||
if (!browserWindow) return
|
||||
return browserWindow.setThumbnailClip(region)
|
||||
browserWindow.setThumbnailClip(region)
|
||||
}
|
||||
|
||||
|
||||
export const clearCache = async() => {
|
||||
if (!browserWindow) throw new Error('main window is undefined')
|
||||
return await browserWindow.webContents.session.clearCache()
|
||||
await browserWindow.webContents.session.clearCache()
|
||||
}
|
||||
|
||||
export const getCacheSize = async() => {
|
||||
|
||||
@@ -55,7 +55,10 @@ export default () => {
|
||||
showWindow()
|
||||
})
|
||||
mainOn<boolean>(WIN_MAIN_RENDERER_EVENT_NAME.close, ({ params: isForce }) => {
|
||||
if (isForce) return app.exit(0)
|
||||
if (isForce) {
|
||||
app.exit(0)
|
||||
return
|
||||
}
|
||||
global.lx.isTrafficLightClose = true
|
||||
closeWindow()
|
||||
})
|
||||
@@ -79,7 +82,7 @@ export default () => {
|
||||
|
||||
|
||||
mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.clear_cache, async() => {
|
||||
return await clearCache()
|
||||
await clearCache()
|
||||
})
|
||||
|
||||
mainHandle<number>(WIN_MAIN_RENDERER_EVENT_NAME.get_cache_size, async() => {
|
||||
|
||||
@@ -13,7 +13,7 @@ export default () => {
|
||||
await global.lx.worker.dbService.downloadInfoUpdate(list)
|
||||
})
|
||||
mainHandle<string[]>(WIN_MAIN_RENDERER_EVENT_NAME.download_list_remove, async({ params: ids }) => {
|
||||
return await global.lx.worker.dbService.downloadInfoRemove(ids)
|
||||
await global.lx.worker.dbService.downloadInfoRemove(ids)
|
||||
})
|
||||
mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.download_list_clear, async() => {
|
||||
await global.lx.worker.dbService.downloadInfoClear()
|
||||
|
||||
@@ -6,7 +6,10 @@ import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames'
|
||||
const handleInflate = async(data: Buffer) => {
|
||||
return await new Promise((resolve: (result: Buffer) => void, reject) => {
|
||||
inflate(data, (err, result) => {
|
||||
if (err) return reject(err)
|
||||
if (err) {
|
||||
reject(err)
|
||||
return
|
||||
}
|
||||
resolve(result)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -9,7 +9,8 @@ export default () => {
|
||||
mainHandle<LX.Sync.SyncServiceActions, any>(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()
|
||||
data.data.enable ? await startServer(parseInt(data.data.port)) : await stopServer()
|
||||
return
|
||||
case 'get_status':
|
||||
return getStatus()
|
||||
case 'generate_code':
|
||||
|
||||
@@ -22,7 +22,7 @@ export default () => {
|
||||
})
|
||||
|
||||
mainHandle<LX.UserApi.UserApiSetApiParams>(WIN_MAIN_RENDERER_EVENT_NAME.set_user_api, async({ params: apiId }) => {
|
||||
return await setApi(apiId)
|
||||
await setApi(apiId)
|
||||
})
|
||||
|
||||
mainHandle<LX.UserApi.UserApiInfo[]>(WIN_MAIN_RENDERER_EVENT_NAME.get_user_api_list, async() => {
|
||||
@@ -34,14 +34,14 @@ export default () => {
|
||||
})
|
||||
|
||||
mainHandle<LX.UserApi.UserApiSetAllowUpdateAlertParams>(WIN_MAIN_RENDERER_EVENT_NAME.user_api_set_allow_update_alert, async({ params: { id, enable } }) => {
|
||||
return setAllowShowUpdateAlert(id, enable)
|
||||
setAllowShowUpdateAlert(id, enable)
|
||||
})
|
||||
|
||||
mainHandle<LX.UserApi.UserApiRequestParams>(WIN_MAIN_RENDERER_EVENT_NAME.request_user_api, async({ params }) => {
|
||||
return await request(params)
|
||||
})
|
||||
mainHandle<LX.UserApi.UserApiRequestCancelParams>(WIN_MAIN_RENDERER_EVENT_NAME.request_user_api_cancel, async({ params: requestKey }) => {
|
||||
return cancelRequest(requestKey)
|
||||
cancelRequest(requestKey)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
2
src/main/types/db_service.d.ts
vendored
2
src/main/types/db_service.d.ts
vendored
@@ -35,7 +35,7 @@ declare namespace LX {
|
||||
interface UserListInfo {
|
||||
id: string
|
||||
name: string
|
||||
source?: LX.Source
|
||||
source?: LX.OnlineSource
|
||||
sourceListId?: string
|
||||
position: number
|
||||
locationUpdateTime: number | null
|
||||
|
||||
@@ -113,7 +113,7 @@ export const updateSetting = (setting?: Partial<LX.AppSetting>, isInit: boolean
|
||||
|
||||
let originSetting: LX.AppSetting
|
||||
if (isInit) {
|
||||
if (setting) setting = migrateSetting(setting)
|
||||
setting &&= migrateSetting(setting)
|
||||
originSetting = { ...defaultSetting }
|
||||
} else originSetting = global.lx.appSetting
|
||||
|
||||
@@ -196,7 +196,7 @@ export const openDevTools = (webContents: Electron.WebContents) => {
|
||||
|
||||
let userThemes: LX.Theme[]
|
||||
export const getAllThemes = () => {
|
||||
if (!userThemes) userThemes = getStore(STORE_NAMES.THEME).get('themes') as LX.Theme[] | null ?? []
|
||||
userThemes ??= getStore(STORE_NAMES.THEME).get('themes') as (LX.Theme[] | null) ?? []
|
||||
return {
|
||||
themes,
|
||||
userThemes,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import fs from 'node:fs'
|
||||
import { checkPath, joinPath } from '@common/utils/nodejs'
|
||||
import { log } from '@common/utils'
|
||||
import { toNewMusicInfo } from '@common/utils/tools'
|
||||
import { filterMusicList, toNewMusicInfo } from '@common/utils/tools'
|
||||
import { APP_EVENT_NAMES, STORE_NAMES } from '@common/constants'
|
||||
|
||||
/**
|
||||
@@ -28,14 +28,7 @@ interface OldUserListInfo {
|
||||
locationUpdateTime?: number
|
||||
list: any[]
|
||||
}
|
||||
const filterMusicList = <T extends LX.Music.MusicInfo>(list: T[]): T[] => {
|
||||
const ids: Set<string> = new Set()
|
||||
return list.filter(s => {
|
||||
if (!s.id || ids.has(s.id)) return false
|
||||
ids.add(s.id)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 迁移 v2.0.0 之前的 list data
|
||||
* @returns
|
||||
@@ -89,7 +82,9 @@ const migrateFile = async(name: string, targetName: string) => {
|
||||
if (!await checkPath(path) && await checkPath(oldPath)) {
|
||||
await fs.promises.copyFile(oldPath, path).catch(err => {
|
||||
log.error(err)
|
||||
}).catch(err => log.error(err))
|
||||
}).catch(err => {
|
||||
log.error(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +110,9 @@ export const migrateDataJson = async() => {
|
||||
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))
|
||||
await fs.promises.writeFile(path, JSON.stringify(newData)).catch(err => {
|
||||
log.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,9 +4,7 @@ import { join } from 'path'
|
||||
import fs from 'fs'
|
||||
import log from 'electron-log'
|
||||
|
||||
interface Stores {
|
||||
[key: string]: Store
|
||||
}
|
||||
type Stores = Record<string, Store>
|
||||
|
||||
const stores: Stores = {}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ export const insertMusicInfoList = (list: LX.DBService.MusicInfo[]) => {
|
||||
const musicInfoInsertStatement = createMusicInfoInsertStatement()
|
||||
const musicInfoOrderInsertStatement = createMusicInfoOrderInsertStatement()
|
||||
const db = getDB()
|
||||
return db.transaction((musics: LX.DBService.MusicInfo[]) => {
|
||||
db.transaction((musics: LX.DBService.MusicInfo[]) => {
|
||||
for (const music of musics) {
|
||||
musicInfoInsertStatement.run(music)
|
||||
musicInfoOrderInsertStatement.run({
|
||||
@@ -238,7 +238,7 @@ export const removeMusicInfos = (listId: string, ids: string[]) => {
|
||||
const musicInfoDeleteStatement = createMusicInfoDeleteStatement()
|
||||
const musicInfoOrderDeleteStatement = createMusicInfoOrderDeleteStatement()
|
||||
const db = getDB()
|
||||
return db.transaction((listId: string, ids: string[]) => {
|
||||
db.transaction((listId: string, ids: string[]) => {
|
||||
for (const id of ids) {
|
||||
musicInfoDeleteStatement.run({ listId, id })
|
||||
musicInfoOrderDeleteStatement.run({ listId, id })
|
||||
|
||||
@@ -39,7 +39,7 @@ const toDBMusicInfo = (musicInfos: LX.Music.MusicInfo[], listId: string, offset:
|
||||
* @returns
|
||||
*/
|
||||
export const getAllUserList = (): LX.List.UserListInfo[] => {
|
||||
if (userLists == null) userLists = queryAllUserList()
|
||||
userLists ??= queryAllUserList()
|
||||
|
||||
return userLists.map(list => {
|
||||
const { position, ...newList } = list
|
||||
@@ -53,7 +53,7 @@ export const getAllUserList = (): LX.List.UserListInfo[] => {
|
||||
* @param lists 列表信息
|
||||
*/
|
||||
export const createUserLists = (position: number, lists: LX.List.UserListInfo[]) => {
|
||||
if (userLists == null) userLists = queryAllUserList()
|
||||
userLists ??= queryAllUserList()
|
||||
if (position < 0 || position >= userLists.length) {
|
||||
const newLists: LX.DBService.UserListInfo[] = lists.map((list, index) => {
|
||||
return {
|
||||
@@ -96,7 +96,7 @@ export const setUserLists = (lists: LX.List.UserListInfo[]) => {
|
||||
*/
|
||||
export const removeUserLists = (ids: string[]) => {
|
||||
deleteUserLists(ids)
|
||||
if (userLists) userLists = queryAllUserList()
|
||||
userLists &&= queryAllUserList()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,7 +117,7 @@ export const updateUserLists = (lists: LX.List.UserListInfo[]) => {
|
||||
}
|
||||
}).filter(Boolean) as LX.DBService.UserListInfo[]
|
||||
updateUserListsFromDB(dbList)
|
||||
if (userLists) userLists = queryAllUserList()
|
||||
userLists &&= queryAllUserList()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,7 +126,7 @@ export const updateUserLists = (lists: LX.List.UserListInfo[]) => {
|
||||
* @param ids 列表ids
|
||||
*/
|
||||
export const updateUserListsPosition = (position: number, ids: string[]) => {
|
||||
if (userLists == null) userLists = queryAllUserList()
|
||||
userLists ??= queryAllUserList()
|
||||
|
||||
const newUserLists = [...userLists]
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
:style="lrcStyles" @wheel="handleWheel" @mousedown="handleLyricMouseDown" @touchstart="handleLyricTouchStart"
|
||||
>
|
||||
<div :class="$style.lyricSpace" />
|
||||
<div ref="dom_lyric_text" :class="[$style.lyricText]" />
|
||||
<div ref="dom_lyric_text" />
|
||||
<div :class="$style.lyricSpace" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -68,11 +68,15 @@ export default {
|
||||
// font-weight: bold;
|
||||
|
||||
:global {
|
||||
.font-lrc, .shadow {
|
||||
padding: 0.08em 0.14em;
|
||||
}
|
||||
.font-lrc {
|
||||
color: var(--color-lyric-unplay);
|
||||
}
|
||||
.shadow {
|
||||
color: transparent;
|
||||
// margin-left: -0.14em;
|
||||
}
|
||||
.line-content {
|
||||
line-height: 1.2;
|
||||
@@ -115,8 +119,16 @@ export default {
|
||||
-webkit-text-fill-color: transparent;
|
||||
-webkit-background-clip: text;
|
||||
background-size: 0 100%;
|
||||
padding-left: 1px;
|
||||
padding-right: 1px;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
}
|
||||
.line .shadow span {
|
||||
padding-left: 1px;
|
||||
padding-right: 1px;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
// &.line-mode {
|
||||
// .shadow {
|
||||
// text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.40);
|
||||
@@ -134,7 +146,7 @@ export default {
|
||||
// .stroke(2px, rgba(0, 0, 0, 0.025));
|
||||
transition: font-size @transition-slow;
|
||||
}
|
||||
.font-mode .line .shadow {
|
||||
.font-mode .line .shadow span {
|
||||
.stroke(1px, var(--color-lyric-shadow-font-mode));
|
||||
transition: font-size @transition-slow;
|
||||
// text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3), 1px 1px 1px rgba(0, 0, 0, 0.3);
|
||||
@@ -171,6 +183,9 @@ export default {
|
||||
.lyric-space {
|
||||
height: 80%;
|
||||
}
|
||||
// .lyric-text {
|
||||
|
||||
// }
|
||||
// .lrc-active {
|
||||
|
||||
// .lrc-line {
|
||||
|
||||
@@ -91,7 +91,7 @@ export default () => {
|
||||
|
||||
const handleMove = (x, y) => {
|
||||
if (isMsDown.value) {
|
||||
if (!isStopScroll) isStopScroll = true
|
||||
isStopScroll ||= true
|
||||
if (cancelScrollFn) {
|
||||
cancelScrollFn()
|
||||
cancelScrollFn = null
|
||||
@@ -164,6 +164,7 @@ export default () => {
|
||||
const scrollLine = (line, oldLine) => {
|
||||
if (line < 0 || !lyric.lines.length) return
|
||||
if (line == 0 && isSetedLines) return isSetedLines = false
|
||||
isSetedLines &&= false
|
||||
if (oldLine == null || line - oldLine != 1) return handleScrollLrc()
|
||||
|
||||
if (setting['desktopLyric.isDelayScroll']) {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
:style="lrcStyles" @wheel="handleWheel" @mousedown="handleLyricMouseDown" @touchstart="handleLyricTouchStart"
|
||||
>
|
||||
<div :class="$style.lyricSpace" />
|
||||
<div ref="dom_lyric_text" :class="[$style.lyricText]" />
|
||||
<div ref="dom_lyric_text" />
|
||||
<div :class="$style.lyricSpace" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -72,17 +72,20 @@ export default {
|
||||
}
|
||||
|
||||
:global {
|
||||
.font-lrc, .shadow {
|
||||
padding: 0.14em 0.07em;
|
||||
}
|
||||
.font-lrc {
|
||||
color: var(--color-lyric-unplay);
|
||||
}
|
||||
.shadow {
|
||||
color: transparent;
|
||||
margin-left: -0.14em;
|
||||
}
|
||||
.line-content {
|
||||
line-height: 1.2;
|
||||
margin: 0 var(--line-gap);
|
||||
overflow-wrap: break-word;
|
||||
letter-spacing: 5px;
|
||||
|
||||
.font-lrc {
|
||||
cursor: grab;
|
||||
@@ -93,6 +96,7 @@ export default {
|
||||
margin-right: var(--line-extended-gap);
|
||||
}
|
||||
&.line-mode {
|
||||
letter-spacing: 5px;
|
||||
.font-lrc {
|
||||
transition: @transition-slow;
|
||||
transition-property: font-size, color;
|
||||
@@ -120,8 +124,13 @@ export default {
|
||||
-webkit-text-fill-color: transparent;
|
||||
-webkit-background-clip: text;
|
||||
background-size: 0 100%;
|
||||
padding: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
&.font-mode .line .shadow span {
|
||||
padding: 2px;
|
||||
}
|
||||
}
|
||||
// .shadow {
|
||||
// .stroke2(rgba(0, 0, 0, 0.05));
|
||||
@@ -139,7 +148,7 @@ export default {
|
||||
// .stroke(2px, rgba(0, 0, 0, 0.025));
|
||||
transition: font-size @transition-slow;
|
||||
}
|
||||
.font-mode .line .shadow {
|
||||
.font-mode .line .shadow span {
|
||||
.stroke(1px, var(--color-lyric-shadow-font-mode));
|
||||
// .stroke(1px, rgba(0, 0, 0, 0.07));
|
||||
transition: font-size @transition-slow;
|
||||
@@ -178,6 +187,9 @@ export default {
|
||||
width: 80%;
|
||||
height: 100%;
|
||||
}
|
||||
// .lyric-text {
|
||||
|
||||
// }
|
||||
// .lrc-active {
|
||||
|
||||
// .lrc-line {
|
||||
|
||||
@@ -91,7 +91,7 @@ export default () => {
|
||||
|
||||
const handleMove = (x, y) => {
|
||||
if (isMsDown.value) {
|
||||
if (!isStopScroll) isStopScroll = true
|
||||
isStopScroll ||= true
|
||||
if (cancelScrollFn) {
|
||||
cancelScrollFn()
|
||||
cancelScrollFn = null
|
||||
@@ -164,6 +164,7 @@ export default () => {
|
||||
const scrollLine = (line, oldLine) => {
|
||||
if (line < 0) return
|
||||
if (line == 0 && isSetedLines) return isSetedLines = false
|
||||
isSetedLines &&= false
|
||||
if (oldLine == null || line - oldLine != 1) return handleScrollLrc()
|
||||
|
||||
if (setting['desktopLyric.isDelayScroll']) {
|
||||
|
||||
@@ -22,16 +22,26 @@ let mouseCheckTools: {
|
||||
let yDiff = Math.abs(this.y - this.preY)
|
||||
if (xDiff > 8) {
|
||||
if (this.x > this.preX) {
|
||||
if (this.x + xDiff * 1.25 > window.innerWidth - 16) return setShow()
|
||||
if (this.x + xDiff * 1.25 > window.innerWidth - 16) {
|
||||
setShow()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if (this.x - xDiff * 1.25 < 8) return setShow()
|
||||
if (this.x - xDiff * 1.25 < 8) {
|
||||
setShow()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if (yDiff > 8) {
|
||||
if (this.y > this.preY) {
|
||||
if (this.y + yDiff * 1.25 > window.innerHeight - 16) return setShow()
|
||||
if (this.y + yDiff * 1.25 > window.innerHeight - 16) {
|
||||
setShow()
|
||||
}
|
||||
} else {
|
||||
if (this.y - yDiff * 1.25 < 8) return setShow()
|
||||
if (this.y - yDiff * 1.25 < 8) {
|
||||
setShow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<component :is="containerEl" ref="dom_scrollContainer" :class="containerClass" tabindex="0" style="outline: none; height: 100%; overflow-y: auto; position: relative; display: block; contain: strict;">
|
||||
<component
|
||||
:is="containerEl"
|
||||
ref="dom_scrollContainer"
|
||||
:class="containerClass"
|
||||
tabindex="0"
|
||||
style="outline: none; height: 100%; overflow-y: auto; position: relative; display: block; contain: strict; box-sizing: border-box; will-change: transform;"
|
||||
>
|
||||
<component :is="contentEl" :class="contentClass" :style="contentStyle">
|
||||
<div v-for="item in views" :key="item.key" :style="item.style">
|
||||
<slot name="default" v-bind="{ item: item.item, index: item.index }" />
|
||||
@@ -19,6 +25,24 @@ import {
|
||||
onBeforeUnmount,
|
||||
} from 'vue'
|
||||
|
||||
/**
|
||||
* 生成防抖函数
|
||||
* @param {*} fn
|
||||
* @param {*} delay
|
||||
*/
|
||||
export const debounce = (fn, delay = 100) => {
|
||||
let timer = null
|
||||
let _args = null
|
||||
return function(...args) {
|
||||
_args = args
|
||||
if (timer) clearTimeout(timer)
|
||||
timer = setTimeout(() => {
|
||||
timer = null
|
||||
fn.apply(this, _args)
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
|
||||
const easeInOutQuad = (t, b, c, d) => {
|
||||
t /= d / 2
|
||||
if (t < 1) return (c / 2) * t * t + b
|
||||
@@ -105,12 +129,14 @@ export default {
|
||||
setup(props, { emit }) {
|
||||
const views = ref([])
|
||||
const dom_scrollContainer = ref(null)
|
||||
let isListScrolling = false
|
||||
const isListScrollingRef = ref(false)
|
||||
let startIndex = -1
|
||||
let endIndex = -1
|
||||
let scrollTop = -1
|
||||
let cachedList = []
|
||||
let cancelScroll = null
|
||||
let isScrolling = false
|
||||
let isAutoScrolling = false
|
||||
let scrollToValue = 0
|
||||
|
||||
const createList = (startIndex, endIndex) => {
|
||||
@@ -171,7 +197,14 @@ export default {
|
||||
scrollTop = currentScrollTop
|
||||
}
|
||||
|
||||
const setStopScrollStatus = debounce(() => {
|
||||
isListScrolling = false
|
||||
isListScrollingRef.value = false
|
||||
}, 200)
|
||||
const onScroll = event => {
|
||||
if (!isListScrolling) isListScrolling = isListScrollingRef.value = true
|
||||
setStopScrollStatus()
|
||||
|
||||
const currentScrollTop = dom_scrollContainer.value.scrollTop
|
||||
if (Math.abs(currentScrollTop - scrollTop) > props.itemHeight * 0.6) {
|
||||
updateView(currentScrollTop)
|
||||
@@ -189,15 +222,15 @@ export default {
|
||||
}
|
||||
}).then(() => {
|
||||
if (animate) {
|
||||
isScrolling = true
|
||||
isAutoScrolling = true
|
||||
scrollToValue = scrollTop
|
||||
cancelScroll = handleScroll(dom_scrollContainer.value, scrollTop, 300, () => {
|
||||
cancelScroll = null
|
||||
isScrolling = false
|
||||
isAutoScrolling = false
|
||||
onScrollEnd(true)
|
||||
}, () => {
|
||||
cancelScroll = null
|
||||
isScrolling = false
|
||||
isAutoScrolling = false
|
||||
onScrollEnd('canceled')
|
||||
})
|
||||
} else {
|
||||
@@ -217,17 +250,21 @@ export default {
|
||||
}
|
||||
|
||||
const getScrollTop = () => {
|
||||
return isScrolling ? scrollToValue : dom_scrollContainer.value.scrollTop
|
||||
return isAutoScrolling ? scrollToValue : dom_scrollContainer.value.scrollTop
|
||||
}
|
||||
|
||||
const handleResize = () => {
|
||||
window.setTimeout(updateView)
|
||||
}
|
||||
|
||||
const contentStyle = computed(() => ({
|
||||
display: 'block',
|
||||
height: props.list.length * props.itemHeight + 'px',
|
||||
}))
|
||||
const contentStyle = computed(() => {
|
||||
const style = {
|
||||
display: 'block',
|
||||
height: props.list.length * props.itemHeight + 'px',
|
||||
}
|
||||
if (isListScrollingRef.value) style['pointer-events'] = 'none'
|
||||
return style
|
||||
})
|
||||
|
||||
const handleReset = list => {
|
||||
cachedList = Array(list.length)
|
||||
|
||||
@@ -56,7 +56,7 @@ export default {
|
||||
}
|
||||
const handleMsMove = event => {
|
||||
if (!msEvent.isMsDown) return
|
||||
if (!dragging.value) dragging.value = true
|
||||
dragging.value ||= true
|
||||
|
||||
let progress = msEvent.msDownProgress + (event.clientX - msEvent.msDownX) / dom_progress.value.clientWidth
|
||||
if (progress > 1) progress = 1
|
||||
|
||||
@@ -65,6 +65,7 @@ import {
|
||||
} from '@renderer/store/player/action'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
import { togglePlay, playNext, playPrev } from '@renderer/core/player'
|
||||
import { LIST_IDS } from '@common/constants'
|
||||
|
||||
export default {
|
||||
name: 'CorePlayBar',
|
||||
@@ -98,7 +99,7 @@ export default {
|
||||
|
||||
const handleToMusicLocation = () => {
|
||||
const listId = playMusicInfo.listId
|
||||
if (!listId || listId == '__temp__' || listId == 'download' || !playMusicInfo.musicInfo) return
|
||||
if (!listId || listId == LIST_IDS.DOWNLOAD || !playMusicInfo.musicInfo) return
|
||||
if (playInfo.playIndex == -1) return
|
||||
router.push({
|
||||
path: '/list',
|
||||
|
||||
@@ -65,6 +65,7 @@ import {
|
||||
} from '@renderer/store/player/action'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
import { togglePlay, playNext, playPrev } from '@renderer/core/player'
|
||||
import { LIST_IDS } from '@common/constants'
|
||||
|
||||
export default {
|
||||
name: 'CorePlayBar',
|
||||
@@ -98,7 +99,7 @@ export default {
|
||||
|
||||
const handleToMusicLocation = () => {
|
||||
const listId = playMusicInfo.listId
|
||||
if (!listId || listId == '__temp__' || listId == 'download' || !playMusicInfo.musicInfo) return
|
||||
if (!listId || listId == LIST_IDS.DOWNLOAD || !playMusicInfo.musicInfo) return
|
||||
if (playInfo.playIndex == -1) return
|
||||
router.push({
|
||||
path: '/list',
|
||||
|
||||
@@ -67,6 +67,7 @@ import {
|
||||
} from '@renderer/store/player/action'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
import { togglePlay, playNext, playPrev } from '@renderer/core/player'
|
||||
import { LIST_IDS } from '@common/constants'
|
||||
|
||||
export default {
|
||||
name: 'CorePlayBar',
|
||||
@@ -100,7 +101,7 @@ export default {
|
||||
|
||||
const handleToMusicLocation = () => {
|
||||
const listId = playMusicInfo.listId
|
||||
if (!listId || listId == '__temp__' || listId == 'download' || !playMusicInfo.musicInfo) return
|
||||
if (!listId || listId == LIST_IDS.DOWNLOAD || !playMusicInfo.musicInfo) return
|
||||
if (playInfo.playIndex == -1) return
|
||||
router.push({
|
||||
path: '/list',
|
||||
|
||||
@@ -142,9 +142,11 @@ export default {
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
window.app_event.on('musicToggled', updateMusicInfo)
|
||||
window.app_event.on('lyricUpdated', updateMusicInfo)
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
window.app_event.off('musicToggled', updateMusicInfo)
|
||||
window.app_event.off('lyricUpdated', updateMusicInfo)
|
||||
})
|
||||
|
||||
|
||||
@@ -125,14 +125,14 @@ export default {
|
||||
let lxlyric = props.lyricInfo.lxlyric
|
||||
if (offsetTagRxp.test(lyric)) {
|
||||
lyric = lyric.replace(offsetTagAllRxp, `$1[offset:${offset}]`)
|
||||
if (tlyric) tlyric = tlyric.replace(offsetTagAllRxp, `$1[offset:${offset}]`)
|
||||
if (lxlyric) lxlyric = lxlyric.replace(offsetTagAllRxp, `$1[offset:${offset}]`)
|
||||
if (rlyric) rlyric = rlyric.replace(offsetTagAllRxp, `$1[offset:${offset}]`)
|
||||
tlyric &&= tlyric.replace(offsetTagAllRxp, `$1[offset:${offset}]`)
|
||||
lxlyric &&= lxlyric.replace(offsetTagAllRxp, `$1[offset:${offset}]`)
|
||||
rlyric &&= rlyric.replace(offsetTagAllRxp, `$1[offset:${offset}]`)
|
||||
} else {
|
||||
lyric = `[offset:${offset}]\n` + lyric
|
||||
if (tlyric) tlyric = `[offset:${offset}]\n` + tlyric
|
||||
if (lxlyric) lxlyric = `[offset:${offset}]\n` + lxlyric
|
||||
if (rlyric) rlyric = `[offset:${offset}]\n` + rlyric
|
||||
lyric &&= `[offset:${offset}]\n` + lyric
|
||||
tlyric &&= `[offset:${offset}]\n` + tlyric
|
||||
lxlyric &&= `[offset:${offset}]\n` + lxlyric
|
||||
rlyric &&= `[offset:${offset}]\n` + rlyric
|
||||
}
|
||||
|
||||
const musicInfo = 'progress' in props.lyricInfo.musicInfo ? props.lyricInfo.musicInfo.meta.musicInfo : props.lyricInfo.musicInfo
|
||||
|
||||
@@ -28,7 +28,7 @@ export default {
|
||||
const router = useRouter()
|
||||
|
||||
watch(() => route.name, (newValue, oldValue) => {
|
||||
if (oldValue == 'Search') {
|
||||
if (oldValue == 'Search' && newValue != 'SongListDetail') {
|
||||
setTimeout(() => {
|
||||
if (appSetting['odc.isAutoClearSearchInput'] && searchText.value) searchText.value = ''
|
||||
if (appSetting['odc.isAutoClearSearchList']) setSearchText('')
|
||||
@@ -64,7 +64,7 @@ export default {
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
if (visibleList.value) visibleList.value = false
|
||||
visibleList.value &&= false
|
||||
if (!searchText.value && route.path != '/search') {
|
||||
setSearchText('')
|
||||
return
|
||||
@@ -83,13 +83,13 @@ export default {
|
||||
switch (action) {
|
||||
case 'focus':
|
||||
isFocused = true
|
||||
if (!visibleList.value) visibleList.value = true
|
||||
visibleList.value ||= true
|
||||
if (searchText.value) handleTipSearch()
|
||||
break
|
||||
case 'blur':
|
||||
isFocused = false
|
||||
setTimeout(() => {
|
||||
if (visibleList.value) visibleList.value = false
|
||||
visibleList.value &&= false
|
||||
}, 50)
|
||||
break
|
||||
case 'submit':
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
</svg>
|
||||
</button>
|
||||
<button v-if="pauseBtn" type="button" :aria-label="$t('list__pause')" @contextmenu.capture.stop @click.stop="handleClick('pause')">
|
||||
<svg v-once version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 277.338 277.338" space="preserve">
|
||||
<svg v-once version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 1024 1024" space="preserve">
|
||||
<use xlink:href="#icon-pause" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
@@ -12,16 +12,16 @@ const useKeyEvent = ({ handleSelectAllData }: {
|
||||
}
|
||||
|
||||
const handle_key_shift_down = () => {
|
||||
if (!keyEvent.isShiftDown) keyEvent.isShiftDown = true
|
||||
keyEvent.isShiftDown ||= true
|
||||
}
|
||||
const handle_key_shift_up = () => {
|
||||
if (keyEvent.isShiftDown) keyEvent.isShiftDown = false
|
||||
keyEvent.isShiftDown &&= false
|
||||
}
|
||||
const handle_key_mod_down = () => {
|
||||
if (!keyEvent.isModDown) keyEvent.isModDown = true
|
||||
keyEvent.isModDown ||= true
|
||||
}
|
||||
const handle_key_mod_up = () => {
|
||||
if (keyEvent.isModDown) keyEvent.isModDown = false
|
||||
keyEvent.isModDown &&= false
|
||||
}
|
||||
const handle_key_mod_a_down = ({ event }: LX.KeyDownEevent) => {
|
||||
if (!event || (event.target as HTMLElement).tagName == 'INPUT') return
|
||||
|
||||
@@ -5,6 +5,7 @@ import { addTempPlayList } from '@renderer/store/player/action'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
import { Ref } from '@common/utils/vueTools'
|
||||
import { playList } from '@renderer/core/player'
|
||||
import { LIST_IDS } from '@common/constants'
|
||||
|
||||
export default ({ selectedList, props, removeAllSelect, emit }: {
|
||||
selectedList: Ref<LX.Music.MusicInfoOnline[]>
|
||||
@@ -34,10 +35,10 @@ export default ({ selectedList, props, removeAllSelect, emit }: {
|
||||
|
||||
const handlePlayMusicLater = (index: number, single: boolean) => {
|
||||
if (selectedList.value.length && !single) {
|
||||
addTempPlayList(selectedList.value.map(s => ({ listId: '__temp__', musicInfo: s })))
|
||||
addTempPlayList(selectedList.value.map(s => ({ listId: LIST_IDS.PLAY_LATER, musicInfo: s })))
|
||||
removeAllSelect()
|
||||
} else {
|
||||
addTempPlayList([{ listId: '__temp__', musicInfo: props.list[index] }])
|
||||
addTempPlayList([{ listId: LIST_IDS.PLAY_LATER, musicInfo: props.list[index] }])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import { requestMsg } from '@renderer/utils/message'
|
||||
import { getRandom } from '@renderer/utils/index'
|
||||
// import { checkMusicFileAvailable } from '@renderer/utils/music'
|
||||
|
||||
let isGettingUrl = false
|
||||
let gettingUrlId = ''
|
||||
const createDelayNextTimeout = (delay: number) => {
|
||||
let timeout: NodeJS.Timeout | null
|
||||
const clearDelayNextTimeout = () => {
|
||||
@@ -86,7 +86,7 @@ const getMusicPlayUrl = async(musicInfo: LX.Music.MusicInfo | LX.Download.ListIt
|
||||
|
||||
export const setMusicUrl = (musicInfo: LX.Music.MusicInfo | LX.Download.ListItem, isRefresh?: boolean) => {
|
||||
if (appSetting['player.autoSkipOnError']) addLoadTimeout()
|
||||
isGettingUrl = true
|
||||
gettingUrlId = musicInfo.id
|
||||
void getMusicPlayUrl(musicInfo, isRefresh).then((url) => {
|
||||
if (!url) return
|
||||
setResource(url)
|
||||
@@ -96,8 +96,10 @@ export const setMusicUrl = (musicInfo: LX.Music.MusicInfo | LX.Download.ListItem
|
||||
window.app_event.error()
|
||||
if (appSetting['player.autoSkipOnError']) addDelayNextTimeout()
|
||||
}).finally(() => {
|
||||
clearLoadTimeout()
|
||||
if (musicInfo === playMusicInfo.musicInfo) isGettingUrl = false
|
||||
if (musicInfo === playMusicInfo.musicInfo) {
|
||||
gettingUrlId = ''
|
||||
clearLoadTimeout()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -111,7 +113,7 @@ const handleRestorePlay = async(restorePlayInfo: LX.Player.SavedPlayInfo) => {
|
||||
})
|
||||
|
||||
|
||||
void getPicPath({ musicInfo, listId: playMusicInfo.isTempPlay ? null : playMusicInfo.listId }).then((url: string) => {
|
||||
void getPicPath({ musicInfo, listId: playMusicInfo.listId }).then((url: string) => {
|
||||
if (musicInfo.id != playMusicInfo.musicInfo?.id) return
|
||||
setMusicInfo({ pic: url })
|
||||
window.app_event.picUpdated()
|
||||
@@ -126,12 +128,11 @@ const handleRestorePlay = async(restorePlayInfo: LX.Player.SavedPlayInfo) => {
|
||||
rlrc: lyricInfo.rlyric,
|
||||
rawlrc: lyricInfo.rawlrcInfo.lyric,
|
||||
})
|
||||
window.app_event.lyricUpdated()
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
if (musicInfo.id != playMusicInfo.musicInfo?.id) return
|
||||
setAllStatus(window.i18n.t('lyric__load_error'))
|
||||
}).finally(() => {
|
||||
window.app_event.lyricUpdated()
|
||||
})
|
||||
|
||||
if (appSetting['player.togglePlayMethod'] == 'random') addPlayedList({ ...playMusicInfo as LX.Player.PlayMusicInfo })
|
||||
@@ -140,14 +141,17 @@ const handleRestorePlay = async(restorePlayInfo: LX.Player.SavedPlayInfo) => {
|
||||
|
||||
// 处理音乐播放
|
||||
const handlePlay = () => {
|
||||
if (window.lx.isPlayedStop) window.lx.isPlayedStop = false
|
||||
if (isGettingUrl) isGettingUrl = false
|
||||
window.lx.isPlayedStop &&= false
|
||||
|
||||
if (window.lx.restorePlayInfo) {
|
||||
void handleRestorePlay(window.lx.restorePlayInfo)
|
||||
window.lx.restorePlayInfo = null
|
||||
return
|
||||
}
|
||||
const musicInfo = playMusicInfo.musicInfo
|
||||
|
||||
if (!musicInfo || gettingUrlId == musicInfo.id) return
|
||||
gettingUrlId &&= ''
|
||||
|
||||
setStop()
|
||||
window.app_event.pause()
|
||||
@@ -155,15 +159,12 @@ const handlePlay = () => {
|
||||
clearDelayNextTimeout()
|
||||
clearLoadTimeout()
|
||||
|
||||
const musicInfo = playMusicInfo.musicInfo
|
||||
|
||||
if (!musicInfo) return
|
||||
if (appSetting['player.togglePlayMethod'] == 'random' && playMusicInfo.listId) addPlayedList({ ...(playMusicInfo as LX.Player.PlayMusicInfo) })
|
||||
|
||||
if (appSetting['player.togglePlayMethod'] == 'random' && !playMusicInfo.isTempPlay) addPlayedList({ ...(playMusicInfo as LX.Player.PlayMusicInfo) })
|
||||
setMusicUrl(musicInfo)
|
||||
|
||||
void setMusicUrl(musicInfo)
|
||||
|
||||
void getPicPath({ musicInfo, listId: playMusicInfo.isTempPlay ? null : playMusicInfo.listId }).then((url: string) => {
|
||||
void getPicPath({ musicInfo, listId: playMusicInfo.listId }).then((url: string) => {
|
||||
if (musicInfo.id != playMusicInfo.musicInfo?.id) return
|
||||
setMusicInfo({ pic: url })
|
||||
window.app_event.picUpdated()
|
||||
@@ -178,12 +179,11 @@ const handlePlay = () => {
|
||||
rlrc: lyricInfo.rlyric,
|
||||
rawlrc: lyricInfo.rawlrcInfo.lyric,
|
||||
})
|
||||
window.app_event.lyricUpdated()
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
if (musicInfo.id != playMusicInfo.musicInfo?.id) return
|
||||
setAllStatus(window.i18n.t('lyric__load_error'))
|
||||
}).finally(() => {
|
||||
window.app_event.lyricUpdated()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -221,11 +221,17 @@ export const playNext = async(isAutoToggle = false): Promise<void> => {
|
||||
return
|
||||
}
|
||||
|
||||
if (playMusicInfo.musicInfo == null) return handleToggleStop()
|
||||
if (playMusicInfo.musicInfo == null) {
|
||||
handleToggleStop()
|
||||
return
|
||||
}
|
||||
|
||||
// console.log(playInfo.playerListId)
|
||||
const currentListId = playInfo.playerListId
|
||||
if (!currentListId) return handleToggleStop()
|
||||
if (!currentListId) {
|
||||
handleToggleStop()
|
||||
return
|
||||
}
|
||||
const currentList = getList(currentListId)
|
||||
|
||||
if (playedList.length) { // 移除已播放列表内不存在原列表的歌曲
|
||||
@@ -263,7 +269,10 @@ export const playNext = async(isAutoToggle = false): Promise<void> => {
|
||||
playerMusicInfo: currentList[playInfo.playerPlayIndex],
|
||||
})
|
||||
|
||||
if (!filteredList.length) return handleToggleStop()
|
||||
if (!filteredList.length) {
|
||||
handleToggleStop()
|
||||
return
|
||||
}
|
||||
// let currentIndex: number = filteredList.indexOf(currentList[playInfo.playerPlayIndex])
|
||||
if (playerIndex == -1 && filteredList.length) playerIndex = 0
|
||||
let nextIndex = playerIndex
|
||||
@@ -309,10 +318,16 @@ export const playNext = async(isAutoToggle = false): Promise<void> => {
|
||||
* 上一曲
|
||||
*/
|
||||
export const playPrev = async(isAutoToggle = false): Promise<void> => {
|
||||
if (playMusicInfo.musicInfo == null) return handleToggleStop()
|
||||
if (playMusicInfo.musicInfo == null) {
|
||||
handleToggleStop()
|
||||
return
|
||||
}
|
||||
|
||||
const currentListId = playInfo.playerListId
|
||||
if (!currentListId) return handleToggleStop()
|
||||
if (!currentListId) {
|
||||
handleToggleStop()
|
||||
return
|
||||
}
|
||||
const currentList = getList(currentListId)
|
||||
|
||||
if (playedList.length) {
|
||||
@@ -350,7 +365,10 @@ export const playPrev = async(isAutoToggle = false): Promise<void> => {
|
||||
playedList,
|
||||
playerMusicInfo: currentList[playInfo.playerPlayIndex],
|
||||
})
|
||||
if (!filteredList.length) return handleToggleStop()
|
||||
if (!filteredList.length) {
|
||||
handleToggleStop()
|
||||
return
|
||||
}
|
||||
|
||||
// let currentIndex = filteredList.indexOf(currentList[playInfo.playerPlayIndex])
|
||||
if (playerIndex == -1 && filteredList.length) playerIndex = 0
|
||||
@@ -398,7 +416,7 @@ export const playPrev = async(isAutoToggle = false): Promise<void> => {
|
||||
export const play = () => {
|
||||
if (playMusicInfo.musicInfo == null) return
|
||||
if (isEmpty()) {
|
||||
void setMusicUrl(playMusicInfo.musicInfo)
|
||||
if (playMusicInfo.musicInfo.id != gettingUrlId) setMusicUrl(playMusicInfo.musicInfo)
|
||||
return
|
||||
}
|
||||
setPlay()
|
||||
@@ -425,7 +443,7 @@ export const stop = () => {
|
||||
* 播放、暂停播放切换
|
||||
*/
|
||||
export const togglePlay = () => {
|
||||
if (window.lx.isPlayedStop) window.lx.isPlayedStop = false
|
||||
window.lx.isPlayedStop &&= false
|
||||
if (isPlay.value) {
|
||||
pause()
|
||||
} else {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ref, computed, ComputedRef } from '@common/utils/vueTools'
|
||||
import { isPlay } from '@renderer/store/player/state'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
// import { interval, intervalCancel } from '@renderer/utils/ipc'
|
||||
import { stop } from './action'
|
||||
import { pause } from './action'
|
||||
|
||||
const time = ref(-1)
|
||||
|
||||
@@ -25,7 +25,7 @@ const timeoutTools: {
|
||||
exit() {
|
||||
window.lx.isPlayedStop = true
|
||||
if (!appSetting['player.waitPlayEndStop'] && isPlay.value) {
|
||||
stop()
|
||||
pause()
|
||||
}
|
||||
},
|
||||
clearTimeout() {
|
||||
@@ -67,12 +67,12 @@ const timeoutTools: {
|
||||
}
|
||||
|
||||
export const startTimeoutStop = (time: number) => {
|
||||
if (window.lx.isPlayedStop) window.lx.isPlayedStop = false
|
||||
window.lx.isPlayedStop &&= false
|
||||
timeoutTools.start(time)
|
||||
}
|
||||
export const stopTimeoutStop = () => {
|
||||
console.warn('stopTimeoutStop')
|
||||
if (window.lx.isPlayedStop) window.lx.isPlayedStop = false
|
||||
window.lx.isPlayedStop &&= false
|
||||
timeoutTools.clearTimeout()
|
||||
}
|
||||
|
||||
|
||||
@@ -33,12 +33,16 @@ export default () => {
|
||||
return async() => {
|
||||
await Promise.all([
|
||||
initUserApi(), // 自定义API
|
||||
]).catch(err => log.error(err))
|
||||
]).catch(err => {
|
||||
log.error(err)
|
||||
})
|
||||
void music.init() // 初始化音乐sdk
|
||||
unregister = registerAction((ids) => {
|
||||
window.app_event.myListUpdate(ids)
|
||||
})
|
||||
window.lxData.userLists = await getUserLists() // 获取用户列表
|
||||
await initPrevPlayInfo().catch(err => log.error(err)) // 初始化上次的歌曲播放信息
|
||||
await initPrevPlayInfo().catch(err => {
|
||||
log.error(err)
|
||||
}) // 初始化上次的歌曲播放信息
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { dataVerify, qualityFilter, sources } from './utils'
|
||||
import { focusWindow } from '@renderer/utils/ipc'
|
||||
import { playNext } from '@renderer/core/player/action'
|
||||
import { toNewMusicInfo } from '@common/utils/tools'
|
||||
import { LIST_IDS } from '@common/constants'
|
||||
|
||||
const useSearchMusic = () => {
|
||||
const router = useRouter()
|
||||
@@ -153,7 +154,7 @@ const usePlayMusic = () => {
|
||||
musicInfo = toNewMusicInfo(musicInfo)
|
||||
markRaw(musicInfo)
|
||||
const isPlaying = !!playMusicInfo.musicInfo
|
||||
addTempPlayList([{ listId: '__temp__', musicInfo, isTop: true }])
|
||||
addTempPlayList([{ listId: LIST_IDS.PLAY_LATER, musicInfo, isTop: true }])
|
||||
if (isPlaying) playNext()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ const getDevices = async() => {
|
||||
|
||||
export default () => {
|
||||
let prevDeviceLabel: string | null = null
|
||||
let prevDeviceId = ''
|
||||
|
||||
const setMediaDevice = async(mediaDeviceId: string) => {
|
||||
let label = prevDeviceLabel
|
||||
@@ -30,9 +31,15 @@ export default () => {
|
||||
|
||||
prevDeviceLabel = label
|
||||
// console.log(device)
|
||||
setMediaDeviceId(mediaDeviceId).catch((err: any) => {
|
||||
setMediaDeviceId(mediaDeviceId).then(() => {
|
||||
prevDeviceId = mediaDeviceId
|
||||
saveMediaDeviceId(mediaDeviceId)
|
||||
}).catch((err: any) => {
|
||||
console.log(err)
|
||||
saveMediaDeviceId('default')
|
||||
setMediaDeviceId('default').finally(() => {
|
||||
prevDeviceId = 'default'
|
||||
saveMediaDeviceId('default')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -53,20 +60,20 @@ export default () => {
|
||||
let mediaDeviceId = appSetting['player.mediaDeviceId']
|
||||
const devices = await getDevices()
|
||||
let device = devices.find(device => device.deviceId === mediaDeviceId)
|
||||
if (!device) device = devices.find(device => device.deviceId === 'default')
|
||||
device ??= devices.find(device => device.deviceId === 'default')
|
||||
// @ts-expect-error
|
||||
if (!device) device = { label: '', deviceId: '' }
|
||||
device ??= { label: '', deviceId: '' }
|
||||
|
||||
// @ts-expect-error
|
||||
handleDeviceChangeStopPlay(device, mediaDeviceId)
|
||||
|
||||
setMediaDeviceId(device!.deviceId).catch((err: any) => {
|
||||
console.log(err)
|
||||
saveMediaDeviceId('default')
|
||||
})
|
||||
void setMediaDevice(device!.deviceId)
|
||||
}
|
||||
|
||||
watch(() => appSetting['player.mediaDeviceId'], setMediaDevice)
|
||||
watch(() => appSetting['player.mediaDeviceId'], (id) => {
|
||||
if (prevDeviceId == id) return
|
||||
void setMediaDevice(id)
|
||||
})
|
||||
|
||||
|
||||
void setMediaDevice(appSetting['player.mediaDeviceId'])
|
||||
|
||||
@@ -49,7 +49,10 @@ export default () => {
|
||||
const addDelayNextTimeout = () => {
|
||||
clearDelayNextTimeout()
|
||||
delayNextTimeout = setTimeout(() => {
|
||||
if (window.lx.isPlayedStop) return setAllStatus('')
|
||||
if (window.lx.isPlayedStop) {
|
||||
setAllStatus('')
|
||||
return
|
||||
}
|
||||
void playNext(true)
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export default () => {
|
||||
if (window.lx.isPlayedStop) return
|
||||
const currentTime = getCurrentTime()
|
||||
|
||||
if (!mediaBuffer.playTime) mediaBuffer.playTime = currentTime
|
||||
mediaBuffer.playTime ||= currentTime
|
||||
let skipTime = currentTime + getRandom(3, 6)
|
||||
if (skipTime > playProgress.maxPlayTime) skipTime = (playProgress.maxPlayTime - currentTime) / 2
|
||||
if (skipTime - mediaBuffer.playTime < 1 || playProgress.maxPlayTime - skipTime < 1) {
|
||||
@@ -61,7 +61,7 @@ export default () => {
|
||||
const setProgress = (time: number, maxTime?: number) => {
|
||||
if (!musicInfo.id) return
|
||||
console.log('setProgress', time, maxTime)
|
||||
restorePlayTime = time
|
||||
if (time > 0) restorePlayTime = time
|
||||
if (mediaBuffer.playTime) {
|
||||
clearBufferTimeout()
|
||||
mediaBuffer.playTime = time
|
||||
@@ -97,7 +97,7 @@ export default () => {
|
||||
}
|
||||
|
||||
const handleError = () => {
|
||||
if (!restorePlayTime) restorePlayTime = getCurrentTime() // 记录出错的播放时间
|
||||
restorePlayTime ||= getCurrentTime() // 记录出错的播放时间
|
||||
console.log('handleError')
|
||||
prevProgressStatus = 'error'
|
||||
handleSetTaskBarState(playProgress.progress, prevProgressStatus)
|
||||
@@ -106,14 +106,8 @@ export default () => {
|
||||
const handleLoadeddata = () => {
|
||||
setMaxplayTime(getDuration())
|
||||
|
||||
console.log('handleLoadeddata', restorePlayTime)
|
||||
if (restorePlayTime) {
|
||||
setCurrentTime(restorePlayTime)
|
||||
restorePlayTime = 0
|
||||
}
|
||||
|
||||
if (playMusicInfo.musicInfo && 'source' in playMusicInfo.musicInfo && !playMusicInfo.musicInfo.interval) {
|
||||
console.log(formatPlayTime2(playProgress.maxPlayTime))
|
||||
// console.log(formatPlayTime2(playProgress.maxPlayTime))
|
||||
|
||||
if (playMusicInfo.listId) {
|
||||
void updateListMusics([{
|
||||
@@ -128,12 +122,15 @@ export default () => {
|
||||
}
|
||||
|
||||
const handleCanplay = () => {
|
||||
console.log('handleCanplay', mediaBuffer.playTime)
|
||||
console.log('handleCanplay', mediaBuffer.playTime, restorePlayTime)
|
||||
clearBufferTimeout()
|
||||
if (mediaBuffer.playTime) {
|
||||
let playTime = mediaBuffer.playTime
|
||||
mediaBuffer.playTime = 0
|
||||
setCurrentTime(playTime)
|
||||
} else if (restorePlayTime) {
|
||||
setCurrentTime(restorePlayTime)
|
||||
restorePlayTime = 0
|
||||
}
|
||||
}
|
||||
const handleWating = () => {
|
||||
|
||||
@@ -32,18 +32,18 @@ export default () => {
|
||||
}
|
||||
|
||||
const handlePlay = () => {
|
||||
if (buttons.empty) buttons.empty = false
|
||||
buttons.empty &&= false
|
||||
buttons.play = true
|
||||
setButtons()
|
||||
}
|
||||
const handlePause = () => {
|
||||
if (buttons.empty) buttons.empty = false
|
||||
buttons.empty &&= false
|
||||
buttons.play = false
|
||||
setButtons()
|
||||
}
|
||||
const handleStop = () => {
|
||||
if (playMusicInfo.musicInfo != null) return
|
||||
if (buttons.collect) buttons.collect = false
|
||||
buttons.collect &&= false
|
||||
buttons.empty = true
|
||||
setButtons()
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
setAllStatus,
|
||||
addPlayedList,
|
||||
clearPlayedList,
|
||||
resetPlayerMusicInfo,
|
||||
// resetPlayerMusicInfo,
|
||||
} from '@renderer/store/player/action'
|
||||
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
@@ -68,9 +68,12 @@ export default () => {
|
||||
}
|
||||
const handleEnded = () => {
|
||||
// setTimeout(() => {
|
||||
if (window.lx.isPlayedStop) return setAllStatus(t('player__end'))
|
||||
resetPlayerMusicInfo()
|
||||
window.app_event.stop()
|
||||
if (window.lx.isPlayedStop) {
|
||||
setAllStatus(t('player__end'))
|
||||
return
|
||||
}
|
||||
// resetPlayerMusicInfo()
|
||||
// window.app_event.stop()
|
||||
setAllStatus(t('player__end'))
|
||||
void playNext(true)
|
||||
// })
|
||||
|
||||
@@ -9,7 +9,7 @@ const changedListIds = new Set<string | null>()
|
||||
|
||||
export default () => {
|
||||
const throttleListChange = throttle(() => {
|
||||
const isSkip = !changedListIds.has(playInfo.playerListId) && !changedListIds.has(playMusicInfo.listId)
|
||||
const isSkip = playMusicInfo.listId && !changedListIds.has(playInfo.playerListId) && !changedListIds.has(playMusicInfo.listId)
|
||||
changedListIds.clear()
|
||||
if (isSkip) return
|
||||
|
||||
|
||||
@@ -49,4 +49,6 @@ export const unregisterKeyEvent = () => {
|
||||
keyBind.unbindKey()
|
||||
}
|
||||
|
||||
export const clearDownKeys = () => keyBind.clearDownKeys()
|
||||
export const clearDownKeys = () => {
|
||||
keyBind.clearDownKeys()
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ void getSetting().then(setting => {
|
||||
break
|
||||
}
|
||||
}
|
||||
if (langId == null) langId = 'en-us'
|
||||
langId ??= 'en-us'
|
||||
}
|
||||
setting['common.langId'] = langId
|
||||
void updateSetting({ 'common.langId': langId })
|
||||
|
||||
@@ -56,7 +56,7 @@ export const createUserList = async({ name, id = `userlist_${Date.now()}`, list
|
||||
name?: string
|
||||
id?: string
|
||||
list?: LX.Music.MusicInfo[]
|
||||
source?: LX.Source
|
||||
source?: LX.OnlineSource
|
||||
sourceListId?: string
|
||||
position?: number
|
||||
}) => {
|
||||
|
||||
@@ -147,7 +147,7 @@ export const listDataOverwrite = ({ defaultList, loveList, userList, tempList }:
|
||||
export const userListCreate = ({ name, id, source, sourceListId, position, locationUpdateTime }: {
|
||||
name: string
|
||||
id: string
|
||||
source?: LX.Source
|
||||
source?: LX.OnlineSource
|
||||
sourceListId?: string
|
||||
position: number
|
||||
locationUpdateTime: number | null
|
||||
|
||||
@@ -191,12 +191,12 @@ export const registerListAction = (appSetting: LX.AppSetting, onListChanged: (li
|
||||
userListsUpdatePosition(position, ids)
|
||||
}
|
||||
const list_music_add = ({ params: { id, musicInfos, addMusicLocationType } }: LX.IpcRendererEventParams<LX.List.ListActionMusicAdd>) => {
|
||||
if (!addMusicLocationType) addMusicLocationType = appSetting['list.addMusicLocationType']
|
||||
addMusicLocationType ??= appSetting['list.addMusicLocationType']
|
||||
const updatedListIds = listMusicAdd(id, musicInfos, addMusicLocationType)
|
||||
if (updatedListIds.length) onListChanged(updatedListIds)
|
||||
}
|
||||
const list_music_move = ({ params: { fromId, toId, musicInfos, addMusicLocationType } }: LX.IpcRendererEventParams<LX.List.ListActionMusicMove>) => {
|
||||
if (!addMusicLocationType) addMusicLocationType = appSetting['list.addMusicLocationType']
|
||||
addMusicLocationType ??= appSetting['list.addMusicLocationType']
|
||||
const updatedListIds = listMusicMove(fromId, toId, musicInfos, addMusicLocationType)
|
||||
if (updatedListIds.length) onListChanged(updatedListIds)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ const fetchList = async(id: string, source: LX.OnlineSource, sourceListId: strin
|
||||
export default async(targetListInfo: LX.List.UserListInfo) => {
|
||||
// console.log(targetListInfo)
|
||||
if (!targetListInfo.source || !targetListInfo.sourceListId) return
|
||||
const list = await fetchList(targetListInfo.id, targetListInfo.source as LX.OnlineSource, targetListInfo.sourceListId)
|
||||
const list = await fetchList(targetListInfo.id, targetListInfo.source, targetListInfo.sourceListId)
|
||||
// console.log(list)
|
||||
void overwriteListMusics({ listId: targetListInfo.id, musicInfos: list })
|
||||
const now = Date.now()
|
||||
|
||||
@@ -224,7 +224,7 @@ export const clearPlayedList = () => {
|
||||
* @param list 歌曲列表
|
||||
*/
|
||||
export const addTempPlayList = (list: LX.Player.TempPlayListItem[]) => {
|
||||
const topList: Array<{ listId: string, musicInfo: LX.Music.MusicInfo | LX.Download.ListItem }> = []
|
||||
const topList: Array<Omit<LX.Player.TempPlayListItem, 'top'>> = []
|
||||
const bottomList = list.filter(({ isTop, ...musicInfo }) => {
|
||||
if (isTop) {
|
||||
topList.push(musicInfo)
|
||||
|
||||
@@ -22,7 +22,10 @@ export const buildBgUrl = (originUrl: string, dataPath: string): string => {
|
||||
}
|
||||
|
||||
export const getThemes = (callback: (themeInfo: LX.ThemeInfo) => void) => {
|
||||
if (themeInfo.themes.length) return callback(themeInfo)
|
||||
if (themeInfo.themes.length) {
|
||||
callback(themeInfo)
|
||||
return
|
||||
}
|
||||
void getTheme().then(info => {
|
||||
themeInfo.themes = markRaw(info.themes)
|
||||
themeInfo.userThemes = shallowReactive(info.userThemes)
|
||||
|
||||
4
src/renderer/types/player.d.ts
vendored
4
src/renderer/types/player.d.ts
vendored
@@ -9,7 +9,7 @@ declare namespace LX {
|
||||
/**
|
||||
* 当前播放歌曲的列表 id
|
||||
*/
|
||||
listId: string
|
||||
listId: string | null
|
||||
/**
|
||||
* 是否属于 “稍后播放”
|
||||
*/
|
||||
@@ -35,7 +35,7 @@ declare namespace LX {
|
||||
/**
|
||||
* 播放列表id
|
||||
*/
|
||||
listId: '__temp__'
|
||||
listId: string | null
|
||||
/**
|
||||
* 歌曲信息
|
||||
*/
|
||||
|
||||
@@ -10,12 +10,15 @@ export default () => {
|
||||
switch (type) {
|
||||
case 'defautlList':
|
||||
case 'playList':
|
||||
case 'playList_v2':
|
||||
message = t('list_import_tip__playlist')
|
||||
break
|
||||
case 'setting':
|
||||
case 'setting_v2':
|
||||
message = t('list_import_tip__setting')
|
||||
break
|
||||
case 'allData':
|
||||
case 'allData_v2':
|
||||
message = t('list_import_tip__alldata')
|
||||
break
|
||||
case 'playListPart':
|
||||
|
||||
@@ -21,7 +21,7 @@ export default (name: string) => {
|
||||
}
|
||||
|
||||
const handle_key_up = () => {
|
||||
if (keyDown.value) keyDown.value = false
|
||||
keyDown.value &&= false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -128,7 +128,7 @@ export default ({ isPlay, lyric, playProgress, isShowLyricProgressSetting, offse
|
||||
}
|
||||
const handleMove = (y) => {
|
||||
if (isMsDown.value) {
|
||||
if (!isStopScroll.value) isStopScroll.value = true
|
||||
isStopScroll.value ||= true
|
||||
if (cancelScrollFn) {
|
||||
cancelScrollFn()
|
||||
cancelScrollFn = null
|
||||
@@ -150,7 +150,7 @@ export default ({ isPlay, lyric, playProgress, isShowLyricProgressSetting, offse
|
||||
|
||||
const handleWheel = (event) => {
|
||||
console.log(event.deltaY)
|
||||
if (!isStopScroll.value) isStopScroll.value = true
|
||||
isStopScroll.value ||= true
|
||||
if (cancelScrollFn) {
|
||||
cancelScrollFn()
|
||||
cancelScrollFn = null
|
||||
@@ -197,6 +197,7 @@ export default ({ isPlay, lyric, playProgress, isShowLyricProgressSetting, offse
|
||||
const scrollLine = (line, oldLine) => {
|
||||
if (line < 0) return
|
||||
if (line == 0 && isSetedLines) return isSetedLines = false
|
||||
isSetedLines &&= false
|
||||
if (oldLine == null || line - oldLine != 1) return handleScrollLrc()
|
||||
|
||||
delayScrollTimeout = setTimeout(() => {
|
||||
|
||||
@@ -44,7 +44,7 @@ const saveViewPrevStateThrottle = throttle((state) => {
|
||||
}, 1000)
|
||||
|
||||
const initPosition = async() => {
|
||||
if (listPosition == null) listPosition = await getListPositionInfoFromData() ?? {}
|
||||
listPosition ??= await getListPositionInfoFromData() ?? {}
|
||||
}
|
||||
export const getListPosition = async(id: string): Promise<number> => {
|
||||
await initPosition()
|
||||
@@ -74,7 +74,7 @@ const saveListPrevSelectIdThrottle = throttle(() => {
|
||||
saveListPrevSelectIdFromData(listPrevSelectId)
|
||||
}, 200)
|
||||
export const getListPrevSelectId = async() => {
|
||||
if (listPrevSelectId == null) listPrevSelectId = await getListPrevSelectIdFromData() ?? LIST_IDS.DEFAULT
|
||||
listPrevSelectId ??= await getListPrevSelectIdFromData() ?? LIST_IDS.DEFAULT
|
||||
return listPrevSelectId ?? LIST_IDS.DEFAULT
|
||||
}
|
||||
export const saveListPrevSelectId = (id: string) => {
|
||||
@@ -138,7 +138,7 @@ export const overwriteListUpdateInfo = async(ids: string[]) => {
|
||||
|
||||
|
||||
export const getSearchSetting = async() => {
|
||||
if (!searchSetting) searchSetting = await getSearchSettingFromData()
|
||||
searchSetting ??= await getSearchSettingFromData()
|
||||
return { ...searchSetting }
|
||||
}
|
||||
export const setSearchSetting = async(setting: Partial<typeof DEFAULT_SETTING['search']>) => {
|
||||
@@ -154,7 +154,7 @@ export const setSearchSetting = async(setting: Partial<typeof DEFAULT_SETTING['s
|
||||
}
|
||||
|
||||
export const getSongListSetting = async() => {
|
||||
if (!songListSetting) songListSetting = await getSongListSettingFromData()
|
||||
songListSetting ??= await getSongListSettingFromData()
|
||||
return { ...songListSetting }
|
||||
}
|
||||
export const setSongListSetting = async(setting: Partial<typeof DEFAULT_SETTING['songList']>) => {
|
||||
@@ -164,7 +164,7 @@ export const setSongListSetting = async(setting: Partial<typeof DEFAULT_SETTING[
|
||||
}
|
||||
|
||||
export const getLeaderboardSetting = async() => {
|
||||
if (!leaderboardSetting) leaderboardSetting = await getLeaderboardSettingFromData()
|
||||
leaderboardSetting ??= await getLeaderboardSettingFromData()
|
||||
return { ...leaderboardSetting }
|
||||
}
|
||||
export const setLeaderboardSetting = async(setting: Partial<typeof DEFAULT_SETTING['leaderboard']>) => {
|
||||
|
||||
@@ -27,7 +27,7 @@ export const dateFormat2 = (time: number): string => {
|
||||
*/
|
||||
let dom_title = document.getElementsByTagName('title')[0]
|
||||
export const setTitle = (title: string | null) => {
|
||||
if (!title) title = '洛雪音乐助手'
|
||||
title ||= '洛雪音乐助手'
|
||||
dom_title.innerText = title
|
||||
}
|
||||
|
||||
@@ -60,14 +60,6 @@ export const deduplicationList = <T extends LX.Music.MusicInfo>(list: T[]): T[]
|
||||
return true
|
||||
})
|
||||
}
|
||||
export const filterMusicList = <T extends LX.Music.MusicInfo>(list: T[]): T[] => {
|
||||
const ids: Set<string> = new Set()
|
||||
return list.filter(s => {
|
||||
if (!s.id || ids.has(s.id)) return false
|
||||
ids.add(s.id)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
export const langS2T = async(str: string) => {
|
||||
return await window.lx.worker.main.langS2t(Buffer.from(str).toString('base64')).then(b64 => Buffer.from(b64, 'base64').toString())
|
||||
|
||||
@@ -21,7 +21,7 @@ export const onSettingChanged = (listener: LX.IpcRendererEventListenerParams<Par
|
||||
}
|
||||
|
||||
export const sendInited = () => {
|
||||
return rendererSend(WIN_MAIN_RENDERER_EVENT_NAME.inited)
|
||||
rendererSend(WIN_MAIN_RENDERER_EVENT_NAME.inited)
|
||||
}
|
||||
|
||||
export const getOtherSource = async(id: string): Promise<LX.Music.MusicInfoOnline[]> => {
|
||||
|
||||
@@ -10,16 +10,16 @@ const useKeyEvent = ({ handleSelectAllData }) => {
|
||||
}
|
||||
|
||||
const handle_key_shift_down = () => {
|
||||
if (!keyEvent.isShiftDown) keyEvent.isShiftDown = true
|
||||
keyEvent.isShiftDown ||= true
|
||||
}
|
||||
const handle_key_shift_up = () => {
|
||||
if (keyEvent.isShiftDown) keyEvent.isShiftDown = false
|
||||
keyEvent.isShiftDown &&= false
|
||||
}
|
||||
const handle_key_mod_down = () => {
|
||||
if (!keyEvent.isModDown) keyEvent.isModDown = true
|
||||
keyEvent.isModDown ||= true
|
||||
}
|
||||
const handle_key_mod_up = () => {
|
||||
if (keyEvent.isModDown) keyEvent.isModDown = false
|
||||
keyEvent.isModDown &&= false
|
||||
}
|
||||
const handle_key_mod_a_down = ({ event }) => {
|
||||
if (event.target.tagName == 'INPUT') return
|
||||
|
||||
@@ -137,10 +137,10 @@ export default {
|
||||
},
|
||||
handle_key_mod_down() {
|
||||
console.log('handle_key_mod_down')
|
||||
if (!this.isModDown) this.isModDown = true
|
||||
this.isModDown ||= true
|
||||
},
|
||||
handle_key_mod_up() {
|
||||
if (this.isModDown) this.isModDown = false
|
||||
this.isModDown &&= false
|
||||
},
|
||||
handle_key_mod_f_down() {
|
||||
if (this.visible) this.$refs.dom_input.focus()
|
||||
|
||||
@@ -10,16 +10,16 @@ const useKeyEvent = ({ handleSelectAllData }) => {
|
||||
}
|
||||
|
||||
const handle_key_shift_down = () => {
|
||||
if (!keyEvent.isShiftDown) keyEvent.isShiftDown = true
|
||||
keyEvent.isShiftDown ||= true
|
||||
}
|
||||
const handle_key_shift_up = () => {
|
||||
if (keyEvent.isShiftDown) keyEvent.isShiftDown = false
|
||||
keyEvent.isShiftDown &&= false
|
||||
}
|
||||
const handle_key_mod_down = () => {
|
||||
if (!keyEvent.isModDown) keyEvent.isModDown = true
|
||||
keyEvent.isModDown ||= true
|
||||
}
|
||||
const handle_key_mod_up = () => {
|
||||
if (keyEvent.isModDown) keyEvent.isModDown = false
|
||||
keyEvent.isModDown &&= false
|
||||
}
|
||||
const handle_key_mod_a_down = ({ event }) => {
|
||||
if (event.target.tagName == 'INPUT') return
|
||||
|
||||
@@ -1,259 +0,0 @@
|
||||
<!-- <template>
|
||||
<teleport to="#root">
|
||||
<div ref="dom_menu" :class="$style.container" :style="menuStyles" :aria-hidden="!modelValue">
|
||||
<div :class="$style.group">
|
||||
<div :class="$style.subGroup">
|
||||
<button :class="$style.btn" :disabled="!itemMenuControl.rename" ignore-tip :aria-label="$t('lists__rename')" @click="menuClick('rename')">{{ $t('lists__rename') }}</button>
|
||||
<button :class="$style.btn" :disabled="offsetDisabled" ignore-tip :aria-label="$t('lists__sync')" @click="menuClick('sync')">{{ $t('lists__sync') }}</button>
|
||||
</div>
|
||||
<div :class="$style.subGroup">
|
||||
<button :class="$style.btn" :disabled="offsetDisabled" ignore-tip :aria-label="$t('lists__sort_list')" @click="menuClick('sort')">{{ $t('lists__sort_list') }}</button>
|
||||
<button :class="$style.btn" :disabled="offsetDisabled" ignore-tip :aria-label="$t('lyric_menu__offset_dec_100')" @click="menuClick(-100)">- 100ms</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed, useRefGetter, ref, useCommit, watch } from '@common/utils/vueTools'
|
||||
import useMenuLocation from '@renderer/utils/compositions/useMenuLocation'
|
||||
import { setLyricEdited, removeLyricEdited, debounce } from '@renderer/utils'
|
||||
|
||||
const offsetTagRxp = /(?:^|\n)\s*\[offset:\s*(\S+(?:\d+)*)\s*\]/
|
||||
const offsetTagAllRxp = /(?:^|\n)\s*\[offset:\s*(\S+(?:\d+)*)\s*\]/g
|
||||
|
||||
const saveLyric = debounce((musicInfo, lyricInfo) => {
|
||||
setLyricEdited(musicInfo, lyricInfo)
|
||||
})
|
||||
const removeLyric = debounce(musicInfo => {
|
||||
removeLyricEdited(musicInfo)
|
||||
})
|
||||
|
||||
const getOffset = lrc => {
|
||||
let offset = offsetTagRxp.exec(lrc)
|
||||
if (offset) {
|
||||
offset = parseInt(offset[1])
|
||||
if (Number.isNaN(offset)) offset = 0
|
||||
} else offset = 0
|
||||
return offset
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'LyricMenu',
|
||||
props: {
|
||||
modelValue: Boolean,
|
||||
xy: Object,
|
||||
lyricInfo: Object,
|
||||
},
|
||||
emits: ['updateLyric', 'update:modelValue'],
|
||||
setup(props, { emit }) {
|
||||
const setting = useRefGetter('setting')
|
||||
const playDetailSetting = useRefGetter('playDetailSetting')
|
||||
const setPlayDetailLyricAlign = useCommit('setPlayDetailLyricAlign')
|
||||
const setPlayDetailLyricFont = useCommit('setPlayDetailLyricFont')
|
||||
|
||||
const offset = ref(0)
|
||||
const offsetDisabled = ref(true)
|
||||
const originOffset = ref(0)
|
||||
|
||||
const visible = computed(() => props.modelValue)
|
||||
const musicInfo = computed(() => props.lyricInfo.musicInfo)
|
||||
const location = computed(() => props.xy)
|
||||
|
||||
const onHide = () => {
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
|
||||
const setFontAlign = val => {
|
||||
if (playDetailSetting.value.style.align == val) return
|
||||
setPlayDetailLyricAlign(val)
|
||||
}
|
||||
|
||||
const fontSizeUp = step => {
|
||||
if (setting.value.playDetail.style.fontSize >= 200) return
|
||||
setPlayDetailLyricFont(Math.min(setting.value.playDetail.style.fontSize + step, 200))
|
||||
}
|
||||
const fontSizeDown = step => {
|
||||
if (setting.value.playDetail.style.fontSize <= 70) return
|
||||
setPlayDetailLyricFont(Math.max(setting.value.playDetail.style.fontSize - step, 70))
|
||||
}
|
||||
const fontSizeReset = () => {
|
||||
setPlayDetailLyricFont(100)
|
||||
}
|
||||
|
||||
const updateLyric = offset => {
|
||||
let lyric = props.lyricInfo.lyric
|
||||
let tlyric = props.lyricInfo.tlyric
|
||||
let rlyric = props.lyricInfo.rlyric
|
||||
let lxlyric = props.lyricInfo.lxlyric
|
||||
if (offsetTagRxp.test(lyric)) {
|
||||
lyric = lyric.replace(offsetTagAllRxp, `[offset:${offset}]`)
|
||||
if (tlyric) tlyric = tlyric.replace(offsetTagAllRxp, `[offset:${offset}]`)
|
||||
if (lxlyric) lxlyric = lxlyric.replace(offsetTagAllRxp, `[offset:${offset}]`)
|
||||
if (rlyric) rlyric = rlyric.replace(offsetTagAllRxp, `[offset:${offset}]`)
|
||||
} else {
|
||||
lyric = `[offset:${offset}]\n` + lyric
|
||||
if (tlyric) tlyric = `[offset:${offset}]\n` + tlyric
|
||||
if (lxlyric) lxlyric = `[offset:${offset}]\n` + lxlyric
|
||||
if (rlyric) rlyric = `[offset:${offset}]\n` + rlyric
|
||||
}
|
||||
|
||||
if (offset == originOffset.value) {
|
||||
removeLyric(props.lyricInfo.musicInfo)
|
||||
} else {
|
||||
saveLyric(props.lyricInfo.musicInfo, {
|
||||
lyric,
|
||||
tlyric,
|
||||
rlyric,
|
||||
lxlyric,
|
||||
})
|
||||
}
|
||||
|
||||
emit('updateLyric', {
|
||||
lyric,
|
||||
tlyric,
|
||||
rlyric,
|
||||
lxlyric,
|
||||
offset,
|
||||
})
|
||||
}
|
||||
const setOffset = step => {
|
||||
offset.value += step
|
||||
updateLyric(offset.value)
|
||||
}
|
||||
const offsetReset = () => {
|
||||
if (offset.value == originOffset.value) return
|
||||
offset.value = originOffset.value
|
||||
updateLyric(originOffset.value)
|
||||
}
|
||||
|
||||
const parseLrcOffset = () => {
|
||||
offset.value = getOffset(props.lyricInfo.lyric)
|
||||
originOffset.value = getOffset(props.lyricInfo.rawlyric)
|
||||
offsetDisabled.value = !props.lyricInfo.lyric
|
||||
}
|
||||
|
||||
|
||||
const { dom_menu, menuStyles } = useMenuLocation({
|
||||
visible,
|
||||
location,
|
||||
onHide,
|
||||
})
|
||||
|
||||
watch(musicInfo, () => {
|
||||
if (!props.modelValue) return
|
||||
parseLrcOffset()
|
||||
})
|
||||
watch(visible, val => {
|
||||
if (!val) return
|
||||
parseLrcOffset()
|
||||
})
|
||||
|
||||
return {
|
||||
dom_menu,
|
||||
menuStyles,
|
||||
playDetailSetting,
|
||||
offset,
|
||||
originOffset,
|
||||
fontSizeUp,
|
||||
fontSizeDown,
|
||||
fontSizeReset,
|
||||
setOffset,
|
||||
offsetReset,
|
||||
setFontAlign,
|
||||
offsetDisabled,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.container {
|
||||
font-size: 12px;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
transform-origin: 0 0 0;
|
||||
transition: .25s ease;
|
||||
transition-property: transform, opacity;
|
||||
border-radius: @radius-border;
|
||||
background-color: var(--color-main-background);
|
||||
box-shadow: 0 1px 8px 0 rgba(0,0,0,.2);
|
||||
z-index: 10;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.title {
|
||||
flex: auto;
|
||||
padding: 10px 0 10px 10px;
|
||||
color: var(--color-font-label);
|
||||
white-space: nowrap;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.subGroup {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
}
|
||||
|
||||
.btn {
|
||||
flex: auto;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
min-width: 60px;
|
||||
height: 34px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
// color: var(--color-button-font);
|
||||
padding: 0 10px;
|
||||
outline: none;
|
||||
transition: @transition-normal;
|
||||
transition-property: background-color, opacity;
|
||||
box-sizing: border-box;
|
||||
.mixin-ellipsis-1;
|
||||
background-color: var(--color-main-background);
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
opacity: .7;
|
||||
background-color: @color-theme_2-hover;
|
||||
}
|
||||
&:active {
|
||||
background-color: @color-theme_2-active;
|
||||
}
|
||||
&.active {
|
||||
background-color: var(--color-main-background);
|
||||
color: var(--color-button-background-active);
|
||||
cursor: default;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
cursor: default;
|
||||
opacity: .4;
|
||||
&:hover {
|
||||
background: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.titleBtn {
|
||||
flex: none;
|
||||
padding: 0 10;
|
||||
min-width: 40px;
|
||||
opacity: .7;
|
||||
|
||||
&[disabled] {
|
||||
opacity: .3;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
-->
|
||||
@@ -51,8 +51,11 @@ export default () => {
|
||||
break
|
||||
case 'playListPart_v2':
|
||||
listData = configData.data
|
||||
listData.list = filterMusicList(listData.list).map(m => fixNewMusicInfoQuality(m))
|
||||
break
|
||||
default: return showImportTip(configData.type)
|
||||
default:
|
||||
showImportTip(configData.type)
|
||||
return
|
||||
}
|
||||
|
||||
const targetList = [defaultList, loveList, ...userLists].find(l => l.id == listData.id)
|
||||
|
||||
@@ -35,8 +35,8 @@ const verifyQueryParams = async(to, from, next) => {
|
||||
|
||||
if (_source == null || _type == null) {
|
||||
const setting = await getSearchSetting()
|
||||
if (!_source) _source = setting.source
|
||||
if (!_type) _type = setting.type
|
||||
_source ??= setting.source
|
||||
_type ??= setting.type
|
||||
|
||||
next({
|
||||
path: to.path,
|
||||
|
||||
@@ -79,9 +79,9 @@ export default {
|
||||
name: list.name,
|
||||
id: list.id,
|
||||
list: filterMusicList(list.list.map(m => toNewMusicInfo(m))),
|
||||
position: list.position,
|
||||
source: list.source,
|
||||
sourceListId: list.sourceListId,
|
||||
locationUpdateTime: list.locationUpdateTime ?? null,
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -98,15 +98,15 @@ export default {
|
||||
try {
|
||||
const targetList = allLists.find(l => l.id == list.id)
|
||||
if (targetList) {
|
||||
targetList.list = list.list.map(m => fixNewMusicInfoQuality(m))
|
||||
targetList.list = filterMusicList(list.list).map(m => fixNewMusicInfoQuality(m))
|
||||
} else {
|
||||
allLists.push({
|
||||
name: list.name,
|
||||
id: list.id,
|
||||
list: list.list.map(m => fixNewMusicInfoQuality(m)),
|
||||
position: list.position,
|
||||
list: filterMusicList(list.list).map(m => fixNewMusicInfoQuality(m)),
|
||||
source: list.source,
|
||||
sourceListId: list.sourceListId,
|
||||
locationUpdateTime: list.locationUpdateTime ?? null,
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@@ -127,9 +127,9 @@ export default {
|
||||
const autoTheme = reactive({})
|
||||
const updateAutoTheme = (info) => {
|
||||
let light = findTheme(info, appSetting['theme.lightId'])
|
||||
if (!light) light = info.themes.find(theme => theme.id == 'green')
|
||||
light ??= info.themes.find(theme => theme.id == 'green')
|
||||
let dark = findTheme(info, appSetting['theme.darkId'])
|
||||
if (!dark) dark = info.themes.find(theme => theme.id == 'black')
|
||||
dark ??= info.themes.find(theme => theme.id == 'black')
|
||||
autoTheme['--color-primary-theme-light'] = light.config.themeColors['--color-theme']
|
||||
autoTheme['--background-image-theme-light'] = light.isCustom
|
||||
? light.config.extInfo['--background-image'] == 'none'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<material-modal :show="props.modelValue" teleport="#view" width="60%" @close="emit('update:modelValue', $event)" @after-enter="$refs.input.focus()">
|
||||
<material-modal :show="props.modelValue" teleport="#view" width="60%" bg-close @close="emit('update:modelValue', $event)" @after-enter="$refs.input.focus()">
|
||||
<main :class="$style.main">
|
||||
<h2>{{ $t('songlist__import_input_title') }}</h2>
|
||||
<div :class="$style.inputContent">
|
||||
|
||||
@@ -172,7 +172,7 @@ export const filterDuplicateMusic = async(list: LX.Music.MusicInfo[], isFilterVa
|
||||
if (isFilterVariant) {
|
||||
list.forEach((musicInfo, index) => {
|
||||
let musicInfoName = musicInfo.name.toLowerCase().replace(variantRxp, '').replace(variantRxp2, '')
|
||||
if (!musicInfoName) musicInfoName = musicInfo.name.toLowerCase().replace(/\s+/g, '')
|
||||
musicInfoName ||= musicInfo.name.toLowerCase().replace(/\s+/g, '')
|
||||
handleFilter(musicInfoName, index, musicInfo)
|
||||
})
|
||||
} else {
|
||||
@@ -195,7 +195,7 @@ export const searchListMusic = (list: LX.Music.MusicInfo[], text: string) => {
|
||||
if (rxp.test(str)) result.push(mInfo)
|
||||
}
|
||||
|
||||
const sortedList: any[] = []
|
||||
const sortedList: Array<{ num: number, data: LX.Music.MusicInfo }> = []
|
||||
|
||||
for (const mInfo of result) {
|
||||
sortInsert(sortedList, {
|
||||
|
||||
Reference in New Issue
Block a user