mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/lyswhut/lx-music-desktop.git
synced 2026-09-20 08:04:00 +08:00
Compare commits
117 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b726ee42f7 | ||
|
|
6b71b4709e | ||
|
|
0701b78720 | ||
|
|
82d1ac1e83 | ||
|
|
8acf98631f | ||
|
|
0a31d19561 | ||
|
|
91279922f1 | ||
|
|
96804ae2e2 | ||
|
|
3ae8a89961 | ||
|
|
f8f13fd32c | ||
|
|
ae61d2c629 | ||
|
|
b1918e8b15 | ||
|
|
ce7bca5b36 | ||
|
|
51be62bc29 | ||
|
|
c1ff3971de | ||
|
|
57db286405 | ||
|
|
592b670e96 | ||
|
|
97fdcaac51 | ||
|
|
271d9ac13e | ||
|
|
b6cea110a7 | ||
|
|
4b5dae4779 | ||
|
|
a250317a28 | ||
|
|
a4d3c06119 | ||
|
|
246412cb2c | ||
|
|
2fb013fb41 | ||
|
|
6f9d684b2d | ||
|
|
af6a2ec581 | ||
|
|
488b28921f | ||
|
|
4c23fc1011 | ||
|
|
db1c903f1b | ||
|
|
3fdf7411fc | ||
|
|
1da275ff3a | ||
|
|
bc5167f38d | ||
|
|
609b6f3123 | ||
|
|
fe35081e03 | ||
|
|
72df1a35a0 | ||
|
|
90ffcf5071 | ||
|
|
35f60cc536 | ||
|
|
31bb40c50b | ||
|
|
bc7b35f816 | ||
|
|
0c76612a4c | ||
|
|
ab86674aa3 | ||
|
|
8cd5a01dd5 | ||
|
|
4ef5bafcca | ||
|
|
2c783e0657 | ||
|
|
57a8b7842e | ||
|
|
4b5a7b6a74 | ||
|
|
c09c31767d | ||
|
|
ecff04618b | ||
|
|
c377469dad | ||
|
|
ecb41382cb | ||
|
|
3150b12b99 | ||
|
|
364260b070 | ||
|
|
5ccf6f077d | ||
|
|
5b113c5c22 | ||
|
|
ff53900ea3 | ||
|
|
b0b0403d9a | ||
|
|
21c3b3ab57 | ||
|
|
e85709a341 | ||
|
|
04be42a4d6 | ||
|
|
8196e8a84d | ||
|
|
1f85492690 | ||
|
|
e15178150b | ||
|
|
d83aee2507 | ||
|
|
354a9904e9 | ||
|
|
fe3379ffbb | ||
|
|
0164936038 | ||
|
|
9106d6455f | ||
|
|
5ddea2b1da | ||
|
|
2549c5587e | ||
|
|
c9f28d8105 | ||
|
|
75b2db4749 | ||
|
|
b6f291d721 | ||
|
|
4f7e227392 | ||
|
|
7d95eb34c7 | ||
|
|
7e3ee38920 | ||
|
|
d44c2c26a8 | ||
|
|
d77a41c20f | ||
|
|
c50c747691 | ||
|
|
bcf3dde60d | ||
|
|
33ec3de1b4 | ||
|
|
26be5b5c4f | ||
|
|
56af57c682 | ||
|
|
3a5cb3f7c2 | ||
|
|
724c6be85b | ||
|
|
1c4a56f95b | ||
|
|
9c21e96538 | ||
|
|
67074bd682 | ||
|
|
b535987392 | ||
|
|
d5c0c2e3d3 | ||
|
|
7580c79c00 | ||
|
|
c0a13622b0 | ||
|
|
58d7326f77 | ||
|
|
45d7a12d0d | ||
|
|
40b4f15340 | ||
|
|
9c28ff67c1 | ||
|
|
e3f146dabf | ||
|
|
2b2a6a7f10 | ||
|
|
192574dfe8 | ||
|
|
8e95e06b2d | ||
|
|
1963b3ee71 | ||
|
|
507f66b3b6 | ||
|
|
94bf9eae8b | ||
|
|
0fa32f976c | ||
|
|
198238d4b5 | ||
|
|
66631ac105 | ||
|
|
19c5996351 | ||
|
|
cd3140a9c7 | ||
|
|
56201d6a01 | ||
|
|
654cb41e3e | ||
|
|
fa744d9204 | ||
|
|
3e1c56ff82 | ||
|
|
5d5164c4e9 | ||
|
|
0791e760a4 | ||
|
|
b53fe2dd5e | ||
|
|
1648b0c2dd | ||
|
|
1a5adfabf4 |
@@ -16,7 +16,9 @@
|
||||
"no-multiple-empty-lines": [1, {"max": 2}],
|
||||
"comma-dangle": [2, "always-multiline"],
|
||||
"standard/no-callback-literal": "off",
|
||||
"prefer-const": "off"
|
||||
"prefer-const": "off",
|
||||
"no-labels": "off",
|
||||
"node/no-callback-literal": "off"
|
||||
},
|
||||
"settings": {
|
||||
"html/html-extensions": [".html", ".vue"]
|
||||
|
||||
12
.github/workflows/beta-pack.yml
vendored
12
.github/workflows/beta-pack.yml
vendored
@@ -52,6 +52,14 @@ jobs:
|
||||
name: lx-music-desktop-x86-Setup
|
||||
path: build/* x86 Setup.exe
|
||||
|
||||
- name: Build Package Setup arm64
|
||||
run: npm run pack:win:setup:arm64
|
||||
- name: Upload Artifact Setup arm64
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: lx-music-desktop-arm64-Setup
|
||||
path: build/* arm64 Setup.exe
|
||||
|
||||
- name: Build Package Setup x86_64
|
||||
run: npm run pack:win:setup:x86_64
|
||||
- name: Upload Artifact Setup x86_64
|
||||
@@ -116,8 +124,8 @@ jobs:
|
||||
|
||||
- name: Build Package dmg
|
||||
run: |
|
||||
npm run publish:mac:dmg
|
||||
npm run publish:mac:dmg:arm64
|
||||
npm run pack:mac:dmg
|
||||
npm run pack:mac:dmg:arm64
|
||||
env:
|
||||
ELECTRON_CACHE: $HOME/.cache/electron
|
||||
ELECTRON_BUILDERCACHE: $HOME/.cache/electron-builder
|
||||
|
||||
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
@@ -40,6 +40,7 @@ jobs:
|
||||
run: |
|
||||
npm run publish:win:setup:x64:always
|
||||
npm run publish:win:setup:x86
|
||||
npm run publish:win:setup:arm64
|
||||
npm run publish:win:setup:x86_64
|
||||
npm run publish:win:7z:x64
|
||||
npm run publish:win:7z:x86
|
||||
|
||||
8
.ncurc.js
Normal file
8
.ncurc.js
Normal file
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
upgrade: true,
|
||||
reject: [
|
||||
'vue-loader',
|
||||
'webpack-dev-server',
|
||||
// 'eslint-config-standard'
|
||||
]
|
||||
}
|
||||
12
.vscode/settings.json
vendored
12
.vscode/settings.json
vendored
@@ -5,5 +5,15 @@
|
||||
"@lyric/*": "${workspaceFolder}/src/renderer-lyric/*",
|
||||
"@static/*": "${workspaceFolder}/src/static/*",
|
||||
"@common/*": "${workspaceFolder}/src/common/*",
|
||||
}
|
||||
},
|
||||
"i18n-ally.localesPaths": [
|
||||
"src/renderer/lang"
|
||||
],
|
||||
"i18n-ally.displayLanguage": "zh-cn",
|
||||
"i18n-ally.sourceLanguage": "zh-cn",
|
||||
"i18n-ally.namespace": true,
|
||||
"i18n-ally.translate.engines": ["google-cn", "google"],
|
||||
"i18n-ally.pathMatcher": "{locale}/{namespaces}.json",
|
||||
"i18n-ally.keystyle": "flat",
|
||||
"i18n-ally.sortKeys": true
|
||||
}
|
||||
|
||||
98
CHANGELOG.md
98
CHANGELOG.md
@@ -6,6 +6,104 @@ 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/).
|
||||
|
||||
## [1.14.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.13.0...v1.14.0) - 2021-10-02
|
||||
|
||||
### 新增
|
||||
|
||||
- 新增歌词简体中文转繁体中文,当软件语言被设置为繁体中文后,播放歌曲的歌词也将自动转成繁体中文显示
|
||||
- 新增单个列表导入/导出功能,可以方便分享歌曲列表,可在右击“我的列表”里的列表名后弹出的菜单中使用
|
||||
- 新增删除列表前的确认弹窗,防止误删列表
|
||||
- 新增歌词文本选择复制功能,可在详情页进度条上方的歌词文本选择按钮进入歌词文本选择模式,选择完成后可鼠标右击或者使用系统快捷键复制
|
||||
- 新增重复歌曲列表,可以方便移除我的列表中的重复歌曲,此列表会列出目标列表里歌曲名相同的歌曲,可在右击“我的列表”里的列表名后弹出的菜单中使用
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复mg排行榜无法加载的问题
|
||||
- 修复点击播放详情页的进度条跳进度时会出现偏移的问题
|
||||
- 修复在有提示信息的地方长按鼠标按键时提示信息会闪烁的问题
|
||||
- 修复下载歌曲时的歌词下载不尝试获取缓存歌词的问题
|
||||
- 修复GNOME等桌面下每次打开应用时需重新设置歌词窗口置顶的问题
|
||||
|
||||
## [1.13.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.12.2...v1.13.0) - 2021-09-05
|
||||
|
||||
如果你喜欢并经常使用洛雪音乐,并想要第一时间尝鲜洛雪的新功能,可以加入测试企鹅群768786588,
|
||||
注意:测试版的功可能会不稳定,打算潜水的勿加。
|
||||
|
||||
### 新增
|
||||
|
||||
- 歌曲搜索框新增清理按钮,点击此按钮可以清理搜索框并返回初始搜索界面
|
||||
- 新增“下载的歌词文件编码格式”设置,默认下载的歌词编码仍是`UTF-8`,对于某些在设备(如车机)上出现歌词中文乱码的用户可以尝试选择以`GBK`编码格式保存歌词文件
|
||||
- 新增设置-桌面歌词-歌词字体设置,此设置可用于设置桌面歌词的字体(已知的问题:Windows 7 下可能会出现字体列表为空的情况,这是当前系统的 Powershell 版本小于5.1导致的,请自行**尝试**看常见解决)
|
||||
|
||||
### 优化
|
||||
|
||||
- 支持网易源“我喜欢”歌单以注入token的方式打开。由于网易源的“我喜欢”歌单需要登录才能打开(若你看不懂后半句就去阅读 常见问题-无法打开外部歌单),现若想要打开此类歌单,需要在歌单链接后面拼上 `###` 再加上有效的token,拼接格式:`[id|url]###token`,例子(最后面的xxxxxx替换成你的token):`https://music.163.com/#/playlist?id=123456&userid=123456###xxxxxx`
|
||||
- 软件内快捷键的最小化触发时,如果已启用托盘,则隐藏程序,否则最小化程序
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复某些情况下同步功能会导致切歌混乱的问题
|
||||
- 修复从电脑浏览器复制的企鹅歌单链接无法打开的问题
|
||||
|
||||
## [1.12.2](https://github.com/lyswhut/lx-music-desktop/compare/v1.12.1...v1.12.2) - 2021-08-11
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复播放下载列表的歌曲时切歌的问题
|
||||
- 修复播放下载列表的歌曲时歌词无法显示的问题
|
||||
- 修复下载列表稍后播放功能无效的问题
|
||||
- 修复同步服务器启动失败时,关闭同步服务不会清空失败信息的问题
|
||||
|
||||
## [1.12.1](https://github.com/lyswhut/lx-music-desktop/compare/v1.12.0...v1.12.1) - 2021-08-08
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复随机播放下无法切歌的问题
|
||||
|
||||
## [1.12.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.11.0...v1.12.0) - 2021-08-08
|
||||
|
||||
### 新增
|
||||
|
||||
- 新增局域网同步功能(实验性,首次使用前建议先备份一次列表),此功能需要配合PC端使用,移动端与PC端处在同一个局域网(路由器的网络)下时,可以多端实时同步歌曲列表,使用问题请看"常见问题"。
|
||||
|
||||
### 优化
|
||||
|
||||
- 添加播放器对系统媒体控制与显示的兼容处理,现在在windows下的锁屏界面可以正确显示当前播放的音乐信息及切换歌曲了
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复导入kg歌单最多只能加载100、500首歌曲的问题。注:现在可以加载1000+首歌曲的歌单,但出于未知原因会导致部分歌曲无法加载(可能是无版权导致的),目前酷狗码仍然最多只能加载500首歌
|
||||
- 修复某些情况下所显示的歌词、封面图片与当前正在播放的歌曲不一致的问题
|
||||
|
||||
## [1.11.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.10.2...v1.11.0) - 2021-07-18
|
||||
|
||||
### 新增
|
||||
|
||||
- 添加 win arm64 架构的安装包构建
|
||||
- 新增“添加歌曲到列表时的位置”设置,可选项为列表的“顶部”与“底部”
|
||||
|
||||
### 优化
|
||||
|
||||
- 优化网络请求,尝试去解决无法连接服务器的问题
|
||||
- 优化mg源打开歌单的链接兼容
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复mg源搜索失效的问题
|
||||
|
||||
### 移除
|
||||
|
||||
- 因wy源的歌单列表已没有“最新”排序的选项,所以现跟随移除wy源歌单列表按“最新”排序的按钮
|
||||
|
||||
### 变更
|
||||
|
||||
- 添加歌曲到列表时从原来的底部改为顶部,若你想要将你的列表歌曲顺序反转以适应这一变更,可先按住`shift`键的情况下点击列表的最后一首歌,然后再点击列表的第一首歌,完成倒序选中,最后随便右击列表的任意一首歌,在弹出的菜单中选择调整顺序,在弹出框输入1后确定即可反转列表。
|
||||
若你想要恢复原来的行为则可以去更改“添加歌曲到列表时的位置”设置项。
|
||||
|
||||
### 其他
|
||||
|
||||
- 更新electron到v13.1.7
|
||||
|
||||
## [1.10.2](https://github.com/lyswhut/lx-music-desktop/compare/v1.10.1...v1.10.2) - 2021-05-25
|
||||
|
||||
### 修复
|
||||
|
||||
87
FAQ.md
87
FAQ.md
@@ -13,13 +13,13 @@
|
||||
尝试更换网络,如切换到移动网络,若移动网络还是不行则尝试开关下手机的飞行模式后再试,<br>
|
||||
若使用家庭网络的话,可尝试将光猫断电5分钟左右再通电联网后播放。
|
||||
|
||||
### 提示`getaddrinfo EAI_AGAIN ...`
|
||||
### 提示 `getaddrinfo EAI_AGAIN ...` 或 `无法连接到服务器`
|
||||
|
||||
尝试在在浏览器打开这个地址`http://ts.tempmusic.tk`,浏览器显示404是正常的,如果不是404那就证明所在网络无法访问接口服务器。
|
||||
若网页无法打开或打开来不是404,则可能是DNS的问题,可以尝试以下办法:
|
||||
|
||||
1. 将DNS改成自动获取试试(注:改完可能需要清理下系统DNS缓存才生效)
|
||||
2. 手动把DNS改一下,不要用360的DNS,可以把DNS改成`114.114.114.114`、`8.8.8.8`(注:改完可能需要清理下系统DNS缓存才生效)
|
||||
2. 手动把DNS改一下,不要用360的DNS,可以把DNS改成`223.6.6.6`、`8.8.8.8`(注:改完可能需要清理下系统DNS缓存才生效)
|
||||
|
||||
改完DNS后可能需要重启软件才生效
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
若网页无法打开或打来不是404,则应该是DNS的问题,可以尝试以下办法:
|
||||
|
||||
1. 将DNS改成自动获取试试
|
||||
2. 手动把DNS改一下,不要用360的DNS,可以把DNS改成`114.114.114.114`、`8.8.8.8`
|
||||
2. 手动把DNS改一下,不要用360的DNS,可以把DNS改成`223.6.6.6`、`8.8.8.8`
|
||||
|
||||
## 列表多选
|
||||
|
||||
@@ -58,10 +58,69 @@
|
||||
|
||||
## 无法打开外部歌单
|
||||
|
||||
不支持垮源打开歌单,请**确认**你需要打开的歌单平台是否与软件标签所写的**歌单源**对应(不一样的话请通过右上角切换歌单源);<br>
|
||||
不支持跨源打开歌单,请**确认**你需要打开的歌单平台是否与软件标签所写的**歌单源**对应(不一样的话请通过右上角切换歌单源);<br>
|
||||
对于分享出来的歌单,若打开失败,可尝试先在浏览器中打开后,再从浏览器地址栏复制URL地址到软件打开;<br>
|
||||
或者如果你知道歌单 id 也可以直接输入歌单 id 打开。<br>
|
||||
|
||||
注:网易源的“我喜欢”歌单无法在未登录的情况下打开,所以你需要手动创建一个歌单后将“我喜欢”里的歌曲移动到该歌单打开
|
||||
|
||||
### 打开网易源“我喜欢”歌单
|
||||
|
||||
由于网易源的“我喜欢”歌单需要登录才能打开,从v1.13.0起提供了可以以注入token的方式打开网易源“我喜欢”歌单的功能,现若想要打开此类歌单,需要在歌单链接或id后面拼上 `###` 再加上有效的token,拼接格式:`[id|url]###token`,例子(最后面的xxxxxx替换成你的token):
|
||||
- `https://music.163.com/#/playlist?id=11332&userid=123456###xxxxxx`
|
||||
- `11332###xxxxxx`
|
||||
|
||||
即:将 `歌单链接或者歌单ID`、`###`、`token` 这三者拼到一起。
|
||||
|
||||
#### `token`的获取方法
|
||||
|
||||
**注:`token`是你账号的临时身份令牌,不要随便泄露给他人**<br>
|
||||
在浏览器打开登录网易云音乐并**登录**后,按`F12`,此时将会打开开发者窗口,然后按你使用的浏览器操作:
|
||||
|
||||
##### Chrome、360、QQ等浏览器
|
||||
|
||||
这些浏览器打开此窗口时界面可能是中文也可能是英文,英文的话按括号里的来
|
||||
|
||||
1. 点击窗口顶部`应用程序(application)`
|
||||
2. 展开左侧 `Cookies`
|
||||
3. 点击 `https://music.163.com`
|
||||
4. 在右侧窗口找到 `名称(Name)` 为 `MUSIC_U` 的这行,这行的第二列(`值(Value)`)内的那串内容就是`token`,双击它进入编辑状态,然后按`ctrl + c`键就可以将它复制
|
||||
|
||||
##### 火狐浏览器
|
||||
|
||||
1. 点击窗口顶部`存储`
|
||||
2. 展开左侧 `Cookie`
|
||||
3. 点击 `https://music.163.com`
|
||||
4. 在右侧窗口找到 `名称` 为 `MUSIC_U` 的这行,这行的最后一列(`值`)内的那串内容就是`token`,双击它进入编辑状态,然后按`ctrl + c`键就可以将它复制
|
||||
|
||||
## 更新已收藏的在线歌单
|
||||
|
||||
该功能仅对直接从歌单详情页点“收藏”按钮收藏的歌单有效,可右击已收藏的列表名从弹出的菜单中选择“更新”使用该功能,
|
||||
|
||||
需要注意的是:这将会覆盖本地的目标列表,歌曲将被替换成最新的在线列表。
|
||||
|
||||
|
||||
## 同步功能的使用(实验性,首次使用前建议先备份一次列表)
|
||||
|
||||
**注意:由于同步传输时的数据是明文传输,请在受信任的网络下使用此功能!**<br>
|
||||
此功能需要配合移动端使用,PC端与移动端处在同一个局域网(路由器的网络)下时,可以多端实时同步歌曲列表,使用方法:
|
||||
|
||||
1. 在PC端的设置-数据同步开启同步功能(这时如果出现安全软件、防火墙等提示网络连接弹窗时需要点击允许)
|
||||
2. 在移动端的设置-同步-同步服务器地址输入PC端显示的同步服务器地址(如果显示可以多个,则输入与**移动端上显示的本机地址**最相似的那个),端口号与PC端的同步端口一致(**输入完毕后需要按一下键盘上的回车键使输入的内容生效**)
|
||||
3. 输入完这两项后点击“启动同步”
|
||||
4. 若连接成功,对于首次同步时,若两边的设备的列表不为空,则PC端会弹出选择列表同步方式的弹窗,同步方式的说明弹窗下面有介绍
|
||||
|
||||
#### 关于同步弹窗的说明
|
||||
|
||||
对于首次同步时,若两边的设备的列表不为空,则PC端会弹出选择列表同步方式的弹窗,此弹窗内的同步方式仅针对**首次同步**,<br>
|
||||
第一次同步成功后,以后再同步时将会自动根据两边设备的列表内容合并同步,不信你可以在同步完成后断开两边的连接,然后在两边增删一些歌曲或列表后再同步试试看~😉
|
||||
|
||||
#### 连接同步服务失败的可能原因
|
||||
|
||||
- 此功能需要PC端与移动端都连接在同一个路由器下的网络才能使用
|
||||
- 检查防火墙是否拦截了PC端的服务端口
|
||||
- 路由器若开启了AP隔离,则此功能无法使用
|
||||
|
||||
## 界面异常(界面显示不完整)
|
||||
|
||||
### Windows 7 下界面异常
|
||||
@@ -97,6 +156,14 @@
|
||||
- 添加启动参数`-dha`运行(添加的方法请自行百度“给快捷方式加参数”)
|
||||
- 尝试将绿色版的软件放在**桌面**或**我的文档**运行
|
||||
|
||||
## Windows 7 下桌面歌词字体列表为空
|
||||
|
||||
Windows 7 系统系统需要安装 Powershell 5.1及以上版本才可正常获取系统字体列表。
|
||||
|
||||
想要查看当前 Powershell 版本可以在 Powershell 窗口输入命令:`Get-Host`
|
||||
|
||||
最新 Powershell 安装包可以去官方 [Github releases](https://github.com/PowerShell/PowerShell/releases) 页下载,安装过程中若出现错误,请自行按照提示或者百度/Google解决。
|
||||
|
||||
## 安装版安装失败,提示安装程序并未成功地运行完成
|
||||
|
||||
对于部分电脑出现安装失败的问题,可以做出以下尝试:
|
||||
@@ -152,9 +219,9 @@ Windows 7 未开启 Aero 效果时桌面歌词会有问题,详情看上面的
|
||||
|
||||
以 **`.dmg`** 结尾的文件为 MAC 版本;
|
||||
|
||||
以 **`.AppImage`**、**`.deb`** 结尾的为 Linux 版本。
|
||||
以 **`.AppImage`**、**`.deb`**、**`.rpm`**、**`.pacman`** 结尾的为 Linux 版本。
|
||||
|
||||
带有`x64`的为64位的系统版本,带`x86`的为32位的系统版本;若两个都带有的则为集合版,安装时会自动根据系统位数选择对应的版本安装。
|
||||
带有`x64`的为64位的系统版本,带`x86`的为32位的系统版本;若两个都带有的则为集合版,安装时会自动根据系统位数选择对应的版本安装;带有`arm`的为arm架构系统的版本。
|
||||
|
||||
## 软件更新
|
||||
|
||||
@@ -162,13 +229,13 @@ Windows 7 未开启 Aero 效果时桌面歌词会有问题,详情看上面的
|
||||
若下载未完成时软件被关闭,下次启动软件会再次自动下载。<br>
|
||||
若还是**更新失败**,可能是无法访问GitHub导致的,这时需要手动更新,即下载最新安装包直接覆盖安装即可。<br>
|
||||
注意:**绿色版**的软件自动更新功能**不可用**,建议使用安装版!!<br>
|
||||
注意:**Mac版**、**Linux deb**版不支持自动更新!
|
||||
注意:**Mac版**、**Linux**版不支持自动更新!
|
||||
|
||||
## 更新已收藏的在线歌单
|
||||
### Windows 安装版在升级后,卸载了旧版本,但没有安装新版本
|
||||
|
||||
该功能仅对直接从歌单详情页点“收藏”按钮收藏的歌单有效,可右击已收藏的列表名从弹出的菜单中选择“同步”使用该功能,
|
||||
出现这个问题的原因一般是你当初在安装本软件的时候是以管理员身份安装的,运行软件的时候没有以管理员身份运行,所以卸载后无法再装上。
|
||||
|
||||
需要注意的是:这将会覆盖本地的目标列表,歌曲将被替换成最新的在线列表。
|
||||
安装本软件时建议选择 `为当前用户安装`,并安装在当前用户目录或者安装在不需要管理员权限的目录(即其他分区下),不要选`为所有用户安装`。
|
||||
|
||||
## 缺少`xxx.dll`
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@ const isDev = process.env.NODE_ENV === 'development'
|
||||
|
||||
module.exports = {
|
||||
modules: {
|
||||
localIdentName: isDev ? '[folder]-[name]--[local]--[hash:base64:5]' : '[hash:base64:5]',
|
||||
localIdentName: isDev ? '[path][name]__[local]--[hash:base64:5]' : '[hash:base64:5]',
|
||||
exportLocalsConvention: 'camelCase',
|
||||
},
|
||||
sourceMap: isDev,
|
||||
}
|
||||
|
||||
@@ -29,11 +29,10 @@ module.exports = {
|
||||
exclude: /node_modules/,
|
||||
enforce: 'pre',
|
||||
},
|
||||
// {
|
||||
// test: /\.js$/,
|
||||
// loader: 'babel-loader',
|
||||
// exclude: /node_modules/,
|
||||
// },
|
||||
{
|
||||
test: /\.node$/,
|
||||
use: 'node-loader',
|
||||
},
|
||||
],
|
||||
},
|
||||
performance: {
|
||||
|
||||
@@ -15,6 +15,7 @@ module.exports = merge(baseConfig, {
|
||||
},
|
||||
externals: [
|
||||
...Object.keys(dependencies || {}),
|
||||
// 'font-list',
|
||||
],
|
||||
node: {
|
||||
__dirname: false,
|
||||
|
||||
@@ -18,7 +18,7 @@ module.exports = {
|
||||
filename: '[name].js',
|
||||
libraryTarget: 'commonjs2',
|
||||
path: path.join(__dirname, '../../dist/electron'),
|
||||
publicPath: './',
|
||||
publicPath: 'auto',
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
@@ -44,6 +44,10 @@ module.exports = {
|
||||
exclude: /node_modules/,
|
||||
enforce: 'pre',
|
||||
},
|
||||
{
|
||||
test: /\.node$/,
|
||||
use: 'node-loader',
|
||||
},
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader',
|
||||
@@ -72,15 +76,6 @@ module.exports = {
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
test: /\.styl(:?us)?$/,
|
||||
oneOf: mergeCSSLoader({
|
||||
loader: 'stylus-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
test: /\.pug$/,
|
||||
oneOf: [
|
||||
@@ -97,26 +92,38 @@ module.exports = {
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'imgs/[name]--[folder].[ext]',
|
||||
type: 'asset',
|
||||
parser: {
|
||||
dataUrlCondition: {
|
||||
maxSize: 10000,
|
||||
},
|
||||
},
|
||||
generator: {
|
||||
filename: 'imgs/[name]-[contenthash:8][ext]',
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'media/[name]--[folder].[ext]',
|
||||
type: 'asset',
|
||||
parser: {
|
||||
dataUrlCondition: {
|
||||
maxSize: 10000,
|
||||
},
|
||||
},
|
||||
generator: {
|
||||
filename: 'media/[name]-[contenthash:8][ext]',
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'fonts/[name]--[folder].[ext]',
|
||||
type: 'asset',
|
||||
parser: {
|
||||
dataUrlCondition: {
|
||||
maxSize: 10000,
|
||||
},
|
||||
},
|
||||
generator: {
|
||||
filename: 'fonts/[name]-[contenthash:8][ext]',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -18,7 +18,7 @@ module.exports = {
|
||||
filename: '[name].js',
|
||||
libraryTarget: 'commonjs2',
|
||||
path: path.join(__dirname, '../../dist/electron'),
|
||||
publicPath: './',
|
||||
publicPath: 'auto',
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
@@ -44,6 +44,10 @@ module.exports = {
|
||||
exclude: /node_modules/,
|
||||
enforce: 'pre',
|
||||
},
|
||||
{
|
||||
test: /\.node$/,
|
||||
use: 'node-loader',
|
||||
},
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader',
|
||||
@@ -72,15 +76,6 @@ module.exports = {
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
test: /\.styl(:?us)?$/,
|
||||
oneOf: mergeCSSLoader({
|
||||
loader: 'stylus-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
test: /\.pug$/,
|
||||
oneOf: [
|
||||
@@ -97,26 +92,38 @@ module.exports = {
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'imgs/[name]--[folder].[ext]',
|
||||
type: 'asset',
|
||||
parser: {
|
||||
dataUrlCondition: {
|
||||
maxSize: 10000,
|
||||
},
|
||||
},
|
||||
generator: {
|
||||
filename: 'imgs/[name]-[contenthash:8][ext]',
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'media/[name]--[folder].[ext]',
|
||||
type: 'asset',
|
||||
parser: {
|
||||
dataUrlCondition: {
|
||||
maxSize: 10000,
|
||||
},
|
||||
},
|
||||
generator: {
|
||||
filename: 'media/[name]-[contenthash:8][ext]',
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'fonts/[name]--[folder].[ext]',
|
||||
type: 'asset',
|
||||
parser: {
|
||||
dataUrlCondition: {
|
||||
maxSize: 10000,
|
||||
},
|
||||
},
|
||||
generator: {
|
||||
filename: 'fonts/[name]-[contenthash:8][ext]',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -16,7 +16,8 @@ const rendererLyricConfig = require('./renderer-lyric/webpack.config.dev')
|
||||
|
||||
let electronProcess = null
|
||||
let manualRestart = false
|
||||
let hotMiddleware
|
||||
let hotMiddlewareRenderer
|
||||
let hotMiddlewareRendererLyric
|
||||
|
||||
|
||||
function startRenderer() {
|
||||
@@ -24,7 +25,7 @@ function startRenderer() {
|
||||
// rendererConfig.entry.renderer = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.renderer)
|
||||
// rendererConfig.mode = 'development'
|
||||
const compiler = webpack(rendererConfig)
|
||||
hotMiddleware = webpackHotMiddleware(compiler, {
|
||||
hotMiddlewareRenderer = webpackHotMiddleware(compiler, {
|
||||
log: false,
|
||||
heartbeat: 2500,
|
||||
})
|
||||
@@ -32,15 +33,15 @@ function startRenderer() {
|
||||
compiler.hooks.compilation.tap('compilation', compilation => {
|
||||
// console.log(Object.keys(compilation.hooks))
|
||||
HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync('html-webpack-plugin-after-emit', (data, cb) => {
|
||||
hotMiddleware.publish({ action: 'reload' })
|
||||
hotMiddlewareRenderer.publish({ action: 'reload' })
|
||||
cb()
|
||||
})
|
||||
})
|
||||
|
||||
compiler.hooks.done.tap('done', stats => {
|
||||
// logStats('Renderer', 'Compile done')
|
||||
// logStats('Renderer', stats)
|
||||
})
|
||||
// compiler.hooks.done.tap('done', stats => {
|
||||
// // logStats('Renderer', 'Compile done')
|
||||
// // logStats('Renderer', stats)
|
||||
// })
|
||||
|
||||
const server = new WebpackDevServer(
|
||||
compiler,
|
||||
@@ -54,7 +55,7 @@ function startRenderer() {
|
||||
errors: true,
|
||||
},
|
||||
before(app, ctx) {
|
||||
app.use(hotMiddleware)
|
||||
app.use(hotMiddlewareRenderer)
|
||||
ctx.middleware.waitUntilValid(() => {
|
||||
resolve()
|
||||
})
|
||||
@@ -71,7 +72,7 @@ function startRendererLyric() {
|
||||
// rendererConfig.entry.renderer = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.renderer)
|
||||
// rendererConfig.mode = 'development'
|
||||
const compiler = webpack(rendererLyricConfig)
|
||||
hotMiddleware = webpackHotMiddleware(compiler, {
|
||||
hotMiddlewareRendererLyric = webpackHotMiddleware(compiler, {
|
||||
log: false,
|
||||
heartbeat: 2500,
|
||||
})
|
||||
@@ -79,15 +80,15 @@ function startRendererLyric() {
|
||||
compiler.hooks.compilation.tap('compilation', compilation => {
|
||||
// console.log(Object.keys(compilation.hooks))
|
||||
HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync('html-webpack-plugin-after-emit', (data, cb) => {
|
||||
hotMiddleware.publish({ action: 'reload' })
|
||||
hotMiddlewareRendererLyric.publish({ action: 'reload' })
|
||||
cb()
|
||||
})
|
||||
})
|
||||
|
||||
compiler.hooks.done.tap('done', stats => {
|
||||
// logStats('Renderer', 'Compile done')
|
||||
// logStats('Renderer', stats)
|
||||
})
|
||||
// compiler.hooks.done.tap('done', stats => {
|
||||
// // logStats('Renderer', 'Compile done')
|
||||
// // logStats('Renderer', stats)
|
||||
// })
|
||||
|
||||
const server = new WebpackDevServer(
|
||||
compiler,
|
||||
@@ -101,7 +102,7 @@ function startRendererLyric() {
|
||||
errors: true,
|
||||
},
|
||||
before(app, ctx) {
|
||||
app.use(hotMiddleware)
|
||||
app.use(hotMiddlewareRendererLyric)
|
||||
ctx.middleware.waitUntilValid(() => {
|
||||
resolve()
|
||||
})
|
||||
@@ -120,7 +121,8 @@ function startMain() {
|
||||
const compiler = webpack(mainConfig)
|
||||
|
||||
compiler.hooks.watchRun.tapAsync('watch-run', (compilation, done) => {
|
||||
hotMiddleware.publish({ action: 'compiling' })
|
||||
hotMiddlewareRenderer.publish({ action: 'compiling' })
|
||||
hotMiddlewareRendererLyric.publish({ action: 'compiling' })
|
||||
done()
|
||||
})
|
||||
|
||||
|
||||
8917
package-lock.json
generated
8917
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
103
package.json
103
package.json
@@ -1,15 +1,16 @@
|
||||
{
|
||||
"name": "lx-music-desktop",
|
||||
"version": "1.10.2",
|
||||
"version": "1.14.0",
|
||||
"description": "一个免费的音乐查找助手",
|
||||
"main": "./dist/electron/main.js",
|
||||
"productName": "lx-music-desktop",
|
||||
"scripts": {
|
||||
"pack": "node build-config/pack.js && npm run pack:win:setup:x64",
|
||||
"pack:win": "node build-config/pack.js && npm run pack:win:setup:x86_64 && npm run pack:win:setup:x64 && npm run pack:win:7z",
|
||||
"pack:win": "node build-config/pack.js && npm run pack:win:setup:x64 && npm run pack:win:setup:x86 && npm run pack:win:setup:arm64 && npm run pack:win:setup:x86_64 && npm run pack:win:7z",
|
||||
"pack:win:setup:x86_64": "cross-env TARGET=Setup ARCH=x86_64 electron-builder -w=nsis --x64 --ia32 -p never",
|
||||
"pack:win:setup:x64": "cross-env TARGET=Setup ARCH=x64 electron-builder -w=nsis --x64 -p never",
|
||||
"pack:win:setup:x86": "cross-env TARGET=Setup ARCH=x86 electron-builder -w=nsis --ia32 -p never",
|
||||
"pack:win:setup:arm64": "cross-env TARGET=Setup ARCH=arm64 electron-builder -w=nsis --arm64 -p never",
|
||||
"pack:win:portable": "npm run pack:win:portable:x86_64 && npm run pack:win:portable:x64 && npm run pack:win:portable:x86",
|
||||
"pack:win:portable:x86_64": "cross-env TARGET=portable ARCH=x86_64 electron-builder -w=portable --x64 --ia32 -p never",
|
||||
"pack:win:portable:x64": "cross-env TARGET=portable ARCH=x64 electron-builder -w=portable --x64 -p never",
|
||||
@@ -35,6 +36,7 @@
|
||||
"publish:win:setup:x64:always": "cross-env TARGET=Setup ARCH=x64 electron-builder -w=nsis --x64 -p always",
|
||||
"publish:win:setup:x64": "cross-env TARGET=Setup ARCH=x64 electron-builder -w=nsis --x64 -p always",
|
||||
"publish:win:setup:x86": "cross-env TARGET=Setup ARCH=x86 electron-builder -w=nsis --ia32 -p onTagOrDraft",
|
||||
"publish:win:setup:arm64": "cross-env TARGET=Setup ARCH=arm64 electron-builder -w=nsis --arm64 -p onTagOrDraft",
|
||||
"publish:win:setup:x86_64": "cross-env TARGET=Setup ARCH=x86_64 electron-builder -w=nsis --x64 --ia32 -p onTagOrDraft",
|
||||
"publish:win:portable": "npm run publish:win:portable:x86_64 && npm run publish:win:portable:x64 && npm run publish:win:portable:x86",
|
||||
"publish:win:portable:x86_64": "cross-env TARGET=portable ARCH=x86_64 electron-builder -w=portable --x64 --ia32 -p onTagOrDraft",
|
||||
@@ -68,7 +70,7 @@
|
||||
"up": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://localhost:1081 npm i"
|
||||
},
|
||||
"browserslist": [
|
||||
"Electron 12.0.9"
|
||||
"Electron 13.3.0"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
@@ -81,6 +83,9 @@
|
||||
"files": [
|
||||
"dist/electron/**/*"
|
||||
],
|
||||
"asar": {
|
||||
"smartUnpack": false
|
||||
},
|
||||
"extraResources": [
|
||||
"./licenses"
|
||||
],
|
||||
@@ -161,50 +166,51 @@
|
||||
},
|
||||
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.3",
|
||||
"@babel/plugin-proposal-class-properties": "^7.13.0",
|
||||
"@babel/core": "^7.15.5",
|
||||
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-modules-umd": "^7.14.0",
|
||||
"@babel/plugin-transform-runtime": "^7.14.3",
|
||||
"@babel/plugin-transform-modules-umd": "^7.14.5",
|
||||
"@babel/plugin-transform-runtime": "^7.15.0",
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"@babel/preset-env": "^7.14.2",
|
||||
"@babel/preset-env": "^7.15.6",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-minify-webpack-plugin": "^0.3.1",
|
||||
"babel-preset-minify": "^0.5.1",
|
||||
"browserslist": "^4.16.6",
|
||||
"cfonts": "^2.9.2",
|
||||
"chalk": "^4.1.1",
|
||||
"browserslist": "^4.17.2",
|
||||
"cfonts": "^2.10.0",
|
||||
"chalk": "^4.1.2",
|
||||
"changelog-parser": "^2.8.0",
|
||||
"copy-webpack-plugin": "^9.0.0",
|
||||
"core-js": "^3.12.1",
|
||||
"copy-webpack-plugin": "^9.0.1",
|
||||
"core-js": "^3.18.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^5.2.6",
|
||||
"css-minimizer-webpack-plugin": "^3.0.0",
|
||||
"css-loader": "^6.3.0",
|
||||
"css-minimizer-webpack-plugin": "^3.0.2",
|
||||
"del": "^6.0.0",
|
||||
"electron": "^12.0.9",
|
||||
"electron-builder": "^22.11.4",
|
||||
"electron": "^13.4.0",
|
||||
"electron-builder": "^22.11.7",
|
||||
"electron-debug": "^3.2.0",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"eslint": "^7.27.0",
|
||||
"eslint-config-standard": "^14.1.1",
|
||||
"electron-to-chromium": "^1.3.856",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-standard": "^16.0.3",
|
||||
"eslint-formatter-friendly": "^7.0.0",
|
||||
"eslint-loader": "^4.0.2",
|
||||
"eslint-plugin-html": "^6.1.2",
|
||||
"eslint-plugin-import": "^2.23.3",
|
||||
"eslint-plugin-html": "^6.2.0",
|
||||
"eslint-plugin-import": "^2.24.2",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"eslint-plugin-standard": "^4.1.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"friendly-errors-webpack-plugin": "^1.7.0",
|
||||
"html-webpack-plugin": "^5.3.1",
|
||||
"html-webpack-plugin": "^5.3.2",
|
||||
"less": "^4.1.1",
|
||||
"less-loader": "^9.0.0",
|
||||
"less-loader": "^10.0.1",
|
||||
"less-plugin-clean-css": "^1.5.1",
|
||||
"markdown-it": "^12.0.6",
|
||||
"mini-css-extract-plugin": "^1.6.0",
|
||||
"postcss": "^8.3.0",
|
||||
"postcss-loader": "^5.3.0",
|
||||
"markdown-it": "^12.2.0",
|
||||
"mini-css-extract-plugin": "^2.3.0",
|
||||
"node-loader": "^2.0.0",
|
||||
"postcss": "^8.3.8",
|
||||
"postcss-loader": "^6.1.1",
|
||||
"postcss-pxtorem": "^6.0.0",
|
||||
"pug": "^3.0.2",
|
||||
"pug-loader": "^2.4.0",
|
||||
@@ -212,34 +218,37 @@
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"spinnies": "^0.5.1",
|
||||
"stylus": "^0.54.8",
|
||||
"stylus-loader": "^6.0.0",
|
||||
"terser-webpack-plugin": "^5.1.2",
|
||||
"terser-webpack-plugin": "^5.2.4",
|
||||
"url-loader": "^4.1.1",
|
||||
"vue-loader": "^15.9.7",
|
||||
"vue-template-compiler": "^2.6.12",
|
||||
"webpack": "^5.37.1",
|
||||
"webpack-cli": "^4.7.0",
|
||||
"vue-loader": "^15.9.8",
|
||||
"vue-template-compiler": "^2.6.14",
|
||||
"webpack": "^5.56.0",
|
||||
"webpack-cli": "^4.8.0",
|
||||
"webpack-dev-server": "^3.11.2",
|
||||
"webpack-hot-middleware": "^2.25.0",
|
||||
"webpack-merge": "^5.7.3"
|
||||
"webpack-hot-middleware": "^2.25.1",
|
||||
"webpack-merge": "^5.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"crypto-js": "^4.0.0",
|
||||
"dnscache": "^1.0.2",
|
||||
"electron-log": "^4.3.5",
|
||||
"electron-store": "^8.0.0",
|
||||
"bufferutil": "^4.0.4",
|
||||
"crypto-js": "^4.1.1",
|
||||
"electron-log": "^4.4.1",
|
||||
"electron-store": "^8.0.1",
|
||||
"electron-updater": "^4.3.9",
|
||||
"font-list": "git+https://github.com/lyswhut/node-font-list.git#c6caf4060e471afe143a4aca30d554644522966d",
|
||||
"http-terminator": "^3.0.3",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"image-size": "^1.0.0",
|
||||
"js-htmlencode": "^0.3.0",
|
||||
"lrc-file-parser": "^1.0.7",
|
||||
"needle": "^2.6.0",
|
||||
"koa": "^2.13.3",
|
||||
"long": "^4.0.0",
|
||||
"lrc-file-parser": "^1.2.1",
|
||||
"needle": "^3.0.0",
|
||||
"node-id3": "^0.2.3",
|
||||
"request": "^2.88.2",
|
||||
"vue": "^2.6.12",
|
||||
"vue-i18n": "^8.24.4",
|
||||
"vue-router": "^3.5.1",
|
||||
"socket.io": "^4.2.0",
|
||||
"utf-8-validate": "^5.0.6",
|
||||
"vue": "^2.6.14",
|
||||
"vue-i18n": "^8.26.5",
|
||||
"vue-router": "^3.5.2",
|
||||
"vuex": "^3.6.2",
|
||||
"vuex-router-sync": "^5.0.0"
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ module.exports = {
|
||||
'*-height', '*-width',
|
||||
'flex', '::-webkit-scrollbar',
|
||||
'top', 'left', 'bottom', 'right',
|
||||
'border-radius',
|
||||
'border-radius', 'gap',
|
||||
],
|
||||
selectorBlackList: ['html', 'ignore-to-rem'],
|
||||
replace: true,
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
### 新增
|
||||
|
||||
- 新增歌词简体中文转繁体中文,当软件语言被设置为繁体中文后,播放歌曲的歌词也将自动转成繁体中文显示
|
||||
- 新增单个列表导入/导出功能,可以方便分享歌曲列表,可在右击“我的列表”里的列表名后弹出的菜单中使用
|
||||
- 新增删除列表前的确认弹窗,防止误删列表
|
||||
- 新增歌词文本选择复制功能,可在详情页进度条上方的歌词文本选择按钮进入歌词文本选择模式,选择完成后可鼠标右击或者使用系统快捷键复制
|
||||
- 新增重复歌曲列表,可以方便移除我的列表中的重复歌曲,此列表会列出目标列表里歌曲名相同的歌曲,可在右击“我的列表”里的列表名后弹出的菜单中使用
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复企鹅音乐搜索歌曲没有结果的问题
|
||||
- 修复mg排行榜无法加载的问题
|
||||
- 修复点击播放详情页的进度条跳进度时会出现偏移的问题
|
||||
- 修复在有提示信息的地方长按鼠标按键时提示信息会闪烁的问题
|
||||
- 修复下载歌曲时的歌词下载不尝试获取缓存歌词的问题
|
||||
- 修复GNOME等桌面下每次打开应用时需重新设置歌词窗口置顶的问题
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2,7 +2,7 @@ const path = require('path')
|
||||
const os = require('os')
|
||||
|
||||
const defaultSetting = {
|
||||
version: '1.0.41',
|
||||
version: '1.0.45',
|
||||
player: {
|
||||
togglePlayMethod: 'listLoop',
|
||||
highQuality: false,
|
||||
@@ -26,6 +26,7 @@ const defaultSetting = {
|
||||
theme: 0,
|
||||
isLockScreen: true,
|
||||
style: {
|
||||
font: '',
|
||||
fontSize: 120,
|
||||
opacity: 95,
|
||||
isZoomActiveLrc: true,
|
||||
@@ -36,6 +37,7 @@ const defaultSetting = {
|
||||
isShowSource: true,
|
||||
prevSelectListId: 'default',
|
||||
isSaveScrollLocation: true,
|
||||
addMusicLocationType: 'top',
|
||||
},
|
||||
download: {
|
||||
enable: false,
|
||||
@@ -43,6 +45,7 @@ const defaultSetting = {
|
||||
fileName: '歌名 - 歌手',
|
||||
maxDownloadNum: 3,
|
||||
isDownloadLrc: false,
|
||||
lrcFormat: 'utf8',
|
||||
isEmbedPic: true,
|
||||
isEmbedLyric: false,
|
||||
isUseOtherSource: false,
|
||||
@@ -84,6 +87,10 @@ const defaultSetting = {
|
||||
isToTray: false,
|
||||
themeId: 0,
|
||||
},
|
||||
sync: {
|
||||
enable: false,
|
||||
port: '23332',
|
||||
},
|
||||
windowSizeId: 2,
|
||||
themeId: 0,
|
||||
langId: null,
|
||||
|
||||
@@ -22,6 +22,7 @@ const names = {
|
||||
set_app_setting: 'set_app_setting',
|
||||
set_window_size: 'set_window_size',
|
||||
show_save_dialog: 'show_save_dialog',
|
||||
get_system_fonts: 'get_system_fonts',
|
||||
|
||||
handle_request: 'handle_request',
|
||||
cancel_request: 'cancel_request',
|
||||
@@ -32,6 +33,8 @@ const names = {
|
||||
|
||||
restart_window: 'restart_window',
|
||||
|
||||
lang_s2t: 'lang_s2t',
|
||||
|
||||
handle_kw_decode_lyric: 'handle_kw_decode_lyric',
|
||||
get_lyric_info: 'get_lyric_info',
|
||||
set_lyric_info: 'set_lyric_info',
|
||||
@@ -66,6 +69,14 @@ const names = {
|
||||
get_music_url: 'get_music_url',
|
||||
save_music_url: 'save_music_url',
|
||||
clear_music_url: 'clear_music_url',
|
||||
|
||||
sync_enable: 'sync_enable',
|
||||
sync_status: 'sync_status',
|
||||
sync_get_status: 'sync_get_status',
|
||||
sync_generate_code: 'sync_generate_code',
|
||||
sync_action_list: 'sync_action_list',
|
||||
sync_list: 'sync_list',
|
||||
|
||||
},
|
||||
winLyric: {
|
||||
close: 'close',
|
||||
|
||||
@@ -10,6 +10,10 @@ class Common extends EventEmitter {
|
||||
configStatus(name) {
|
||||
this.emit(COMMON_EVENT_NAME.configStatus, name)
|
||||
}
|
||||
|
||||
saveMyList(data) {
|
||||
this.emit(COMMON_EVENT_NAME.saveMyList, data)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Common
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
exports.common = {
|
||||
initConfig: 'initConfig',
|
||||
configStatus: 'config',
|
||||
saveMyList: 'saveMyList',
|
||||
}
|
||||
|
||||
exports.mainWindow = {
|
||||
|
||||
@@ -7,6 +7,7 @@ const WinLyric = require('./WinLyric')
|
||||
const HotKey = require('./HotKey')
|
||||
|
||||
const { Event: UserApi } = require('../modules/userApi')
|
||||
const { Event: Sync } = require('../modules/sync')
|
||||
|
||||
if (!global.lx_event.common) global.lx_event.common = new Common()
|
||||
if (!global.lx_event.mainWindow) global.lx_event.mainWindow = new MainWindow()
|
||||
@@ -15,3 +16,4 @@ if (!global.lx_event.winLyric) global.lx_event.winLyric = new WinLyric()
|
||||
if (!global.lx_event.hotKey) global.lx_event.hotKey = new HotKey()
|
||||
|
||||
if (!global.lx_event.userApi) global.lx_event.userApi = new UserApi()
|
||||
if (!global.lx_event.sync) global.lx_event.sync = new Sync()
|
||||
|
||||
@@ -55,12 +55,12 @@ app.on('web-contents-created', (event, contents) => {
|
||||
if (!navigationUrlWhiteList.some(url => url.test(navigationUrl))) return event.preventDefault()
|
||||
console.log('navigation to url:', navigationUrl)
|
||||
})
|
||||
contents.on('new-window', async(event, navigationUrl) => {
|
||||
event.preventDefault()
|
||||
if (/^devtools/.test(navigationUrl)) return
|
||||
console.log(navigationUrl)
|
||||
if (!/^https?:\/\//.test(navigationUrl)) return
|
||||
await shell.openExternal(navigationUrl)
|
||||
contents.setWindowOpenHandler(({ url }) => {
|
||||
if (!/^devtools/.test(url) && /^https?:\/\//.test(url)) {
|
||||
shell.openExternal(url)
|
||||
}
|
||||
console.log(url)
|
||||
return { action: 'deny' }
|
||||
})
|
||||
contents.on('will-attach-webview', (event, webPreferences, params) => {
|
||||
// Strip away preload scripts if unused or verify their location is legitimate
|
||||
|
||||
23
src/main/modules/sync/event/event.js
Normal file
23
src/main/modules/sync/event/event.js
Normal file
@@ -0,0 +1,23 @@
|
||||
const { EventEmitter } = require('events')
|
||||
const SYNC_EVENT_NAME = require('./name')
|
||||
|
||||
class Sync extends EventEmitter {
|
||||
status(status) {
|
||||
this.emit(SYNC_EVENT_NAME.status, status)
|
||||
}
|
||||
|
||||
sync_list(data) {
|
||||
this.emit(SYNC_EVENT_NAME.sync_list, data)
|
||||
}
|
||||
|
||||
sync_handle_list(data) {
|
||||
this.emit(SYNC_EVENT_NAME.sync_handle_list, data)
|
||||
}
|
||||
|
||||
action_list(data) {
|
||||
this.emit(SYNC_EVENT_NAME.sync_action_list, data)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Sync
|
||||
|
||||
6
src/main/modules/sync/event/name.js
Normal file
6
src/main/modules/sync/event/name.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
sync_action_list: 'sync_action_list',
|
||||
sync_list: 'sync_list',
|
||||
sync_handle_list: 'sync_handle_list',
|
||||
status: 'status',
|
||||
}
|
||||
15
src/main/modules/sync/index.js
Normal file
15
src/main/modules/sync/index.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const Event = require('./event/event')
|
||||
const eventNames = require('./event/name')
|
||||
const modules = require('./modules')
|
||||
const { startServer, stopServer, getStatus, generateCode } = require('./server/server')
|
||||
|
||||
|
||||
module.exports = {
|
||||
startServer,
|
||||
stopServer,
|
||||
getStatus,
|
||||
generateCode,
|
||||
Event,
|
||||
eventNames,
|
||||
modules,
|
||||
}
|
||||
1
src/main/modules/sync/modules/index.js
Normal file
1
src/main/modules/sync/modules/index.js
Normal file
@@ -0,0 +1 @@
|
||||
exports.list = require('./list')
|
||||
41
src/main/modules/sync/modules/list.js
Normal file
41
src/main/modules/sync/modules/list.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const { encryptMsg, decryptMsg } = require('../server/utils')
|
||||
let io
|
||||
|
||||
const handleListAction = ({ action, data }) => {
|
||||
// console.log(action, data)
|
||||
global.lx_event.sync.action_list({ action, data })
|
||||
}
|
||||
|
||||
// const addMusic = (orderId, callback) => {
|
||||
// // ...
|
||||
// }
|
||||
|
||||
const broadcast = async(action, data, excludeIds = []) => {
|
||||
if (!io) return
|
||||
const sockets = await io.fetchSockets()
|
||||
for (const socket of sockets) {
|
||||
if (excludeIds.includes(socket.data.keyInfo.clientId)) continue
|
||||
socket.emit(action, encryptMsg(socket.data.keyInfo, data))
|
||||
}
|
||||
}
|
||||
|
||||
exports.sendListAction = (action, data) => {
|
||||
// io.sockets
|
||||
return broadcast('list:action', JSON.stringify({ action, data }))
|
||||
}
|
||||
|
||||
exports.registerListHandler = (_io, socket) => {
|
||||
io = _io
|
||||
socket.on('list:action', msg => {
|
||||
// console.log(msg)
|
||||
msg = decryptMsg(socket.data.keyInfo, msg)
|
||||
if (!msg) return
|
||||
handleListAction(JSON.parse(msg))
|
||||
broadcast('list:action', msg, [socket.data.keyInfo.clientId])
|
||||
// socket.broadcast.emit('list:action', { action: 'list_remove', data: { id: 'default', index: 0 } })
|
||||
})
|
||||
// socket.on('list:add', addMusic)
|
||||
}
|
||||
exports.unregisterListHandler = () => {
|
||||
io = null
|
||||
}
|
||||
69
src/main/modules/sync/server/auth.js
Normal file
69
src/main/modules/sync/server/auth.js
Normal file
@@ -0,0 +1,69 @@
|
||||
const { aesEncrypt, aesDecrypt, createClientKeyInfo, getClientKeyInfo, setClientKeyInfo } = require('./utils')
|
||||
|
||||
const authMsg = 'lx-music auth::'
|
||||
const helloMsg = 'Hello~::^-^::'
|
||||
|
||||
exports.authCode = async(req, res, authCode) => {
|
||||
let code = 401
|
||||
let msg = 'Forbidden'
|
||||
// console.log(req.headers)
|
||||
if (req.headers.m) {
|
||||
label:
|
||||
if (req.headers.i) {
|
||||
const keyInfo = getClientKeyInfo(req.headers.i)
|
||||
if (!keyInfo) break label
|
||||
let text
|
||||
try {
|
||||
text = aesDecrypt(req.headers.m, keyInfo.key, keyInfo.iv)
|
||||
} catch (err) {
|
||||
break label
|
||||
}
|
||||
console.log(text)
|
||||
if (text.startsWith(authMsg)) {
|
||||
code = 200
|
||||
const deviceName = text.replace(authMsg, '') || 'Unknown'
|
||||
if (deviceName != keyInfo.deviceName) {
|
||||
keyInfo.deviceName = deviceName
|
||||
setClientKeyInfo(keyInfo)
|
||||
}
|
||||
msg = aesEncrypt(helloMsg, keyInfo.key, keyInfo.iv)
|
||||
}
|
||||
} else {
|
||||
let key = ''.padStart(16, Buffer.from(authCode).toString('hex'))
|
||||
const iv = Buffer.from(key.split('').reverse().join('')).toString('base64')
|
||||
key = Buffer.from(key).toString('base64')
|
||||
// console.log(authCode, key, iv)
|
||||
let text
|
||||
try {
|
||||
text = aesDecrypt(req.headers.m, key, iv)
|
||||
} catch (err) {
|
||||
break label
|
||||
}
|
||||
console.log(text)
|
||||
if (text.startsWith(authMsg)) {
|
||||
code = 200
|
||||
const deviceName = text.replace(authMsg, '') || 'Unknown'
|
||||
msg = aesEncrypt(JSON.stringify(createClientKeyInfo(deviceName)), key, iv)
|
||||
}
|
||||
}
|
||||
}
|
||||
res.writeHead(code)
|
||||
res.end(msg)
|
||||
}
|
||||
|
||||
exports.authConnect = async req => {
|
||||
const { i, t } = req._query
|
||||
label:
|
||||
if (i && t) {
|
||||
const keyInfo = getClientKeyInfo(i)
|
||||
if (!keyInfo) break label
|
||||
let text
|
||||
try {
|
||||
text = aesDecrypt(t, keyInfo.key, keyInfo.iv)
|
||||
} catch (err) {
|
||||
break label
|
||||
}
|
||||
if (text == 'lx-music connect') return
|
||||
}
|
||||
throw new Error('failed')
|
||||
}
|
||||
7
src/main/modules/sync/server/index.js
Normal file
7
src/main/modules/sync/server/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const { startServer, stopServer, getStatus } = require('./server')
|
||||
|
||||
module.exports = {
|
||||
startServer,
|
||||
stopServer,
|
||||
getStatus,
|
||||
}
|
||||
172
src/main/modules/sync/server/server.js
Normal file
172
src/main/modules/sync/server/server.js
Normal file
@@ -0,0 +1,172 @@
|
||||
const http = require('http')
|
||||
const sio = require('socket.io')
|
||||
const { createHttpTerminator } = require('http-terminator')
|
||||
const modules = require('../modules')
|
||||
const { authCode, authConnect } = require('./auth')
|
||||
const { getAddress, getServerId, generateCode, getClientKeyInfo } = require('./utils')
|
||||
const syncList = require('./syncList')
|
||||
|
||||
|
||||
let status = {
|
||||
status: false,
|
||||
message: '',
|
||||
address: [],
|
||||
code: '',
|
||||
devices: [],
|
||||
}
|
||||
|
||||
const handleConnection = (io, socket) => {
|
||||
console.log('connection')
|
||||
// console.log(socket.handshake.query)
|
||||
for (const module of Object.values(modules)) {
|
||||
module.registerListHandler(io, socket)
|
||||
}
|
||||
}
|
||||
|
||||
const authConnection = (req, callback) => {
|
||||
// console.log(req.headers)
|
||||
// // console.log(req.auth)
|
||||
// console.log(req._query.authCode)
|
||||
authConnect(req).then(() => {
|
||||
callback(null, true)
|
||||
}).catch(err => {
|
||||
callback(err, false)
|
||||
})
|
||||
}
|
||||
|
||||
let httpTerminator = null
|
||||
let io = null
|
||||
|
||||
const handleStartServer = (port = 9527) => new Promise((resolve, reject) => {
|
||||
const httpServer = http.createServer((req, res) => {
|
||||
// console.log(req.url)
|
||||
let code
|
||||
let msg
|
||||
switch (req.url) {
|
||||
case '/hello':
|
||||
code = 200
|
||||
msg = 'Hello~::^-^::'
|
||||
break
|
||||
case '/id':
|
||||
code = 200
|
||||
msg = 'OjppZDo6' + getServerId()
|
||||
break
|
||||
case '/ah':
|
||||
authCode(req, res, status.code)
|
||||
break
|
||||
default:
|
||||
code = 401
|
||||
msg = 'Forbidden'
|
||||
break
|
||||
}
|
||||
if (!code) return
|
||||
res.writeHead(code)
|
||||
res.end(msg)
|
||||
})
|
||||
httpTerminator = createHttpTerminator({
|
||||
server: httpServer,
|
||||
})
|
||||
io = sio(httpServer, {
|
||||
path: '/sync',
|
||||
serveClient: false,
|
||||
connectTimeout: 10000,
|
||||
pingTimeout: 30000,
|
||||
maxHttpBufferSize: 3e6,
|
||||
allowRequest: authConnection,
|
||||
transports: ['websocket'],
|
||||
})
|
||||
|
||||
io.on('connection', async socket => {
|
||||
socket.on('disconnect', reason => {
|
||||
console.log('disconnect', reason)
|
||||
status.devices.splice(status.devices.findIndex(k => k.clientId == keyInfo.clientId), 1)
|
||||
global.lx_event.sync.status(status)
|
||||
})
|
||||
const keyInfo = getClientKeyInfo(socket.handshake.query.i)
|
||||
// socket.lx_keyInfo = keyInfo
|
||||
socket.data.keyInfo = keyInfo
|
||||
try {
|
||||
await syncList(io, socket)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return
|
||||
}
|
||||
status.devices.push(keyInfo)
|
||||
handleConnection(io, socket, keyInfo)
|
||||
global.lx_event.sync.status(status)
|
||||
})
|
||||
|
||||
httpServer.on('error', error => {
|
||||
console.log(error)
|
||||
reject(error)
|
||||
})
|
||||
|
||||
httpServer.on('listening', () => {
|
||||
const addr = httpServer.address()
|
||||
const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port
|
||||
console.info(`Listening on ${bind}`)
|
||||
resolve()
|
||||
})
|
||||
|
||||
httpServer.listen(port)
|
||||
})
|
||||
|
||||
const handleStopServer = async() => {
|
||||
if (!httpTerminator) return
|
||||
await io.close()
|
||||
await httpTerminator.terminate().catch(() => {})
|
||||
io = null
|
||||
httpTerminator = null
|
||||
}
|
||||
|
||||
exports.stopServer = async() => {
|
||||
if (!status.status) {
|
||||
status.status = false
|
||||
status.message = ''
|
||||
status.address = []
|
||||
status.code = ''
|
||||
global.lx_event.sync.status(status)
|
||||
return
|
||||
}
|
||||
console.log('stoping sync server...')
|
||||
return handleStopServer().then(() => {
|
||||
console.log('sync server stoped')
|
||||
status.status = false
|
||||
status.message = ''
|
||||
status.address = []
|
||||
status.code = ''
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
status.message = err.message
|
||||
}).finally(() => {
|
||||
global.lx_event.sync.status(status)
|
||||
})
|
||||
}
|
||||
exports.startServer = async port => {
|
||||
if (status.status) await handleStopServer()
|
||||
|
||||
console.log('starting sync server...')
|
||||
return handleStartServer(port).then(() => {
|
||||
console.log('sync server started')
|
||||
status.status = true
|
||||
status.message = ''
|
||||
status.address = getAddress()
|
||||
status.code = generateCode()
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
status.status = false
|
||||
status.message = err.message
|
||||
status.address = []
|
||||
status.code = ''
|
||||
}).finally(() => {
|
||||
global.lx_event.sync.status(status)
|
||||
})
|
||||
}
|
||||
|
||||
exports.getStatus = () => status
|
||||
|
||||
exports.generateCode = async() => {
|
||||
status.code = generateCode()
|
||||
global.lx_event.sync.status(status)
|
||||
return status.code
|
||||
}
|
||||
394
src/main/modules/sync/server/syncList.js
Normal file
394
src/main/modules/sync/server/syncList.js
Normal file
@@ -0,0 +1,394 @@
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const fsPromises = fs.promises
|
||||
const { app } = require('electron')
|
||||
const { encryptMsg, decryptMsg } = require('./utils')
|
||||
const SYNC_EVENT_NAMES = require('../event/name')
|
||||
const { common: COMMON_EVENT_NAME } = require('@main/events/_name')
|
||||
const { throttle } = require('@common/utils')
|
||||
|
||||
let io
|
||||
let syncingId = null
|
||||
const wait = (time = 1000) => new Promise((resolve, reject) => setTimeout(resolve, time))
|
||||
|
||||
const getRemoteListData = socket => new Promise((resolve, reject) => {
|
||||
console.log('getRemoteListData')
|
||||
const handleError = reason => {
|
||||
reject(new Error(reason))
|
||||
}
|
||||
const handleSuccess = enData => {
|
||||
socket.removeListener('disconnect', handleError)
|
||||
socket.removeListener('list:sync', handleSuccess)
|
||||
console.log('getRemoteListData', 'handleSuccess')
|
||||
const data = JSON.parse(decryptMsg(socket.data.keyInfo, enData))
|
||||
if (!data) return reject(new Error('Get remote list data failed'))
|
||||
if (data.action != 'getData') return
|
||||
resolve(data.data)
|
||||
}
|
||||
|
||||
socket.on('disconnect', handleError)
|
||||
socket.on('list:sync', handleSuccess)
|
||||
socket.emit('list:sync', encryptMsg(socket.data.keyInfo, JSON.stringify({ action: 'getData', data: 'all' })))
|
||||
})
|
||||
|
||||
const getLocalListData = () => new Promise((resolve, reject) => {
|
||||
const handleSuccess = ({ action, data }) => {
|
||||
if (action !== 'getData') return
|
||||
global.lx_event.sync.off(SYNC_EVENT_NAMES.sync_handle_list, handleSuccess)
|
||||
resolve(data)
|
||||
}
|
||||
global.lx_event.sync.on(SYNC_EVENT_NAMES.sync_handle_list, handleSuccess)
|
||||
global.lx_event.sync.sync_list({
|
||||
action: 'getData',
|
||||
})
|
||||
})
|
||||
const getSyncMode = keyInfo => new Promise((resolve, reject) => {
|
||||
const handleSuccess = ({ action, data }) => {
|
||||
if (action !== 'selectMode') return
|
||||
global.lx_event.sync.off(SYNC_EVENT_NAMES.sync_handle_list, handleSuccess)
|
||||
resolve(data)
|
||||
}
|
||||
global.lx_event.sync.on(SYNC_EVENT_NAMES.sync_handle_list, handleSuccess)
|
||||
global.lx_event.sync.sync_list({
|
||||
action: 'selectMode',
|
||||
data: keyInfo,
|
||||
})
|
||||
})
|
||||
|
||||
const finishedSync = socket => {
|
||||
return socket.emit('list:sync', encryptMsg(socket.data.keyInfo, JSON.stringify({
|
||||
action: 'finished',
|
||||
})))
|
||||
}
|
||||
|
||||
const setLocalList = listData => {
|
||||
global.lx_event.sync.sync_list({
|
||||
action: 'setData',
|
||||
data: listData,
|
||||
})
|
||||
}
|
||||
const setRemotelList = async(socket, listData) => {
|
||||
if (!io) return
|
||||
const sockets = await io.fetchSockets()
|
||||
for (const socket of sockets) {
|
||||
// if (excludeIds.includes(socket.data.keyInfo.clientId)) continue
|
||||
socket.emit('list:sync', encryptMsg(socket.data.keyInfo, JSON.stringify({ action: 'setData', data: listData })))
|
||||
}
|
||||
}
|
||||
|
||||
let writeFilePromises = {}
|
||||
const updateSnapshot = (path, data) => {
|
||||
console.log('updateSnapshot', path)
|
||||
let writeFilePromise = writeFilePromises[path] || Promise.resolve()
|
||||
return writeFilePromise.then(() => {
|
||||
writeFilePromise = writeFilePromises[path] = fsPromises.writeFile(path, data)
|
||||
return writeFilePromise
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const createListDataObj = listData => {
|
||||
const listDataObj = {}
|
||||
for (const list of listData.userList) listDataObj[list.id] = list
|
||||
return listDataObj
|
||||
}
|
||||
|
||||
const handleMergeList = (sourceList, targetList, addMusicLocationType) => {
|
||||
let newList
|
||||
switch (addMusicLocationType) {
|
||||
case 'top':
|
||||
newList = [...targetList.list, ...sourceList.list]
|
||||
break
|
||||
case 'bottom':
|
||||
default:
|
||||
newList = [...sourceList.list, ...targetList.list]
|
||||
break
|
||||
}
|
||||
const map = {}
|
||||
const ids = []
|
||||
switch (addMusicLocationType) {
|
||||
case 'top':
|
||||
newList = [...targetList.list, ...sourceList.list]
|
||||
for (let i = newList.length - 1; i > -1; i--) {
|
||||
const item = newList[i]
|
||||
if (map[item.songmid]) continue
|
||||
ids.unshift(item.songmid)
|
||||
map[item.songmid] = item
|
||||
}
|
||||
break
|
||||
case 'bottom':
|
||||
default:
|
||||
newList = [...sourceList.list, ...targetList.list]
|
||||
for (const item of newList) {
|
||||
if (map[item.songmid]) continue
|
||||
ids.push(item.songmid)
|
||||
map[item.songmid] = item
|
||||
}
|
||||
break
|
||||
}
|
||||
return {
|
||||
...sourceList,
|
||||
list: ids.map(id => map[id]),
|
||||
}
|
||||
}
|
||||
const mergeList = (sourceListData, targetListData) => {
|
||||
const addMusicLocationType = global.appSetting.list.addMusicLocationType
|
||||
const newListData = {}
|
||||
newListData.defaultList = handleMergeList(sourceListData.defaultList, targetListData.defaultList, addMusicLocationType)
|
||||
newListData.loveList = handleMergeList(sourceListData.loveList, targetListData.loveList, addMusicLocationType)
|
||||
|
||||
const listDataObj = createListDataObj(sourceListData)
|
||||
newListData.userList = [...sourceListData.userList]
|
||||
|
||||
for (const list of targetListData.userList) {
|
||||
const targetList = listDataObj[list.id]
|
||||
if (targetList) {
|
||||
targetList.list = handleMergeList(targetList, list, addMusicLocationType).list
|
||||
} else {
|
||||
newListData.userList.push(list)
|
||||
}
|
||||
}
|
||||
|
||||
return newListData
|
||||
}
|
||||
const overwriteList = (sourceListData, targetListData) => {
|
||||
const newListData = {}
|
||||
newListData.defaultList = sourceListData.defaultList
|
||||
newListData.loveList = sourceListData.loveList
|
||||
|
||||
const listDataObj = createListDataObj(sourceListData)
|
||||
newListData.userList = [...sourceListData.userList]
|
||||
|
||||
for (const list of targetListData.userList) {
|
||||
const targetList = listDataObj[list.id]
|
||||
if (targetList) continue
|
||||
newListData.userList.push(list)
|
||||
}
|
||||
|
||||
return newListData
|
||||
}
|
||||
|
||||
const handleMergeListData = async socket => {
|
||||
let isSelectingMode = false
|
||||
const handleDisconnect = () => {
|
||||
if (!isSelectingMode) return
|
||||
global.lx_event.sync.sync_list({
|
||||
action: 'closeSelectMode',
|
||||
})
|
||||
}
|
||||
socket.on('disconnect', handleDisconnect)
|
||||
isSelectingMode = true
|
||||
const mode = await getSyncMode(socket.data.keyInfo)
|
||||
isSelectingMode = false
|
||||
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
|
||||
console.log('handleMergeListData', 'remoteListData, localListData')
|
||||
let listData
|
||||
switch (mode) {
|
||||
case 'merge_local_remote':
|
||||
listData = mergeList(localListData, remoteListData)
|
||||
break
|
||||
case 'merge_remote_local':
|
||||
listData = mergeList(remoteListData, localListData)
|
||||
break
|
||||
case 'overwrite_local_remote':
|
||||
listData = overwriteList(localListData, remoteListData)
|
||||
break
|
||||
case 'overwrite_remote_local':
|
||||
listData = overwriteList(remoteListData, localListData)
|
||||
break
|
||||
case 'overwrite_local_remote_full':
|
||||
listData = localListData
|
||||
break
|
||||
case 'overwrite_remote_local_full':
|
||||
listData = remoteListData
|
||||
break
|
||||
case 'none': return
|
||||
case 'cancel':
|
||||
socket.disconnect(true)
|
||||
throw new Error('cancel')
|
||||
}
|
||||
return listData
|
||||
}
|
||||
|
||||
const handleSyncList = async socket => {
|
||||
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
|
||||
console.log('handleSyncList', 'remoteListData, localListData')
|
||||
const listData = {}
|
||||
if (localListData.defaultList.list.length || localListData.loveList.list.length || localListData.userList.length) {
|
||||
if (remoteListData.defaultList.list.length || remoteListData.loveList.list.length || remoteListData.userList.length) {
|
||||
const mergedList = await handleMergeListData(socket)
|
||||
console.log('handleMergeListData', 'mergedList')
|
||||
console.log(mergedList)
|
||||
if (!mergedList) return
|
||||
listData.defaultList = mergedList.defaultList
|
||||
listData.loveList = mergedList.loveList
|
||||
listData.userList = mergedList.userList
|
||||
setLocalList(mergedList)
|
||||
setRemotelList(socket, mergedList)
|
||||
} else {
|
||||
setRemotelList(socket, localListData)
|
||||
listData.defaultList = localListData.defaultList
|
||||
listData.loveList = localListData.loveList
|
||||
listData.userList = localListData.userList
|
||||
}
|
||||
} else {
|
||||
if (remoteListData.defaultList.list.length || remoteListData.loveList.list.length || remoteListData.userList.length) {
|
||||
setLocalList(remoteListData)
|
||||
listData.defaultList = remoteListData.defaultList
|
||||
listData.loveList = remoteListData.loveList
|
||||
listData.userList = remoteListData.userList
|
||||
} else {
|
||||
listData.defaultList = localListData.defaultList
|
||||
listData.loveList = localListData.loveList
|
||||
listData.userList = localListData.userList
|
||||
}
|
||||
}
|
||||
return updateSnapshot(socket.data.snapshotFilePath, JSON.stringify({
|
||||
defaultList: listData.defaultList,
|
||||
loveList: listData.loveList,
|
||||
userList: listData.userList,
|
||||
})).then(() => {
|
||||
socket.data.isCreatedSnapshot = true
|
||||
return listData
|
||||
})
|
||||
}
|
||||
|
||||
const mergeListDataFromSnapshot = (sourceList, targetList, snapshotList, addMusicLocationType) => {
|
||||
const removedListIds = new Set()
|
||||
const sourceListItemIds = new Set()
|
||||
const targetListItemIds = new Set()
|
||||
for (const m of sourceList.list) sourceListItemIds.add(m.songmid)
|
||||
for (const m of targetList.list) targetListItemIds.add(m.songmid)
|
||||
for (const m of snapshotList.list) {
|
||||
if (!sourceListItemIds.has(m.songmid)) removedListIds.add(m.songmid)
|
||||
}
|
||||
for (const m of snapshotList.list) {
|
||||
if (!targetListItemIds.has(m.songmid)) removedListIds.add(m.songmid)
|
||||
}
|
||||
|
||||
let newList
|
||||
const map = {}
|
||||
const ids = []
|
||||
switch (addMusicLocationType) {
|
||||
case 'top':
|
||||
newList = [...targetList.list, ...sourceList.list]
|
||||
for (let i = newList.length - 1; i > -1; i--) {
|
||||
const item = newList[i]
|
||||
if (map[item.songmid] || removedListIds.has(item.songmid)) continue
|
||||
ids.unshift(item.songmid)
|
||||
map[item.songmid] = item
|
||||
}
|
||||
break
|
||||
case 'bottom':
|
||||
default:
|
||||
newList = [...sourceList.list, ...targetList.list]
|
||||
for (const item of newList) {
|
||||
if (map[item.songmid] || removedListIds.has(item.songmid)) continue
|
||||
ids.push(item.songmid)
|
||||
map[item.songmid] = item
|
||||
}
|
||||
break
|
||||
}
|
||||
return {
|
||||
...sourceList,
|
||||
list: ids.map(id => map[id]),
|
||||
}
|
||||
}
|
||||
const handleMergeListDataFromSnapshot = async(socket, snapshot) => {
|
||||
const addMusicLocationType = global.appSetting.list.addMusicLocationType
|
||||
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
|
||||
console.log('handleMergeListDataFromSnapshot', 'remoteListData, localListData')
|
||||
const newListData = {}
|
||||
newListData.defaultList = mergeListDataFromSnapshot(localListData.defaultList, remoteListData.defaultList, snapshot.defaultList, addMusicLocationType)
|
||||
newListData.loveList = mergeListDataFromSnapshot(localListData.loveList, remoteListData.loveList, snapshot.loveList, addMusicLocationType)
|
||||
const localUserListData = createListDataObj(localListData)
|
||||
const remoteUserListData = createListDataObj(remoteListData)
|
||||
const snapshotUserListData = createListDataObj(snapshot)
|
||||
const removedListIds = new Set()
|
||||
const localUserListIds = new Set()
|
||||
const remoteUserListIds = new Set()
|
||||
for (const l of localListData.userList) localUserListIds.add(l.id)
|
||||
for (const l of remoteListData.userList) remoteUserListIds.add(l.id)
|
||||
|
||||
for (const l of snapshot.userList) {
|
||||
if (!localUserListIds.has(l.id)) removedListIds.add(l.id)
|
||||
}
|
||||
for (const l of snapshot.userList) {
|
||||
if (!remoteUserListIds.has(l.id)) removedListIds.add(l.id)
|
||||
}
|
||||
|
||||
let newUserList = []
|
||||
for (const list of localListData.userList) {
|
||||
if (removedListIds.has(list.id)) continue
|
||||
const remoteList = remoteUserListData[list.id]
|
||||
let newList
|
||||
if (remoteList) {
|
||||
newList = mergeListDataFromSnapshot(list, remoteList, snapshotUserListData[list.id], addMusicLocationType)
|
||||
} else {
|
||||
newList = { ...list }
|
||||
}
|
||||
newUserList.push(newList)
|
||||
}
|
||||
for (const list of remoteListData.userList) {
|
||||
if (removedListIds.has(list.id) || localUserListData[list.id]) continue
|
||||
newUserList.push({ ...list })
|
||||
}
|
||||
newListData.userList = newUserList
|
||||
setLocalList(newListData)
|
||||
setRemotelList(socket, newListData)
|
||||
return updateSnapshot(socket.data.snapshotFilePath, JSON.stringify({
|
||||
defaultList: newListData.defaultList,
|
||||
loveList: newListData.loveList,
|
||||
userList: newListData.userList,
|
||||
})).then(() => {
|
||||
socket.data.isCreatedSnapshot = true
|
||||
return newListData
|
||||
})
|
||||
}
|
||||
|
||||
const registerUpdateSnapshotTask = (socket, snapshot) => {
|
||||
if (!socket.data.isCreatedSnapshot) return
|
||||
const handleUpdateSnapshot = throttle(({ defaultList, loveList, userList }) => {
|
||||
if (defaultList != null) snapshot.defaultList = defaultList
|
||||
if (loveList != null) snapshot.loveList = loveList
|
||||
if (userList != null) snapshot.userList = userList
|
||||
updateSnapshot(socket.data.snapshotFilePath, JSON.stringify(snapshot))
|
||||
}, 10000)
|
||||
global.lx_event.common.on(COMMON_EVENT_NAME.saveMyList, handleUpdateSnapshot)
|
||||
socket.on('disconnect', () => {
|
||||
global.lx_event.common.off(COMMON_EVENT_NAME.saveMyList, handleUpdateSnapshot)
|
||||
})
|
||||
}
|
||||
|
||||
const syncList = async socket => {
|
||||
socket.data.snapshotFilePath = path.join(app.getPath('userData'), `snapshot-${Buffer.from(socket.data.keyInfo.clientId).toString('hex').substring(0, 10)}.json`)
|
||||
let fileData
|
||||
let isSyncRequired = false
|
||||
try {
|
||||
fileData = await fsPromises.readFile(socket.data.snapshotFilePath)
|
||||
fileData = JSON.parse(fileData)
|
||||
} catch (err) {
|
||||
if (err.code !== 'ENOENT') throw err
|
||||
isSyncRequired = true
|
||||
}
|
||||
console.log('isSyncRequired', isSyncRequired)
|
||||
if (isSyncRequired) return handleSyncList(socket)
|
||||
return handleMergeListDataFromSnapshot(socket, fileData)
|
||||
}
|
||||
|
||||
const checkSyncQueue = async() => {
|
||||
if (!syncingId) return
|
||||
await wait()
|
||||
return checkSyncQueue()
|
||||
}
|
||||
module.exports = async(_io, socket) => {
|
||||
io = _io
|
||||
await checkSyncQueue()
|
||||
syncingId = socket.data.keyInfo.clientId
|
||||
return syncList(socket).then(newListData => {
|
||||
registerUpdateSnapshotTask(socket, { ...newListData })
|
||||
return finishedSync(socket)
|
||||
}).finally(() => {
|
||||
syncingId = null
|
||||
})
|
||||
}
|
||||
93
src/main/modules/sync/server/utils.js
Normal file
93
src/main/modules/sync/server/utils.js
Normal file
@@ -0,0 +1,93 @@
|
||||
const { networkInterfaces } = require('os')
|
||||
const { randomBytes, createCipheriv, createDecipheriv } = require('crypto')
|
||||
const getStore = require('@common/store')
|
||||
const STORE_NAME = 'sync'
|
||||
|
||||
exports.getAddress = () => {
|
||||
const nets = networkInterfaces()
|
||||
const results = []
|
||||
// console.log(nets)
|
||||
|
||||
for (const name of Object.keys(nets)) {
|
||||
for (const net of nets[name]) {
|
||||
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
|
||||
if (net.family === 'IPv4' && !net.internal) {
|
||||
results.push(net.address)
|
||||
}
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
let serverId
|
||||
exports.getServerId = () => {
|
||||
if (serverId) return serverId
|
||||
const store = getStore(STORE_NAME)
|
||||
serverId = store.get('serverId')
|
||||
if (!serverId) {
|
||||
serverId = randomBytes(4 * 4).toString('base64')
|
||||
store.set('serverId', serverId)
|
||||
}
|
||||
return serverId
|
||||
}
|
||||
|
||||
let keyInfos
|
||||
exports.createClientKeyInfo = deviceName => {
|
||||
const keyInfo = {
|
||||
clientId: randomBytes(4 * 4).toString('base64'),
|
||||
key: randomBytes(16).toString('base64'),
|
||||
iv: randomBytes(16).toString('base64'),
|
||||
deviceName,
|
||||
}
|
||||
const store = getStore(STORE_NAME)
|
||||
if (!keyInfos) keyInfos = store.get('keys') || {}
|
||||
if (Object.keys(keyInfos).length > 101) throw new Error('max keys')
|
||||
|
||||
keyInfos[keyInfo.clientId] = keyInfo
|
||||
store.set('keys', keyInfos)
|
||||
return keyInfo
|
||||
}
|
||||
exports.setClientKeyInfo = keyInfo => {
|
||||
keyInfos[keyInfo.clientId] = keyInfo
|
||||
const store = getStore(STORE_NAME)
|
||||
store.set('keys', keyInfos)
|
||||
}
|
||||
exports.getClientKeyInfo = clientId => {
|
||||
if (!keyInfos) {
|
||||
const store = getStore(STORE_NAME)
|
||||
keyInfos = store.get('keys') || {}
|
||||
}
|
||||
return keyInfos[clientId] || null
|
||||
}
|
||||
|
||||
exports.generateCode = () => {
|
||||
return Math.random().toString().substring(2, 8)
|
||||
}
|
||||
|
||||
exports.aesEncrypt = (buffer, key, iv) => {
|
||||
const cipher = createCipheriv('aes-128-cbc', Buffer.from(key, 'base64'), Buffer.from(iv, 'base64'))
|
||||
return Buffer.concat([cipher.update(buffer), cipher.final()]).toString('base64')
|
||||
}
|
||||
|
||||
exports.aesDecrypt = (text, key, iv) => {
|
||||
const decipher = createDecipheriv('aes-128-cbc', Buffer.from(key, 'base64'), Buffer.from(iv, 'base64'))
|
||||
return Buffer.concat([decipher.update(Buffer.from(text, 'base64')), decipher.final()]).toString()
|
||||
}
|
||||
|
||||
exports.encryptMsg = (keyInfo, msg) => {
|
||||
return msg
|
||||
// if (!keyInfo) return ''
|
||||
// return exports.aesEncrypt(msg, keyInfo.key, keyInfo.iv)
|
||||
}
|
||||
|
||||
exports.decryptMsg = (keyInfo, enMsg) => {
|
||||
return enMsg
|
||||
// if (!keyInfo) return ''
|
||||
// let msg = ''
|
||||
// try {
|
||||
// msg = exports.aesDecrypt(enMsg, keyInfo.key, keyInfo.iv)
|
||||
// } catch (err) {
|
||||
// console.log(err)
|
||||
// }
|
||||
// return msg
|
||||
}
|
||||
@@ -70,55 +70,63 @@ const destroyTray = () => {
|
||||
const createMenu = tray => {
|
||||
if (!global.modules.tray) return
|
||||
let menu = []
|
||||
global.modules.mainWindow && menu.push(global.modules.mainWindow.isVisible() ? {
|
||||
label: '隐藏主界面',
|
||||
click() {
|
||||
global.modules.mainWindow.hide()
|
||||
},
|
||||
} : {
|
||||
label: '显示主界面',
|
||||
click() {
|
||||
if (!global.modules.mainWindow) return
|
||||
if (!global.modules.mainWindow.isVisible()) {
|
||||
global.modules.mainWindow.show()
|
||||
global.modules.mainWindow && menu.push(global.modules.mainWindow.isVisible()
|
||||
? {
|
||||
label: '隐藏主界面',
|
||||
click() {
|
||||
global.modules.mainWindow.hide()
|
||||
},
|
||||
}
|
||||
global.modules.mainWindow.restore()
|
||||
global.modules.mainWindow.focus()
|
||||
},
|
||||
})
|
||||
menu.push(global.appSetting.desktopLyric.enable ? {
|
||||
label: '关闭桌面歌词',
|
||||
click() {
|
||||
global.lx_core.setAppConfig({ desktopLyric: { enable: false } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
} : {
|
||||
label: '开启桌面歌词',
|
||||
click() {
|
||||
global.lx_core.setAppConfig({ desktopLyric: { enable: true } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
})
|
||||
menu.push(global.appSetting.desktopLyric.isLock ? {
|
||||
label: '解锁桌面歌词',
|
||||
click() {
|
||||
global.lx_core.setAppConfig({ desktopLyric: { isLock: false } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
} : {
|
||||
label: '锁定桌面歌词',
|
||||
click() {
|
||||
global.lx_core.setAppConfig({ desktopLyric: { isLock: true } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
})
|
||||
menu.push(global.appSetting.desktopLyric.isAlwaysOnTop ? {
|
||||
label: '取消置顶',
|
||||
click() {
|
||||
global.lx_core.setAppConfig({ desktopLyric: { isAlwaysOnTop: false } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
} : {
|
||||
label: '置顶歌词',
|
||||
click() {
|
||||
global.lx_core.setAppConfig({ desktopLyric: { isAlwaysOnTop: true } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
})
|
||||
: {
|
||||
label: '显示主界面',
|
||||
click() {
|
||||
if (!global.modules.mainWindow) return
|
||||
if (!global.modules.mainWindow.isVisible()) {
|
||||
global.modules.mainWindow.show()
|
||||
}
|
||||
global.modules.mainWindow.restore()
|
||||
global.modules.mainWindow.focus()
|
||||
},
|
||||
})
|
||||
menu.push(global.appSetting.desktopLyric.enable
|
||||
? {
|
||||
label: '关闭桌面歌词',
|
||||
click() {
|
||||
global.lx_core.setAppConfig({ desktopLyric: { enable: false } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
}
|
||||
: {
|
||||
label: '开启桌面歌词',
|
||||
click() {
|
||||
global.lx_core.setAppConfig({ desktopLyric: { enable: true } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
})
|
||||
menu.push(global.appSetting.desktopLyric.isLock
|
||||
? {
|
||||
label: '解锁桌面歌词',
|
||||
click() {
|
||||
global.lx_core.setAppConfig({ desktopLyric: { isLock: false } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
}
|
||||
: {
|
||||
label: '锁定桌面歌词',
|
||||
click() {
|
||||
global.lx_core.setAppConfig({ desktopLyric: { isLock: true } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
})
|
||||
menu.push(global.appSetting.desktopLyric.isAlwaysOnTop
|
||||
? {
|
||||
label: '取消置顶',
|
||||
click() {
|
||||
global.lx_core.setAppConfig({ desktopLyric: { isAlwaysOnTop: false } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
}
|
||||
: {
|
||||
label: '置顶歌词',
|
||||
click() {
|
||||
global.lx_core.setAppConfig({ desktopLyric: { isAlwaysOnTop: true } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
})
|
||||
menu.push({
|
||||
label: '退出',
|
||||
click() {
|
||||
|
||||
@@ -47,8 +47,16 @@ exports.createWindow = async userApi => {
|
||||
show: false,
|
||||
webPreferences: {
|
||||
contextIsolation: true,
|
||||
worldSafeExecuteJavaScript: true,
|
||||
// worldSafeExecuteJavaScript: true,
|
||||
nodeIntegration: false,
|
||||
|
||||
spellcheck: false,
|
||||
autoplayPolicy: 'document-user-activation-required',
|
||||
enableWebSQL: false,
|
||||
disableDialogs: true,
|
||||
webgl: false,
|
||||
images: false,
|
||||
|
||||
preload: path.join(dir, 'renderer/preload.js'),
|
||||
},
|
||||
})
|
||||
@@ -59,6 +67,7 @@ exports.createWindow = async userApi => {
|
||||
})
|
||||
}
|
||||
global.modules.userApiWindow.webContents.session.setPermissionRequestHandler((webContents, permission, callback) => {
|
||||
// eslint-disable-next-line node/no-callback-literal
|
||||
callback(false)
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const path = require('path')
|
||||
const { BrowserWindow } = require('electron')
|
||||
const { winLyric: WIN_LYRIC_EVENT_NAME } = require('../../events/_name')
|
||||
const { debounce } = require('../../../common/utils')
|
||||
const { debounce, isLinux } = require('../../../common/utils')
|
||||
const { getLyricWindowBounds } = require('./utils')
|
||||
|
||||
require('./event')
|
||||
@@ -66,6 +66,10 @@ const winEvent = lyricWindow => {
|
||||
if (global.appSetting.desktopLyric.isLock) {
|
||||
global.modules.lyricWindow.setIgnoreMouseEvents(true, { forward: false })
|
||||
}
|
||||
// linux下每次重开时貌似要重新设置置顶
|
||||
if (isLinux && global.appSetting.desktopLyric.isAlwaysOnTop) {
|
||||
global.modules.lyricWindow.setAlwaysOnTop(global.appSetting.desktopLyric.isAlwaysOnTop, 'screen-saver')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,11 @@ require('./playList')
|
||||
require('./data')
|
||||
require('./lyric')
|
||||
require('./musicUrl')
|
||||
require('./systemFonts')
|
||||
|
||||
require('./kw_decodeLyric')
|
||||
|
||||
require('./userApi')
|
||||
require('./sync')
|
||||
require('./s2t')
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ mainOn(ipcMainWindowNames.save_playlist, (event, { type, data }) => {
|
||||
switch (type) {
|
||||
case 'myList':
|
||||
handleSaveList(data)
|
||||
global.lx_event.common.saveMyList(data)
|
||||
break
|
||||
case 'downloadList':
|
||||
getStore('downloadList').set('list', data)
|
||||
|
||||
9
src/main/rendererEvents/s2t.js
Normal file
9
src/main/rendererEvents/s2t.js
Normal file
@@ -0,0 +1,9 @@
|
||||
const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
|
||||
const { tranditionalize } = require('../utils/simplify-chinese-main')
|
||||
|
||||
|
||||
mainHandle(ipcMainWindowNames.lang_s2t, async(event, textBase64) => {
|
||||
if (!global.modules.mainWindow) throw new Error('mainWindow is undefined')
|
||||
const text = tranditionalize(Buffer.from(textBase64, 'base64').toString())
|
||||
return Buffer.from(text).toString('base64')
|
||||
})
|
||||
33
src/main/rendererEvents/sync.js
Normal file
33
src/main/rendererEvents/sync.js
Normal file
@@ -0,0 +1,33 @@
|
||||
const { mainSend, NAMES: { mainWindow: ipcMainWindowNames }, mainOn, mainHandle } = require('@common/ipc')
|
||||
const { eventNames, modules, startServer, stopServer, getStatus, generateCode } = require('../modules/sync')
|
||||
|
||||
|
||||
mainOn(ipcMainWindowNames.sync_action_list, (event, { action, data }) => {
|
||||
modules.list.sendListAction(action, data)
|
||||
})
|
||||
|
||||
mainHandle(ipcMainWindowNames.sync_enable, (event, { enable, port }) => {
|
||||
return enable ? startServer(port) : stopServer()
|
||||
})
|
||||
|
||||
mainHandle(ipcMainWindowNames.sync_get_status, () => {
|
||||
return getStatus()
|
||||
})
|
||||
|
||||
mainHandle(ipcMainWindowNames.sync_generate_code, () => {
|
||||
return generateCode()
|
||||
})
|
||||
|
||||
mainOn(ipcMainWindowNames.sync_list, (event, { action, data }) => {
|
||||
global.lx_event.sync.sync_handle_list({ action, data })
|
||||
})
|
||||
|
||||
global.lx_event.sync.on(eventNames.sync_action_list, ({ action, data }) => {
|
||||
mainSend(global.modules.mainWindow, ipcMainWindowNames.sync_action_list, { action, data })
|
||||
})
|
||||
global.lx_event.sync.on(eventNames.status, status => {
|
||||
mainSend(global.modules.mainWindow, ipcMainWindowNames.sync_status, status)
|
||||
})
|
||||
global.lx_event.sync.on(eventNames.sync_list, ({ action, data }) => {
|
||||
mainSend(global.modules.mainWindow, ipcMainWindowNames.sync_list, { action, data })
|
||||
})
|
||||
5
src/main/rendererEvents/systemFonts.js
Normal file
5
src/main/rendererEvents/systemFonts.js
Normal file
@@ -0,0 +1,5 @@
|
||||
const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('@common/ipc')
|
||||
const { getFonts } = require('@main/utils/fontManage')
|
||||
|
||||
mainHandle(ipcMainWindowNames.get_system_fonts, getFonts)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { log } = require('../../common/utils')
|
||||
const { log, isWin } = require('../../common/utils')
|
||||
const { autoUpdater } = require('electron-updater')
|
||||
const { mainOn, mainSend, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
|
||||
|
||||
@@ -120,6 +120,11 @@ module.exports = () => {
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
autoUpdater.checkForUpdates()
|
||||
// 由于集合安装包中不包含win arm版,这将会导致arm版更新失败
|
||||
if (isWin && process.arch.includes('arm')) {
|
||||
handleSendEvent({ type: ipcMainWindowNames.update_error, info: 'failed' })
|
||||
} else {
|
||||
autoUpdater.checkForUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
9
src/main/utils/fontManage.js
Normal file
9
src/main/utils/fontManage.js
Normal file
@@ -0,0 +1,9 @@
|
||||
// const { getAvailableFontFamilies } = require('electron-font-manager')
|
||||
|
||||
|
||||
// exports.getAvailableFontFamilies = getAvailableFontFamilies
|
||||
|
||||
|
||||
const fontList = require('font-list')
|
||||
|
||||
exports.getFonts = fontList.getFonts
|
||||
18
src/main/utils/simplify-chinese-main/.gitignore
vendored
Normal file
18
src/main/utils/simplify-chinese-main/.gitignore
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
todo.md
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
yarn-debug.log
|
||||
yarn-error.log
|
||||
package-lock.json
|
||||
tsconfig.tsbuildinfo
|
||||
report.*.json
|
||||
|
||||
.eslintcache
|
||||
.DS_Store
|
||||
.idea
|
||||
.vscode
|
||||
.yarn
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
22
src/main/utils/simplify-chinese-main/LICENSE.md
Normal file
22
src/main/utils/simplify-chinese-main/LICENSE.md
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2021 Shigma
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
10
src/main/utils/simplify-chinese-main/README.md
Normal file
10
src/main/utils/simplify-chinese-main/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# simplify-chinese
|
||||
|
||||
Convert chinese characters between simplified form and tranditional form / 汉字简繁体转换工具。
|
||||
|
||||
```js
|
||||
const { simplify, tranditionalize } = require('simplify-chinese')
|
||||
|
||||
console.log(simplify('窩窩頭')) // 窝窝头
|
||||
console.log(tranditionalize('窝窝头')) // 窩窩頭
|
||||
```
|
||||
2
src/main/utils/simplify-chinese-main/chinese.js
Normal file
2
src/main/utils/simplify-chinese-main/chinese.js
Normal file
File diff suppressed because one or more lines are too long
2
src/main/utils/simplify-chinese-main/index.d.ts
vendored
Normal file
2
src/main/utils/simplify-chinese-main/index.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export function simplify(source: string): string
|
||||
export function tranditionalize(source: string): string
|
||||
30
src/main/utils/simplify-chinese-main/index.js
Normal file
30
src/main/utils/simplify-chinese-main/index.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const { simplified, traditional } = require('./chinese')
|
||||
|
||||
const stMap = new Map()
|
||||
const tsMap = new Map()
|
||||
|
||||
simplified.split('').forEach((char, index) => {
|
||||
stMap.set(char, traditional[index])
|
||||
tsMap.set(traditional[index], char)
|
||||
})
|
||||
|
||||
function simplify(source) {
|
||||
let result = []
|
||||
for (const char of source) {
|
||||
result.push(tsMap.get(char) || char)
|
||||
}
|
||||
return result.join('')
|
||||
}
|
||||
|
||||
function tranditionalize(source) {
|
||||
let result = []
|
||||
for (const char of source) {
|
||||
result.push(stMap.get(char) || char)
|
||||
}
|
||||
return result.join('')
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
simplify,
|
||||
tranditionalize,
|
||||
}
|
||||
10
src/main/utils/simplify-chinese-main/package.json
Normal file
10
src/main/utils/simplify-chinese-main/package.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "simplify-chinese",
|
||||
"description": "Convert chinese characters between simplified form and tranditional form 汉字简繁体转换工具",
|
||||
"version": "1.1.0",
|
||||
"main": "index.js",
|
||||
"typings": "index.d.ts",
|
||||
"repository": "https://github.com/koishijs/simplify-chinese.git",
|
||||
"author": "Shigma <1700011071@pku.edu.cn>",
|
||||
"license": "MIT"
|
||||
}
|
||||
@@ -39,6 +39,7 @@ export default {
|
||||
y: -1,
|
||||
theme: 0,
|
||||
style: {
|
||||
font: '',
|
||||
fontSize: 125,
|
||||
opacity: 80,
|
||||
isZoomActiveLrc: true,
|
||||
|
||||
@@ -21,6 +21,7 @@ export default {
|
||||
default() {
|
||||
return {
|
||||
style: {
|
||||
font: '',
|
||||
fontSize: 125,
|
||||
opacity: 80,
|
||||
isZoomActiveLrc: true,
|
||||
@@ -77,6 +78,7 @@ export default {
|
||||
computed: {
|
||||
lrcStyles() {
|
||||
return {
|
||||
fontFamily: this.lrcConfig.style.font,
|
||||
fontSize: this.lrcConfig.style.fontSize / 100 + 'rem',
|
||||
opacity: this.lrcConfig.style.opacity / 100,
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
core-icons
|
||||
material-version-modal(v-show="version.showModal")
|
||||
material-pact-modal(v-show="!setting.isAgreePact || globalObj.isShowPact")
|
||||
material-sync-mode-modal(v-show="globalObj.sync.isShowSyncMode")
|
||||
#container(v-else :class="theme")
|
||||
core-aside#left
|
||||
#right
|
||||
@@ -17,24 +18,19 @@
|
||||
core-icons
|
||||
material-version-modal(v-show="version.showModal")
|
||||
material-pact-modal(v-show="!setting.isAgreePact || globalObj.isShowPact")
|
||||
material-sync-mode-modal(v-show="globalObj.sync.isShowSyncMode")
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dnscache from 'dnscache'
|
||||
import { mapMutations, mapGetters, mapActions } from 'vuex'
|
||||
import { rendererOn, rendererSend, rendererInvoke, NAMES } from '../common/ipc'
|
||||
import { isLinux } from '../common/utils'
|
||||
import music from './utils/music'
|
||||
import { throttle, openUrl, compareVer, getPlayList, parseUrlParams, saveSetting } from './utils'
|
||||
import { base as eventBaseName } from './event/names'
|
||||
import { base as eventBaseName, sync as eventSyncName } from './event/names'
|
||||
import apiSourceInfo from './utils/music/api-source-info'
|
||||
|
||||
window.ELECTRON_DISABLE_SECURITY_WARNINGS = process.env.ELECTRON_DISABLE_SECURITY_WARNINGS
|
||||
dnscache({
|
||||
enable: true,
|
||||
ttl: 21600,
|
||||
cachesize: 1000,
|
||||
})
|
||||
|
||||
const listThrottle = (fn, delay = 100) => {
|
||||
let timer = null
|
||||
@@ -67,6 +63,11 @@ export default {
|
||||
message: 'initing',
|
||||
apis: {},
|
||||
},
|
||||
sync: {
|
||||
enable: false,
|
||||
isShowSyncMode: false,
|
||||
deviceName: '',
|
||||
},
|
||||
},
|
||||
updateTimeout: null,
|
||||
envParams: {
|
||||
@@ -109,11 +110,20 @@ export default {
|
||||
document.body.classList.add(this.isDT ? 'disableTransparent' : 'transparent')
|
||||
window.eventHub.$emit(eventBaseName.bindKey)
|
||||
this.init()
|
||||
window.eventHub.$on(eventSyncName.handle_action_list, this.handleSyncAction)
|
||||
window.eventHub.$on(eventSyncName.handle_sync_list, this.handleSyncList)
|
||||
if (this.setting.sync.enable && this.setting.sync.port) {
|
||||
rendererInvoke(NAMES.mainWindow.sync_enable, {
|
||||
enable: this.setting.sync.enable,
|
||||
port: this.setting.sync.port,
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
setting: {
|
||||
handler(n, o) {
|
||||
saveSetting(n)
|
||||
global.appSetting = n
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
@@ -199,7 +209,25 @@ export default {
|
||||
methods: {
|
||||
...mapActions(['getVersionInfo']),
|
||||
...mapMutations(['setNewVersion', 'setVersionModalVisible', 'setDownloadProgress', 'setSetting', 'setDesktopLyricConfig']),
|
||||
...mapMutations('list', ['initList']),
|
||||
...mapMutations('list', {
|
||||
list_initList: 'initList',
|
||||
list_setList: 'setList',
|
||||
list_listAdd: 'listAdd',
|
||||
list_listMove: 'listMove',
|
||||
list_listAddMultiple: 'listAddMultiple',
|
||||
list_listMoveMultiple: 'listMoveMultiple',
|
||||
list_listRemove: 'listRemove',
|
||||
list_listRemoveMultiple: 'listRemoveMultiple',
|
||||
list_listClear: 'listClear',
|
||||
list_updateMusicInfo: 'updateMusicInfo',
|
||||
list_createUserList: 'createUserList',
|
||||
list_removeUserList: 'removeUserList',
|
||||
list_setUserListName: 'setUserListName',
|
||||
list_moveupUserList: 'moveupUserList',
|
||||
list_movedownUserList: 'movedownUserList',
|
||||
list_setMusicPosition: 'setMusicPosition',
|
||||
list_setSyncListData: 'setSyncListData',
|
||||
}),
|
||||
...mapMutations('download', ['updateDownloadList']),
|
||||
...mapMutations('search', {
|
||||
setSearchHistoryList: 'setHistory',
|
||||
@@ -260,7 +288,7 @@ export default {
|
||||
})
|
||||
|
||||
rendererOn(NAMES.mainWindow.set_config, (event, config) => {
|
||||
console.log(config)
|
||||
// console.log(config)
|
||||
// this.setDesktopLyricConfig(config)
|
||||
// console.log('set_config', JSON.stringify(this.setting) === JSON.stringify(config))
|
||||
this.setSetting(Object.assign({}, this.setting, config))
|
||||
@@ -278,6 +306,7 @@ export default {
|
||||
this.listenEvent()
|
||||
asyncTask.push(this.initData())
|
||||
asyncTask.push(this.initUserApi())
|
||||
this.globalObj.sync.enable = this.setting.sync.enable
|
||||
this.globalObj.apiSource = this.setting.apiSource
|
||||
if (/^user_api/.test(this.setting.apiSource)) {
|
||||
rendererInvoke(NAMES.mainWindow.set_user_api, this.setting.apiSource)
|
||||
@@ -334,7 +363,7 @@ export default {
|
||||
|
||||
if (!defaultList.list) defaultList.list = []
|
||||
if (!loveList.list) loveList.list = []
|
||||
this.initList({ defaultList, loveList, userList })
|
||||
this.list_initList({ defaultList, loveList, userList })
|
||||
this.initDownloadList(downloadList) // 初始化下载列表
|
||||
this.initPlayInfo()
|
||||
})
|
||||
@@ -596,6 +625,84 @@ export default {
|
||||
event.target.value = ''
|
||||
event.target.blur()
|
||||
},
|
||||
handleSyncAction({ action, data }) {
|
||||
if (typeof data == 'object') data.isSync = true
|
||||
// console.log(action, data)
|
||||
|
||||
switch (action) {
|
||||
case 'set_list':
|
||||
this.list_setList(data)
|
||||
break
|
||||
case 'list_add':
|
||||
this.list_listAdd(data)
|
||||
break
|
||||
case 'list_move':
|
||||
this.list_listMove(data)
|
||||
break
|
||||
case 'list_add_multiple':
|
||||
this.list_listAddMultiple(data)
|
||||
break
|
||||
case 'list_move_multiple':
|
||||
this.list_listMoveMultiple(data)
|
||||
break
|
||||
case 'list_remove':
|
||||
this.list_listRemove(data)
|
||||
break
|
||||
case 'list_remove_multiple':
|
||||
this.list_listRemoveMultiple(data)
|
||||
break
|
||||
case 'list_clear':
|
||||
this.list_listClear(data)
|
||||
break
|
||||
case 'update_music_info':
|
||||
this.list_updateMusicInfo(data)
|
||||
break
|
||||
case 'create_user_list':
|
||||
this.list_createUserList(data)
|
||||
break
|
||||
case 'remove_user_list':
|
||||
this.list_removeUserList(data)
|
||||
break
|
||||
case 'set_user_list_name':
|
||||
this.list_setUserListName(data)
|
||||
break
|
||||
case 'moveup_user_list':
|
||||
this.list_moveupUserList(data)
|
||||
break
|
||||
case 'movedown_user_list':
|
||||
this.list_movedownUserList(data)
|
||||
break
|
||||
case 'set_music_position':
|
||||
this.list_setMusicPosition(data)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
},
|
||||
handleSyncList({ action, data }) {
|
||||
switch (action) {
|
||||
case 'getData':
|
||||
global.eventHub.$emit(eventSyncName.send_sync_list, {
|
||||
action: 'getData',
|
||||
data: {
|
||||
defaultList: this.defaultList,
|
||||
loveList: this.loveList,
|
||||
userList: this.userList,
|
||||
},
|
||||
})
|
||||
break
|
||||
case 'setData':
|
||||
this.list_setSyncListData(data)
|
||||
break
|
||||
case 'selectMode':
|
||||
this.globalObj.sync.deviceName = data.deviceName
|
||||
this.globalObj.sync.isShowSyncMode = true
|
||||
break
|
||||
case 'closeSelectMode':
|
||||
this.globalObj.sync.isShowSyncMode = false
|
||||
break
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.clearUpdateTimeout()
|
||||
@@ -683,7 +790,7 @@ body {
|
||||
}
|
||||
#view {
|
||||
flex: auto;
|
||||
height: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
|
||||
@@ -123,6 +123,9 @@ small {
|
||||
.small {
|
||||
font-size: .9em;
|
||||
}
|
||||
.tip {
|
||||
color: @color-theme_2-font-label;
|
||||
}
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -192,6 +195,9 @@ each(@themes, {
|
||||
button, input, textarea, a {
|
||||
color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
.tip {
|
||||
color: ~'@{color-@{value}-theme_2-font-label}';
|
||||
}
|
||||
|
||||
.hover, a {
|
||||
&:hover {
|
||||
|
||||
@@ -217,5 +217,10 @@ svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/19
|
||||
g#icon-comment(fill='currentColor')
|
||||
// 0 0 24 24
|
||||
path(d='M16 11H8V9H16V11M22 4V16C22 17.11 21.11 18 20 18H13.9L10.2 21.71C10 21.9 9.75 22 9.5 22H9C8.45 22 8 21.55 8 21V18H4C2.9 18 2 17.11 2 16V4C2 2.89 2.9 2 4 2H20C21.11 2 22 2.9 22 4M20 4H4V16H10V19.08L13.08 16H20V4')
|
||||
|
||||
g#icon-text(fill='currentColor')
|
||||
// 0 0 24 24
|
||||
path(fill='currentColor', d='M21,6V8H3V6H21M3,18H12V16H3V18M3,13H21V11H3V13Z')
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ div(:class="$style.player")
|
||||
|
||||
<script>
|
||||
import Lyric from '@renderer/utils/lyric-font-player'
|
||||
import { rendererSend, rendererOn, NAMES } from '../../../common/ipc'
|
||||
import { rendererSend, rendererOn, NAMES, rendererInvoke } from '../../../common/ipc'
|
||||
import { formatPlayTime2, getRandom, checkPath, setTitle, clipboardWriteText, debounce, throttle, assertApiSupport } from '../../utils'
|
||||
import { mapGetters, mapActions, mapMutations } from 'vuex'
|
||||
import { requestMsg } from '../../utils/message'
|
||||
@@ -236,6 +236,7 @@ export default {
|
||||
break
|
||||
}
|
||||
})
|
||||
this.registerMediaSessionHandler()
|
||||
navigator.mediaDevices.addEventListener('devicechange', this.handleMediaListChange)
|
||||
document.addEventListener('mousemove', this.handleVolumeMsMove)
|
||||
document.addEventListener('mouseup', this.handleVolumeMsUp)
|
||||
@@ -255,6 +256,8 @@ export default {
|
||||
if (window.restorePlayInfo) {
|
||||
this.handleRestorePlay(window.restorePlayInfo)
|
||||
window.restorePlayInfo = null
|
||||
navigator.mediaSession.playbackState = 'paused'
|
||||
this.updateMediaSessionInfo()
|
||||
return
|
||||
}
|
||||
// console.log('changePlay')
|
||||
@@ -265,7 +268,7 @@ export default {
|
||||
'setting.player.togglePlayMethod'(n) {
|
||||
audio.loop = n === 'singleLoop'
|
||||
if (this.playedList.length) this.clearPlayedList()
|
||||
if (n == 'random') this.setPlayedList(this.playMusicInfo)
|
||||
if (n == 'random' && this.playMusicInfo && !this.playMusicInfo.isTempPlay) this.setPlayedList(this.playMusicInfo)
|
||||
},
|
||||
'setting.player.isMute'(n) {
|
||||
audio.muted = n
|
||||
@@ -411,7 +414,7 @@ export default {
|
||||
this.restorePlayTime = 0
|
||||
}
|
||||
if (!this.targetSong.interval && this.listId != 'download') {
|
||||
this.updateMusicInfo({ id: this.listId, index: this.playIndex, data: { interval: formatPlayTime2(this.maxPlayTime) }, musicInfo: this.targetSong })
|
||||
this.updateMusicInfo({ listId: this.listId, id: this.targetSong.songmid, musicInfo: this.targetSong, data: { interval: formatPlayTime2(this.maxPlayTime) } })
|
||||
}
|
||||
})
|
||||
audio.addEventListener('loadstart', () => {
|
||||
@@ -480,10 +483,11 @@ export default {
|
||||
},
|
||||
async play() {
|
||||
this.clearDelayNextTimeout()
|
||||
this.updateMediaSessionInfo()
|
||||
|
||||
const targetSong = this.targetSong
|
||||
|
||||
if (this.setting.player.togglePlayMethod == 'random') this.setPlayedList(this.playMusicInfo)
|
||||
if (this.setting.player.togglePlayMethod == 'random' && !this.playMusicInfo.isTempPlay) this.setPlayedList(this.playMusicInfo)
|
||||
this.retryNum = 0
|
||||
this.restorePlayTime = 0
|
||||
|
||||
@@ -551,20 +555,28 @@ export default {
|
||||
this.handleUpdateWinLyricInfo('play', audio.currentTime * 1000)
|
||||
this.setAppTitle()
|
||||
this.sendProgressEvent(this.progress, 'normal')
|
||||
navigator.mediaSession.playbackState = 'playing'
|
||||
},
|
||||
stopPlay() {
|
||||
this.isPlay = false
|
||||
window.lrc.pause()
|
||||
this.handleUpdateWinLyricInfo('pause')
|
||||
this.sendProgressEvent(this.progress, 'paused')
|
||||
this.clearAppTitle()
|
||||
this.$nextTick(() => {
|
||||
if (this.playMusicInfo) {
|
||||
this.sendProgressEvent(this.progress, 'paused')
|
||||
navigator.mediaSession.playbackState = 'paused'
|
||||
} else {
|
||||
this.sendProgressEvent(this.progress, 'none')
|
||||
navigator.mediaSession.playbackState = 'none'
|
||||
}
|
||||
})
|
||||
},
|
||||
handleSetProgress(event) {
|
||||
this.setProgress(event.offsetX / this.pregessWidth)
|
||||
this.setProgress(event.offsetX / this.pregessWidth * this.maxPlayTime)
|
||||
},
|
||||
setProgress(pregress) {
|
||||
setProgress(time) {
|
||||
if (!audio.src) return
|
||||
const time = pregress * this.maxPlayTime
|
||||
if (this.restorePlayTime) this.restorePlayTime = time
|
||||
if (this.mediaBuffer.playTime) {
|
||||
this.clearBufferTimeout()
|
||||
@@ -634,16 +646,36 @@ export default {
|
||||
|
||||
if (!this.musicInfo.img) {
|
||||
this.getPic(targetSong).then(() => {
|
||||
if (targetSong.songmid !== this.musicInfo.songmid) return
|
||||
this.musicInfo.img = targetSong.img
|
||||
this.updateMediaSessionInfo()
|
||||
})
|
||||
}
|
||||
},
|
||||
setLrc(targetSong) {
|
||||
this.getLrc(targetSong).then(({ lyric, tlyric, lxlyric }) => {
|
||||
this.musicInfo.lrc = lyric
|
||||
this.musicInfo.tlrc = tlyric
|
||||
this.musicInfo.lxlrc = lxlyric
|
||||
}).catch(() => {
|
||||
if (targetSong.songmid !== this.musicInfo.songmid) return
|
||||
return (
|
||||
global.i18n.locale == 'zh-tw'
|
||||
? Promise.all([
|
||||
lyric
|
||||
? rendererInvoke(NAMES.mainWindow.lang_s2t, Buffer.from(lyric).toString('base64')).then(b64 => Buffer.from(b64, 'base64').toString())
|
||||
: Promise.resolve(''),
|
||||
tlyric
|
||||
? rendererInvoke(NAMES.mainWindow.lang_s2t, Buffer.from(tlyric).toString('base64')).then(b64 => Buffer.from(b64, 'base64').toString())
|
||||
: Promise.resolve(''),
|
||||
lxlyric
|
||||
? rendererInvoke(NAMES.mainWindow.lang_s2t, Buffer.from(lxlyric).toString('base64')).then(b64 => Buffer.from(b64, 'base64').toString())
|
||||
: Promise.resolve(''),
|
||||
])
|
||||
: Promise.resolve([lyric, tlyric, lxlyric])
|
||||
).then(([lyric, tlyric, lxlyric]) => {
|
||||
this.musicInfo.lrc = lyric
|
||||
this.musicInfo.tlrc = tlyric
|
||||
this.musicInfo.lxlrc = lxlyric
|
||||
})
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
this.status = this.statusText = this.$t('core.player.lyric_error')
|
||||
}).finally(() => {
|
||||
this.handleUpdateWinLyricInfo('lyric', { lrc: this.musicInfo.lrc, tlrc: this.musicInfo.tlrc, lxlrc: this.musicInfo.lxlrc })
|
||||
@@ -834,7 +866,7 @@ export default {
|
||||
this.playNext()
|
||||
break
|
||||
case 'progress':
|
||||
this.setProgress(data)
|
||||
this.setProgress(data * this.maxPlayTime)
|
||||
break
|
||||
case 'volume':
|
||||
break
|
||||
@@ -902,6 +934,59 @@ export default {
|
||||
|
||||
if (this.setting.player.togglePlayMethod == 'random') this.setPlayedList(this.playMusicInfo)
|
||||
},
|
||||
updateMediaSessionInfo() {
|
||||
const mediaMetadata = {
|
||||
title: this.targetSong.name,
|
||||
artist: this.targetSong.singer,
|
||||
album: this.targetSong.albumName,
|
||||
}
|
||||
if (this.targetSong.img) mediaMetadata.artwork = [{ src: this.targetSong.img }]
|
||||
navigator.mediaSession.metadata = new window.MediaMetadata(mediaMetadata)
|
||||
},
|
||||
registerMediaSessionHandler() {
|
||||
// navigator.mediaSession.setActionHandler('play', () => {
|
||||
// if (this.isPlay || !this.playMusicInfo) return
|
||||
// console.log('play')
|
||||
// this.startPlay()
|
||||
// })
|
||||
// navigator.mediaSession.setActionHandler('pause', () => {
|
||||
// if (!this.isPlay || !this.playMusicInfo) return
|
||||
// console.log('pause')
|
||||
// this.stopPlay()
|
||||
// })
|
||||
navigator.mediaSession.setActionHandler('stop', () => {
|
||||
if (!this.isPlay || !this.playMusicInfo) return
|
||||
console.log('stop')
|
||||
this.stopPlay()
|
||||
})
|
||||
navigator.mediaSession.setActionHandler('seekbackward', details => {
|
||||
if (!this.isPlay || !this.playMusicInfo) return
|
||||
console.log('seekbackward')
|
||||
this.setProgress(Math.max(audio.currentTime - details.seekOffset, 0))
|
||||
})
|
||||
navigator.mediaSession.setActionHandler('seekforward', details => {
|
||||
if (!this.isPlay || !this.playMusicInfo) return
|
||||
console.log('seekforward')
|
||||
this.setProgress(Math.min(audio.currentTime + details.seekOffset, audio.duration))
|
||||
})
|
||||
navigator.mediaSession.setActionHandler('seekto', details => {
|
||||
console.log('seekto', details.seekTime)
|
||||
let time = Math.min(details.seekTime, audio.duration)
|
||||
time = Math.max(time, 0)
|
||||
this.setProgress(time)
|
||||
})
|
||||
navigator.mediaSession.setActionHandler('previoustrack', () => {
|
||||
console.log('previoustrack')
|
||||
this.playPrev()
|
||||
})
|
||||
navigator.mediaSession.setActionHandler('nexttrack', () => {
|
||||
console.log('nexttrack')
|
||||
this.playNext()
|
||||
})
|
||||
// navigator.mediaSession.setActionHandler('skipad', () => {
|
||||
// console.log('')
|
||||
// })
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -27,17 +27,28 @@
|
||||
p(v-if="musicInfo.album") {{$t('core.player.album')}}{{musicInfo.album}}
|
||||
|
||||
div(:class="$style.right")
|
||||
div(:class="[$style.lyric, lyricEvent.isMsDown ? $style.draging : null]" @wheel="handleWheel" @mousedown="handleLyricMouseDown" ref="dom_lyric")
|
||||
div(:class="[$style.lyric, { [$style.draging]: lyricEvent.isMsDown }]" @wheel="handleWheel" @mousedown="handleLyricMouseDown" ref="dom_lyric")
|
||||
div(:class="$style.lyricSpace")
|
||||
div(:class="[$style.lyricText]" ref="dom_lyric_text")
|
||||
//- p(v-for="(info, index) in lyricLines" :key="index" :class="lyric.line == index ? $style.lrcActive : null") {{info.text}}
|
||||
div(:class="$style.lyricSpace")
|
||||
transition(enter-active-class="animated fadeIn" leave-active-class="animated fadeOut")
|
||||
div(:class="[$style.lyricSelectContent, 'select', 'scroll']" v-if="isShowLrcSelectContent" @contextmenu="handleCopySelectText")
|
||||
//- div(:class="$style.lyricSpace")
|
||||
div(v-for="(info, index) in lyricLines" :key="index" :class="[$style.lyricSelectline, { [$style.lrcActive]: lyric.line == index }]")
|
||||
span {{info.text}}
|
||||
br(v-if="info.translation")
|
||||
span(:class="$style.lyricSelectlineTransition") {{info.translation}}
|
||||
//- div(:class="$style.lyricSpace")
|
||||
|
||||
material-music-comment(:class="$style.comment" :titleFormat="this.setting.download.fileName" :musicInfo="musicInfo" v-model="isShowComment")
|
||||
|
||||
div(:class="$style.footer")
|
||||
div(:class="$style.footerLeft")
|
||||
div(:class="$style.footerLeftControlBtns")
|
||||
div(:class="[$style.footerLeftControlBtn, { [$style.active]: isShowLrcSelectContent }]" @click="isShowLrcSelectContent = !isShowLrcSelectContent" :tips="$t('core.player.lyric_select')")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='95%' viewBox='0 0 24 24' space='preserve')
|
||||
use(xlink:href='#icon-text')
|
||||
div(:class="[$style.footerLeftControlBtn, isShowComment ? $style.active : null]" @click="isShowComment = !isShowComment" :tips="$t('core.player.comment_show')")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='95%' viewBox='0 0 24 24' space='preserve')
|
||||
use(xlink:href='#icon-comment')
|
||||
@@ -59,7 +70,7 @@
|
||||
div(:class="$style.progressContent")
|
||||
div(:class="$style.progress")
|
||||
//- div(:class="[$style.progressBar, $style.progressBar1]" :style="{ transform: `scaleX(${progress || 0})` }")
|
||||
div(:class="[$style.progressBar, $style.progressBar2, isActiveTransition ? $style.barTransition : '']" @transitionend="handleTransitionEnd" :style="{ transform: `scaleX(${playInfo.progress || 0})`, willChange: isPlay || isActiveTransition ? 'transform' : 'auto' }")
|
||||
div(:class="[$style.progressBar, $style.progressBar2, isActiveTransition ? $style.barTransition : '']" @transitionend="handleTransitionEnd" :style="{ transform: `scaleX(${playInfo.progress || 0})` }")
|
||||
div(:class="$style.progressMask" @click='setProgress' ref="dom_progress")
|
||||
div(:class="$style.timeLabel")
|
||||
span(style="margin-left: 15px") {{playInfo.status}}
|
||||
@@ -87,7 +98,7 @@
|
||||
<script>
|
||||
import { mapGetters, mapMutations } from 'vuex'
|
||||
import { base as eventBaseName } from '../../event/names'
|
||||
import { scrollTo } from '../../utils'
|
||||
import { clipboardWriteText, scrollTo } from '../../utils'
|
||||
|
||||
let cancelScrollFn = null
|
||||
|
||||
@@ -230,23 +241,22 @@ export default {
|
||||
lyricLines: [],
|
||||
isSetedLines: false,
|
||||
isShowComment: false,
|
||||
isShowLrcSelectContent: false,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.setProgressWidth()
|
||||
})
|
||||
document.addEventListener('mousemove', this.handleMouseMsMove)
|
||||
document.addEventListener('mouseup', this.handleMouseMsUp)
|
||||
window.addEventListener('resize', this.handleResize)
|
||||
this.listenEvent()
|
||||
|
||||
// console.log('object', this.$refs.dom_lyric_text)
|
||||
this.setLyric(this.lyricLines)
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.unlistenEvent()
|
||||
|
||||
this.clearLyricScrollTimeout()
|
||||
document.removeEventListener('mousemove', this.handleMouseMsMove)
|
||||
document.removeEventListener('mouseup', this.handleMouseMsUp)
|
||||
window.removeEventListener('resize', this.handleResize)
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['setting']),
|
||||
@@ -256,6 +266,16 @@ export default {
|
||||
...mapMutations('player', [
|
||||
'visiblePlayerDetail',
|
||||
]),
|
||||
listenEvent() {
|
||||
document.addEventListener('mousemove', this.handleMouseMsMove)
|
||||
document.addEventListener('mouseup', this.handleMouseMsUp)
|
||||
window.addEventListener('resize', this.handleResize)
|
||||
},
|
||||
unlistenEvent() {
|
||||
document.removeEventListener('mousemove', this.handleMouseMsMove)
|
||||
document.removeEventListener('mouseup', this.handleMouseMsUp)
|
||||
window.removeEventListener('resize', this.handleResize)
|
||||
},
|
||||
setLyric(lines) {
|
||||
const dom_lines = document.createDocumentFragment()
|
||||
for (const line of lines) {
|
||||
@@ -359,6 +379,12 @@ export default {
|
||||
close() {
|
||||
window.eventHub.$emit(eventBaseName.close)
|
||||
},
|
||||
handleCopySelectText() {
|
||||
let str = window.getSelection().toString()
|
||||
str = str.trim()
|
||||
if (!str.length) return
|
||||
clipboardWriteText(str)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -635,14 +661,41 @@ export default {
|
||||
// transition: @transition-theme !important;
|
||||
// transition-property: color, font-size;
|
||||
// }
|
||||
// .lrc-active {
|
||||
// color: @color-theme;
|
||||
// font-size: 1.2em;
|
||||
// }
|
||||
}
|
||||
.lyricSelectContent {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
// text-align: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
background-color: @color-theme_2-background_1;
|
||||
z-index: 10;
|
||||
color: @color-player-detail-lyric;
|
||||
|
||||
.lyricSelectline {
|
||||
padding: 8px 0;
|
||||
overflow-wrap: break-word;
|
||||
transition: @transition-theme !important;
|
||||
transition-property: color, font-size;
|
||||
line-height: 1.3;
|
||||
}
|
||||
.lyricSelectlineTransition {
|
||||
font-size: 14px;
|
||||
}
|
||||
.lrc-active {
|
||||
color: @color-theme;
|
||||
}
|
||||
}
|
||||
|
||||
.lyric-space {
|
||||
height: 70%;
|
||||
}
|
||||
.lrc-active {
|
||||
color: @color-theme;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.comment {
|
||||
flex: 0 0 0;
|
||||
@@ -853,6 +906,13 @@ each(@themes, {
|
||||
// .lrc-active {
|
||||
// color: ~'@{color-@{value}-theme}';
|
||||
// }
|
||||
.lyricSelectContent {
|
||||
background-color: ~'@{color-@{value}-theme_2-background_1}';
|
||||
color: ~'@{color-@{value}-player-detail-lyric}';
|
||||
.lrc-active {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
}
|
||||
}
|
||||
.footerLeftControlBtns {
|
||||
color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
|
||||
257
src/renderer/components/material/DuplicateMusicModal.vue
Normal file
257
src/renderer/components/material/DuplicateMusicModal.vue
Normal file
@@ -0,0 +1,257 @@
|
||||
<template lang="pug">
|
||||
Modal(:show="visible" @close="$emit('update:visible', false)" bg-close)
|
||||
div(:class="$style.header")
|
||||
h2 {{listInfo.name}}
|
||||
main.scroll(:class="$style.main")
|
||||
ul(ref="dom_list" v-if="duplicateList.length" :class="$style.list")
|
||||
li(v-for="(item, index) in duplicateList" :key="item.songmid" :class="$style.listItem")
|
||||
div(:class="$style.num") {{item.index + 1}}
|
||||
div(:class="$style.text")
|
||||
h3(:class="$style.text") {{item.musicInfo.name}} - {{item.musicInfo.singer}}
|
||||
h3(v-if="item.musicInfo.albumName" :class="[$style.text, $style.albumName]") {{item.musicInfo.albumName}}
|
||||
div(:class="$style.label") {{item.musicInfo.source}}
|
||||
div(:class="$style.label") {{item.musicInfo.interval}}
|
||||
div(:class="$style.btns")
|
||||
button(type="button" @click="handlePlay(index)" :class="$style.btn")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='50%' viewBox='0 0 287.386 287.386' space='preserve' v-once)
|
||||
use(xlink:href='#icon-testPlay')
|
||||
button(type="button" @click="handleRemove(index)" :class="$style.btn")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='50%' viewBox='0 0 212.982 212.982' space='preserve' v-once)
|
||||
use(xlink:href='#icon-delete')
|
||||
div(:class="$style.noItem" v-else)
|
||||
p(v-text="$t('view.list.no_item')")
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapMutations } from 'vuex'
|
||||
import Modal from './Modal'
|
||||
import Btn from './Btn'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
listInfo: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
model: {
|
||||
prop: 'visible',
|
||||
event: 'visible',
|
||||
},
|
||||
components: {
|
||||
Modal,
|
||||
Btn,
|
||||
},
|
||||
watch: {
|
||||
visible(n) {
|
||||
if (n) this.handleFilterList()
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
duplicateList: [],
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.listInfo.list) this.handleFilterList()
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('list', [
|
||||
'listRemove',
|
||||
]),
|
||||
...mapMutations('player', {
|
||||
setPlayList: 'setList',
|
||||
}),
|
||||
handleFilterList() {
|
||||
const listMap = new Map()
|
||||
const duplicateList = []
|
||||
this.listInfo.list.forEach((musicInfo, index) => {
|
||||
const musicInfoName = musicInfo.name.toLowerCase().trim()
|
||||
if (listMap.has(musicInfoName)) {
|
||||
const targetMusicInfo = listMap.get(musicInfoName)
|
||||
duplicateList.push({
|
||||
index: this.listInfo.list.indexOf(targetMusicInfo),
|
||||
musicInfo: targetMusicInfo,
|
||||
}, {
|
||||
index,
|
||||
musicInfo,
|
||||
})
|
||||
} else {
|
||||
listMap.set(musicInfoName, musicInfo)
|
||||
}
|
||||
})
|
||||
|
||||
this.duplicateList = duplicateList
|
||||
},
|
||||
handleRemove(index) {
|
||||
const { musicInfo: targetMusicInfo } = this.duplicateList.splice(index, 1)[0]
|
||||
// let duplicates = []
|
||||
// for (let index = 0; index < this.duplicateList.length; index++) {
|
||||
// const { musicInfo } = this.duplicateList[index]
|
||||
// if (musicInfo.name == targetMusicInfo.name) {
|
||||
// duplicates.push(index)
|
||||
// if (duplicates.length > 1) break
|
||||
// }
|
||||
// }
|
||||
// console.log(duplicates)
|
||||
// if (duplicates.length < 2) this.duplicateList.splice(duplicates[0], 1)
|
||||
|
||||
this.listRemove({ listId: this.listInfo.id, id: targetMusicInfo.songmid })
|
||||
|
||||
this.handleFilterList()
|
||||
},
|
||||
handlePlay(index) {
|
||||
const { index: musicInfoIndex } = this.duplicateList[index]
|
||||
this.setPlayList({ list: this.listInfo, index: musicInfoIndex })
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '../../assets/styles/layout.less';
|
||||
|
||||
.header {
|
||||
flex: none;
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
.main {
|
||||
min-height: 200px;
|
||||
width: 460px;
|
||||
}
|
||||
|
||||
.list {
|
||||
// background-color: @color-search-form-background;
|
||||
font-size: 13px;
|
||||
transition-property: height;
|
||||
position: relative;
|
||||
.listItem {
|
||||
position: relative;
|
||||
padding: 8px 5px;
|
||||
transition: background-color .2s ease;
|
||||
line-height: 1.3;
|
||||
// overflow: hidden;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
background-color: @color-theme_2-hover;
|
||||
}
|
||||
// border-radius: 4px;
|
||||
// &:last-child {
|
||||
// border-bottom-left-radius: 4px;
|
||||
// border-bottom-right-radius: 4px;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
.num {
|
||||
flex: none;
|
||||
font-size: 12px;
|
||||
width: 30px;
|
||||
text-align: center;
|
||||
color: @color-theme_2-font-label;
|
||||
}
|
||||
|
||||
.text {
|
||||
flex: auto;
|
||||
padding-left: 5px;
|
||||
.mixin-ellipsis-1;
|
||||
}
|
||||
.albumName {
|
||||
font-size: 12px;
|
||||
opacity: 0.6;
|
||||
.mixin-ellipsis-1;
|
||||
}
|
||||
.label {
|
||||
flex: none;
|
||||
font-size: 12px;
|
||||
opacity: 0.5;
|
||||
padding: 0 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
// transform: rotate(45deg);
|
||||
// background-color:
|
||||
}
|
||||
.btns {
|
||||
flex: none;
|
||||
font-size: 12px;
|
||||
padding: 0 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.btn {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-radius: @form-radius;
|
||||
margin-right: 5px;
|
||||
cursor: pointer;
|
||||
padding: 4px 7px;
|
||||
color: @color-btn;
|
||||
outline: none;
|
||||
transition: background-color 0.2s ease;
|
||||
line-height: 0;
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
svg {
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: @color-theme_2-hover;
|
||||
}
|
||||
&:active {
|
||||
background-color: @color-theme_2-active;
|
||||
}
|
||||
}
|
||||
|
||||
.no-item {
|
||||
position: relative;
|
||||
height: 200px;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
color: @color-theme_2-font-label;
|
||||
}
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#container.@{value}) {
|
||||
.listItem {
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-theme_2-hover}';
|
||||
}
|
||||
}
|
||||
.num {
|
||||
color: ~'@{color-@{value}-theme_2-font-label}';
|
||||
}
|
||||
.btn {
|
||||
color: ~'@{color-@{value}-btn}';
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-theme_2-hover}';
|
||||
}
|
||||
&:active {
|
||||
background-color: ~'@{color-@{value}-theme_2-active}';
|
||||
}
|
||||
}
|
||||
.no-item {
|
||||
p {
|
||||
color: ~'@{color-@{value}-theme_2-font-label}';
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
@@ -3,7 +3,9 @@ transition(enter-active-class="animated fadeIn"
|
||||
leave-active-class="animated fadeOut")
|
||||
div(:class="$style.modal" v-show="show" @click="bgClose && close()")
|
||||
transition(:enter-active-class="inClass"
|
||||
:leave-active-class="outClass")
|
||||
:leave-active-class="outClass"
|
||||
@after-leave="$emit('after-leave', $event)"
|
||||
)
|
||||
div(:class="$style.content" v-show="show" @click.stop)
|
||||
header(:class="$style.header")
|
||||
button(type="button" @click="close" v-if="closeBtn")
|
||||
@@ -140,7 +142,8 @@ export default {
|
||||
box-shadow: 0 0 3px rgba(0, 0, 0, .3);
|
||||
overflow: hidden;
|
||||
max-height: 80%;
|
||||
max-width: 70%;
|
||||
max-width: 76%;
|
||||
min-width: 220px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
|
||||
@@ -9,6 +9,10 @@ div(:class="$style.container")
|
||||
@keyup.40.prevent="handleKeyDown"
|
||||
@keyup.38.prevent="handleKeyUp"
|
||||
@contextmenu="handleContextMenu")
|
||||
transition(enter-active-class="animated zoomIn" leave-active-class="animated zoomOut")
|
||||
button(type="button" @click="handleClearList" v-show="text")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 24 24' space='preserve')
|
||||
use(xlink:href='#icon-window-close')
|
||||
button(type="button" @click="handleSearch")
|
||||
slot
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 30.239 30.239' space='preserve')
|
||||
@@ -144,6 +148,11 @@ export default {
|
||||
this.text = `${this.text.substring(0, dom_input.selectionStart)}${str}${this.text.substring(dom_input.selectionEnd, this.text.length)}`
|
||||
this.$emit('input', this.text)
|
||||
},
|
||||
handleClearList() {
|
||||
this.text = ''
|
||||
this.$emit('input', this.text)
|
||||
this.sendEvent('submit')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -210,14 +219,17 @@ export default {
|
||||
// background-color: @color-search-form-background;
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
cursor: pointer;
|
||||
height: 100%;
|
||||
padding: 6px 7px;
|
||||
color: @color-btn;
|
||||
transition: background-color .2s ease;
|
||||
|
||||
&:last-child {
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: @color-theme-hover;
|
||||
}
|
||||
|
||||
@@ -374,6 +374,7 @@ export default {
|
||||
.albumName {
|
||||
font-size: 12px;
|
||||
opacity: 0.6;
|
||||
.mixin-ellipsis-1;
|
||||
}
|
||||
.source {
|
||||
flex: none;
|
||||
|
||||
@@ -45,8 +45,8 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
label() {
|
||||
if (!this.value) return ''
|
||||
if (!this.itemName) return this.value
|
||||
if (this.value == null) return ''
|
||||
if (this.itemName == null) return this.value
|
||||
const item = this.list.find(l => l[this.itemKey] == this.value)
|
||||
if (!item) return ''
|
||||
return item[this.itemName]
|
||||
|
||||
164
src/renderer/components/material/SyncModeModal.vue
Normal file
164
src/renderer/components/material/SyncModeModal.vue
Normal file
@@ -0,0 +1,164 @@
|
||||
<template lang="pug">
|
||||
material-modal(:show="globalObj.sync.isShowSyncMode" @close="handleClose(false)" :bgClose="false" :close-btn="false")
|
||||
main(:class="$style.main")
|
||||
h2 {{$t('material.sync_mode_modal.title', { name: globalObj.sync.deviceName })}}
|
||||
div.scroll(:class="$style.content")
|
||||
dl(:class="$style.btnGroup")
|
||||
dt(:class="$style.label") {{$t('material.sync_mode_modal.merge_label')}}
|
||||
dd(:class="$style.btns")
|
||||
material-btn(:class="$style.btn" @click="handleSelectMode('merge_local_remote')") {{$t('material.sync_mode_modal.merge_btn_local_remote')}}
|
||||
material-btn(:class="$style.btn" @click="handleSelectMode('merge_remote_local')") {{$t('material.sync_mode_modal.merge_btn_remote_local')}}
|
||||
dl(:class="$style.btnGroup")
|
||||
dt(:class="$style.label") {{$t('material.sync_mode_modal.overwrite_label')}}
|
||||
dd(:class="$style.btns")
|
||||
material-btn(:class="$style.btn" @click="handleSelectMode('overwrite_local_remote')") {{$t('material.sync_mode_modal.overwrite_btn_local_remote')}}
|
||||
material-btn(:class="$style.btn" @click="handleSelectMode('overwrite_remote_local')") {{$t('material.sync_mode_modal.overwrite_btn_remote_local')}}
|
||||
dd(style="font-size: 14px; margin-top: 5px;")
|
||||
material-checkbox(id="sync_mode_modal_isOverwrite" v-model="isOverwrite" :label="$t('material.sync_mode_modal.overwrite')")
|
||||
|
||||
dl(:class="$style.btnGroup")
|
||||
dt(:class="$style.label") {{$t('material.sync_mode_modal.other_label')}}
|
||||
dd(:class="$style.btns")
|
||||
material-btn(:class="$style.btn" @click="handleSelectMode('none')") {{$t('material.sync_mode_modal.overwrite_btn_none')}}
|
||||
material-btn(:class="$style.btn" @click="handleSelectMode('cancel')") {{$t('material.sync_mode_modal.overwrite_btn_cancel')}}
|
||||
dl(:class="$style.btnGroup")
|
||||
dd
|
||||
section(:class="$style.tipGroup")
|
||||
h3(:class="$style.title") {{$t('material.sync_mode_modal.merge_tip')}}
|
||||
p(:class="$style.tip") {{$t('material.sync_mode_modal.merge_tip_desc')}}
|
||||
section(:class="$style.tipGroup")
|
||||
h3(:class="$style.title") {{$t('material.sync_mode_modal.overwrite_tip')}}
|
||||
p(:class="$style.tip") {{$t('material.sync_mode_modal.overwrite_tip_desc')}}
|
||||
section(:class="$style.tipGroup")
|
||||
h3(:class="$style.title") {{$t('material.sync_mode_modal.other_tip')}}
|
||||
p(:class="$style.tip") {{$t('material.sync_mode_modal.other_tip_desc')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { sync as eventSyncName } from '@renderer/event/names'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
isOverwrite: false,
|
||||
globalObj: {
|
||||
sync: {
|
||||
isShowSyncMode: false,
|
||||
deviceName: '',
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.globalObj = window.globalObj
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
handleSelectMode(mode) {
|
||||
if (mode.startsWith('overwrite') && this.isOverwrite) mode += '_full'
|
||||
window.eventHub.$emit(eventSyncName.send_sync_list, {
|
||||
action: 'selectMode',
|
||||
data: mode,
|
||||
})
|
||||
this.handleClose()
|
||||
},
|
||||
handleClose() {
|
||||
this.globalObj.sync.isShowSyncMode = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '../../assets/styles/layout.less';
|
||||
|
||||
.main {
|
||||
padding: 15px;
|
||||
max-width: 700px;
|
||||
min-width: 200px;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
color: @color-theme_2-font;
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: auto;
|
||||
padding: 15px 0 5px;
|
||||
padding-right: 5px;
|
||||
.btnGroup + .btnGroup {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.label {
|
||||
color: @color-theme_2-font-label;
|
||||
font-size: 14px;
|
||||
line-height: 2;
|
||||
}
|
||||
.desc {
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.tipGroup {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: 12px;
|
||||
|
||||
+ .tipGroup {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.title {
|
||||
white-space: nowrap;
|
||||
font-weight: bold;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.tip {
|
||||
line-height: 1.3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btns {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.btn {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
+.btn {
|
||||
margin-left: 15px;
|
||||
}
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
each(@themes, {
|
||||
:global(#container.@{value}) {
|
||||
.main {
|
||||
h2 {
|
||||
color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
}
|
||||
.name {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
@@ -1,7 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import keyBind from '../utils/keyBind'
|
||||
import { rendererOn, rendererSend, NAMES, rendererInvoke } from '../../common/ipc'
|
||||
import { base as baseName } from './names'
|
||||
import { base as baseName, sync as syncName } from './names'
|
||||
import { common as hotKeyNamesCommon } from '../../common/hotKey'
|
||||
|
||||
const eventHub = window.eventHub = new Vue()
|
||||
@@ -25,6 +25,14 @@ eventHub.$on(baseName.bindKey, () => {
|
||||
// console.log(event, key)
|
||||
if (!window.isEditingHotKey && appHotKeyConfig.local.enable && appHotKeyConfig.local.keys[key]) {
|
||||
if (type == 'up') return
|
||||
|
||||
// 软件内快捷键的最小化触发时
|
||||
// 如果已启用托盘,则隐藏程序,否则最小化程序 https://github.com/lyswhut/lx-music-desktop/issues/603
|
||||
if (appHotKeyConfig.local.keys[key].action == hotKeyNamesCommon.min.action && global.appSetting.tray.isToTray) {
|
||||
eventHub.$emit(hotKeyNamesCommon.hide_toggle.action)
|
||||
return
|
||||
}
|
||||
|
||||
eventHub.$emit(appHotKeyConfig.local.keys[key].action)
|
||||
return
|
||||
}
|
||||
@@ -77,3 +85,18 @@ rendererOn(NAMES.mainWindow.set_hot_key_config, (event, config) => {
|
||||
}
|
||||
window.eventHub.$emit(baseName.set_hot_key_config, config)
|
||||
})
|
||||
|
||||
rendererOn(NAMES.mainWindow.sync_action_list, (event, { action, data }) => {
|
||||
window.eventHub.$emit(syncName.handle_action_list, { action, data })
|
||||
})
|
||||
eventHub.$on(syncName.send_action_list, ({ action, data }) => {
|
||||
if (!window.globalObj.sync.enable) return
|
||||
rendererSend(NAMES.mainWindow.sync_action_list, { action, data })
|
||||
})
|
||||
rendererOn(NAMES.mainWindow.sync_list, (event, { action, data }) => {
|
||||
window.eventHub.$emit(syncName.handle_sync_list, { action, data })
|
||||
})
|
||||
eventHub.$on(syncName.send_sync_list, ({ action, data }) => {
|
||||
if (!window.globalObj.sync.enable) return
|
||||
rendererSend(NAMES.mainWindow.sync_list, { action, data })
|
||||
})
|
||||
|
||||
@@ -10,6 +10,12 @@ const names = {
|
||||
set_config: 'set_config',
|
||||
set_hot_key_config: 'set_hot_key_config',
|
||||
},
|
||||
sync: {
|
||||
send_action_list: 'send_action_list',
|
||||
handle_action_list: 'handle_action_list',
|
||||
send_sync_list: 'send_sync_list',
|
||||
handle_sync_list: 'handle_sync_list',
|
||||
},
|
||||
}
|
||||
|
||||
for (const item of Object.keys(names)) {
|
||||
@@ -20,3 +26,4 @@ for (const item of Object.keys(names)) {
|
||||
}
|
||||
|
||||
export const base = names.base
|
||||
export const sync = names.sync
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"date_format_second": "{num} seconds ago",
|
||||
"cancel_button_text": "Cancel",
|
||||
"confirm_button_text": "OK",
|
||||
"date_format_hour": "{num} hours ago",
|
||||
"date_format_minute": "{num} minutes ago",
|
||||
"date_format_hour": "{num} hours ago"
|
||||
"date_format_second": "{num} seconds ago"
|
||||
}
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
{
|
||||
"copy_title": " (Click to copy)",
|
||||
"volume": "Volume: ",
|
||||
"pause": "Pause",
|
||||
"play": "Play",
|
||||
"prev": "Prev",
|
||||
"next": "Next",
|
||||
"playing": "Now playing...",
|
||||
"stop": "Paused",
|
||||
"end": "Stopped",
|
||||
"refresh_url": "Music URL expired, refreshing...",
|
||||
"error": "Error loading music. Switch to next song after 5 seconds",
|
||||
"loading": "Music loading...",
|
||||
"buffering": "Buffering...",
|
||||
"geting_url": "Getting music link...",
|
||||
"lyric_error": "Failed to get lyrics",
|
||||
"hide_detail": "Hide detail page (Right-click in the view to quickly hide the details page)",
|
||||
"name": "Name: ",
|
||||
"singer": "Artist: ",
|
||||
"album": "Album: ",
|
||||
"add_music_to": "Add the current song to...",
|
||||
"desktop_lyric_on": "Open Desktop Lyrics",
|
||||
"desktop_lyric_off": "Close Desktop Lyrics",
|
||||
"desktop_lyric_lock": "Right click to lock lyrics",
|
||||
"desktop_lyric_unlock": "Right click to unlock lyrics",
|
||||
"play_toggle_mode_list_loop": "List Loop",
|
||||
"play_toggle_mode_random": "List Random",
|
||||
"play_toggle_mode_list": "Play in order",
|
||||
"play_toggle_mode_single_loop": "Single Loop",
|
||||
"play_toggle_mode_off": "Disable",
|
||||
"pic_tip": "Right click to locate the currently playing song in \"My List\"",
|
||||
|
||||
"comment_show": "Song comments",
|
||||
"comment_hot_loading": "Hot comments are loading",
|
||||
"comment_new_loading": "Latest comments are loading",
|
||||
"album": "Album: ",
|
||||
"buffering": "Buffering...",
|
||||
"comment_hot_load_error": "Hot comments failed to load, click to try to reload",
|
||||
"comment_new_load_error": "The latest comment failed to load, click to try to reload",
|
||||
"comment_refresh": "Refresh comments",
|
||||
"comment_no_content": "No comments yet",
|
||||
"comment_hot_loading": "Hot comments are loading",
|
||||
"comment_hot_title": "Hot Comment",
|
||||
"comment_new_load_error": "The latest comment failed to load, click to try to reload",
|
||||
"comment_new_loading": "Latest comments are loading",
|
||||
"comment_new_title": "Latest comment",
|
||||
"comment_title": "{name} comment"
|
||||
"comment_no_content": "No comments yet",
|
||||
"comment_refresh": "Refresh comments",
|
||||
"comment_show": "Song comments",
|
||||
"comment_title": "{name} comment",
|
||||
"copy_title": " (Click to copy)",
|
||||
"desktop_lyric_lock": "Right click to lock lyrics",
|
||||
"desktop_lyric_off": "Close Desktop Lyrics",
|
||||
"desktop_lyric_on": "Open Desktop Lyrics",
|
||||
"desktop_lyric_unlock": "Right click to unlock lyrics",
|
||||
"end": "Stopped",
|
||||
"error": "Error loading music. Switch to next song after 5 seconds",
|
||||
"geting_url": "Getting music link...",
|
||||
"hide_detail": "Hide detail page (Right-click in the view to quickly hide the details page)",
|
||||
"loading": "Music loading...",
|
||||
"lyric_error": "Failed to get lyrics",
|
||||
"lyric_select": "Lyric text selection",
|
||||
"name": "Name: ",
|
||||
"next": "Next",
|
||||
"pause": "Pause",
|
||||
"pic_tip": "Right click to locate the currently playing song in \"My List\"",
|
||||
"play": "Play",
|
||||
"play_toggle_mode_list": "Play in order",
|
||||
"play_toggle_mode_list_loop": "List Loop",
|
||||
"play_toggle_mode_off": "Disable",
|
||||
"play_toggle_mode_random": "List Random",
|
||||
"play_toggle_mode_single_loop": "Single Loop",
|
||||
"playing": "Now playing...",
|
||||
"prev": "Prev",
|
||||
"refresh_url": "Music URL expired, refreshing...",
|
||||
"singer": "Artist: ",
|
||||
"stop": "Paused",
|
||||
"volume": "Volume: "
|
||||
}
|
||||
|
||||
19
src/renderer/lang/en-us/material/sync_mode_modal.json
Normal file
19
src/renderer/lang/en-us/material/sync_mode_modal.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"merge_btn_local_remote": "Local list merge remote list",
|
||||
"merge_btn_remote_local": "Remote list merge local list",
|
||||
"merge_label": "Merge",
|
||||
"merge_tip": "Merge:",
|
||||
"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.",
|
||||
"other_label": "Other",
|
||||
"other_tip": "Other: ",
|
||||
"other_tip_desc": "\"Only use real-time synchronization function\" will not modify the lists of both parties, only real-time synchronization operations; \"Cancel synchronization\" will directly disconnect the two parties.",
|
||||
"overwrite": "Full coverage",
|
||||
"overwrite_btn_cancel": "Cancel sync",
|
||||
"overwrite_btn_local_remote": "Local list Overwrite remote list",
|
||||
"overwrite_btn_none": "Only use real-time synchronization",
|
||||
"overwrite_btn_remote_local": "Remote list Overwrite local list",
|
||||
"overwrite_label": "Cover",
|
||||
"overwrite_tip": "Cover: ",
|
||||
"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.",
|
||||
"title": "Choose how to synchronize the list with {name}"
|
||||
}
|
||||
@@ -1,28 +1,38 @@
|
||||
{
|
||||
"lists_new_list_btn": "Create list",
|
||||
"lists_new_list_input": "New list...",
|
||||
"lists_rename": "Rename",
|
||||
"lists_moveup": "Move Up",
|
||||
"lists_movedown": "Move Down",
|
||||
"lists_sync": "Sync",
|
||||
"lists_remove": "Remove",
|
||||
"action": "Manage",
|
||||
"album": "Album",
|
||||
"default_list": "Recently Played",
|
||||
"list_add_to": "Add to ...",
|
||||
"list_copy_name": "Copy name",
|
||||
"list_download": "Download",
|
||||
"list_move_to": "Move to ...",
|
||||
"list_play": "Play",
|
||||
"list_play_later": "Play later",
|
||||
"list_copy_name": "Copy name",
|
||||
"list_add_to": "Add to ...",
|
||||
"list_move_to": "Move to ...",
|
||||
"list_sort": "Adjust position",
|
||||
"list_download": "Download",
|
||||
"list_search": "Search",
|
||||
"list_remove": "Remove",
|
||||
"list_search": "Search",
|
||||
"list_sort": "Adjust position",
|
||||
"list_source_detail": "Song Page",
|
||||
"default_list": "Recently Played",
|
||||
"lists_duplicate": "Duplicate song",
|
||||
"lists_export": "Export",
|
||||
"lists_export_part_desc": "Choose where to save the list file",
|
||||
"lists_import": "Import",
|
||||
"lists_import_part_button_cancel": "No",
|
||||
"lists_import_part_button_confirm": "Overwrite",
|
||||
"lists_import_part_confirm": "The imported list ({importName}) has the same ID as the local list ({localName}). Do you overwrite the local list?",
|
||||
"lists_import_part_desc": "Select list file",
|
||||
"lists_movedown": "Move Down",
|
||||
"lists_moveup": "Move Up",
|
||||
"lists_new_list_btn": "Create list",
|
||||
"lists_new_list_input": "New list...",
|
||||
"lists_remove": "Remove",
|
||||
"lists_remove_tip": "Do you really want to remove {name}?",
|
||||
"lists_remove_tip_button": "Yes, that's right",
|
||||
"lists_rename": "Rename",
|
||||
"lists_sync": "Update",
|
||||
"loding_list": "Loading...",
|
||||
"love_list": "Favorites",
|
||||
"name": "Name",
|
||||
"no_item": "Nothing's here...",
|
||||
"singer": "Artist",
|
||||
"album": "Album",
|
||||
"action": "Manage",
|
||||
"time": "Length",
|
||||
"loding_list": "Loading...",
|
||||
"no_item": "Nothing's here..."
|
||||
"time": "Length"
|
||||
}
|
||||
|
||||
@@ -1,154 +1,156 @@
|
||||
{
|
||||
"about": "About lx-music-desktop",
|
||||
"backup": "Backup and restore",
|
||||
"backup_all": "All data (list data and setting data)",
|
||||
"backup_all_export": "Export",
|
||||
"backup_all_export_desc": "Select the backup to...",
|
||||
"backup_all_import": "Import",
|
||||
"backup_all_import_desc": "Select a backup file",
|
||||
"backup_part": "Partial data (list data includes audition list, favorite list, user-defined list, setting data does not include shortcut key settings)",
|
||||
"backup_part_export_list": "Export lists",
|
||||
"backup_part_export_list_desc": "Save the list to...",
|
||||
"backup_part_export_setting": "Export settings",
|
||||
"backup_part_export_setting_desc": "Save the list to...",
|
||||
"backup_part_import_list": "Import lists",
|
||||
"backup_part_import_list_desc": "Select a list backup",
|
||||
"backup_part_import_setting": "Import settings",
|
||||
"backup_part_import_setting_desc": "Select the Settings file",
|
||||
"basic": "General",
|
||||
"basic_theme": "Theme",
|
||||
"basic_show_animation": "Show switching animation",
|
||||
"basic_animation": "Random pop-up animation",
|
||||
"basic_source_title": "Choose a music source",
|
||||
"basic_source_test": "Test API (Available for most software features)",
|
||||
"basic_source_temp": "Temporary API (some features not available; workaround if Test API unavailable)",
|
||||
"basic_source": "Music source",
|
||||
"basic_sourcename_title": "Select the name of music source",
|
||||
"basic_sourcename_real": "Original",
|
||||
"basic_sourcename_alias": "Aliases",
|
||||
"basic_sourcename": "Source name",
|
||||
"basic_source_status_success": "Initialization successful",
|
||||
"basic_source_status_initing": "Initializing",
|
||||
"basic_source_status_failed": "Initialization failed",
|
||||
"basic_source_user_api_btn": "Custom Source Management",
|
||||
"basic_window_size_title": "Set the window size",
|
||||
"basic_window_size": "Window size",
|
||||
"basic_window_size_smaller": "Smaller",
|
||||
"basic_window_size_small": "Small",
|
||||
"basic_window_size_medium": "Medium",
|
||||
"basic_window_size_big": "Large",
|
||||
"basic_window_size_larger": "Larger",
|
||||
"basic_window_size_oversized": "Oversized",
|
||||
"basic_window_size_huge": "Huge",
|
||||
"basic_to_tray": "Do not exit the software when closing the software and minimize it to the system tray",
|
||||
"basic_lang_title": "The language displayed in the software",
|
||||
"basic_lang": "Language",
|
||||
"basic_control_btn_position": "Control Button Position",
|
||||
"basic_control_btn_position_left": "Left",
|
||||
"basic_control_btn_position_right": "Right",
|
||||
|
||||
"play": "Play",
|
||||
"play_save_play_time": "Remember playback progress",
|
||||
"play_lyric_transition": "Show lyrics translation",
|
||||
"play_lyric_lxlrc": "Use Karaoke-style lyrics playback (if supported)",
|
||||
"play_quality": "Play 320K quality songs first (if supported)",
|
||||
"play_task_bar": "Show playing progress on the taskbar",
|
||||
"play_mediaDevice_title": "Select a media device for audio output",
|
||||
"play_mediaDevice": "Audio output",
|
||||
"play_mediaDevice_remove_stop_play": "Pause the song when the current sound output device is changed",
|
||||
|
||||
"basic_lang": "Language",
|
||||
"basic_lang_title": "The language displayed in the software",
|
||||
"basic_show_animation": "Show switching animation",
|
||||
"basic_source": "Music source",
|
||||
"basic_source_status_failed": "Initialization failed",
|
||||
"basic_source_status_initing": "Initializing",
|
||||
"basic_source_status_success": "Initialization successful",
|
||||
"basic_source_temp": "Temporary API (some features not available; workaround if Test API unavailable)",
|
||||
"basic_source_test": "Test API (Available for most software features)",
|
||||
"basic_source_title": "Choose a music source",
|
||||
"basic_source_user_api_btn": "Custom Source Management",
|
||||
"basic_sourcename": "Source name",
|
||||
"basic_sourcename_alias": "Aliases",
|
||||
"basic_sourcename_real": "Original",
|
||||
"basic_sourcename_title": "Select the name of music source",
|
||||
"basic_theme": "Theme",
|
||||
"basic_to_tray": "Do not exit the software when closing the software and minimize it to the system tray",
|
||||
"basic_window_size": "Window size",
|
||||
"basic_window_size_big": "Large",
|
||||
"basic_window_size_huge": "Huge",
|
||||
"basic_window_size_larger": "Larger",
|
||||
"basic_window_size_medium": "Medium",
|
||||
"basic_window_size_oversized": "Oversized",
|
||||
"basic_window_size_small": "Small",
|
||||
"basic_window_size_smaller": "Smaller",
|
||||
"basic_window_size_title": "Set the window size",
|
||||
"click_copy": "Click to copy",
|
||||
"click_open": "Click to open",
|
||||
"desktop_lyric": "Desktop Lyric Settings",
|
||||
"desktop_lyric_enable": "Display lyrics",
|
||||
"desktop_lyric_lock": "Lock lyrics",
|
||||
"desktop_lyric_always_on_top": "Make the lyrics always above other windows",
|
||||
"desktop_lyric_enable": "Display lyrics",
|
||||
"desktop_lyric_font": "Lyric font",
|
||||
"desktop_lyric_font_default": "Default",
|
||||
"desktop_lyric_lock": "Lock lyrics",
|
||||
"desktop_lyric_lock_screen": "It is not allowed to drag the lyrics window out of the main screen",
|
||||
|
||||
"search": "Search",
|
||||
"search_hot": "Top Searches",
|
||||
"search_history": "Search history",
|
||||
"search_focus_search_box": "Automatically focus the search box on startup",
|
||||
|
||||
"list": "List",
|
||||
"list_source": "Show song source (only valid for my music category)",
|
||||
"list_scroll": "Remember the position of the scroll bar of the playlist (only valid for my music classification)",
|
||||
|
||||
"download": "Download",
|
||||
"download_enable": "Whether to enable download function",
|
||||
"download_path_title": "Define the path to downloading",
|
||||
"download_path": "Download path",
|
||||
"download_path_label": "Current: ",
|
||||
"download_path_open_label": "Click to open this path",
|
||||
"download_path_change_btn": "Change",
|
||||
"download_name_title": "Select the music naming method for downloading",
|
||||
"download_name": "Music file naming",
|
||||
"download_data_embed": "Whether to embed the following content in the audio file",
|
||||
"download_embed_pic": "Embedding cover",
|
||||
"download_embed_lyric": "Embedding lyric",
|
||||
"download_lyric_title": "Select whether to download the lyrics file",
|
||||
"download_embed_pic": "Embedding cover",
|
||||
"download_enable": "Whether to enable download function",
|
||||
"download_lyric": "Lyrics download",
|
||||
"download_lyric_format": "Downloaded lyrics file encoding format",
|
||||
"download_lyric_format_gbk": "GBK (Try to select this format when Chinese garbled characters appear on some devices)",
|
||||
"download_lyric_format_utf8": "UTF-8",
|
||||
"download_lyric_title": "Select whether to download the lyrics file",
|
||||
"download_name": "Music file naming",
|
||||
"download_name1": "Title - Artist",
|
||||
"download_name2": "Artist - Title",
|
||||
"download_name3": "Title only",
|
||||
"download_use_other_source": "Automatically change the source to download (when the song cannot be downloaded from the original source, try to switch to another source to download. Note: this function does not 100% guarantee that the version of the song after the source is changed is the same as the original version)",
|
||||
"download_name_title": "Select the music naming method for downloading",
|
||||
"download_path": "Download path",
|
||||
"download_path_change_btn": "Change",
|
||||
"download_path_label": "Current: ",
|
||||
"download_path_open_label": "Click to open this path",
|
||||
"download_path_title": "Define the path to downloading",
|
||||
"download_select_save_path": "Select the save path",
|
||||
|
||||
"download_use_other_source": "Automatically change the source to download (when the song cannot be downloaded from the original source, try to switch to another source to download. Note: this function does not 100% guarantee that the version of the song after the source is changed is the same as the original version)",
|
||||
"hot_key": "Shortcut Key Settings",
|
||||
"hot_key_local_title": "Shortcut Keys in Software",
|
||||
"hot_key_global_title": "Global Shortcut Key",
|
||||
"hot_key_tip_input": "Please enter a new key",
|
||||
"hot_key_unset_input": "Not Set",
|
||||
"hot_key_common_toggle_close": "Quit Program",
|
||||
"hot_key_common_toggle_min": "Minimize/Restore Program",
|
||||
"hot_key_common_min": "Minimize the program",
|
||||
"hot_key_common_toggle_hide": "Show/Hide Program",
|
||||
"hot_key_common_focus_search_input": "Focus Search Box",
|
||||
"hot_key_player_toggle_play": "Play/Pause Control",
|
||||
"hot_key_player_prev": "Previous Song",
|
||||
"hot_key_common_min": "Minimize the program",
|
||||
"hot_key_common_toggle_close": "Quit Program",
|
||||
"hot_key_common_toggle_hide": "Show/Hide Program",
|
||||
"hot_key_common_toggle_min": "Minimize/Restore Program",
|
||||
"hot_key_desktop_lyric_toggle_always_top": "Top Desktop Lyrics Switch",
|
||||
"hot_key_desktop_lyric_toggle_lock": "Desktop Lyric Lock Switch",
|
||||
"hot_key_desktop_lyric_toggle_visible": "Turn on/off desktop lyrics",
|
||||
"hot_key_global_title": "Global Shortcut Key",
|
||||
"hot_key_local_title": "Shortcut Keys in Software",
|
||||
"hot_key_player_next": "Next Song",
|
||||
"hot_key_player_volume_up": "Increase Volume",
|
||||
"hot_key_player_prev": "Previous Song",
|
||||
"hot_key_player_toggle_play": "Play/Pause Control",
|
||||
"hot_key_player_volume_down": "Reduce Volume",
|
||||
"hot_key_player_volume_mute": "Mute Switch",
|
||||
"hot_key_desktop_lyric_toggle_visible": "Turn on/off desktop lyrics",
|
||||
"hot_key_desktop_lyric_toggle_lock": "Desktop Lyric Lock Switch",
|
||||
"hot_key_desktop_lyric_toggle_always_top": "Top Desktop Lyrics Switch",
|
||||
|
||||
"hot_key_player_volume_up": "Increase Volume",
|
||||
"hot_key_tip_input": "Please enter a new key",
|
||||
"hot_key_unset_input": "Not Set",
|
||||
"is_enable": "Enabled",
|
||||
"is_show": "Showed",
|
||||
"list": "List",
|
||||
"list_add_music_location_type": "Position when adding a song to the list",
|
||||
"list_add_music_location_type_bottom": "Bottom",
|
||||
"list_add_music_location_type_top": "Top",
|
||||
"list_scroll": "Remember the position of the scroll bar of the playlist (only valid for my music classification)",
|
||||
"list_source": "Show song source (only valid for my music category)",
|
||||
"network": "Network",
|
||||
"network_proxy_title": "HTTP Proxy (False setting would block Internet connections)",
|
||||
"network_proxy_host": "Host",
|
||||
"network_proxy_port": "Port",
|
||||
"network_proxy_username": "Username",
|
||||
"network_proxy_password": "Password",
|
||||
|
||||
"network_proxy_port": "Port",
|
||||
"network_proxy_title": "HTTP Proxy (False setting would block Internet connections)",
|
||||
"network_proxy_username": "Username",
|
||||
"odc": "Auto clear",
|
||||
"odc_clear_search_input": "Clear the search box when you are not searching",
|
||||
"odc_clear_search_list": "Clear the search list when you are not searching",
|
||||
|
||||
"backup": "Backup and restore",
|
||||
"backup_part": "Partial data (list data includes audition list, favorite list, user-defined list, setting data does not include shortcut key settings)",
|
||||
"backup_part_import_list": "Import lists",
|
||||
"backup_part_export_list": "Export lists",
|
||||
"backup_part_import_setting": "Import settings",
|
||||
"backup_part_export_setting": "Export settings",
|
||||
"backup_all": "All data (list data and setting data)",
|
||||
"backup_all_import": "Import",
|
||||
"backup_all_export": "Export",
|
||||
"backup_all_import_desc": "Select a backup file",
|
||||
"backup_all_export_desc": "Select the backup to...",
|
||||
"backup_part_import_setting_desc": "Select the Settings file",
|
||||
"backup_part_export_setting_desc": "Save the list to...",
|
||||
"backup_part_import_list_desc": "Select a list backup",
|
||||
"backup_part_export_list_desc": "Save the list to...",
|
||||
|
||||
"other": "Extras",
|
||||
"other_play_list_cache": "List cache management (links to songs that have been cached in my list, alternative sources for playback, after cleaning up, you need to re-acquire them when you play and download songs, and do not clean up if there are no issues related to song playback)",
|
||||
"other_play_list_cache_clear_btn": "Clear list cache information",
|
||||
"other_resource_cache": "Resource cache management (pictures, audios and other caches, pictures and other resources will need to be downloaded again after cleaning up, it is not recommended to clean up, the software will dynamically manage the cache size according to the disk space)",
|
||||
"other_resource_cache_clear_btn": "Clear resource cache",
|
||||
"other_resource_cache_label": "The software has used cache size: ",
|
||||
"other_tray_theme": "Tray Icon Style",
|
||||
"other_tray_theme_native": "Solid Color",
|
||||
"other_tray_theme_origin": "Primary Color",
|
||||
"other_resource_cache": "Resource cache management (pictures, audios and other caches, pictures and other resources will need to be downloaded again after cleaning up, it is not recommended to clean up, the software will dynamically manage the cache size according to the disk space)",
|
||||
"other_resource_cache_label": "The software has used cache size: ",
|
||||
"other_resource_cache_clear_btn": "Clear resource cache",
|
||||
"other_play_list_cache": "List cache management (links to songs that have been cached in my list, alternative sources for playback, after cleaning up, you need to re-acquire them when you play and download songs, and do not clean up if there are no issues related to song playback)",
|
||||
"other_play_list_cache_clear_btn": "Clear list cache information",
|
||||
|
||||
"play": "Play",
|
||||
"play_lyric_lxlrc": "Use Karaoke-style lyrics playback (if supported)",
|
||||
"play_lyric_transition": "Show lyrics translation",
|
||||
"play_mediaDevice": "Audio output",
|
||||
"play_mediaDevice_remove_stop_play": "Pause the song when the current sound output device is changed",
|
||||
"play_mediaDevice_title": "Select a media device for audio output",
|
||||
"play_quality": "Play 320K quality songs first (if supported)",
|
||||
"play_save_play_time": "Remember playback progress",
|
||||
"play_task_bar": "Show playing progress on the taskbar",
|
||||
"search": "Search",
|
||||
"search_focus_search_box": "Automatically focus the search box on startup",
|
||||
"search_history": "Search history",
|
||||
"search_hot": "Top Searches",
|
||||
"sync": "Data synchronization [This is a test function, it is recommended to back up the playlist before using it for the first time]",
|
||||
"sync_address": "Synchronization service address: {address}",
|
||||
"sync_auth_code": "Connection code: {code}",
|
||||
"sync_device": "Connected devices: {devices}",
|
||||
"sync_enable": "Enable the synchronization function (because the data is transmitted in clear text, please use it under a trusted network)",
|
||||
"sync_port": "Sync port settings",
|
||||
"sync_port_tip": "Please enter the synchronization service port number",
|
||||
"sync_refresh_code": "Refresh the connection code",
|
||||
"update": "Update",
|
||||
"update_latest_label": "Latest version: ",
|
||||
"update_unknown": "Unknown",
|
||||
"update_checking": "Checking for updates...",
|
||||
"update_current_label": "Current version: ",
|
||||
"update_downloading": "Update is found and being downloaded...⏳",
|
||||
"update_progress": "Download progress: ",
|
||||
"update_latest": "The software is up-to-date, enjoy yourself!🥂",
|
||||
"update_open_version_modal_btn": "Open the update window🚀",
|
||||
"update_checking": "Checking for updates...",
|
||||
"update_init": "Processing update...",
|
||||
|
||||
"about": "About lx-music-desktop",
|
||||
|
||||
|
||||
"is_enable": "Enabled",
|
||||
"is_show": "Showed",
|
||||
"click_open": "Click to open",
|
||||
"click_copy": "Click to copy"
|
||||
"update_latest": "The software is up-to-date, enjoy yourself!🥂",
|
||||
"update_latest_label": "Latest version: ",
|
||||
"update_open_version_modal_btn": "Open the update window🚀",
|
||||
"update_progress": "Download progress: ",
|
||||
"update_unknown": "Unknown"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"date_format_second": "{num}秒前",
|
||||
"cancel_button_text": "我不",
|
||||
"confirm_button_text": "好的",
|
||||
"date_format_hour": "{num}小时前",
|
||||
"date_format_minute": "{num}分钟前",
|
||||
"date_format_hour": "{num}小时前"
|
||||
"date_format_second": "{num}秒前"
|
||||
}
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
{
|
||||
"copy_title": "(点击复制)",
|
||||
"volume": "当前音量:",
|
||||
"pause": "暂停",
|
||||
"play": "播放",
|
||||
"prev": "上一首",
|
||||
"next": "下一首",
|
||||
"playing": "播放中...",
|
||||
"stop": "暂停播放",
|
||||
"end": "播放完毕",
|
||||
"refresh_url": "URL过期,正在刷新URL...",
|
||||
"error": "音频加载出错,5 秒后切换下一首",
|
||||
"loading": "音乐加载中...",
|
||||
"buffering": "缓冲中...",
|
||||
"geting_url": "歌曲链接获取中...",
|
||||
"lyric_error": "歌词获取失败",
|
||||
"hide_detail": "隐藏详情页(界面内右键双击可快速隐藏详情页)",
|
||||
"name": "歌曲名:",
|
||||
"singer": "艺术家:",
|
||||
"album": "专辑名:",
|
||||
"add_music_to": "添加当前歌曲到...",
|
||||
"desktop_lyric_on": "开启桌面歌词",
|
||||
"desktop_lyric_off": "关闭桌面歌词",
|
||||
"desktop_lyric_lock": "右击锁定歌词",
|
||||
"desktop_lyric_unlock": "右击解锁歌词",
|
||||
"play_toggle_mode_list_loop": "列表循环",
|
||||
"play_toggle_mode_random": "列表随机",
|
||||
"play_toggle_mode_list": "顺序播放",
|
||||
"play_toggle_mode_single_loop": "单曲循环",
|
||||
"play_toggle_mode_off": "禁用",
|
||||
"pic_tip": "右击在“我的列表”定位当前播放的歌曲",
|
||||
|
||||
"comment_show": "歌曲评论",
|
||||
"comment_hot_loading": "热门评论加载中",
|
||||
"comment_new_loading": "最新评论加载中",
|
||||
"album": "专辑名:",
|
||||
"buffering": "缓冲中...",
|
||||
"comment_hot_load_error": "热门评论加载失败,点击尝试重新加载",
|
||||
"comment_new_load_error": "最新评论加载失败,点击尝试重新加载",
|
||||
"comment_refresh": "刷新评论",
|
||||
"comment_no_content": "暂无评论",
|
||||
"comment_hot_loading": "热门评论加载中",
|
||||
"comment_hot_title": "热门评论",
|
||||
"comment_new_load_error": "最新评论加载失败,点击尝试重新加载",
|
||||
"comment_new_loading": "最新评论加载中",
|
||||
"comment_new_title": "最新评论",
|
||||
"comment_title": "{name} 的评论"
|
||||
"comment_no_content": "暂无评论",
|
||||
"comment_refresh": "刷新评论",
|
||||
"comment_show": "歌曲评论",
|
||||
"comment_title": "{name} 的评论",
|
||||
"copy_title": "(点击复制)",
|
||||
"desktop_lyric_lock": "右击锁定歌词",
|
||||
"desktop_lyric_off": "关闭桌面歌词",
|
||||
"desktop_lyric_on": "开启桌面歌词",
|
||||
"desktop_lyric_unlock": "右击解锁歌词",
|
||||
"end": "播放完毕",
|
||||
"error": "音频加载出错,5 秒后切换下一首",
|
||||
"geting_url": "歌曲链接获取中...",
|
||||
"hide_detail": "隐藏详情页(界面内右键双击可快速隐藏详情页)",
|
||||
"loading": "音乐加载中...",
|
||||
"lyric_error": "歌词获取失败",
|
||||
"lyric_select": "歌词文本选择",
|
||||
"name": "歌曲名:",
|
||||
"next": "下一首",
|
||||
"pause": "暂停",
|
||||
"pic_tip": "右击在“我的列表”定位当前播放的歌曲",
|
||||
"play": "播放",
|
||||
"play_toggle_mode_list": "顺序播放",
|
||||
"play_toggle_mode_list_loop": "列表循环",
|
||||
"play_toggle_mode_off": "禁用",
|
||||
"play_toggle_mode_random": "列表随机",
|
||||
"play_toggle_mode_single_loop": "单曲循环",
|
||||
"playing": "播放中...",
|
||||
"prev": "上一首",
|
||||
"refresh_url": "URL过期,正在刷新URL...",
|
||||
"singer": "艺术家:",
|
||||
"stop": "暂停播放",
|
||||
"volume": "当前音量:"
|
||||
}
|
||||
|
||||
19
src/renderer/lang/zh-cn/material/sync_mode_modal.json
Normal file
19
src/renderer/lang/zh-cn/material/sync_mode_modal.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"merge_btn_local_remote": "本机列表 合并 远程列表",
|
||||
"merge_btn_remote_local": "远程列表 合并 本机列表",
|
||||
"merge_label": "合并",
|
||||
"merge_tip": "合并:",
|
||||
"merge_tip_desc": "将两边的列表合并到一起,相同的歌曲将被去掉(去掉的是被合并者的歌曲),不同的歌曲将被添加。",
|
||||
"other_label": "其他",
|
||||
"other_tip": "其他:",
|
||||
"other_tip_desc": "“仅使用实时同步功能”将不修改双方的列表,仅实时同步操作;“取消同步”将直接断开双方的连接。",
|
||||
"overwrite": "完全覆盖",
|
||||
"overwrite_btn_cancel": "取消同步",
|
||||
"overwrite_btn_local_remote": "本机列表 覆盖 远程列表",
|
||||
"overwrite_btn_none": "仅使用实时同步功能",
|
||||
"overwrite_btn_remote_local": "远程列表 覆盖 本机列表",
|
||||
"overwrite_label": "覆盖",
|
||||
"overwrite_tip": "覆盖:",
|
||||
"overwrite_tip_desc": "被覆盖者与覆盖者列表ID相同的列表将被删除后替换成覆盖者的列表(列表ID不同的列表将被合并到一起),若勾选完全覆盖,则被覆盖者的所有列表将被移除,然后替换成覆盖者的列表。",
|
||||
"title": "选择与 {name} 的列表同步方式"
|
||||
}
|
||||
@@ -1,28 +1,38 @@
|
||||
{
|
||||
"lists_new_list_btn": "新建列表",
|
||||
"lists_new_list_input": "新列表...",
|
||||
"lists_rename": "重命名",
|
||||
"lists_moveup": "上移",
|
||||
"lists_movedown": "下移",
|
||||
"lists_sync": "同步",
|
||||
"lists_remove": "删除",
|
||||
"action": "操作",
|
||||
"album": "专辑",
|
||||
"default_list": "试听列表",
|
||||
"list_add_to": "添加到...",
|
||||
"list_copy_name": "复制歌曲名",
|
||||
"list_download": "下载",
|
||||
"list_move_to": "移动到...",
|
||||
"list_play": "播放",
|
||||
"list_play_later": "稍后播放",
|
||||
"list_copy_name": "复制歌曲名",
|
||||
"list_source_detail": "歌曲详情页",
|
||||
"list_add_to": "添加到...",
|
||||
"list_move_to": "移动到...",
|
||||
"list_sort": "调整位置",
|
||||
"list_download": "下载",
|
||||
"list_search": "搜索",
|
||||
"list_remove": "删除",
|
||||
"default_list": "试听列表",
|
||||
"list_search": "搜索",
|
||||
"list_sort": "调整位置",
|
||||
"list_source_detail": "歌曲详情页",
|
||||
"lists_duplicate": "重复歌曲",
|
||||
"lists_export": "导出",
|
||||
"lists_export_part_desc": "选择列表文件保存位置",
|
||||
"lists_import": "导入",
|
||||
"lists_import_part_button_cancel": "不要啊",
|
||||
"lists_import_part_button_confirm": "覆盖掉",
|
||||
"lists_import_part_confirm": "导入的列表({importName})与本地列表({localName})的ID相同,是否覆盖本地列表?",
|
||||
"lists_import_part_desc": "选择列表文件",
|
||||
"lists_movedown": "下移",
|
||||
"lists_moveup": "上移",
|
||||
"lists_new_list_btn": "新建列表",
|
||||
"lists_new_list_input": "新列表...",
|
||||
"lists_remove": "删除",
|
||||
"lists_remove_tip": "你真的想要移除 {name} 吗?",
|
||||
"lists_remove_tip_button": "是的 没错",
|
||||
"lists_rename": "重命名",
|
||||
"lists_sync": "更新",
|
||||
"loding_list": "加载中...",
|
||||
"love_list": "收藏",
|
||||
"name": "歌曲名",
|
||||
"no_item": "列表竟然是空的...",
|
||||
"singer": "歌手",
|
||||
"album": "专辑",
|
||||
"action": "操作",
|
||||
"time": "时长",
|
||||
"loding_list": "加载中...",
|
||||
"no_item": "列表竟然是空的..."
|
||||
"time": "时长"
|
||||
}
|
||||
|
||||
@@ -1,154 +1,156 @@
|
||||
{
|
||||
"about": "关于洛雪音乐",
|
||||
"backup": "备份与恢复",
|
||||
"backup_all": "所有数据(列表数据与设置数据)",
|
||||
"backup_all_export": "导出",
|
||||
"backup_all_export_desc": "选择备份保存位置",
|
||||
"backup_all_import": "导入",
|
||||
"backup_all_import_desc": "选择备份文件",
|
||||
"backup_part": "部分数据(列表数据包括试听列表、收藏列表、用户自定义列表,设置数据不包括快捷键设置)",
|
||||
"backup_part_export_list": "导出列表",
|
||||
"backup_part_export_list_desc": "选择歌单保存位置",
|
||||
"backup_part_export_setting": "导出设置",
|
||||
"backup_part_export_setting_desc": "选择设置保存位置",
|
||||
"backup_part_import_list": "导入列表",
|
||||
"backup_part_import_list_desc": "选择列表文件",
|
||||
"backup_part_import_setting": "导入设置",
|
||||
"backup_part_import_setting_desc": "选择配置文件",
|
||||
"basic": "基本设置",
|
||||
"basic_theme": "主题颜色",
|
||||
"basic_animation": "弹出层随机动画",
|
||||
"basic_show_animation": "显示切换动画",
|
||||
"basic_source_title": "选择音乐来源",
|
||||
"basic_source_test": "测试接口(几乎软件的所有功能都可用)",
|
||||
"basic_source_temp": "临时接口(软件的某些功能不可用,建议测试接口不可用再使用本接口)",
|
||||
"basic_source": "音乐来源",
|
||||
"basic_sourcename_title": "选择音源名字类型",
|
||||
"basic_sourcename_real": "原名",
|
||||
"basic_sourcename_alias": "别名",
|
||||
"basic_sourcename": "音源名字",
|
||||
"basic_source_status_success": "初始化成功",
|
||||
"basic_source_status_initing": "初始化中",
|
||||
"basic_source_status_failed": "初始化失败",
|
||||
"basic_source_user_api_btn": "自定义源管理",
|
||||
"basic_window_size_title": "设置软件窗口尺寸",
|
||||
"basic_window_size": "窗口尺寸",
|
||||
"basic_window_size_smaller": "较小",
|
||||
"basic_window_size_small": "小",
|
||||
"basic_window_size_medium": "中",
|
||||
"basic_window_size_big": "大",
|
||||
"basic_window_size_larger": "较大",
|
||||
"basic_window_size_oversized": "超大",
|
||||
"basic_window_size_huge": "巨大",
|
||||
"basic_to_tray": "关闭软件时不退出软件将其最小化到系统托盘",
|
||||
"basic_lang_title": "软件显示的语言",
|
||||
"basic_lang": "语言",
|
||||
"basic_control_btn_position": "控制按钮位置",
|
||||
"basic_control_btn_position_left": "左边",
|
||||
"basic_control_btn_position_right": "右边",
|
||||
|
||||
"play": "播放设置",
|
||||
"play_save_play_time": "记住播放进度",
|
||||
"play_lyric_transition": "显示歌词翻译",
|
||||
"play_lyric_lxlrc": "使用卡拉OK式歌词播放(如果支持)",
|
||||
"play_quality": "优先播放320K品质的歌曲(如果支持)",
|
||||
"play_task_bar": "在任务栏上显示当前歌曲播放进度",
|
||||
"play_mediaDevice_title": "选择声音输出的媒体设备",
|
||||
"play_mediaDevice": "音频输出",
|
||||
"play_mediaDevice_remove_stop_play": "当前的声音输出设备被改变时暂停播放歌曲",
|
||||
|
||||
"basic_lang": "语言",
|
||||
"basic_lang_title": "软件显示的语言",
|
||||
"basic_show_animation": "显示切换动画",
|
||||
"basic_source": "音乐来源",
|
||||
"basic_source_status_failed": "初始化失败",
|
||||
"basic_source_status_initing": "初始化中",
|
||||
"basic_source_status_success": "初始化成功",
|
||||
"basic_source_temp": "临时接口(软件的某些功能不可用,建议测试接口不可用再使用本接口)",
|
||||
"basic_source_test": "测试接口(几乎软件的所有功能都可用)",
|
||||
"basic_source_title": "选择音乐来源",
|
||||
"basic_source_user_api_btn": "自定义源管理",
|
||||
"basic_sourcename": "音源名字",
|
||||
"basic_sourcename_alias": "别名",
|
||||
"basic_sourcename_real": "原名",
|
||||
"basic_sourcename_title": "选择音源名字类型",
|
||||
"basic_theme": "主题颜色",
|
||||
"basic_to_tray": "关闭软件时不退出软件将其最小化到系统托盘",
|
||||
"basic_window_size": "窗口尺寸",
|
||||
"basic_window_size_big": "大",
|
||||
"basic_window_size_huge": "巨大",
|
||||
"basic_window_size_larger": "较大",
|
||||
"basic_window_size_medium": "中",
|
||||
"basic_window_size_oversized": "超大",
|
||||
"basic_window_size_small": "小",
|
||||
"basic_window_size_smaller": "较小",
|
||||
"basic_window_size_title": "设置软件窗口尺寸",
|
||||
"click_copy": "点击复制",
|
||||
"click_open": "点击打开",
|
||||
"desktop_lyric": "桌面歌词设置",
|
||||
"desktop_lyric_enable": "显示歌词",
|
||||
"desktop_lyric_lock": "锁定歌词",
|
||||
"desktop_lyric_always_on_top": "使歌词总是在其他窗口之上",
|
||||
"desktop_lyric_enable": "显示歌词",
|
||||
"desktop_lyric_font": "歌词字体",
|
||||
"desktop_lyric_font_default": "默认",
|
||||
"desktop_lyric_lock": "锁定歌词",
|
||||
"desktop_lyric_lock_screen": "不允许歌词窗口拖出主屏幕之外",
|
||||
|
||||
"search": "搜索设置",
|
||||
"search_hot": "显示热门搜索",
|
||||
"search_history": "显示历史搜索记录",
|
||||
"search_focus_search_box": "启动时自动聚焦搜索框",
|
||||
|
||||
"list": "列表设置",
|
||||
"list_source": "显示歌曲源(仅对我的音乐分类有效)",
|
||||
"list_scroll": "记住播放列表滚动条位置(仅对我的音乐分类有效)",
|
||||
|
||||
"download": "下载设置",
|
||||
"download_enable": "是否启用下载功能",
|
||||
"download_path_title": "下载歌曲保存的路径",
|
||||
"download_path": "下载路径",
|
||||
"download_path_label": "当前下载路径:",
|
||||
"download_path_open_label": "点击打开当前路径",
|
||||
"download_path_change_btn": "更改",
|
||||
"download_name_title": "下载歌曲时的命名方式",
|
||||
"download_name": "文件命名方式",
|
||||
"download_data_embed": "是否将以下内容嵌入到音频文件中",
|
||||
"download_embed_pic": "封面嵌入",
|
||||
"download_embed_lyric": "歌词嵌入",
|
||||
"download_lyric_title": "是否同时下载歌词文件",
|
||||
"download_embed_pic": "封面嵌入",
|
||||
"download_enable": "是否启用下载功能",
|
||||
"download_lyric": "歌词下载",
|
||||
"download_lyric_format": "下载的歌词文件编码格式",
|
||||
"download_lyric_format_gbk": "GBK(在某些设备上出现中文乱码时可尝试选择此格式)",
|
||||
"download_lyric_format_utf8": "UTF-8",
|
||||
"download_lyric_title": "是否同时下载歌词文件",
|
||||
"download_name": "文件命名方式",
|
||||
"download_name1": "歌名 - 歌手",
|
||||
"download_name2": "歌手 - 歌名",
|
||||
"download_name3": "歌名",
|
||||
"download_use_other_source": "自动换源下载(当无法从歌曲的原始源下载时,尝试切换到其他源下载,注:此功能不100%保证换源后的歌曲版本与原版一致)",
|
||||
"download_name_title": "下载歌曲时的命名方式",
|
||||
"download_path": "下载路径",
|
||||
"download_path_change_btn": "更改",
|
||||
"download_path_label": "当前下载路径:",
|
||||
"download_path_open_label": "点击打开当前路径",
|
||||
"download_path_title": "下载歌曲保存的路径",
|
||||
"download_select_save_path": "选择歌曲保存路径",
|
||||
|
||||
"download_use_other_source": "自动换源下载(当无法从歌曲的原始源下载时,尝试切换到其他源下载,注:此功能不100%保证换源后的歌曲版本与原版一致)",
|
||||
"hot_key": "快捷键设置",
|
||||
"hot_key_local_title": "软件内快捷键",
|
||||
"hot_key_global_title": "全局快捷键",
|
||||
"hot_key_tip_input": "请输入新的按键",
|
||||
"hot_key_unset_input": "未设置",
|
||||
"hot_key_common_toggle_close": "退出程序",
|
||||
"hot_key_common_toggle_min": "最小化/还原程序",
|
||||
"hot_key_common_min": "最小化程序",
|
||||
"hot_key_common_toggle_hide": "显示/隐藏程序",
|
||||
"hot_key_common_focus_search_input": "聚焦搜索框",
|
||||
"hot_key_player_toggle_play": "播放/暂停控制",
|
||||
"hot_key_player_prev": "上一首歌曲",
|
||||
"hot_key_common_min": "最小化程序",
|
||||
"hot_key_common_toggle_close": "退出程序",
|
||||
"hot_key_common_toggle_hide": "显示/隐藏程序",
|
||||
"hot_key_common_toggle_min": "最小化/还原程序",
|
||||
"hot_key_desktop_lyric_toggle_always_top": "桌面歌词置顶切换",
|
||||
"hot_key_desktop_lyric_toggle_lock": "桌面歌词锁定切换",
|
||||
"hot_key_desktop_lyric_toggle_visible": "开/关桌面歌词",
|
||||
"hot_key_global_title": "全局快捷键",
|
||||
"hot_key_local_title": "软件内快捷键",
|
||||
"hot_key_player_next": "下一首歌曲",
|
||||
"hot_key_player_volume_up": "增加音量",
|
||||
"hot_key_player_prev": "上一首歌曲",
|
||||
"hot_key_player_toggle_play": "播放/暂停控制",
|
||||
"hot_key_player_volume_down": "减少音量",
|
||||
"hot_key_player_volume_mute": "静音切换",
|
||||
"hot_key_desktop_lyric_toggle_visible": "开/关桌面歌词",
|
||||
"hot_key_desktop_lyric_toggle_lock": "桌面歌词锁定切换",
|
||||
"hot_key_desktop_lyric_toggle_always_top": "桌面歌词置顶切换",
|
||||
|
||||
"hot_key_player_volume_up": "增加音量",
|
||||
"hot_key_tip_input": "请输入新的按键",
|
||||
"hot_key_unset_input": "未设置",
|
||||
"is_enable": "是否启用",
|
||||
"is_show": "是否显示",
|
||||
"list": "列表设置",
|
||||
"list_add_music_location_type": "添加歌曲到列表时的位置",
|
||||
"list_add_music_location_type_bottom": "底部",
|
||||
"list_add_music_location_type_top": "顶部",
|
||||
"list_scroll": "记住播放列表滚动条位置(仅对我的音乐分类有效)",
|
||||
"list_source": "显示歌曲源(仅对我的音乐分类有效)",
|
||||
"network": "网络设置",
|
||||
"network_proxy_title": "HTTP代理设置(乱设置软件将无法联网)",
|
||||
"network_proxy_host": "主机",
|
||||
"network_proxy_port": "端口",
|
||||
"network_proxy_username": "用户名",
|
||||
"network_proxy_password": "密码",
|
||||
|
||||
"network_proxy_port": "端口",
|
||||
"network_proxy_title": "HTTP代理设置(乱设置软件将无法联网)",
|
||||
"network_proxy_username": "用户名",
|
||||
"odc": "强迫症设置",
|
||||
"odc_clear_search_input": "离开搜索界面时清空搜索框",
|
||||
"odc_clear_search_list": "离开搜索界面时清空搜索列表",
|
||||
|
||||
"backup": "备份与恢复",
|
||||
"backup_part": "部分数据(列表数据包括试听列表、收藏列表、用户自定义列表,设置数据不包括快捷键设置)",
|
||||
"backup_part_import_list": "导入列表",
|
||||
"backup_part_export_list": "导出列表",
|
||||
"backup_part_import_setting": "导入设置",
|
||||
"backup_part_export_setting": "导出设置",
|
||||
"backup_all": "所有数据(列表数据与设置数据)",
|
||||
"backup_all_import": "导入",
|
||||
"backup_all_export": "导出",
|
||||
"backup_all_import_desc": "选择备份文件",
|
||||
"backup_all_export_desc": "选择备份保存位置",
|
||||
"backup_part_import_setting_desc": "选择配置文件",
|
||||
"backup_part_export_setting_desc": "选择设置保存位置",
|
||||
"backup_part_import_list_desc": "选择列表文件",
|
||||
"backup_part_export_list_desc": "选择歌单保存位置",
|
||||
|
||||
"other": "其他",
|
||||
"other_play_list_cache": "列表缓存管理(我的列表中已缓存的歌曲链接、播放代替源,清理后播放、下载歌曲时需要重新获取,没有歌曲播放相关的问题不要清理)",
|
||||
"other_play_list_cache_clear_btn": "清理列表缓存信息",
|
||||
"other_resource_cache": "资源缓存管理(图片、音频等缓存,清理后图片等资源将需要重新下载,不建议清理,软件会根据磁盘空间动态管理缓存大小)",
|
||||
"other_resource_cache_clear_btn": "清理资源缓存",
|
||||
"other_resource_cache_label": "软件已使用缓存大小:",
|
||||
"other_tray_theme": "托盘图标样式",
|
||||
"other_tray_theme_native": "纯色",
|
||||
"other_tray_theme_origin": "原色",
|
||||
"other_resource_cache": "资源缓存管理(图片、音频等缓存,清理后图片等资源将需要重新下载,不建议清理,软件会根据磁盘空间动态管理缓存大小)",
|
||||
"other_resource_cache_label": "软件已使用缓存大小:",
|
||||
"other_resource_cache_clear_btn": "清理资源缓存",
|
||||
"other_play_list_cache": "列表缓存管理(我的列表中已缓存的歌曲链接、播放代替源,清理后播放、下载歌曲时需要重新获取,没有歌曲播放相关的问题不要清理)",
|
||||
"other_play_list_cache_clear_btn": "清理列表缓存信息",
|
||||
|
||||
"play": "播放设置",
|
||||
"play_lyric_lxlrc": "使用卡拉OK式歌词播放(如果支持)",
|
||||
"play_lyric_transition": "显示歌词翻译",
|
||||
"play_mediaDevice": "音频输出",
|
||||
"play_mediaDevice_remove_stop_play": "当前的声音输出设备被改变时暂停播放歌曲",
|
||||
"play_mediaDevice_title": "选择声音输出的媒体设备",
|
||||
"play_quality": "优先播放320K品质的歌曲(如果支持)",
|
||||
"play_save_play_time": "记住播放进度",
|
||||
"play_task_bar": "在任务栏上显示当前歌曲播放进度",
|
||||
"search": "搜索设置",
|
||||
"search_focus_search_box": "启动时自动聚焦搜索框",
|
||||
"search_history": "显示历史搜索记录",
|
||||
"search_hot": "显示热门搜索",
|
||||
"sync": "数据同步 [此为测试功能,首次使用前建议先备份一次歌单]",
|
||||
"sync_address": "同步服务地址:{address}",
|
||||
"sync_auth_code": "连接码:{code}",
|
||||
"sync_device": "已连接的设备:{devices}",
|
||||
"sync_enable": "启用同步功能(由于数据是明文传输,请在受信任的网络下使用)",
|
||||
"sync_port": "同步端口设置",
|
||||
"sync_port_tip": "请输入同步服务端口号",
|
||||
"sync_refresh_code": "刷新连接码",
|
||||
"update": "软件更新",
|
||||
"update_latest_label": "最新版本:",
|
||||
"update_unknown": "未知",
|
||||
"update_checking": "检查更新中...",
|
||||
"update_current_label": "当前版本:",
|
||||
"update_downloading": "发现新版本并在努力下载中,请稍后...⏳",
|
||||
"update_progress": "下载进度:",
|
||||
"update_latest": "软件已是最新,尽情地体验吧~🥂",
|
||||
"update_open_version_modal_btn": "打开更新窗口 🚀",
|
||||
"update_checking": "检查更新中...",
|
||||
"update_init": "处理更新中...",
|
||||
|
||||
"about": "关于洛雪音乐",
|
||||
|
||||
|
||||
"is_enable": "是否启用",
|
||||
"is_show": "是否显示",
|
||||
"click_open": "点击打开",
|
||||
"click_copy": "点击复制"
|
||||
"update_latest": "软件已是最新,尽情地体验吧~🥂",
|
||||
"update_latest_label": "最新版本:",
|
||||
"update_open_version_modal_btn": "打开更新窗口 🚀",
|
||||
"update_progress": "下载进度:",
|
||||
"update_unknown": "未知"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"date_format_second": "{num}秒前",
|
||||
"cancel_button_text": "取消",
|
||||
"confirm_button_text": "好的",
|
||||
"date_format_hour": "{num}小時前",
|
||||
"date_format_minute": "{num}分鐘前",
|
||||
"date_format_hour": "{num}小時前"
|
||||
"date_format_second": "{num}秒前"
|
||||
}
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
{
|
||||
"copy_title": "(點擊複製)",
|
||||
"volume": "當前音量:",
|
||||
"pause": "暫停",
|
||||
"play": "播放",
|
||||
"prev": "上一首",
|
||||
"next": "下一首",
|
||||
"playing": "播放中...",
|
||||
"stop": "暫停播放",
|
||||
"end": "播放完畢",
|
||||
"refresh_url": "URL過期,正在刷新URL...",
|
||||
"error": "音頻加載出錯,5 秒後切換下一首",
|
||||
"loading": "音樂加載中...",
|
||||
"buffering": "緩衝中...",
|
||||
"geting_url": "歌曲鏈接獲取中...",
|
||||
"lyric_error": "歌詞獲取失敗",
|
||||
"hide_detail": "隱藏詳情頁(界面內右鍵雙擊可快速隱藏詳情頁)",
|
||||
"name": "歌曲名:",
|
||||
"singer": "藝術家:",
|
||||
"album": "專輯名:",
|
||||
"add_music_to": "添加當前歌曲到...",
|
||||
"desktop_lyric_on": "開啟桌面歌詞",
|
||||
"desktop_lyric_off": "關閉桌面歌詞",
|
||||
"desktop_lyric_lock": "右擊鎖定歌詞",
|
||||
"desktop_lyric_unlock": "右擊解鎖歌詞",
|
||||
"play_toggle_mode_list_loop": "列表循環",
|
||||
"play_toggle_mode_random": "列表隨機",
|
||||
"play_toggle_mode_list": "順序播放",
|
||||
"play_toggle_mode_single_loop": "單曲循環",
|
||||
"play_toggle_mode_off": "禁用",
|
||||
"pic_tip": "右擊在“我的列表”定位當前播放的歌曲",
|
||||
|
||||
"comment_show": "歌曲評論",
|
||||
"comment_hot_loading": "熱門評論加載中",
|
||||
"comment_new_loading": "最新評論加載中",
|
||||
"album": "專輯名:",
|
||||
"buffering": "緩衝中...",
|
||||
"comment_hot_load_error": "熱門評論加載失敗,點擊嘗試重新加載",
|
||||
"comment_new_load_error": "最新評論加載失敗,點擊嘗試重新加載",
|
||||
"comment_refresh": "刷新評論",
|
||||
"comment_no_content": "暫無評論",
|
||||
"comment_hot_loading": "熱門評論加載中",
|
||||
"comment_hot_title": "熱門評論",
|
||||
"comment_new_load_error": "最新評論加載失敗,點擊嘗試重新加載",
|
||||
"comment_new_loading": "最新評論加載中",
|
||||
"comment_new_title": "最新評論",
|
||||
"comment_title": "{name} 的評論"
|
||||
"comment_no_content": "暫無評論",
|
||||
"comment_refresh": "刷新評論",
|
||||
"comment_show": "歌曲評論",
|
||||
"comment_title": "{name} 的評論",
|
||||
"copy_title": "(點擊複製)",
|
||||
"desktop_lyric_lock": "右擊鎖定歌詞",
|
||||
"desktop_lyric_off": "關閉桌面歌詞",
|
||||
"desktop_lyric_on": "開啟桌面歌詞",
|
||||
"desktop_lyric_unlock": "右擊解鎖歌詞",
|
||||
"end": "播放完畢",
|
||||
"error": "音頻加載出錯,5 秒後切換下一首",
|
||||
"geting_url": "歌曲鏈接獲取中...",
|
||||
"hide_detail": "隱藏詳情頁(界面內右鍵雙擊可快速隱藏詳情頁)",
|
||||
"loading": "音樂加載中...",
|
||||
"lyric_error": "歌詞獲取失敗",
|
||||
"lyric_select": "歌詞文本選擇",
|
||||
"name": "歌曲名:",
|
||||
"next": "下一首",
|
||||
"pause": "暫停",
|
||||
"pic_tip": "右擊在“我的列表”定位當前播放的歌曲",
|
||||
"play": "播放",
|
||||
"play_toggle_mode_list": "順序播放",
|
||||
"play_toggle_mode_list_loop": "列表循環",
|
||||
"play_toggle_mode_off": "禁用",
|
||||
"play_toggle_mode_random": "列表隨機",
|
||||
"play_toggle_mode_single_loop": "單曲循環",
|
||||
"playing": "播放中...",
|
||||
"prev": "上一首",
|
||||
"refresh_url": "URL過期,正在刷新URL...",
|
||||
"singer": "藝術家:",
|
||||
"stop": "暫停播放",
|
||||
"volume": "當前音量:"
|
||||
}
|
||||
|
||||
19
src/renderer/lang/zh-tw/material/sync_mode_modal.json
Normal file
19
src/renderer/lang/zh-tw/material/sync_mode_modal.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"merge_btn_local_remote": "本機列表 合併 遠程列表",
|
||||
"merge_btn_remote_local": "遠程列表 合併 本機列表",
|
||||
"merge_label": "合併",
|
||||
"merge_tip": "合併:",
|
||||
"merge_tip_desc": "將兩邊的列表合併到一起,相同的歌曲將被去掉(去掉的是被合併者的歌曲),不同的歌曲將被添加。",
|
||||
"other_label": "其他",
|
||||
"other_tip": "其他:",
|
||||
"other_tip_desc": "“僅使用實時同步功能”將不修改雙方的列表,僅實時同步操作;“取消同步”將直接斷開雙方的連接。",
|
||||
"overwrite": "完全覆蓋",
|
||||
"overwrite_btn_cancel": "取消同步",
|
||||
"overwrite_btn_local_remote": "本機列表 覆蓋 遠程列表",
|
||||
"overwrite_btn_none": "僅使用實時同步功能",
|
||||
"overwrite_btn_remote_local": "遠程列表 覆蓋 本機列表",
|
||||
"overwrite_label": "覆蓋",
|
||||
"overwrite_tip": "覆蓋:",
|
||||
"overwrite_tip_desc": "被覆蓋者與覆蓋者列表ID相同的列表將被刪除後替換成覆蓋者的列表(列表ID不同的列表將被合併到一起),若勾選完全覆蓋,則被覆蓋者的所有列表將被移除,然後替換成覆蓋者的列表。",
|
||||
"title": "選擇與 {name} 的列表同步方式"
|
||||
}
|
||||
@@ -1,28 +1,38 @@
|
||||
{
|
||||
"lists_new_list_btn": "新建列表",
|
||||
"lists_new_list_input": "新列表...",
|
||||
"lists_rename": "重命名",
|
||||
"lists_moveup": "上移",
|
||||
"lists_movedown": "下移",
|
||||
"lists_sync": "同步",
|
||||
"lists_remove": "刪除",
|
||||
"action": "操作",
|
||||
"album": "專輯",
|
||||
"default_list": "試聽列表",
|
||||
"list_add_to": "添加到...",
|
||||
"list_copy_name": "複製歌曲名",
|
||||
"list_download": "下載",
|
||||
"list_move_to": "移動到...",
|
||||
"list_play": "播放",
|
||||
"list_play_later": "稍後播放",
|
||||
"list_copy_name": "複製歌曲名",
|
||||
"list_add_to": "添加到...",
|
||||
"list_move_to": "移動到...",
|
||||
"list_sort": "調整位置",
|
||||
"list_download": "下載",
|
||||
"list_search": "搜索",
|
||||
"list_remove": "刪除",
|
||||
"list_search": "搜索",
|
||||
"list_sort": "調整位置",
|
||||
"list_source_detail": "歌曲詳情頁",
|
||||
"default_list": "試聽列表",
|
||||
"lists_duplicate": "重複歌曲",
|
||||
"lists_export": "導出",
|
||||
"lists_export_part_desc": "選擇列表文件保存位置",
|
||||
"lists_import": "導入",
|
||||
"lists_import_part_button_cancel": "不要啊",
|
||||
"lists_import_part_button_confirm": "覆蓋掉",
|
||||
"lists_import_part_confirm": "導入的列表({importName})與本地列表({localName})的ID相同,是否覆蓋本地列表?",
|
||||
"lists_import_part_desc": "選擇列表文件",
|
||||
"lists_movedown": "下移",
|
||||
"lists_moveup": "上移",
|
||||
"lists_new_list_btn": "新建列表",
|
||||
"lists_new_list_input": "新列表...",
|
||||
"lists_remove": "刪除",
|
||||
"lists_remove_tip": "你真的想要移除 {name} 嗎?",
|
||||
"lists_remove_tip_button": "是的 沒錯",
|
||||
"lists_rename": "重命名",
|
||||
"lists_sync": "更新",
|
||||
"loding_list": "加載中...",
|
||||
"love_list": "收藏列表",
|
||||
"name": "歌曲名",
|
||||
"no_item": "列表竟然是空的...",
|
||||
"singer": "歌手",
|
||||
"album": "專輯",
|
||||
"action": "操作",
|
||||
"time": "時長",
|
||||
"loding_list": "加載中...",
|
||||
"no_item": "列表竟然是空的..."
|
||||
"time": "時長"
|
||||
}
|
||||
|
||||
@@ -1,154 +1,156 @@
|
||||
{
|
||||
"about": "關於洛雪音樂",
|
||||
"backup": "備份與恢復",
|
||||
"backup_all": "所有數據(列表數據與設置數據)",
|
||||
"backup_all_export": "導出",
|
||||
"backup_all_export_desc": "選擇備份保存位置",
|
||||
"backup_all_import": "導入",
|
||||
"backup_all_import_desc": "選擇備份文件",
|
||||
"backup_part": "部分數據(列表數據包括試聽列表、收藏列表、用戶自定義列表,設置數據不包括快捷鍵設置)",
|
||||
"backup_part_export_list": "導出列表",
|
||||
"backup_part_export_list_desc": "選擇歌單保存位置",
|
||||
"backup_part_export_setting": "導出設置",
|
||||
"backup_part_export_setting_desc": "選擇設置保存位置",
|
||||
"backup_part_import_list": "導入列表",
|
||||
"backup_part_import_list_desc": "選擇列表文件",
|
||||
"backup_part_import_setting": "導入設置",
|
||||
"backup_part_import_setting_desc": "選擇配置文件",
|
||||
"basic": "基本設置",
|
||||
"basic_theme": "主題顏色",
|
||||
"basic_animation": "彈出層隨機動畫",
|
||||
"basic_show_animation": "顯示切換動畫",
|
||||
"basic_source_title": "選擇音樂來源",
|
||||
"basic_source_test": "測試接口(幾乎軟件的所有功能都可用)",
|
||||
"basic_source_temp": "臨時接口(軟件的某些功能不可用,建議測試接口不可用再使用本接口)",
|
||||
"basic_source": "音樂來源",
|
||||
"basic_sourcename_title": "選擇音源名字類型",
|
||||
"basic_sourcename_real": "原名",
|
||||
"basic_sourcename_alias": "別名",
|
||||
"basic_sourcename": "音源名字",
|
||||
"basic_source_status_success": "初始化成功",
|
||||
"basic_source_status_initing": "初始化中",
|
||||
"basic_source_status_failed": "初始化失敗",
|
||||
"basic_source_user_api_btn": "自定義源管理",
|
||||
"basic_window_size_title": "設置軟件窗口尺寸",
|
||||
"basic_window_size": "窗口尺寸",
|
||||
"basic_window_size_smaller": "較小",
|
||||
"basic_window_size_small": "小",
|
||||
"basic_window_size_medium": "中",
|
||||
"basic_window_size_big": "大",
|
||||
"basic_window_size_larger": "較大",
|
||||
"basic_window_size_oversized": "超大",
|
||||
"basic_window_size_huge": "巨大",
|
||||
"basic_to_tray": "關閉軟件時不退出軟件將其最小化到系統托盤",
|
||||
"basic_lang_title": "軟件顯示的語言",
|
||||
"basic_lang": "語言",
|
||||
"basic_control_btn_position": "控制按鈕位置",
|
||||
"basic_control_btn_position_left": "左邊",
|
||||
"basic_control_btn_position_right": "右邊",
|
||||
|
||||
"play": "播放設置",
|
||||
"play_save_play_time": "記住播放進度",
|
||||
"play_lyric_transition": "顯示歌詞翻譯",
|
||||
"play_lyric_lxlrc": "使用卡拉OK式歌詞播放(如果支持)",
|
||||
"play_quality": "優先播放320K品質的歌曲(如果支持)",
|
||||
"play_task_bar": "在任務欄上顯示當前歌曲播放進度",
|
||||
"play_mediaDevice_title": "選擇聲音輸出的媒體設備",
|
||||
"play_mediaDevice": "音頻輸出",
|
||||
"play_mediaDevice_remove_stop_play": "當前的聲音輸出設備被改變時暫停播放歌曲",
|
||||
|
||||
"basic_lang": "語言",
|
||||
"basic_lang_title": "軟件顯示的語言",
|
||||
"basic_show_animation": "顯示切換動畫",
|
||||
"basic_source": "音樂來源",
|
||||
"basic_source_status_failed": "初始化失敗",
|
||||
"basic_source_status_initing": "初始化中",
|
||||
"basic_source_status_success": "初始化成功",
|
||||
"basic_source_temp": "臨時接口(軟件的某些功能不可用,建議測試接口不可用再使用本接口)",
|
||||
"basic_source_test": "測試接口(幾乎軟件的所有功能都可用)",
|
||||
"basic_source_title": "選擇音樂來源",
|
||||
"basic_source_user_api_btn": "自定義源管理",
|
||||
"basic_sourcename": "音源名字",
|
||||
"basic_sourcename_alias": "別名",
|
||||
"basic_sourcename_real": "原名",
|
||||
"basic_sourcename_title": "選擇音源名字類型",
|
||||
"basic_theme": "主題顏色",
|
||||
"basic_to_tray": "關閉軟件時不退出軟件將其最小化到系統托盤",
|
||||
"basic_window_size": "窗口尺寸",
|
||||
"basic_window_size_big": "大",
|
||||
"basic_window_size_huge": "巨大",
|
||||
"basic_window_size_larger": "較大",
|
||||
"basic_window_size_medium": "中",
|
||||
"basic_window_size_oversized": "超大",
|
||||
"basic_window_size_small": "小",
|
||||
"basic_window_size_smaller": "較小",
|
||||
"basic_window_size_title": "設置軟件窗口尺寸",
|
||||
"click_copy": "點擊複製",
|
||||
"click_open": "點擊打開",
|
||||
"desktop_lyric": "桌面歌詞設置",
|
||||
"desktop_lyric_enable": "顯示歌詞",
|
||||
"desktop_lyric_lock": "鎖定歌詞",
|
||||
"desktop_lyric_always_on_top": "使歌詞總是在其他窗口之上",
|
||||
"desktop_lyric_enable": "顯示歌詞",
|
||||
"desktop_lyric_font": "歌詞字體",
|
||||
"desktop_lyric_font_default": "默認",
|
||||
"desktop_lyric_lock": "鎖定歌詞",
|
||||
"desktop_lyric_lock_screen": "不允許歌詞窗口拖出主屏幕之外",
|
||||
|
||||
"search": "搜索設置",
|
||||
"search_hot": "顯示熱門搜索",
|
||||
"search_history": "顯示歷史搜索記錄",
|
||||
"search_focus_search_box": "啟動時自動聚焦搜索框",
|
||||
|
||||
"list": "列表設置",
|
||||
"list_source": "顯示歌曲源(僅對我的音樂分類有效)",
|
||||
"list_scroll": "記住播放列表滾動條位置(僅對我的音樂分類有效)",
|
||||
|
||||
"download": "下載設置",
|
||||
"download_enable": "是否啟用下載功能",
|
||||
"download_path_title": "下載歌曲保存的路徑",
|
||||
"download_path": "下載路徑",
|
||||
"download_path_label": "當前下載路徑:",
|
||||
"download_path_open_label": "點擊打開當前路徑",
|
||||
"download_path_change_btn": "更改",
|
||||
"download_name_title": "下載歌曲時的命名方式",
|
||||
"download_name": "文件命名方式",
|
||||
"download_data_embed": "是否將以下內容嵌入到音頻文件中",
|
||||
"download_embed_pic": "封面嵌入",
|
||||
"download_embed_lyric": "歌詞嵌入",
|
||||
"download_lyric_title": "是否同時下載歌詞文件",
|
||||
"download_embed_pic": "封面嵌入",
|
||||
"download_enable": "是否啟用下載功能",
|
||||
"download_lyric": "歌詞下載",
|
||||
"download_lyric_format": "下載的歌詞文件編碼格式",
|
||||
"download_lyric_format_gbk": "GBK(在某些設備上出現中文亂碼時可嘗試選擇此格式)",
|
||||
"download_lyric_format_utf8": "UTF-8",
|
||||
"download_lyric_title": "是否同時下載歌詞文件",
|
||||
"download_name": "文件命名方式",
|
||||
"download_name1": "歌名 - 歌手",
|
||||
"download_name2": "歌手 - 歌名",
|
||||
"download_name3": "歌名",
|
||||
"download_use_other_source": "自動換源下載(當無法從歌曲的原始源下載時,嘗試切換到其他源下載,注:此功能不100%保證換源後的歌曲版本與原版一致)",
|
||||
"download_name_title": "下載歌曲時的命名方式",
|
||||
"download_path": "下載路徑",
|
||||
"download_path_change_btn": "更改",
|
||||
"download_path_label": "當前下載路徑:",
|
||||
"download_path_open_label": "點擊打開當前路徑",
|
||||
"download_path_title": "下載歌曲保存的路徑",
|
||||
"download_select_save_path": "選擇歌曲保存路徑",
|
||||
|
||||
"download_use_other_source": "自動換源下載(當無法從歌曲的原始源下載時,嘗試切換到其他源下載,注:此功能不100%保證換源後的歌曲版本與原版一致)",
|
||||
"hot_key": "快捷鍵設置",
|
||||
"hot_key_local_title": "軟件內快捷鍵",
|
||||
"hot_key_global_title": "全局快捷鍵",
|
||||
"hot_key_tip_input": "請輸入新的按鍵",
|
||||
"hot_key_unset_input": "未設置",
|
||||
"hot_key_common_toggle_close": "退出程序",
|
||||
"hot_key_common_toggle_min": "最小化/還原程序",
|
||||
"hot_key_common_min": "最小化程序",
|
||||
"hot_key_common_toggle_hide": "顯示/隱藏程序",
|
||||
"hot_key_common_focus_search_input": "聚焦搜索框",
|
||||
"hot_key_player_toggle_play": "播放/暫停控制",
|
||||
"hot_key_player_prev": "上一首歌曲",
|
||||
"hot_key_common_min": "最小化程序",
|
||||
"hot_key_common_toggle_close": "退出程序",
|
||||
"hot_key_common_toggle_hide": "顯示/隱藏程序",
|
||||
"hot_key_common_toggle_min": "最小化/還原程序",
|
||||
"hot_key_desktop_lyric_toggle_always_top": "桌面歌詞置頂切換",
|
||||
"hot_key_desktop_lyric_toggle_lock": "桌面歌詞鎖定切換",
|
||||
"hot_key_desktop_lyric_toggle_visible": "開/關桌面歌詞",
|
||||
"hot_key_global_title": "全局快捷鍵",
|
||||
"hot_key_local_title": "軟件內快捷鍵",
|
||||
"hot_key_player_next": "下一首歌曲",
|
||||
"hot_key_player_volume_up": "增加音量",
|
||||
"hot_key_player_prev": "上一首歌曲",
|
||||
"hot_key_player_toggle_play": "播放/暫停控制",
|
||||
"hot_key_player_volume_down": "減少音量",
|
||||
"hot_key_player_volume_mute": "靜音切換",
|
||||
"hot_key_desktop_lyric_toggle_visible": "開/關桌面歌詞",
|
||||
"hot_key_desktop_lyric_toggle_lock": "桌面歌詞鎖定切換",
|
||||
"hot_key_desktop_lyric_toggle_always_top": "桌面歌詞置頂切換",
|
||||
|
||||
"hot_key_player_volume_up": "增加音量",
|
||||
"hot_key_tip_input": "請輸入新的按鍵",
|
||||
"hot_key_unset_input": "未設置",
|
||||
"is_enable": "是否啟用",
|
||||
"is_show": "是否顯示",
|
||||
"list": "列表設置",
|
||||
"list_add_music_location_type": "添加歌曲到列表時的位置",
|
||||
"list_add_music_location_type_bottom": "底部",
|
||||
"list_add_music_location_type_top": "頂部",
|
||||
"list_scroll": "記住播放列表滾動條位置(僅對我的音樂分類有效)",
|
||||
"list_source": "顯示歌曲源(僅對我的音樂分類有效)",
|
||||
"network": "網絡設置",
|
||||
"network_proxy_title": "HTTP代理設置(亂設置軟件將無法聯網)",
|
||||
"network_proxy_host": "主機",
|
||||
"network_proxy_port": "端口",
|
||||
"network_proxy_username": "用戶名",
|
||||
"network_proxy_password": "密碼",
|
||||
|
||||
"network_proxy_port": "端口",
|
||||
"network_proxy_title": "HTTP代理設置(亂設置軟件將無法聯網)",
|
||||
"network_proxy_username": "用戶名",
|
||||
"odc": "強迫症設置",
|
||||
"odc_clear_search_input": "離開搜索界面時清空搜索框",
|
||||
"odc_clear_search_list": "離開搜索界面時清空搜索列表",
|
||||
|
||||
"backup": "備份與恢復",
|
||||
"backup_part": "部分數據(列表數據包括試聽列表、收藏列表、用戶自定義列表,設置數據不包括快捷鍵設置)",
|
||||
"backup_part_import_list": "導入列表",
|
||||
"backup_part_export_list": "導出列表",
|
||||
"backup_part_import_setting": "導入設置",
|
||||
"backup_part_export_setting": "導出設置",
|
||||
"backup_all": "所有數據(列表數據與設置數據)",
|
||||
"backup_all_import": "導入",
|
||||
"backup_all_export": "導出",
|
||||
"backup_all_import_desc": "選擇備份文件",
|
||||
"backup_all_export_desc": "選擇備份保存位置",
|
||||
"backup_part_import_setting_desc": "選擇配置文件",
|
||||
"backup_part_export_setting_desc": "選擇設置保存位置",
|
||||
"backup_part_import_list_desc": "選擇列表文件",
|
||||
"backup_part_export_list_desc": "選擇歌單保存位置",
|
||||
|
||||
"other": "其他",
|
||||
"other_play_list_cache": "列表緩存管理(我的列表中已緩存的歌曲鏈接、播放代替源,清理後播放、下載歌曲時需要重新獲取,沒有歌曲播放相關的問題不要清理)",
|
||||
"other_play_list_cache_clear_btn": "清理列表緩存信息",
|
||||
"other_resource_cache": "資源緩存管理(圖片、音頻等緩存,清理後圖片等資源將需要重新下載,不建議清理,軟件會根據磁盤空間動態管理緩存大小)",
|
||||
"other_resource_cache_clear_btn": "清理資源緩存",
|
||||
"other_resource_cache_label": "軟件已使用緩存大小:",
|
||||
"other_tray_theme": "托盤圖標樣式",
|
||||
"other_tray_theme_native": "純色",
|
||||
"other_tray_theme_origin": "原色",
|
||||
"other_resource_cache": "資源緩存管理(圖片、音頻等緩存,清理後圖片等資源將需要重新下載,不建議清理,軟件會根據磁盤空間動態管理緩存大小)",
|
||||
"other_resource_cache_label": "軟件已使用緩存大小:",
|
||||
"other_resource_cache_clear_btn": "清理資源緩存",
|
||||
"other_play_list_cache": "列表緩存管理(我的列表中已緩存的歌曲鏈接、播放代替源,清理後播放、下載歌曲時需要重新獲取,沒有歌曲播放相關的問題不要清理)",
|
||||
"other_play_list_cache_clear_btn": "清理列表緩存信息",
|
||||
|
||||
"play": "播放設置",
|
||||
"play_lyric_lxlrc": "使用卡拉OK式歌詞播放(如果支持)",
|
||||
"play_lyric_transition": "顯示歌詞翻譯",
|
||||
"play_mediaDevice": "音頻輸出",
|
||||
"play_mediaDevice_remove_stop_play": "當前的聲音輸出設備被改變時暫停播放歌曲",
|
||||
"play_mediaDevice_title": "選擇聲音輸出的媒體設備",
|
||||
"play_quality": "優先播放320K品質的歌曲(如果支持)",
|
||||
"play_save_play_time": "記住播放進度",
|
||||
"play_task_bar": "在任務欄上顯示當前歌曲播放進度",
|
||||
"search": "搜索設置",
|
||||
"search_focus_search_box": "啟動時自動聚焦搜索框",
|
||||
"search_history": "顯示歷史搜索記錄",
|
||||
"search_hot": "顯示熱門搜索",
|
||||
"sync": "數據同步 [此為測試功能,首次使用前建議先備份一次歌單]",
|
||||
"sync_address": "同步服務地址:{address}",
|
||||
"sync_auth_code": "連接碼:{code}",
|
||||
"sync_device": "已連接的設備:{devices}",
|
||||
"sync_enable": "啟用同步功能(由於數據是明文傳輸,請在受信任的網絡下使用)",
|
||||
"sync_port": "同步端口設置",
|
||||
"sync_port_tip": "請輸入同步服務端口號",
|
||||
"sync_refresh_code": "刷新連接碼",
|
||||
"update": "軟件更新",
|
||||
"update_latest_label": "最新版本:",
|
||||
"update_unknown": "未知",
|
||||
"update_checking": "檢查更新中...",
|
||||
"update_current_label": "當前版本:",
|
||||
"update_downloading": "發現新版本並在努力下載中,請稍後...⏳",
|
||||
"update_progress": "下載進度:",
|
||||
"update_latest": "軟件已是最新,盡情地體驗吧~🥂",
|
||||
"update_open_version_modal_btn": "打開更新窗口 🚀",
|
||||
"update_checking": "檢查更新中...",
|
||||
"update_init": "處理更新中...",
|
||||
|
||||
"about": "關於洛雪音樂",
|
||||
|
||||
|
||||
"is_enable": "是否啟用",
|
||||
"is_show": "是否顯示",
|
||||
"click_open": "點擊打開",
|
||||
"click_copy": "點擊複製"
|
||||
"update_latest": "軟件已是最新,盡情地體驗吧~🥂",
|
||||
"update_latest_label": "最新版本:",
|
||||
"update_open_version_modal_btn": "打開更新窗口 🚀",
|
||||
"update_progress": "下載進度:",
|
||||
"update_unknown": "未知"
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ Vue.config.productionTip = false
|
||||
Vue.config.devtools = process.env.NODE_ENV === 'development'
|
||||
|
||||
getSetting().then(({ setting, version }) => {
|
||||
global.appSetting = setting
|
||||
// Set language automatically
|
||||
if (!window.i18n.availableLocales.includes(setting.langId)) {
|
||||
let langId = null
|
||||
|
||||
74
src/renderer/plugins/Dialog/Dialog.vue
Normal file
74
src/renderer/plugins/Dialog/Dialog.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<Modal :show="visible" @close="handleCancel" @after-leave="afterLeave" :closeBtn="false">
|
||||
<main :class="$style.main">{{message}}</main>
|
||||
<footer :class="$style.footer">
|
||||
<Btn :class="$style.btn" v-if="showCancel" @click="handleCancel">{{cancelBtnText}}</Btn>
|
||||
<Btn :class="$style.btn" @click="handleComfirm">{{confirmBtnText}}</Btn>
|
||||
</footer>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Modal from '@renderer/components/material/Modal'
|
||||
import Btn from '@renderer/components/material/Btn'
|
||||
export default {
|
||||
components: {
|
||||
Modal,
|
||||
Btn,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
message: '',
|
||||
showCancel: false,
|
||||
cancelButtonText: '',
|
||||
confirmButtonText: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
cancelBtnText() {
|
||||
return this.cancelButtonText || this.$t('base.cancel_button_text')
|
||||
},
|
||||
confirmBtnText() {
|
||||
return this.confirmButtonText || this.$t('base.confirm_button_text')
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
const el = this.$el
|
||||
el.parentNode.removeChild(el)
|
||||
},
|
||||
methods: {
|
||||
afterLeave(el, done) {
|
||||
this.$destroy()
|
||||
},
|
||||
handleCancel() {
|
||||
|
||||
},
|
||||
handleComfirm() {
|
||||
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
|
||||
.main {
|
||||
flex: auto;
|
||||
min-height: 40px;
|
||||
padding: 15px;
|
||||
font-size: 14px;
|
||||
max-width: 320px;
|
||||
min-width: 220px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.footer {
|
||||
flex: none;
|
||||
padding: 0 15px 15px;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: flex-end;
|
||||
gap: 15px;
|
||||
}
|
||||
</style>
|
||||
54
src/renderer/plugins/Dialog/index.js
Normal file
54
src/renderer/plugins/Dialog/index.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import Dialog from './Dialog'
|
||||
import i18n from '../i18n'
|
||||
import store from '@renderer/store'
|
||||
import Vue from 'vue'
|
||||
|
||||
const defaultOptions = {
|
||||
message: '',
|
||||
showCancel: false,
|
||||
cancelButtonText: '',
|
||||
confirmButtonText: '',
|
||||
}
|
||||
|
||||
const dialog = {
|
||||
install(Vue, options) {
|
||||
const DialogConstructor = Vue.extend(Dialog)
|
||||
|
||||
const dialog = function Dialog(options) {
|
||||
const { message, showCancel, cancelButtonText, confirmButtonText } =
|
||||
Object.assign({}, defaultOptions, typeof options == 'string' ? { message: options } : options || {})
|
||||
return new Promise((resolve, reject) => {
|
||||
let instance = new DialogConstructor({ i18n, store }).$mount(document.createElement('div'))
|
||||
|
||||
// 属性设置
|
||||
instance.visible = true
|
||||
instance.message = message
|
||||
instance.showCancel = showCancel
|
||||
instance.cancelButtonText = cancelButtonText
|
||||
instance.confirmButtonText = confirmButtonText
|
||||
|
||||
// 挂载
|
||||
document.getElementById('container').appendChild(instance.$el)
|
||||
|
||||
instance.handleCancel = () => {
|
||||
instance.visible = false
|
||||
resolve(false)
|
||||
}
|
||||
|
||||
instance.handleComfirm = () => {
|
||||
instance.visible = false
|
||||
resolve(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
dialog.confirm = options => dialog(
|
||||
typeof options == 'string'
|
||||
? { message: options, showCancel: true }
|
||||
: { ...options, showCancel: true },
|
||||
)
|
||||
|
||||
Vue.prototype.$dialog = dialog
|
||||
},
|
||||
}
|
||||
|
||||
Vue.use(dialog)
|
||||
@@ -3,6 +3,8 @@ import { debounce } from '../../utils'
|
||||
|
||||
let instance
|
||||
let prevTips
|
||||
let prevX = 0
|
||||
let prevY = 0
|
||||
|
||||
const getTips = el =>
|
||||
el
|
||||
@@ -53,6 +55,9 @@ const updateTips = event => {
|
||||
}
|
||||
|
||||
document.body.addEventListener('mousemove', event => {
|
||||
if (event.x == prevX && event.y == prevY) return
|
||||
prevX = event.x
|
||||
prevY = event.y
|
||||
hideTips()
|
||||
showTips(event)
|
||||
})
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
// import './axios'
|
||||
import './Dialog'
|
||||
import './Tips'
|
||||
|
||||
@@ -11,11 +11,14 @@ import {
|
||||
getMusicUrl as getMusicUrlFormStorage,
|
||||
setMusicUrl,
|
||||
assertApiSupport,
|
||||
filterFileName,
|
||||
} from '../../utils'
|
||||
import { NAMES, rendererInvoke } from '@common/ipc'
|
||||
|
||||
window.downloadList = []
|
||||
// state
|
||||
const state = {
|
||||
list: [],
|
||||
list: window.downloadList,
|
||||
waitingList: [],
|
||||
downloadStatus: {
|
||||
RUN: 'run',
|
||||
@@ -31,7 +34,6 @@ const dls = {}
|
||||
const tryNum = {}
|
||||
let isRuningActionTask = false
|
||||
|
||||
const filterFileName = /[\\/:*?#"<>|]/g
|
||||
|
||||
// getters
|
||||
const getters = {
|
||||
@@ -191,13 +193,13 @@ const getMusicUrl = async function(downloadInfo, isUseOtherSource, isRefresh) {
|
||||
return cachedUrl && !isRefresh
|
||||
? cachedUrl
|
||||
: (
|
||||
isUseOtherSource
|
||||
? handleGetMusicUrl.call(this, downloadInfo.musicInfo, downloadInfo.type)
|
||||
: music[downloadInfo.musicInfo.source].getMusicUrl(downloadInfo.musicInfo, downloadInfo.type).promise
|
||||
).then(({ url }) => {
|
||||
setMusicUrl(downloadInfo.musicInfo, downloadInfo.type, url)
|
||||
return url
|
||||
})
|
||||
isUseOtherSource
|
||||
? handleGetMusicUrl.call(this, downloadInfo.musicInfo, downloadInfo.type)
|
||||
: music[downloadInfo.musicInfo.source].getMusicUrl(downloadInfo.musicInfo, downloadInfo.type).promise
|
||||
).then(({ url }) => {
|
||||
setMusicUrl(downloadInfo.musicInfo, downloadInfo.type, url)
|
||||
return url
|
||||
})
|
||||
}
|
||||
const getPic = function(musicInfo, retryedSource = [], originMusic) {
|
||||
// console.log(musicInfo.source)
|
||||
@@ -223,7 +225,8 @@ const getPic = function(musicInfo, retryedSource = [], originMusic) {
|
||||
})
|
||||
})
|
||||
}
|
||||
const getLyric = function(musicInfo, retryedSource = [], originMusic) {
|
||||
|
||||
const handleGetLyric = function(musicInfo, retryedSource = [], originMusic) {
|
||||
if (!originMusic) originMusic = musicInfo
|
||||
let reqPromise
|
||||
try {
|
||||
@@ -247,6 +250,33 @@ const getLyric = function(musicInfo, retryedSource = [], originMusic) {
|
||||
})
|
||||
}
|
||||
|
||||
const getLyric = function(musicInfo, isUseOtherSource) {
|
||||
return getLyricFromStorage(musicInfo).then(lrcInfo => {
|
||||
return (
|
||||
lrcInfo.lyric
|
||||
? Promise.resolve({ lyric: lrcInfo.lyric, tlyric: lrcInfo.tlyric || '' })
|
||||
: (
|
||||
isUseOtherSource
|
||||
? handleGetLyric.call(this, musicInfo)
|
||||
: music[musicInfo.source].getLyric(musicInfo).promise
|
||||
).then(({ lyric, tlyric, lxlyric }) => {
|
||||
setLyric(musicInfo, { lyric, tlyric, lxlyric })
|
||||
return { lyric, tlyric, lxlyric }
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
return null
|
||||
})
|
||||
).then(lrcs => {
|
||||
if (!lrcs) return lrcs
|
||||
if (global.i18n.locale != 'zh-tw') return lrcs
|
||||
return rendererInvoke(NAMES.mainWindow.lang_s2t, Buffer.from(lrcs.lyric).toString('base64')).then(b64 => Buffer.from(b64, 'base64').toString()).then(lyric => {
|
||||
lrcs.lyric = lyric
|
||||
return lrcs
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 修复 1.1.x版本 酷狗源歌词格式
|
||||
const fixKgLyric = lrc => /\[00:\d\d:\d\d.\d+\]/.test(lrc) ? lrc.replace(/(?:\[00:(\d\d:\d\d.\d+\]))/gm, '[$1') : lrc
|
||||
|
||||
@@ -263,30 +293,16 @@ const saveMeta = function(downloadInfo, filePath, isUseOtherSource, isEmbedPic,
|
||||
? downloadInfo.musicInfo.img
|
||||
? Promise.resolve(downloadInfo.musicInfo.img)
|
||||
: (
|
||||
isUseOtherSource
|
||||
? getPic.call(this, downloadInfo.musicInfo)
|
||||
: music[downloadInfo.musicInfo.source].getPic(downloadInfo.musicInfo).promise
|
||||
).catch(err => {
|
||||
console.log(err)
|
||||
return null
|
||||
})
|
||||
: Promise.resolve(),
|
||||
isEmbedLyric
|
||||
? getLyricFromStorage(downloadInfo.musicInfo).then(lrcInfo => {
|
||||
return lrcInfo.lyric
|
||||
? Promise.resolve({ lyric: lrcInfo.lyric, tlyric: lrcInfo.tlyric || '' })
|
||||
: (
|
||||
isUseOtherSource
|
||||
? getLyric.call(this, downloadInfo.musicInfo)
|
||||
: music[downloadInfo.musicInfo.source].getLyric(downloadInfo.musicInfo).promise
|
||||
).then(({ lyric, tlyric, lxlyric }) => {
|
||||
setLyric(downloadInfo.musicInfo, { lyric, tlyric, lxlyric })
|
||||
return { lyric, tlyric, lxlyric }
|
||||
}).catch(err => {
|
||||
? getPic.call(this, downloadInfo.musicInfo)
|
||||
: music[downloadInfo.musicInfo.source].getPic(downloadInfo.musicInfo).promise
|
||||
).catch(err => {
|
||||
console.log(err)
|
||||
return null
|
||||
})
|
||||
})
|
||||
: Promise.resolve(),
|
||||
isEmbedLyric
|
||||
? getLyric.call(this, downloadInfo.musicInfo, isUseOtherSource)
|
||||
: Promise.resolve(),
|
||||
]
|
||||
Promise.all(tasks).then(([imgUrl, lyrics = {}]) => {
|
||||
@@ -306,19 +322,11 @@ const saveMeta = function(downloadInfo, filePath, isUseOtherSource, isEmbedPic,
|
||||
* @param {*} downloadInfo
|
||||
* @param {*} filePath
|
||||
*/
|
||||
const downloadLyric = (downloadInfo, filePath) => {
|
||||
const promise = getLyric(downloadInfo.musicInfo).then(lrcInfo => {
|
||||
return lrcInfo.lyric
|
||||
? Promise.resolve({ lyric: lrcInfo.lyric, tlyric: lrcInfo.tlyric || '' })
|
||||
: music[downloadInfo.musicInfo.source].getLyric(downloadInfo.musicInfo).promise.then(({ lyric, tlyric, lxlyric }) => {
|
||||
setLyric(downloadInfo.musicInfo, { lyric, tlyric, lxlyric })
|
||||
return { lyric, tlyric, lxlyric }
|
||||
})
|
||||
})
|
||||
promise.then(lrcs => {
|
||||
if (lrcs.lyric) {
|
||||
const downloadLyric = function(downloadInfo, isUseOtherSource, filePath, lrcFormat) {
|
||||
getLyric.call(this, downloadInfo.musicInfo, isUseOtherSource).then(lrcs => {
|
||||
if (lrcs?.lyric) {
|
||||
lrcs.lyric = fixKgLyric(lrcs.lyric)
|
||||
saveLrc(filePath.replace(/(mp3|flac|ape|wav)$/, 'lrc'), lrcs.lyric)
|
||||
saveLrc(filePath.replace(/(mp3|flac|ape|wav)$/, 'lrc'), lrcs.lyric, lrcFormat)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -369,9 +377,10 @@ const actions = {
|
||||
status: state.downloadStatus.WAITING,
|
||||
statusText: '待下载',
|
||||
url: null,
|
||||
fileName: `${rootState.setting.download.fileName
|
||||
// songmid: musicInfo.songmid,
|
||||
fileName: filterFileName(`${rootState.setting.download.fileName
|
||||
.replace('歌名', musicInfo.name)
|
||||
.replace('歌手', musicInfo.singer)}.${ext}`.replace(filterFileName, ''),
|
||||
.replace('歌手', musicInfo.singer)}.${ext}`),
|
||||
progress: {
|
||||
downloaded: 0,
|
||||
total: 0,
|
||||
@@ -434,7 +443,7 @@ const actions = {
|
||||
dispatch('startTask')
|
||||
|
||||
saveMeta.call(_this, downloadInfo, downloadInfo.filePath, rootState.setting.download.isUseOtherSource, rootState.setting.download.isEmbedPic, rootState.setting.download.isEmbedLyric)
|
||||
if (rootState.setting.download.isDownloadLrc) downloadLyric(downloadInfo, downloadInfo.filePath)
|
||||
if (rootState.setting.download.isDownloadLrc) downloadLyric.call(_this, downloadInfo, rootState.setting.download.isUseOtherSource, downloadInfo.filePath, rootState.setting.download.lrcFormat)
|
||||
console.log('on complate')
|
||||
},
|
||||
onError(err) {
|
||||
@@ -667,7 +676,7 @@ const mutations = {
|
||||
downloadInfo.order = order
|
||||
},
|
||||
updateDownloadList(state, list) {
|
||||
state.list = list
|
||||
state.list = window.downloadList = list
|
||||
},
|
||||
updateUrl(state, { downloadInfo, url }) {
|
||||
downloadInfo.url = url
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import musicSdk from '../../utils/music'
|
||||
import { clearLyric, clearMusicUrl } from '../../utils'
|
||||
import { sync as eventSyncName } from '@renderer/event/names'
|
||||
|
||||
let allList = {}
|
||||
window.allList = allList
|
||||
|
||||
const allListInit = (defaultList, loveList, userList) => {
|
||||
for (const id of Object.keys(allList)) {
|
||||
delete allList[id]
|
||||
}
|
||||
allList[defaultList.id] = defaultList
|
||||
allList[loveList.id] = loveList
|
||||
for (const list of userList) allList[list.id] = list
|
||||
@@ -67,11 +71,40 @@ const mutations = {
|
||||
if (userList != null) state.userList = userList
|
||||
allListInit(state.defaultList, state.loveList, state.userList)
|
||||
state.isInitedList = true
|
||||
|
||||
// if (!isSync) {
|
||||
// window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
// action: 'init_list',
|
||||
// data: { defaultList, loveList, userList },
|
||||
// })
|
||||
// }
|
||||
},
|
||||
setList(state, { id, list, name, location, source, sourceListId }) {
|
||||
setSyncListData(state, { defaultList, loveList, userList }) {
|
||||
state.defaultList.list.splice(0, state.defaultList.list.length, ...defaultList.list)
|
||||
state.loveList.list.splice(0, state.loveList.list.length, ...loveList.list)
|
||||
for (let index = 0; index < userList.length; index++) {
|
||||
const list = userList[index]
|
||||
const targetList = allList[list.id]
|
||||
if (targetList) {
|
||||
userList.splice(index, 1, targetList)
|
||||
targetList.name = list.name
|
||||
targetList.list.splice(0, targetList.list.length, ...list.list)
|
||||
}
|
||||
}
|
||||
console.log()
|
||||
state.userList = userList
|
||||
allListInit(state.defaultList, state.loveList, state.userList)
|
||||
},
|
||||
setList(state, { id, list, name, location, source, sourceListId, isSync }) {
|
||||
const targetList = allList[id]
|
||||
if (targetList) {
|
||||
if (name && targetList.name === name) {
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'set_list',
|
||||
data: { id, list, name, location, source, sourceListId },
|
||||
})
|
||||
}
|
||||
targetList.list.splice(0, targetList.list.length, ...list)
|
||||
targetList.location = location
|
||||
return
|
||||
@@ -79,6 +112,12 @@ const mutations = {
|
||||
|
||||
id += '_' + Math.random()
|
||||
}
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'set_list',
|
||||
data: { id, list, name, location, source, sourceListId },
|
||||
})
|
||||
}
|
||||
let newList = {
|
||||
name,
|
||||
id,
|
||||
@@ -90,46 +129,127 @@ const mutations = {
|
||||
state.userList.push(newList)
|
||||
allListUpdate(newList)
|
||||
},
|
||||
listAdd(state, { id, musicInfo }) {
|
||||
listAdd(state, { id, musicInfo, addMusicLocationType, isSync }) {
|
||||
if (!addMusicLocationType) addMusicLocationType = this.state.setting.list.addMusicLocationType
|
||||
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'list_add',
|
||||
data: { id, musicInfo, addMusicLocationType },
|
||||
})
|
||||
}
|
||||
|
||||
const targetList = allList[id]
|
||||
if (!targetList) return
|
||||
if (targetList.list.some(s => s.songmid === musicInfo.songmid)) return
|
||||
targetList.list.push(musicInfo)
|
||||
switch (addMusicLocationType) {
|
||||
case 'top':
|
||||
targetList.list.unshift(musicInfo)
|
||||
break
|
||||
case 'bottom':
|
||||
default:
|
||||
targetList.list.push(musicInfo)
|
||||
break
|
||||
}
|
||||
},
|
||||
listMove(state, { fromId, musicInfo, toId }) {
|
||||
listMove(state, { fromId, musicInfo, toId, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'list_move',
|
||||
data: { fromId, musicInfo, toId },
|
||||
})
|
||||
}
|
||||
|
||||
const fromList = allList[fromId]
|
||||
const toList = allList[toId]
|
||||
if (!fromList || !toList) return
|
||||
fromList.list.splice(fromList.list.indexOf(musicInfo), 1)
|
||||
fromList.list.splice(fromList.list.findIndex(s => s.songmid === musicInfo.songmid), 1)
|
||||
let index = toList.list.findIndex(s => s.songmid === musicInfo.songmid)
|
||||
if (index < 0) toList.list.push(musicInfo)
|
||||
if (index < 0) {
|
||||
switch (this.state.setting.list.addMusicLocationType) {
|
||||
case 'top':
|
||||
toList.list.unshift(musicInfo)
|
||||
break
|
||||
case 'bottom':
|
||||
default:
|
||||
toList.list.push(musicInfo)
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
listAddMultiple(state, { id, list }) {
|
||||
listAddMultiple(state, { id, list, addMusicLocationType, isSync }) {
|
||||
if (!addMusicLocationType) addMusicLocationType = this.state.setting.list.addMusicLocationType
|
||||
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'list_add_multiple',
|
||||
data: { id, list, addMusicLocationType },
|
||||
})
|
||||
}
|
||||
|
||||
let targetList = allList[id]
|
||||
if (!targetList) return
|
||||
let newList = [...targetList.list, ...list]
|
||||
let map = {}
|
||||
let ids = []
|
||||
for (const item of newList) {
|
||||
if (map[item.songmid]) continue
|
||||
ids.push(item.songmid)
|
||||
map[item.songmid] = item
|
||||
let newList
|
||||
const map = {}
|
||||
const ids = []
|
||||
switch (addMusicLocationType) {
|
||||
case 'top':
|
||||
newList = [...list, ...targetList.list]
|
||||
for (let i = newList.length - 1; i > -1; i--) {
|
||||
const item = newList[i]
|
||||
if (map[item.songmid]) continue
|
||||
ids.unshift(item.songmid)
|
||||
map[item.songmid] = item
|
||||
}
|
||||
break
|
||||
case 'bottom':
|
||||
default:
|
||||
newList = [...targetList.list, ...list]
|
||||
for (const item of newList) {
|
||||
if (map[item.songmid]) continue
|
||||
ids.push(item.songmid)
|
||||
map[item.songmid] = item
|
||||
}
|
||||
break
|
||||
}
|
||||
targetList.list.splice(0, targetList.list.length, ...ids.map(id => map[id]))
|
||||
},
|
||||
// { fromId, toId, list }
|
||||
listMoveMultiple(state, { fromId, toId, list }) {
|
||||
listMoveMultiple(state, { fromId, toId, list, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'list_move_multiple',
|
||||
data: { fromId, toId, list },
|
||||
})
|
||||
}
|
||||
|
||||
// console.log(state.commit)
|
||||
this.commit('list/listRemoveMultiple', { id: fromId, list })
|
||||
this.commit('list/listAddMultiple', { id: toId, list })
|
||||
this.commit('list/listRemoveMultiple', { listId: fromId, ids: list.map(s => s.songmid), isSync: true })
|
||||
this.commit('list/listAddMultiple', { id: toId, list, isSync: true })
|
||||
},
|
||||
listRemove(state, { id, index }) {
|
||||
let targetList = allList[id]
|
||||
listRemove(state, { listId, id, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'list_remove',
|
||||
data: { listId, id },
|
||||
})
|
||||
}
|
||||
|
||||
let targetList = allList[listId]
|
||||
if (!targetList) return
|
||||
const index = targetList.list.findIndex(item => item.songmid == id)
|
||||
if (index < 0) return
|
||||
targetList.list.splice(index, 1)
|
||||
},
|
||||
listRemoveMultiple(state, { id, list }) {
|
||||
let targetList = allList[id]
|
||||
listRemoveMultiple(state, { listId, ids: musicIds, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'list_remove_multiple',
|
||||
data: { listId, ids: musicIds },
|
||||
})
|
||||
}
|
||||
|
||||
let targetList = allList[listId]
|
||||
if (!targetList) return
|
||||
let map = {}
|
||||
let ids = []
|
||||
@@ -137,25 +257,50 @@ const mutations = {
|
||||
ids.push(item.songmid)
|
||||
map[item.songmid] = item
|
||||
}
|
||||
for (const item of list) {
|
||||
if (map[item.songmid]) delete map[item.songmid]
|
||||
for (const songmid of musicIds) {
|
||||
if (map[songmid]) delete map[songmid]
|
||||
}
|
||||
let newList = []
|
||||
for (const id of ids) if (map[id]) newList.push(map[id])
|
||||
|
||||
targetList.list.splice(0, targetList.list.length, ...newList)
|
||||
},
|
||||
listClear(state, id) {
|
||||
listClear(state, { id, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'list_clear',
|
||||
data: { id },
|
||||
})
|
||||
}
|
||||
|
||||
let targetList = allList[id]
|
||||
if (!targetList) return
|
||||
targetList.list.splice(0, targetList.list.length)
|
||||
},
|
||||
updateMusicInfo(state, { id, index, data, musicInfo = {} }) {
|
||||
let targetList = allList[id]
|
||||
if (!targetList) return Object.assign(musicInfo, data)
|
||||
Object.assign(targetList.list[index], data)
|
||||
updateMusicInfo(state, { listId, id, data, musicInfo, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'update_music_info',
|
||||
data: { listId, id, data, musicInfo },
|
||||
})
|
||||
}
|
||||
|
||||
let targetList = allList[listId]
|
||||
if (!targetList) {
|
||||
if (musicInfo) Object.assign(musicInfo, data)
|
||||
return
|
||||
}
|
||||
const targetMusicInfo = targetList.list.find(item => item.songmid == id)
|
||||
if (targetMusicInfo) Object.assign(targetMusicInfo, data)
|
||||
},
|
||||
createUserList(state, { name, id = `userlist_${Date.now()}`, list = [], source, sourceListId }) {
|
||||
createUserList(state, { name, id = `userlist_${Date.now()}`, list = [], source, sourceListId, position, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'create_user_list',
|
||||
data: { name, id, list, source, sourceListId, position },
|
||||
})
|
||||
}
|
||||
|
||||
let newList = state.userList.find(item => item.id === id)
|
||||
if (!newList) {
|
||||
newList = {
|
||||
@@ -166,38 +311,82 @@ const mutations = {
|
||||
source,
|
||||
sourceListId,
|
||||
}
|
||||
state.userList.push(newList)
|
||||
if (position == null) {
|
||||
state.userList.push(newList)
|
||||
} else {
|
||||
state.userList.splice(position + 1, 0, newList)
|
||||
}
|
||||
allListUpdate(newList)
|
||||
}
|
||||
this.commit('list/listAddMultiple', { id, list })
|
||||
this.commit('list/listAddMultiple', { id, list, isSync: true })
|
||||
},
|
||||
removeUserList(state, index) {
|
||||
removeUserList(state, { id, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'remove_user_list',
|
||||
data: { id },
|
||||
})
|
||||
}
|
||||
|
||||
const index = state.userList.findIndex(l => l.id === id)
|
||||
if (index < 0) return
|
||||
let list = state.userList.splice(index, 1)[0]
|
||||
allListRemove(list)
|
||||
},
|
||||
setUserListName(state, { index, name }) {
|
||||
let list = state.userList[index]
|
||||
setUserListName(state, { id, name, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'set_user_list_name',
|
||||
data: { id, name },
|
||||
})
|
||||
}
|
||||
|
||||
let list = allList[id]
|
||||
if (!list) return
|
||||
list.name = name
|
||||
},
|
||||
moveupUserList(state, index) {
|
||||
let targetList = state.userList[index]
|
||||
moveupUserList(state, { id, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'moveup_user_list',
|
||||
data: { id },
|
||||
})
|
||||
}
|
||||
|
||||
const index = state.userList.findIndex(l => l.id == id)
|
||||
if (index < 0) return
|
||||
let targetList = allList[id]
|
||||
state.userList.splice(index, 1)
|
||||
state.userList.splice(index - 1, 0, targetList)
|
||||
},
|
||||
movedownUserList(state, index) {
|
||||
let targetList = state.userList[index]
|
||||
movedownUserList(state, { id, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'movedown_user_list',
|
||||
data: { id },
|
||||
})
|
||||
}
|
||||
const index = state.userList.findIndex(l => l.id == id)
|
||||
if (index < 0) return
|
||||
let targetList = allList[id]
|
||||
state.userList.splice(index, 1)
|
||||
state.userList.splice(index + 1, 0, targetList)
|
||||
},
|
||||
setListScroll(state, { id, location }) {
|
||||
if (allList[id]) allList[id].location = location
|
||||
},
|
||||
sortList(state, { id, sortNum, musicInfos }) {
|
||||
let targetList = allList[id]
|
||||
this.commit('list/listRemoveMultiple', { id, list: musicInfos })
|
||||
setMusicPosition(state, { id, position, list, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.$emit(eventSyncName.send_action_list, {
|
||||
action: 'set_music_position',
|
||||
data: { id, position, list },
|
||||
})
|
||||
}
|
||||
|
||||
targetList.list.splice(sortNum - 1, 0, ...musicInfos)
|
||||
let targetList = allList[id]
|
||||
this.commit('list/listRemoveMultiple', { listId: id, ids: list.map(m => m.songmid), isSync: true })
|
||||
|
||||
targetList.list.splice(position - 1, 0, ...list)
|
||||
},
|
||||
clearCache() {
|
||||
const lists = Object.values(allList)
|
||||
|
||||
@@ -40,7 +40,7 @@ const filterList = async({ playedList, listInfo, savePath, commit }) => {
|
||||
canPlayList.push(item)
|
||||
|
||||
// 排除已播放音乐
|
||||
let index = filteredPlayedList.indexOf(item)
|
||||
let index = filteredPlayedList.findIndex(m => (m.songmid || m.musicInfo.songmid) == item.musicInfo.songmid)
|
||||
if (index > -1) {
|
||||
filteredPlayedList.splice(index, 1)
|
||||
continue
|
||||
@@ -52,7 +52,7 @@ const filterList = async({ playedList, listInfo, savePath, commit }) => {
|
||||
// if (!assertApiSupport(s.source)) return false
|
||||
canPlayList.push(s)
|
||||
|
||||
let index = filteredPlayedList.indexOf(s)
|
||||
let index = filteredPlayedList.findIndex(m => (m.songmid || m.musicInfo.songmid) == s.songmid)
|
||||
if (index > -1) {
|
||||
filteredPlayedList.splice(index, 1)
|
||||
return false
|
||||
@@ -141,6 +141,7 @@ const getLyric = function(musicInfo, retryedSource = [], originMusic) {
|
||||
})
|
||||
}
|
||||
|
||||
let prevListPlayIndex
|
||||
// getters
|
||||
const getters = {
|
||||
list: state => state.listInfo.list,
|
||||
@@ -152,17 +153,31 @@ const getters = {
|
||||
const isTempPlay = !!state.playMusicInfo.isTempPlay
|
||||
const isPlayList = listId === playListId
|
||||
let playIndex = -1
|
||||
let listPlayIndex = state.playIndex
|
||||
let listPlayIndex = Math.min(state.playIndex, state.listInfo.list.length - 1)
|
||||
|
||||
if (listId != '__temp__') {
|
||||
const currentSongmid = state.playMusicInfo.musicInfo.songmid || state.playMusicInfo.musicInfo.musicInfo.songmid
|
||||
if (isPlayList) {
|
||||
playIndex = state.listInfo.list.indexOf(state.playMusicInfo.musicInfo)
|
||||
playIndex = state.listInfo.list.findIndex(m => (m.songmid || m.musicInfo.songmid) == currentSongmid)
|
||||
if (!isTempPlay) listPlayIndex = playIndex
|
||||
} else if (listId == 'download') {
|
||||
playIndex = window.downloadList.findIndex(m => m.musicInfo.songmid == currentSongmid)
|
||||
} else {
|
||||
let list = window.allList[listId]
|
||||
if (list) playIndex = list.list.indexOf(state.playMusicInfo.musicInfo)
|
||||
if (list) playIndex = list.list.findIndex(m => m.songmid == currentSongmid)
|
||||
}
|
||||
}
|
||||
if (listPlayIndex >= 0) prevListPlayIndex = listPlayIndex
|
||||
// if (listPlayIndex < 0) {
|
||||
// let length = state.listInfo.list.length
|
||||
// if (length) {
|
||||
// let index = Math.min(prevListPlayIndex, 0)
|
||||
// if (index > length - 1) index = length - 1
|
||||
// listPlayIndex = prevListPlayIndex = index
|
||||
// }
|
||||
// } else {
|
||||
// prevListPlayIndex = listPlayIndex
|
||||
// }
|
||||
// console.log({
|
||||
// listId,
|
||||
// playIndex,
|
||||
@@ -170,7 +185,7 @@ const getters = {
|
||||
// listPlayIndex,
|
||||
// isPlayList,
|
||||
// isTempPlay,
|
||||
// musicInfo: state.playMusicInfo.musicInfo,
|
||||
// // musicInfo: state.playMusicInfo.musicInfo,
|
||||
// })
|
||||
return {
|
||||
listId,
|
||||
@@ -247,12 +262,21 @@ const actions = {
|
||||
async playPrev({ state, rootState, commit, getters }) {
|
||||
const currentListId = state.listInfo.id
|
||||
const currentList = state.listInfo.list
|
||||
const playInfo = getters.playInfo
|
||||
if (state.playedList.length) {
|
||||
let currentSongmid
|
||||
if (state.playMusicInfo.isTempPlay) {
|
||||
const musicInfo = currentList[playInfo.listPlayIndex]
|
||||
if (musicInfo) currentSongmid = musicInfo.songmid || musicInfo.musicInfo.songmid
|
||||
} else {
|
||||
currentSongmid = state.playMusicInfo.musicInfo.songmid || state.playMusicInfo.musicInfo.musicInfo.songmid
|
||||
}
|
||||
// 从已播放列表移除播放列表已删除的歌曲
|
||||
let index
|
||||
for (index = state.playedList.indexOf(state.playMusicInfo) - 1; index > -1; index--) {
|
||||
for (index = state.playedList.findIndex(m => (m.musicInfo.songmid || m.musicInfo.musicInfo.songmid) === currentSongmid) - 1; index > -1; index--) {
|
||||
const playMusicInfo = state.playedList[index]
|
||||
if (playMusicInfo.listId == currentListId && !currentList.includes(playMusicInfo.musicInfo)) {
|
||||
const currentSongmid = playMusicInfo.musicInfo.songmid || playMusicInfo.musicInfo.musicInfo.songmid
|
||||
if (playMusicInfo.listId == currentListId && !currentList.some(m => (m.songmid || m.musicInfo.songmid) === currentSongmid)) {
|
||||
commit('removePlayedList', index)
|
||||
continue
|
||||
}
|
||||
@@ -272,9 +296,14 @@ const actions = {
|
||||
commit,
|
||||
})
|
||||
if (!filteredList.length) return commit('setPlayMusicInfo', null)
|
||||
const playInfo = getters.playInfo
|
||||
let currentIndex = filteredList.indexOf(currentList[playInfo.listPlayIndex])
|
||||
if (currentIndex == -1) currentIndex = 0
|
||||
|
||||
let listPlayIndex = playInfo.listPlayIndex
|
||||
const currentListLength = state.listInfo.list.length - 1
|
||||
if (listPlayIndex == -1 && currentListLength) {
|
||||
listPlayIndex = prevListPlayIndex >= currentListLength ? 0 : prevListPlayIndex + 1
|
||||
}
|
||||
let currentIndex = listPlayIndex
|
||||
if (currentIndex < 0) currentIndex = 0
|
||||
let nextIndex = currentIndex
|
||||
if (!playInfo.isTempPlay) {
|
||||
switch (rootState.setting.player.togglePlayMethod) {
|
||||
@@ -308,12 +337,22 @@ const actions = {
|
||||
}
|
||||
const currentListId = state.listInfo.id
|
||||
const currentList = state.listInfo.list
|
||||
const playInfo = getters.playInfo
|
||||
|
||||
if (state.playedList.length) {
|
||||
let currentSongmid
|
||||
if (state.playMusicInfo.isTempPlay) {
|
||||
const musicInfo = currentList[playInfo.listPlayIndex]
|
||||
if (musicInfo) currentSongmid = musicInfo.songmid || musicInfo.musicInfo.songmid
|
||||
} else {
|
||||
currentSongmid = state.playMusicInfo.musicInfo.songmid || state.playMusicInfo.musicInfo.musicInfo.songmid
|
||||
}
|
||||
// 从已播放列表移除播放列表已删除的歌曲
|
||||
let index
|
||||
for (index = state.playedList.indexOf(state.playMusicInfo) + 1; index < state.playedList.length; index++) {
|
||||
for (index = state.playedList.findIndex(m => (m.musicInfo.songmid || m.musicInfo.musicInfo.songmid) === currentSongmid) + 1; index < state.playedList.length; index++) {
|
||||
const playMusicInfo = state.playedList[index]
|
||||
if (playMusicInfo.listId == currentListId && !currentList.includes(playMusicInfo.musicInfo)) {
|
||||
const currentSongmid = playMusicInfo.musicInfo.songmid || playMusicInfo.musicInfo.musicInfo.songmid
|
||||
if (playMusicInfo.listId == currentListId && !currentList.some(m => (m.songmid || m.musicInfo.songmid) === currentSongmid)) {
|
||||
commit('removePlayedList', index)
|
||||
continue
|
||||
}
|
||||
@@ -333,9 +372,14 @@ const actions = {
|
||||
})
|
||||
|
||||
if (!filteredList.length) return commit('setPlayMusicInfo', null)
|
||||
const playInfo = getters.playInfo
|
||||
const currentIndex = filteredList.indexOf(currentList[playInfo.listPlayIndex])
|
||||
let listPlayIndex = playInfo.listPlayIndex
|
||||
const currentListLength = state.listInfo.list.length - 1
|
||||
if (listPlayIndex == -1 && currentListLength) {
|
||||
listPlayIndex = prevListPlayIndex > currentListLength ? currentListLength : prevListPlayIndex - 1
|
||||
}
|
||||
const currentIndex = listPlayIndex
|
||||
let nextIndex = currentIndex
|
||||
|
||||
switch (rootState.setting.player.togglePlayMethod) {
|
||||
case 'listLoop':
|
||||
nextIndex = currentIndex === filteredList.length - 1 ? 0 : currentIndex + 1
|
||||
@@ -433,7 +477,10 @@ const mutations = {
|
||||
playIndex = -1
|
||||
} else {
|
||||
let listId = playMusicInfo.listId
|
||||
if (listId != '__temp__' && !playMusicInfo.isTempPlay && listId === state.listInfo.id) playIndex = state.listInfo.list.indexOf(playMusicInfo.musicInfo)
|
||||
if (listId != '__temp__' && !playMusicInfo.isTempPlay && listId === state.listInfo.id) {
|
||||
const currentSongmid = playMusicInfo.musicInfo.songmid || playMusicInfo.musicInfo.musicInfo.songmid
|
||||
playIndex = state.listInfo.list.findIndex(m => (m.songmid || m.musicInfo.songmid) == currentSongmid)
|
||||
}
|
||||
}
|
||||
|
||||
state.playMusicInfo = playMusicInfo
|
||||
|
||||
@@ -3,6 +3,8 @@ import path from 'path'
|
||||
import { shell, clipboard } from 'electron'
|
||||
import crypto from 'crypto'
|
||||
import { rendererSend, rendererInvoke, NAMES } from '../../common/ipc'
|
||||
import iconv from 'iconv-lite'
|
||||
import { gzip, gunzip } from 'zlib'
|
||||
|
||||
/**
|
||||
* 获取两个数之间的随机整数,大于等于min,小于max
|
||||
@@ -80,7 +82,7 @@ const encodeNames = {
|
||||
''': "'",
|
||||
''': "'",
|
||||
}
|
||||
export const decodeName = (str = '') => str.replace(/(?:&|<|>|"|'|')/gm, s => encodeNames[s])
|
||||
export const decodeName = (str = '') => str?.replace(/(?:&|<|>|"|'|')/gm, s => encodeNames[s]) || ''
|
||||
|
||||
const easeInOutQuad = (t, b, c, d) => {
|
||||
t /= d / 2
|
||||
@@ -276,11 +278,22 @@ export const setMeta = (filePath, meta) => {
|
||||
* 保存歌词文件
|
||||
* @param {*} filePath
|
||||
* @param {*} lrc
|
||||
* @param {*} format
|
||||
*/
|
||||
export const saveLrc = (filePath, lrc) => {
|
||||
fs.writeFile(filePath, lrc, 'utf8', err => {
|
||||
if (err) console.log(err)
|
||||
})
|
||||
export const saveLrc = (filePath, lrc, format) => {
|
||||
switch (format) {
|
||||
case 'gbk':
|
||||
fs.writeFile(filePath, iconv.encode(lrc, 'gbk', { addBOM: true }), err => {
|
||||
if (err) console.log(err)
|
||||
})
|
||||
break
|
||||
case 'utf8':
|
||||
default:
|
||||
fs.writeFile(filePath, lrc, 'utf8', err => {
|
||||
if (err) console.log(err)
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -421,3 +434,39 @@ export const setMusicUrl = (musicInfo, type, url) => rendererSend(NAMES.mainWind
|
||||
url,
|
||||
})
|
||||
export const clearMusicUrl = () => rendererSend(NAMES.mainWindow.clear_music_url)
|
||||
|
||||
export const gzipData = str => {
|
||||
return new Promise((resolve, reject) => {
|
||||
gzip(str, (err, result) => {
|
||||
if (err) return reject(err)
|
||||
resolve(result)
|
||||
})
|
||||
})
|
||||
}
|
||||
export const gunzipData = buf => {
|
||||
return new Promise((resolve, reject) => {
|
||||
gunzip(buf, (err, result) => {
|
||||
if (err) return reject(err)
|
||||
resolve(result.toString())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const saveLxConfigFile = async(path, data) => {
|
||||
if (!path.endsWith('.lxmc')) path += '.lxmc'
|
||||
fs.writeFile(path, await gzipData(JSON.stringify(data)), 'binary', err => {
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
|
||||
export const readLxConfigFile = async path => {
|
||||
let isJSON = path.endsWith('.json')
|
||||
let data = await fs.promises.readFile(path, isJSON ? 'utf8' : 'binary')
|
||||
if (!data || isJSON) return data
|
||||
data = await gunzipData(Buffer.from(data, 'binary'))
|
||||
return data.toString('utf8')
|
||||
}
|
||||
|
||||
|
||||
const fileNameRxp = /[\\/:*?#"<>|]/g
|
||||
export const filterFileName = name => name.replace(fileNameRxp, '')
|
||||
|
||||
@@ -120,6 +120,7 @@ module.exports = class Lyric {
|
||||
return {
|
||||
text: line.text,
|
||||
time: line.time,
|
||||
translation: line.translation,
|
||||
dom_line: fontPlayer.lineContent,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -48,7 +48,7 @@ module.exports = class LinePlayer {
|
||||
_initLines() {
|
||||
this.lines = []
|
||||
this.translationLines = []
|
||||
const lines = this.lyric.split('\n')
|
||||
const lines = this.lyric.split(/\r\n|\r|\n/)
|
||||
const linesMap = {}
|
||||
// const translationLines = this.translationLyric.split('\n')
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import { requestMsg } from '../../message'
|
||||
import { headers, timeout } from '../options'
|
||||
import { dnsLookup } from '../utils'
|
||||
|
||||
const api_test = {
|
||||
getMusicUrl(songInfo, type) {
|
||||
@@ -8,6 +9,7 @@ const api_test = {
|
||||
method: 'get',
|
||||
timeout,
|
||||
headers,
|
||||
lookup: dnsLookup,
|
||||
family: 4,
|
||||
})
|
||||
requestObj.promise = requestObj.promise.then(({ body }) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { httpFetch } from '../../request'
|
||||
// import { formatPlayTime } from '../../index'
|
||||
// import jshtmlencode from 'js-htmlencode'
|
||||
|
||||
|
||||
const boardList = [
|
||||
// { id: 'bd__601', name: '歌单榜', bangid: '601' },
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// import '../../polyfill/array.find'
|
||||
// import jshtmlencode from 'js-htmlencode'
|
||||
|
||||
import { httpFetch } from '../../request'
|
||||
import { formatPlayTime } from '../../index'
|
||||
// import { debug } from '../../utils/env'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import { requestMsg } from '../../message'
|
||||
import { headers, timeout } from '../options'
|
||||
import { dnsLookup } from '../utils'
|
||||
|
||||
const api_test = {
|
||||
getMusicUrl(songInfo, type) {
|
||||
@@ -8,6 +9,7 @@ const api_test = {
|
||||
method: 'get',
|
||||
timeout,
|
||||
headers,
|
||||
lookup: dnsLookup,
|
||||
family: 4,
|
||||
})
|
||||
requestObj.promise = requestObj.promise.then(({ body }) => {
|
||||
|
||||
@@ -63,17 +63,19 @@ export default {
|
||||
reply: [],
|
||||
}
|
||||
|
||||
return item.pcontent ? {
|
||||
id: item.id,
|
||||
text: decodeName(item.pcontent).split('\n'),
|
||||
time: null,
|
||||
userName: item.puser,
|
||||
avatar: null,
|
||||
userId: item.puser_id,
|
||||
likedCount: null,
|
||||
replyNum: null,
|
||||
reply: [data],
|
||||
} : data
|
||||
return item.pcontent
|
||||
? {
|
||||
id: item.id,
|
||||
text: decodeName(item.pcontent).split('\n'),
|
||||
time: null,
|
||||
userName: item.puser,
|
||||
avatar: null,
|
||||
userId: item.puser_id,
|
||||
likedCount: null,
|
||||
replyNum: null,
|
||||
reply: [data],
|
||||
}
|
||||
: data
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// import '../../polyfill/array.find'
|
||||
// import jshtmlencode from 'js-htmlencode'
|
||||
|
||||
import { httpFetch } from '../../request'
|
||||
import { decodeName, formatPlayTime, sizeFormate } from '../../index'
|
||||
// import { debug } from '../../utils/env'
|
||||
|
||||
@@ -165,7 +165,9 @@ export default {
|
||||
: result.body.err_code
|
||||
) !== 0)
|
||||
) return this.createHttp(url, options, ++retryNum)
|
||||
return result.body.data || result.body.info
|
||||
if (result.body.data) return result.body.data
|
||||
if (Array.isArray(result.body.info)) return result.body
|
||||
return result.body.info
|
||||
},
|
||||
|
||||
createTask(hashs) {
|
||||
@@ -183,9 +185,9 @@ export default {
|
||||
let list = hashs
|
||||
let tasks = []
|
||||
while (list.length) {
|
||||
tasks.push(Object.assign({ data: list.slice(0, 20) }, data))
|
||||
if (list.length < 20) break
|
||||
list = list.slice(20)
|
||||
tasks.push(Object.assign({ data: list.slice(0, 100) }, data))
|
||||
if (list.length < 100) break
|
||||
list = list.slice(100)
|
||||
}
|
||||
let url = 'http://kmr.service.kugou.com/v2/album_audio/audio'
|
||||
return tasks.map(task => this.createHttp(url, {
|
||||
@@ -267,6 +269,68 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
deDuplication(datas) {
|
||||
let ids = new Set()
|
||||
return datas.filter(({ hash }) => {
|
||||
if (ids.has(hash)) return false
|
||||
ids.add(hash)
|
||||
return true
|
||||
})
|
||||
},
|
||||
|
||||
async getUserListDetailByLink({ info }, link) {
|
||||
let listInfo = info['0']
|
||||
let total = listInfo.count
|
||||
let tasks = []
|
||||
let page = 0
|
||||
while (total) {
|
||||
const limit = total > 90 ? 90 : total
|
||||
total -= limit
|
||||
page += 1
|
||||
tasks.push(this.createHttp(link.replace(/pagesize=\d+/, 'pagesize=' + limit).replace(/page=\d+/, 'page=' + page), {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
|
||||
Referer: link,
|
||||
},
|
||||
}).then(data => data.list.info))
|
||||
}
|
||||
let result = await Promise.all(tasks).then(([...datas]) => datas.flat())
|
||||
result = await Promise.all(this.createTask(this.deDuplication(result).map(item => ({ hash: item.hash })))).then(([...datas]) => datas.flat())
|
||||
// console.log(result)
|
||||
return {
|
||||
list: this.filterData2(result) || [],
|
||||
page,
|
||||
limit: this.listDetailLimit,
|
||||
total: listInfo.count,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: listInfo.name,
|
||||
img: listInfo.pic && listInfo.pic.replace('{size}', 240),
|
||||
// desc: body.result.info.list_desc,
|
||||
author: listInfo.list_create_username,
|
||||
// play_count: this.formatPlayCount(listInfo.count),
|
||||
},
|
||||
}
|
||||
},
|
||||
createGetListDetail2Task(id, total) {
|
||||
let tasks = []
|
||||
let page = 0
|
||||
while (total) {
|
||||
const limit = total > 300 ? 300 : total
|
||||
total -= limit
|
||||
page += 1
|
||||
tasks.push(this.createHttp('https://mobiles.kugou.com/api/v5/special/song_v2?appid=1058&global_specialid=' + id + '&specialid=0&plat=0&version=8000&page=' + page + '&pagesize=' + limit + '&srcappid=2919&clientver=20000&clienttime=1586163263991&mid=1586163263991&uuid=1586163263991&dfid=-&signature=' + toMD5('NVPh5oo715z5DIWAeQlhMDsWXXQV4hwtappid=1058clienttime=1586163263991clientver=20000dfid=-global_specialid=' + id + 'mid=1586163263991page=' + page + 'pagesize=' + limit + 'plat=0specialid=0srcappid=2919uuid=1586163263991version=8000NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt'), {
|
||||
headers: {
|
||||
mid: '1586163263991',
|
||||
Referer: 'https://m3ws.kugou.com/share/index.php',
|
||||
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
|
||||
dfid: '-',
|
||||
clienttime: '1586163263991',
|
||||
},
|
||||
}).then(data => data.info))
|
||||
}
|
||||
return Promise.all(tasks).then(([...datas]) => datas.flat())
|
||||
},
|
||||
async getUserListDetail2(global_collection_id) {
|
||||
let id = global_collection_id
|
||||
if (id.length > 1000) throw new Error('get list error')
|
||||
@@ -279,22 +343,14 @@ export default {
|
||||
clienttime: '1586163242519',
|
||||
},
|
||||
})
|
||||
let songInfo = await this.createHttp('https://mobiles.kugou.com/api/v5/special/song_v2?appid=1058&global_specialid=' + id + '&specialid=0&plat=0&version=8000&pagesize=' + info.songcount + '&srcappid=2919&clientver=20000&clienttime=1586163263991&mid=1586163263991&uuid=1586163263991&dfid=-&signature=' + toMD5('NVPh5oo715z5DIWAeQlhMDsWXXQV4hwtappid=1058clienttime=1586163263991clientver=20000dfid=-global_specialid=' + id + 'mid=1586163263991pagesize=' + info.songcount + 'plat=0specialid=0srcappid=2919uuid=1586163263991version=8000NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt'), {
|
||||
headers: {
|
||||
mid: '1586163263991',
|
||||
Referer: 'https://m3ws.kugou.com/share/index.php',
|
||||
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
|
||||
dfid: '-',
|
||||
clienttime: '1586163263991',
|
||||
},
|
||||
})
|
||||
let result = await Promise.all(this.createTask(songInfo.info.map(item => ({ hash: item.hash })))).then(([...datas]) => datas.flat())
|
||||
const songInfo = await this.createGetListDetail2Task(id, info.songcount)
|
||||
let result = await Promise.all(this.createTask(this.deDuplication(songInfo).map(item => ({ hash: item.hash })))).then(([...datas]) => datas.flat())
|
||||
// console.log(info, songInfo)
|
||||
return {
|
||||
list: this.filterData2(result) || [],
|
||||
page: 1,
|
||||
limit: songInfo.total,
|
||||
total: songInfo.total,
|
||||
limit: this.listDetailLimit,
|
||||
total: info.songcount,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: info.specialname,
|
||||
@@ -333,10 +389,9 @@ export default {
|
||||
// console.log(body, location)
|
||||
if (statusCode > 400) return this.getUserListDetail(link, page, ++retryNum)
|
||||
if (location) {
|
||||
// console.log(location)
|
||||
if (location.includes('global_collection_id')) return this.getUserListDetail2(location.replace(/^.*?global_collection_id=(\w+)(?:&.*$|#.*$|$)/, '$1'))
|
||||
if (location.includes('chain=')) return this.getUserListDetail3(location.replace(/^.*?chain=(\w+)(?:&.*$|#.*$|$)/, '$1'), page)
|
||||
|
||||
// console.log('location', location)
|
||||
if (location.includes('.html')) {
|
||||
if (location.includes('zlist.html')) {
|
||||
let link = location.replace(/^(.*)zlist\.html/, 'https://m3ws.kugou.com/zlist/list')
|
||||
@@ -348,27 +403,12 @@ export default {
|
||||
return this.getUserListDetail(link, page, ++retryNum)
|
||||
} else return this.getUserListDetail3(location.replace(/.+\/(\w+).html(?:\?.*|&.*$|#.*$|$)/, '$1'), page)
|
||||
}
|
||||
// console.log('location', location)
|
||||
return this.getUserListDetail(link, page, ++retryNum)
|
||||
}
|
||||
if (typeof body == 'string') return this.getUserListDetail2(body.replace(/^[\s\S]+?"global_collection_id":"(\w+)"[\s\S]+?$/, '$1'))
|
||||
if (body.errcode !== 0) return this.getUserListDetail(link, page, ++retryNum)
|
||||
let listInfo = body.info['0']
|
||||
let result = body.list.info.map(item => ({ hash: item.hash }))
|
||||
result = await Promise.all(this.createTask(result)).then(([...datas]) => datas.flat())
|
||||
return {
|
||||
list: this.filterData2(result) || [],
|
||||
page,
|
||||
limit: this.listDetailLimit,
|
||||
total: listInfo.count,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: listInfo.name,
|
||||
img: listInfo.pic && listInfo.pic.replace('{size}', 240),
|
||||
// desc: body.result.info.list_desc,
|
||||
author: listInfo.list_create_username,
|
||||
// play_count: this.formatPlayCount(listInfo.count),
|
||||
},
|
||||
}
|
||||
return this.getUserListDetailByLink(body, link)
|
||||
},
|
||||
|
||||
getListDetail(id, page, tryNum = 0) { // 获取歌曲列表内的音乐
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user