mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/lyswhut/lx-music-desktop.git
synced 2026-09-20 16:14:05 +08:00
Compare commits
1 Commits
v2.4.0
...
api-source
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fe34545b9 |
119
.eslintrc
Normal file
119
.eslintrc
Normal file
@@ -0,0 +1,119 @@
|
||||
{
|
||||
"root": true,
|
||||
"extends": [
|
||||
"standard"
|
||||
],
|
||||
"plugins": [
|
||||
"html"
|
||||
],
|
||||
"parser": "@babel/eslint-parser",
|
||||
"parserOptions": {
|
||||
// "requireConfigFile": false
|
||||
},
|
||||
"rules": {
|
||||
"no-new": "off",
|
||||
"camelcase": "off",
|
||||
"no-return-assign": "off",
|
||||
"space-before-function-paren": ["error", "never"],
|
||||
"no-var": "error",
|
||||
"no-fallthrough": "off",
|
||||
"prefer-promise-reject-errors": "off",
|
||||
"eqeqeq": "off",
|
||||
"no-multiple-empty-lines": [1, {"max": 2}],
|
||||
"comma-dangle": [2, "always-multiline"],
|
||||
"standard/no-callback-literal": "off",
|
||||
"prefer-const": "off",
|
||||
"no-labels": "off",
|
||||
"node/no-callback-literal": "off"
|
||||
},
|
||||
"ignorePatterns": ["vendors", "*.min.js", "dist"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": [ "*.vue" ],
|
||||
"rules": {
|
||||
"no-new": "off",
|
||||
"camelcase": "off",
|
||||
"no-return-assign": "off",
|
||||
"space-before-function-paren": ["error", "never"],
|
||||
"no-var": "error",
|
||||
"no-fallthrough": "off",
|
||||
"prefer-promise-reject-errors": "off",
|
||||
"eqeqeq": "off",
|
||||
"no-multiple-empty-lines": [1, {"max": 2}],
|
||||
"comma-dangle": [2, "always-multiline"],
|
||||
"standard/no-callback-literal": "off",
|
||||
"prefer-const": "off",
|
||||
"no-labels": "off",
|
||||
"node/no-callback-literal": "off",
|
||||
"vue/multi-word-component-names": "off",
|
||||
"@typescript-eslint/strict-boolean-expressions": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/space-before-function-paren": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/naming-convention": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"vue/max-attributes-per-line": "off",
|
||||
"vue/singleline-html-element-content-newline": "off",
|
||||
"vue/use-v-on-exact": "off",
|
||||
"@typescript-eslint/restrict-template-expressions": "off",
|
||||
// "no-undef": "off"
|
||||
},
|
||||
"parser": "vue-eslint-parser",
|
||||
"extends": [
|
||||
"plugin:vue/base",
|
||||
// "plugin:vue/strongly-recommended"
|
||||
"plugin:vue/vue3-recommended",
|
||||
"standard-with-typescript",
|
||||
],
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"parser": {
|
||||
// Script parser for `<script>`
|
||||
"js": "@typescript-eslint/parser",
|
||||
|
||||
// Script parser for `<script lang="ts">`
|
||||
"ts": "@typescript-eslint/parser"
|
||||
},
|
||||
"extraFileExtensions": [".vue"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [ "*.ts" ],
|
||||
"rules": {
|
||||
"no-new": "off",
|
||||
"camelcase": "off",
|
||||
"no-return-assign": "off",
|
||||
"space-before-function-paren": ["error", "never"],
|
||||
"no-var": "error",
|
||||
"no-fallthrough": "off",
|
||||
"prefer-promise-reject-errors": "off",
|
||||
"eqeqeq": "off",
|
||||
"no-multiple-empty-lines": [1, {"max": 2}],
|
||||
"comma-dangle": [2, "always-multiline"],
|
||||
"standard/no-callback-literal": "off",
|
||||
"prefer-const": "off",
|
||||
"no-labels": "off",
|
||||
"node/no-callback-literal": "off",
|
||||
"@typescript-eslint/strict-boolean-expressions": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/space-before-function-paren": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/restrict-template-expressions": [1, {
|
||||
"allowBoolean": true
|
||||
}],
|
||||
"@typescript-eslint/naming-convention": "off",
|
||||
"@typescript-eslint/return-await": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"multiline-ternary": "off",
|
||||
"@typescript-eslint/comma-dangle": "off",
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"extends": [
|
||||
"standard-with-typescript"
|
||||
],
|
||||
"parserOptions": {
|
||||
"project": "./src/**/tsconfig.json"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
const baseRule = {
|
||||
'no-new': 'off',
|
||||
camelcase: 'off',
|
||||
'no-return-assign': 'off',
|
||||
'space-before-function-paren': ['error', 'never'],
|
||||
'no-var': 'error',
|
||||
'no-fallthrough': 'off',
|
||||
eqeqeq: 'off',
|
||||
'require-atomic-updates': ['error', { allowProperties: true }],
|
||||
'no-multiple-empty-lines': [1, { max: 2 }],
|
||||
'comma-dangle': [2, 'always-multiline'],
|
||||
'standard/no-callback-literal': 'off',
|
||||
'prefer-const': 'off',
|
||||
'no-labels': 'off',
|
||||
'node/no-callback-literal': 'off',
|
||||
'multiline-ternary': 'off',
|
||||
}
|
||||
const typescriptRule = {
|
||||
...baseRule,
|
||||
'@typescript-eslint/strict-boolean-expressions': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/space-before-function-paren': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/restrict-template-expressions': [1, {
|
||||
allowBoolean: true,
|
||||
allowAny: true,
|
||||
}],
|
||||
'@typescript-eslint/restrict-plus-operands': [1, {
|
||||
allowBoolean: true,
|
||||
allowAny: true,
|
||||
}],
|
||||
'@typescript-eslint/no-misused-promises': [
|
||||
'error',
|
||||
{
|
||||
checksVoidReturn: {
|
||||
arguments: false,
|
||||
attributes: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/naming-convention': 'off',
|
||||
'@typescript-eslint/return-await': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
'@typescript-eslint/comma-dangle': 'off',
|
||||
}
|
||||
const vueRule = {
|
||||
...typescriptRule,
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'vue/max-attributes-per-line': 'off',
|
||||
'vue/singleline-html-element-content-newline': 'off',
|
||||
'vue/use-v-on-exact': 'off',
|
||||
}
|
||||
|
||||
exports.base = {
|
||||
extends: ['standard'],
|
||||
rules: baseRule,
|
||||
parser: '@babel/eslint-parser',
|
||||
}
|
||||
|
||||
exports.html = {
|
||||
files: ['*.html'],
|
||||
plugins: ['html'],
|
||||
}
|
||||
|
||||
exports.typescript = {
|
||||
files: ['*.ts'],
|
||||
rules: typescriptRule,
|
||||
parser: '@typescript-eslint/parser',
|
||||
extends: [
|
||||
'standard-with-typescript',
|
||||
],
|
||||
}
|
||||
|
||||
exports.vue = {
|
||||
files: ['*.vue'],
|
||||
rules: vueRule,
|
||||
parser: 'vue-eslint-parser',
|
||||
extends: [
|
||||
// 'plugin:vue/vue3-essential',
|
||||
'plugin:vue/base',
|
||||
'plugin:vue/vue3-recommended',
|
||||
'plugin:vue-pug/vue3-recommended',
|
||||
// "plugin:vue/strongly-recommended"
|
||||
'standard-with-typescript',
|
||||
],
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
parser: {
|
||||
// Script parser for `<script>`
|
||||
js: '@typescript-eslint/parser',
|
||||
|
||||
// Script parser for `<script lang="ts">`
|
||||
ts: '@typescript-eslint/parser',
|
||||
},
|
||||
extraFileExtensions: ['.vue'],
|
||||
},
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
const { base, typescript } = require('./.eslintrc.base.cjs')
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
...base,
|
||||
overrides: [
|
||||
{
|
||||
...typescript,
|
||||
parserOptions: {
|
||||
project: './tsconfig.json',
|
||||
},
|
||||
},
|
||||
],
|
||||
ignorePatterns: [
|
||||
'node_modules',
|
||||
'*.min.js',
|
||||
'dist',
|
||||
'build',
|
||||
],
|
||||
}
|
||||
40
.github/workflows/beta-pack.yml
vendored
40
.github/workflows/beta-pack.yml
vendored
@@ -6,41 +6,9 @@ on:
|
||||
- beta
|
||||
|
||||
jobs:
|
||||
# CheckCode:
|
||||
# name: Lint Code
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - name: Check out git repository
|
||||
# uses: actions/checkout@v3
|
||||
|
||||
# - name: Install Node.js
|
||||
# uses: actions/setup-node@v3
|
||||
# with:
|
||||
# node-version: '16'
|
||||
|
||||
# - name: Cache file
|
||||
# uses: actions/cache@v3
|
||||
# with:
|
||||
# path: |
|
||||
# node_modules
|
||||
# $HOME/.cache/electron
|
||||
# $HOME/.cache/electron-builder
|
||||
# $HOME/.npm/_prebuilds
|
||||
# key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-build-
|
||||
|
||||
# - name: Install Dependencies
|
||||
# run: |
|
||||
# npm ci
|
||||
|
||||
# - name: Lint src code
|
||||
# run: npm run lint
|
||||
|
||||
Windows:
|
||||
name: Windows
|
||||
runs-on: windows-latest
|
||||
# needs: CheckCode
|
||||
steps:
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v3
|
||||
@@ -67,7 +35,7 @@ jobs:
|
||||
npm ci
|
||||
|
||||
- name: Build src code
|
||||
run: npm run build
|
||||
run: npm run build:src
|
||||
|
||||
- name: Build Package Setup x64
|
||||
run: npm run pack:win:setup:x64
|
||||
@@ -133,7 +101,6 @@ jobs:
|
||||
Mac:
|
||||
name: Mac
|
||||
runs-on: macos-latest
|
||||
# needs: CheckCode
|
||||
steps:
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v3
|
||||
@@ -160,7 +127,7 @@ jobs:
|
||||
npm ci
|
||||
|
||||
- name: Build src code
|
||||
run: npm run build
|
||||
run: npm run build:src
|
||||
|
||||
- name: Build Package dmg
|
||||
run: |
|
||||
@@ -191,7 +158,6 @@ jobs:
|
||||
Linux:
|
||||
name: Linux
|
||||
runs-on: ubuntu-latest
|
||||
# needs: CheckCode
|
||||
steps:
|
||||
- name: Install package
|
||||
run: sudo apt-get update && sudo apt-get install -y rpm libarchive-tools
|
||||
@@ -221,7 +187,7 @@ jobs:
|
||||
npm ci
|
||||
|
||||
- name: Build src code
|
||||
run: npm run build
|
||||
run: npm run build:src
|
||||
|
||||
- name: Build Package deb x64
|
||||
run: npm run pack:linux:deb:x64
|
||||
|
||||
28
.github/workflows/build-test.yml
vendored
28
.github/workflows/build-test.yml
vendored
@@ -1,28 +0,0 @@
|
||||
name: Run build test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Eslint check
|
||||
run: npm run lint
|
||||
|
||||
- name: Test Build
|
||||
run: npm run build
|
||||
16
.github/workflows/publish-version-info.yml
vendored
16
.github/workflows/publish-version-info.yml
vendored
@@ -1,16 +0,0 @@
|
||||
name: Publish NPM Version Info
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
dispatch:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Repository Dispatch
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
repository: lyswhut/lx-music-desktop-version-info
|
||||
event-type: npm-release
|
||||
40
.github/workflows/release.yml
vendored
40
.github/workflows/release.yml
vendored
@@ -6,41 +6,9 @@ on:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
# CheckCode:
|
||||
# name: Lint Code
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - name: Check out git repository
|
||||
# uses: actions/checkout@v3
|
||||
|
||||
# - name: Install Node.js
|
||||
# uses: actions/setup-node@v3
|
||||
# with:
|
||||
# node-version: '16'
|
||||
|
||||
# - name: Cache file
|
||||
# uses: actions/cache@v3
|
||||
# with:
|
||||
# path: |
|
||||
# node_modules
|
||||
# $HOME/.cache/electron
|
||||
# $HOME/.cache/electron-builder
|
||||
# $HOME/.npm/_prebuilds
|
||||
# key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-build-
|
||||
|
||||
# - name: Install Dependencies
|
||||
# run: |
|
||||
# npm ci
|
||||
|
||||
# - name: Lint src code
|
||||
# run: npm run lint
|
||||
|
||||
Windows:
|
||||
name: Windows
|
||||
runs-on: windows-latest
|
||||
# needs: CheckCode
|
||||
steps:
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v3
|
||||
@@ -67,7 +35,7 @@ jobs:
|
||||
npm ci
|
||||
|
||||
- name: Build src code
|
||||
run: npm run build
|
||||
run: npm run build:src
|
||||
|
||||
- name: Release package
|
||||
run: |
|
||||
@@ -90,7 +58,6 @@ jobs:
|
||||
Mac:
|
||||
name: Mac
|
||||
runs-on: macos-latest
|
||||
# needs: CheckCode
|
||||
steps:
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v3
|
||||
@@ -117,7 +84,7 @@ jobs:
|
||||
npm ci
|
||||
|
||||
- name: Build src code
|
||||
run: npm run build
|
||||
run: npm run build:src
|
||||
|
||||
- name: Release package
|
||||
run: |
|
||||
@@ -137,7 +104,6 @@ jobs:
|
||||
Linux:
|
||||
name: Linux
|
||||
runs-on: ubuntu-latest
|
||||
# needs: CheckCode
|
||||
steps:
|
||||
- name: Install package
|
||||
run: sudo apt-get update && sudo apt-get install -y rpm libarchive-tools
|
||||
@@ -167,7 +133,7 @@ jobs:
|
||||
npm ci
|
||||
|
||||
- name: Build src code
|
||||
run: npm run build
|
||||
run: npm run build:src
|
||||
|
||||
- name: Release package
|
||||
run: |
|
||||
|
||||
11
.ncurc.js
11
.ncurc.js
@@ -5,22 +5,11 @@ module.exports = {
|
||||
'chalk',
|
||||
'del',
|
||||
'comlink',
|
||||
'vue',
|
||||
'@types/ws',
|
||||
// 'eslint-config-standard-with-typescript',
|
||||
'typescript', // https://github.com/microsoft/TypeScript/pull/54567
|
||||
],
|
||||
|
||||
// target: 'newest',
|
||||
// filter: [
|
||||
// 'electron-builder',
|
||||
// 'electron-updater',
|
||||
// 'electron-log',
|
||||
// ],
|
||||
|
||||
// target: 'patch',
|
||||
// filter: [
|
||||
// 'vue',
|
||||
// ],
|
||||
|
||||
// target: 'minor',
|
||||
|
||||
81
CHANGELOG.md
81
CHANGELOG.md
@@ -6,87 +6,6 @@ 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.4.0](https://github.com/lyswhut/lx-music-desktop/compare/v2.3.0...v2.4.0) - 2023-09-09
|
||||
|
||||
目前本项目的原始发布地址只有 **GitHub** 及 **蓝奏网盘** ,其他渠道均为第三方转载发布,可信度请自行鉴别。
|
||||
本项目无微信公众号之类的官方账号,谨防被骗。
|
||||
|
||||
### 不兼容性变更
|
||||
|
||||
该版本修改了同步协议逻辑,同步功能至少需要PC端v2.4.0或移动端v1.1.0版本或同步服务v2.0.0才能连接使用。
|
||||
|
||||
### 新增
|
||||
|
||||
- 新增我的列表名右键菜单-排序歌曲-随机乱序功能,使用它可以对选中列表内歌曲进行随机重排(#1440)
|
||||
- 新增数据同步服务端模式已认证设备列表管理,该功能位置:设置-数据同步-服务端模式-已认证设备列表
|
||||
- 新增“不喜欢歌曲”功能,可以在我的列表或者在线列表内歌曲的右击菜单使用,还可以去“设置-其他”手动编辑不喜欢规则,注:“上一曲”、“下一曲”功能将跳过符合“不喜欢歌曲”规则的歌曲,但你仍可以手动播放这些歌曲
|
||||
- 新增同步功能对“不喜欢歌曲”列表的同步
|
||||
- 新增软件内快捷键“不喜欢该歌曲”设置,全局快捷键“收藏歌曲”、“取消收藏”、“不喜欢该歌曲”设置
|
||||
- 新增设置-播放设置-点击相同列表内的歌曲切歌时是否清空已播放列表(随机模式下列表内所有歌曲会重新参与随机)选项,默认关闭
|
||||
|
||||
### 优化
|
||||
|
||||
- 优化音效设置-环境音效启用、禁用时的操作效果显示,修复禁用环境音效时仍然可以调整增益、新增预设的问题
|
||||
- 过滤翻译歌词或罗马音歌词中只有“//”的行(#1499)
|
||||
- 点击打开歌单弹窗背景将不再自动关闭弹窗,防止选择输入框里的内容时意外关闭弹窗
|
||||
- 优化数据传输逻辑,列表同步指令使用队列机制,保证列表同步操作的顺序
|
||||
- 优化桌面歌词在开启 缩放当前播放的歌词 并关闭 延迟歌词滚动 时的歌词滚动位置计算问题,现在歌词滚动应该可以正确滚动到目标位置了
|
||||
- 优化歌词在短时间内快速播放时的滚动效果,现在遇到这种情况时滚动将更平滑
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复字体设置某些字体无法应用的问题
|
||||
- 修复搜索提示功能失效的问题(#1452, @Folltoshe)
|
||||
- 修复我的列表名右键菜单-排序歌曲按专辑名排序无效的问题(#1440)
|
||||
- 修复若路径存在 # 字符时,软件无法启动的问题
|
||||
- 修复搜索框在某些情况下输入内容后搜索时会自动清空的问题(#1472)
|
||||
- 修复某些tx源歌词因数据异常解析失败的问题
|
||||
- 修复windows平台下隐藏窗口后再显示时任务栏按钮丢失的问题
|
||||
- 修复首句歌词被提前播放的问题
|
||||
- 修复潜在导致列表数据不同步的问题
|
||||
- 修复kg无评论时的加载处理问题
|
||||
|
||||
### 变更
|
||||
|
||||
- 播放模式应该只适用于列表内的歌曲,所以单曲循环模式不应对“稍后播放”的歌曲有效,该行为现在与移动端一致
|
||||
- 随机模式下,通过点击与播放列表相同的列表切歌时,将不再清空已播放列表,即已播放的歌曲不再重新参与随机,若想恢复之前的行为可以去设置-播放设置启用清空已播放列表选项
|
||||
|
||||
### 其他
|
||||
|
||||
- 更新 electron 到 v22.3.23
|
||||
- 重构同步服务端功能部分代码,使其更易扩展新功能
|
||||
|
||||
## [2.3.0](https://github.com/lyswhut/lx-music-desktop/compare/v2.2.2...v2.3.0) - 2023-06-29
|
||||
|
||||
### 新增
|
||||
|
||||
- 新增音效设置(实验性功能),支持10段均衡器设置、内置的一些环境混响音效、音调升降调节、3D立体环绕音效(由于升降调需要实时处理音频数据,这会导致额外的CPU占用,已知问题:如果CPU资源不够时将处理导致任务堆积而出现声音异常,这时需要暂停播放一段时间等堆积的任务处理完毕再播放)
|
||||
- 播放速率设置面板新增是否音调补偿设置,在调整播放速率后,可以选择是否启用音调补偿,默认启用
|
||||
|
||||
### 优化
|
||||
|
||||
- Windows、MacOS平台下的字体列表改用原生方式获取,现在Windows平台下能显示当前已安装的更多类型字体了(注:MacOS平台未测,可用性未知)
|
||||
- 移除桌面歌词窗口透明边距,在Linux下的桌面歌词可以完全拖到贴合屏幕边缘了
|
||||
- 过滤嵌入、下载的翻译、罗马音歌词时间标签,与主歌词时间不匹配的歌词将被丢弃,防止出现原歌词与翻译歌词顺序错乱的问题(#1358)
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复列表名翻译显示
|
||||
- 修复因插入数字类型的ID导致其意外在末尾追加 .0 导致列表数据异常的问题,同时也可能导致同步数据丢失的问题(要完全修复这个问题还需要同时将移动端、同步服务更新到最新版本)
|
||||
- 修复下载时出现302错误的问题
|
||||
- 修复播放某些在线音频会没有声音的问题
|
||||
- 修复改变播放速率时会导致歌词报错的问题
|
||||
- 修复tx热门评论昵称被错误切割的问题 (#1397, By: @helloplhm-qwq, @Folltoshe)
|
||||
- 修复wy源热搜词失效的问题(#1401, @Folltoshe)
|
||||
- 修复Deepin 20下启用桌面歌词时可能会导致桌面卡死的问题(#1288)
|
||||
- 修复添加单首歌曲弹窗列表创建按钮无法取消的问题
|
||||
- 修复mg歌单搜索歌单播放数量显示问题
|
||||
- 修复tx翻译歌词解析丢失的问题(更新版本后需手动清理一次歌词缓存)
|
||||
|
||||
### 其他
|
||||
|
||||
- 更新 electron 到 v22.3.15
|
||||
|
||||
## [2.2.2](https://github.com/lyswhut/lx-music-desktop/compare/v2.2.1...v2.2.2) - 2023-05-01
|
||||
|
||||
### 修复
|
||||
|
||||
24
README.md
24
README.md
@@ -52,8 +52,6 @@
|
||||
|
||||
目前本项目的原始发布地址只有**GitHub**及**蓝奏网盘**,其他渠道均为第三方转载发布,与本项目无关!
|
||||
|
||||
为了提高使用门槛,本软件内的默认设置、UI操作不以新手友好为目标,所以使用前建议先根据你的喜好浏览调整一遍软件设置,阅读一遍[音乐播放列表机制](https://lyswhut.github.io/lx-music-doc/desktop/faq/playlist)及[可用的鼠标、键盘快捷操作](https://lyswhut.github.io/lx-music-doc/desktop/faq/hotkey)
|
||||
|
||||
#### Scheme URL支持
|
||||
|
||||
从v1.17.0起支持 Scheme URL,可以使用此功能从浏览器等场景下调用LX Music,我们开发了一个[油猴脚本](https://github.com/lyswhut/lx-music-script#readme)配套使用,<br>
|
||||
@@ -91,7 +89,25 @@
|
||||
|
||||
### 源码使用方法
|
||||
|
||||
已迁移至:<https://lyswhut.github.io/lx-music-doc/desktop/use-source-code>
|
||||
环境要求:Node.js 16+
|
||||
|
||||
```bash
|
||||
# 开发模式
|
||||
npm run dev
|
||||
|
||||
# 构建免安装版
|
||||
npm run pack:dir
|
||||
|
||||
# 构建安装包(Windows版)
|
||||
npm run pack:win
|
||||
|
||||
# 构建安装包(Mac版)
|
||||
npm run pack:mac
|
||||
|
||||
# 构建安装包(Linux版)
|
||||
npm run pack:linux
|
||||
|
||||
```
|
||||
|
||||
### UI界面
|
||||
|
||||
@@ -113,7 +129,7 @@
|
||||
|
||||
1. 参照[源码使用方法](https://lyswhut.github.io/lx-music-doc/desktop/use-source-code)设置开发环境
|
||||
2. 克隆本仓库代码并切换到`dev`分支开发
|
||||
3. 提交PR至`dev`分支
|
||||
3. 提交PR
|
||||
|
||||
### 项目协议
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ module.exports = async(context) => {
|
||||
const resPath = `${appOutDir}/${productFilename}.app/Contents/Resources`
|
||||
|
||||
// 创建APP语言包文件
|
||||
return Promise.all(
|
||||
return await Promise.all(
|
||||
Object.entries(macLanguagesInfoPlistStrings).map(([lang, config]) => {
|
||||
let infos = Object.entries(config).map(([k, v]) => `"${k}" = "${v}";`).join('\n')
|
||||
return fs.writeFile(`${resPath}/${lang}.lproj/InfoPlist.strings`, infos)
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
// 修补依赖源码以使vite构建的依赖恢复正常工作
|
||||
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
|
||||
const rootPath = path.join(__dirname, '../')
|
||||
|
||||
const patchs = [
|
||||
[
|
||||
path.join(rootPath, './node_modules/ws/package.json'),
|
||||
'\n "browser": "./browser.js",',
|
||||
'',
|
||||
],
|
||||
[
|
||||
path.join(rootPath, './node_modules/music-metadata/package.json'),
|
||||
'"default": "./lib/core.js"',
|
||||
'"default": "./lib/index.js"',
|
||||
],
|
||||
[
|
||||
path.join(rootPath, './node_modules/strtok3/package.json'),
|
||||
'"default": "./lib/core.js"',
|
||||
'"default": "./lib/index.js"',
|
||||
],
|
||||
]
|
||||
|
||||
;(async() => {
|
||||
for (const [filePath, fromStr, toStr] of patchs) {
|
||||
console.log(`Patching ${filePath.replace(rootPath, '')}`)
|
||||
try {
|
||||
const file = (await fs.promises.readFile(filePath)).toString()
|
||||
await fs.promises.writeFile(filePath, file.replace(fromStr, toStr))
|
||||
} catch (err) {
|
||||
console.error(`Patch ${filePath.replace(rootPath, '')} failed: ${err.message}`)
|
||||
}
|
||||
}
|
||||
console.log('\nDependencies patch finished.\n')
|
||||
})()
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -15,7 +15,6 @@ module.exports = {
|
||||
externals: {
|
||||
'font-list': 'font-list',
|
||||
'better-sqlite3': 'better-sqlite3',
|
||||
'electron-font-manager': 'electron-font-manager',
|
||||
bufferutil: 'bufferutil',
|
||||
'utf-8-validate': 'utf-8-validate',
|
||||
'qrc_decode.node': isDev ? path.join(__dirname, '../../build/Release/qrc_decode.node') : path.join('../build/Release/qrc_decode.node'),
|
||||
|
||||
@@ -17,7 +17,6 @@ const { Worker, isMainThread, parentPort } = require('worker_threads')
|
||||
|
||||
|
||||
function build() {
|
||||
console.time('build')
|
||||
del.sync(['dist/**', 'build/**'])
|
||||
|
||||
const spinners = new Spinnies({ color: 'blue' })
|
||||
@@ -37,7 +36,6 @@ function build() {
|
||||
process.stdout.write('\x1B[2J\x1B[0f')
|
||||
console.log(`\n\n${results}`)
|
||||
console.log(`${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n`)
|
||||
console.timeEnd('build')
|
||||
process.exit()
|
||||
}
|
||||
|
||||
|
||||
@@ -20,21 +20,26 @@ module.exports = {
|
||||
type: 'commonjs2',
|
||||
},
|
||||
path: path.join(__dirname, '../../dist'),
|
||||
publicPath: '',
|
||||
publicPath: 'auto',
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@root': path.join(__dirname, '../../src'),
|
||||
'@': path.join(__dirname, '../../src'),
|
||||
'@main': path.join(__dirname, '../../src/main'),
|
||||
'@renderer': path.join(__dirname, '../../src/renderer'),
|
||||
'@lyric': path.join(__dirname, '../../src/renderer-lyric'),
|
||||
'@static': path.join(__dirname, '../../src/static'),
|
||||
'@common': path.join(__dirname, '../../src/common'),
|
||||
},
|
||||
extensions: ['.tsx', '.ts', '.js', '.json', '.node'],
|
||||
extensions: ['.tsx', '.ts', '.js', '.json', '.vue', '.node'],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
exclude: /node_modules/,
|
||||
|
||||
@@ -19,15 +19,6 @@ module.exports = merge(baseConfig, {
|
||||
externals: [
|
||||
// ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)),
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
|
||||
@@ -12,21 +12,26 @@ module.exports = {
|
||||
type: 'commonjs2',
|
||||
},
|
||||
path: path.join(__dirname, '../../dist'),
|
||||
publicPath: '',
|
||||
publicPath: 'auto',
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@root': path.join(__dirname, '../../src'),
|
||||
'@': path.join(__dirname, '../../src'),
|
||||
'@main': path.join(__dirname, '../../src/main'),
|
||||
'@renderer': path.join(__dirname, '../../src/renderer'),
|
||||
'@lyric': path.join(__dirname, '../../src/renderer-lyric'),
|
||||
'@static': path.join(__dirname, '../../src/static'),
|
||||
'@common': path.join(__dirname, '../../src/common'),
|
||||
},
|
||||
extensions: ['.tsx', '.ts', '.js', '.json', '.node'],
|
||||
extensions: ['.tsx', '.ts', '.js', '.json', '.vue', '.node'],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
exclude: /node_modules/,
|
||||
|
||||
@@ -18,15 +18,6 @@ module.exports = merge(baseConfig, {
|
||||
externals: [
|
||||
// ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)),
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
|
||||
@@ -20,21 +20,26 @@ module.exports = {
|
||||
type: 'commonjs2',
|
||||
},
|
||||
path: path.join(__dirname, '../../dist'),
|
||||
publicPath: '',
|
||||
publicPath: 'auto',
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@root': path.join(__dirname, '../../src'),
|
||||
'@': path.join(__dirname, '../../src'),
|
||||
'@main': path.join(__dirname, '../../src/main'),
|
||||
'@renderer': path.join(__dirname, '../../src/renderer'),
|
||||
'@lyric': path.join(__dirname, '../../src/renderer-lyric'),
|
||||
'@static': path.join(__dirname, '../../src/static'),
|
||||
'@common': path.join(__dirname, '../../src/common'),
|
||||
},
|
||||
extensions: ['.tsx', '.ts', '.js', '.json', '.node'],
|
||||
extensions: ['.tsx', '.ts', '.js', '.json', '.vue', '.node'],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
exclude: /node_modules/,
|
||||
@@ -44,12 +49,6 @@ module.exports = {
|
||||
appendTsSuffixTo: [/\.vue$/],
|
||||
},
|
||||
},
|
||||
parser: {
|
||||
worker: [
|
||||
'*audioContext.audioWorklet.addModule()',
|
||||
'...',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.node$/,
|
||||
|
||||
@@ -19,15 +19,6 @@ module.exports = merge(baseConfig, {
|
||||
externals: [
|
||||
// ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)),
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
|
||||
@@ -9,10 +9,5 @@
|
||||
"@common/*": ["src/common/*"],
|
||||
}
|
||||
},
|
||||
"vueCompilerOptions": {
|
||||
"plugins": [
|
||||
"@vue/language-plugin-pug"
|
||||
]
|
||||
},
|
||||
"exclude": ["node_modules", "build", "dist"]
|
||||
}
|
||||
|
||||
9795
package-lock.json
generated
9795
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
112
package.json
112
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "lx-music-desktop",
|
||||
"version": "2.4.0",
|
||||
"version": "2.3.0-beta.3",
|
||||
"description": "一个免费的音乐查找助手",
|
||||
"main": "./dist/main.js",
|
||||
"productName": "lx-music-desktop",
|
||||
@@ -55,17 +55,20 @@
|
||||
"publish:linux:rpm": "cross-env ARCH=x64 electron-builder -l=rpm --x64 -p onTagOrDraft",
|
||||
"publish:linux:pacman": "cross-env ARCH=x64 electron-builder -l=pacman --x64 -p onTagOrDraft",
|
||||
"dev": "cross-env NODE_OPTIONS=--max-http-header-size=200000 node build-config/runner-dev.js",
|
||||
"clean:electron": "rimraf dist",
|
||||
"clean": "rimraf dist && rimraf build",
|
||||
"build:theme": "node src/common/theme/createThemes.js",
|
||||
"build": "node build-config/pack.js",
|
||||
"build:src": "node build-config/pack.js",
|
||||
"build:main": "cross-env NODE_ENV=production webpack --config build-config/main/webpack.config.prod.js --progress",
|
||||
"build:renderer": "cross-env NODE_ENV=production webpack --config build-config/renderer/webpack.config.prod.js --progress",
|
||||
"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 .ts,.js,.vue -f node_modules/eslint-formatter-friendly src",
|
||||
"lint:fix": "eslint --ext .ts,.js,.vue -f node_modules/eslint-formatter-friendly --fix src",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"dp": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://127.0.0.1:2081 npm run pack",
|
||||
"up": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://127.0.0.1:2081 npm i"
|
||||
"dp": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://127.0.0.1:1081 npm run pack",
|
||||
"up": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://127.0.0.1:1081 npm i"
|
||||
},
|
||||
"browserslist": [
|
||||
"Electron 22.3.0"
|
||||
@@ -94,9 +97,6 @@
|
||||
"node_modules/better-sqlite3/lib",
|
||||
"node_modules/better-sqlite3/package.json",
|
||||
"node_modules/better-sqlite3/build/Release/better_sqlite3.node",
|
||||
"node_modules/electron-font-manager/index.js",
|
||||
"node_modules/electron-font-manager/package.json",
|
||||
"node_modules/electron-font-manager/build/Release/font_manager.node",
|
||||
"node_modules/node-gyp-build",
|
||||
"node_modules/bufferutil",
|
||||
"node_modules/utf-8-validate",
|
||||
@@ -205,103 +205,101 @@
|
||||
},
|
||||
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.22.17",
|
||||
"@babel/eslint-parser": "^7.22.15",
|
||||
"@babel/core": "^7.21.8",
|
||||
"@babel/eslint-parser": "^7.21.8",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-modules-umd": "^7.22.5",
|
||||
"@babel/plugin-transform-runtime": "^7.22.15",
|
||||
"@babel/preset-env": "^7.22.15",
|
||||
"@babel/preset-typescript": "^7.22.15",
|
||||
"@tsconfig/recommended": "^1.0.2",
|
||||
"@babel/plugin-transform-modules-umd": "^7.18.6",
|
||||
"@babel/plugin-transform-runtime": "^7.21.4",
|
||||
"@babel/preset-env": "^7.21.5",
|
||||
"@babel/preset-typescript": "^7.21.5",
|
||||
"@types/better-sqlite3": "^7.6.4",
|
||||
"@types/needle": "^3.2.0",
|
||||
"@types/tunnel": "^0.0.3",
|
||||
"@types/ws": "8.5.4",
|
||||
"@volar/vue-language-plugin-pug": "^1.6.5",
|
||||
"@vue/language-plugin-pug": "^1.8.10",
|
||||
"babel-loader": "^9.1.3",
|
||||
"browserslist": "^4.21.10",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.2",
|
||||
"@typescript-eslint/parser": "^5.59.2",
|
||||
"@volar/vue-language-plugin-pug": "^1.6.4",
|
||||
"babel-loader": "^9.1.2",
|
||||
"browserslist": "^4.21.5",
|
||||
"chalk": "^4.1.2",
|
||||
"changelog-parser": "^3.0.1",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"core-js": "^3.32.2",
|
||||
"core-js": "^3.30.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^6.8.1",
|
||||
"css-minimizer-webpack-plugin": "^5.0.1",
|
||||
"css-loader": "^6.7.3",
|
||||
"css-minimizer-webpack-plugin": "^5.0.0",
|
||||
"del": "^6.1.1",
|
||||
"electron": "^22.3.23",
|
||||
"electron-builder": "^24.6.4",
|
||||
"electron": "^22.3.8",
|
||||
"electron-builder": "^24.3.0",
|
||||
"electron-debug": "^3.2.0",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-to-chromium": "^1.4.513",
|
||||
"electron-updater": "^6.1.4",
|
||||
"eslint": "^8.49.0",
|
||||
"eslint-config-standard": "^17.1.0",
|
||||
"eslint-config-standard-with-typescript": "^39.0.0",
|
||||
"electron-to-chromium": "^1.4.385",
|
||||
"electron-updater": "^6.1.0",
|
||||
"eslint": "^8.40.0",
|
||||
"eslint-config-standard": "^17.0.0",
|
||||
"eslint-config-standard-with-typescript": "^34.0.1",
|
||||
"eslint-formatter-friendly": "github:lyswhut/eslint-friendly-formatter#2170d1320e2fad13615a9dcf229669f0bb473a53",
|
||||
"eslint-plugin-html": "^7.1.0",
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"eslint-plugin-vue-pug": "^0.6.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-n": "^15.7.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-vue": "^9.11.1",
|
||||
"eslint-webpack-plugin": "^4.0.1",
|
||||
"html-webpack-plugin": "^5.5.3",
|
||||
"less": "^4.2.0",
|
||||
"less-loader": "^11.1.3",
|
||||
"mini-css-extract-plugin": "^2.7.6",
|
||||
"html-webpack-plugin": "^5.5.1",
|
||||
"less": "^4.1.3",
|
||||
"less-loader": "^11.1.0",
|
||||
"mini-css-extract-plugin": "^2.7.5",
|
||||
"node-loader": "^2.0.0",
|
||||
"postcss": "^8.4.29",
|
||||
"postcss-loader": "^7.3.3",
|
||||
"postcss": "^8.4.23",
|
||||
"postcss-loader": "^7.3.0",
|
||||
"postcss-pxtorem": "^6.0.0",
|
||||
"pug": "^3.0.2",
|
||||
"pug-plain-loader": "^1.1.0",
|
||||
"rimraf": "^5.0.1",
|
||||
"rimraf": "^5.0.0",
|
||||
"spinnies": "github:lyswhut/spinnies#233305c58694aa3b053e3ab9af9049993f918b9d",
|
||||
"svg-sprite-loader": "^6.0.11",
|
||||
"svg-transform-loader": "^2.0.13",
|
||||
"svgo-loader": "^4.0.0",
|
||||
"terser": "^5.19.4",
|
||||
"terser-webpack-plugin": "^5.3.9",
|
||||
"ts-loader": "^9.4.4",
|
||||
"typescript": "^5.1.6",
|
||||
"vue-eslint-parser": "^9.3.1",
|
||||
"vue-loader": "^17.2.2",
|
||||
"terser": "^5.17.1",
|
||||
"terser-webpack-plugin": "^5.3.8",
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^5.0.4",
|
||||
"vue-eslint-parser": "^9.2.1",
|
||||
"vue-loader": "^17.1.0",
|
||||
"vue-template-compiler": "^2.7.14",
|
||||
"webpack": "^5.88.2",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "^4.15.1",
|
||||
"webpack": "^5.82.0",
|
||||
"webpack-cli": "^5.1.0",
|
||||
"webpack-dev-server": "^4.15.0",
|
||||
"webpack-hot-middleware": "github:lyswhut/webpack-hot-middleware#329c4375134b89d39da23a56a94db651247c74a1",
|
||||
"webpack-merge": "^5.9.0"
|
||||
"webpack-merge": "^5.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@simonwep/pickr": "^1.8.2",
|
||||
"better-sqlite3": "^8.6.0",
|
||||
"better-sqlite3": "^8.3.0",
|
||||
"bufferutil": "^4.0.7",
|
||||
"comlink": "~4.3.1",
|
||||
"crypto-js": "^4.1.1",
|
||||
"electron-font-manager": "github:lyswhut/electron-font-manager#6d2f5ecf850c4fe34812b9394913680462ee0dae",
|
||||
"electron-log": "^5.0.0-beta.28",
|
||||
"electron-log": "^4.4.8",
|
||||
"electron-store": "^8.1.0",
|
||||
"font-list": "^1.5.1",
|
||||
"font-list": "^1.4.5",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"image-size": "^1.0.2",
|
||||
"jschardet": "^3.0.0",
|
||||
"long": "^5.2.3",
|
||||
"message2call": "^0.1.2",
|
||||
"music-metadata": "^8.1.4",
|
||||
"needle": "github:lyswhut/needle#93299ac841b7e9a9f82ca7279b88aaaeda404060",
|
||||
"node-id3": "^0.2.6",
|
||||
"sortablejs": "^1.15.0",
|
||||
"tunnel": "^0.0.6",
|
||||
"utf-8-validate": "^6.0.3",
|
||||
"vue": "~3.2.47",
|
||||
"vue-router": "^4.2.4",
|
||||
"ws": "^8.14.1"
|
||||
"vue": "^3.2.47",
|
||||
"vue-router": "^4.1.6",
|
||||
"ws": "^8.13.0"
|
||||
},
|
||||
"overrides": {
|
||||
"got": "^11",
|
||||
"json5": "latest",
|
||||
"minimatch": "latest",
|
||||
"semver": "latest",
|
||||
"svg-sprite-loader": {
|
||||
"postcss": "latest"
|
||||
},
|
||||
|
||||
@@ -1,47 +1,7 @@
|
||||
目前本项目的原始发布地址只有 **GitHub** 及 **蓝奏网盘** ,其他渠道均为第三方转载发布,可信度请自行鉴别。
|
||||
本项目无微信公众号之类的官方账号,谨防被骗。
|
||||
|
||||
### 不兼容性变更
|
||||
|
||||
该版本修改了同步协议逻辑,同步功能至少需要PC端v2.4.0或移动端v1.1.0版本或同步服务v2.0.0才能连接使用。
|
||||
|
||||
### 新增
|
||||
|
||||
- 新增我的列表名右键菜单-排序歌曲-随机乱序功能,使用它可以对选中列表内歌曲进行随机重排(#1440)
|
||||
- 新增数据同步服务端模式已认证设备列表管理,该功能位置:设置-数据同步-服务端模式-已认证设备列表
|
||||
- 新增“不喜欢歌曲”功能,可以在我的列表或者在线列表内歌曲的右击菜单使用,还可以去“设置-其他”手动编辑不喜欢规则,注:“上一曲”、“下一曲”功能将跳过符合“不喜欢歌曲”规则的歌曲,但你仍可以手动播放这些歌曲
|
||||
- 新增同步功能对“不喜欢歌曲”列表的同步
|
||||
- 新增软件内快捷键“不喜欢该歌曲”设置,全局快捷键“收藏歌曲”、“取消收藏”、“不喜欢该歌曲”设置
|
||||
- 新增设置-播放设置-点击相同列表内的歌曲切歌时是否清空已播放列表(随机模式下列表内所有歌曲会重新参与随机)选项,默认关闭
|
||||
|
||||
### 优化
|
||||
|
||||
- 优化音效设置-环境音效启用、禁用时的操作效果显示,修复禁用环境音效时仍然可以调整增益、新增预设的问题
|
||||
- 过滤翻译歌词或罗马音歌词中只有“//”的行(#1499)
|
||||
- 点击打开歌单弹窗背景将不再自动关闭弹窗,防止选择输入框里的内容时意外关闭弹窗
|
||||
- 优化数据传输逻辑,列表同步指令使用队列机制,保证列表同步操作的顺序
|
||||
- 优化桌面歌词在开启 缩放当前播放的歌词 并关闭 延迟歌词滚动 时的歌词滚动位置计算问题,现在歌词滚动应该可以正确滚动到目标位置了
|
||||
- 优化歌词在短时间内快速播放时的滚动效果,现在遇到这种情况时滚动将更平滑
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复字体设置某些字体无法应用的问题
|
||||
- 修复搜索提示功能失效的问题(#1452, @Folltoshe)
|
||||
- 修复我的列表名右键菜单-排序歌曲按专辑名排序无效的问题(#1440)
|
||||
- 修复若路径存在 # 字符时,软件无法启动的问题
|
||||
- 修复搜索框在某些情况下输入内容后搜索时会自动清空的问题(#1472)
|
||||
- 修复某些tx源歌词因数据异常解析失败的问题
|
||||
- 修复windows平台下隐藏窗口后再显示时任务栏按钮丢失的问题
|
||||
- 修复首句歌词被提前播放的问题
|
||||
- 修复潜在导致列表数据不同步的问题
|
||||
- 修复kg无评论时的加载处理问题
|
||||
|
||||
### 变更
|
||||
|
||||
- 播放模式应该只适用于列表内的歌曲,所以单曲循环模式不应对“稍后播放”的歌曲有效,该行为现在与移动端一致
|
||||
- 随机模式下,通过点击与播放列表相同的列表切歌时,将不再清空已播放列表,即已播放的歌曲不再重新参与随机,若想恢复之前的行为可以去设置-播放设置启用清空已播放列表选项
|
||||
- 新增音效设置(实验性功能),支持10段均衡器设置、内置的一些环境混响音效、3D立体环绕音效
|
||||
|
||||
### 其他
|
||||
|
||||
- 更新 electron 到 v22.3.23
|
||||
- 重构同步服务端功能部分代码,使其更易扩展新功能
|
||||
- 更新 electron 到 v22.3.8
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,15 +0,0 @@
|
||||
/* eslint-env node */
|
||||
const { base, typescript } = require('../../.eslintrc.base.cjs')
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
...base,
|
||||
overrides: [
|
||||
{
|
||||
...typescript,
|
||||
parserOptions: {
|
||||
project: './tsconfig.json',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -1,9 +1,5 @@
|
||||
export const URL_SCHEME_RXP = /^lxmusic:\/\//
|
||||
|
||||
export const SPLIT_CHAR = {
|
||||
DISLIKE_NAME: '@',
|
||||
DISLIKE_NAME_ALIAS: '#',
|
||||
} as const
|
||||
|
||||
export const STORE_NAMES = {
|
||||
APP_SETTINGS: 'config_v2',
|
||||
@@ -54,8 +50,8 @@ export const DEFAULT_SETTING = {
|
||||
},
|
||||
|
||||
songList: {
|
||||
source: 'kw',
|
||||
sortId: 'new',
|
||||
source: 'kg',
|
||||
sortId: '5',
|
||||
tagId: '',
|
||||
},
|
||||
|
||||
@@ -80,3 +76,24 @@ export const DOWNLOAD_STATUS = {
|
||||
} as const
|
||||
|
||||
export const QUALITYS = ['flac24bit', 'flac', 'wav', 'ape', '320k', '192k', '128k'] as const
|
||||
|
||||
export const SYNC_CODE = {
|
||||
helloMsg: 'Hello~::^-^::~v3~',
|
||||
idPrefix: 'OjppZDo6',
|
||||
authMsg: 'lx-music auth::',
|
||||
authFailed: 'Auth failed',
|
||||
missingAuthCode: 'Missing auth code',
|
||||
getServiceIdFailed: 'Get service id failed',
|
||||
connectServiceFailed: 'Connect service failed',
|
||||
connecting: 'Connecting...',
|
||||
unknownServiceAddress: 'Unknown service address',
|
||||
msgBlockedIp: 'Blocked IP',
|
||||
msgConnect: 'lx-music connect',
|
||||
msgAuthFailed: 'Auth failed',
|
||||
} as const
|
||||
|
||||
export const SYNC_CLOSE_CODE = {
|
||||
normal: 1000,
|
||||
failed: 4100,
|
||||
} as const
|
||||
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
export const ENV_PARAMS = [
|
||||
'PORT',
|
||||
'BIND_IP',
|
||||
'CONFIG_PATH',
|
||||
'LOG_PATH',
|
||||
'DATA_PATH',
|
||||
'PROXY_HEADER',
|
||||
'MAX_SNAPSHOT_NUM',
|
||||
'LIST_ADD_MUSIC_LOCATION_TYPE',
|
||||
'LX_USER_',
|
||||
] as const
|
||||
|
||||
|
||||
export const LIST_IDS = {
|
||||
DEFAULT: 'default',
|
||||
LOVE: 'love',
|
||||
TEMP: 'temp',
|
||||
DOWNLOAD: 'download',
|
||||
PLAY_LATER: null,
|
||||
} as const
|
||||
|
||||
export const SYNC_CODE = {
|
||||
helloMsg: 'Hello~::^-^::~v4~',
|
||||
idPrefix: 'OjppZDo6',
|
||||
authMsg: 'lx-music auth::',
|
||||
msgAuthFailed: 'Auth failed',
|
||||
msgBlockedIp: 'Blocked IP',
|
||||
msgConnect: 'lx-music connect',
|
||||
|
||||
|
||||
authFailed: 'Auth failed',
|
||||
missingAuthCode: 'Missing auth code',
|
||||
getServiceIdFailed: 'Get service id failed',
|
||||
connectServiceFailed: 'Connect service failed',
|
||||
connecting: 'Connecting...',
|
||||
unknownServiceAddress: 'Unknown service address',
|
||||
} as const
|
||||
|
||||
export const SYNC_CLOSE_CODE = {
|
||||
normal: 1000,
|
||||
failed: 4100,
|
||||
} as const
|
||||
|
||||
export const TRANS_MODE: Readonly<Record<LX.Sync.List.SyncMode, LX.Sync.List.SyncMode>> = {
|
||||
merge_local_remote: 'merge_remote_local',
|
||||
merge_remote_local: 'merge_local_remote',
|
||||
overwrite_local_remote: 'overwrite_remote_local',
|
||||
overwrite_remote_local: 'overwrite_local_remote',
|
||||
overwrite_local_remote_full: 'overwrite_remote_local_full',
|
||||
overwrite_remote_local_full: 'overwrite_local_remote_full',
|
||||
cancel: 'cancel',
|
||||
} as const
|
||||
|
||||
export const File = {
|
||||
serverDataPath: 'sync/server',
|
||||
clientDataPath: 'sync/client',
|
||||
|
||||
serverInfoJSON: 'serverInfo.json',
|
||||
userDir: 'users',
|
||||
userDevicesJSON: 'devices.json',
|
||||
listDir: 'list',
|
||||
listSnapshotDir: 'snapshot',
|
||||
listSnapshotInfoJSON: 'snapshotInfo.json',
|
||||
dislikeDir: 'dislike',
|
||||
dislikeSnapshotDir: 'snapshot',
|
||||
dislikeSnapshotInfoJSON: 'snapshotInfo.json',
|
||||
|
||||
syncAuthKeysJSON: 'syncAuthKey.json',
|
||||
} as const
|
||||
|
||||
export const FeaturesList = [
|
||||
'list',
|
||||
'dislike',
|
||||
] as const
|
||||
@@ -1,5 +1,5 @@
|
||||
import path from 'node:path'
|
||||
import os from 'node:os'
|
||||
import { join } from 'path'
|
||||
import { homedir } from 'os'
|
||||
|
||||
const isMac = process.platform == 'darwin'
|
||||
const isWin = process.platform == 'win32'
|
||||
@@ -29,7 +29,6 @@ const defaultSetting: LX.AppSetting = {
|
||||
'player.volume': 1,
|
||||
'player.isMute': false,
|
||||
'player.playbackRate': 1,
|
||||
'player.preservesPitch': true,
|
||||
'player.mediaDeviceId': 'default',
|
||||
'player.isMediaDeviceRemovedStopPlay': false,
|
||||
'player.isShowLyricTranslation': false,
|
||||
@@ -41,7 +40,6 @@ const defaultSetting: LX.AppSetting = {
|
||||
'player.waitPlayEndStop': true,
|
||||
'player.waitPlayEndStopTime': '',
|
||||
'player.autoSkipOnError': true,
|
||||
'player.isAutoCleanPlayedList': false,
|
||||
'player.soundEffect.convolution.fileName': '',
|
||||
'player.soundEffect.convolution.mainGain': 10,
|
||||
'player.soundEffect.convolution.sendGain': 0,
|
||||
@@ -58,7 +56,6 @@ const defaultSetting: LX.AppSetting = {
|
||||
'player.soundEffect.panner.enable': false,
|
||||
'player.soundEffect.panner.soundR': 5,
|
||||
'player.soundEffect.panner.speed': 25,
|
||||
'player.soundEffect.pitchShifter.playbackRate': 1,
|
||||
|
||||
'playDetail.isZoomActiveLrc': false,
|
||||
'playDetail.isShowLyricProgressSetting': false,
|
||||
@@ -76,7 +73,7 @@ const defaultSetting: LX.AppSetting = {
|
||||
'desktopLyric.height': 300,
|
||||
'desktopLyric.x': null,
|
||||
'desktopLyric.y': null,
|
||||
'desktopLyric.isLockScreen': isWin,
|
||||
'desktopLyric.isLockScreen': true,
|
||||
'desktopLyric.isDelayScroll': true,
|
||||
'desktopLyric.scrollAlign': 'center',
|
||||
'desktopLyric.isHoverHide': false,
|
||||
@@ -103,7 +100,7 @@ const defaultSetting: LX.AppSetting = {
|
||||
'list.actionButtonsVisible': false,
|
||||
|
||||
'download.enable': false,
|
||||
'download.savePath': path.join(os.homedir(), 'Desktop'),
|
||||
'download.savePath': join(homedir(), 'Desktop'),
|
||||
'download.fileName': '歌名 - 歌手',
|
||||
'download.maxDownloadNum': 3,
|
||||
'download.skipExistFile': true,
|
||||
@@ -137,8 +134,8 @@ const defaultSetting: LX.AppSetting = {
|
||||
'sync.server.maxSsnapshotNum': 5,
|
||||
'sync.client.host': '',
|
||||
|
||||
// 'theme.id': 'blue_plus',
|
||||
'theme.id': 'green',
|
||||
'theme.id': 'blue_plus',
|
||||
// 'theme.id': 'green',
|
||||
'theme.lightId': 'green',
|
||||
'theme.darkId': 'black',
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ const ignoreErrorMessage = [
|
||||
]
|
||||
|
||||
process.on('uncaughtException', err => {
|
||||
if (ignoreErrorMessage.includes(err?.message)) return
|
||||
if (ignoreErrorMessage.includes(err.message)) return
|
||||
console.error('An uncaught error occurred!')
|
||||
console.error(err)
|
||||
log.error(err)
|
||||
|
||||
@@ -66,21 +66,6 @@ const hotKey = {
|
||||
action: 'volume_mute',
|
||||
type: '',
|
||||
},
|
||||
music_love: {
|
||||
name: 'music_love',
|
||||
action: 'music_love',
|
||||
type: '',
|
||||
},
|
||||
music_unlove: {
|
||||
name: 'music_unlove',
|
||||
action: 'music_unlove',
|
||||
type: '',
|
||||
},
|
||||
music_dislike: {
|
||||
name: 'music_dislike',
|
||||
action: 'music_dislike',
|
||||
type: '',
|
||||
},
|
||||
},
|
||||
desktop_lyric: {
|
||||
toggle_visible: {
|
||||
|
||||
@@ -36,12 +36,6 @@ const modules = {
|
||||
list_music_check_exist: 'list_music_check_exist',
|
||||
list_music_get_list_ids: 'list_music_get_list_ids',
|
||||
},
|
||||
dislike: {
|
||||
get_dislike_music_infos: 'get_dislike_music_infos',
|
||||
add_dislike_music_infos: 'add_dislike_music_infos',
|
||||
overwrite_dislike_music_infos: 'overwrite_dislike_music_infos',
|
||||
clear_dislike_music_infos: 'clear_dislike_music_infos',
|
||||
},
|
||||
winMain: {
|
||||
focus: 'focus',
|
||||
close: 'close',
|
||||
@@ -100,8 +94,6 @@ const modules = {
|
||||
save_sound_effect_eq_preset: 'save_sound_effect_eq_preset',
|
||||
get_sound_effect_convolution_preset: 'get_sound_effect_convolution_preset',
|
||||
save_sound_effect_convolution_preset: 'save_sound_effect_convolution_preset',
|
||||
// get_sound_effect_pitch_shifter_preset: 'get_sound_effect_pitch_shifter_preset',
|
||||
// save_sound_effect_pitch_shifter_preset: 'save_sound_effect_pitch_shifter_preset',
|
||||
get_hot_key: 'get_hot_key',
|
||||
|
||||
import_user_api: 'import_user_api',
|
||||
@@ -133,8 +125,6 @@ const modules = {
|
||||
get_music_url_count: 'get_music_url_count',
|
||||
|
||||
sync_action: 'sync_action',
|
||||
sync_get_server_devices: 'sync_get_server_devices',
|
||||
sync_remove_server_device: 'sync_remove_server_device',
|
||||
|
||||
process_new_desktop_lyric_client: 'process_new_desktop_lyric_client',
|
||||
|
||||
@@ -188,7 +178,6 @@ for (const moduleName of Object.keys(modules) as Array<keyof typeof modules>) {
|
||||
|
||||
export const CMMON_EVENT_NAME = modules.common
|
||||
export const PLAYER_EVENT_NAME = modules.player
|
||||
export const DISLIKE_EVENT_NAME = modules.dislike
|
||||
export const WIN_MAIN_RENDERER_EVENT_NAME = modules.winMain
|
||||
export const WIN_LYRIC_RENDERER_EVENT_NAME = modules.winLyric
|
||||
export const HOTKEY_RENDERER_EVENT_NAME = modules.hotKey
|
||||
|
||||
@@ -30,7 +30,7 @@ export function mainHandle<V>(name: string, listener: LX.IpcMainInvokeEventListe
|
||||
export function mainHandle<T, V>(name: string, listener: LX.IpcMainInvokeEventListenerParamsValue<T, V>): void
|
||||
export function mainHandle<T, V>(name: string, listener: LX.IpcMainInvokeEventListenerParamsValue<T, V>): void {
|
||||
ipcMain.handle(name, async(event, params) => {
|
||||
return listener({ event, params })
|
||||
return await listener({ event, params })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ export function mainHandleOnce<V>(name: string, listener: LX.IpcMainInvokeEventL
|
||||
export function mainHandleOnce<T, V>(name: string, listener: LX.IpcMainInvokeEventListenerParamsValue<T, V>): void
|
||||
export function mainHandleOnce<T, V>(name: string, listener: LX.IpcMainInvokeEventListenerParamsValue<T, V>): void {
|
||||
ipcMain.handleOnce(name, async(event, params) => {
|
||||
return listener({ event, params })
|
||||
return await listener({ event, params })
|
||||
})
|
||||
}
|
||||
export const mainHandleRemove = (name: string) => {
|
||||
|
||||
@@ -17,7 +17,7 @@ export async function rendererInvoke<V>(name: string): Promise<V>
|
||||
export async function rendererInvoke<T>(name: string, params: T): Promise<void>
|
||||
export async function rendererInvoke<T, V>(name: string, params: T): Promise<V>
|
||||
export async function rendererInvoke <T, V>(name: string, params?: T): Promise<V> {
|
||||
return ipcRenderer.invoke(name, params)
|
||||
return await ipcRenderer.invoke(name, params)
|
||||
}
|
||||
|
||||
export function rendererOn(name: string, listener: LX.IpcRendererEventListener): void
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* @param {string} c1 rgb(a) color2
|
||||
* @returns color
|
||||
*/
|
||||
export const RGB_Linear_Blend=(p,c0,c1)=>{
|
||||
exports.RGB_Linear_Blend=(p,c0,c1)=>{
|
||||
var i=parseInt,r=Math.round,P=1-p,[a,b,c,d]=c0.split(","),[e,f,g,h]=c1.split(","),x=d||h,j=x?","+(!d?h:!h?d:r((parseFloat(d)*P+parseFloat(h)*p)*1000)/1000+")"):")";
|
||||
return"rgb"+(x?"a(":"(")+r(i(a[3]=="a"?a.slice(5):a.slice(4))*P+i(e[3]=="a"?e.slice(5):e.slice(4))*p)+","+r(i(b)*P+i(f)*p)+","+r(i(c)*P+i(g)*p)+j;
|
||||
}
|
||||
@@ -20,7 +20,7 @@ export const RGB_Linear_Blend=(p,c0,c1)=>{
|
||||
* @param {string} c1 rgb(a) color2
|
||||
* @returns color
|
||||
*/
|
||||
export const RGB_Log_Blend=(p,c0,c1)=>{
|
||||
exports.RGB_Log_Blend=(p,c0,c1)=>{
|
||||
var i=parseInt,r=Math.round,P=1-p,[a,b,c,d]=c0.split(","),[e,f,g,h]=c1.split(","),x=d||h,j=x?","+(!d?h:!h?d:r((parseFloat(d)*P+parseFloat(h)*p)*1000)/1000+")"):")";
|
||||
return"rgb"+(x?"a(":"(")+r((P*i(a[3]=="a"?a.slice(5):a.slice(4))**2+p*i(e[3]=="a"?e.slice(5):e.slice(4))**2)**0.5)+","+r((P*i(b)**2+p*i(f)**2)**0.5)+","+r((P*i(c)**2+p*i(g)**2)**0.5)+j;
|
||||
}
|
||||
@@ -32,7 +32,7 @@ export const RGB_Log_Blend=(p,c0,c1)=>{
|
||||
* @param {string} c0 rgb(a) color
|
||||
* @returns color
|
||||
*/
|
||||
export const RGB_Linear_Shade=(p,c0)=>{
|
||||
exports.RGB_Linear_Shade=(p,c0)=>{
|
||||
var i=parseInt,r=Math.round,[a,b,c,d]=c0.split(","),n=p<0,t=n?0:255*p,P=n?1+p:1-p;
|
||||
return"rgb"+(d?"a(":"(")+r(i(a[3]=="a"?a.slice(5):a.slice(4))*P+t)+","+r(i(b)*P+t)+","+r(i(c)*P+t)+(d?","+d:")");
|
||||
}
|
||||
@@ -44,7 +44,7 @@ export const RGB_Linear_Shade=(p,c0)=>{
|
||||
* @param {string} c0 rgb(a) color
|
||||
* @returns color
|
||||
*/
|
||||
export const RGB_Log_Shade=(p,c0)=>{
|
||||
exports.RGB_Log_Shade=(p,c0)=>{
|
||||
var i=parseInt,r=Math.round,[a,b,c,d]=c0.split(","),n=p<0,t=n?0:p*255**2,P=n?1+p:1-p;
|
||||
return"rgb"+(d?"a(":"(")+r((P*i(a[3]=="a"?a.slice(5):a.slice(4))**2+t)**0.5)+","+r((P*i(b)**2+t)**0.5)+","+r((P*i(c)**2+t)**0.5)+(d?","+d:")");
|
||||
}
|
||||
@@ -56,7 +56,7 @@ export const RGB_Log_Shade=(p,c0)=>{
|
||||
* @param {string} color
|
||||
* @returns color
|
||||
*/
|
||||
export const RGB_Alpha_Shade = (p, color) => {
|
||||
exports.RGB_Alpha_Shade = (p, color) => {
|
||||
var i = parseInt
|
||||
var n = p < 0
|
||||
var [r, g, b, a] = color.split(",")
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//! 更新默认主题配置后,需要执行 npm run build:theme 重新构建index.json
|
||||
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { createThemeColors } from './utils'
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { createThemeColors } = require('./utils')
|
||||
|
||||
const defaultThemes = [
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { RGB_Linear_Shade, RGB_Alpha_Shade } from './colorUtils'
|
||||
const { RGB_Linear_Shade, RGB_Alpha_Shade } = require('./colorUtils')
|
||||
|
||||
export const createThemeColors = (rgbaColor, fontRgbaColor, isDark) => {
|
||||
exports.createThemeColors = (rgbaColor, fontRgbaColor, isDark) => {
|
||||
const colors = {
|
||||
'--color-primary': rgbaColor,
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"typeRoots": [
|
||||
"module": "esnext",
|
||||
"moduleResolution": "nodenext",
|
||||
"typeRoots": [ /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
"./types"
|
||||
],
|
||||
"paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
"@common/*": ["common/*"],
|
||||
},
|
||||
},
|
||||
// "include": [
|
||||
// "**/*.ts",
|
||||
// "**/*.js",
|
||||
// "**/*.vue",
|
||||
// "**/*.json",
|
||||
// ],
|
||||
}
|
||||
|
||||
17
src/common/types/app_setting.d.ts
vendored
17
src/common/types/app_setting.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
import type { I18n } from '@root/lang/i18n'
|
||||
import type { I18n } from '@/lang/i18n'
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -113,11 +113,6 @@ declare global {
|
||||
*/
|
||||
'player.playbackRate': number
|
||||
|
||||
/**
|
||||
* 是否自动调整音频的音高以补偿对播放速率设置所做的更改
|
||||
*/
|
||||
'player.preservesPitch': boolean
|
||||
|
||||
/**
|
||||
* 音频输出设备id
|
||||
*/
|
||||
@@ -248,21 +243,11 @@ declare global {
|
||||
*/
|
||||
'player.soundEffect.panner.speed': number
|
||||
|
||||
/**
|
||||
* 升降声调
|
||||
*/
|
||||
'player.soundEffect.pitchShifter.playbackRate': number
|
||||
|
||||
/**
|
||||
* 是否启用音频加载失败时自动切歌
|
||||
*/
|
||||
'player.autoSkipOnError': boolean
|
||||
|
||||
/**
|
||||
* 点击相同列表内的歌曲切歌时是否清空已播放列表(随机模式下列表内所有歌曲会重新参与随机)
|
||||
*/
|
||||
'player.isAutoCleanPlayedList': boolean
|
||||
|
||||
/**
|
||||
* 播放详情页-是否缩放当前播放的歌词行
|
||||
*/
|
||||
|
||||
37
src/common/types/dislike_list.d.ts
vendored
37
src/common/types/dislike_list.d.ts
vendored
@@ -1,37 +0,0 @@
|
||||
|
||||
|
||||
declare namespace LX {
|
||||
namespace Dislike {
|
||||
// interface ListItemMusicText {
|
||||
// id?: string
|
||||
// // type: 'music'
|
||||
// name: string | null
|
||||
// singer: string | null
|
||||
// }
|
||||
// interface ListItemMusic {
|
||||
// id?: number
|
||||
// type: 'musicId'
|
||||
// musicId: string
|
||||
// meta: LX.Music.MusicInfo
|
||||
// }
|
||||
// type ListItem = ListItemMusicText
|
||||
// type ListItem = string
|
||||
// type ListItem = ListItemMusic | ListItemMusicText
|
||||
|
||||
interface DislikeMusicInfo {
|
||||
name: string
|
||||
singer: string
|
||||
}
|
||||
|
||||
type DislikeRules = string
|
||||
|
||||
interface DislikeInfo {
|
||||
// musicIds: Set<string>
|
||||
names: Set<string>
|
||||
musicNames: Set<string>
|
||||
singerNames: Set<string>
|
||||
// list: LX.Dislike.ListItem[]
|
||||
rules: DislikeRules
|
||||
}
|
||||
}
|
||||
}
|
||||
30
src/common/types/dislike_list_sync.d.ts
vendored
30
src/common/types/dislike_list_sync.d.ts
vendored
@@ -1,30 +0,0 @@
|
||||
declare namespace LX {
|
||||
|
||||
namespace Sync {
|
||||
namespace Dislike {
|
||||
interface ListInfo {
|
||||
lastSyncDate?: number
|
||||
snapshotKey: string
|
||||
}
|
||||
|
||||
interface SyncActionBase <A> {
|
||||
action: A
|
||||
}
|
||||
interface SyncActionData<A, D> extends SyncActionBase<A> {
|
||||
data: D
|
||||
}
|
||||
type SyncAction<A, D = undefined> = D extends undefined ? SyncActionBase<A> : SyncActionData<A, D>
|
||||
type ActionList = SyncAction<'dislike_data_overwrite', LX.Dislike.DislikeRules>
|
||||
| SyncAction<'dislike_music_add', LX.Dislike.DislikeMusicInfo[]>
|
||||
| SyncAction<'dislike_music_clear'>
|
||||
|
||||
type SyncMode = 'merge_local_remote'
|
||||
| 'merge_remote_local'
|
||||
| 'overwrite_local_remote'
|
||||
| 'overwrite_remote_local'
|
||||
// | 'none'
|
||||
| 'cancel'
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
2
src/common/types/download_list.d.ts
vendored
2
src/common/types/download_list.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
import { type Message } from '@root/lang'
|
||||
import { type Message } from '@/lang'
|
||||
|
||||
// interface DownloadList {
|
||||
|
||||
|
||||
1
src/common/types/list.d.ts
vendored
1
src/common/types/list.d.ts
vendored
@@ -139,5 +139,6 @@ declare namespace LX {
|
||||
userList: UserListInfoFull[]
|
||||
tempList: LX.Music.MusicInfo[]
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
34
src/common/types/list_sync.d.ts
vendored
34
src/common/types/list_sync.d.ts
vendored
@@ -1,34 +0,0 @@
|
||||
declare namespace LX {
|
||||
|
||||
namespace Sync {
|
||||
namespace List {
|
||||
interface ListInfo {
|
||||
lastSyncDate?: number
|
||||
snapshotKey: string
|
||||
}
|
||||
|
||||
type ActionList = LX.Sync.SyncAction<'list_data_overwrite', LX.List.ListActionDataOverwrite>
|
||||
| SyncAction<'list_create', LX.List.ListActionAdd>
|
||||
| SyncAction<'list_remove', LX.List.ListActionRemove>
|
||||
| SyncAction<'list_update', LX.List.ListActionUpdate>
|
||||
| SyncAction<'list_update_position', LX.List.ListActionUpdatePosition>
|
||||
| SyncAction<'list_music_add', LX.List.ListActionMusicAdd>
|
||||
| SyncAction<'list_music_move', LX.List.ListActionMusicMove>
|
||||
| SyncAction<'list_music_remove', LX.List.ListActionMusicRemove>
|
||||
| SyncAction<'list_music_update', LX.List.ListActionMusicUpdate>
|
||||
| SyncAction<'list_music_update_position', LX.List.ListActionMusicUpdatePosition>
|
||||
| SyncAction<'list_music_overwrite', LX.List.ListActionMusicOverwrite>
|
||||
| SyncAction<'list_music_clear', LX.List.ListActionMusicClear>
|
||||
|
||||
type ListData = Omit<LX.List.ListDataFull, 'tempList'>
|
||||
type SyncMode = 'merge_local_remote'
|
||||
| 'merge_remote_local'
|
||||
| 'overwrite_local_remote'
|
||||
| 'overwrite_remote_local'
|
||||
| 'overwrite_local_remote_full'
|
||||
| 'overwrite_remote_local_full'
|
||||
// | 'none'
|
||||
| 'cancel'
|
||||
}
|
||||
}
|
||||
}
|
||||
5
src/common/types/sound_effect.d.ts
vendored
5
src/common/types/sound_effect.d.ts
vendored
@@ -21,10 +21,5 @@ declare namespace LX {
|
||||
mainGain: number
|
||||
sendGain: number
|
||||
}
|
||||
// interface PitchShifterPreset {
|
||||
// id: string
|
||||
// name: string
|
||||
// playbackRate: number
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
73
src/common/types/sync.d.ts
vendored
73
src/common/types/sync.d.ts
vendored
@@ -19,27 +19,52 @@ declare namespace LX {
|
||||
}
|
||||
type SyncAction<A, D = undefined> = D extends undefined ? SyncActionBase<A> : SyncActionData<A, D>
|
||||
|
||||
|
||||
interface ModeTypes {
|
||||
list: LX.Sync.List.SyncMode
|
||||
dislike: LX.Sync.Dislike.SyncMode
|
||||
}
|
||||
|
||||
type ModeType = { [K in keyof ModeTypes]: { type: K, mode: ModeTypes[K] } }[keyof ModeTypes]
|
||||
|
||||
type SyncMainWindowActions = SyncAction<'select_mode', { deviceName: string, type: keyof ModeTypes }>
|
||||
type SyncMainWindowActions = SyncAction<'select_mode', string>
|
||||
| SyncAction<'close_select_mode'>
|
||||
| SyncAction<'client_status', ClientStatus>
|
||||
| SyncAction<'server_status', ServerStatus>
|
||||
|
||||
type SyncServiceActions = SyncAction<'select_mode', ModeType>
|
||||
type SyncServiceActions = SyncAction<'select_mode', Mode>
|
||||
| SyncAction<'get_server_status'>
|
||||
| SyncAction<'get_client_status'>
|
||||
| SyncAction<'generate_code'>
|
||||
| SyncAction<'enable_server', EnableServer>
|
||||
| SyncAction<'enable_client', EnableClient>
|
||||
|
||||
type ServerDevices = ServerKeyInfo[]
|
||||
type ActionList = SyncAction<'list_data_overwrite', LX.List.ListActionDataOverwrite>
|
||||
| SyncAction<'list_create', LX.List.ListActionAdd>
|
||||
| SyncAction<'list_remove', LX.List.ListActionRemove>
|
||||
| SyncAction<'list_update', LX.List.ListActionUpdate>
|
||||
| SyncAction<'list_update_position', LX.List.ListActionUpdatePosition>
|
||||
| SyncAction<'list_music_add', LX.List.ListActionMusicAdd>
|
||||
| SyncAction<'list_music_move', LX.List.ListActionMusicMove>
|
||||
| SyncAction<'list_music_remove', LX.List.ListActionMusicRemove>
|
||||
| SyncAction<'list_music_update', LX.List.ListActionMusicUpdate>
|
||||
| SyncAction<'list_music_update_position', LX.List.ListActionMusicUpdatePosition>
|
||||
| SyncAction<'list_music_overwrite', LX.List.ListActionMusicOverwrite>
|
||||
| SyncAction<'list_music_clear', LX.List.ListActionMusicClear>
|
||||
|
||||
type ActionSync = SyncAction<'list:sync:list_sync_get_md5', string>
|
||||
| SyncAction<'list:sync:list_sync_get_list_data', ListData>
|
||||
| SyncAction<'list:sync:list_sync_get_sync_mode', Mode>
|
||||
| SyncAction<'list:sync:action', ActionList>
|
||||
// | SyncAction<'finished'>
|
||||
|
||||
type ActionSyncType = Actions<ActionSync>
|
||||
|
||||
type ActionSyncSend = SyncAction<'list:sync:list_sync_get_md5'>
|
||||
| SyncAction<'list:sync:list_sync_get_list_data'>
|
||||
| SyncAction<'list:sync:list_sync_get_sync_mode'>
|
||||
| SyncAction<'list:sync:list_sync_set_data', LX.Sync.ListData>
|
||||
| SyncAction<'list:sync:action', ActionList>
|
||||
| SyncAction<'list:sync:finished'>
|
||||
|
||||
type ActionSyncSendType = Actions<ActionSyncSend>
|
||||
|
||||
interface List {
|
||||
action: string
|
||||
data: any
|
||||
}
|
||||
|
||||
interface ServerStatus {
|
||||
status: boolean
|
||||
@@ -65,21 +90,21 @@ declare namespace LX {
|
||||
clientId: string
|
||||
key: string
|
||||
deviceName: string
|
||||
lastConnectDate?: number
|
||||
lastSyncDate?: number
|
||||
snapshotKey: string
|
||||
isMobile: boolean
|
||||
}
|
||||
|
||||
interface ListConfig {
|
||||
skipSnapshot: boolean
|
||||
}
|
||||
interface DislikeConfig {
|
||||
skipSnapshot: boolean
|
||||
}
|
||||
type ServerType = 'desktop-app' | 'server'
|
||||
interface EnabledFeatures {
|
||||
list?: false | ListConfig
|
||||
dislike?: false | DislikeConfig
|
||||
}
|
||||
type SupportedFeatures = Partial<{ [k in keyof EnabledFeatures]: number }>
|
||||
type ListData = Omit<LX.List.ListDataFull, 'tempList'>
|
||||
|
||||
type Mode = 'merge_local_remote'
|
||||
| 'merge_remote_local'
|
||||
| 'overwrite_local_remote'
|
||||
| 'overwrite_remote_local'
|
||||
| 'overwrite_local_remote_full'
|
||||
| 'overwrite_remote_local_full'
|
||||
// | 'none'
|
||||
| 'cancel'
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
10
src/common/types/utils.d.ts
vendored
10
src/common/types/utils.d.ts
vendored
@@ -10,13 +10,3 @@ type Modify<T, R> = Omit<T, keyof R> & R
|
||||
type Actions<T extends { action: string, data?: any }> = {
|
||||
[U in T as U['action']]: 'data' extends keyof U ? U['data'] : undefined
|
||||
}
|
||||
|
||||
type WarpPromiseValue<T> = T extends ((...args: infer P) => Promise<infer R>)
|
||||
? ((...args: P) => Promise<R>)
|
||||
: T extends ((...args: infer P2) => infer R2)
|
||||
? ((...args: P2) => Promise<R2>)
|
||||
: Promise<T>
|
||||
|
||||
type WarpPromiseRecord<T extends Record<string, any>> = {
|
||||
[K in keyof T]: WarpPromiseValue<T[K]>
|
||||
}
|
||||
|
||||
@@ -225,23 +225,3 @@ export const arrPushByPosition = <T>(list: T[], newList: T[], position: number)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
|
||||
// https://stackoverflow.com/a/2450976
|
||||
export const arrShuffle = <T>(array: T[]) => {
|
||||
let currentIndex = array.length
|
||||
let randomIndex
|
||||
|
||||
// While there remain elements to shuffle.
|
||||
while (currentIndex != 0) {
|
||||
// Pick a remaining element.
|
||||
randomIndex = Math.floor(Math.random() * currentIndex)
|
||||
currentIndex--;
|
||||
|
||||
// And swap it with the current element.
|
||||
[array[currentIndex], array[randomIndex]] = [
|
||||
array[randomIndex], array[currentIndex]]
|
||||
}
|
||||
|
||||
return array
|
||||
}
|
||||
|
||||
@@ -37,8 +37,6 @@ class Task extends EventEmitter {
|
||||
progress = { total: 0, downloaded: 0, speed: 0, progress: 0 }
|
||||
statsEstimate = { time: 0, bytes: 0, prevBytes: 0 }
|
||||
requestInstance: http.ClientRequest | null = null
|
||||
maxRedirectNum = 2
|
||||
private redirectNum = 0
|
||||
|
||||
|
||||
constructor(url: string, savePath: string, filename: string, options: Partial<Options> = {}) {
|
||||
@@ -61,7 +59,6 @@ class Task extends EventEmitter {
|
||||
|
||||
async __init() {
|
||||
const { path, startByte, endByte } = this.chunkInfo
|
||||
this.redirectNum = 0
|
||||
this.progress.downloaded = 0
|
||||
this.progress.progress = 0
|
||||
this.progress.speed = 0
|
||||
@@ -115,7 +112,6 @@ class Task extends EventEmitter {
|
||||
|
||||
__httpFetch(url: string, options: Options['requestOptions']) {
|
||||
// console.log(options)
|
||||
let redirected = false
|
||||
this.requestInstance = request(url, options)
|
||||
.on('response', response => {
|
||||
if (response.statusCode !== 200 && response.statusCode !== 206) {
|
||||
@@ -129,15 +125,6 @@ class Task extends EventEmitter {
|
||||
})
|
||||
return
|
||||
}
|
||||
if ((response.statusCode == 301 || response.statusCode == 302) && response.headers.location && this.redirectNum < this.maxRedirectNum) {
|
||||
console.log('current url:', url)
|
||||
console.log('redirect to:', response.headers.location)
|
||||
redirected = true
|
||||
this.redirectNum++
|
||||
const location = response.headers.location
|
||||
this.__httpFetch(location, options)
|
||||
return
|
||||
}
|
||||
this.status = STATUS.failed
|
||||
this.emit('fail', response)
|
||||
this.__closeRequest()
|
||||
@@ -166,7 +153,6 @@ class Task extends EventEmitter {
|
||||
})
|
||||
.on('error', err => { this.__handleError(err) })
|
||||
.on('close', () => {
|
||||
if (redirected) return
|
||||
void this.__closeWriteStream()
|
||||
})
|
||||
.end()
|
||||
@@ -270,7 +256,7 @@ class Task extends EventEmitter {
|
||||
const result = this.__handleDiffChunk(chunk)
|
||||
if (result) chunk = result
|
||||
else {
|
||||
void this.__handleStop().finally(() => {
|
||||
this.__handleStop().finally(() => {
|
||||
// this.__handleError(new Error('Resume failed, response chunk does not match.'))
|
||||
// Resume failed, response chunk does not match, remove file and restart download
|
||||
console.log('Resume failed, response chunk does not match.')
|
||||
|
||||
110
src/common/utils/effects/snow.min.js
vendored
110
src/common/utils/effects/snow.min.js
vendored
@@ -4,24 +4,24 @@
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
// Change the following variables to customize the appearance of the particles
|
||||
let numParticles = window.innerWidth / 50
|
||||
let maxSpeed = 2.5
|
||||
let minSpeed = 1
|
||||
let maxSize = 5
|
||||
let minSize = 3
|
||||
let maxOpacity = 1
|
||||
let minOpacity = 0.2
|
||||
var numParticles = window.innerWidth / 50;
|
||||
var maxSpeed = 2.5;
|
||||
var minSpeed = 1;
|
||||
var maxSize = 5;
|
||||
var minSize = 3;
|
||||
var maxOpacity = 1;
|
||||
var minOpacity = 0.2;
|
||||
|
||||
// If the following is true, the canvas resolution will be set to match the full viewport width and height.
|
||||
// var fixCanvasResolution = true;
|
||||
|
||||
let snow_particles = []
|
||||
var snow_particles = [];
|
||||
|
||||
let canvas
|
||||
var canvas
|
||||
|
||||
window.onload = function() {
|
||||
canvas = document.createElement('canvas')
|
||||
canvas.style.position = 'fixed'
|
||||
canvas.style.position = 'fixed';
|
||||
canvas.style.top = '0px'
|
||||
canvas.style.left = '0px'
|
||||
canvas.style.pointerEvents = 'none'
|
||||
@@ -30,62 +30,62 @@ window.onload = function() {
|
||||
canvas.height = document.documentElement.clientHeight
|
||||
document.body.appendChild(canvas)
|
||||
|
||||
// if (fixCanvasResolution) {
|
||||
// // canvas.width = window.innerWidth;
|
||||
// canvas.height = window.innerHeight;
|
||||
// }
|
||||
// if (fixCanvasResolution) {
|
||||
// // canvas.width = window.innerWidth;
|
||||
// canvas.height = window.innerHeight;
|
||||
// }
|
||||
|
||||
InitPoints()
|
||||
InitPoints();
|
||||
|
||||
window.requestAnimationFrame(Redraw, 15)
|
||||
requestAnimationFrame(Redraw, 15);
|
||||
window.addEventListener('resize', this.onWindowResize)
|
||||
}
|
||||
// function onWindowResize(e) {
|
||||
// canvas.width = document.documentElement.clientWidth
|
||||
// canvas.height = document.documentElement.clientHeight
|
||||
// }
|
||||
function onWindowResize (e) {
|
||||
canvas.width = document.documentElement.clientWidth
|
||||
canvas.height = document.documentElement.clientHeight
|
||||
}
|
||||
|
||||
function Redraw() {
|
||||
let ctx = canvas.getContext('2d')
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
for (let i = 0; i < snow_particles.length; i++) {
|
||||
let newYPos = snow_particles[i].yPos + snow_particles[i].speed
|
||||
if (newYPos > window.innerHeight) {
|
||||
newYPos = getRandomInt(-100, -10)
|
||||
snow_particles[i].xPos = getRandomInt(0, window.innerWidth)
|
||||
snow_particles[i].speed = Math.random() * (maxSpeed - minSpeed) + minSpeed
|
||||
snow_particles[i].opacity = Math.random() * (maxOpacity - minOpacity) + minOpacity
|
||||
snow_particles[i].size = Math.random() * (maxSize - minSize) + minSize
|
||||
}
|
||||
snow_particles[i].yPos = newYPos
|
||||
ctx.beginPath()
|
||||
ctx.arc(snow_particles[i].xPos, newYPos, snow_particles[i].size, 0, 2 * Math.PI)
|
||||
ctx.fillStyle = 'rgba(255, 255, 255, ' + snow_particles[i].opacity + ')'
|
||||
ctx.fill()
|
||||
}
|
||||
window.requestAnimationFrame(Redraw)
|
||||
var ctx = canvas.getContext("2d");
|
||||
ctx.clearRect(0,0,canvas.width,canvas.height);
|
||||
for (var i = 0; i < snow_particles.length; i++) {
|
||||
var newYPos = snow_particles[i].yPos + snow_particles[i].speed;
|
||||
if (newYPos > window.innerHeight) {
|
||||
newYPos = getRandomInt(-100,-10);
|
||||
snow_particles[i].xPos = getRandomInt(0, window.innerWidth);
|
||||
snow_particles[i].speed = Math.random() * (maxSpeed - minSpeed) + minSpeed;
|
||||
snow_particles[i].opacity = Math.random() * (maxOpacity - minOpacity) + minOpacity;
|
||||
snow_particles[i].size = Math.random() * (maxSize - minSize) + minSize;
|
||||
}
|
||||
snow_particles[i].yPos = newYPos;
|
||||
ctx.beginPath();
|
||||
ctx.arc(snow_particles[i].xPos, newYPos, snow_particles[i].size, 0, 2 * Math.PI);
|
||||
ctx.fillStyle = "rgba(255, 255, 255, " + snow_particles[i].opacity + ")";
|
||||
ctx.fill();
|
||||
}
|
||||
requestAnimationFrame(Redraw)
|
||||
}
|
||||
|
||||
function InitPoints() {
|
||||
for (let i = 0; i < numParticles; i++) {
|
||||
let startX = getRandomInt(0, window.innerWidth)
|
||||
let startY = getRandomInt(0, window.innerHeight)
|
||||
let speed = Math.random() * (maxSpeed - minSpeed) + minSpeed
|
||||
let opacity = Math.random() * (maxOpacity - minOpacity) + minOpacity
|
||||
let size = Math.random() * (maxSize - minSize) + minSize
|
||||
for (var i = 0; i < numParticles; i++) {
|
||||
var startX = getRandomInt(0, window.innerWidth);
|
||||
var startY = getRandomInt(0, window.innerHeight);
|
||||
var speed = Math.random() * (maxSpeed - minSpeed) + minSpeed;
|
||||
var opacity = Math.random() * (maxOpacity - minOpacity) + minOpacity;
|
||||
var size = Math.random() * (maxSize - minSize) + minSize;
|
||||
|
||||
snow_particles.push({
|
||||
xPos: startX,
|
||||
yPos: startY,
|
||||
speed,
|
||||
opacity,
|
||||
size,
|
||||
})
|
||||
}
|
||||
snow_particles.push({
|
||||
"xPos": startX,
|
||||
"yPos": startY,
|
||||
"speed": speed,
|
||||
"opacity": opacity,
|
||||
"size": size,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getRandomInt(min, max) {
|
||||
min = Math.ceil(min)
|
||||
max = Math.floor(max)
|
||||
return Math.floor(Math.random() * (max - min)) + min
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(Math.random() * (max - min)) + min;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,5 @@ export const clipboardReadText = (): string => {
|
||||
|
||||
export const encodePath = (path: string) => {
|
||||
// https://github.com/lyswhut/lx-music-desktop/issues/963
|
||||
// https://github.com/lyswhut/lx-music-desktop/issues/1461
|
||||
return path.replaceAll('%', '%25').replaceAll('#', '%23')
|
||||
return path.replaceAll('%', '%25')
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import log from 'electron-log'
|
||||
|
||||
log.transports.file.level = 'info'
|
||||
|
||||
export const isLinux = process.platform == 'linux'
|
||||
export const isWin = process.platform == 'win32'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getNow, TimeoutTools } from './utils'
|
||||
const { getNow, TimeoutTools } = require('./utils')
|
||||
|
||||
// const fontFormateRxp = /(?=<\d+,\d+>).*?/g
|
||||
const fontSplitRxp = /(?=<\d+,\d+>).*?/g
|
||||
@@ -25,7 +25,7 @@ const createAnimation = (dom, duration, isVertical) => new window.Animation(new
|
||||
// https://jsfiddle.net/ceqpnbky/
|
||||
// https://jsfiddle.net/ceqpnbky/1/
|
||||
|
||||
export default class FontPlayer {
|
||||
module.exports = class FontPlayer {
|
||||
constructor({
|
||||
time = 0,
|
||||
rate = 1,
|
||||
@@ -69,8 +69,8 @@ export default class FontPlayer {
|
||||
|
||||
this.lineContent = null
|
||||
|
||||
this.timeoutTools = new TimeoutTools(50)
|
||||
this.waitPlayTimeout = new TimeoutTools(50)
|
||||
this.timeoutTools = new TimeoutTools(80)
|
||||
this.waitPlayTimeout = new TimeoutTools(80)
|
||||
|
||||
this._init()
|
||||
}
|
||||
@@ -133,8 +133,8 @@ export default class FontPlayer {
|
||||
// let lineText = ''
|
||||
let lrcShadowContent
|
||||
for (const font of fonts) {
|
||||
if (!timeRxp.test(font)) return this._handleLineParse()
|
||||
text = font.replace(timeRxp, '')
|
||||
if (RegExp.$2 == '') return this._handleLineParse()
|
||||
const time = parseInt(RegExp.$2)
|
||||
|
||||
const dom = document.createElement('span')
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import LinePlayer from './line-player'
|
||||
import FontPlayer from './font-player'
|
||||
const LinePlayer = require('./line-player')
|
||||
const FontPlayer = require('./font-player')
|
||||
|
||||
const fontTimeExp = /<(\d+),(\d+)>/g
|
||||
|
||||
export default class Lyric {
|
||||
module.exports = class Lyric {
|
||||
constructor({
|
||||
lyric = '',
|
||||
extendedLyrics = [],
|
||||
@@ -68,7 +68,7 @@ export default class Lyric {
|
||||
_handleLinePlayerOnPlay = (num, text, curTime) => {
|
||||
if (this.isLineMode) {
|
||||
if (num < this.playingLineNum + 1) {
|
||||
for (let i = this.playingLineNum, minNum = Math.max(num, 0) - 1; i > minNum; i--) {
|
||||
for (let i = this.playingLineNum; i > num - 1; i--) {
|
||||
const font = this._lineFonts[i]
|
||||
font.reset()
|
||||
font.lineContent.classList.remove(this.activeLineClassName)
|
||||
@@ -86,7 +86,7 @@ export default class Lyric {
|
||||
}
|
||||
} else {
|
||||
if (num < this.playingLineNum + 1) {
|
||||
for (let i = this.playingLineNum, minNum = Math.max(num, 0) - 1; i > minNum; i--) {
|
||||
for (let i = this.playingLineNum; i > num - 1; i--) {
|
||||
const font = this._lineFonts[i]
|
||||
font.lineContent.classList.remove(this.activeLineClassName)
|
||||
font.reset()
|
||||
@@ -103,12 +103,10 @@ export default class Lyric {
|
||||
}
|
||||
}
|
||||
this.playingLineNum = num
|
||||
if (num > -1) {
|
||||
const font = this._lineFonts[num]
|
||||
font.lineContent.classList.add(this.activeLineClassName)
|
||||
font.play(curTime - this._lines[num].time)
|
||||
}
|
||||
this.onPlay(num, this._lines[num]?.text ?? '')
|
||||
const font = this._lineFonts[num]
|
||||
font.lineContent.classList.add(this.activeLineClassName)
|
||||
font.play(curTime - this._lines[num].time)
|
||||
this.onPlay(num, this._lines[num].text)
|
||||
}
|
||||
|
||||
_initLines = (lyricLines, offset, isUpdate) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getNow, TimeoutTools } from './utils'
|
||||
const { getNow, TimeoutTools } = require('./utils')
|
||||
|
||||
const timeFieldExp = /^(?:\[[\d:.]+\])+/g
|
||||
const timeExp = /\d{1,3}(:\d{1,3}){0,2}(?:\.\d{1,3})/g
|
||||
@@ -29,8 +29,7 @@ const parseExtendedLyric = (lrcLinesMap, extendedLyric) => {
|
||||
if (result) {
|
||||
const timeField = result[0]
|
||||
const text = line.replace(timeFieldExp, '').trim()
|
||||
// https://github.com/lyswhut/lx-music-desktop/issues/1499
|
||||
if (text && text != '//') {
|
||||
if (text) {
|
||||
const times = timeField.match(timeExp)
|
||||
if (times == null) continue
|
||||
for (let time of times) {
|
||||
@@ -43,7 +42,7 @@ const parseExtendedLyric = (lrcLinesMap, extendedLyric) => {
|
||||
}
|
||||
}
|
||||
|
||||
export default class LinePlayer {
|
||||
module.exports = class LinePlayer {
|
||||
constructor({ offset = 0, rate = 1, onPlay = function() { }, onSetLyric = function() { } } = {}) {
|
||||
this.tags = {}
|
||||
this.lines = null
|
||||
@@ -148,7 +147,7 @@ export default class LinePlayer {
|
||||
const currentTime = this._currentTime()
|
||||
const driftTime = currentTime - curLine.time
|
||||
|
||||
if (driftTime >= 0) {
|
||||
if (driftTime >= 0 || this.curLineNum === 0) {
|
||||
let nextLine = this.lines[this.curLineNum + 1]
|
||||
const delay = (nextLine.time - curLine.time - driftTime) / this._rate
|
||||
|
||||
@@ -167,17 +166,6 @@ export default class LinePlayer {
|
||||
this._refresh()
|
||||
return
|
||||
}
|
||||
} else if (this.curLineNum == 0) {
|
||||
let firstLine = this.lines[0]
|
||||
const delay = (firstLine.time - currentTime) / this._rate
|
||||
if (this.isPlay) {
|
||||
timeoutTools.start(() => {
|
||||
if (!this.isPlay) return
|
||||
this._refresh()
|
||||
}, delay)
|
||||
}
|
||||
this.onPlay(-1, '', currentTime)
|
||||
return
|
||||
}
|
||||
|
||||
this.curLineNum = this._findCurLineNum(currentTime, this.curLineNum) - 1
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
|
||||
export const getNow = typeof performance == 'object' && window.performance.now ? window.performance.now.bind(window.performance) : Date.now.bind(Date)
|
||||
const getNow = exports.getNow = typeof performance == 'object' && window.performance.now ? window.performance.now.bind(window.performance) : Date.now.bind(Date)
|
||||
|
||||
export class TimeoutTools {
|
||||
constructor(thresholdTime = 80) {
|
||||
exports.TimeoutTools = class TimeoutTools {
|
||||
constructor(thresholdTime = 200) {
|
||||
this.invokeTime = 0
|
||||
this.animationFrameId = null
|
||||
this.timeoutId = null
|
||||
@@ -17,7 +17,6 @@ export class TimeoutTools {
|
||||
// console.log('diff', diff)
|
||||
if (diff > 0) {
|
||||
if (diff < this.thresholdTime) return this.run()
|
||||
// console.log('run timeout', diff, diff - this.thresholdTime)
|
||||
return this.timeoutId = window.setTimeout(() => {
|
||||
this.timeoutId = null
|
||||
this.run()
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import fs from 'node:fs'
|
||||
import crypto from 'node:crypto'
|
||||
import { gzip, gunzip } from 'node:zlib'
|
||||
import path from 'node:path'
|
||||
import fs from 'fs'
|
||||
import crypto from 'crypto'
|
||||
import { gzip, gunzip } from 'zlib'
|
||||
import { log } from '@common/utils'
|
||||
import path from 'path'
|
||||
|
||||
export const joinPath = (...paths: string[]): string => path.join(...paths)
|
||||
|
||||
@@ -15,7 +15,7 @@ export const dirname = (p: string): string => path.dirname(p)
|
||||
* @param {*} path 路径
|
||||
*/
|
||||
export const checkPath = async(path: string): Promise<boolean> => {
|
||||
return new Promise(resolve => {
|
||||
return await new Promise(resolve => {
|
||||
if (!path) {
|
||||
resolve(false)
|
||||
return
|
||||
@@ -31,7 +31,7 @@ export const checkPath = async(path: string): Promise<boolean> => {
|
||||
}
|
||||
|
||||
export const getFileStats = async(path: string): Promise<fs.Stats | null> => {
|
||||
return new Promise(resolve => {
|
||||
return await new Promise(resolve => {
|
||||
if (!path) {
|
||||
resolve(null)
|
||||
return
|
||||
@@ -97,7 +97,7 @@ export const readFile = async(path: string) => fs.promises.readFile(path)
|
||||
export const toMD5 = (str: string) => crypto.createHash('md5').update(str).digest('hex')
|
||||
|
||||
export const gzipData = async(str: string): Promise<Buffer> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return await new Promise((resolve, reject) => {
|
||||
gzip(str, (err, result) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
@@ -109,7 +109,7 @@ export const gzipData = async(str: string): Promise<Buffer> => {
|
||||
}
|
||||
|
||||
export const gunzipData = async(buf: Buffer): Promise<string> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return await new Promise((resolve, reject) => {
|
||||
gunzip(buf, (err, result) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
|
||||
@@ -8,31 +8,15 @@ const easeInOutQuad = (t: number, b: number, c: number, d: number): number => {
|
||||
|
||||
type Noop = () => void
|
||||
const noop: Noop = () => {}
|
||||
type ScrollElement<T> = {
|
||||
lx_scrollLockKey?: number
|
||||
lx_scrollNextParams?: [ScrollElement<HTMLElement>, number, number, Noop]
|
||||
lx_scrollTimeout?: number
|
||||
lx_scrollDelayTimeout?: number
|
||||
} & T
|
||||
|
||||
const handleScrollY = (element: ScrollElement<HTMLElement>, to: number, duration = 300, fn = noop): Noop => {
|
||||
const handleScrollY = (element: HTMLElement, to: number, duration = 300, fn = noop): Noop => {
|
||||
if (!element) {
|
||||
fn()
|
||||
return noop
|
||||
}
|
||||
const clean = () => {
|
||||
element.lx_scrollLockKey = undefined
|
||||
element.lx_scrollNextParams = undefined
|
||||
if (element.lx_scrollTimeout) window.clearTimeout(element.lx_scrollTimeout)
|
||||
element.lx_scrollTimeout = undefined
|
||||
}
|
||||
if (element.lx_scrollLockKey) {
|
||||
element.lx_scrollNextParams = [element, to, duration, fn]
|
||||
element.lx_scrollLockKey = -1
|
||||
return clean
|
||||
}
|
||||
// @ts-expect-error
|
||||
const start = element.scrollTop ?? element.scrollY ?? 0
|
||||
let cancel = false
|
||||
if (to > start) {
|
||||
let maxScrollTop = element.scrollHeight - element.clientHeight
|
||||
if (to > maxScrollTop) to = maxScrollTop
|
||||
@@ -50,19 +34,9 @@ const handleScrollY = (element: ScrollElement<HTMLElement>, to: number, duration
|
||||
}
|
||||
|
||||
let currentTime = 0
|
||||
let val: number
|
||||
let key = Math.random()
|
||||
let val
|
||||
|
||||
const animateScroll = () => {
|
||||
element.lx_scrollTimeout = undefined
|
||||
// if (element.lx_scrollLockKey != key) {
|
||||
if (element.lx_scrollNextParams && currentTime > duration * 0.75) {
|
||||
const [_element, to, duration, fn] = element.lx_scrollNextParams
|
||||
clean()
|
||||
handleScrollY(_element, to, duration, fn)
|
||||
return
|
||||
}
|
||||
|
||||
currentTime += increment
|
||||
val = Math.trunc(easeInOutQuad(currentTime, start, change, duration))
|
||||
if (element.scrollTo) {
|
||||
@@ -71,23 +45,19 @@ const handleScrollY = (element: ScrollElement<HTMLElement>, to: number, duration
|
||||
element.scrollTop = val
|
||||
}
|
||||
if (currentTime < duration) {
|
||||
element.lx_scrollTimeout = window.setTimeout(animateScroll, increment)
|
||||
} else {
|
||||
if (element.lx_scrollNextParams) {
|
||||
const [_element, to, duration, fn] = element.lx_scrollNextParams
|
||||
clean()
|
||||
handleScrollY(_element, to, duration, fn)
|
||||
} else {
|
||||
clean()
|
||||
if (cancel) {
|
||||
fn()
|
||||
return
|
||||
}
|
||||
window.setTimeout(animateScroll, increment)
|
||||
} else {
|
||||
fn()
|
||||
}
|
||||
}
|
||||
|
||||
element.lx_scrollLockKey = key
|
||||
animateScroll()
|
||||
|
||||
return clean
|
||||
return () => {
|
||||
cancel = true
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 设置滚动条位置
|
||||
@@ -97,24 +67,16 @@ const handleScrollY = (element: ScrollElement<HTMLElement>, to: number, duration
|
||||
* @param {*} fn 滚动完成后的回调
|
||||
* @param {*} delay 延迟执行时间
|
||||
*/
|
||||
export const scrollTo = (element: ScrollElement<HTMLElement>, to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
|
||||
export const scrollTo = (element: HTMLElement, to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
|
||||
let cancelFn: () => void
|
||||
if (element.lx_scrollDelayTimeout != null) {
|
||||
window.clearTimeout(element.lx_scrollDelayTimeout)
|
||||
element.lx_scrollDelayTimeout = undefined
|
||||
}
|
||||
let timeout: number | null
|
||||
if (delay) {
|
||||
let scrollCancelFn: Noop
|
||||
cancelFn = () => {
|
||||
if (element.lx_scrollDelayTimeout == null) {
|
||||
scrollCancelFn?.()
|
||||
} else {
|
||||
window.clearTimeout(element.lx_scrollDelayTimeout)
|
||||
element.lx_scrollDelayTimeout = undefined
|
||||
}
|
||||
timeout == null ? scrollCancelFn?.() : clearTimeout(timeout)
|
||||
}
|
||||
element.lx_scrollDelayTimeout = window.setTimeout(() => {
|
||||
element.lx_scrollDelayTimeout = undefined
|
||||
timeout = window.setTimeout(() => {
|
||||
timeout = null
|
||||
scrollCancelFn = handleScrollY(element, to, duration, fn)
|
||||
}, delay)
|
||||
} else {
|
||||
@@ -122,24 +84,14 @@ export const scrollTo = (element: ScrollElement<HTMLElement>, to: number, durati
|
||||
}
|
||||
return cancelFn
|
||||
}
|
||||
const handleScrollX = (element: ScrollElement<HTMLElement>, to: number, duration = 300, fn = () => {}): () => void => {
|
||||
const handleScrollX = (element: HTMLElement, to: number, duration = 300, fn = () => {}): () => void => {
|
||||
if (!element) {
|
||||
fn()
|
||||
return noop
|
||||
}
|
||||
const clean = () => {
|
||||
element.lx_scrollLockKey = undefined
|
||||
element.lx_scrollNextParams = undefined
|
||||
if (element.lx_scrollTimeout) window.clearTimeout(element.lx_scrollTimeout)
|
||||
element.lx_scrollTimeout = undefined
|
||||
}
|
||||
if (element.lx_scrollLockKey) {
|
||||
element.lx_scrollNextParams = [element, to, duration, fn]
|
||||
element.lx_scrollLockKey = -1
|
||||
return clean
|
||||
}
|
||||
// @ts-expect-error
|
||||
const start = element.scrollLeft || element.scrollX || 0
|
||||
let cancel = false
|
||||
if (to > start) {
|
||||
let maxScrollLeft = element.scrollWidth - element.clientWidth
|
||||
if (to > maxScrollLeft) to = maxScrollLeft
|
||||
@@ -157,18 +109,9 @@ const handleScrollX = (element: ScrollElement<HTMLElement>, to: number, duration
|
||||
}
|
||||
|
||||
let currentTime = 0
|
||||
let val: number
|
||||
let key = Math.random()
|
||||
let val
|
||||
|
||||
const animateScroll = () => {
|
||||
element.lx_scrollTimeout = undefined
|
||||
if (element.lx_scrollNextParams && currentTime > duration * 0.75) {
|
||||
const [_element, to, duration, fn] = element.lx_scrollNextParams
|
||||
clean()
|
||||
handleScrollY(_element, to, duration, fn)
|
||||
return
|
||||
}
|
||||
|
||||
currentTime += increment
|
||||
val = Math.trunc(easeInOutQuad(currentTime, start, change, duration))
|
||||
if (element.scrollTo) {
|
||||
@@ -177,21 +120,19 @@ const handleScrollX = (element: ScrollElement<HTMLElement>, to: number, duration
|
||||
element.scrollLeft = val
|
||||
}
|
||||
if (currentTime < duration) {
|
||||
element.lx_scrollTimeout = window.setTimeout(animateScroll, increment)
|
||||
} else {
|
||||
if (element.lx_scrollNextParams) {
|
||||
const [_element, to, duration, fn] = element.lx_scrollNextParams
|
||||
clean()
|
||||
handleScrollY(_element, to, duration, fn)
|
||||
} else {
|
||||
clean()
|
||||
if (cancel) {
|
||||
fn()
|
||||
return
|
||||
}
|
||||
window.setTimeout(animateScroll, increment)
|
||||
} else {
|
||||
fn()
|
||||
}
|
||||
}
|
||||
element.lx_scrollLockKey = key
|
||||
animateScroll()
|
||||
return clean
|
||||
return () => {
|
||||
cancel = true
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 设置滚动条位置
|
||||
@@ -201,24 +142,16 @@ const handleScrollX = (element: ScrollElement<HTMLElement>, to: number, duration
|
||||
* @param {*} fn 滚动完成后的回调
|
||||
* @param {*} delay 延迟执行时间
|
||||
*/
|
||||
export const scrollXTo = (element: ScrollElement<HTMLElement>, to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
|
||||
export const scrollXTo = (element: HTMLElement, to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
|
||||
let cancelFn: Noop
|
||||
if (element.lx_scrollDelayTimeout != null) {
|
||||
window.clearTimeout(element.lx_scrollDelayTimeout)
|
||||
element.lx_scrollDelayTimeout = undefined
|
||||
}
|
||||
let timeout: number | null
|
||||
if (delay) {
|
||||
let scrollCancelFn: Noop
|
||||
cancelFn = () => {
|
||||
if (element.lx_scrollDelayTimeout == null) {
|
||||
scrollCancelFn?.()
|
||||
} else {
|
||||
window.clearTimeout(element.lx_scrollDelayTimeout)
|
||||
element.lx_scrollDelayTimeout = undefined
|
||||
}
|
||||
timeout == null ? scrollCancelFn?.() : clearTimeout(timeout)
|
||||
}
|
||||
element.lx_scrollDelayTimeout = window.setTimeout(() => {
|
||||
element.lx_scrollDelayTimeout = undefined
|
||||
timeout = window.setTimeout(() => {
|
||||
timeout = null
|
||||
scrollCancelFn = handleScrollX(element, to, duration, fn)
|
||||
}, delay)
|
||||
} else {
|
||||
@@ -227,24 +160,14 @@ export const scrollXTo = (element: ScrollElement<HTMLElement>, to: number, durat
|
||||
return cancelFn
|
||||
}
|
||||
|
||||
const handleScrollXR = (element: ScrollElement<HTMLElement>, to: number, duration = 300, fn = () => {}): () => void => {
|
||||
const handleScrollXR = (element: HTMLElement, to: number, duration = 300, fn = () => {}): () => void => {
|
||||
if (!element) {
|
||||
fn()
|
||||
return noop
|
||||
}
|
||||
const clean = () => {
|
||||
element.lx_scrollLockKey = undefined
|
||||
element.lx_scrollNextParams = undefined
|
||||
if (element.lx_scrollTimeout) window.clearTimeout(element.lx_scrollTimeout)
|
||||
element.lx_scrollTimeout = undefined
|
||||
}
|
||||
if (element.lx_scrollLockKey) {
|
||||
element.lx_scrollNextParams = [element, to, duration, fn]
|
||||
element.lx_scrollLockKey = -1
|
||||
return clean
|
||||
}
|
||||
// @ts-expect-error
|
||||
const start = element.scrollLeft || element.scrollX as number || 0
|
||||
let cancel = false
|
||||
if (to < start) {
|
||||
let maxScrollLeft = -element.scrollWidth + element.clientWidth
|
||||
if (to < maxScrollLeft) to = maxScrollLeft
|
||||
@@ -263,18 +186,9 @@ const handleScrollXR = (element: ScrollElement<HTMLElement>, to: number, duratio
|
||||
}
|
||||
|
||||
let currentTime = 0
|
||||
let val: number
|
||||
let key = Math.random()
|
||||
let val
|
||||
|
||||
const animateScroll = () => {
|
||||
element.lx_scrollTimeout = undefined
|
||||
if (element.lx_scrollNextParams && currentTime > duration * 0.75) {
|
||||
const [_element, to, duration, fn] = element.lx_scrollNextParams
|
||||
clean()
|
||||
handleScrollY(_element, to, duration, fn)
|
||||
return
|
||||
}
|
||||
|
||||
currentTime += increment
|
||||
val = Math.trunc(easeInOutQuad(currentTime, start, change, duration))
|
||||
|
||||
@@ -284,23 +198,19 @@ const handleScrollXR = (element: ScrollElement<HTMLElement>, to: number, duratio
|
||||
element.scrollLeft = val
|
||||
}
|
||||
if (currentTime < duration) {
|
||||
element.lx_scrollTimeout = window.setTimeout(animateScroll, increment)
|
||||
} else {
|
||||
if (element.lx_scrollNextParams) {
|
||||
const [_element, to, duration, fn] = element.lx_scrollNextParams
|
||||
clean()
|
||||
handleScrollY(_element, to, duration, fn)
|
||||
} else {
|
||||
clean()
|
||||
if (cancel) {
|
||||
fn()
|
||||
return
|
||||
}
|
||||
window.setTimeout(animateScroll, increment)
|
||||
} else {
|
||||
fn()
|
||||
}
|
||||
}
|
||||
|
||||
element.lx_scrollLockKey = key
|
||||
animateScroll()
|
||||
|
||||
return clean
|
||||
return () => {
|
||||
cancel = true
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 设置滚动条位置 (writing-mode: vertical-rl 专用)
|
||||
@@ -310,24 +220,16 @@ const handleScrollXR = (element: ScrollElement<HTMLElement>, to: number, duratio
|
||||
* @param fn 滚动完成后的回调
|
||||
* @param delay 延迟执行时间
|
||||
*/
|
||||
export const scrollXRTo = (element: ScrollElement<HTMLElement>, to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
|
||||
export const scrollXRTo = (element: HTMLElement, to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
|
||||
let cancelFn: Noop
|
||||
if (element.lx_scrollDelayTimeout != null) {
|
||||
window.clearTimeout(element.lx_scrollDelayTimeout)
|
||||
element.lx_scrollDelayTimeout = undefined
|
||||
}
|
||||
let timeout: number | null
|
||||
if (delay) {
|
||||
let scrollCancelFn: Noop
|
||||
cancelFn = () => {
|
||||
if (element.lx_scrollDelayTimeout == null) {
|
||||
scrollCancelFn?.()
|
||||
} else {
|
||||
window.clearTimeout(element.lx_scrollDelayTimeout)
|
||||
element.lx_scrollDelayTimeout = undefined
|
||||
}
|
||||
timeout == null ? scrollCancelFn?.() : clearTimeout(timeout)
|
||||
}
|
||||
element.lx_scrollDelayTimeout = window.setTimeout(() => {
|
||||
element.lx_scrollDelayTimeout = undefined
|
||||
timeout = window.setTimeout(() => {
|
||||
timeout = null
|
||||
scrollCancelFn = handleScrollXR(element, to, duration, fn)
|
||||
}, delay)
|
||||
} else {
|
||||
|
||||
@@ -17,7 +17,11 @@ import {
|
||||
type ComputedRef,
|
||||
type Ref,
|
||||
type ShallowRef,
|
||||
defineProps,
|
||||
defineEmits,
|
||||
defineComponent,
|
||||
shallowReactive,
|
||||
defineExpose,
|
||||
withDefaults,
|
||||
} from 'vue'
|
||||
// import { useStore } from 'vuex'
|
||||
@@ -71,7 +75,11 @@ export {
|
||||
unref,
|
||||
onMounted,
|
||||
markRaw,
|
||||
defineProps,
|
||||
defineEmits,
|
||||
defineComponent,
|
||||
shallowReactive,
|
||||
defineExpose,
|
||||
withDefaults,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
/* eslint-env node */
|
||||
const { base, typescript } = require('../../.eslintrc.base.cjs')
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
...base,
|
||||
overrides: [
|
||||
{
|
||||
...typescript,
|
||||
parserOptions: {
|
||||
project: './tsconfig.json',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -81,7 +81,6 @@
|
||||
"list__add_to": "Add to ...",
|
||||
"list__collect": "Collect",
|
||||
"list__copy_name": "Copy name",
|
||||
"list__dislike": "Dislike",
|
||||
"list__download": "Download",
|
||||
"list__export_part_desc": "Choose where to save the list file",
|
||||
"list__file": "Locate the file",
|
||||
@@ -126,7 +125,6 @@
|
||||
"list_sort_modal_by_down": "Descending",
|
||||
"list_sort_modal_by_field": "Sort field",
|
||||
"list_sort_modal_by_name": "Song name",
|
||||
"list_sort_modal_by_random": "Random",
|
||||
"list_sort_modal_by_singer": "Singer name",
|
||||
"list_sort_modal_by_source": "Song source",
|
||||
"list_sort_modal_by_time": "Duration",
|
||||
@@ -225,7 +223,6 @@
|
||||
"player__play_toggle_mode_off": "Disable",
|
||||
"player__play_toggle_mode_random": "List Random",
|
||||
"player__play_toggle_mode_single_loop": "Single Loop",
|
||||
"player__playback_preserves_pitch": "Pitch compensation",
|
||||
"player__playback_rate": "Current playback rate:",
|
||||
"player__playback_rate_reset_btn": "Reset",
|
||||
"player__playing": "Now playing...",
|
||||
@@ -266,10 +263,6 @@
|
||||
"player__sound_effect_panner_enabled": "enable",
|
||||
"player__sound_effect_panner_sound_r": "Sound distance",
|
||||
"player__sound_effect_panner_sound_speed": "Surround speed",
|
||||
"player__sound_effect_pitch_shifter": "Pitch adjustment",
|
||||
"player__sound_effect_pitch_shifter_preset_semitones": "{num} semitones",
|
||||
"player__sound_effect_pitch_shifter_reset_btn": "Reset",
|
||||
"player__sound_effect_pitch_shifter_tip": "Since ups and downs need to process audio data in real time, this will cause additional CPU usage\n\nKnown issues:\nIf the CPU resources are not enough, the processing will cause the task to accumulate and the sound will be abnormal. At this time, it is necessary to pause the playback for a period of time and wait for the accumulated tasks to be processed before playing.",
|
||||
"player__stop": "Paused",
|
||||
"player__volume": "Volume: ",
|
||||
"player__volume_mute_label": "Mute",
|
||||
@@ -383,10 +376,6 @@
|
||||
"setting__desktop_lyric_shadow_color": "Shadow color",
|
||||
"setting__desktop_lyric_show_taskbar": "Display lyrics progress on the taskbar (this setting is used as a workaround when the screen recording software cannot capture the lyrics window)",
|
||||
"setting__desktop_lyric_unplay_color": "Color not playing",
|
||||
"setting__dislike_list_input_tip": "song name@artist name\nSong name\n@ singer name",
|
||||
"setting__dislike_list_save_btn": "Save",
|
||||
"setting__dislike_list_tips": "1. If there is a \"@\" symbol in the song or singer's name, you need to replace it with \"#\"\n2. Specify a song of a singer: Name@Singer\n3. Specify a song: Name\n4. Specify a certain singer: @Singer",
|
||||
"setting__dislike_list_title": "List of Disliked Song Rules",
|
||||
"setting__download": "Download",
|
||||
"setting__download_data_embed": "Whether to embed the following content in the audio file",
|
||||
"setting__download_embed_lyric": "Embedding lyric",
|
||||
@@ -426,9 +415,6 @@
|
||||
"setting__hot_key_desktop_lyric_toggle_visible": "Turn on/off desktop lyrics",
|
||||
"setting__hot_key_global_title": "Global Shortcut Key",
|
||||
"setting__hot_key_local_title": "Shortcut Keys in Software",
|
||||
"setting__hot_key_player_music_dislike": "Dislike the song",
|
||||
"setting__hot_key_player_music_love": "Favorites Song",
|
||||
"setting__hot_key_player_music_unlove": "Cancel collection",
|
||||
"setting__hot_key_player_next": "Next Song",
|
||||
"setting__hot_key_player_prev": "Previous Song",
|
||||
"setting__hot_key_player_toggle_play": "Play/Pause Control",
|
||||
@@ -457,9 +443,6 @@
|
||||
"setting__odc_clear_search_input": "Clear the search box when you are not searching",
|
||||
"setting__odc_clear_search_list": "Clear the search list when you are not searching",
|
||||
"setting__other": "Extras",
|
||||
"setting__other_dislike_list": "dislike song rule",
|
||||
"setting__other_dislike_list_label": "Number of rules:",
|
||||
"setting__other_dislike_list_show_btn": "Edit dislike song rules",
|
||||
"setting__other_listdata": "List Data Cleanup",
|
||||
"setting__other_listdata_clear_btn": "Clear my list data",
|
||||
"setting__other_listdata_clear_tip_confirm": "This will clear all lists you have created and all songs in your favourites, do you really want to continue?",
|
||||
@@ -485,7 +468,6 @@
|
||||
"setting__other_tray_theme_native": "White",
|
||||
"setting__other_tray_theme_origin": "Primary Color",
|
||||
"setting__play": "Play",
|
||||
"setting__play_auto_clean_played_list": "Whether to clear the existing playlist when clicking the same list as the playlist to switch songs (all songs in the list in random mode will participate in the random again)",
|
||||
"setting__play_auto_skip_on_error": "Automatically switch songs on playback error",
|
||||
"setting__play_detail": "Play details page settings",
|
||||
"setting__play_detail_align": "Lyric Alignment",
|
||||
@@ -534,16 +516,10 @@
|
||||
"setting__sync_server_address": "Synchronization service address: {address}",
|
||||
"setting__sync_server_auth_code": "Connection code: {code}",
|
||||
"setting__sync_server_device": "Connected devices: {devices}",
|
||||
"setting__sync_server_device_list_btn_remove": "Remove",
|
||||
"setting__sync_server_device_list_noitem": "Nothing here ┗( ▔, ▔ )┛",
|
||||
"setting__sync_server_device_list_time": "Last connection time: {time}",
|
||||
"setting__sync_server_device_list_tips": "💡 After the device is removed, you need to re-enter the connection code when reconnecting",
|
||||
"setting__sync_server_device_list_title": "Certified device",
|
||||
"setting__sync_server_mode": "Server mode (since the data is transmitted in clear text, please use it under a trusted network)",
|
||||
"setting__sync_server_port": "Sync port settings",
|
||||
"setting__sync_server_port_tip": "Please enter the synchronization service port number",
|
||||
"setting__sync_server_refresh_code": "Refresh the connection code",
|
||||
"setting__sync_server_show_device_list": "List of certified devices",
|
||||
"setting__sync_tip": "For how to use it, please see the \"Sync function\" section of the FAQ",
|
||||
"setting__update": "Update",
|
||||
"setting__update_checking": "Checking for updates...",
|
||||
@@ -558,7 +534,6 @@
|
||||
"setting__update_show_change_log": "Show changelog on first boot after version update",
|
||||
"setting__update_try_auto_update": "Attempt to download updates automatically when a new version is found",
|
||||
"setting__update_unknown": "Unknown",
|
||||
"setting__update_unknown_tip": "❓ Failed to obtain the latest version information, it is recommended to go to the About interface to open the project release address to check whether the current version is the latest",
|
||||
"setting_sync_status_enabled": "connected",
|
||||
"song_list": "Playlists",
|
||||
"songlist__import_input_btn_confirm": "Open",
|
||||
@@ -589,20 +564,14 @@
|
||||
"source_xm": "Xiami",
|
||||
"sync__auth_code_input_tip": "Please enter the connection code",
|
||||
"sync__auth_code_title": "Need to enter the connection code",
|
||||
"sync__dislike_merge_tip_desc": "Merge the content of the two lists and remove the duplicates",
|
||||
"sync__dislike_other_tip_desc": "\"Cancel sync\" will not use the dislike list sync feature",
|
||||
"sync__dislike_overwrite_tip_desc": "The list of overriddens will be replaced with the list of overriders",
|
||||
"sync__dislike_title": "Choose how to sync with {name}'s dislike list",
|
||||
"sync__list_merge_tip_desc": "Merge the two lists together, the same song will be removed (the song of the merged person is removed), and different songs will be added.",
|
||||
"sync__list_other_tip_desc": "\"Cancel Sync\" will not use list sync.",
|
||||
"sync__list_overwrite_tip_desc": "The list with the same ID of the covered person and the covered list will be deleted and replaced with the list of the covered person (lists with different list IDs will be merged together). If you check Complete coverage, all lists of the covered person will be moved. \nDivide, and then replace with a list of overriders.",
|
||||
"sync__list_title": "Choose how to synchronize the list with {name}",
|
||||
"sync__merge_btn_local_remote": "Local list merge remote list",
|
||||
"sync__merge_btn_remote_local": "Remote list merge local list",
|
||||
"sync__merge_label": "Merge",
|
||||
"sync__merge_tip": "Merge:",
|
||||
"sync__merge_tip_desc": "Merge the two lists together, the same song will be removed (the song of the merged person is removed), and different songs will be added.",
|
||||
"sync__other_label": "Other",
|
||||
"sync__other_tip": "Other: ",
|
||||
"sync__other_tip_desc": "\"Cancel Sync\" will directly disconnect the two parties.",
|
||||
"sync__overwrite": "Full coverage",
|
||||
"sync__overwrite_btn_cancel": "Cancel sync",
|
||||
"sync__overwrite_btn_local_remote": "Local list Overwrite remote list",
|
||||
@@ -610,6 +579,8 @@
|
||||
"sync__overwrite_btn_remote_local": "Remote list Overwrite local list",
|
||||
"sync__overwrite_label": "Cover",
|
||||
"sync__overwrite_tip": "Over: ",
|
||||
"sync__overwrite_tip_desc": "The list with the same ID of the covered person and the covered list will be deleted and replaced with the list of the covered person (lists with different list IDs will be merged together). If you check Complete coverage, all lists of the covered person will be moved. \nDivide, and then replace with a list of overriders.",
|
||||
"sync__title": "Choose how to synchronize the list with {name}",
|
||||
"sync_status_disabled": "not connected",
|
||||
"tag__high_quality": "HQ",
|
||||
"tag__lossless": "SQ",
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
// "typeRoots": [
|
||||
// "./types"
|
||||
// ],
|
||||
"module": "esnext", /* Specify what module code is generated. */
|
||||
"moduleResolution": "nodenext", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
},
|
||||
// "include": [
|
||||
// "**/*.ts",
|
||||
// "**/*.js",
|
||||
// "**/*.vue",
|
||||
// "**/*.json",
|
||||
// ],
|
||||
// "exclude": ["dbService"]
|
||||
}
|
||||
|
||||
@@ -81,7 +81,6 @@
|
||||
"list__add_to": "添加到...",
|
||||
"list__collect": "收藏",
|
||||
"list__copy_name": "复制歌曲名",
|
||||
"list__dislike": "不喜欢",
|
||||
"list__download": "下载",
|
||||
"list__export_part_desc": "选择列表文件保存位置",
|
||||
"list__file": "定位文件",
|
||||
@@ -126,7 +125,6 @@
|
||||
"list_sort_modal_by_down": "降序",
|
||||
"list_sort_modal_by_field": "排序字段",
|
||||
"list_sort_modal_by_name": "歌曲名",
|
||||
"list_sort_modal_by_random": "随机乱序",
|
||||
"list_sort_modal_by_singer": "歌手名",
|
||||
"list_sort_modal_by_source": "歌曲源",
|
||||
"list_sort_modal_by_time": "时长",
|
||||
@@ -225,7 +223,6 @@
|
||||
"player__play_toggle_mode_off": "禁用",
|
||||
"player__play_toggle_mode_random": "列表随机",
|
||||
"player__play_toggle_mode_single_loop": "单曲循环",
|
||||
"player__playback_preserves_pitch": "音调补偿",
|
||||
"player__playback_rate": "当前播放速率:",
|
||||
"player__playback_rate_reset_btn": "重置",
|
||||
"player__playing": "播放中...",
|
||||
@@ -265,10 +262,6 @@
|
||||
"player__sound_effect_panner_enabled": "启用",
|
||||
"player__sound_effect_panner_sound_r": "声音距离",
|
||||
"player__sound_effect_panner_sound_speed": "环绕速度",
|
||||
"player__sound_effect_pitch_shifter": "音调升降调节",
|
||||
"player__sound_effect_pitch_shifter_preset_semitones": "{num} 半音",
|
||||
"player__sound_effect_pitch_shifter_reset_btn": "重置",
|
||||
"player__sound_effect_pitch_shifter_tip": "由于升降调需要实时处理音频数据,这会导致额外的CPU占用\n\n已知问题:\n如果CPU资源不够时将处理导致任务堆积而出现声音异常,这时需要暂停播放一段时间等堆积的任务处理完毕再播放",
|
||||
"player__stop": "暂停播放",
|
||||
"player__volume": "当前音量:",
|
||||
"player__volume_mute_label": "静音",
|
||||
@@ -382,10 +375,6 @@
|
||||
"setting__desktop_lyric_shadow_color": "阴影颜色",
|
||||
"setting__desktop_lyric_show_taskbar": "在任务栏显示歌词进程(此设置用于在录屏软件无法捕获歌词窗口时的变通解决方法)",
|
||||
"setting__desktop_lyric_unplay_color": "未播放颜色",
|
||||
"setting__dislike_list_input_tip": "歌曲名@歌手名\n歌曲名\n@歌手名",
|
||||
"setting__dislike_list_save_btn": "保存",
|
||||
"setting__dislike_list_tips": "1. 每条一行,若歌曲或者歌手名字中存在“@”符号,需要将其替换成“#”\n2. 指定某歌手的某首歌:歌曲名@歌手名\n3. 指定某首歌:歌曲名\n4. 指定某歌手:@歌手名",
|
||||
"setting__dislike_list_title": "不喜欢的歌曲规则列表",
|
||||
"setting__download": "下载设置",
|
||||
"setting__download_data_embed": "是否将以下内容嵌入到音频文件中",
|
||||
"setting__download_embed_lyric": "歌词嵌入",
|
||||
@@ -425,9 +414,6 @@
|
||||
"setting__hot_key_desktop_lyric_toggle_visible": "开/关桌面歌词",
|
||||
"setting__hot_key_global_title": "全局快捷键",
|
||||
"setting__hot_key_local_title": "软件内快捷键",
|
||||
"setting__hot_key_player_music_dislike": "不喜欢该歌曲",
|
||||
"setting__hot_key_player_music_love": "收藏歌曲",
|
||||
"setting__hot_key_player_music_unlove": "取消收藏",
|
||||
"setting__hot_key_player_next": "下一首歌曲",
|
||||
"setting__hot_key_player_prev": "上一首歌曲",
|
||||
"setting__hot_key_player_toggle_play": "播放/暂停控制",
|
||||
@@ -456,9 +442,6 @@
|
||||
"setting__odc_clear_search_input": "离开搜索界面时清空搜索框",
|
||||
"setting__odc_clear_search_list": "离开搜索界面时清空搜索列表",
|
||||
"setting__other": "其他",
|
||||
"setting__other_dislike_list": "不喜欢的歌曲规则",
|
||||
"setting__other_dislike_list_label": "规则数量:",
|
||||
"setting__other_dislike_list_show_btn": "编辑不喜欢歌曲规则",
|
||||
"setting__other_listdata": "列表数据清理",
|
||||
"setting__other_listdata_clear_btn": "清空我的列表数据",
|
||||
"setting__other_listdata_clear_tip_confirm": "这将清理你创建的 所有列表 及收藏的 所有歌曲,是否真的要继续?",
|
||||
@@ -484,7 +467,6 @@
|
||||
"setting__other_tray_theme_native": "白色",
|
||||
"setting__other_tray_theme_origin": "原色",
|
||||
"setting__play": "播放设置",
|
||||
"setting__play_auto_clean_played_list": "点击与播放列表相同的列表切歌时是否清空已播放列表(随机模式下列表内所有歌曲会重新参与随机)",
|
||||
"setting__play_auto_skip_on_error": "播放错误时自动切换歌曲",
|
||||
"setting__play_detail": "播放详情页设置",
|
||||
"setting__play_detail_align": "歌词对齐方式",
|
||||
@@ -533,16 +515,10 @@
|
||||
"setting__sync_server_address": "同步服务地址:{address}",
|
||||
"setting__sync_server_auth_code": "连接码:{code}",
|
||||
"setting__sync_server_device": "已连接的设备:{devices}",
|
||||
"setting__sync_server_device_list_btn_remove": "移除",
|
||||
"setting__sync_server_device_list_noitem": "这里啥也没有 ┗( ▔, ▔ )┛",
|
||||
"setting__sync_server_device_list_time": "最后连接时间:{time}",
|
||||
"setting__sync_server_device_list_tips": "💡 设备被移除后,再连接时需要重新输入连接码",
|
||||
"setting__sync_server_device_list_title": "已认证设备",
|
||||
"setting__sync_server_mode": "服务端模式(由于数据是明文传输,请在受信任的网络下使用)",
|
||||
"setting__sync_server_port": "同步端口设置",
|
||||
"setting__sync_server_port_tip": "请输入同步服务端口号",
|
||||
"setting__sync_server_refresh_code": "刷新连接码",
|
||||
"setting__sync_server_show_device_list": "已认证设备列表",
|
||||
"setting__sync_tip": "使用方式请看常见问题“同步功能”部分",
|
||||
"setting__update": "软件更新",
|
||||
"setting__update_checking": "检查更新中...",
|
||||
@@ -557,7 +533,6 @@
|
||||
"setting__update_show_change_log": "更新版本后的首次启动时显示更新日志",
|
||||
"setting__update_try_auto_update": "发现新版本时尝试自动下载更新",
|
||||
"setting__update_unknown": "未知",
|
||||
"setting__update_unknown_tip": "❓ 获取最新版本信息失败,建议去关于界面打开项目发布地址查看当前版本是否最新",
|
||||
"setting_sync_status_enabled": "已连接",
|
||||
"song_list": "歌单",
|
||||
"songlist__import_input_btn_confirm": "打开",
|
||||
@@ -588,20 +563,14 @@
|
||||
"source_xm": "虾米音乐",
|
||||
"sync__auth_code_input_tip": "请输入连接码",
|
||||
"sync__auth_code_title": "需要输入连接码",
|
||||
"sync__dislike_merge_tip_desc": "合并两边列表内容并去重",
|
||||
"sync__dislike_other_tip_desc": "“取消同步”将不使用不喜欢列表同步功能",
|
||||
"sync__dislike_overwrite_tip_desc": "被覆盖者的列表将被替换成覆盖者的列表",
|
||||
"sync__dislike_title": "选择与 {name} 的不喜欢列表同步方式",
|
||||
"sync__list_merge_tip_desc": "将两边的列表合并到一起,相同的歌曲将被去掉(去掉的是被合并者的歌曲),不同的歌曲将被添加。",
|
||||
"sync__list_other_tip_desc": "“取消同步”将不使用列表同步功能。",
|
||||
"sync__list_overwrite_tip_desc": "被覆盖者与覆盖者列表ID相同的列表将被删除后替换成覆盖者的列表(列表ID不同的列表将被合并到一起),若勾选完全覆盖,则被覆盖者的所有列表将被移除,然后替换成覆盖者的列表。",
|
||||
"sync__list_title": "选择与 {name} 的列表同步方式",
|
||||
"sync__merge_btn_local_remote": "本机列表 合并 远程列表",
|
||||
"sync__merge_btn_remote_local": "远程列表 合并 本机列表",
|
||||
"sync__merge_label": "合并",
|
||||
"sync__merge_tip": "合并:",
|
||||
"sync__merge_tip_desc": "将两边的列表合并到一起,相同的歌曲将被去掉(去掉的是被合并者的歌曲),不同的歌曲将被添加。",
|
||||
"sync__other_label": "其他",
|
||||
"sync__other_tip": "其他:",
|
||||
"sync__other_tip_desc": "“取消同步”将直接断开双方的连接。",
|
||||
"sync__overwrite": "完全覆盖",
|
||||
"sync__overwrite_btn_cancel": "取消同步",
|
||||
"sync__overwrite_btn_local_remote": "本机列表 覆盖 远程列表",
|
||||
@@ -609,6 +578,8 @@
|
||||
"sync__overwrite_btn_remote_local": "远程列表 覆盖 本机列表",
|
||||
"sync__overwrite_label": "覆盖",
|
||||
"sync__overwrite_tip": "覆盖:",
|
||||
"sync__overwrite_tip_desc": "被覆盖者与覆盖者列表ID相同的列表将被删除后替换成覆盖者的列表(列表ID不同的列表将被合并到一起),若勾选完全覆盖,则被覆盖者的所有列表将被移除,然后替换成覆盖者的列表。",
|
||||
"sync__title": "选择与 {name} 的列表同步方式",
|
||||
"sync_status_disabled": "未连接",
|
||||
"tag__high_quality": "HQ",
|
||||
"tag__lossless": "SQ",
|
||||
|
||||
@@ -81,7 +81,6 @@
|
||||
"list__add_to": "添加到...",
|
||||
"list__collect": "收藏",
|
||||
"list__copy_name": "複製歌曲名",
|
||||
"list__dislike": "不喜歡",
|
||||
"list__download": "下載",
|
||||
"list__export_part_desc": "選擇列表文件保存位置",
|
||||
"list__file": "定位文件",
|
||||
@@ -126,7 +125,6 @@
|
||||
"list_sort_modal_by_down": "降序",
|
||||
"list_sort_modal_by_field": "排序字段",
|
||||
"list_sort_modal_by_name": "歌曲名",
|
||||
"list_sort_modal_by_random": "隨機亂序",
|
||||
"list_sort_modal_by_singer": "歌手名",
|
||||
"list_sort_modal_by_source": "歌曲源",
|
||||
"list_sort_modal_by_time": "時長",
|
||||
@@ -225,7 +223,6 @@
|
||||
"player__play_toggle_mode_off": "禁用",
|
||||
"player__play_toggle_mode_random": "列表隨機",
|
||||
"player__play_toggle_mode_single_loop": "單曲循環",
|
||||
"player__playback_preserves_pitch": "音調補償",
|
||||
"player__playback_rate": "當前播放速率:",
|
||||
"player__playback_rate_reset_btn": "重置",
|
||||
"player__playing": "播放中...",
|
||||
@@ -247,7 +244,7 @@
|
||||
"player__sound_effect_biquad_filter_save_input": "新預設...",
|
||||
"player__sound_effect_convolution": "環境混響音效",
|
||||
"player__sound_effect_convolution_file_bright_hall": "大廳",
|
||||
"player__sound_effect_convolution_file_cardiod_35_10_spread": "心形擴散",
|
||||
"player__sound_effect_convolution_file_cardiod_35_10_spread": "搖滾",
|
||||
"player__sound_effect_convolution_file_cinema_diningroom": "電影院",
|
||||
"player__sound_effect_convolution_file_dining_living_true_stereo": "餐廳",
|
||||
"player__sound_effect_convolution_file_feedback_spring": "反饋彈簧",
|
||||
@@ -266,10 +263,6 @@
|
||||
"player__sound_effect_panner_enabled": "啟用",
|
||||
"player__sound_effect_panner_sound_r": "聲音距離",
|
||||
"player__sound_effect_panner_sound_speed": "環繞速度",
|
||||
"player__sound_effect_pitch_shifter": "音調升降調節",
|
||||
"player__sound_effect_pitch_shifter_preset_semitones": "{num} 半音",
|
||||
"player__sound_effect_pitch_shifter_reset_btn": "重置",
|
||||
"player__sound_effect_pitch_shifter_tip": "由於升降調需要實時處理音頻數據,這會導致額外的CPU佔用\n\n已知問題:\n如果CPU資源不夠時將處理導致任務堆積而出現聲音異常,這時需要暫停播放一段時間等堆積的任務處理完畢再播放",
|
||||
"player__stop": "暫停播放",
|
||||
"player__volume": "當前音量:",
|
||||
"player__volume_mute_label": "靜音",
|
||||
@@ -383,10 +376,6 @@
|
||||
"setting__desktop_lyric_shadow_color": "陰影顏色",
|
||||
"setting__desktop_lyric_show_taskbar": "在任務欄顯示歌詞進程(此設置用於在錄屏軟件無法捕獲歌詞窗口時的變通解決方法)",
|
||||
"setting__desktop_lyric_unplay_color": "未播放顏色",
|
||||
"setting__dislike_list_input_tip": "歌曲名@歌手名\n歌曲名\n@歌手名",
|
||||
"setting__dislike_list_save_btn": "保存",
|
||||
"setting__dislike_list_tips": "1. 每條一行,若歌曲或者歌手名字中存在“@”符號,需要將其替換成“#”\n2. 指定某歌手的某首歌:歌曲名@歌手名\n3. 指定某首歌:歌曲名\n4. 指定某歌手:@歌手名",
|
||||
"setting__dislike_list_title": "不喜歡的歌曲規則列表",
|
||||
"setting__download": "下載設置",
|
||||
"setting__download_data_embed": "是否將以下內容嵌入到音頻文件中",
|
||||
"setting__download_embed_lyric": "歌詞嵌入",
|
||||
@@ -426,9 +415,6 @@
|
||||
"setting__hot_key_desktop_lyric_toggle_visible": "開/關桌面歌詞",
|
||||
"setting__hot_key_global_title": "全局快捷鍵",
|
||||
"setting__hot_key_local_title": "軟件內快捷鍵",
|
||||
"setting__hot_key_player_music_dislike": "不喜歡該歌曲",
|
||||
"setting__hot_key_player_music_love": "收藏歌曲",
|
||||
"setting__hot_key_player_music_unlove": "取消收藏",
|
||||
"setting__hot_key_player_next": "下一首歌曲",
|
||||
"setting__hot_key_player_prev": "上一首歌曲",
|
||||
"setting__hot_key_player_toggle_play": "播放/暫停控制",
|
||||
@@ -457,9 +443,6 @@
|
||||
"setting__odc_clear_search_input": "離開搜索界面時清空搜索框",
|
||||
"setting__odc_clear_search_list": "離開搜索界面時清空搜索列表",
|
||||
"setting__other": "其他",
|
||||
"setting__other_dislike_list": "不喜歡的歌曲規則",
|
||||
"setting__other_dislike_list_label": "規則數量:",
|
||||
"setting__other_dislike_list_show_btn": "編輯不喜歡歌曲規則",
|
||||
"setting__other_listdata": "列表數據清理",
|
||||
"setting__other_listdata_clear_btn": "清空我的列表數據",
|
||||
"setting__other_listdata_clear_tip_confirm": "這將清理你創建的 所有列表 及收藏的 所有歌曲,是否真的要繼續?",
|
||||
@@ -485,7 +468,6 @@
|
||||
"setting__other_tray_theme_native": "白色",
|
||||
"setting__other_tray_theme_origin": "原色",
|
||||
"setting__play": "播放設置",
|
||||
"setting__play_auto_clean_played_list": "點擊與播放列表相同的列表切歌時是否清空已播放列表(隨機模式下列表內所有歌曲會重新參與隨機)",
|
||||
"setting__play_auto_skip_on_error": "播放錯誤時自動切換歌曲",
|
||||
"setting__play_detail": "播放詳情頁設置",
|
||||
"setting__play_detail_align": "歌詞對齊方式",
|
||||
@@ -534,16 +516,10 @@
|
||||
"setting__sync_server_address": "同步服務地址:{address}",
|
||||
"setting__sync_server_auth_code": "連接碼:{code}",
|
||||
"setting__sync_server_device": "已連接的設備:{devices}",
|
||||
"setting__sync_server_device_list_btn_remove": "移除",
|
||||
"setting__sync_server_device_list_noitem": "這裡啥也沒有 ┗( ▔, ▔ )┛",
|
||||
"setting__sync_server_device_list_time": "最後連接時間:{time}",
|
||||
"setting__sync_server_device_list_tips": "💡 設備被移除後,再連接時需要重新輸入連接碼",
|
||||
"setting__sync_server_device_list_title": "已認證設備",
|
||||
"setting__sync_server_mode": "服務端模式(由於數據是明文傳輸,請在受信任的網絡下使用)",
|
||||
"setting__sync_server_port": "同步端口設置",
|
||||
"setting__sync_server_port_tip": "請輸入同步服務端口號",
|
||||
"setting__sync_server_refresh_code": "刷新連接碼",
|
||||
"setting__sync_server_show_device_list": "已認證設備列表",
|
||||
"setting__sync_tip": "使用方式請看常見問題“同步功能”部分",
|
||||
"setting__update": "軟件更新",
|
||||
"setting__update_checking": "檢查更新中...",
|
||||
@@ -558,7 +534,6 @@
|
||||
"setting__update_show_change_log": "更新版本後的首次啟動時顯示更新日誌",
|
||||
"setting__update_try_auto_update": "發現新版本時嘗試自動下載更新",
|
||||
"setting__update_unknown": "未知",
|
||||
"setting__update_unknown_tip": "❓ 獲取最新版本信息失敗,建議去關於界面打開項目發布地址查看當前版本是否最新",
|
||||
"setting_sync_status_enabled": "已連接",
|
||||
"song_list": "歌單",
|
||||
"songlist__import_input_show_btn": "打開歌單",
|
||||
@@ -588,20 +563,14 @@
|
||||
"source_xm": "蝦米音樂",
|
||||
"sync__auth_code_input_tip": "請輸入連接碼",
|
||||
"sync__auth_code_title": "需要輸入連接碼",
|
||||
"sync__dislike_merge_tip_desc": "合併兩邊列表內容並去重",
|
||||
"sync__dislike_other_tip_desc": "“取消同步”將不使用不喜歡列表同步功能",
|
||||
"sync__dislike_overwrite_tip_desc": "被覆蓋者的列表將被替換成覆蓋者的列表",
|
||||
"sync__dislike_title": "選擇與 {name} 的不喜歡列表同步方式",
|
||||
"sync__list_merge_tip_desc": "將兩邊的列表合併到一起,相同的歌曲將被去掉(去掉的是被合併者的歌曲),不同的歌曲將被添加。",
|
||||
"sync__list_other_tip_desc": "“取消同步”將不使用列表同步功能。",
|
||||
"sync__list_overwrite_tip_desc": "被覆蓋者與覆蓋者列表ID相同的列表將被刪除後替換成覆蓋者的列表(列表ID不同的列表將被合併到一起),若勾選完全覆蓋,則被覆蓋者的所有列表將被移除,然後替換成覆蓋者的列表。",
|
||||
"sync__list_title": "選擇與 {name} 的列表同步方式",
|
||||
"sync__merge_btn_local_remote": "本機列表 合併 遠程列表",
|
||||
"sync__merge_btn_remote_local": "遠程列表 合併 本機列表",
|
||||
"sync__merge_label": "合併",
|
||||
"sync__merge_tip": "合併:",
|
||||
"sync__merge_tip_desc": "將兩邊的列表合併到一起,相同的歌曲將被去掉(去掉的是被合併者的歌曲),不同的歌曲將被添加。",
|
||||
"sync__other_label": "其他",
|
||||
"sync__other_tip": "其他:",
|
||||
"sync__other_tip_desc": "“取消同步”將直接斷開雙方的連接。",
|
||||
"sync__overwrite": "完全覆蓋",
|
||||
"sync__overwrite_btn_cancel": "取消同步",
|
||||
"sync__overwrite_btn_local_remote": "本機列表 覆蓋 遠程列表",
|
||||
@@ -609,6 +578,8 @@
|
||||
"sync__overwrite_btn_remote_local": "遠程列表 覆蓋 本機列表",
|
||||
"sync__overwrite_label": "覆蓋",
|
||||
"sync__overwrite_tip": "覆蓋:",
|
||||
"sync__overwrite_tip_desc": "被覆蓋者與覆蓋者列表ID相同的列表將被刪除後替換成覆蓋者的列表(列表ID不同的列表將被合併到一起),若勾選完全覆蓋,則被覆蓋者的所有列表將被移除,然後替換成覆蓋者的列表。",
|
||||
"sync__title": "選擇與 {name} 的列表同步方式",
|
||||
"sync_status_disabled": "未連接",
|
||||
"tag__high_quality": "HQ",
|
||||
"tag__lossless": "SQ",
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
/* eslint-env node */
|
||||
const { base, typescript } = require('../../.eslintrc.base.cjs')
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
...base,
|
||||
overrides: [
|
||||
{
|
||||
...typescript,
|
||||
parserOptions: {
|
||||
project: './tsconfig.json',
|
||||
},
|
||||
},
|
||||
],
|
||||
ignorePatterns: [
|
||||
'vendors',
|
||||
],
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import path from 'node:path'
|
||||
import { join, dirname } from 'path'
|
||||
import { existsSync, mkdirSync, renameSync } from 'fs'
|
||||
import { app, shell, screen, nativeTheme, dialog } from 'electron'
|
||||
import { URL_SCHEME_RXP } from '@common/constants'
|
||||
@@ -6,23 +6,26 @@ import { getTheme, initHotKey, initSetting, parseEnvParams } from './utils'
|
||||
import { navigationUrlWhiteList } from '@common/config'
|
||||
import defaultSetting from '@common/defaultSetting'
|
||||
import { closeWindow, isExistWindow as isExistMainWindow, showWindow as showMainWindow } from './modules/winMain'
|
||||
import { createAppEvent, createDislikeEvent, createListEvent } from '@main/event'
|
||||
import { createAppEvent, createListEvent } from '@main/event'
|
||||
import { isMac, log } from '@common/utils'
|
||||
import createWorkers from './worker'
|
||||
import { migrateDBData } from './utils/migrate'
|
||||
import { encodePath, openDirInExplorer } from '@common/utils/electron'
|
||||
import { openDirInExplorer } from '@common/utils/electron'
|
||||
|
||||
export const initGlobalData = () => {
|
||||
global.isDev = process.env.NODE_ENV !== 'production'
|
||||
const envParams = parseEnvParams()
|
||||
global.envParams = {
|
||||
cmdParams: envParams.cmdParams,
|
||||
deeplink: envParams.deeplink,
|
||||
}
|
||||
|
||||
global.staticPath =
|
||||
process.env.NODE_ENV !== 'production'
|
||||
? webpackStaticPath
|
||||
: path.join(encodePath(__dirname), 'static')
|
||||
if (global.isDev) {
|
||||
// eslint-disable-next-line no-undef
|
||||
global.staticPath = webpackStaticPath
|
||||
} else {
|
||||
global.staticPath = join(__dirname, '/static')
|
||||
}
|
||||
}
|
||||
|
||||
export const initSingleInstanceHandle = () => {
|
||||
@@ -72,10 +75,10 @@ export const applyElectronEnvParams = () => {
|
||||
export const setUserDataPath = () => {
|
||||
// windows平台下如果应用目录下存在 portable 文件夹则将数据存在此文件下
|
||||
if (process.platform == 'win32') {
|
||||
const portablePath = path.join(path.dirname(app.getPath('exe')), '/portable')
|
||||
const portablePath = join(dirname(app.getPath('exe')), '/portable')
|
||||
if (existsSync(portablePath)) {
|
||||
app.setPath('appData', portablePath)
|
||||
const appDataPath = path.join(portablePath, '/userData')
|
||||
const appDataPath = join(portablePath, '/userData')
|
||||
if (!existsSync(appDataPath)) mkdirSync(appDataPath)
|
||||
app.setPath('userData', appDataPath)
|
||||
}
|
||||
@@ -83,12 +86,12 @@ export const setUserDataPath = () => {
|
||||
|
||||
const userDataPath = app.getPath('userData')
|
||||
global.lxOldDataPath = userDataPath
|
||||
global.lxDataPath = path.join(userDataPath, 'LxDatas')
|
||||
global.lxDataPath = join(userDataPath, 'LxDatas')
|
||||
if (!existsSync(global.lxDataPath)) mkdirSync(global.lxDataPath)
|
||||
}
|
||||
|
||||
export const registerDeeplink = (startApp: () => void) => {
|
||||
if (process.env.NODE_ENV !== 'production' && process.platform === 'win32') {
|
||||
if (global.isDev && process.platform === 'win32') {
|
||||
// Set the path of electron.exe and your app.
|
||||
// These two additional parameters are only available on windows.
|
||||
// console.log(process.execPath, process.argv)
|
||||
@@ -114,8 +117,8 @@ export const registerDeeplink = (startApp: () => void) => {
|
||||
export const listenerAppEvent = (startApp: () => void) => {
|
||||
app.on('web-contents-created', (event, contents) => {
|
||||
contents.on('will-navigate', (event, navigationUrl) => {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
console.log('navigation to url:', navigationUrl.length > 130 ? navigationUrl.substring(0, 130) + '...' : navigationUrl)
|
||||
if (global.isDev) {
|
||||
console.log('navigation to url:', navigationUrl)
|
||||
return
|
||||
}
|
||||
if (!navigationUrlWhiteList.some(url => url.test(navigationUrl))) {
|
||||
@@ -212,7 +215,6 @@ export const initAppSetting = async() => {
|
||||
// mainWindowClosed: true,
|
||||
event_app: createAppEvent(),
|
||||
event_list: createListEvent(),
|
||||
event_dislike: createDislikeEvent(),
|
||||
appSetting: defaultSetting,
|
||||
worker: createWorkers(),
|
||||
hotKey: {
|
||||
@@ -238,13 +240,13 @@ export const initAppSetting = async() => {
|
||||
if (!isInitialized) {
|
||||
let dbFileExists = await global.lx.worker.dbService.init(global.lxDataPath)
|
||||
if (dbFileExists === null) {
|
||||
const backPath = path.join(global.lxDataPath, `lx.data.db.${Date.now()}.bak`)
|
||||
const backPath = join(global.lxDataPath, `lx.data.db.${Date.now()}.bak`)
|
||||
dialog.showMessageBoxSync({
|
||||
type: 'warning',
|
||||
message: 'Database verify failed',
|
||||
detail: `数据库表结构校验失败,我们将把有问题的数据库备份到:${backPath}\n若此问题导致你的数据丢失,你可以尝试从备份文件找回它们。\n\nThe database table structure verification failed, we will back up the problematic database to: ${backPath}\nIf this problem causes your data to be lost, you can try to retrieve them from the backup file.`,
|
||||
})
|
||||
renameSync(path.join(global.lxDataPath, 'lx.data.db'), backPath)
|
||||
renameSync(join(global.lxDataPath, 'lx.data.db'), backPath)
|
||||
openDirInExplorer(backPath)
|
||||
dbFileExists = await global.lx.worker.dbService.init(global.lxDataPath)
|
||||
}
|
||||
@@ -254,7 +256,7 @@ export const initAppSetting = async() => {
|
||||
}
|
||||
// global.lx.theme = getTheme()
|
||||
|
||||
isInitialized ||= true
|
||||
isInitialized = true
|
||||
}
|
||||
|
||||
export const quitApp = () => {
|
||||
|
||||
@@ -102,4 +102,4 @@ declare class EventType extends Event {
|
||||
off<K extends keyof EventMethods>(event: K, listener: EventMethods[K]): this
|
||||
}
|
||||
|
||||
export type Type = Omit<EventType, keyof Omit<EventEmitter, 'on' | 'off' | 'once'>>
|
||||
export declare type Type = Omit<EventType, keyof Omit<EventEmitter, 'on' | 'off' | 'once'>>
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
import { EventEmitter } from 'events'
|
||||
|
||||
|
||||
export class Event extends EventEmitter {
|
||||
dislike_changed() {
|
||||
this.emit('dislike_changed')
|
||||
}
|
||||
|
||||
/**
|
||||
* 覆盖整个列表数据
|
||||
* @param dislikeData 列表数据
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async dislike_data_overwrite(dislikeData: LX.Dislike.DislikeRules, isRemote: boolean = false) {
|
||||
await global.lx.worker.dbService.dislikeInfoOverwrite(dislikeData)
|
||||
this.emit('dislike_data_overwrite', dislikeData, isRemote)
|
||||
this.dislike_changed()
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加歌曲到列表
|
||||
* @param dislikeId 列表id
|
||||
* @param musicInfos 添加的歌曲信息
|
||||
* @param addMusicLocationType 添加在到列表的位置
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async dislike_music_add(musicInfo: LX.Dislike.DislikeMusicInfo[], isRemote: boolean = false) {
|
||||
// const changedIds =
|
||||
await global.lx.worker.dbService.dislikeInfoAdd(musicInfo)
|
||||
// await checkUpdateDislike(changedIds)
|
||||
this.emit('dislike_music_add', musicInfo, isRemote)
|
||||
this.dislike_changed()
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空列表内的歌曲
|
||||
* @param ids 列表Id
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async dislike_music_clear(isRemote: boolean = false) {
|
||||
// const changedIds =
|
||||
await global.lx.worker.dbService.dislikeInfoOverwrite('')
|
||||
// await checkUpdateDislike(changedIds)
|
||||
this.emit('dislike_music_clear', isRemote)
|
||||
this.dislike_changed()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type EventMethods = Omit<EventType, keyof EventEmitter>
|
||||
declare class EventType extends Event {
|
||||
on<K extends keyof EventMethods>(event: K, listener: EventMethods[K]): this
|
||||
once<K extends keyof EventMethods>(event: K, listener: EventMethods[K]): this
|
||||
off<K extends keyof EventMethods>(event: K, listener: EventMethods[K]): this
|
||||
}
|
||||
export type Type = Omit<EventType, keyof Omit<EventEmitter, 'on' | 'off' | 'once'>>
|
||||
@@ -14,18 +14,6 @@ import { EventEmitter } from 'events'
|
||||
// musicOverwrite,
|
||||
// } from '@main/workers/dbService/modules/list'
|
||||
|
||||
// 兼容v2.3.0之前版本插入数字类型的ID导致其意外在末尾追加 .0 的问题,确保所有ID都是字符串类型
|
||||
const fixListIdType = (lists: LX.List.UserListInfo[] | LX.List.UserListInfoFull[]) => {
|
||||
for (const list of lists) {
|
||||
if (typeof list.sourceListId == 'number') {
|
||||
list.sourceListId = String(list.sourceListId)
|
||||
if (typeof list.id == 'number') {
|
||||
list.id = String(list.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Event extends EventEmitter {
|
||||
list_changed() {
|
||||
this.emit('list_changed')
|
||||
@@ -37,7 +25,6 @@ export class Event extends EventEmitter {
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async list_data_overwrite(listData: MakeOptional<LX.List.ListDataFull, 'tempList'>, isRemote: boolean = false) {
|
||||
fixListIdType(listData.userList)
|
||||
await global.lx.worker.dbService.listDataOverwrite(listData)
|
||||
this.emit('list_data_overwrite', listData, isRemote)
|
||||
this.list_changed()
|
||||
@@ -50,7 +37,6 @@ export class Event extends EventEmitter {
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async list_create(position: number, lists: LX.List.UserListInfo[], isRemote: boolean = false) {
|
||||
fixListIdType(lists)
|
||||
await global.lx.worker.dbService.createUserLists(position, lists)
|
||||
this.emit('list_create', position, lists, isRemote)
|
||||
this.list_changed()
|
||||
@@ -185,4 +171,4 @@ declare class EventType extends Event {
|
||||
once<K extends keyof EventMethods>(event: K, listener: EventMethods[K]): this
|
||||
off<K extends keyof EventMethods>(event: K, listener: EventMethods[K]): this
|
||||
}
|
||||
export type Type = Omit<EventType, keyof Omit<EventEmitter, 'on' | 'off' | 'once'>>
|
||||
export declare type Type = Omit<EventType, keyof Omit<EventEmitter, 'on' | 'off' | 'once'>>
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { Event as App, type Type as AppType } from './AppEvent'
|
||||
import { Event as List, type Type as ListType } from './ListEvent'
|
||||
import { Event as Dislike, type Type as DislikeType } from './DislikeEvent'
|
||||
|
||||
export type {
|
||||
AppType,
|
||||
ListType,
|
||||
DislikeType,
|
||||
}
|
||||
|
||||
export const createAppEvent = (): AppType => {
|
||||
@@ -16,7 +14,3 @@ export const createListEvent = (): ListType => {
|
||||
return new List()
|
||||
}
|
||||
|
||||
export const createDislikeEvent = (): DislikeType => {
|
||||
return new Dislike()
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import { app } from 'electron'
|
||||
import electronDebug from 'electron-debug'
|
||||
import * as electronDebug from 'electron-debug'
|
||||
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
|
||||
// Install `electron-debug` with `devtron`
|
||||
electronDebug({
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { app } from 'electron'
|
||||
import './utils/logInit'
|
||||
import '@common/error'
|
||||
import {
|
||||
initGlobalData,
|
||||
|
||||
@@ -20,7 +20,7 @@ export default () => {
|
||||
})
|
||||
|
||||
mainHandle<string[]>(CMMON_EVENT_NAME.get_system_fonts, async() => {
|
||||
return getFonts()
|
||||
return await getFonts()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export { registerRendererEvents } from './winRendererEvent'
|
||||
|
||||
export { default } from './rendererEvent'
|
||||
@@ -1,18 +0,0 @@
|
||||
import { mainHandle } from '@common/mainIpc'
|
||||
import { DISLIKE_EVENT_NAME } from '@common/ipcNames'
|
||||
|
||||
// 列表操作事件(公共,只注册一次)
|
||||
export default () => {
|
||||
mainHandle<LX.Dislike.DislikeInfo>(DISLIKE_EVENT_NAME.get_dislike_music_infos, async() => {
|
||||
return global.lx.worker.dbService.getDislikeListInfo()
|
||||
})
|
||||
mainHandle<LX.Dislike.DislikeMusicInfo[]>(DISLIKE_EVENT_NAME.add_dislike_music_infos, async({ params: listData }) => {
|
||||
await global.lx.event_dislike.dislike_music_add(listData, false)
|
||||
})
|
||||
mainHandle<LX.Dislike.DislikeRules>(DISLIKE_EVENT_NAME.overwrite_dislike_music_infos, async({ params: rules }) => {
|
||||
await global.lx.event_dislike.dislike_data_overwrite(rules, false)
|
||||
})
|
||||
mainHandle(DISLIKE_EVENT_NAME.clear_dislike_music_infos, async() => {
|
||||
await global.lx.event_dislike.dislike_music_clear(false)
|
||||
})
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { DISLIKE_EVENT_NAME } from '@common/ipcNames'
|
||||
|
||||
// 发送列表操作事件到渲染进程的注册方法
|
||||
// 哪个渲染进程需要接收则引入此方法注册
|
||||
export const registerRendererEvents = (sendEvent: <T = any>(name: string, params?: T | undefined) => void) => {
|
||||
const dislike_music_add = async(listData: LX.Dislike.DislikeMusicInfo[]) => {
|
||||
sendEvent<LX.Dislike.DislikeMusicInfo[]>(DISLIKE_EVENT_NAME.add_dislike_music_infos, listData)
|
||||
}
|
||||
const dislike_data_overwrite = async(rules: LX.Dislike.DislikeRules) => {
|
||||
sendEvent<LX.Dislike.DislikeRules>(DISLIKE_EVENT_NAME.overwrite_dislike_music_infos, rules)
|
||||
}
|
||||
const dislike_music_clear = async() => {
|
||||
sendEvent(DISLIKE_EVENT_NAME.clear_dislike_music_infos)
|
||||
}
|
||||
global.lx.event_dislike.on('dislike_music_add', dislike_music_add)
|
||||
global.lx.event_dislike.on('dislike_data_overwrite', dislike_data_overwrite)
|
||||
global.lx.event_dislike.on('dislike_music_clear', dislike_music_clear)
|
||||
|
||||
return () => {
|
||||
global.lx.event_dislike.off('dislike_music_add', dislike_music_add)
|
||||
global.lx.event_dislike.off('dislike_data_overwrite', dislike_data_overwrite)
|
||||
global.lx.event_dislike.off('dislike_music_clear', dislike_music_clear)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
import common from './common'
|
||||
import list from './list'
|
||||
import dislike from './dislike'
|
||||
|
||||
export default () => {
|
||||
common()
|
||||
list()
|
||||
dislike()
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { PLAYER_EVENT_NAME } from '@common/ipcNames'
|
||||
// 列表操作事件(公共,只注册一次)
|
||||
export default () => {
|
||||
mainHandle<LX.List.UserListInfo[]>(PLAYER_EVENT_NAME.list_get, async() => {
|
||||
return global.lx.worker.dbService.getAllUserList()
|
||||
return await global.lx.worker.dbService.getAllUserList()
|
||||
})
|
||||
mainHandle<LX.List.ListActionDataOverwrite>(PLAYER_EVENT_NAME.list_data_overwire, async({ params: listData }) => {
|
||||
await global.lx.event_list.list_data_overwrite(listData, false)
|
||||
@@ -22,7 +22,7 @@ export default () => {
|
||||
await global.lx.event_list.list_update_position(position, ids, false)
|
||||
})
|
||||
mainHandle<string, LX.Music.MusicInfo[]>(PLAYER_EVENT_NAME.list_music_get, async({ params: listId }) => {
|
||||
return global.lx.worker.dbService.getListMusics(listId)
|
||||
return await global.lx.worker.dbService.getListMusics(listId)
|
||||
})
|
||||
mainHandle<LX.List.ListActionMusicAdd>(PLAYER_EVENT_NAME.list_music_add, async({ params: { id, musicInfos, addMusicLocationType } }) => {
|
||||
await global.lx.event_list.list_music_add(id, musicInfos, addMusicLocationType, false)
|
||||
@@ -46,9 +46,9 @@ export default () => {
|
||||
await global.lx.event_list.list_music_clear(listId, false)
|
||||
})
|
||||
mainHandle<LX.List.ListActionCheckMusicExistList, boolean>(PLAYER_EVENT_NAME.list_music_check_exist, async({ params: { listId, musicInfoId } }) => {
|
||||
return global.lx.worker.dbService.checkListExistMusic(listId, musicInfoId)
|
||||
return await global.lx.worker.dbService.checkListExistMusic(listId, musicInfoId)
|
||||
})
|
||||
mainHandle<string, string[]>(PLAYER_EVENT_NAME.list_music_get_list_ids, async({ params: musicInfoId }) => {
|
||||
return global.lx.worker.dbService.getMusicExistListIds(musicInfoId)
|
||||
return await global.lx.worker.dbService.getMusicExistListIds(musicInfoId)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { request, generateRsaKey } from './utils'
|
||||
import { getSyncAuthKey, setSyncAuthKey } from './data'
|
||||
import { getSyncAuthKey, setSyncAuthKey } from '../data'
|
||||
import { SYNC_CODE } from '@common/constants'
|
||||
import log from '../log'
|
||||
import { aesDecrypt, aesEncrypt, getComputerName, rsaDecrypt } from '../utils'
|
||||
import { toMD5 } from '@common/utils/nodejs'
|
||||
import { SYNC_CODE } from '@common/constants_sync'
|
||||
|
||||
|
||||
const hello = async(urlInfo: LX.Sync.Client.UrlInfo) => request(`${urlInfo.httpProtocol}//${urlInfo.hostPath}/hello`)
|
||||
@@ -26,7 +25,7 @@ const getServerId = async(urlInfo: LX.Sync.Client.UrlInfo) => request(`${urlInfo
|
||||
})
|
||||
|
||||
const codeAuth = async(urlInfo: LX.Sync.Client.UrlInfo, serverId: string, authCode: string) => {
|
||||
let key = toMD5(authCode).substring(0, 16)
|
||||
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')
|
||||
let { publicKey, privateKey } = await generateRsaKey()
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import WebSocket from 'ws'
|
||||
import { encryptMsg, decryptMsg } from './utils'
|
||||
import { callObj } from './sync'
|
||||
// import { action as commonAction } from '@root/store/modules/common'
|
||||
// import { getStore } from '@root/store'
|
||||
// import registerSyncListHandler from './syncList'
|
||||
import * as modules from './modules'
|
||||
// import { action as commonAction } from '@/store/modules/common'
|
||||
// import { getStore } from '@/store'
|
||||
import registerSyncListHandler from './syncList'
|
||||
import log from '../log'
|
||||
import { SYNC_CLOSE_CODE, SYNC_CODE } from '@common/constants'
|
||||
import { dateFormat } from '@common/utils/common'
|
||||
import { aesEncrypt, getAddress } from '../utils'
|
||||
import { sendClientStatus } from '@main/modules/winMain'
|
||||
import { createMsg2call } from 'message2call'
|
||||
import { SYNC_CLOSE_CODE, SYNC_CODE } from '@common/constants_sync'
|
||||
|
||||
let status: LX.Sync.ClientStatus = {
|
||||
status: false,
|
||||
@@ -31,11 +30,16 @@ export const sendSyncMessage = (message: string) => {
|
||||
sendClientStatus(status)
|
||||
}
|
||||
|
||||
const handleConnection = (socket: LX.Sync.Client.Socket) => {
|
||||
for (const moduleInit of Object.values(modules)) {
|
||||
moduleInit(socket)
|
||||
}
|
||||
}
|
||||
|
||||
const heartbeatTools = {
|
||||
failedNum: 0,
|
||||
maxTryNum: 100000,
|
||||
stepMs: 3000,
|
||||
connectTimeout: null as NodeJS.Timeout | null,
|
||||
pingTimeout: null as NodeJS.Timeout | null,
|
||||
delayRetryTimeout: null as NodeJS.Timeout | null,
|
||||
handleOpen() {
|
||||
@@ -55,16 +59,15 @@ const heartbeatTools = {
|
||||
}, 30000 + 1000)
|
||||
},
|
||||
reConnnect() {
|
||||
this.clearTimeout()
|
||||
if (this.pingTimeout) {
|
||||
clearTimeout(this.pingTimeout)
|
||||
this.pingTimeout = null
|
||||
}
|
||||
// client = null
|
||||
if (!client) return
|
||||
|
||||
if (++this.failedNum > this.maxTryNum) {
|
||||
this.failedNum = 0
|
||||
sendSyncStatus({
|
||||
status: false,
|
||||
message: 'Connect error',
|
||||
})
|
||||
throw new Error('connect error')
|
||||
}
|
||||
|
||||
@@ -87,10 +90,6 @@ const heartbeatTools = {
|
||||
}, waitTime)
|
||||
},
|
||||
clearTimeout() {
|
||||
if (this.connectTimeout) {
|
||||
clearTimeout(this.connectTimeout)
|
||||
this.connectTimeout = null
|
||||
}
|
||||
if (this.delayRetryTimeout) {
|
||||
clearTimeout(this.delayRetryTimeout)
|
||||
this.delayRetryTimeout = null
|
||||
@@ -102,32 +101,7 @@ const heartbeatTools = {
|
||||
},
|
||||
connect(socket: LX.Sync.Client.Socket) {
|
||||
console.log('heartbeatTools connect')
|
||||
this.connectTimeout = setTimeout(() => {
|
||||
this.connectTimeout = null
|
||||
if (client) {
|
||||
try {
|
||||
client.close(SYNC_CLOSE_CODE.failed)
|
||||
} catch {}
|
||||
}
|
||||
if (++this.failedNum > this.maxTryNum) {
|
||||
this.failedNum = 0
|
||||
sendSyncStatus({
|
||||
status: false,
|
||||
message: 'Connect error',
|
||||
})
|
||||
throw new Error('connect error')
|
||||
}
|
||||
sendSyncStatus({
|
||||
status: false,
|
||||
message: 'Connect timeout, try reconnect...',
|
||||
})
|
||||
this.reConnnect()
|
||||
}, 2 * 60 * 1000)
|
||||
socket.on('open', () => {
|
||||
if (this.connectTimeout) {
|
||||
clearTimeout(this.connectTimeout)
|
||||
this.connectTimeout = null
|
||||
}
|
||||
this.handleOpen()
|
||||
})
|
||||
socket.on('ping', () => {
|
||||
@@ -149,7 +123,7 @@ const heartbeatTools = {
|
||||
let client: LX.Sync.Client.Socket | null
|
||||
// let listSyncPromise: Promise<void>
|
||||
export const connect = (urlInfo: LX.Sync.Client.UrlInfo, keyInfo: LX.Sync.ClientKeyInfo) => {
|
||||
client = new WebSocket(`${urlInfo.wsProtocol}//${urlInfo.hostPath}/socket?i=${encodeURIComponent(keyInfo.clientId)}&t=${encodeURIComponent(aesEncrypt(SYNC_CODE.msgConnect, keyInfo.key))}`, {
|
||||
client = new WebSocket(`${urlInfo.wsProtocol}//${urlInfo.hostPath}?i=${encodeURIComponent(keyInfo.clientId)}&t=${encodeURIComponent(aesEncrypt(SYNC_CODE.msgConnect, keyInfo.key))}`, {
|
||||
}) as LX.Sync.Client.Socket
|
||||
client.data = {
|
||||
keyInfo,
|
||||
@@ -157,70 +131,39 @@ export const connect = (urlInfo: LX.Sync.Client.UrlInfo, keyInfo: LX.Sync.Client
|
||||
}
|
||||
heartbeatTools.connect(client)
|
||||
|
||||
// listSyncPromise = registerSyncListHandler(socket)
|
||||
let events: Partial<{ [K in keyof LX.Sync.ActionSyncSendType]: Array<(data: LX.Sync.ActionSyncSendType[K]) => (void | Promise<void>)> }> = {}
|
||||
let closeEvents: Array<(err: Error) => (void | Promise<void>)> = []
|
||||
let disconnected = true
|
||||
|
||||
const message2read = createMsg2call<LX.Sync.ServerSyncActions>({
|
||||
funcsObj: {
|
||||
...callObj,
|
||||
finished() {
|
||||
log.info('sync list success')
|
||||
client!.isReady = true
|
||||
sendSyncStatus({
|
||||
status: true,
|
||||
message: '',
|
||||
})
|
||||
heartbeatTools.failedNum = 0
|
||||
},
|
||||
},
|
||||
timeout: 120 * 1000,
|
||||
sendMessage(data) {
|
||||
if (disconnected) throw new Error('disconnected')
|
||||
void encryptMsg(keyInfo, JSON.stringify(data)).then((data) => {
|
||||
client?.send(data)
|
||||
}).catch((err) => {
|
||||
log.error('encrypt msg error: ', err)
|
||||
client?.close(SYNC_CLOSE_CODE.failed)
|
||||
})
|
||||
},
|
||||
onCallBeforeParams(rawArgs) {
|
||||
return [client, ...rawArgs]
|
||||
},
|
||||
onError(error, path, groupName) {
|
||||
const name = groupName ?? ''
|
||||
log.error(`sync call ${name} ${path.join('.')} error:`, error)
|
||||
// if (groupName == null) return
|
||||
// client?.close(SYNC_CLOSE_CODE.failed)
|
||||
// sendSyncStatus({
|
||||
// status: false,
|
||||
// message: error.message,
|
||||
// })
|
||||
},
|
||||
})
|
||||
|
||||
client.remote = message2read.remote
|
||||
client.remoteQueueList = message2read.createQueueRemote('list')
|
||||
client.remoteQueueDislike = message2read.createQueueRemote('dislike')
|
||||
|
||||
client.addEventListener('message', ({ data }) => {
|
||||
if (data == 'ping') return
|
||||
if (typeof data === 'string') {
|
||||
void decryptMsg(keyInfo, data).then((data) => {
|
||||
let syncData: LX.Sync.ServerSyncActions
|
||||
try {
|
||||
syncData = JSON.parse(data)
|
||||
} catch (err) {
|
||||
log.error('parse msg error: ', err)
|
||||
client?.close(SYNC_CLOSE_CODE.failed)
|
||||
return
|
||||
}
|
||||
message2read.onMessage(syncData)
|
||||
}).catch((error) => {
|
||||
log.error('decrypt msg error: ', error)
|
||||
client?.close(SYNC_CLOSE_CODE.failed)
|
||||
})
|
||||
let syncData: LX.Sync.ActionSync
|
||||
try {
|
||||
syncData = JSON.parse(decryptMsg(keyInfo, data))
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
const handlers = events[syncData.action]
|
||||
if (handlers) {
|
||||
// @ts-expect-error
|
||||
for (const handler of handlers) void handler(syncData.data)
|
||||
}
|
||||
}
|
||||
})
|
||||
client.onRemoteEvent = function(eventName, handler) {
|
||||
let eventArr = events[eventName]
|
||||
if (!eventArr) events[eventName] = eventArr = []
|
||||
// let eventArr = events.get(eventName)
|
||||
// if (!eventArr) events.set(eventName, eventArr = [])
|
||||
eventArr.push(handler)
|
||||
|
||||
return () => {
|
||||
eventArr!.splice(eventArr!.indexOf(handler), 1)
|
||||
}
|
||||
}
|
||||
client.sendData = function(eventName, data, callback) {
|
||||
client?.send(encryptMsg(keyInfo, JSON.stringify({ action: eventName, data })), callback)
|
||||
}
|
||||
client.onClose = function(handler: typeof closeEvents[number]) {
|
||||
closeEvents.push(handler)
|
||||
return () => {
|
||||
@@ -228,32 +171,46 @@ export const connect = (urlInfo: LX.Sync.Client.UrlInfo, keyInfo: LX.Sync.Client
|
||||
}
|
||||
}
|
||||
|
||||
const initMessage = 'Wait syncing...'
|
||||
client.addEventListener('open', () => {
|
||||
log.info('connect')
|
||||
// const store = getStore()
|
||||
// global.lx.syncKeyInfo = keyInfo
|
||||
client!.isReady = false
|
||||
client!.moduleReadys = {
|
||||
list: false,
|
||||
dislike: false,
|
||||
}
|
||||
disconnected = false
|
||||
sendSyncStatus({
|
||||
status: false,
|
||||
message: initMessage,
|
||||
message: 'Wait syncing...',
|
||||
})
|
||||
void registerSyncListHandler(client as LX.Sync.Client.Socket).then(() => {
|
||||
log.info('sync list success')
|
||||
handleConnection(client as LX.Sync.Client.Socket)
|
||||
log.info('register list sync service success')
|
||||
client!.isReady = true
|
||||
heartbeatTools.failedNum = 0
|
||||
sendSyncStatus({
|
||||
status: true,
|
||||
message: '',
|
||||
})
|
||||
}).catch(err => {
|
||||
if (err.message == 'closed') {
|
||||
sendSyncStatus({
|
||||
status: false,
|
||||
message: '',
|
||||
})
|
||||
} else {
|
||||
console.log(err)
|
||||
log.r_error(err.stack)
|
||||
sendSyncStatus({
|
||||
status: false,
|
||||
message: err.message,
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
client.addEventListener('close', ({ code }) => {
|
||||
const err = new Error('closed')
|
||||
try {
|
||||
for (const handler of closeEvents) void handler(err)
|
||||
} catch (err: any) {
|
||||
log.error(err?.message)
|
||||
}
|
||||
for (const handler of closeEvents) void handler(err)
|
||||
closeEvents = []
|
||||
disconnected = true
|
||||
message2read.onDestroy()
|
||||
events = {}
|
||||
switch (code) {
|
||||
case SYNC_CLOSE_CODE.normal:
|
||||
// case SYNC_CLOSE_CODE.failed:
|
||||
@@ -261,15 +218,6 @@ export const connect = (urlInfo: LX.Sync.Client.UrlInfo, keyInfo: LX.Sync.Client
|
||||
status: false,
|
||||
message: '',
|
||||
})
|
||||
break
|
||||
case SYNC_CLOSE_CODE.failed:
|
||||
if (!status.message || status.message == initMessage) {
|
||||
sendSyncStatus({
|
||||
status: false,
|
||||
message: 'failed',
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
})
|
||||
client.addEventListener('error', ({ message }) => {
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { File } from '../../../../common/constants_sync'
|
||||
import { exists } from '../utils'
|
||||
|
||||
|
||||
let syncAuthKeys: Record<string, LX.Sync.ClientKeyInfo>
|
||||
|
||||
|
||||
const saveSyncAuthKeys = async() => {
|
||||
const syncAuthKeysFilePath = path.join(global.lxDataPath, File.clientDataPath, File.syncAuthKeysJSON)
|
||||
return fs.promises.writeFile(syncAuthKeysFilePath, JSON.stringify(syncAuthKeys), 'utf8')
|
||||
}
|
||||
|
||||
export const initClientInfo = async() => {
|
||||
if (syncAuthKeys != null) return
|
||||
const syncAuthKeysFilePath = path.join(global.lxDataPath, File.clientDataPath, File.syncAuthKeysJSON)
|
||||
if (await fs.promises.stat(syncAuthKeysFilePath).then(() => true).catch(() => false)) {
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
syncAuthKeys = JSON.parse((await fs.promises.readFile(syncAuthKeysFilePath)).toString())
|
||||
} else {
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
syncAuthKeys = {}
|
||||
const syncDataPath = path.join(global.lxDataPath, File.clientDataPath)
|
||||
if (!await exists(syncDataPath)) {
|
||||
await fs.promises.mkdir(syncDataPath, { recursive: true })
|
||||
}
|
||||
void saveSyncAuthKeys()
|
||||
}
|
||||
}
|
||||
|
||||
export const getSyncAuthKey = async(serverId: string) => {
|
||||
await initClientInfo()
|
||||
return syncAuthKeys[serverId] ?? null
|
||||
}
|
||||
export const setSyncAuthKey = async(serverId: string, info: LX.Sync.ClientKeyInfo) => {
|
||||
await initClientInfo()
|
||||
syncAuthKeys[serverId] = info
|
||||
void saveSyncAuthKeys()
|
||||
}
|
||||
|
||||
// let syncHost: string
|
||||
// export const getSyncHost = async() => {
|
||||
// if (syncHost === undefined) {
|
||||
// const store = getStore(STORE_NAMES.SYNC)
|
||||
// syncHost = (store.get('syncHost') as typeof syncHost | null) ?? ''
|
||||
// }
|
||||
// return syncHost
|
||||
// }
|
||||
// export const setSyncHost = async(host: string) => {
|
||||
// // let hostInfo = await getData(syncHostPrefix) || {}
|
||||
// // hostInfo.host = host
|
||||
// // hostInfo.port = port
|
||||
// syncHost = host
|
||||
// const store = getStore(STORE_NAMES.SYNC)
|
||||
// store.set('syncHost', syncHost)
|
||||
// }
|
||||
// let syncHostHistory: string[]
|
||||
// export const getSyncHostHistory = async() => {
|
||||
// if (syncHostHistory === undefined) {
|
||||
// const store = getStore(STORE_NAMES.SYNC)
|
||||
// syncHostHistory = (store.get('syncHostHistory') as string[]) ?? []
|
||||
// }
|
||||
// return syncHostHistory
|
||||
// }
|
||||
// export const addSyncHostHistory = async(host: string) => {
|
||||
// let syncHostHistory = await getSyncHostHistory()
|
||||
// if (syncHostHistory.some(h => h == host)) return
|
||||
// syncHostHistory.unshift(host)
|
||||
// if (syncHostHistory.length > 20) syncHostHistory = syncHostHistory.slice(0, 20) // 最多存储20个
|
||||
// const store = getStore(STORE_NAMES.SYNC)
|
||||
// store.set('syncHostHistory', syncHostHistory)
|
||||
// }
|
||||
// export const removeSyncHostHistory = async(index: number) => {
|
||||
// syncHostHistory.splice(index, 1)
|
||||
// const store = getStore(STORE_NAMES.SYNC)
|
||||
// store.set('syncHostHistory', syncHostHistory)
|
||||
// }
|
||||
@@ -1,10 +1,9 @@
|
||||
import handleAuth from './auth'
|
||||
import { connect as socketConnect, disconnect as socketDisconnect, sendSyncStatus, sendSyncMessage } from './client'
|
||||
// import { getSyncHost } from '@root/utils/data'
|
||||
// import { getSyncHost } from '@/utils/data'
|
||||
import { SYNC_CODE } from '@common/constants'
|
||||
import log from '../log'
|
||||
import { parseUrl } from './utils'
|
||||
import migrateData from '../migrate'
|
||||
import { SYNC_CODE } from '@common/constants_sync'
|
||||
|
||||
let connectId = 0
|
||||
|
||||
@@ -30,8 +29,6 @@ const connectServer = async(host: string, authCode?: string) => {
|
||||
message: SYNC_CODE.connecting,
|
||||
})
|
||||
const id = connectId
|
||||
await migrateData(global.lxDataPath)
|
||||
|
||||
return handleConnect(host, authCode).catch(async err => {
|
||||
if (id != connectId) return
|
||||
sendSyncStatus({
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
// 这个文件导出的方法将暴露给服务端调用,第一个参数固定为当前 socket 对象
|
||||
import { handleRemoteDislikeAction, getLocalDislikeData, setLocalDislikeData } from '@main/modules/sync/dislikeEvent'
|
||||
import { toMD5 } from '@common/utils/nodejs'
|
||||
import { removeSelectModeListener, sendCloseSelectMode, sendSelectMode } from '@main/modules/winMain'
|
||||
import log from '@main/modules/sync/log'
|
||||
import { registerEvent, unregisterEvent } from './localEvent'
|
||||
|
||||
const logInfo = (eventName: string, success = false) => {
|
||||
log.info(`[${eventName}]${eventName.replace('dislike:sync:dislike_sync_', '').replaceAll('_', ' ')}${success ? ' success' : ''}`)
|
||||
}
|
||||
// const logError = (eventName: string, err: Error) => {
|
||||
// log.error(`[${eventName}]${eventName.replace('dislike:sync:dislike_sync_', '').replaceAll('_', ' ')} error: ${err.message}`)
|
||||
// }
|
||||
const getSyncMode = async(socket: LX.Sync.Client.Socket): Promise<LX.Sync.Dislike.SyncMode> => new Promise((resolve, reject) => {
|
||||
const handleDisconnect = (err: Error) => {
|
||||
sendCloseSelectMode()
|
||||
removeSelectModeListener()
|
||||
reject(err)
|
||||
}
|
||||
let removeEventClose = socket.onClose(handleDisconnect)
|
||||
sendSelectMode(socket.data.keyInfo.serverName, 'dislike', (mode) => {
|
||||
if (mode == null) {
|
||||
reject(new Error('cancel'))
|
||||
return
|
||||
}
|
||||
resolve(mode)
|
||||
removeSelectModeListener()
|
||||
removeEventClose()
|
||||
})
|
||||
})
|
||||
const handler: LX.Sync.ClientSyncHandlerDislikeActions<LX.Sync.Client.Socket> = {
|
||||
async onDislikeSyncAction(socket, action) {
|
||||
if (!socket.moduleReadys?.dislike) return
|
||||
await handleRemoteDislikeAction(action)
|
||||
},
|
||||
|
||||
async dislike_sync_get_md5(socket) {
|
||||
logInfo('dislike:sync:dislike_sync_get_md5')
|
||||
return toMD5((await getLocalDislikeData()).trim())
|
||||
},
|
||||
|
||||
|
||||
async dislike_sync_get_sync_mode(socket) {
|
||||
return getSyncMode(socket)
|
||||
},
|
||||
|
||||
async dislike_sync_get_list_data(socket) {
|
||||
logInfo('dislike:sync:dislike_sync_get_list_data')
|
||||
return getLocalDislikeData()
|
||||
},
|
||||
|
||||
async dislike_sync_set_list_data(socket, data) {
|
||||
logInfo('dislike:sync:dislike_sync_set_list_data')
|
||||
await setLocalDislikeData(data)
|
||||
},
|
||||
|
||||
async dislike_sync_finished(socket) {
|
||||
logInfo('dislike:sync:finished')
|
||||
socket.moduleReadys.dislike = true
|
||||
registerEvent(socket)
|
||||
socket.onClose(() => {
|
||||
unregisterEvent()
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
export default handler
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
|
||||
export { default as handler } from './handler'
|
||||
|
||||
export * from './localEvent'
|
||||
@@ -1,27 +0,0 @@
|
||||
import { SYNC_CLOSE_CODE } from '@common/constants_sync'
|
||||
import { registerDislikeActionEvent } from '@main/modules/sync/dislikeEvent'
|
||||
|
||||
let unregisterLocalListAction: (() => void) | null
|
||||
|
||||
export const registerEvent = (socket: LX.Sync.Client.Socket) => {
|
||||
// socket = _socket
|
||||
// socket.onClose(() => {
|
||||
// unregisterLocalListAction?.()
|
||||
// unregisterLocalListAction = null
|
||||
// })
|
||||
unregisterEvent()
|
||||
unregisterLocalListAction = registerDislikeActionEvent((action) => {
|
||||
if (!socket.moduleReadys?.dislike) return
|
||||
void socket.remoteQueueDislike.onDislikeSyncAction(action).catch(err => {
|
||||
// TODO send status
|
||||
socket.moduleReadys.dislike = false
|
||||
socket.close(SYNC_CLOSE_CODE.failed)
|
||||
console.log(err.message)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const unregisterEvent = () => {
|
||||
unregisterLocalListAction?.()
|
||||
unregisterLocalListAction = null
|
||||
}
|
||||
@@ -1,20 +1 @@
|
||||
import * as list from './list'
|
||||
import * as dislike from './dislike'
|
||||
// export * as theme from './theme'
|
||||
|
||||
|
||||
export const callObj = Object.assign({},
|
||||
list.handler,
|
||||
dislike.handler,
|
||||
)
|
||||
|
||||
|
||||
export const modules = {
|
||||
list,
|
||||
dislike,
|
||||
}
|
||||
|
||||
export const featureVersion = {
|
||||
list: 1,
|
||||
dislike: 1,
|
||||
} as const
|
||||
export { default as list } from './list'
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
// 这个文件导出的方法将暴露给服务端调用,第一个参数固定为当前 socket 对象
|
||||
import {
|
||||
handleRemoteListAction,
|
||||
getLocalListData,
|
||||
setLocalListData,
|
||||
} from '@main/modules/sync/listEvent'
|
||||
import { toMD5 } from '@common/utils/nodejs'
|
||||
import { removeSelectModeListener, sendCloseSelectMode, sendSelectMode } from '@main/modules/winMain'
|
||||
import log from '@main/modules/sync/log'
|
||||
import { registerEvent, unregisterEvent } from './localEvent'
|
||||
|
||||
const logInfo = (eventName: string, success = false) => {
|
||||
log.info(`[${eventName}]${eventName.replace('list:sync:list_sync_', '').replaceAll('_', ' ')}${success ? ' success' : ''}`)
|
||||
}
|
||||
// const logError = (eventName: string, err: Error) => {
|
||||
// log.error(`[${eventName}]${eventName.replace('list:sync:list_sync_', '').replaceAll('_', ' ')} error: ${err.message}`)
|
||||
// }
|
||||
const getSyncMode = async(socket: LX.Sync.Client.Socket): Promise<LX.Sync.List.SyncMode> => new Promise((resolve, reject) => {
|
||||
const handleDisconnect = (err: Error) => {
|
||||
sendCloseSelectMode()
|
||||
removeSelectModeListener()
|
||||
reject(err)
|
||||
}
|
||||
let removeEventClose = socket.onClose(handleDisconnect)
|
||||
sendSelectMode(socket.data.keyInfo.serverName, 'list', (mode) => {
|
||||
if (mode == null) {
|
||||
reject(new Error('cancel'))
|
||||
return
|
||||
}
|
||||
resolve(mode)
|
||||
removeSelectModeListener()
|
||||
removeEventClose()
|
||||
})
|
||||
})
|
||||
|
||||
const handler: LX.Sync.ClientSyncHandlerListActions<LX.Sync.Client.Socket> = {
|
||||
async onListSyncAction(socket, action) {
|
||||
if (!socket.moduleReadys?.list) return
|
||||
await handleRemoteListAction(action)
|
||||
},
|
||||
|
||||
async list_sync_get_md5(socket) {
|
||||
logInfo('list:sync:list_sync_get_md5')
|
||||
return toMD5(JSON.stringify(await getLocalListData()))
|
||||
},
|
||||
|
||||
|
||||
async list_sync_get_sync_mode(socket) {
|
||||
return getSyncMode(socket)
|
||||
},
|
||||
|
||||
async list_sync_get_list_data(socket) {
|
||||
logInfo('list:sync:list_sync_get_list_data')
|
||||
return getLocalListData()
|
||||
},
|
||||
|
||||
async list_sync_set_list_data(socket, data) {
|
||||
logInfo('list:sync:list_sync_set_list_data')
|
||||
await setLocalListData(data)
|
||||
},
|
||||
|
||||
async list_sync_finished(socket) {
|
||||
logInfo('list:sync:finished')
|
||||
socket.moduleReadys.list = true
|
||||
registerEvent(socket)
|
||||
socket.onClose(() => {
|
||||
unregisterEvent()
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
export default handler
|
||||
@@ -1,4 +1,7 @@
|
||||
import initOn from './on'
|
||||
import initSend from './send'
|
||||
|
||||
export { default as handler } from './handler'
|
||||
|
||||
export * from './localEvent'
|
||||
export default (socket: LX.Sync.Client.Socket) => {
|
||||
initOn(socket)
|
||||
initSend(socket)
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import { SYNC_CLOSE_CODE } from '@common/constants_sync'
|
||||
import { registerListActionEvent } from '@main/modules/sync/listEvent'
|
||||
|
||||
let unregisterLocalListAction: (() => void) | null
|
||||
|
||||
export const registerEvent = (socket: LX.Sync.Client.Socket) => {
|
||||
// socket = _socket
|
||||
// socket.onClose(() => {
|
||||
// unregisterLocalListAction?.()
|
||||
// unregisterLocalListAction = null
|
||||
// })
|
||||
unregisterEvent()
|
||||
unregisterLocalListAction = registerListActionEvent((action) => {
|
||||
if (!socket.moduleReadys?.list) return
|
||||
void socket.remoteQueueList.onListSyncAction(action).catch(err => {
|
||||
// TODO send status
|
||||
socket.moduleReadys.list = false
|
||||
socket.close(SYNC_CLOSE_CODE.failed)
|
||||
console.log(err.message)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const unregisterEvent = () => {
|
||||
unregisterLocalListAction?.()
|
||||
unregisterLocalListAction = null
|
||||
}
|
||||
9
src/main/modules/sync/client/modules/list/on.ts
Normal file
9
src/main/modules/sync/client/modules/list/on.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { handleRemoteListAction } from '@main/modules/sync/utils'
|
||||
|
||||
export default (socket: LX.Sync.Client.Socket) => {
|
||||
socket.onRemoteEvent('list:sync:action', (action) => {
|
||||
if (!socket.isReady) return
|
||||
void handleRemoteListAction(action)
|
||||
})
|
||||
}
|
||||
|
||||
21
src/main/modules/sync/client/modules/list/send.ts
Normal file
21
src/main/modules/sync/client/modules/list/send.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { registerListActionEvent } from '@main/modules/sync/utils'
|
||||
|
||||
let socket: LX.Sync.Client.Socket | null
|
||||
let unregisterLocalListAction: (() => void) | null
|
||||
|
||||
|
||||
const sendListAction = (action: LX.Sync.ActionList) => {
|
||||
// console.log('sendListAction', action.action)
|
||||
if (!socket?.isReady) return
|
||||
socket.sendData('list:sync:action', action)
|
||||
}
|
||||
|
||||
export default (_socket: LX.Sync.Client.Socket) => {
|
||||
socket = _socket
|
||||
socket.onClose(() => {
|
||||
socket = null
|
||||
unregisterLocalListAction?.()
|
||||
unregisterLocalListAction = null
|
||||
})
|
||||
unregisterLocalListAction = registerListActionEvent(sendListAction)
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
// 这个文件导出的方法将暴露给服务端调用,第一个参数固定为当前 socket 对象
|
||||
|
||||
// import { getUserSpace } from '@/user'
|
||||
// import { modules } from '../modules'
|
||||
|
||||
import { featureVersion } from '../modules'
|
||||
|
||||
const handler: Omit<LX.Sync.ClientSyncHandlerActions<LX.Sync.Client.Socket>, 'finished'> = {
|
||||
async getEnabledFeatures(socket, serverType, supportedFeatures) {
|
||||
// const userSpace = getUserSpace(socket.userInfo.name)
|
||||
const features: LX.Sync.EnabledFeatures = {}
|
||||
switch (serverType) {
|
||||
case 'server':
|
||||
if (featureVersion.list == supportedFeatures.list) {
|
||||
features.list = { skipSnapshot: false }
|
||||
}
|
||||
if (featureVersion.dislike == supportedFeatures.dislike) {
|
||||
features.dislike = { skipSnapshot: false }
|
||||
}
|
||||
return features
|
||||
case 'desktop-app':
|
||||
default:
|
||||
if (featureVersion.list == supportedFeatures.list) {
|
||||
features.list = { skipSnapshot: false }
|
||||
}
|
||||
if (featureVersion.dislike == supportedFeatures.dislike) {
|
||||
features.dislike = { skipSnapshot: false }
|
||||
}
|
||||
return features
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default handler
|
||||
@@ -1,8 +0,0 @@
|
||||
import handler from './handler'
|
||||
import { callObj as _callObj } from '../modules'
|
||||
export { modules } from '../modules'
|
||||
|
||||
export const callObj = {
|
||||
...handler,
|
||||
..._callObj,
|
||||
}
|
||||
73
src/main/modules/sync/client/syncList.ts
Normal file
73
src/main/modules/sync/client/syncList.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import log from '../log'
|
||||
import { getLocalListData, setLocalListData } from '../utils'
|
||||
import { toMD5 } from '@common/utils/nodejs'
|
||||
import { removeSelectModeListener, sendCloseSelectMode, sendSelectMode } from '@main/modules/winMain'
|
||||
import { SYNC_CLOSE_CODE } from '@common/constants'
|
||||
|
||||
const logInfo = (eventName: keyof LX.Sync.ActionSyncSendType, success = false) => {
|
||||
log.info(`[${eventName as string}]${eventName.replace('list:sync:list_sync_', '').replaceAll('_', ' ')}${success ? ' success' : ''}`)
|
||||
}
|
||||
const logError = (eventName: keyof LX.Sync.ActionSyncSendType, err: Error) => {
|
||||
log.error(`[${eventName as string}]${eventName.replace('list:sync:list_sync_', '').replaceAll('_', ' ')} error: ${err.message}`)
|
||||
}
|
||||
|
||||
export default async(socket: LX.Sync.Client.Socket) => new Promise<void>((resolve, reject) => {
|
||||
let listenEvents: Array<() => void> = []
|
||||
const unregisterEvents = () => {
|
||||
while (listenEvents.length) listenEvents.shift()?.()
|
||||
}
|
||||
|
||||
socket.onClose(() => {
|
||||
unregisterEvents()
|
||||
sendCloseSelectMode()
|
||||
removeSelectModeListener()
|
||||
reject(new Error('closed'))
|
||||
})
|
||||
listenEvents.push(socket.onRemoteEvent('list:sync:list_sync_get_md5', async() => {
|
||||
logInfo('list:sync:list_sync_get_md5')
|
||||
const md5 = toMD5(JSON.stringify(await getLocalListData()))
|
||||
socket?.sendData('list:sync:list_sync_get_md5', md5, (err) => {
|
||||
if (err) {
|
||||
logError('list:sync:list_sync_get_md5', err)
|
||||
socket.close(SYNC_CLOSE_CODE.failed)
|
||||
return
|
||||
}
|
||||
logInfo('list:sync:list_sync_get_md5', true)
|
||||
})
|
||||
}))
|
||||
listenEvents.push(socket.onRemoteEvent('list:sync:list_sync_get_list_data', async() => {
|
||||
logInfo('list:sync:list_sync_get_list_data')
|
||||
socket?.sendData('list:sync:list_sync_get_list_data', await getLocalListData(), (err) => {
|
||||
if (err) {
|
||||
logError('list:sync:list_sync_get_list_data', err)
|
||||
socket.close(SYNC_CLOSE_CODE.failed)
|
||||
return
|
||||
}
|
||||
logInfo('list:sync:list_sync_get_list_data', true)
|
||||
})
|
||||
}))
|
||||
listenEvents.push(socket.onRemoteEvent('list:sync:list_sync_get_sync_mode', async() => {
|
||||
logInfo('list:sync:list_sync_get_sync_mode')
|
||||
sendSelectMode(socket.data.keyInfo.serverName, (mode) => {
|
||||
removeSelectModeListener()
|
||||
socket?.sendData('list:sync:list_sync_get_sync_mode', mode, (err) => {
|
||||
if (err) {
|
||||
logError('list:sync:list_sync_get_sync_mode', err)
|
||||
socket.close(SYNC_CLOSE_CODE.failed)
|
||||
return
|
||||
}
|
||||
logInfo('list:sync:list_sync_get_sync_mode', true)
|
||||
})
|
||||
})
|
||||
}))
|
||||
listenEvents.push(socket.onRemoteEvent('list:sync:list_sync_set_data', async(data) => {
|
||||
logInfo('list:sync:list_sync_set_data')
|
||||
await setLocalListData(data)
|
||||
logInfo('list:sync:list_sync_set_data', true)
|
||||
}))
|
||||
listenEvents.push(socket.onRemoteEvent('list:sync:finished', async() => {
|
||||
unregisterEvents()
|
||||
resolve()
|
||||
logInfo('list:sync:finished', true)
|
||||
}))
|
||||
})
|
||||
@@ -1,6 +1,5 @@
|
||||
import { generateKeyPair } from 'node:crypto'
|
||||
import { httpFetch, type RequestOptions } from '@main/utils/request'
|
||||
import { decodeData, encodeData } from '../utils'
|
||||
|
||||
export const request = async(url: string, options: RequestOptions = { }) => {
|
||||
return httpFetch(url, {
|
||||
@@ -75,14 +74,15 @@ export const generateRsaKey = async() => new Promise<{ publicKey: string, privat
|
||||
)
|
||||
})
|
||||
|
||||
export const encryptMsg = async(keyInfo: LX.Sync.ClientKeyInfo, msg: string): Promise<string> => {
|
||||
return encodeData(msg)
|
||||
|
||||
export const encryptMsg = (keyInfo: LX.Sync.ClientKeyInfo, msg: string): string => {
|
||||
return msg
|
||||
// if (!keyInfo) return ''
|
||||
// return aesEncrypt(msg, keyInfo.key, keyInfo.iv)
|
||||
}
|
||||
|
||||
export const decryptMsg = async(keyInfo: LX.Sync.ClientKeyInfo, enMsg: string): Promise<string> => {
|
||||
return decodeData(enMsg)
|
||||
export const decryptMsg = (keyInfo: LX.Sync.ClientKeyInfo, enMsg: string): string => {
|
||||
return enMsg
|
||||
// if (!keyInfo) return ''
|
||||
// let msg = ''
|
||||
// try {
|
||||
|
||||
185
src/main/modules/sync/data.ts
Normal file
185
src/main/modules/sync/data.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
import { randomBytes } from 'node:crypto'
|
||||
import { STORE_NAMES } from '@common/constants'
|
||||
import getStore from '@main/utils/store'
|
||||
import { throttle } from '@common/utils/common'
|
||||
import path from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
import log from './log'
|
||||
|
||||
|
||||
export const getSyncAuthKey = async(serverId: string) => {
|
||||
const store = getStore(STORE_NAMES.SYNC)
|
||||
const keys = store.get('syncAuthKey') as Record<string, LX.Sync.ClientKeyInfo> | null
|
||||
if (!keys) return null
|
||||
return keys[serverId] ?? null
|
||||
}
|
||||
export const setSyncAuthKey = async(serverId: string, info: LX.Sync.ClientKeyInfo) => {
|
||||
const store = getStore(STORE_NAMES.SYNC)
|
||||
let keys: Record<string, LX.Sync.ClientKeyInfo> = (store.get('syncAuthKey') as Record<string, LX.Sync.ClientKeyInfo> | null) ?? {}
|
||||
keys[serverId] = info
|
||||
store.set('syncAuthKey', keys)
|
||||
}
|
||||
|
||||
let syncHost: string
|
||||
export const getSyncHost = async() => {
|
||||
if (syncHost === undefined) {
|
||||
const store = getStore(STORE_NAMES.SYNC)
|
||||
syncHost = (store.get('syncHost') as typeof syncHost | null) ?? ''
|
||||
}
|
||||
return syncHost
|
||||
}
|
||||
export const setSyncHost = async(host: string) => {
|
||||
// let hostInfo = await getData(syncHostPrefix) || {}
|
||||
// hostInfo.host = host
|
||||
// hostInfo.port = port
|
||||
syncHost = host
|
||||
const store = getStore(STORE_NAMES.SYNC)
|
||||
store.set('syncHost', syncHost)
|
||||
}
|
||||
let syncHostHistory: string[]
|
||||
export const getSyncHostHistory = async() => {
|
||||
if (syncHostHistory === undefined) {
|
||||
const store = getStore(STORE_NAMES.SYNC)
|
||||
syncHostHistory = (store.get('syncHostHistory') as string[]) ?? []
|
||||
}
|
||||
return syncHostHistory
|
||||
}
|
||||
export const addSyncHostHistory = async(host: string) => {
|
||||
let syncHostHistory = await getSyncHostHistory()
|
||||
if (syncHostHistory.some(h => h == host)) return
|
||||
syncHostHistory.unshift(host)
|
||||
if (syncHostHistory.length > 20) syncHostHistory = syncHostHistory.slice(0, 20) // 最多存储20个
|
||||
const store = getStore(STORE_NAMES.SYNC)
|
||||
store.set('syncHostHistory', syncHostHistory)
|
||||
}
|
||||
export const removeSyncHostHistory = async(index: number) => {
|
||||
syncHostHistory.splice(index, 1)
|
||||
const store = getStore(STORE_NAMES.SYNC)
|
||||
store.set('syncHostHistory', syncHostHistory)
|
||||
}
|
||||
|
||||
export interface SnapshotInfo {
|
||||
latest: string | null
|
||||
time: number
|
||||
list: string[]
|
||||
}
|
||||
interface DevicesInfo {
|
||||
serverId: string
|
||||
clients: Record<string, LX.Sync.ServerKeyInfo>
|
||||
snapshotInfo: SnapshotInfo
|
||||
}
|
||||
// const devicesFilePath = path.join(global.lx.dataPath, 'devices.json')
|
||||
const devicesInfo: DevicesInfo = { serverId: '', clients: {}, snapshotInfo: { latest: null, time: 0, list: [] } }
|
||||
let deviceKeys: string[] = []
|
||||
const saveDevicesInfoThrottle = throttle(() => {
|
||||
const store = getStore(STORE_NAMES.SYNC)
|
||||
store.set('clients', devicesInfo.clients)
|
||||
})
|
||||
|
||||
const initDeviceInfo = () => {
|
||||
const store = getStore(STORE_NAMES.SYNC)
|
||||
const serverId = store.get('serverId') as string | undefined
|
||||
if (serverId) devicesInfo.serverId = serverId
|
||||
else {
|
||||
devicesInfo.serverId = randomBytes(4 * 4).toString('base64')
|
||||
const store = getStore(STORE_NAMES.SYNC)
|
||||
store.set('serverId', devicesInfo.serverId)
|
||||
}
|
||||
const devices = store.get('clients') as DevicesInfo['clients'] | undefined
|
||||
if (devices) devicesInfo.clients = devices
|
||||
deviceKeys = Object.values(devicesInfo.clients).map(device => device.snapshotKey).filter(k => k)
|
||||
const snapshotInfo = store.get('snapshotInfo') as DevicesInfo['snapshotInfo'] | undefined
|
||||
if (snapshotInfo) devicesInfo.snapshotInfo = snapshotInfo
|
||||
}
|
||||
|
||||
export const createClientKeyInfo = (deviceName: string, isMobile: boolean): LX.Sync.ServerKeyInfo => {
|
||||
const keyInfo: LX.Sync.ServerKeyInfo = {
|
||||
clientId: randomBytes(4 * 4).toString('base64'),
|
||||
key: randomBytes(16).toString('base64'),
|
||||
deviceName,
|
||||
isMobile,
|
||||
snapshotKey: '',
|
||||
lastSyncDate: 0,
|
||||
}
|
||||
saveClientKeyInfo(keyInfo)
|
||||
return keyInfo
|
||||
}
|
||||
export const saveClientKeyInfo = (keyInfo: LX.Sync.ServerKeyInfo) => {
|
||||
if (devicesInfo.clients[keyInfo.clientId] == null && Object.keys(devicesInfo.clients).length > 101) throw new Error('max keys')
|
||||
devicesInfo.clients[keyInfo.clientId] = keyInfo
|
||||
saveDevicesInfoThrottle()
|
||||
}
|
||||
export const getClientKeyInfo = (clientId?: string): LX.Sync.ServerKeyInfo | null => {
|
||||
if (!clientId) return null
|
||||
if (!devicesInfo.serverId) initDeviceInfo()
|
||||
return devicesInfo.clients[clientId] ?? null
|
||||
}
|
||||
export const getServerId = (): string => {
|
||||
if (!devicesInfo.serverId) initDeviceInfo()
|
||||
return devicesInfo.serverId
|
||||
}
|
||||
export const isIncluedsDevice = (name: string) => {
|
||||
return deviceKeys.includes(name)
|
||||
}
|
||||
export const clearOldSnapshot = async() => {
|
||||
if (!devicesInfo.snapshotInfo) return
|
||||
const snapshotList = devicesInfo.snapshotInfo.list.filter(name => !isIncluedsDevice(name))
|
||||
let requiredSave = snapshotList.length > global.lx.appSetting['sync.server.maxSsnapshotNum']
|
||||
while (snapshotList.length > global.lx.appSetting['sync.server.maxSsnapshotNum']) {
|
||||
const name = snapshotList.pop()
|
||||
if (name) {
|
||||
await removeSnapshot(name)
|
||||
devicesInfo.snapshotInfo.list.splice(devicesInfo.snapshotInfo.list.indexOf(name), 1)
|
||||
} else break
|
||||
}
|
||||
if (requiredSave) saveSnapshotInfo(devicesInfo.snapshotInfo)
|
||||
}
|
||||
export const updateDeviceSnapshotKey = (keyInfo: LX.Sync.ServerKeyInfo, key: string) => {
|
||||
if (keyInfo.snapshotKey) deviceKeys.splice(deviceKeys.indexOf(keyInfo.snapshotKey), 1)
|
||||
keyInfo.snapshotKey = key
|
||||
keyInfo.lastSyncDate = Date.now()
|
||||
saveClientKeyInfo(keyInfo)
|
||||
deviceKeys.push(key)
|
||||
saveDevicesInfoThrottle()
|
||||
void clearOldSnapshot()
|
||||
}
|
||||
|
||||
const saveSnapshotInfoThrottle = throttle(() => {
|
||||
const store = getStore(STORE_NAMES.SYNC)
|
||||
store.set('snapshotInfo', devicesInfo.snapshotInfo)
|
||||
})
|
||||
export const getSnapshotInfo = (): SnapshotInfo => {
|
||||
return devicesInfo.snapshotInfo
|
||||
}
|
||||
export const saveSnapshotInfo = (info: SnapshotInfo) => {
|
||||
devicesInfo.snapshotInfo = info
|
||||
saveSnapshotInfoThrottle()
|
||||
}
|
||||
|
||||
export const getSnapshot = async(name: string) => {
|
||||
console.log('getSnapshot', name)
|
||||
const filePath = path.join(global.lxDataPath, `snapshot_${name}`)
|
||||
let listData: LX.Sync.ListData
|
||||
try {
|
||||
listData = JSON.parse((await fs.promises.readFile(filePath)).toString('utf-8'))
|
||||
} catch (err) {
|
||||
log.warn(err)
|
||||
return null
|
||||
}
|
||||
return listData
|
||||
}
|
||||
export const saveSnapshot = async(name: string, data: string) => {
|
||||
console.log('saveSnapshot', name)
|
||||
const filePath = path.join(global.lxDataPath, `snapshot_${name}`)
|
||||
return fs.promises.writeFile(filePath, data).catch((err) => {
|
||||
log.error(err)
|
||||
throw err
|
||||
})
|
||||
}
|
||||
export const removeSnapshot = async(name: string) => {
|
||||
console.log('removeSnapshot', name)
|
||||
const filePath = path.join(global.lxDataPath, `snapshot_${name}`)
|
||||
return fs.promises.unlink(filePath).catch((err) => {
|
||||
log.error(err)
|
||||
})
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
|
||||
export const getLocalDislikeData = async(): Promise<LX.Dislike.DislikeRules> => {
|
||||
return (await global.lx.worker.dbService.getDislikeListInfo()).rules
|
||||
}
|
||||
|
||||
export const setLocalDislikeData = async(listData: LX.Dislike.DislikeRules) => {
|
||||
await global.lx.event_dislike.dislike_data_overwrite(listData, true)
|
||||
}
|
||||
|
||||
export const registerDislikeActionEvent = (sendDislikeAction: (action: LX.Sync.Dislike.ActionList) => (void | Promise<void>)) => {
|
||||
const dislike_music_add = async(listData: LX.Dislike.DislikeMusicInfo[], isRemote: boolean = false) => {
|
||||
if (isRemote) return
|
||||
await sendDislikeAction({ action: 'dislike_music_add', data: listData })
|
||||
}
|
||||
const dislike_data_overwrite = async(listInfos: LX.Dislike.DislikeRules, isRemote: boolean = false) => {
|
||||
if (isRemote) return
|
||||
await sendDislikeAction({ action: 'dislike_data_overwrite', data: listInfos })
|
||||
}
|
||||
const dislike_music_clear = async(isRemote: boolean = false) => {
|
||||
if (isRemote) return
|
||||
await sendDislikeAction({ action: 'dislike_music_clear' })
|
||||
}
|
||||
|
||||
global.lx.event_dislike.on('dislike_music_add', dislike_music_add)
|
||||
global.lx.event_dislike.on('dislike_data_overwrite', dislike_data_overwrite)
|
||||
global.lx.event_dislike.on('dislike_music_clear', dislike_music_clear)
|
||||
return () => {
|
||||
global.lx.event_dislike.off('dislike_music_add', dislike_music_add)
|
||||
global.lx.event_dislike.off('dislike_data_overwrite', dislike_data_overwrite)
|
||||
global.lx.event_dislike.off('dislike_music_clear', dislike_music_clear)
|
||||
}
|
||||
}
|
||||
|
||||
export const handleRemoteDislikeAction = async(event: LX.Sync.Dislike.ActionList) => {
|
||||
// console.log('handleRemoteDislikeAction', event)
|
||||
|
||||
switch (event.action) {
|
||||
case 'dislike_music_add':
|
||||
await global.lx.event_dislike.dislike_music_add(event.data, true)
|
||||
break
|
||||
case 'dislike_data_overwrite':
|
||||
await global.lx.event_dislike.dislike_data_overwrite(event.data, true)
|
||||
break
|
||||
case 'dislike_music_clear':
|
||||
await global.lx.event_dislike.dislike_music_clear(true)
|
||||
break
|
||||
default:
|
||||
throw new Error('unknown list sync action')
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user