Merge branch 'dev'

pull/930/merge v1.19.0
lyswhut 2022-03-20 13:52:15 +08:00
commit f10c1e923e
115 changed files with 2368 additions and 821 deletions

View File

@ -92,6 +92,11 @@ jobs:
name: lx-music-desktop-win_arm64-green name: lx-music-desktop-win_arm64-green
path: build/*win_arm64 green.7z path: build/*win_arm64 green.7z
- name: Generate file MD5
run: |
cd build
Get-FileHash *.exe,*.7z -Algorithm MD5 | Format-List
Mac: Mac:
name: Mac name: Mac
runs-on: macos-latest runs-on: macos-latest
@ -143,6 +148,11 @@ jobs:
name: lx-music-desktop-mac-dmg-arm64 name: lx-music-desktop-mac-dmg-arm64
path: build/*-arm64.dmg path: build/*-arm64.dmg
- name: Generate file MD5
run: |
cd build
md5 *.dmg
Linux: Linux:
name: Linux name: Linux
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -231,3 +241,8 @@ jobs:
with: with:
name: lx-music-desktop-x64-pacman name: lx-music-desktop-x64-pacman
path: build/* x64.pacman path: build/* x64.pacman
- name: Generate file MD5
run: |
cd build
md5sum *.deb *.rpm *.pacman *.AppImage

View File

@ -49,6 +49,11 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BT_TOKEN: ${{ secrets.BT_TOKEN }} BT_TOKEN: ${{ secrets.BT_TOKEN }}
- name: Generate file MD5
run: |
cd build
Get-FileHash *.exe,*.7z -Algorithm MD5 | Format-List
Mac: Mac:
name: Mac name: Mac
runs-on: macos-latest runs-on: macos-latest
@ -89,6 +94,11 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BT_TOKEN: ${{ secrets.BT_TOKEN }} BT_TOKEN: ${{ secrets.BT_TOKEN }}
- name: Generate file MD5
run: |
cd build
md5 *.dmg
Linux: Linux:
name: Linux name: Linux
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -134,3 +144,8 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BT_TOKEN: ${{ secrets.BT_TOKEN }} BT_TOKEN: ${{ secrets.BT_TOKEN }}
- name: Generate file MD5
run: |
cd build
md5sum *.deb *.rpm *.pacman *.AppImage

View File

@ -6,6 +6,53 @@ Project versioning adheres to [Semantic Versioning](http://semver.org/).
Commit convention is based on [Conventional Commits](http://conventionalcommits.org). Commit convention is based on [Conventional Commits](http://conventionalcommits.org).
Change log format is based on [Keep a Changelog](http://keepachangelog.com/). Change log format is based on [Keep a Changelog](http://keepachangelog.com/).
## [1.19.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.18.0...v1.19.0) - 2022-03-20
### 新增
- 新增对播放详情页歌词大小、是否缩放、对齐方式的设置,可以去设置-播放详情页设置查看
- 新增播放详情页通过歌词调整播放进度,默认关闭,需要到设置-播放详情页设置开启,开启后在播放详情页拖动歌词时将会出现跳转当前行歌词播放的按钮
- 新增全屏状态按F11可以进入、退出全屏状态由于全屏时会隐藏控制栏按钮所以需要使用鼠标右键双击详情页的任意地方都可以来关闭播放详情页
- 新增动态主题“道法自然”,你可以预先设置一个亮色主题及暗色主题,此后将根据系统的亮、暗主题色自动切换为你预先设置的相应主题。注:鼠标 右击 此主题项即可打开亮、暗色主题设置窗口。
- 新增对kw源卡拉OK歌词的支持
### 优化
- 优化Windows任务栏缩略图工具栏控制按钮在浅色任务栏下的显示效果
- 添加音频可视化与音频输出设备冲突的提示
- 优化歌词的播放偏移
- 优化托盘菜单操作(#686
- 优化播放下载列表时的切歌性能
### 修复
- 修复“当前的声音输出设备被改变时暂停播放歌曲”设置无效的问题
- 修复桌面歌词没有处理停止播放状态的问题
- 修复AppImage包无法运行的问题
- 修复Windows任务栏缩略图工具栏控制按钮的歌曲收藏按钮状态更新问题
- 修复使用链接导入的歌单无法在我的列表打开原歌单详情页的问题
- 修复播放下载列表的歌曲时增删下载任务导致正在的歌曲序号改变时,不会更新到新增序号的问题
### 文档
添加LX中定义的快捷操作汇总说明到常见问题中这是目前可用的鼠标、键盘快捷操作它们都可以在更新日志中找到
- 鼠标右击播放栏的歌曲图片封面可以定位当前播放的歌曲
- 鼠标右击播放栏进度条上的LRC按钮可以锁定/解锁桌面歌词
- 歌曲搜索框、歌单链接输入框内鼠标右击可以将当前剪贴板上的文字粘贴到输入框内
- 鼠标右击搜索界面中的单条搜索历史可以将其移除
- 歌曲列表内的文字在选中后,鼠标右击可以复制已选中的文字,此功能只对搜索、歌单、排行榜、我的列表中的列表有效
- 鼠标在播放详情页内右键双击可以关闭播放详情页
- 鼠标左击播放栏上的歌曲名字可以将它复制
- 鼠标右击“道法自然英文Auto”主题可以打开亮、暗主题设置窗口
- 歌曲搜索框的候选内容可以用键盘上下方向键选择,按回车键搜索已选内容
- 在歌单详情页按退格键可以返回歌单列表
- 歌曲列表中可以使用Ctrl、Shift键进行多选这类似Windows下的文件选择详情看常见问题列表多选部分
- 在我的列表内可以使用Ctrl+f键打开搜索框进行列表内歌曲搜索搜索框按Esc键可以关闭搜索框搜索框内按上下方向键可以选择歌曲按回车键跳转到已选歌曲按Ctrl+回车可以跳转并播放已选歌曲
- 在我的列表按住Ctrl键可以进入列表拖动模式此时可以用鼠标拖动列表调整列表的位置
- 编辑列表名时按Esc键可以取消编辑
- 按F11可以进入、退出全屏状态
## [1.18.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.17.1...v1.18.0) - 2022-02-26 ## [1.18.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.17.1...v1.18.0) - 2022-02-26
### 新增 ### 新增

20
FAQ.md
View File

@ -6,7 +6,7 @@
洛雪音乐的最初定位不是作为播放器开发的,它主要用于**查找歌曲**,软件的播放功能仅用于试听,不建议用作为常用播放器使用。 洛雪音乐的最初定位不是作为播放器开发的,它主要用于**查找歌曲**,软件的播放功能仅用于试听,不建议用作为常用播放器使用。
## LX Music中的音乐播放列表机制 ## 音乐播放列表机制
1. 默认情况下,播放搜索列表、歌单列表、排行榜列表的歌曲时会自动将该歌曲添加到“我的列表”的试听列表后再播放,手动将歌曲添加到试听列表,再去试听列表找到这首歌点播放是等价的 1. 默认情况下,播放搜索列表、歌单列表、排行榜列表的歌曲时会自动将该歌曲添加到“我的列表”的试听列表后再播放,手动将歌曲添加到试听列表,再去试听列表找到这首歌点播放是等价的
2. 如果你想要播放多首歌曲,需要使用多选功能(若不知道如何多选请看常见问题)多选后,将歌曲这些歌曲添加到“我的列表”播放,或使用稍后播放功能播放 2. 如果你想要播放多首歌曲,需要使用多选功能(若不知道如何多选请看常见问题)多选后,将歌曲这些歌曲添加到“我的列表”播放,或使用稍后播放功能播放
@ -15,6 +15,24 @@
5. 对于排行榜详情列表除了可以使用第2条的方式播放外你可以在右击排行榜名字后弹出的菜单中播放或收藏整个排行榜这与第四条的歌单中的播放、与收藏按钮功能一致 5. 对于排行榜详情列表除了可以使用第2条的方式播放外你可以在右击排行榜名字后弹出的菜单中播放或收藏整个排行榜这与第四条的歌单中的播放、与收藏按钮功能一致
6. v1.18.0及之后新增了“双击列表里的歌曲时自动切换到当前列表播放”设置,默认关闭,此功能仅对歌单、排行榜有效 6. v1.18.0及之后新增了“双击列表里的歌曲时自动切换到当前列表播放”设置,默认关闭,此功能仅对歌单、排行榜有效
## 可用的鼠标、键盘快捷操作
- 鼠标右击播放栏的歌曲图片封面可以定位当前播放的歌曲
- 鼠标右击播放栏进度条上的`LRC`按钮可以锁定/解锁桌面歌词
- 歌曲搜索框、歌单链接输入框内鼠标右击可以将当前剪贴板上的文字粘贴到输入框内
- 鼠标右击搜索界面中的单条搜索历史可以将其移除
- 歌曲列表内的文字在选中后,鼠标右击可以复制已选中的文字,此功能只对搜索、歌单、排行榜、我的列表中的列表有效
- 鼠标在播放详情页内右键双击可以关闭播放详情页
- 鼠标左击播放栏上的歌曲名字可以将它复制
- 鼠标右击设置-主题设置的“道法自然英文Auto”主题可以打开亮、暗主题设置窗口
- 歌曲搜索框的候选内容可以用键盘上下方向键选择,按回车键搜索已选内容
- 在歌单详情页按退格键可以返回歌单列表
- 歌曲列表中可以使用`Ctrl`、`Shift`键进行多选这类似Windows下的文件选择详情看常见问题列表多选部分
- 在我的列表内可以使用`Ctrl + f`键打开搜索框进行列表内歌曲搜索,搜索框按`Esc`键可以关闭搜索框,搜索框内按上下方向键可以选择歌曲,按`回车`键跳转到已选歌曲,按`Ctrl + 回车`可以跳转并播放已选歌曲
- 在我的列表按住`Ctrl`键可以进入列表拖动模式,此时可以用鼠标拖动列表调整列表的位置
- 编辑列表名时按`Esc`键可以取消编辑
- 按`F11`可以进入、退出全屏状态v1.19.0新增)
## 歌曲无法试听与下载 ## 歌曲无法试听与下载
### 所有歌曲都提示 `请求异常😮,可以多试几次,若还是不行就换一首吧。。。` ### 所有歌曲都提示 `请求异常😮,可以多试几次,若还是不行就换一首吧。。。`

View File

@ -56,6 +56,8 @@
从v1.17.0起支持 Scheme URL可以使用此功能从浏览器等场景下调用LX Music我们开发了一个[油猴脚本](https://github.com/lyswhut/lx-music-script#readme)配套使用,<br> 从v1.17.0起支持 Scheme URL可以使用此功能从浏览器等场景下调用LX Music我们开发了一个[油猴脚本](https://github.com/lyswhut/lx-music-script#readme)配套使用,<br>
脚本安装地址:<https://greasyfork.org/zh-CN/scripts/438148><br> 脚本安装地址:<https://greasyfork.org/zh-CN/scripts/438148><br>
若你想自己调用LX Music可以看常见问题[Scheme URL支持](https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md#scheme-url%E6%94%AF%E6%8C%81)部分说明
#### 启动参数 #### 启动参数
目前软件已支持的启动参数如下: 目前软件已支持的启动参数如下:
@ -76,7 +78,7 @@
- Windows`%APPDATA%/lx-music-desktop` - Windows`%APPDATA%/lx-music-desktop`
- Linux`$XDG_CONFIG_HOME/lx-music-desktop` 或 `~/.config/lx-music-desktop` - Linux`$XDG_CONFIG_HOME/lx-music-desktop` 或 `~/.config/lx-music-desktop`
- macOS`~/Library/Application/lx-music-desktop` - macOS`~/Library/Application Support/lx-music-desktop`
在Windows平台下若程序目录下存在`portable`目录则自动使用此目录作为数据存储目录v1.17.0新增)。 在Windows平台下若程序目录下存在`portable`目录则自动使用此目录作为数据存储目录v1.17.0新增)。

818
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "lx-music-desktop", "name": "lx-music-desktop",
"version": "1.18.0", "version": "1.19.0",
"description": "一个免费的音乐查找助手", "description": "一个免费的音乐查找助手",
"main": "./dist/electron/main.js", "main": "./dist/electron/main.js",
"productName": "lx-music-desktop", "productName": "lx-music-desktop",
@ -173,7 +173,7 @@
}, },
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme", "homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
"devDependencies": { "devDependencies": {
"@babel/core": "^7.17.5", "@babel/core": "^7.17.8",
"@babel/eslint-parser": "^7.17.0", "@babel/eslint-parser": "^7.17.0",
"@babel/plugin-proposal-class-properties": "^7.16.7", "@babel/plugin-proposal-class-properties": "^7.16.7",
"@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-dynamic-import": "^7.8.3",
@ -183,25 +183,25 @@
"@babel/preset-env": "^7.16.11", "@babel/preset-env": "^7.16.11",
"babel-loader": "^8.2.3", "babel-loader": "^8.2.3",
"babel-preset-minify": "^0.5.1", "babel-preset-minify": "^0.5.1",
"browserslist": "^4.19.3", "browserslist": "^4.20.2",
"cfonts": "^2.10.0", "cfonts": "^2.10.0",
"chalk": "^4.1.2", "chalk": "^4.1.2",
"changelog-parser": "^2.8.0", "changelog-parser": "^2.8.1",
"copy-webpack-plugin": "^10.2.4", "copy-webpack-plugin": "^10.2.4",
"core-js": "^3.21.1", "core-js": "^3.21.1",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"css-loader": "^6.6.0", "css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^3.4.1", "css-minimizer-webpack-plugin": "^3.4.1",
"del": "^6.0.0", "del": "^6.0.0",
"electron": "^13.6.9", "electron": "^13.6.9",
"electron-builder": "^23.0.1", "electron-builder": "^23.0.2",
"electron-debug": "^3.2.0", "electron-debug": "^3.2.0",
"electron-devtools-installer": "^3.2.0", "electron-devtools-installer": "^3.2.0",
"electron-to-chromium": "^1.4.73", "electron-to-chromium": "^1.4.88",
"electron-updater": "^5.0.0", "electron-updater": "^5.0.0",
"eslint": "^8.10.0", "eslint": "^8.11.0",
"eslint-config-standard": "^16.0.3", "eslint-config-standard": "^16.0.3",
"eslint-formatter-friendly": "^7.0.0", "eslint-formatter-friendly": "git+https://github.com/lyswhut/eslint-friendly-formatter.git#2170d1320e2fad13615a9dcf229669f0bb473a53",
"eslint-plugin-html": "^6.2.0", "eslint-plugin-html": "^6.2.0",
"eslint-plugin-import": "^2.25.4", "eslint-plugin-import": "^2.25.4",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
@ -214,9 +214,9 @@
"less": "^4.1.2", "less": "^4.1.2",
"less-loader": "^10.2.0", "less-loader": "^10.2.0",
"markdown-it": "^12.3.2", "markdown-it": "^12.3.2",
"mini-css-extract-plugin": "^2.5.3", "mini-css-extract-plugin": "^2.6.0",
"node-loader": "^2.0.0", "node-loader": "^2.0.0",
"postcss": "^8.4.7", "postcss": "^8.4.12",
"postcss-loader": "^6.2.1", "postcss-loader": "^6.2.1",
"postcss-pxtorem": "^6.0.0", "postcss-pxtorem": "^6.0.0",
"pug": "^3.0.2", "pug": "^3.0.2",
@ -224,7 +224,7 @@
"pug-plain-loader": "^1.1.0", "pug-plain-loader": "^1.1.0",
"raw-loader": "^4.0.2", "raw-loader": "^4.0.2",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"spinnies": "^0.5.1", "spinnies": "git+https://github.com/lyswhut/spinnies.git#233305c58694aa3b053e3ab9af9049993f918b9d",
"svg-sprite-loader": "^6.0.11", "svg-sprite-loader": "^6.0.11",
"svg-transform-loader": "^2.0.13", "svg-transform-loader": "^2.0.13",
"svgo-loader": "^3.0.0", "svgo-loader": "^3.0.0",
@ -232,7 +232,7 @@
"url-loader": "^4.1.1", "url-loader": "^4.1.1",
"vue-loader": "^17.0.0", "vue-loader": "^17.0.0",
"vue-template-compiler": "^2.6.14", "vue-template-compiler": "^2.6.14",
"webpack": "^5.69.1", "webpack": "^5.70.0",
"webpack-cli": "^4.9.2", "webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.7.4", "webpack-dev-server": "^4.7.4",
"webpack-hot-middleware": "github:lyswhut/webpack-hot-middleware#329c4375134b89d39da23a56a94db651247c74a1", "webpack-hot-middleware": "github:lyswhut/webpack-hot-middleware#329c4375134b89d39da23a56a94db651247c74a1",
@ -244,7 +244,7 @@
"electron-log": "^4.4.6", "electron-log": "^4.4.6",
"electron-store": "^8.0.1", "electron-store": "^8.0.1",
"font-list": "github:lyswhut/node-font-list#4edbb1933b49a9bac1eedd63a31da16b487fe57d", "font-list": "github:lyswhut/node-font-list#4edbb1933b49a9bac1eedd63a31da16b487fe57d",
"http-terminator": "^3.0.4", "http-terminator": "^3.2.0",
"iconv-lite": "^0.6.3", "iconv-lite": "^0.6.3",
"image-size": "^1.0.1", "image-size": "^1.0.1",
"koa": "^2.13.4", "koa": "^2.13.4",
@ -255,10 +255,10 @@
"request": "^2.88.2", "request": "^2.88.2",
"socket.io": "^4.4.1", "socket.io": "^4.4.1",
"sortablejs": "^1.14.0", "sortablejs": "^1.14.0",
"utf-8-validate": "^5.0.8", "utf-8-validate": "^5.0.9",
"vue": "^3.2.31", "vue": "^3.2.31",
"vue-i18n": "^9.2.0-beta.30", "vue-i18n": "^9.2.0-beta.32",
"vue-router": "^4.0.12", "vue-router": "^4.0.14",
"vuex": "^4.0.2" "vuex": "^4.0.2"
} }
} }

View File

@ -1,29 +1,44 @@
### 新增 ### 新增
- 新增“双击列表里的歌曲时自动切换到当前列表播放”设置,此功能仅对歌单、排行榜有效,默认关闭 - 新增对播放详情页歌词大小、是否缩放、对齐方式的设置,可以去设置-播放详情页设置查看
- 新增打开收藏的在线列表的对应平台详情页功能,可以在我的列表-列表右键菜单中使用 - 新增播放详情页通过歌词调整播放进度,默认关闭,需要到设置-播放详情页设置开启,开启后在播放详情页拖动歌词时将会出现跳转当前行歌词播放的按钮
- 新增定时暂停播放功能,由于此功能大多数人可能不常用,所以将其放在设置-基本设置中 - 新增全屏状态按F11可以进入、退出全屏状态由于全屏时会隐藏控制栏按钮所以需要使用鼠标右键双击详情页的任意地方都可以来关闭播放详情页
- 新增任务栏缩略图工具栏控制按钮此功能仅在Windows平台可用按钮分别为收藏/取消收藏(将歌曲添加到“我的收藏”列表)、上一曲、播放/暂停、下一曲 - 新增动态主题“道法自然”,你可以预先设置一个亮色主题及暗色主题,此后将根据系统的亮、暗主题色自动切换为你预先设置的相应主题。注:鼠标 右击 此主题项即可打开亮、暗色主题设置窗口。
- 新增设置-基本设置-软件字体设置此设置可用于设置主界面的字体已知的问题Windows 7 下可能会出现字体列表为空的情况,这是当前系统的 Powershell 版本小于5.1导致的,请自行尝试看常见解决) - 新增对kw源卡拉OK歌词的支持
- 新增Scheme URL对音乐搜索的调用支持详情看常见问题-Scheme URL支持
- 新增Scheme URL以url传参的方式调用详情看常见问题-Scheme URL支持
- 自定义源新增更新弹窗方法,同时自定义源管理新增是否允许源显示更新弹窗设置(出于防止滥用考虑),当源作者想要通知用户源已更新时,可以调用此方法弹窗告诉用户,调用说明看常见问题-自定义源部分
### 优化 ### 优化
- 过滤tx源某些不支持播放的歌曲解决播放此类内容会导致意外的问题 - 优化Windows任务栏缩略图工具栏控制按钮在浅色任务栏下的显示效果
- 把歌曲的热门评论与最新评论拆分成两个列表显示 - 添加音频可视化与音频输出设备冲突的提示
- 优化歌词的播放偏移
- 优化托盘菜单操作(#686
- 优化播放下载列表时的切歌性能
### 修复 ### 修复
- 修复排行榜名字右击菜单的播放功能在播放非激活的列表时的列表获取问题 - 修复“当前的声音输出设备被改变时暂停播放歌曲”设置无效的问题
- 修复修改列表名时无法使用`Ctrl`键的问题 - 修复桌面歌词没有处理停止播放状态的问题
- 修复wy源某些歌曲获取歌词翻译的问题处理 - 修复AppImage包无法运行的问题
- 修复下载功能的歌词换源时会进入死循环的问题 - 修复Windows任务栏缩略图工具栏控制按钮的歌曲收藏按钮状态更新问题
- 修复某些歌曲无法下载的问题 - 修复使用链接导入的歌单无法在我的列表打开原歌单详情页的问题
- 修复windows平台下软件目录存在`portable`文件夹时,仍会创建`C:\Users\<user>\AppData\Roaming\lx-music-desktop\Dictionaries\en-US-9-0.bdic`文件的问题现在不会再创建文件但仍会创建空目录Electron的问题目前暂无解决方法 - 修复播放下载列表的歌曲时增删下载任务导致正在的歌曲序号改变时,不会更新到新增序号的问题
- 修复播放器的停止逻辑问题
### 其他 ### 文档
- 更新electron到v13.6.9 添加LX中定义的快捷操作汇总说明到常见问题中这是目前可用的鼠标、键盘快捷操作它们都可以在更新日志中找到
- 鼠标右击播放栏的歌曲图片封面可以定位当前播放的歌曲
- 鼠标右击播放栏进度条上的LRC按钮可以锁定/解锁桌面歌词
- 歌曲搜索框、歌单链接输入框内鼠标右击可以将当前剪贴板上的文字粘贴到输入框内
- 鼠标右击搜索界面中的单条搜索历史可以将其移除
- 歌曲列表内的文字在选中后,鼠标右击可以复制已选中的文字,此功能只对搜索、歌单、排行榜、我的列表中的列表有效
- 鼠标在播放详情页内右键双击可以关闭播放详情页
- 鼠标左击播放栏上的歌曲名字可以将它复制
- 鼠标右击“道法自然英文Auto”主题可以打开亮、暗主题设置窗口
- 歌曲搜索框的候选内容可以用键盘上下方向键选择,按回车键搜索已选内容
- 在歌单详情页按退格键可以返回歌单列表
- 歌曲列表中可以使用Ctrl、Shift键进行多选这类似Windows下的文件选择详情看常见问题列表多选部分
- 在我的列表内可以使用Ctrl+f键打开搜索框进行列表内歌曲搜索搜索框按Esc键可以关闭搜索框搜索框内按上下方向键可以选择歌曲按回车键跳转到已选歌曲按Ctrl+回车可以跳转并播放已选歌曲
- 在我的列表按住Ctrl键可以进入列表拖动模式此时可以用鼠标拖动列表调整列表的位置
- 编辑列表名时按Esc键可以取消编辑
- 按F11可以进入、退出全屏状态

File diff suppressed because one or more lines are too long

View File

@ -106,7 +106,7 @@ module.exports = {
}, },
{ {
id: 13, id: 13,
name: '黑纸白字', name: '黑灯瞎火',
className: 'black', className: 'black',
}, },
{ {
@ -125,4 +125,6 @@ module.exports = {
className: 'happy_new_year', className: 'happy_new_year',
}, },
], ],
themeLights: [0, 1, 2, 3, 4, 10, 5, 6, 11, 12, 7, 8, 9],
themeDarks: [13, 7],
} }

View File

@ -2,7 +2,7 @@ const path = require('path')
const os = require('os') const os = require('os')
const defaultSetting = { const defaultSetting = {
version: '1.0.51', version: '1.0.54',
player: { player: {
togglePlayMethod: 'listLoop', togglePlayMethod: 'listLoop',
highQuality: false, highQuality: false,
@ -19,6 +19,14 @@ const defaultSetting = {
waitPlayEndStop: true, waitPlayEndStop: true,
waitPlayEndStopTime: '', waitPlayEndStopTime: '',
}, },
playDetail: {
isZoomActiveLrc: true,
isShowLyricProgressSetting: false,
style: {
fontSize: 100,
align: 'center',
},
},
desktopLyric: { desktopLyric: {
enable: false, enable: false,
isLock: false, isLock: false,
@ -97,7 +105,11 @@ const defaultSetting = {
port: '23332', port: '23332',
}, },
windowSizeId: 2, windowSizeId: 2,
themeId: 0, theme: {
id: 0,
lightId: 0,
darkId: 13,
},
langId: null, langId: null,
sourceId: 'kw', sourceId: 'kw',
apiSource: 'temp', apiSource: 'temp',
@ -116,7 +128,7 @@ const overwriteSetting = {
// 使用新年皮肤 // 使用新年皮肤
if (new Date().getMonth() < 2) { if (new Date().getMonth() < 2) {
defaultSetting.themeId = 9 defaultSetting.theme.id = 9
defaultSetting.desktopLyric.theme = 3 defaultSetting.desktopLyric.theme = 3
} }

View File

@ -4,6 +4,7 @@ const names = {
close: 'close', close: 'close',
min: 'min', min: 'min',
max: 'max', max: 'max',
fullscreen: 'fullscreen',
set_app_name: 'set_app_name', set_app_name: 'set_app_name',
clear_cache: 'clear_cache', clear_cache: 'clear_cache',
get_cache_size: 'get_cache_size', get_cache_size: 'get_cache_size',
@ -15,6 +16,7 @@ const names = {
interval_callback: 'interval_callback', interval_callback: 'interval_callback',
interval_cancel: 'interval_cancel', interval_cancel: 'interval_cancel',
open_dev_tools: 'open_dev_tools', open_dev_tools: 'open_dev_tools',
system_theme_change: 'system_theme_change',
set_music_meta: 'set_music_meta', set_music_meta: 'set_music_meta',
progress: 'progress', progress: 'progress',

View File

@ -157,11 +157,6 @@ exports.initSetting = isShowErrorAlert => {
electronStore_config.set('setting.list.isSaveScrollLocation', scroll.enable) electronStore_config.set('setting.list.isSaveScrollLocation', scroll.enable)
delete setting.list.scroll delete setting.list.scroll
} }
if (setting.player.isShowLyricTransition != null) { // 修正拼写问题 v1.8.2 及以前
setting.player.isShowLyricTranslation = setting.player.isShowLyricTransition
delete setting.player.isShowLyricTransition
}
} }
// 从我的列表分离下载列表 v1.7.0 后 // 从我的列表分离下载列表 v1.7.0 后
@ -173,6 +168,18 @@ exports.initSetting = isShowErrorAlert => {
const { version: settingVersion, setting: newSetting } = exports.mergeSetting(setting, electronStore_config.get('version')) const { version: settingVersion, setting: newSetting } = exports.mergeSetting(setting, electronStore_config.get('version'))
// 修正拼写问题 v1.8.2 及以前
if (newSetting.player.isShowLyricTransition != null) {
newSetting.player.isShowLyricTranslation = newSetting.player.isShowLyricTransition
delete newSetting.player.isShowLyricTransition
}
// 迁移v1.19.0之前的主题设置
if (newSetting.themeId != null) {
newSetting.theme.id = newSetting.themeId
delete newSetting.themeId
}
// 重置 ^0.18.2 排行榜ID // 重置 ^0.18.2 排行榜ID
if (!newSetting.leaderboard.tabId.includes('__')) newSetting.leaderboard.tabId = 'kw__16' if (!newSetting.leaderboard.tabId.includes('__')) newSetting.leaderboard.tabId = 'kw__16'

View File

@ -1,6 +1,7 @@
{ {
"action": "Manage", "action": "Manage",
"agree": "Accept", "agree": "Accept",
"alert_button_text": "All right",
"audio_visualization": "Audio visualization (experimental)", "audio_visualization": "Audio visualization (experimental)",
"back": "Back", "back": "Back",
"cancel_button_text": "Cancel", "cancel_button_text": "Cancel",
@ -168,7 +169,7 @@
"player__music_singer": "Artist: ", "player__music_singer": "Artist: ",
"player__next": "Next", "player__next": "Next",
"player__pause": "Pause", "player__pause": "Pause",
"player__pic_tip": "Right click to locate the currently playing song in \"My List\"", "player__pic_tip": "Play details page (right-click to locate the currently playing song in \"My List\")",
"player__play": "Play", "player__play": "Play",
"player__play_toggle_mode_list": "Play in order", "player__play_toggle_mode_list": "Play in order",
"player__play_toggle_mode_list_loop": "List Loop", "player__play_toggle_mode_list_loop": "List Loop",
@ -227,6 +228,7 @@
"setting__basic_sourcename_real": "Original", "setting__basic_sourcename_real": "Original",
"setting__basic_sourcename_title": "Select the name of music source", "setting__basic_sourcename_title": "Select the name of music source",
"setting__basic_theme": "Theme", "setting__basic_theme": "Theme",
"setting__basic_theme_auto_tip": "This is a dynamic theme, you can preset a light theme and a dark theme, and then it will automatically switch to the corresponding theme you preset according to the system's light and dark theme colors.\nNote: Right-click this theme item to open the light and dark theme setting window.",
"setting__basic_to_tray": "Do not exit the software when closing the software and minimize it to the system tray", "setting__basic_to_tray": "Do not exit the software when closing the software and minimize it to the system tray",
"setting__basic_window_size": "Window size", "setting__basic_window_size": "Window size",
"setting__basic_window_size_big": "Large", "setting__basic_window_size_big": "Large",
@ -323,16 +325,29 @@
"setting__other_tray_theme_native": "White", "setting__other_tray_theme_native": "White",
"setting__other_tray_theme_origin": "Primary Color", "setting__other_tray_theme_origin": "Primary Color",
"setting__play": "Play", "setting__play": "Play",
"setting__play_detail": "Play details page settings",
"setting__play_detail_align": "Lyric Alignment",
"setting__play_detail_align_center": "Centered",
"setting__play_detail_align_left": "Left",
"setting__play_detail_align_right": "Right",
"setting__play_detail_detail_lyric_progress": "Allows to adjust playback progress by lyrics",
"setting__play_detail_font_size": "Lyrics font size (you can use the keyboard + - adjust the font size on the playback details page)",
"setting__play_detail_font_size_current": "Current font size: {size}",
"setting__play_detail_font_size_reset": "Reset",
"setting__play_detail_font_zoom": "Zoom the currently playing lyrics",
"setting__play_lyric_lxlrc": "Use Karaoke-style lyrics playback (if supported)", "setting__play_lyric_lxlrc": "Use Karaoke-style lyrics playback (if supported)",
"setting__play_lyric_s2t": "Convert the playing and downloading lyrics to Traditional Chinese", "setting__play_lyric_s2t": "Convert the playing and downloading lyrics to Traditional Chinese",
"setting__play_lyric_transition": "Show lyrics translation", "setting__play_lyric_transition": "Show lyrics translation",
"setting__play_mediaDevice": "Audio output", "setting__play_mediaDevice": "Audio output",
"setting__play_mediaDevice_remove_stop_play": "Pause the song when the current sound output device is changed", "setting__play_mediaDevice_remove_stop_play": "Pause the song when the current sound output device is changed",
"setting__play_mediaDevice_title": "Select a media device for audio output", "setting__play_mediaDevice_title": "Select a media device for audio output",
"setting__play_media_device_error_tip": "This function conflicts with the audio visualization function. You have enabled audio visualization when you started the software this time. This setting is temporarily unavailable. Please restart the software and then modify this setting.",
"setting__play_media_device_tip": "This feature conflicts with Audio Visualization, both cannot be enabled at the same time, would you like to turn Audio Visualization off and apply the selected audio output settings?",
"setting__play_quality": "Play 320K quality songs first (if supported)", "setting__play_quality": "Play 320K quality songs first (if supported)",
"setting__play_save_play_time": "Remember playback progress", "setting__play_save_play_time": "Remember playback progress",
"setting__play_task_bar": "Show playing progress on the taskbar", "setting__play_task_bar": "Show playing progress on the taskbar",
"setting__play_timeout": "Timed pause", "setting__play_timeout": "Timed pause",
"setting__player_audio_visualization_tip": "The custom audio output device will conflict with the audio visualization function. After the audio visualization is enabled, the audio output device will be reset to the default output device. At present, this problem cannot be solved. Do you still want to enable it?",
"setting__search": "Search", "setting__search": "Search",
"setting__search_focus_search_box": "Automatically focus the search box on startup", "setting__search_focus_search_box": "Automatically focus the search box on startup",
"setting__search_history": "Search history", "setting__search_history": "Search history",
@ -397,6 +412,8 @@
"sync__title": "Choose how to synchronize the list with {name}", "sync__title": "Choose how to synchronize the list with {name}",
"tag__high_quality": "HQ", "tag__high_quality": "HQ",
"tag__lossless": "SQ", "tag__lossless": "SQ",
"theme_auto": "Auto",
"theme_auto_tip": "Right-click to open the light and dark theme settings window",
"theme_black": "Black", "theme_black": "Black",
"theme_blue": "Blue", "theme_blue": "Blue",
"theme_blue2": "Purple Blue", "theme_blue2": "Purple Blue",
@ -410,6 +427,10 @@
"theme_pink": "Pink", "theme_pink": "Pink",
"theme_purple": "Purple", "theme_purple": "Purple",
"theme_red": "Red", "theme_red": "Red",
"theme_selector_modal__dark_title": "dark theme",
"theme_selector_modal__light_title": "Bright theme",
"theme_selector_modal__title": "Follow system theme settings",
"theme_selector_modal__title_tip": "Note: You can set a light theme and a dark theme in advance, and then it will automatically switch to the corresponding theme you set in advance according to the light and dark theme colors of the system.",
"theme_yellow": "Yellow", "theme_yellow": "Yellow",
"user_api__allow_show_update_alert": "Allow update popup to show", "user_api__allow_show_update_alert": "Allow update popup to show",
"user_api__btn_export": "Export", "user_api__btn_export": "Export",

View File

@ -1,6 +1,7 @@
{ {
"action": "操作", "action": "操作",
"agree": "接受", "agree": "接受",
"alert_button_text": "好吧",
"audio_visualization": "音频可视化(实验性)", "audio_visualization": "音频可视化(实验性)",
"back": "返回", "back": "返回",
"cancel_button_text": "我不", "cancel_button_text": "我不",
@ -168,7 +169,7 @@
"player__music_singer": "艺术家:", "player__music_singer": "艺术家:",
"player__next": "下一首", "player__next": "下一首",
"player__pause": "暂停", "player__pause": "暂停",
"player__pic_tip": "右击在“我的列表”定位当前播放的歌曲", "player__pic_tip": "播放详情页(右击在“我的列表”定位当前播放的歌曲",
"player__play": "播放", "player__play": "播放",
"player__play_toggle_mode_list": "顺序播放", "player__play_toggle_mode_list": "顺序播放",
"player__play_toggle_mode_list_loop": "列表循环", "player__play_toggle_mode_list_loop": "列表循环",
@ -227,6 +228,7 @@
"setting__basic_sourcename_real": "原名", "setting__basic_sourcename_real": "原名",
"setting__basic_sourcename_title": "选择音源名字类型", "setting__basic_sourcename_title": "选择音源名字类型",
"setting__basic_theme": "主题颜色", "setting__basic_theme": "主题颜色",
"setting__basic_theme_auto_tip": "此乃动态主题,你可以预先设置一个亮色主题及暗色主题,此后将根据系统的亮、暗主题色自动切换为你预先设置的相应主题。\n注鼠标 右击 此主题项即可打开亮、暗色主题设置窗口。",
"setting__basic_to_tray": "关闭软件时不退出软件将其最小化到系统托盘", "setting__basic_to_tray": "关闭软件时不退出软件将其最小化到系统托盘",
"setting__basic_window_size": "窗口尺寸", "setting__basic_window_size": "窗口尺寸",
"setting__basic_window_size_big": "大", "setting__basic_window_size_big": "大",
@ -323,16 +325,29 @@
"setting__other_tray_theme_native": "白色", "setting__other_tray_theme_native": "白色",
"setting__other_tray_theme_origin": "原色", "setting__other_tray_theme_origin": "原色",
"setting__play": "播放设置", "setting__play": "播放设置",
"setting__play_detail": "播放详情页设置",
"setting__play_detail_align": "歌词对齐方式",
"setting__play_detail_align_center": "居中",
"setting__play_detail_align_left": "居左",
"setting__play_detail_align_right": "居右",
"setting__play_detail_detail_lyric_progress": "允许通过歌词调整播放进度",
"setting__play_detail_font_size": "歌词字体大小(可以在播放详情页使用键盘的 + - 调整字体大小)",
"setting__play_detail_font_size_current": "当前字体大小:{size}",
"setting__play_detail_font_size_reset": "重置",
"setting__play_detail_font_zoom": "缩放当前正在播放的歌词",
"setting__play_lyric_lxlrc": "使用卡拉OK式歌词播放如果支持", "setting__play_lyric_lxlrc": "使用卡拉OK式歌词播放如果支持",
"setting__play_lyric_s2t": "将播放与下载的歌词转换为繁体中文", "setting__play_lyric_s2t": "将播放与下载的歌词转换为繁体中文",
"setting__play_lyric_transition": "显示歌词翻译", "setting__play_lyric_transition": "显示歌词翻译",
"setting__play_mediaDevice": "音频输出", "setting__play_mediaDevice": "音频输出",
"setting__play_mediaDevice_remove_stop_play": "当前的声音输出设备被改变时暂停播放歌曲", "setting__play_mediaDevice_remove_stop_play": "当前的声音输出设备被改变时暂停播放歌曲",
"setting__play_mediaDevice_title": "选择声音输出的媒体设备", "setting__play_mediaDevice_title": "选择声音输出的媒体设备",
"setting__play_media_device_error_tip": "此功能与音频可视化功能冲突,你本次启动软件时已启用过音频可视化,此设置暂不可用,请 重启 软件后,再来修改此设置。",
"setting__play_media_device_tip": "此功能与音频可视化功能冲突,两者无法同时启用,是否将音频可视化关闭 并 应用所选音频输出设置?",
"setting__play_quality": "优先播放320K品质的歌曲如果支持", "setting__play_quality": "优先播放320K品质的歌曲如果支持",
"setting__play_save_play_time": "记住播放进度", "setting__play_save_play_time": "记住播放进度",
"setting__play_task_bar": "在任务栏上显示当前歌曲播放进度", "setting__play_task_bar": "在任务栏上显示当前歌曲播放进度",
"setting__play_timeout": "定时暂停", "setting__play_timeout": "定时暂停",
"setting__player_audio_visualization_tip": "自定义音频输出设备与音频可视化功能会冲突,启用了音频可视化后音频输出设备将会被重置为默认的输出设备,目前此问题暂无法解决,是否仍要开启?",
"setting__search": "搜索设置", "setting__search": "搜索设置",
"setting__search_focus_search_box": "启动时自动聚焦搜索框", "setting__search_focus_search_box": "启动时自动聚焦搜索框",
"setting__search_history": "显示历史搜索记录", "setting__search_history": "显示历史搜索记录",
@ -397,6 +412,8 @@
"sync__title": "选择与 {name} 的列表同步方式", "sync__title": "选择与 {name} 的列表同步方式",
"tag__high_quality": "HQ", "tag__high_quality": "HQ",
"tag__lossless": "SQ", "tag__lossless": "SQ",
"theme_auto": "道法自然",
"theme_auto_tip": "鼠标 右击 可打开亮、暗主题设置窗口",
"theme_black": "黑灯瞎火", "theme_black": "黑灯瞎火",
"theme_blue": "蓝田生玉", "theme_blue": "蓝田生玉",
"theme_blue2": "清热版蓝", "theme_blue2": "清热版蓝",
@ -410,6 +427,10 @@
"theme_pink": "粉装玉琢", "theme_pink": "粉装玉琢",
"theme_purple": "重斤球紫", "theme_purple": "重斤球紫",
"theme_red": "热情似火", "theme_red": "热情似火",
"theme_selector_modal__dark_title": "暗色主题",
"theme_selector_modal__light_title": "亮色主题",
"theme_selector_modal__title": "跟随系统主题设置",
"theme_selector_modal__title_tip": "注:你可以预先设置一个亮色主题及暗色主题,此后将根据系统的亮、暗主题色自动切换为你预先设置的相应主题。",
"theme_yellow": "信口雌黄", "theme_yellow": "信口雌黄",
"user_api__allow_show_update_alert": "允许显示更新弹窗", "user_api__allow_show_update_alert": "允许显示更新弹窗",
"user_api__btn_export": "导出", "user_api__btn_export": "导出",

View File

@ -1,6 +1,7 @@
{ {
"action": "操作", "action": "操作",
"agree": "接受", "agree": "接受",
"alert_button_text": "好吧",
"audio_visualization": "音頻可視化(實驗性)", "audio_visualization": "音頻可視化(實驗性)",
"back": "返回", "back": "返回",
"cancel_button_text": "取消", "cancel_button_text": "取消",
@ -168,7 +169,7 @@
"player__music_singer": "藝術家:", "player__music_singer": "藝術家:",
"player__next": "下一首", "player__next": "下一首",
"player__pause": "暫停", "player__pause": "暫停",
"player__pic_tip": "右擊在“我的列表”定位當前播放的歌曲", "player__pic_tip": "播放詳情頁(右擊在“我的列表”定位當前播放的歌曲",
"player__play": "播放", "player__play": "播放",
"player__play_toggle_mode_list": "順序播放", "player__play_toggle_mode_list": "順序播放",
"player__play_toggle_mode_list_loop": "列表循環", "player__play_toggle_mode_list_loop": "列表循環",
@ -227,6 +228,7 @@
"setting__basic_sourcename_real": "原名", "setting__basic_sourcename_real": "原名",
"setting__basic_sourcename_title": "選擇音源名字類型", "setting__basic_sourcename_title": "選擇音源名字類型",
"setting__basic_theme": "主題顏色", "setting__basic_theme": "主題顏色",
"setting__basic_theme_auto_tip": "此乃動態主題,你可以預先設置一個亮色主題及暗色主題,此後將根據系統的亮、暗主題色自動切換為你預先設置的相應主題。\n注鼠標 右擊 此主題項即可打開亮、暗色主題設置窗口。",
"setting__basic_to_tray": "關閉軟件時不退出軟件將其最小化到系統托盤", "setting__basic_to_tray": "關閉軟件時不退出軟件將其最小化到系統托盤",
"setting__basic_window_size": "窗口尺寸", "setting__basic_window_size": "窗口尺寸",
"setting__basic_window_size_big": "大", "setting__basic_window_size_big": "大",
@ -323,16 +325,29 @@
"setting__other_tray_theme_native": "白色", "setting__other_tray_theme_native": "白色",
"setting__other_tray_theme_origin": "原色", "setting__other_tray_theme_origin": "原色",
"setting__play": "播放設置", "setting__play": "播放設置",
"setting__play_detail": "播放詳情頁設置",
"setting__play_detail_align": "歌詞對齊方式",
"setting__play_detail_align_center": "居中",
"setting__play_detail_align_left": "居左",
"setting__play_detail_align_right": "居右",
"setting__play_detail_detail_lyric_progress": "允許通過歌詞調整播放進度",
"setting__play_detail_font_size": "歌詞字體大小(可以在播放詳情頁使用鍵盤的 + - 調整字體大小)",
"setting__play_detail_font_size_current": "當前字體大小:{size}",
"setting__play_detail_font_size_reset": "重置",
"setting__play_detail_font_zoom": "縮放當前正在播放的歌詞",
"setting__play_lyric_lxlrc": "使用卡拉OK式歌詞播放如果支持", "setting__play_lyric_lxlrc": "使用卡拉OK式歌詞播放如果支持",
"setting__play_lyric_s2t": "將播放與下載的歌詞轉換為繁體中文", "setting__play_lyric_s2t": "將播放與下載的歌詞轉換為繁體中文",
"setting__play_lyric_transition": "顯示歌詞翻譯", "setting__play_lyric_transition": "顯示歌詞翻譯",
"setting__play_mediaDevice": "音頻輸出", "setting__play_mediaDevice": "音頻輸出",
"setting__play_mediaDevice_remove_stop_play": "當前的聲音輸出設備被改變時暫停播放歌曲", "setting__play_mediaDevice_remove_stop_play": "當前的聲音輸出設備被改變時暫停播放歌曲",
"setting__play_mediaDevice_title": "選擇聲音輸出的媒體設備", "setting__play_mediaDevice_title": "選擇聲音輸出的媒體設備",
"setting__play_media_device_error_tip": "此功能與音頻可視化功能衝突,你本次啟動軟件時已啟用過音頻可視化,此設置暫不可用,請 重啟 軟件後,再來修改此設置。",
"setting__play_media_device_tip": "此功能與音頻可視化功能衝突,兩者無法同時啟用,是否將音頻可視化關閉 並 應用所選音頻輸出設置?",
"setting__play_quality": "優先播放320K品質的歌曲如果支持", "setting__play_quality": "優先播放320K品質的歌曲如果支持",
"setting__play_save_play_time": "記住播放進度", "setting__play_save_play_time": "記住播放進度",
"setting__play_task_bar": "在任務欄上顯示當前歌曲播放進度", "setting__play_task_bar": "在任務欄上顯示當前歌曲播放進度",
"setting__play_timeout": "定時暫停", "setting__play_timeout": "定時暫停",
"setting__player_audio_visualization_tip": "自定義音頻輸出設備與音頻可視化功能會衝突,啟用了音頻可視化後音頻輸出設備將會被重置為默認的輸出設備,目前此問題暫無法解決,是否仍要開啟?",
"setting__search": "搜索設置", "setting__search": "搜索設置",
"setting__search_focus_search_box": "啟動時自動聚焦搜索框", "setting__search_focus_search_box": "啟動時自動聚焦搜索框",
"setting__search_history": "顯示歷史搜索記錄", "setting__search_history": "顯示歷史搜索記錄",
@ -397,6 +412,8 @@
"sync__title": "選擇與 {name} 的列表同步方式", "sync__title": "選擇與 {name} 的列表同步方式",
"tag__high_quality": "HQ", "tag__high_quality": "HQ",
"tag__lossless": "SQ", "tag__lossless": "SQ",
"theme_auto": "道法自然",
"theme_auto_tip": "鼠標 右擊 可打開亮、暗主題設置窗口",
"theme_black": "黑燈瞎火", "theme_black": "黑燈瞎火",
"theme_blue": "藍田生玉", "theme_blue": "藍田生玉",
"theme_blue2": "清熱版藍", "theme_blue2": "清熱版藍",
@ -410,6 +427,10 @@
"theme_pink": "粉裝玉琢", "theme_pink": "粉裝玉琢",
"theme_purple": "重斤球紫", "theme_purple": "重斤球紫",
"theme_red": "熱情似火", "theme_red": "熱情似火",
"theme_selector_modal__dark_title": "暗色主題",
"theme_selector_modal__light_title": "亮色主題",
"theme_selector_modal__title": "跟隨系統主題設置",
"theme_selector_modal__title_tip": "注:你可以預先設置一個亮色主題及暗色主題,此後將根據系統的亮、暗主題色自動切換為你預先設置的相應主題。",
"theme_yellow": "信口雌黃", "theme_yellow": "信口雌黃",
"user_api__allow_show_update_alert": "允許顯示更新彈窗", "user_api__allow_show_update_alert": "允許顯示更新彈窗",
"user_api__btn_export": "導出", "user_api__btn_export": "導出",

View File

@ -26,6 +26,14 @@ class MainWindow extends EventEmitter {
this.emit(MAIN_WINDOW_EVENT_NAME.show) this.emit(MAIN_WINDOW_EVENT_NAME.show)
} }
focus() {
this.emit(MAIN_WINDOW_EVENT_NAME.focus)
}
blur() {
this.emit(MAIN_WINDOW_EVENT_NAME.blur)
}
hide() { hide() {
this.emit(MAIN_WINDOW_EVENT_NAME.hide) this.emit(MAIN_WINDOW_EVENT_NAME.hide)
} }

View File

@ -14,6 +14,8 @@ exports.mainWindow = {
ready_to_show: 'ready_to_show', ready_to_show: 'ready_to_show',
show: 'show', show: 'show',
hide: 'hide', hide: 'hide',
focus: 'focus',
blur: 'blur',
} }
exports.tray = { exports.tray = {

View File

@ -6,7 +6,7 @@
*/ */
const electron = require('electron') const electron = require('electron')
const electronDebug = require('electron-debug') const electronDebug = require('electron-debug')
const { default: installExtension, VUEJS3_DEVTOOLS } = require('electron-devtools-installer') const { default: installExtension, VUEJS_DEVTOOLS } = require('electron-devtools-installer')
// Install `electron-debug` with `devtron` // Install `electron-debug` with `devtron`
electronDebug({ electronDebug({
showDevTools: true, showDevTools: true,
@ -15,7 +15,7 @@ electronDebug({
// Install `vue-devtools` // Install `vue-devtools`
electron.app.on('ready', () => { electron.app.on('ready', () => {
installExtension(VUEJS3_DEVTOOLS) installExtension(VUEJS_DEVTOOLS)
.then(name => console.log(`Added Extension: ${name}`)) .then(name => console.log(`Added Extension: ${name}`))
.catch(err => console.log('An error occurred: ', err)) .catch(err => console.log('An error occurred: ', err))
}) })

View File

@ -1,4 +1,4 @@
const { app, BrowserWindow, shell } = require('electron') const { app, BrowserWindow, shell, nativeTheme } = require('electron')
const path = require('path') const path = require('path')
const urlSchemeRxp = /^lxmusic:\/\// const urlSchemeRxp = /^lxmusic:\/\//
@ -170,7 +170,7 @@ function createWindow() {
// icon: path.join(global.__static, isWin ? 'icons/256x256.ico' : 'icons/512x512.png'), // icon: path.join(global.__static, isWin ? 'icons/256x256.ico' : 'icons/512x512.png'),
resizable: false, resizable: false,
maximizable: false, maximizable: false,
fullscreenable: false, fullscreenable: true,
show: false, show: false,
webPreferences: { webPreferences: {
contextIsolation: false, contextIsolation: false,
@ -180,7 +180,14 @@ function createWindow() {
}, },
}) })
global.modules.mainWindow.loadURL(winURL + `?dt=${!!global.envParams.cmdParams.dt}&theme=${themes.find(t => t.id == global.appSetting.themeId)?.className ?? themes[0].className}`) const shouldUseDarkColors = nativeTheme.shouldUseDarkColors
const themeId = global.appSetting.theme.id == 'auto'
? shouldUseDarkColors
? global.appSetting.theme.darkId
: global.appSetting.theme.lightId
: global.appSetting.theme.id
const themeClass = themes.find(t => t.id == themeId)?.className ?? themes[0].className
global.modules.mainWindow.loadURL(winURL + `?dt=${!!global.envParams.cmdParams.dt}&dark=${shouldUseDarkColors}&theme=${themeClass}`)
winEvent(global.modules.mainWindow) winEvent(global.modules.mainWindow)
// global.modules.mainWindow.webContents.openDevTools() // global.modules.mainWindow.webContents.openDevTools()

View File

@ -1,5 +1,5 @@
const { app, Tray, Menu, nativeImage } = require('electron') const { app, Tray, Menu, nativeImage } = require('electron')
// const { isWin } = require('../../common/utils') const { isWin } = require('@common/utils')
const { tray: TRAY_EVENT_NAME, common: COMMON_EVENT_NAME, mainWindow: MAIN_WINDOW_NAME } = require('../events/_name') const { tray: TRAY_EVENT_NAME, common: COMMON_EVENT_NAME, mainWindow: MAIN_WINDOW_NAME } = require('../events/_name')
const path = require('path') const path = require('path')
let isEnableTray = null let isEnableTray = null
@ -40,6 +40,14 @@ global.lx_event.mainWindow.on(MAIN_WINDOW_NAME.ready_to_show, () => {
global.lx_event.mainWindow.on(MAIN_WINDOW_NAME.show, () => { global.lx_event.mainWindow.on(MAIN_WINDOW_NAME.show, () => {
createMenu(global.modules.tray) createMenu(global.modules.tray)
}) })
if (!isWin) {
global.lx_event.mainWindow.on(MAIN_WINDOW_NAME.focus, () => {
createMenu(global.modules.tray)
})
global.lx_event.mainWindow.on(MAIN_WINDOW_NAME.blur, () => {
createMenu(global.modules.tray)
})
}
global.lx_event.mainWindow.on(MAIN_WINDOW_NAME.hide, () => { global.lx_event.mainWindow.on(MAIN_WINDOW_NAME.hide, () => {
createMenu(global.modules.tray) createMenu(global.modules.tray)
}) })
@ -75,24 +83,27 @@ const destroyTray = () => {
const createMenu = tray => { const createMenu = tray => {
if (!global.modules.tray) return if (!global.modules.tray) return
let menu = [] let menu = []
global.modules.mainWindow && menu.push(global.modules.mainWindow.isVisible() if (global.modules.mainWindow) {
? { const isShow = global.modules.mainWindow.isVisible() && (isWin ? true : global.modules.mainWindow.isFocused())
label: '隐藏主界面', menu.push(isShow
click() { ? {
global.modules.mainWindow.hide() label: '隐藏主界面',
}, click() {
} global.modules.mainWindow.hide()
: { },
label: '显示主界面', }
click() { : {
if (!global.modules.mainWindow) return label: '显示主界面',
if (!global.modules.mainWindow.isVisible()) { click() {
global.modules.mainWindow.show() if (!global.modules.mainWindow) return
} if (!global.modules.mainWindow.isVisible()) {
global.modules.mainWindow.restore() global.modules.mainWindow.show()
global.modules.mainWindow.focus() }
}, global.modules.mainWindow.restore()
}) global.modules.mainWindow.focus()
},
})
}
menu.push(global.appSetting.desktopLyric.enable menu.push(global.appSetting.desktopLyric.enable
? { ? {
label: '关闭桌面歌词', label: '关闭桌面歌词',

View File

@ -22,10 +22,11 @@ require('./musicUrl')
require('./systemFonts') require('./systemFonts')
require('./wait') require('./wait')
require('./openDevtools') require('./openDevtools')
require('./nativeTheme')
if (isWin) require('./taskbar') if (isWin) require('./taskbar')
// require('./kw_decodeLyric') require('./kw_decodeLyric')
require('./userApi') require('./userApi')
require('./sync') require('./sync')

View File

@ -0,0 +1,11 @@
const { nativeTheme } = require('electron')
const { NAMES: { mainWindow: ipcMainWindowNames }, mainSend } = require('@common/ipc')
nativeTheme.addListener('updated', (event) => {
// console.log(event.sender.shouldUseDarkColors)
if (!global.modules.mainWindow) return
mainSend(global.modules.mainWindow, ipcMainWindowNames.system_theme_change, event.sender.shouldUseDarkColors)
// console.log(nativeTheme.themeSource)
})

View File

@ -1,5 +1,5 @@
const { app } = require('electron') const { app } = require('electron')
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc') const { mainOn, mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
mainOn(ipcMainWindowNames.min, event => { mainOn(ipcMainWindowNames.min, event => {
if (global.modules.mainWindow) { if (global.modules.mainWindow) {
@ -16,3 +16,8 @@ mainOn(ipcMainWindowNames.close, (event, isForce) => {
global.isTrafficLightClose = true global.isTrafficLightClose = true
if (global.modules.mainWindow) global.modules.mainWindow.close() if (global.modules.mainWindow) global.modules.mainWindow.close()
}) })
mainHandle(ipcMainWindowNames.fullscreen, async(event, isFullscreen) => {
if (!global.modules.mainWindow) return false
await global.modules.mainWindow.setFullScreen(isFullscreen)
return isFullscreen
})

View File

@ -26,6 +26,11 @@ module.exports = mainWindow => {
// }) // })
mainWindow.on('focus', () => { mainWindow.on('focus', () => {
mainSend(mainWindow, ipcMainWindowNames.focus) mainSend(mainWindow, ipcMainWindowNames.focus)
global.lx_event.mainWindow.focus()
})
mainWindow.on('blur', () => {
global.lx_event.mainWindow.blur()
}) })
mainWindow.once('ready-to-show', () => { mainWindow.once('ready-to-show', () => {

View File

@ -172,7 +172,6 @@ export default {
this.lyric.lines = lines this.lyric.lines = lines
this.lyric.line = 0 this.lyric.line = 0
}, },
offset: 100,
}) })
}, },
mounted() { mounted() {
@ -207,6 +206,13 @@ export default {
this.isPlay = false this.isPlay = false
window.lrc.pause() window.lrc.pause()
break break
case 'stop':
this.isPlay = false
this.lyrics.lyric = ''
this.lyrics.tlyric = ''
this.lyrics.lxlyric = ''
this.setLyric()
break
case 'info': case 'info':
// console.log('info', data) // console.log('info', data)
this.lyrics.lyric = data.lrc this.lyrics.lyric = data.lrc
@ -215,7 +221,9 @@ export default {
this.setLyric() this.setLyric()
this.$nextTick(() => { this.$nextTick(() => {
this.lyric.line = data.line this.lyric.line = data.line
rendererSend(NAMES.winLyric.get_lyric_info, 'status') setTimeout(() => {
rendererSend(NAMES.winLyric.get_lyric_info, 'status')
})
}) })
case 'music_info': case 'music_info':
this.musicInfo.name = data.name this.musicInfo.name = data.name

View File

@ -16,24 +16,36 @@
<script> <script>
import { useRefGetter, watch, onMounted } from '@renderer/utils/vueTools' import { useRefGetter, watch, onMounted } from '@renderer/utils/vueTools'
import useApp from '@renderer/core/useApp' import useApp from '@renderer/core/useApp'
import { isFullscreen } from '@renderer/core/share'
import { getFontSizeWithScreen } from '@renderer/utils'
export default { export default {
setup() { setup() {
const theme = useRefGetter('theme') const theme = useRefGetter('theme')
const font = useRefGetter('font') const font = useRefGetter('font')
const windowSizeActive = useRefGetter('windowSizeActive')
const dom_root = document.getElementById('root') const dom_root = document.getElementById('root')
watch(theme, (val) => { watch(theme, (val) => {
dom_root.className = val dom_root.className = val
}, {
immediate: true,
}) })
watch(font, (val) => { watch(font, (val) => {
document.documentElement.style.fontFamily = val document.documentElement.style.fontFamily = val
}, { }, {
immediate: true, immediate: true,
}) })
watch(isFullscreen, val => {
if (val) {
document.body.classList.remove(window.dt ? 'disableTransparent' : 'transparent')
document.body.classList.add('fullscreen')
document.documentElement.style.fontSize = getFontSizeWithScreen(window.screen.width) + 'px'
} else {
document.body.classList.remove('fullscreen')
document.body.classList.add(window.dt ? 'disableTransparent' : 'transparent')
document.documentElement.style.fontSize = windowSizeActive.value.fontSize
}
})
useApp() useApp()
@ -85,13 +97,13 @@ body {
.transparent { .transparent {
padding: @shadow-app; padding: @shadow-app;
#waiting-mask { // #waiting-mask {
border-radius: @radius-border; // border-radius: @radius-border;
left: @shadow-app; // left: @shadow-app;
right: @shadow-app; // right: @shadow-app;
top: @shadow-app; // top: @shadow-app;
bottom: @shadow-app; // bottom: @shadow-app;
} // }
#root { #root {
box-shadow: 0 0 @shadow-app rgba(0, 0, 0, 0.5); box-shadow: 0 0 @shadow-app rgba(0, 0, 0, 0.5);
border-radius: @radius-border; border-radius: @radius-border;
@ -114,6 +126,14 @@ body {
margin-right: 5Px; margin-right: 5Px;
} }
} }
.fullscreen {
background-color: #fff;
#right {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
#container { #container {
position: relative; position: relative;

View File

@ -86,7 +86,8 @@ table {
flex-flow: row nowrap; flex-flow: row nowrap;
align-items: center; align-items: center;
// border-top: 1px solid rgba(0, 0, 0, 0.12); // border-top: 1px solid rgba(0, 0, 0, 0.12);
transition: background-color 0.2s ease; transition: 0.2s ease;
transition-property: background-color, color;
border-bottom: 1px solid @color-theme_2-line; border-bottom: 1px solid @color-theme_2-line;
box-sizing: border-box; box-sizing: border-box;
&:hover { &:hover {
@ -102,7 +103,7 @@ table {
flex: none; flex: none;
padding: 0 6px; padding: 0 6px;
position: relative; position: relative;
transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1); // transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
font-size: 13px; font-size: 13px;
line-height: 16px; line-height: 16px;
vertical-align: middle; vertical-align: middle;
@ -223,13 +224,13 @@ input, textarea {
border-radius: 3px; border-radius: 3px;
background-color: @color-scrollbar-thumb; background-color: @color-scrollbar-thumb;
// background-color: rgba(0, 0, 0, 0.2); // background-color: rgba(0, 0, 0, 0.2);
transition: all 0.4s ease; transition: background-color 0.4s ease;
} }
&::-webkit-scrollbar-thumb:hover { &::-webkit-scrollbar-thumb:hover {
border-radius: 3px; border-radius: 3px;
background-color: @color-scrollbar-thumb-hover; background-color: @color-scrollbar-thumb-hover;
// background-color: rgba(0, 0, 0, 0.4); // background-color: rgba(0, 0, 0, 0.4);
transition: all 0.4s ease; transition: background-color 0.4s ease;
} }
} }

View File

@ -1,6 +1,10 @@
<template> <template>
<ul :class="$style.list" :style="listStyles" ref="dom_list"> <ul :class="$style.list" :style="listStyles" ref="dom_list" role="toolbar" :aria-hidden="!show">
<li v-for="item in menus" <li v-for="item in menus"
role="tab"
tabindex="0"
:aria-label="item[itemName]"
ignore-tip
:key="item.action" :key="item.action"
@click="handleClick(item)" @click="handleClick(item)"
v-show="!item.hide && (item.action == 'download' ? setting.download.enable : true)" v-show="!item.hide && (item.action == 'download' ? setting.download.enable : true)"

View File

@ -10,7 +10,7 @@
</div> </div>
<ul class="selection-list scroll" :class="$style.list" :style="listStyles" ref="dom_list"> <ul class="selection-list scroll" :class="$style.list" :style="listStyles" ref="dom_list">
<li v-for="(item, index) in list" :key="index" :class="(itemKey ? item[itemKey] : item) == modelValue ? $style.active : null" <li v-for="(item, index) in list" :key="index" :class="(itemKey ? item[itemKey] : item) == modelValue ? $style.active : null"
@click="handleClick(item)" :tips="itemName ? item[itemName] : item">{{itemName ? item[itemName] : item}}</li> @click="handleClick(item)" :aria-label="itemName ? item[itemName] : item">{{itemName ? item[itemName] : item}}</li>
</ul> </ul>
</div> </div>
</template> </template>

View File

@ -141,8 +141,8 @@ export default {
const scrollContainerHeight = dom_scrollContainer.value.clientHeight const scrollContainerHeight = dom_scrollContainer.value.clientHeight
const currentEndIndex = currentStartIndex + Math.ceil(scrollContainerHeight / itemHeight) const currentEndIndex = currentStartIndex + Math.ceil(scrollContainerHeight / itemHeight)
const continuous = currentStartIndex <= endIndex && currentEndIndex >= startIndex const continuous = currentStartIndex <= endIndex && currentEndIndex >= startIndex
const currentStartRenderIndex = Math.max(Math.floor(currentScrollTop / itemHeight) - props.outsideNum, 0) const currentStartRenderIndex = Math.max(currentStartIndex - props.outsideNum, 0)
const currentEndRenderIndex = currentStartIndex + Math.ceil(scrollContainerHeight / itemHeight) + props.outsideNum const currentEndRenderIndex = currentEndIndex + props.outsideNum + 1
// console.log(continuous) // console.log(continuous)
// debugger // debugger
if (continuous) { if (continuous) {
@ -161,7 +161,7 @@ export default {
// // console.log('scroll up') // // console.log('scroll up')
// views.value = createList(currentStartRenderIndex, currentEndRenderIndex) // views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
// } else return // } else return
if (currentScrollTop == scrollTop) return if (currentScrollTop == scrollTop && endIndex >= currentEndIndex) return
views.value = createList(currentStartRenderIndex, currentEndRenderIndex) views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
} else { } else {
views.value = createList(currentStartRenderIndex, currentEndRenderIndex) views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
@ -215,19 +215,28 @@ export default {
return isScrolling ? scrollToValue : dom_scrollContainer.value.scrollTop return isScrolling ? scrollToValue : dom_scrollContainer.value.scrollTop
} }
const handleResize = () => {
setTimeout(updateView)
}
const contentStyle = computed(() => ({ const contentStyle = computed(() => ({
display: 'block', display: 'block',
height: props.list.length * props.itemHeight + 'px', height: props.list.length * props.itemHeight + 'px',
})) }))
watch(() => props.itemHeight, updateView) const handleReset = list => {
watch(() => props.list, (list) => {
cachedList = Array(list.length) cachedList = Array(list.length)
startIndex = -1 startIndex = -1
endIndex = -1 endIndex = -1
nextTick(() => { nextTick(() => {
updateView() updateView()
}) })
}
watch(() => props.itemHeight, () => {
handleReset(props.list)
})
watch(() => props.list, (list) => {
handleReset(list)
}, { }, {
deep: true, deep: true,
}) })
@ -238,9 +247,11 @@ export default {
startIndex = -1 startIndex = -1
endIndex = -1 endIndex = -1
updateView() updateView()
window.addEventListener('resize', handleResize)
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
dom_scrollContainer.value.removeEventListener('scroll', onScroll) dom_scrollContainer.value.removeEventListener('scroll', onScroll)
window.removeEventListener('resize', handleResize)
if (cancelScroll) cancelScroll() if (cancelScroll) cancelScroll()
}) })

View File

@ -26,16 +26,29 @@ const themes = {
naruto: 'rgba(87,144,167,.14)', naruto: 'rgba(87,144,167,.14)',
happy_new_year: 'rgba(192,57,43,.1)', happy_new_year: 'rgba(192,57,43,.1)',
} }
const getBarWidth = canvasWidth => {
let barWidth = (canvasWidth / 128) * 2.5
const width = canvasWidth / 86
const diffWidth = barWidth - width
// console.log(barWidth - width)
// if (barWidth - width > 20) newBarWidth = 20
// barWidth = newBarWidth
return diffWidth > 32
? canvasWidth / 128 // 4k
: diffWidth > 12 ? width : barWidth
}
export default { export default {
setup() { setup() {
const dom_canvas = ref(null) const dom_canvas = ref(null)
const analyser = getAnalyser() const analyser = getAnalyser()
let ctx let ctx
let bufferLength let bufferLength = 0
let dataArray let dataArray
let WIDTH let WIDTH
let HEIGHT let HEIGHT
let MAX_HEIGHT
let barWidth let barWidth
let barHeight let barHeight
let x = 0 let x = 0
@ -89,7 +102,7 @@ export default {
// let b = 50 // let b = 50
// ctx.fillStyle = 'rgb(' + r + ',' + g + ',' + b + ')' // ctx.fillStyle = 'rgb(' + r + ',' + g + ',' + b + ')'
barHeight = barHeight * frequencyAvg + barHeight * 0.42 barHeight = (barHeight * frequencyAvg + barHeight * 0.42) * MAX_HEIGHT
ctx.fillRect(x, HEIGHT - barHeight, barWidth, barHeight) ctx.fillRect(x, HEIGHT - barHeight, barWidth, barHeight)
x += barWidth x += barWidth
@ -101,7 +114,7 @@ export default {
analyser.fftSize = 256 analyser.fftSize = 256
bufferLength = analyser.frequencyBinCount bufferLength = analyser.frequencyBinCount
// console.log(bufferLength) // console.log(bufferLength)
barWidth = (WIDTH / bufferLength) * 2.5 barWidth = getBarWidth(WIDTH)
dataArray = new Uint8Array(bufferLength) dataArray = new Uint8Array(bufferLength)
renderFrame() renderFrame()
} }
@ -110,14 +123,27 @@ export default {
isPlaying = false isPlaying = false
} }
const handleResize = () => {
const canvas = dom_canvas.value
canvas.width = canvas.clientWidth
canvas.height = canvas.clientHeight
WIDTH = canvas.width
HEIGHT = canvas.height
MAX_HEIGHT = Math.round(HEIGHT * 0.4 / 255 * 10000) / 10000
// console.log(MAX_HEIGHT)
barWidth = getBarWidth(WIDTH)
}
window.eventHub.on(eventPlayerNames.play, handlePlay) window.eventHub.on(eventPlayerNames.play, handlePlay)
window.eventHub.on(eventPlayerNames.pause, handlePause) window.eventHub.on(eventPlayerNames.pause, handlePause)
window.eventHub.on(eventPlayerNames.error, handlePause) window.eventHub.on(eventPlayerNames.error, handlePause)
window.addEventListener('resize', handleResize)
onBeforeUnmount(() => { onBeforeUnmount(() => {
handlePause() handlePause()
window.eventHub.off(eventPlayerNames.play, handlePlay) window.eventHub.off(eventPlayerNames.play, handlePlay)
window.eventHub.off(eventPlayerNames.pause, handlePause) window.eventHub.off(eventPlayerNames.pause, handlePause)
window.eventHub.off(eventPlayerNames.error, handlePause) window.eventHub.off(eventPlayerNames.error, handlePause)
window.removeEventListener('resize', handleResize)
}) })
onMounted(() => { onMounted(() => {
@ -127,6 +153,8 @@ export default {
canvas.height = canvas.clientHeight canvas.height = canvas.clientHeight
WIDTH = canvas.width WIDTH = canvas.width
HEIGHT = canvas.height HEIGHT = canvas.height
MAX_HEIGHT = Math.round(HEIGHT * 0.4 / 255 * 10000) / 10000
// console.log(MAX_HEIGHT)
if (isPlay.value) handlePlay() if (isPlay.value) handlePlay()
}) })

View File

@ -79,7 +79,7 @@ export default {
.main { .main {
padding: 15px; padding: 15px;
max-width: 300px; max-width: 400px;
min-width: 200px; min-width: 200px;
display: flex; display: flex;
flex-flow: column nowrap; flex-flow: column nowrap;

View File

@ -51,7 +51,7 @@ export default {
.main { .main {
padding: 15px; padding: 15px;
max-width: 300px; max-width: 400px;
min-width: 200px; min-width: 200px;
display: flex; display: flex;
flex-flow: column nowrap; flex-flow: column nowrap;

View File

@ -1,10 +1,10 @@
<template> <template>
<material-modal :show="show" :bg-close="bgClose" @close="handleClose" :teleport="teleport"> <material-modal :show="show" :bg-close="bgClose" @close="handleClose" :teleport="teleport" max-width="70%">
<main :class="$style.main"> <main :class="$style.main">
<h2>{{$t('list_add__' + (isMove ? 'title_first_move' : 'title_first_add'))}}&nbsp;<span :class="$style.name">{{this.musicInfo && `${musicInfo.name}`}}</span>&nbsp;{{$t('list_add__title_last')}}</h2> <h2>{{$t('list_add__' + (isMove ? 'title_first_move' : 'title_first_add'))}}&nbsp;<span :class="$style.name">{{this.musicInfo && `${musicInfo.name}`}}</span>&nbsp;{{$t('list_add__title_last')}}</h2>
<div class="scroll" :class="$style.btnContent"> <div class="scroll" :class="$style.btnContent">
<base-btn :class="$style.btn" :tips="$t('list_add__btn_title', { name: item.name })" :key="item.id" :disabled="item.isExist" @click="handleClick(index)" v-for="(item, index) in lists">{{item.name}}</base-btn> <base-btn :class="$style.btn" :aria-label="$t('list_add__btn_title', { name: item.name })" :key="item.id" :disabled="item.isExist" @click="handleClick(index)" v-for="(item, index) in lists">{{item.name}}</base-btn>
<base-btn :class="[$style.btn, $style.newList, isEditing ? $style.editing : null]" @click="handleEditing($event)" :tips="$t('lists__new_list_btn')"> <base-btn :class="[$style.btn, $style.newList, isEditing ? $style.editing : null]" @click="handleEditing($event)" :aria-label="$t('lists__new_list_btn')">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" viewBox="0 0 42 42" space="preserve"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" viewBox="0 0 42 42" space="preserve">
<use xlink:href="#icon-addTo"></use> <use xlink:href="#icon-addTo"></use>
</svg> </svg>
@ -74,15 +74,31 @@ export default {
return { return {
isEditing: false, isEditing: false,
newListName: '', newListName: '',
rowNum: 3,
} }
}, },
computed: { computed: {
spaceNum() { spaceNum() {
return this.lists.length < 2 ? 0 : (3 - this.lists.length % 3 - 1) return this.lists.length < 2 ? 0 : (this.rowNum - this.lists.length % this.rowNum - 1)
}, },
}, },
mounted() {
window.addEventListener('resize', this.handleResize)
this.handleResize()
},
beforeUnmount() {
window.removeEventListener('resize', this.handleResize)
},
methods: { methods: {
...mapMutations('list', ['listAdd', 'listMove', 'createUserList']), ...mapMutations('list', ['listAdd', 'listMove', 'createUserList']),
handleResize() {
const width = window.innerWidth
this.rowNum = width < 1920
? 3
: width < 2560
? 4
: width < 3840 ? 5 : 6
},
handleClick(index) { handleClick(index) {
this.isMove this.isMove
? this.listMove({ fromId: this.fromListId, toId: this.lists[index].id, musicInfo: this.musicInfo }) ? this.listMove({ fromId: this.fromListId, toId: this.lists[index].id, musicInfo: this.musicInfo })
@ -117,7 +133,7 @@ export default {
.main { .main {
// padding: 15px 0; // padding: 15px 0;
max-width: 620px; // max-width: 70%;
min-width: 200px; min-width: 200px;
display: flex; display: flex;
flex-flow: column nowrap; flex-flow: column nowrap;
@ -147,14 +163,17 @@ export default {
justify-content: space-evenly; justify-content: space-evenly;
} }
@item-width: (100% / 3);
.btn { .btn {
position: relative;
box-sizing: border-box; box-sizing: border-box;
margin-left: 15px; margin-left: 15px;
margin-bottom: 15px; margin-bottom: 15px;
height: 36px; height: 36px;
line-height: 36px; line-height: 36px;
padding: 0 10px !important; padding: 0 10px !important;
width: 180px; width: calc(@item-width - 15px);
min-width: 160px;
.mixin-ellipsis-1; .mixin-ellipsis-1;
} }
@ -181,6 +200,9 @@ export default {
} }
} }
.newListInput { .newListInput {
position: absolute;
left: 0;
top: 0;
width: 100%; width: 100%;
height: 34px; height: 34px;
border: none; border: none;
@ -191,9 +213,30 @@ export default {
font-size: 14px; font-size: 14px;
text-align: center; text-align: center;
font-family: inherit; font-family: inherit;
box-sizing: border-box;
padding: 0 10px;
display: none; display: none;
} }
@item-width2: (100% / 4);
@media (min-width: 1920px){
.btn {
width: calc(@item-width2 - 15px);
}
}
@item-width3: (100% / 5);
@media (min-width: 2560px){
.btn {
width: calc(@item-width3 - 15px);
}
}
@item-width4: (100% / 6);
@media (min-width: 3840px){
.btn {
width: calc(@item-width4 - 15px);
}
}
each(@themes, { each(@themes, {
:global(#root.@{value}) { :global(#root.@{value}) {
.main { .main {

View File

@ -1,10 +1,10 @@
<template> <template>
<material-modal :show="show" :bg-close="bgClose" @close="handleClose"> <material-modal :show="show" :bg-close="bgClose" @close="handleClose" max-width="70%">
<main :class="$style.main"> <main :class="$style.main">
<h2>{{$t('list_add__multiple_' + (isMove ? 'title_move' : 'title_add'), { num: musicList.length })}}</h2> <h2>{{$t('list_add__multiple_' + (isMove ? 'title_move' : 'title_add'), { num: musicList.length })}}</h2>
<div class="scroll" :class="$style.btnContent"> <div class="scroll" :class="$style.btnContent">
<base-btn :class="$style.btn" :tips="$t('list_add__multiple_btn_title', { name: item.name })" :key="item.id" @click="handleClick(index)" v-for="(item, index) in lists">{{item.name}}</base-btn> <base-btn :class="$style.btn" :aria-label="$t('list_add__multiple_btn_title', { name: item.name })" :key="item.id" @click="handleClick(index)" v-for="(item, index) in lists">{{item.name}}</base-btn>
<base-btn :class="[$style.btn, $style.newList, isEditing ? $style.editing : null]" @click="handleEditing($event)" :tips="$t('lists__new_list_btn')"> <base-btn :class="[$style.btn, $style.newList, isEditing ? $style.editing : null]" @click="handleEditing($event)" :aria-label="$t('lists__new_list_btn')">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" viewBox="0 0 42 42" space="preserve"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" viewBox="0 0 42 42" space="preserve">
<use xlink:href="#icon-addTo"></use> <use xlink:href="#icon-addTo"></use>
</svg> </svg>
@ -74,16 +74,32 @@ export default {
return { return {
isEditing: false, isEditing: false,
newListName: '', newListName: '',
rowNum: 3,
} }
}, },
computed: { computed: {
spaceNum() { spaceNum() {
return this.lists.length < 2 ? 0 : (3 - this.lists.length % 3 - 1) return this.lists.length < 2 ? 0 : (this.rowNum - this.lists.length % this.rowNum - 1)
}, },
}, },
mounted() {
window.addEventListener('resize', this.handleResize)
this.handleResize()
},
beforeUnmount() {
window.removeEventListener('resize', this.handleResize)
},
methods: { methods: {
...mapMutations('list', ['listAddMultiple', 'listMoveMultiple', 'createUserList']), ...mapMutations('list', ['listAddMultiple', 'listMoveMultiple', 'createUserList']),
handleResize() {
const width = window.innerWidth
this.rowNum = width < 1920
? 3
: width < 2560
? 4
: width < 3840 ? 5 : 6
},
handleClick(index) { handleClick(index) {
this.isMove this.isMove
? this.listMoveMultiple({ fromId: this.fromListId, toId: this.lists[index].id, list: this.musicList }) ? this.listMoveMultiple({ fromId: this.fromListId, toId: this.lists[index].id, list: this.musicList })
@ -119,7 +135,7 @@ export default {
.main { .main {
// padding: 15px 0; // padding: 15px 0;
max-width: 620px; // max-width: 620px;
min-width: 200px; min-width: 200px;
display: flex; display: flex;
flex-flow: column nowrap; flex-flow: column nowrap;
@ -145,14 +161,17 @@ export default {
justify-content: space-evenly; justify-content: space-evenly;
} }
@item-width: (100% / 3);
.btn { .btn {
position: relative;
box-sizing: border-box; box-sizing: border-box;
margin-left: 15px; margin-left: 15px;
margin-bottom: 15px; margin-bottom: 15px;
height: 36px; height: 36px;
line-height: 36px; line-height: 36px;
padding: 0 10px !important; padding: 0 10px !important;
width: 180px; width: calc(@item-width - 15px);
min-width: 160px;
.mixin-ellipsis-1; .mixin-ellipsis-1;
} }
@ -179,6 +198,9 @@ export default {
} }
} }
.newListInput { .newListInput {
position: absolute;
left: 0;
top: 0;
width: 100%; width: 100%;
height: 34px; height: 34px;
border: none; border: none;
@ -189,9 +211,31 @@ export default {
font-size: 14px; font-size: 14px;
text-align: center; text-align: center;
font-family: inherit; font-family: inherit;
box-sizing: border-box;
padding: 0 10px;
display: none; display: none;
} }
@item-width2: (100% / 4);
@media (min-width: 1920px){
.btn {
width: calc(@item-width2 - 15px);
}
}
@item-width3: (100% / 5);
@media (min-width: 2560px){
.btn {
width: calc(@item-width3 - 15px);
}
}
@item-width4: (100% / 6);
@media (min-width: 3840px){
.btn {
width: calc(@item-width4 - 15px);
}
}
each(@themes, { each(@themes, {
:global(#root.@{value}) { :global(#root.@{value}) {
.main { .main {

View File

@ -3,7 +3,7 @@
<div :class="[$style.volume, {[$style.muted]: setting.player.isMute} ]"> <div :class="[$style.volume, {[$style.muted]: setting.player.isMute} ]">
<div :class="$style.volumeBar" ref="dom_volumeBar" :style="{ transform: `scaleX(${volume || 0})` }"></div> <div :class="$style.volumeBar" ref="dom_volumeBar" :style="{ transform: `scaleX(${volume || 0})` }"></div>
</div> </div>
<div :class="$style.volumeMask" @mousedown="handleVolumeMsDown" :tips="`${$t('player__volume')}${parseInt(volume * 100)}%`"></div> <div :class="$style.volumeMask" @mousedown="handleVolumeMsDown" :aria-label="`${$t('player__volume')}${parseInt(volume * 100)}%`"></div>
</div> </div>
</template> </template>

View File

@ -1,11 +1,11 @@
<template> <template>
<div :class="$style.controlBtn"> <div :class="$style.controlBtn" v-show="!isFullscreen">
<button type="button" :class="[$style.btn, $style.close]" :tips="$t('close')" @click="close"> <button type="button" :class="[$style.btn, $style.close]" :aria-label="$t('close')" @click="close">
<svg :class="$style.controlBtniIcon" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" width="100%" viewBox="0 0 24 24" space="preserve"> <svg :class="$style.controlBtniIcon" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" width="100%" viewBox="0 0 24 24" space="preserve">
<use xlink:href="#icon-window-close"></use> <use xlink:href="#icon-window-close"></use>
</svg> </svg>
</button> </button>
<button type="button" :class="[$style.btn, $style.min]" :tips="$t('min')" @click="min"> <button type="button" :class="[$style.btn, $style.min]" :aria-label="$t('min')" @click="min">
<svg :class="$style.controlBtniIcon" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" width="100%" viewBox="0 0 24 24" space="preserve"> <svg :class="$style.controlBtniIcon" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" width="100%" viewBox="0 0 24 24" space="preserve">
<use xlink:href="#icon-window-minimize"></use> <use xlink:href="#icon-window-minimize"></use>
</svg> </svg>
@ -16,10 +16,12 @@
<script> <script>
import { base as eventBaseName } from '@renderer/event/names' import { base as eventBaseName } from '@renderer/event/names'
// import { getRandom } from '../../utils' // import { getRandom } from '../../utils'
import { isFullscreen } from '@renderer/core/share'
export default { export default {
setup() { setup() {
return { return {
isFullscreen,
min() { min() {
window.eventHub.emit(eventBaseName.min) window.eventHub.emit(eventBaseName.min)
}, },

View File

@ -1,8 +1,8 @@
<template> <template>
<div :class="$style.menu"> <div :class="$style.menu">
<ul :class="$style.list"> <ul :class="$style.list" role="toolbar">
<li v-for="item in menus" :key="item.to"> <li v-for="item in menus" :key="item.to" :class="$style.navItem" role="presentation">
<router-link :class="$style.link" :active-class="$style.active" :to="item.to" :tips="item.tips"> <router-link :class="$style.link" :active-class="$style.active" role="tab" :aria-selected="$route.name == item.to" :to="item.to" :aria-label="item.tips">
<div :class="$style.icon"> <div :class="$style.icon">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" :viewBox="item.iconSize" space="preserve"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" :viewBox="item.iconSize" space="preserve">
<use :xlink:href="item.icon"></use> <use :xlink:href="item.icon"></use>
@ -98,13 +98,26 @@ export default {
// .mixin-ellipsis-1; // .mixin-ellipsis-1;
// } // }
} }
.navItem {
position: relative;
&:before {
content: '';
display: block;
width: 100%;
padding-bottom: 84%;
}
}
.link { .link {
display: block; position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
// display: block;
box-sizing: border-box; box-sizing: border-box;
text-decoration: none; text-decoration: none;
position: relative; // padding: 18px 3px;
padding: 18px 3px;
// margin: 5px 0; // margin: 5px 0;
// border-left: 5px solid transparent; // border-left: 5px solid transparent;
transition: @transition-theme; transition: @transition-theme;
@ -114,6 +127,9 @@ export default {
font-size: 11.5px; font-size: 11.5px;
text-align: center; text-align: center;
outline: none; outline: none;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.3s ease; transition: background-color 0.3s ease;
// border-radius: @radius-border; // border-radius: @radius-border;

View File

@ -1,5 +1,5 @@
<template> <template>
<div :class="$style.aside"> <div :class="[$style.aside, { [$style.fullscreen]: isFullscreen }]">
<ControlBtns v-if="setting.controlBtnPosition == 'left'" /> <ControlBtns v-if="setting.controlBtnPosition == 'left'" />
<div :class="$style.logo" v-else>L X</div> <div :class="$style.logo" v-else>L X</div>
<NavBar /> <NavBar />
@ -8,6 +8,7 @@
<script> <script>
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import { isFullscreen } from '@renderer/core/share'
import ControlBtns from './ControlBtns' import ControlBtns from './ControlBtns'
import NavBar from './NavBar' import NavBar from './NavBar'
@ -15,6 +16,11 @@ import NavBar from './NavBar'
export default { export default {
name: 'CoreAside', name: 'CoreAside',
components: { ControlBtns, NavBar }, components: { ControlBtns, NavBar },
setup() {
return {
isFullscreen,
}
},
computed: { computed: {
...mapGetters(['setting']), ...mapGetters(['setting']),
}, },
@ -36,6 +42,13 @@ export default {
-webkit-user-select: none; -webkit-user-select: none;
display: flex; display: flex;
flex-flow: column nowrap; flex-flow: column nowrap;
&.fullscreen {
-webkit-app-region: no-drag;
.logo {
display: none;
}
}
} }
.logo { .logo {

View File

@ -1,5 +1,5 @@
<template lang="pug"> <template lang="pug">
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' style="display: none;") svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' style="display: none;" aria-hidden="true")
defs defs
g#icon-search(fill='currentColor') g#icon-search(fill='currentColor')
// 30.239 30.239 // 30.239 30.239

View File

@ -1,7 +1,7 @@
<template> <template>
<div :class="$style.controlBtn"> <div :class="$style.controlBtn">
<common-volume-bar :setting="setting" /> <common-volume-bar :setting="setting" />
<div :class="$style.titleBtn" @click="toggleDesktopLyric" @contextmenu="toggleLockDesktopLyric" :tips="toggleDesktopLyricBtnTitle"> <div :class="$style.titleBtn" @click="toggleDesktopLyric" @contextmenu="toggleLockDesktopLyric" :aria-label="toggleDesktopLyricBtnTitle">
<svg v-show="setting.desktopLyric.enable" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 512 512" space="preserve"> <svg v-show="setting.desktopLyric.enable" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 512 512" space="preserve">
<use xlink:href="#icon-desktop-lyric-on"></use> <use xlink:href="#icon-desktop-lyric-on"></use>
</svg> </svg>
@ -9,7 +9,7 @@
<use xlink:href="#icon-desktop-lyric-off"></use> <use xlink:href="#icon-desktop-lyric-off"></use>
</svg> </svg>
</div> </div>
<div :class="$style.titleBtn" @click="toggleNextPlayMode" :tips="nextTogglePlayName"> <div :class="$style.titleBtn" @click="toggleNextPlayMode" :aria-label="nextTogglePlayName">
<svg v-show="setting.player.togglePlayMethod == 'listLoop'" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="80%" viewBox="0 0 24 24" space="preserve"> <svg v-show="setting.player.togglePlayMethod == 'listLoop'" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="80%" viewBox="0 0 24 24" space="preserve">
<use xlink:href="#icon-list-loop"></use> <use xlink:href="#icon-list-loop"></use>
</svg> </svg>
@ -26,7 +26,7 @@
<use xlink:href="#icon-single"></use> <use xlink:href="#icon-single"></use>
</svg> </svg>
</div> </div>
<div :class="$style.titleBtn" @click="addMusicTo" :tips="$t('player__add_music_to')"> <div :class="$style.titleBtn" @click="addMusicTo" :aria-label="$t('player__add_music_to')">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="80%" viewBox="0 0 512 512" space="preserve"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="80%" viewBox="0 0 512 512" space="preserve">
<use xlink:href="#icon-add-2"></use> <use xlink:href="#icon-add-2"></use>
</svg> </svg>

View File

@ -1,13 +1,13 @@
<template lang="pug"> <template lang="pug">
div(:class="$style.player") div(:class="$style.player")
div(:class="$style.left" @contextmenu="handleToMusicLocation" @click="showPlayerDetail" :tips="$t('player__pic_tip')") div(:class="$style.left" @contextmenu="handleToMusicLocation" @click="showPlayerDetail" :aria-label="$t('player__pic_tip')")
img(v-if="musicInfo.img" :src="musicInfo.img" @error="imgError") img(v-if="musicInfo.img" :src="musicInfo.img" @error="imgError")
svg(v-else version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='102%' width='100%' viewBox='0 0 60 60' space='preserve') svg(v-else version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='102%' width='100%' viewBox='0 0 60 60' space='preserve')
use(:xlink:href='`#${$style.iconPic}`') use(:xlink:href='`#${$style.iconPic}`')
div(:class="$style.middle") div(:class="$style.middle")
div(:class="$style.column1") div(:class="$style.column1")
div(:class="$style.container") div(:class="$style.container")
div(:class="$style.title" @click="handleCopy(title)" :tips="title + $t('copy_tip')") {{title}} div(:class="$style.title" @click="handleCopy(title)" :aria-label="title + $t('copy_tip')") {{title}}
control-btns control-btns
div(:class="$style.column2") div(:class="$style.column2")
common-progress-bar(:progress="progress" :handleTransitionEnd="handleTransitionEnd" :isActiveTransition="isActiveTransition" v-if="!isShowPlayerDetail") common-progress-bar(:progress="progress" :handleTransitionEnd="handleTransitionEnd" :isActiveTransition="isActiveTransition" v-if="!isShowPlayerDetail")
@ -18,15 +18,15 @@ div(:class="$style.player")
span(style="margin: 0 5px;") / span(style="margin: 0 5px;") /
span {{maxPlayTimeStr}} span {{maxPlayTimeStr}}
div(:class="$style.right") div(:class="$style.right")
div(:class="$style.playBtn" @click='playPrev' :tips="$t('player__prev')" style="transform: rotate(180deg);") div(:class="$style.playBtn" @click='playPrev' :aria-label="$t('player__prev')" style="transform: rotate(180deg);")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve') svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve')
use(xlink:href='#icon-nextMusic') use(xlink:href='#icon-nextMusic')
div(:class="$style.playBtn" :tips="isPlay ? $t('player__pause') : $t('player__play')" @click='togglePlay') div(:class="$style.playBtn" :aria-label="isPlay ? $t('player__pause') : $t('player__play')" @click='togglePlay')
svg(v-if="isPlay" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 277.338 277.338' space='preserve') svg(v-if="isPlay" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 277.338 277.338' space='preserve')
use(xlink:href='#icon-pause') use(xlink:href='#icon-pause')
svg(v-else version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 170 170' space='preserve') svg(v-else version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 170 170' space='preserve')
use(xlink:href='#icon-play') use(xlink:href='#icon-play')
div(:class="$style.playBtn" @click='playNext' :tips="$t('player__next')") div(:class="$style.playBtn" @click='playNext' :aria-label="$t('player__next')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve') svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve')
use(xlink:href='#icon-nextMusic') use(xlink:href='#icon-nextMusic')
@ -276,6 +276,7 @@ export default {
.column2 { .column2 {
flex: none; flex: none;
padding: 3px 0; padding: 3px 0;
height: 10px;
position: relative; position: relative;
} }

View File

@ -1,10 +1,21 @@
<template> <template>
<div :class="['right', $style.right]"> <div :class="['right', $style.right]" :style="lrcFontSize">
<div :class="['lyric', $style.lyric, { [$style.draging]: isMsDown }]" @wheel="handleWheel" @mousedown="handleLyricMouseDown" ref="dom_lyric"> <div :class="['lyric', $style.lyric, { [$style.draging]: isMsDown }, { [$style.lrcActiveZoom]: isZoomActiveLrc }]" :style="lrcStyles" @wheel="handleWheel" @mousedown="handleLyricMouseDown" ref="dom_lyric">
<div :class="$style.lyricSpace"></div> <div :class="['pre', $style.lyricSpace]"></div>
<div ref="dom_lyric_text"></div> <div ref="dom_lyric_text"></div>
<div :class="$style.lyricSpace"></div> <div :class="$style.lyricSpace"></div>
</div> </div>
<transition enter-active-class="animated fadeIn" leave-active-class="animated fadeOut">
<div :class="$style.skip" v-if="isShowLyricProgressSetting" v-show="isStopScroll">
<div :class="$style.line" ref="dom_skip_line"></div>
<span :class="$style.label">{{timeStr}}</span>
<base-btn :class="$style.skipBtn" @mouseenter="handleSkipMouseEnter" @mouseleave="handleSkipMouseLeave" @click="handleSkipPlay">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="50%" viewBox="0 0 170 170" space="preserve">
<use xlink:href="#icon-play"></use>
</svg>
</base-btn>
</div>
</transition>
<transition enter-active-class="animated fadeIn" leave-active-class="animated fadeOut"> <transition enter-active-class="animated fadeIn" leave-active-class="animated fadeOut">
<div :class="[$style.lyricSelectContent, 'select', 'scroll', 'lyricSelectContent']" v-if="isShowLrcSelectContent" @contextmenu="handleCopySelectText"> <div :class="[$style.lyricSelectContent, 'select', 'scroll', 'lyricSelectContent']" v-if="isShowLrcSelectContent" @contextmenu="handleCopySelectText">
<div v-for="(info, index) in lyric.lines" :key="index" :class="[$style.lyricSelectline, { [$style.lrcActive]: lyric.line == index }]"> <div v-for="(info, index) in lyric.lines" :key="index" :class="[$style.lyricSelectline, { [$style.lrcActive]: lyric.line == index }]">
@ -20,27 +31,84 @@
<script> <script>
import { clipboardWriteText } from '@renderer/utils' import { clipboardWriteText } from '@renderer/utils'
import { lyric } from '@renderer/core/share/lyric' import { lyric } from '@renderer/core/share/lyric'
import { isPlay, isShowLrcSelectContent } from '@renderer/core/share/player' import { isFullscreen } from '@renderer/core/share'
// import { ref } from '@renderer/utils/vueTools' import { isPlay, isShowLrcSelectContent, isShowPlayComment } from '@renderer/core/share/player'
import { onMounted, onBeforeUnmount, useCommit, useRefGetter, computed } from '@renderer/utils/vueTools'
import useLyric from '@renderer/utils/compositions/useLyric' import useLyric from '@renderer/utils/compositions/useLyric'
export default { export default {
setup() { setup() {
const setting = useRefGetter('setting')
const setPlayDetailLyricFont = useCommit('setPlayDetailLyricFont')
const { const {
dom_lyric, dom_lyric,
dom_lyric_text, dom_lyric_text,
dom_skip_line,
isMsDown, isMsDown,
isStopScroll,
timeStr,
handleLyricMouseDown, handleLyricMouseDown,
handleWheel, handleWheel,
handleSkipPlay,
handleSkipMouseEnter,
handleSkipMouseLeave,
} = useLyric({ isPlay, lyric }) } = useLyric({ isPlay, lyric })
const fontSizeUp = () => {
if (setting.value.playDetail.style.fontSize >= 200) return
setPlayDetailLyricFont(setting.value.playDetail.style.fontSize + 1)
}
const fontSizeDown = () => {
if (setting.value.playDetail.style.fontSize <= 70) return
setPlayDetailLyricFont(setting.value.playDetail.style.fontSize - 1)
}
const lrcStyles = computed(() => {
return {
textAlign: setting.value.playDetail.style.align,
}
})
const lrcFontSize = computed(() => {
let size = setting.value.playDetail.style.fontSize / 100
if (isFullscreen.value) size = size *= 1.4
return {
'--playDetail-lrc-font-size': (isShowPlayComment.value ? size * 0.82 : size) + 'rem',
}
})
const isZoomActiveLrc = computed(() => setting.value.playDetail.isZoomActiveLrc)
const isShowLyricProgressSetting = computed(() => setting.value.playDetail.isShowLyricProgressSetting)
onMounted(() => {
window.eventHub.on('key_shift++_down', fontSizeUp)
window.eventHub.on('key_numadd_down', fontSizeUp)
window.eventHub.on('key_-_down', fontSizeDown)
window.eventHub.on('key_numsub_down', fontSizeDown)
})
onBeforeUnmount(() => {
window.eventHub.off('key_shift++_down', fontSizeUp)
window.eventHub.off('key_numadd_down', fontSizeUp)
window.eventHub.off('key_-_down', fontSizeDown)
window.eventHub.off('key_numsub_down', fontSizeDown)
})
return { return {
dom_lyric, dom_lyric,
dom_lyric_text, dom_lyric_text,
dom_skip_line,
isMsDown, isMsDown,
timeStr,
handleLyricMouseDown, handleLyricMouseDown,
handleWheel, handleWheel,
handleSkipPlay,
handleSkipMouseEnter,
handleSkipMouseLeave,
lyric, lyric,
lrcStyles,
lrcFontSize,
isShowLrcSelectContent, isShowLrcSelectContent,
isShowLyricProgressSetting,
isZoomActiveLrc,
isStopScroll,
} }
}, },
methods: { methods: {
@ -90,7 +158,7 @@ export default {
text-align: center; text-align: center;
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
font-size: 16px; font-size: var(--playDetail-lrc-font-size, 16px);
cursor: grab; cursor: grab;
&.draging { &.draging {
cursor: grabbing; cursor: grabbing;
@ -98,9 +166,11 @@ export default {
:global { :global {
.lrc-content { .lrc-content {
line-height: 1.2; line-height: 1.2;
margin: 16px 0; padding: calc(var(--playDetail-lrc-font-size, 16px) / 2) 0;
overflow-wrap: break-word; overflow-wrap: break-word;
color: @color-player-detail-lyric; color: @color-player-detail-lyric;
transition: @transition-theme;
transition-property: padding;
.translation { .translation {
transition: @transition-theme !important; transition: @transition-theme !important;
@ -119,13 +189,12 @@ export default {
color: @color-theme; color: @color-theme;
} }
.translation { .translation {
font-size: .94em;
color: @color-theme; color: @color-theme;
} }
span { // span {
// color: @color-theme; // // color: @color-theme;
font-size: 1.1em; // font-size: 1.1em;
} // }
} }
span { span {
@ -153,6 +222,64 @@ export default {
// font-size: 1.2em; // font-size: 1.2em;
// } // }
} }
.lrcActiveZoom {
:global {
.lrc-content {
&.active {
.translation {
font-size: .94em;
}
span {
font-size: 1.1em;
}
}
}
}
}
.skip {
position: absolute;
top: calc(38% + var(--playDetail-lrc-font-size, 16px) + 4px);
left: 0;
// height: 6px;
width: 100%;
pointer-events: none;
// opacity: .5;
.line {
border-top: 1px dashed @color-player-detail-lyric-active;
opacity: .15;
margin-right: 30px;
}
.label {
position: absolute;
right: 30px;
top: -14px;
line-height: 1;
font-size: 12px;
color: @color-player-detail-lyric-active;
opacity: .7;
}
.skipBtn {
position: absolute;
right: 0;
top: 0;
transform: translateY(-50%);
width: 30px;
height: 30px;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
background: none !important;
pointer-events: initial;
transition: @transition-theme;
transition-property: opacity;
opacity: .8;
&:hover {
opacity: .6;
}
}
}
.lyricSelectContent { .lyricSelectContent {
position: absolute; position: absolute;
left: 0; left: 0;
@ -217,6 +344,14 @@ each(@themes, {
// .lrc-active { // .lrc-active {
// color: ~'@{color-@{value}-theme}'; // color: ~'@{color-@{value}-theme}';
// } // }
.skip {
.line {
border-top-color: ~'@{color-@{value}-player-detail-lyric-active}';
}
.label {
color:~'@{color-@{value}-player-detail-lyric-active}';
}
}
.lyricSelectContent { .lyricSelectContent {
background-color: ~'@{color-@{value}-theme_2-background_1}'; background-color: ~'@{color-@{value}-theme_2-background_1}';
color: ~'@{color-@{value}-player-detail-lyric}'; color: ~'@{color-@{value}-player-detail-lyric}';

View File

@ -11,15 +11,15 @@ div(:class="$style.footer")
span(style="margin: 0 5px;") / span(style="margin: 0 5px;") /
span {{maxPlayTimeStr}} span {{maxPlayTimeStr}}
div(:class="$style.playControl") div(:class="$style.playControl")
div(:class="$style.playBtn" @click="playPrev" style="transform: rotate(180deg);" :tips="$t('player__prev')") div(:class="$style.playBtn" @click="playPrev" style="transform: rotate(180deg);" :aria-label="$t('player__prev')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve') svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve')
use(xlink:href='#icon-nextMusic') use(xlink:href='#icon-nextMusic')
div(:class="$style.playBtn" @click="togglePlay" :tips="isPlay ? $t('player__pause') : $t('player__play')") div(:class="$style.playBtn" @click="togglePlay" :aria-label="isPlay ? $t('player__pause') : $t('player__play')")
svg(v-if="isPlay" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 277.338 277.338' space='preserve') svg(v-if="isPlay" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 277.338 277.338' space='preserve')
use(xlink:href='#icon-pause') use(xlink:href='#icon-pause')
svg(v-else version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 170 170' space='preserve') svg(v-else version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 170 170' space='preserve')
use(xlink:href='#icon-play') use(xlink:href='#icon-play')
div(:class="$style.playBtn" @click="playNext" :tips="$t('player__next')") div(:class="$style.playBtn" @click="playNext" :aria-label="$t('player__next')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve') svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve')
use(xlink:href='#icon-nextMusic') use(xlink:href='#icon-nextMusic')
</template> </template>

View File

@ -1,21 +1,21 @@
<template lang="pug"> <template lang="pug">
div(:class="$style.footerLeftControlBtns") div(:class="$style.footerLeftControlBtns")
common-volume-bar(:setting="setting") common-volume-bar(:setting="setting")
div(:class="[$style.footerLeftControlBtn, $style.lrcBtn]" @click="toggleDesktopLyric" @contextmenu="toggleLockDesktopLyric" :tips="toggleDesktopLyricBtnTitle") div(:class="[$style.footerLeftControlBtn, $style.lrcBtn]" @click="toggleDesktopLyric" @contextmenu="toggleLockDesktopLyric" :aria-label="toggleDesktopLyricBtnTitle")
svg(v-show="setting.desktopLyric.enable" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='125%' viewBox='0 0 512 512' space='preserve') svg(v-show="setting.desktopLyric.enable" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='125%' viewBox='0 0 512 512' space='preserve')
use(xlink:href='#icon-desktop-lyric-on') use(xlink:href='#icon-desktop-lyric-on')
svg(v-show="!setting.desktopLyric.enable" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='125%' viewBox='0 0 512 512' space='preserve') svg(v-show="!setting.desktopLyric.enable" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='125%' viewBox='0 0 512 512' space='preserve')
use(xlink:href='#icon-desktop-lyric-off') use(xlink:href='#icon-desktop-lyric-off')
div(:class="[$style.footerLeftControlBtn, { [$style.active]: setting.player.audioVisualization }]" @click="toggleAudioVisualization" :tips="$t('audio_visualization')") div(:class="[$style.footerLeftControlBtn, { [$style.active]: setting.player.audioVisualization }]" @click="toggleAudioVisualization" :aria-label="$t('audio_visualization')")
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') 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-audio-wave') use(xlink:href='#icon-audio-wave')
div(:class="[$style.footerLeftControlBtn, { [$style.active]: isShowLrcSelectContent }]" @click="toggleVisibleLrc" :tips="$t('lyric__select')") div(:class="[$style.footerLeftControlBtn, { [$style.active]: isShowLrcSelectContent }]" @click="toggleVisibleLrc" :aria-label="$t('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') 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') use(xlink:href='#icon-text')
div(:class="[$style.footerLeftControlBtn, {[$style.active]: isShowPlayComment}]" @click="toggleVisibleComment" :tips="$t('comment__show')") div(:class="[$style.footerLeftControlBtn, {[$style.active]: isShowPlayComment}]" @click="toggleVisibleComment" :aria-label="$t('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') 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') use(xlink:href='#icon-comment')
div(:class="$style.footerLeftControlBtn" @click="toggleNextPlayMode" :tips="nextTogglePlayName") div(:class="$style.footerLeftControlBtn" @click="toggleNextPlayMode" :aria-label="nextTogglePlayName")
svg(v-show="setting.player.togglePlayMethod == 'listLoop'" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 24 24' space='preserve') svg(v-show="setting.player.togglePlayMethod == 'listLoop'" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-list-loop') use(xlink:href='#icon-list-loop')
svg(v-show="setting.player.togglePlayMethod == 'random'" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 24 24' space='preserve') svg(v-show="setting.player.togglePlayMethod == 'random'" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 24 24' space='preserve')
@ -26,7 +26,7 @@ div(:class="$style.footerLeftControlBtns")
use(xlink:href='#icon-single-loop') use(xlink:href='#icon-single-loop')
svg(v-show="!setting.player.togglePlayMethod" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='120%' viewBox='0 0 24 24' space='preserve') svg(v-show="!setting.player.togglePlayMethod" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='120%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-single') use(xlink:href='#icon-single')
div(:class="$style.footerLeftControlBtn" @click="isShowAddMusicTo = true" :tips="$t('player__add_music_to')") div(:class="$style.footerLeftControlBtn" @click="isShowAddMusicTo = true" :aria-label="$t('player__add_music_to')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 512 512' space='preserve') svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 512 512' space='preserve')
use(xlink:href='#icon-add-2') use(xlink:href='#icon-add-2')
common-list-add-modal(v-model:show="isShowAddMusicTo" :musicInfo="musicInfoItem") common-list-add-modal(v-model:show="isShowAddMusicTo" :musicInfo="musicInfoItem")
@ -46,12 +46,15 @@ import {
import useNextTogglePlay from '@renderer/utils/compositions/useNextTogglePlay' import useNextTogglePlay from '@renderer/utils/compositions/useNextTogglePlay'
import useToggleDesktopLyric from '@renderer/utils/compositions/useToggleDesktopLyric' import useToggleDesktopLyric from '@renderer/utils/compositions/useToggleDesktopLyric'
import { dialog } from '@renderer/plugins/Dialog'
import { setMediaDeviceId } from '@renderer/plugins/player'
export default { export default {
setup() { setup() {
const { t } = useI18n() const { t } = useI18n()
const setting = useRefGetter('setting') const setting = useRefGetter('setting')
const setAudioVisualization = useCommit('setAudioVisualization') const setAudioVisualization = useCommit('setAudioVisualization')
const saveMediaDeviceId = useCommit('setMediaDeviceId')
const toggleVisibleLrc = () => { const toggleVisibleLrc = () => {
setShowPlayLrcSelectContentLrc(!isShowLrcSelectContent.value) setShowPlayLrcSelectContentLrc(!isShowLrcSelectContent.value)
@ -72,8 +75,19 @@ export default {
const isShowAddMusicTo = ref(false) const isShowAddMusicTo = ref(false)
const toggleAudioVisualization = () => { const toggleAudioVisualization = async() => {
setAudioVisualization(!setting.value.player.audioVisualization) const newSetting = !setting.value.player.audioVisualization
if (newSetting && setting.value.player.mediaDeviceId != 'default') {
const confirm = await dialog.confirm({
message: t('setting__player_audio_visualization_tip'),
cancelButtonText: t('cancel_button_text'),
confirmButtonText: t('confirm_button_text'),
})
if (!confirm) return
saveMediaDeviceId('default')
await setMediaDeviceId('default').catch(_ => _)
}
setAudioVisualization(newSetting)
} }
return { return {

View File

@ -3,7 +3,7 @@ div.comment(:class="$style.comment" ref="dom_container")
div(:class="$style.commentHeader") div(:class="$style.commentHeader")
h3 {{$t('comment__title', { name: title })}} h3 {{$t('comment__title', { name: title })}}
div(:class="$style.commentHeaderBtns") div(:class="$style.commentHeaderBtns")
div(:class="$style.commentHeaderBtn" @click="handleShowComment" :tips="$t('comment__refresh')") div(:class="$style.commentHeaderBtn" @click="handleShowComment" :aria-label="$t('comment__refresh')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' style='transform: rotate(45deg);' viewBox='0 0 24 24' space='preserve') svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' style='transform: rotate(45deg);' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-refresh') use(xlink:href='#icon-refresh')
div(:class="$style.commentHeaderBtn" @click="$emit('close')") div(:class="$style.commentHeaderBtn" @click="$emit('close')")
@ -132,7 +132,13 @@ export default {
}, },
methods: { methods: {
setWidth() { setWidth() {
this.$refs.dom_container.style.width = this.$refs.dom_container.clientWidth + 'px' setTimeout(() => {
this.$refs.dom_container.style.width = Math.floor(this.$refs.dom_container.parentNode.clientWidth * 0.5) + 'px'
setTimeout(() => {
this.handleToggleTab(this.tabActiveId, true)
})
})
}, },
async getComment(musicInfo, page, limit, retryNum = 0) { async getComment(musicInfo, page, limit, retryNum = 0) {
let resp let resp
@ -219,8 +225,8 @@ export default {
this.newComment.nextPage = page this.newComment.nextPage = page
this.handleGetNewComment(this.currentMusicInfo, page, this.newComment.limit) this.handleGetNewComment(this.currentMusicInfo, page, this.newComment.limit)
}, },
handleToggleTab(id) { handleToggleTab(id, force) {
if (this.tabActiveId == id) return if (!force && this.tabActiveId == id) return
switch (id) { switch (id) {
case 'hot': case 'hot':
this.$refs.dom_tabMain.scrollLeft = 0 this.$refs.dom_tabMain.scrollLeft = 0

View File

@ -1,32 +1,32 @@
<template lang="pug"> <template lang="pug">
transition(enter-active-class="animated lightSpeedIn" leave-active-class="animated slideOutDown" @after-enter="handleAfterEnter" @after-leave="handleAfterLeave") transition(enter-active-class="animated lightSpeedIn" leave-active-class="animated slideOutDown" @after-enter="handleAfterEnter" @after-leave="handleAfterLeave")
div(:class="$style.container" @contextmenu="handleContextMenu" v-if="isShowPlayerDetail") div(:class="[$style.container, , { [$style.fullscreen]: isFullscreen }]" @contextmenu="handleContextMenu" v-if="isShowPlayerDetail")
//- div(:class="$style.bg" :style="bgStyle") //- div(:class="$style.bg" :style="bgStyle")
//- div(:class="$style.bg2") //- div(:class="$style.bg2")
div(:class="[$style.header, $style.controlBtnLeft]" v-if="setting.controlBtnPosition == 'left'") div(:class="[$style.header, $style.controlBtnLeft]" v-if="setting.controlBtnPosition == 'left'")
div(:class="$style.controBtn") div(:class="$style.controBtn")
button(type="button" :class="$style.hide" :tips="$t('player__hide_detail_tip')" @click="hide") button(type="button" :class="$style.hide" :aria-label="$t('player__hide_detail_tip')" @click="hide")
svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='80%' viewBox='0 0 30.727 30.727' space='preserve') svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='80%' viewBox='0 0 30.727 30.727' space='preserve')
use(xlink:href='#icon-window-hide') use(xlink:href='#icon-window-hide')
button(type="button" :class="$style.min" :tips="$t('min')" @click="min") button(type="button" :class="$style.min" :aria-label="$t('min')" @click="min")
svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='100%' viewBox='0 0 24 24' space='preserve') svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='100%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-window-minimize') use(xlink:href='#icon-window-minimize')
//- button(type="button" :class="$style.max" @click="max") //- button(type="button" :class="$style.max" @click="max")
button(type="button" :class="$style.close" :tips="$t('close')" @click="close") button(type="button" :class="$style.close" :aria-label="$t('close')" @click="close")
svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='100%' viewBox='0 0 24 24' space='preserve') svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='100%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-window-close') use(xlink:href='#icon-window-close')
div(:class="[$style.header, $style.controlBtnRight]" v-else) div(:class="[$style.header, $style.controlBtnRight]" v-else)
div(:class="$style.controBtn") div(:class="$style.controBtn")
button(type="button" :class="$style.hide" :tips="$t('player__hide_detail_tip')" @click="hide") button(type="button" :class="$style.hide" :aria-label="$t('player__hide_detail_tip')" @click="hide")
svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='35%' viewBox='0 0 30.727 30.727' space='preserve') svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='35%' viewBox='0 0 30.727 30.727' space='preserve')
use(xlink:href='#icon-window-hide') use(xlink:href='#icon-window-hide')
button(type="button" :class="$style.min" :tips="$t('min')" @click="min") button(type="button" :class="$style.min" :aria-label="$t('min')" @click="min")
svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='60%' viewBox='0 0 24 24' space='preserve') svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='60%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-window-minimize-2') use(xlink:href='#icon-window-minimize-2')
//- button(type="button" :class="$style.max" @click="max") //- button(type="button" :class="$style.max" @click="max")
button(type="button" :class="$style.close" :tips="$t('close')" @click="close") button(type="button" :class="$style.close" :aria-label="$t('close')" @click="close")
svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='60%' viewBox='0 0 24 24' space='preserve') svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='60%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-window-close-2') use(xlink:href='#icon-window-close-2')
@ -52,6 +52,7 @@ transition(enter-active-class="animated lightSpeedIn" leave-active-class="animat
<script> <script>
import { useRefGetter, ref } from '@renderer/utils/vueTools' import { useRefGetter, ref } from '@renderer/utils/vueTools'
import { isFullscreen } from '@renderer/core/share'
import { base as eventBaseName } from '@renderer/event/names' import { base as eventBaseName } from '@renderer/event/names'
import { import {
isShowPlayerDetail, isShowPlayerDetail,
@ -117,6 +118,7 @@ export default {
handleAfterEnter, handleAfterEnter,
handleAfterLeave, handleAfterLeave,
visibled, visibled,
isFullscreen,
min() { min() {
window.eventHub.emit(eventBaseName.min) window.eventHub.emit(eventBaseName.min)
}, },
@ -161,6 +163,15 @@ export default {
box-sizing: border-box; box-sizing: border-box;
} }
&.fullscreen {
.header {
-webkit-app-region: no-drag;
> * {
display: none;
}
}
}
} }
// .bg { // .bg {
// position: absolute; // position: absolute;
@ -296,9 +307,6 @@ export default {
} }
.right { .right {
flex-basis: 30%; flex-basis: 30%;
.lyric {
font-size: 13px;
}
.lyricSelectContent { .lyricSelectContent {
font-size: 14px; font-size: 14px;
} }

View File

@ -1,11 +1,11 @@
<template> <template>
<div :class="$style.control"> <div :class="$style.control" v-show="!isFullscreen">
<button type="button" :class="[$style.btn, $style.min]" :tips="$t('min')" @click="min"> <button type="button" :class="[$style.btn, $style.min]" :aria-label="$t('min')" @click="min">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="60%" viewBox="0 0 24 24" space="preserve"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="60%" viewBox="0 0 24 24" space="preserve">
<use xlink:href="#icon-window-minimize-2"></use> <use xlink:href="#icon-window-minimize-2"></use>
</svg> </svg>
</button> </button>
<button type="button" :class="[$style.btn, $style.close]" :tips="$t('close')" @click="close"> <button type="button" :class="[$style.btn, $style.close]" :aria-label="$t('close')" @click="close">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="60%" viewBox="0 0 24 24" space="preserve"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="60%" viewBox="0 0 24 24" space="preserve">
<use xlink:href="#icon-window-close-2"></use> <use xlink:href="#icon-window-close-2"></use>
</svg> </svg>
@ -15,6 +15,7 @@
<script> <script>
import { base as eventBaseName } from '@renderer/event/names' import { base as eventBaseName } from '@renderer/event/names'
import { isFullscreen } from '@renderer/core/share'
// import { getRandom } from '../../utils' // import { getRandom } from '../../utils'
export default { export default {
@ -29,6 +30,7 @@ export default {
close() { close() {
window.eventHub.emit(eventBaseName.close) window.eventHub.emit(eventBaseName.close)
}, },
isFullscreen,
} }
}, },
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<div :class="[$style.toolbar, setting.controlBtnPosition == 'left' ? $style.controlBtnLeft : $style.controlBtnRight]"> <div :class="[$style.toolbar, { [$style.fullscreen]: isFullscreen }, setting.controlBtnPosition == 'left' ? $style.controlBtnLeft : $style.controlBtnRight]">
<SearchInput /> <SearchInput />
<div :class="$style.logo" v-if="setting.controlBtnPosition == 'left'">L X</div> <div :class="$style.logo" v-if="setting.controlBtnPosition == 'left'">L X</div>
<ControlBtns v-else /> <ControlBtns v-else />
@ -11,10 +11,16 @@ import { mapGetters } from 'vuex'
import ControlBtns from './ControlBtns' import ControlBtns from './ControlBtns'
import SearchInput from './SearchInput' import SearchInput from './SearchInput'
import { isFullscreen } from '@renderer/core/share'
export default { export default {
name: 'CoreToolBar', name: 'CoreToolBar',
components: { SearchInput, ControlBtns }, components: { SearchInput, ControlBtns },
setup() {
return {
isFullscreen,
}
},
computed: { computed: {
...mapGetters(['setting']), ...mapGetters(['setting']),
}, },
@ -34,6 +40,13 @@ export default {
-webkit-app-region: drag; -webkit-app-region: drag;
z-index: 2; z-index: 2;
&.fullscreen {
-webkit-app-region: no-drag;
.logo {
display: none;
}
}
&.controlBtnLeft { &.controlBtnLeft {
.control { .control {
display: none; display: none;

View File

@ -44,11 +44,11 @@ material-modal(:show="versionInfo.showModal" @close="handleClose")
p 发现有新版本啦但是自动更新功能出问题了 p 发现有新版本啦但是自动更新功能出问题了
p p
| 你可以去&nbsp; | 你可以去&nbsp;
strong.hover.underline(@click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" tips="点击打开") 软件发布页 strong.hover.underline(@click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" aria-label="点击打开") 软件发布页
| &nbsp;&nbsp; | &nbsp;&nbsp;
strong.hover.underline(@click="handleOpenUrl('https://www.lanzoui.com/b0bf2cfa/')" tips="点击打开") 网盘 strong.hover.underline(@click="handleOpenUrl('https://www.lanzoui.com/b0bf2cfa/')" aria-label="点击打开") 网盘
| (密码 | (密码
strong.hover(@click="handleCopy('glqw')" tips="点击复制") glqw strong.hover(@click="handleCopy('glqw')" aria-label="点击复制") glqw
| )&nbsp;下载新版本 | )&nbsp;下载新版本
p p
| 国内Windows/MAC用户推荐到 | 国内Windows/MAC用户推荐到
@ -61,11 +61,11 @@ material-modal(:show="versionInfo.showModal" @close="handleClose")
p 你当前所在网络访问GitHub较慢导致新版本下载超时已经下了半个钟了😳你仍可选择继续等但墙裂建议手动更新版本 p 你当前所在网络访问GitHub较慢导致新版本下载超时已经下了半个钟了😳你仍可选择继续等但墙裂建议手动更新版本
p p
| 你可以去 | 你可以去
base-btn(min @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" tips="点击打开") 软件发布页 base-btn(min @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" aria-label="点击打开") 软件发布页
| |
base-btn(min @click="handleOpenUrl('https://www.lanzoui.com/b0bf2cfa/')" tips="点击打开") 网盘 base-btn(min @click="handleOpenUrl('https://www.lanzoui.com/b0bf2cfa/')" aria-label="点击打开") 网盘
| (密码 | (密码
strong.hover(@click="handleCopy('glqw')" tips="点击复制") glqw strong.hover(@click="handleCopy('glqw')" aria-label="点击复制") glqw
| )下载新版本 | )下载新版本
p p
| 国内Windows/MAC用户推荐到 | 国内Windows/MAC用户推荐到
@ -82,11 +82,11 @@ material-modal(:show="versionInfo.showModal" @close="handleClose")
p 更新信息获取失败可能是无法访问Github导致的请手动检查更新 p 更新信息获取失败可能是无法访问Github导致的请手动检查更新
p p
| 检查方法打开 | 检查方法打开
base-btn(min @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" tips="点击打开") 软件发布页 base-btn(min @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" aria-label="点击打开") 软件发布页
| |
base-btn(min @click="handleOpenUrl('https://www.lanzoui.com/b0bf2cfa/')" tips="点击打开") 网盘 base-btn(min @click="handleOpenUrl('https://www.lanzoui.com/b0bf2cfa/')" aria-label="点击打开") 网盘
| (密码 | (密码
strong.hover(@click="handleCopy('glqw')" tips="点击复制") glqw strong.hover(@click="handleCopy('glqw')" aria-label="点击复制") glqw
| )查看它们的 | )查看它们的
strong 版本号 strong 版本号
| 与当前版本({{versionInfo.version}})对比是否一样 | 与当前版本({{versionInfo.version}})对比是否一样
@ -115,11 +115,11 @@ material-modal(:show="versionInfo.showModal" @close="handleClose")
| 重新打开本弹窗 | 重新打开本弹窗
p p
| 手动更新可以去&nbsp; | 手动更新可以去&nbsp;
strong.hover.underline(@click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" tips="点击打开") 软件发布页 strong.hover.underline(@click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" aria-label="点击打开") 软件发布页
| &nbsp;&nbsp; | &nbsp;&nbsp;
strong.hover.underline(@click="handleOpenUrl('https://www.lanzoui.com/b0bf2cfa/')" tips="点击打开") 网盘 strong.hover.underline(@click="handleOpenUrl('https://www.lanzoui.com/b0bf2cfa/')" aria-label="点击打开") 网盘
| (密码 | (密码
strong.hover(@click="handleCopy('glqw')" tips="点击复制") glqw strong.hover(@click="handleCopy('glqw')" aria-label="点击复制") glqw
| )&nbsp;下载 | )&nbsp;下载
p 国内Windows/MAC用户推荐到网盘下载 p 国内Windows/MAC用户推荐到网盘下载
p 当前下载进度{{progress}} p 当前下载进度{{progress}}

View File

@ -1,30 +1,30 @@
<template lang="pug"> <template lang="pug">
div(:class="$style.btns") div(:class="$style.btns")
button(type="button" v-if="playBtn" @contextmenu.capture.stop :tips="$t('list__play')" @click.stop="handleClick('play')") button(type="button" v-if="playBtn" @contextmenu.capture.stop :aria-label="$t('list__play')" @click.stop="handleClick('play')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 287.386 287.386' space='preserve' v-once) svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 287.386 287.386' space='preserve' v-once)
use(xlink:href='#icon-testPlay') use(xlink:href='#icon-testPlay')
button(type="button" v-if="listAddBtn" @contextmenu.capture.stop :tips="$t('list__add_to')" @click.stop="handleClick('listAdd')") button(type="button" v-if="listAddBtn" @contextmenu.capture.stop :aria-label="$t('list__add_to')" @click.stop="handleClick('listAdd')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 42 42' space='preserve' v-once) svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 42 42' space='preserve' v-once)
use(xlink:href='#icon-addTo') use(xlink:href='#icon-addTo')
button(type="button" v-if="downloadBtn && setting.download.enable" @contextmenu.capture.stop :tips="$t('list__download')" @click.stop="handleClick('download')") button(type="button" v-if="downloadBtn && setting.download.enable" @contextmenu.capture.stop :aria-label="$t('list__download')" @click.stop="handleClick('download')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 475.078 475.077' space='preserve' v-once) svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 475.078 475.077' space='preserve' v-once)
use(xlink:href='#icon-download') use(xlink:href='#icon-download')
//- button(type="button" :tips="$t('list__add')" v-if="userInfo" @click.stop="handleClick('add')") //- button(type="button" :aria-label="$t('list__add')" v-if="userInfo" @click.stop="handleClick('add')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 42 42' space='preserve') svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 42 42' space='preserve')
use(xlink:href='#icon-addTo') use(xlink:href='#icon-addTo')
button(type="button" v-if="startBtn" @contextmenu.capture.stop :tips="$t('list__start')" @click.stop="handleClick('start')") button(type="button" v-if="startBtn" @contextmenu.capture.stop :aria-label="$t('list__start')" @click.stop="handleClick('start')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 170 170' space='preserve' v-once) svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 170 170' space='preserve' v-once)
use(xlink:href='#icon-play') use(xlink:href='#icon-play')
button(type="button" v-if="pauseBtn" @contextmenu.capture.stop :tips="$t('list__pause')" @click.stop="handleClick('pause')") button(type="button" v-if="pauseBtn" @contextmenu.capture.stop :aria-label="$t('list__pause')" @click.stop="handleClick('pause')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 277.338 277.338' space='preserve' v-once) svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 277.338 277.338' space='preserve' v-once)
use(xlink:href='#icon-pause') use(xlink:href='#icon-pause')
button(type="button" v-if="fileBtn" @contextmenu.capture.stop :tips="$t('list__file')" @click.stop="handleClick('file')") button(type="button" v-if="fileBtn" @contextmenu.capture.stop :aria-label="$t('list__file')" @click.stop="handleClick('file')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='-61 0 512 512' space='preserve' v-once) svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='-61 0 512 512' space='preserve' v-once)
use(xlink:href='#icon-musicFile') use(xlink:href='#icon-musicFile')
button(type="button" v-if="searchBtn" @contextmenu.capture.stop :tips="$t('list__search')" @click.stop="handleClick('search')") button(type="button" v-if="searchBtn" @contextmenu.capture.stop :aria-label="$t('list__search')" @click.stop="handleClick('search')")
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' v-once) 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' v-once)
use(xlink:href='#icon-search') use(xlink:href='#icon-search')
button(type="button" v-if="removeBtn" :tips="$t('list__remove')" @click.stop="handleClick('remove')") button(type="button" v-if="removeBtn" :aria-label="$t('list__remove')" @click.stop="handleClick('remove')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 212.982 212.982' space='preserve' v-once) svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 212.982 212.982' space='preserve' v-once)
use(xlink:href='#icon-delete') use(xlink:href='#icon-delete')

View File

@ -4,7 +4,7 @@ teleport(:to="teleport")
transition(enter-active-class="animated fadeIn" leave-active-class="animated fadeOut") transition(enter-active-class="animated fadeIn" leave-active-class="animated fadeOut")
div(:class="$style.modal" v-show="showContent" @click="bgClose && close()") div(:class="$style.modal" v-show="showContent" @click="bgClose && close()")
transition(:enter-active-class="inClass" :leave-active-class="outClass" @after-enter="$emit('after-enter', $event)" @after-leave="handleAfterLeave") transition(:enter-active-class="inClass" :leave-active-class="outClass" @after-enter="$emit('after-enter', $event)" @after-leave="handleAfterLeave")
div(:class="$style.content" v-show="showContent" @click.stop) div(:class="$style.content" v-show="showContent" @click.stop :style="contentStyle")
header(:class="$style.header") header(:class="$style.header")
button(type="button" @click="close" v-if="closeBtn") button(type="button" @click="close" v-if="closeBtn")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 212.982 212.982' space='preserve') svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 212.982 212.982' space='preserve')
@ -34,6 +34,10 @@ export default {
type: String, type: String,
default: '#root', default: '#root',
}, },
maxWidth: {
type: String,
default: '76%',
},
}, },
emits: ['after-enter', 'after-leave', 'close'], emits: ['after-enter', 'after-leave', 'close'],
data() { data() {
@ -91,6 +95,11 @@ export default {
}, },
computed: { computed: {
...mapGetters(['setting']), ...mapGetters(['setting']),
contentStyle() {
return {
maxWidth: this.maxWidth,
}
},
}, },
watch: { watch: {
'setting.randomAnimate'(n) { 'setting.randomAnimate'(n) {
@ -182,7 +191,7 @@ export default {
box-shadow: 0 0 3px rgba(0, 0, 0, .3); box-shadow: 0 0 3px rgba(0, 0, 0, .3);
overflow: hidden; overflow: hidden;
max-height: 80%; max-height: 80%;
max-width: 76%; // max-width: 76%;
min-width: 220px; min-width: 220px;
position: relative; position: relative;
display: flex; display: flex;

View File

@ -20,13 +20,13 @@ div(:class="$style.songList")
div.list-item(@click="handleListItemClick($event, index)" @contextmenu="handleListItemRightClick($event, index)" div.list-item(@click="handleListItemClick($event, index)" @contextmenu="handleListItemRightClick($event, index)"
:class="[{ selected: rightClickSelectedIndex == index }, { active: selectedList.includes(item) }]") :class="[{ selected: rightClickSelectedIndex == index }, { active: selectedList.includes(item) }]")
div.list-item-cell.nobreak.center(:style="{ width: rowWidth.r1 }" style="padding-left: 3px; padding-right: 3px;" :class="$style.noSelect" @click.stop) {{index + 1}} div.list-item-cell.nobreak.center(:style="{ width: rowWidth.r1 }" style="padding-left: 3px; padding-right: 3px;" :class="$style.noSelect" @click.stop) {{index + 1}}
div.list-item-cell.auto(:style="{ width: rowWidth.r2 }" :tips="item.name + ((item._types.ape || item._types.flac || item._types.wav) ? ` - ${$t('tag__lossless')}` : item._types['320k'] ? ` - ${$t('tag__high_quality')}` : '')") div.list-item-cell.auto(:style="{ width: rowWidth.r2 }" :aria-label="item.name + ((item._types.ape || item._types.flac || item._types.wav) ? ` - ${$t('tag__lossless')}` : item._types['320k'] ? ` - ${$t('tag__high_quality')}` : '')")
span.select {{item.name}} span.select {{item.name}}
span.badge.badge-theme-success(:class="[$style.labelQuality, $style.noSelect]" v-if="item._types.ape || item._types.flac || item._types.wav") {{$t('tag__lossless')}} span.badge.badge-theme-success(:class="[$style.labelQuality, $style.noSelect]" v-if="item._types.ape || item._types.flac || item._types.wav") {{$t('tag__lossless')}}
span.badge.badge-theme-info(:class="[$style.labelQuality, $style.noSelect]" v-else-if="item._types['320k']") {{$t('tag__high_quality')}} span.badge.badge-theme-info(:class="[$style.labelQuality, $style.noSelect]" v-else-if="item._types['320k']") {{$t('tag__high_quality')}}
div.list-item-cell(:style="{ width: rowWidth.r3 }" :tips="item.singer") div.list-item-cell(:style="{ width: rowWidth.r3 }" :aria-label="item.singer")
span.select {{item.singer}} span.select {{item.singer}}
div.list-item-cell(:style="{ width: rowWidth.r4 }" :tips="item.albumName") div.list-item-cell(:style="{ width: rowWidth.r4 }" :aria-label="item.albumName")
span.select {{item.albumName}} span.select {{item.albumName}}
div.list-item-cell(:style="{ width: rowWidth.r5 }") div.list-item-cell(:style="{ width: rowWidth.r5 }")
span(:class="[$style.time, $style.noSelect]") {{item.interval || '--/--'}} span(:class="[$style.time, $style.noSelect]") {{item.interval || '--/--'}}

View File

@ -1,5 +1,6 @@
import { computed, useRefGetter, watch, ref, onBeforeUnmount } from '@renderer/utils/vueTools' import { computed, useRefGetter, watch, ref, onBeforeUnmount } from '@renderer/utils/vueTools'
import { windowSizeList } from '@common/config' import { windowSizeList, isFullscreen } from '@renderer/core/share'
import { getFontSizeWithScreen } from '@renderer/utils'
const useKeyEvent = ({ handleSelectAllData }) => { const useKeyEvent = ({ handleSelectAllData }) => {
const keyEvent = { const keyEvent = {
@ -49,7 +50,7 @@ export default ({ props }) => {
const setting = useRefGetter('setting') const setting = useRefGetter('setting')
let lastSelectIndex = -1 let lastSelectIndex = -1
const listItemHeight = computed(() => { const listItemHeight = computed(() => {
return parseInt(windowSizeList.find(item => item.id == setting.value.windowSizeId).fontSize) / 16 * 37 return Math.ceil((isFullscreen.value ? getFontSizeWithScreen() : parseInt(windowSizeList.find(item => item.id == setting.value.windowSizeId).fontSize)) * 2.3)
}) })
const removeAllSelect = () => { const removeAllSelect = () => {

View File

@ -6,24 +6,24 @@
<use xlink:href="#icon-left"></use> <use xlink:href="#icon-left"></use>
</svg></span></li> </svg></span></li>
<li v-else> <li v-else>
<button type="button" @click="handleClick(page - 1)" :tips="$t('pagination__prev')"> <button type="button" @click="handleClick(page - 1)" :aria-label="$t('pagination__prev')">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 451.846 451.847" space="preserve"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 451.846 451.847" space="preserve">
<use xlink:href="#icon-left"></use> <use xlink:href="#icon-left"></use>
</svg> </svg>
</button> </button>
</li> </li>
<li v-if="allPage &gt; btnLength &amp;&amp; page &gt; pageEvg+1" :class="$style.first"> <li v-if="allPage &gt; btnLength &amp;&amp; page &gt; pageEvg+1" :class="$style.first">
<button type="button" @click="handleClick(1)" :tips="$t('pagination__page', { num: 1 })"> <button type="button" @click="handleClick(1)" :aria-label="$t('pagination__page', { num: 1 })">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 451.846 451.847" space="preserve"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 451.846 451.847" space="preserve">
<use xlink:href="#icon-first"></use> <use xlink:href="#icon-first"></use>
</svg> </svg>
</button> </button>
</li> </li>
<li v-for="(p, index) in pages" :key="index" :class="{[$style.active] : p == page}"><span v-if="p === page" v-text="page"></span> <li v-for="(p, index) in pages" :key="index" :class="{[$style.active] : p == page}"><span v-if="p === page" v-text="page"></span>
<button v-else type="button" @click="handleClick(p)" v-text="p" :tips="$t('pagination__page', { num: p })"></button> <button v-else type="button" @click="handleClick(p)" v-text="p" :aria-label="$t('pagination__page', { num: p })"></button>
</li> </li>
<li v-if="allPage &gt; btnLength &amp;&amp; allPage - page &gt; pageEvg" :class="$style.last"> <li v-if="allPage &gt; btnLength &amp;&amp; allPage - page &gt; pageEvg" :class="$style.last">
<button type="button" @click="handleClick(allPage)" :tips="$t('pagination__page', { num: allPage })"> <button type="button" @click="handleClick(allPage)" :aria-label="$t('pagination__page', { num: allPage })">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 451.846 451.847" space="preserve"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 451.846 451.847" space="preserve">
<use xlink:href="#icon-last"></use> <use xlink:href="#icon-last"></use>
</svg> </svg>
@ -34,7 +34,7 @@
<use xlink:href="#icon-right"></use> <use xlink:href="#icon-right"></use>
</svg></span></li> </svg></span></li>
<li v-else> <li v-else>
<button type="button" @click="handleClick(page + 1)" :tips="$t('pagination__next')"> <button type="button" @click="handleClick(page + 1)" :aria-label="$t('pagination__next')">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 451.846 451.847" space="preserve"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 451.846 451.847" space="preserve">
<use xlink:href="#icon-right"></use> <use xlink:href="#icon-right"></use>
</svg> </svg>

View File

@ -1,5 +1,5 @@
import { ref, reactive, shallowRef, markRaw } from '@renderer/utils/vueTools' import { ref, reactive, shallowRef, markRaw } from '@renderer/utils/vueTools'
import { windowSizeList as configWindowSizeList, themes as configThemes } from '@common/config' import { windowSizeList as configWindowSizeList, themes as configThemes, themeLights as themeLightIds, themeDarks as themeDarkIds } from '@common/config'
import { version } from '../../../../package.json' import { version } from '../../../../package.json'
process.versions.app = version process.versions.app = version
@ -36,9 +36,14 @@ export const sync = window.sync = reactive({
devices: [], devices: [],
}, },
}) })
export const isFullscreen = ref(false)
export const windowSizeList = markRaw(configWindowSizeList) export const windowSizeList = markRaw(configWindowSizeList)
export const themes = markRaw(configThemes) export const themes = markRaw(configThemes)
export const themeLights = markRaw(configThemes.filter(t => themeLightIds.includes(t.id)))
export const themeDarks = markRaw(configThemes.filter(t => themeDarkIds.includes(t.id)))
export const themeShouldUseDarkColors = ref(window.shouldUseDarkColors)
delete window.shouldUseDarkColors
export const qualityList = shallowRef({}) export const qualityList = shallowRef({})
export const setQualityList = _qualityList => { export const setQualityList = _qualityList => {

View File

@ -1,12 +1,14 @@
import { openUrl } from '@renderer/utils' import { openUrl } from '@renderer/utils'
import { base as eventBaseName } from '@renderer/event/names' import { base as eventBaseName } from '@renderer/event/names'
import { onSetConfig } from '@renderer/utils/tools' import { onSetConfig, onSystemThemeChange } from '@renderer/utils/tools'
import { isFullscreen, themeShouldUseDarkColors } from '@renderer/core/share'
import { rendererSend, NAMES, rendererInvoke } from '@common/ipc'
import { import {
toRaw, toRaw,
useCommit, useCommit,
onBeforeUnmount, onBeforeUnmount,
watchEffect, watch,
useRefGetter, useRefGetter,
} from '@renderer/utils/vueTools' } from '@renderer/utils/vueTools'
@ -22,6 +24,15 @@ const handleBodyClick = event => {
event.preventDefault() event.preventDefault()
if (/^https?:\/\//.test(event.target.href)) openUrl(event.target.href) if (/^https?:\/\//.test(event.target.href)) openUrl(event.target.href)
} }
const handle_open_devtools = event => {
rendererSend(NAMES.mainWindow.open_dev_tools)
}
const handle_fullscreen = event => {
if (event.event.repeat) return
rendererInvoke(NAMES.mainWindow.fullscreen, !isFullscreen.value).then(fullscreen => {
isFullscreen.value = fullscreen
})
}
export default ({ export default ({
dieableIgnoreMouseEvents, dieableIgnoreMouseEvents,
@ -32,12 +43,13 @@ export default ({
}) => { }) => {
const setSetting = useCommit('setSetting') const setSetting = useCommit('setSetting')
const windowSizeActive = useRefGetter('windowSizeActive') const windowSizeActive = useRefGetter('windowSizeActive')
const isShowAnimation = useRefGetter('isShowAnimation')
watchEffect(() => { watch(windowSizeActive, ({ fontSize }) => {
document.documentElement.style.fontSize = windowSizeActive.value.fontSize document.documentElement.style.fontSize = fontSize
}) })
watchEffect(() => { watch(isShowAnimation, val => {
if (setting.value.isShowAnimation) { if (val) {
if (document.body.classList.contains('disableAnimation')) { if (document.body.classList.contains('disableAnimation')) {
document.body.classList.remove('disableAnimation') document.body.classList.remove('disableAnimation')
} }
@ -54,8 +66,15 @@ export default ({
window.eventHub.emit(eventBaseName.set_config, config) window.eventHub.emit(eventBaseName.set_config, config)
}) })
const rSystemThemeChange = onSystemThemeChange((event, isDark) => {
// console.log(isDark)
themeShouldUseDarkColors.value = isDark
})
window.eventHub.emit(eventBaseName.bindKey) window.eventHub.emit(eventBaseName.bindKey)
window.eventHub.on('key_escape_down', handle_key_esc_down) window.eventHub.on('key_escape_down', handle_key_esc_down)
window.eventHub.on('key_mod+f12_down', handle_open_devtools)
window.eventHub.on('key_f11_down', handle_fullscreen)
document.body.addEventListener('click', handleBodyClick, true) document.body.addEventListener('click', handleBodyClick, true)
if (isProd && !window.dt && !isLinux) { if (isProd && !window.dt && !isLinux) {
@ -68,9 +87,12 @@ export default ({
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.eventHub.off('key_escape_down', handle_key_esc_down) window.eventHub.off('key_escape_down', handle_key_esc_down)
window.eventHub.off('key_mod+f12_down', handle_open_devtools)
window.eventHub.off('key_f11_down', handle_fullscreen)
document.body.removeEventListener('click', handleBodyClick) document.body.removeEventListener('click', handleBodyClick)
window.eventHub.emit(eventBaseName.unbindKey) window.eventHub.emit(eventBaseName.unbindKey)
rSetConfig() rSetConfig()
rSystemThemeChange()
if (isProd && !window.dt && !isLinux) { if (isProd && !window.dt && !isLinux) {
document.body.removeEventListener('mouseenter', enableIgnoreMouseEvents) document.body.removeEventListener('mouseenter', enableIgnoreMouseEvents)

View File

@ -44,9 +44,11 @@ export default ({ setting }) => {
setDesktopLyricInfo('lyric', { lrc: musicInfo.lrc, tlrc: musicInfo.tlrc, lxlrc: musicInfo.lxlrc }) setDesktopLyricInfo('lyric', { lrc: musicInfo.lrc, tlrc: musicInfo.tlrc, lxlrc: musicInfo.lxlrc })
if (isPlay.value && (musicInfo.url || playMusicInfo.listId == 'download')) { if (isPlay.value && (musicInfo.url || playMusicInfo.listId == 'download')) {
const time = getCurrentTime() * 1000 setTimeout(() => {
setDesktopLyricInfo('play', time) const time = getCurrentTime() * 1000
lrc.play(time) setDesktopLyricInfo('play', time)
lrc.play(time)
})
} }
} }

View File

@ -7,6 +7,10 @@ import { setMediaDeviceId } from '@renderer/plugins/player'
import { isPlay } from '@renderer/core/share/player' import { isPlay } from '@renderer/core/share/player'
import { player as eventPlayerNames } from '@renderer/event/names' import { player as eventPlayerNames } from '@renderer/event/names'
const getDevices = async() => {
const devices = await navigator.mediaDevices.enumerateDevices()
return devices.filter(({ kind }) => kind == 'audiooutput')
}
export default ({ setting }) => { export default ({ setting }) => {
let prevDeviceLabel = null let prevDeviceLabel = null
@ -15,7 +19,7 @@ export default ({ setting }) => {
const setMediaDevice = async(mediaDeviceId) => { const setMediaDevice = async(mediaDeviceId) => {
let label = prevDeviceLabel let label = prevDeviceLabel
const devices = await navigator.mediaDevices.enumerateDevices() const devices = await getDevices()
let device = devices.find(device => device.deviceId === mediaDeviceId) let device = devices.find(device => device.deviceId === mediaDeviceId)
if (device) { if (device) {
mediaDeviceId = device.deviceId mediaDeviceId = device.deviceId
@ -41,12 +45,15 @@ export default ({ setting }) => {
setting.value.player.isMediaDeviceRemovedStopPlay && setting.value.player.isMediaDeviceRemovedStopPlay &&
isPlay.value && isPlay.value &&
device.label != prevDeviceLabel device.label != prevDeviceLabel
) window.eventHub.emit(eventPlayerNames.setTogglePlay) ) {
global.isPlayedStop = true
window.eventHub.emit(eventPlayerNames.setPause)
}
} }
const handleMediaListChange = async() => { const handleMediaListChange = async() => {
let mediaDeviceId = setting.value.player.mediaDeviceId let mediaDeviceId = setting.value.player.mediaDeviceId
const devices = await navigator.mediaDevices.enumerateDevices() const devices = await getDevices()
let device = devices.find(device => device.deviceId === mediaDeviceId) let device = devices.find(device => device.deviceId === mediaDeviceId)
if (!device) device = devices.find(device => device.deviceId === 'default') if (!device) device = devices.find(device => device.deviceId === 'default')
if (!device) device = { label: null, deviceId: null } if (!device) device = { label: null, deviceId: null }

View File

@ -1,10 +1,11 @@
import { onBeforeUnmount, useCommit } from '@renderer/utils/vueTools' import { onBeforeUnmount, useCommit } from '@renderer/utils/vueTools'
import { player as eventPlayerNames, taskbar as eventTaskbarNames } from '@renderer/event/names' import { player as eventPlayerNames, taskbar as eventTaskbarNames, list as eventListNames } from '@renderer/event/names'
import { onTaskbarThumbarClick, setTaskbarThumbnailClip, setTaskbarThumbarButtons } from '@renderer/utils/tools' import { onTaskbarThumbarClick, setTaskbarThumbnailClip, setTaskbarThumbarButtons } from '@renderer/utils/tools'
// import store from '@renderer/store' // import store from '@renderer/store'
import { loveList, getList } from '@renderer/core/share/list' import { loveList, getList } from '@renderer/core/share/list'
import { playMusicInfo } from '@renderer/core/share/player' import { playMusicInfo } from '@renderer/core/share/player'
import { throttle } from '@renderer/utils'
export default () => { export default () => {
const listAdd = useCommit('list', 'listAdd') const listAdd = useCommit('list', 'listAdd')
@ -54,6 +55,11 @@ export default () => {
const handleSetTaskbarThumbnailClip = (clip) => { const handleSetTaskbarThumbnailClip = (clip) => {
setTaskbarThumbnailClip(clip) setTaskbarThumbnailClip(clip)
} }
const throttleListChange = throttle(listIds => {
if (!listIds.includes(loveList.id)) return
if (!updateCollectStatus()) return
setButtons()
})
// const updateSetting = () => { // const updateSetting = () => {
// const setting = store.getters.setting // const setting = store.getters.setting
// buttons.lrc = setting.desktopLyric.enable // buttons.lrc = setting.desktopLyric.enable
@ -107,6 +113,7 @@ export default () => {
window.eventHub.on(eventPlayerNames.stop, handleStop) window.eventHub.on(eventPlayerNames.stop, handleStop)
window.eventHub.on(eventPlayerNames.setPlayInfo, handleSetPlayInfo) window.eventHub.on(eventPlayerNames.setPlayInfo, handleSetPlayInfo)
window.eventHub.on(eventTaskbarNames.setTaskbarThumbnailClip, handleSetTaskbarThumbnailClip) window.eventHub.on(eventTaskbarNames.setTaskbarThumbnailClip, handleSetTaskbarThumbnailClip)
window.eventHub.on(eventListNames.listChange, throttleListChange)
onBeforeUnmount(() => { onBeforeUnmount(() => {
rTaskbarThumbarClick() rTaskbarThumbarClick()
@ -115,6 +122,7 @@ export default () => {
window.eventHub.off(eventPlayerNames.stop, handleStop) window.eventHub.off(eventPlayerNames.stop, handleStop)
window.eventHub.off(eventPlayerNames.setPlayInfo, handleSetPlayInfo) window.eventHub.off(eventPlayerNames.setPlayInfo, handleSetPlayInfo)
window.eventHub.off(eventTaskbarNames.setTaskbarThumbnailClip, handleSetTaskbarThumbnailClip) window.eventHub.off(eventTaskbarNames.setTaskbarThumbnailClip, handleSetTaskbarThumbnailClip)
window.eventHub.off(eventListNames.listChange, throttleListChange)
}) })
return () => { return () => {

View File

@ -1,6 +1,6 @@
import { onBeforeUnmount } from '@renderer/utils/vueTools' import { onBeforeUnmount } from '@renderer/utils/vueTools'
import { player as eventPlayerNames, list as eventListNames } from '@renderer/event/names' import { player as eventPlayerNames, list as eventListNames, download as eventDownloadNames } from '@renderer/event/names'
import { playInfo, playMusicInfo, updatePlayIndex } from '@renderer/core/share/player' import { playInfo, playMusicInfo, updatePlayIndex } from '@renderer/core/share/player'
import { getList } from '@renderer/core/share/utils' import { getList } from '@renderer/core/share/utils'
import { throttle } from '@renderer/utils' import { throttle } from '@renderer/utils'
@ -30,9 +30,15 @@ export default ({ playNext }) => {
throttleListChange() throttleListChange()
} }
const handleDownloadListChange = () => {
handleListChange(['download'])
}
window.eventHub.on(eventListNames.listChange, handleListChange) window.eventHub.on(eventListNames.listChange, handleListChange)
window.eventHub.on(eventDownloadNames.listChange, handleDownloadListChange)
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.eventHub.off(eventListNames.listChange, handleListChange) window.eventHub.off(eventListNames.listChange, handleListChange)
window.eventHub.off(eventDownloadNames.listChange, handleDownloadListChange)
}) })
} }

View File

@ -106,7 +106,3 @@ eventHub.on(syncName.send_sync_list, ({ action, data }) => {
if (!sync.enable) return if (!sync.enable) return
rendererSend(NAMES.mainWindow.sync_list, { action, data }) rendererSend(NAMES.mainWindow.sync_list, { action, data })
}) })
eventHub.on('key_mod+f12_down', ({ action, data }) => {
if (!sync.enable) return
rendererSend(NAMES.mainWindow.open_dev_tools)
})

View File

@ -6,12 +6,6 @@
<title>洛雪音乐助手</title> <title>洛雪音乐助手</title>
</head> </head>
<body> <body>
<script>
if (/theme=(\w+)/.test(window.location.href)) document.body.classList.add(RegExp.$1)
;/dt=(\w+)/.exec(window.location.href)
window.dt = RegExp.$1 == 'true'
document.body.classList.add(window.dt ? 'disableTransparent' : 'transparent')
</script>
<!-- <div id="waiting-mask"> <!-- <div id="waiting-mask">
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" height="50%" viewbox="0 0 447.942 447.943"> <svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" height="50%" viewbox="0 0 447.942 447.943">
<path id="logo-path-1" d="M203.806.482c-19.668-3.346-35.76 11.139-35.76 31.086v206.166c-11.642-4.271-24.165-6.725-37.281-6.725-59.905 0-108.469 48.566-108.469 108.473 0 59.903 48.564 108.461 108.469 108.461 34.141 0 64.54-15.82 84.406-40.482l-49.658-49.664c-15.116-15.112-11.708-28.901-9.542-34.14 2.166-5.233 9.514-17.4 30.883-17.4h18.082v-56.885c0-21.132 14.617-38.862 34.266-43.745.032-44.373.032-81.808.032-81.808 140.147 0 131.724 83.974 115.325 132.196-6.42 18.884-2.601 22.05 10.893 7.354C536.473 77.106 298.38 16.566 203.806.482z"/> <path id="logo-path-1" d="M203.806.482c-19.668-3.346-35.76 11.139-35.76 31.086v206.166c-11.642-4.271-24.165-6.725-37.281-6.725-59.905 0-108.469 48.566-108.469 108.473 0 59.903 48.564 108.461 108.469 108.461 34.141 0 64.54-15.82 84.406-40.482l-49.658-49.664c-15.116-15.112-11.708-28.901-9.542-34.14 2.166-5.233 9.514-17.4 30.883-17.4h18.082v-56.885c0-21.132 14.617-38.862 34.266-43.745.032-44.373.032-81.808.032-81.808 140.147 0 131.724 83.974 115.325 132.196-6.42 18.884-2.601 22.05 10.893 7.354C536.473 77.106 298.38 16.566 203.806.482z"/>
@ -20,5 +14,11 @@
</div> --> </div> -->
<div id="root" style="display: none;"> <div id="root" style="display: none;">
</div> </div>
<script>
if (/theme=(\w+)/.test(window.location.href)) document.getElementById('root').classList.add(RegExp.$1)
window.shouldUseDarkColors = /dark=true/.test(window.location.href)
window.dt = /dt=true/.test(window.location.href)
document.body.classList.add(window.dt ? 'disableTransparent' : 'transparent')
</script>
</body> </body>
</html> </html>

View File

@ -1,6 +1,6 @@
<template> <template>
<transition name="tips-fade" @after-leave="afterLeave"> <transition name="tips-fade" @after-leave="afterLeave">
<div v-show="visible" :style="{ left: position.left + 'px' , top: position.top + 'px', transform: transform }" ref="dom_tips" :class="$style.tips">{{message}}</div> <div v-show="visible" :style="{ left: position.left + 'px' , top: position.top + 'px', transform: transform }" ref="dom_tips" :class="$style.tips" role="presentation">{{message}}</div>
</transition> </transition>
</template> </template>

View File

@ -8,10 +8,14 @@ let prevX = 0
let prevY = 0 let prevY = 0
let isDraging = false let isDraging = false
const getTipText = el => {
return el.getAttribute('aria-label') && el.getAttribute('ignore-tip') == null ? el.getAttribute('aria-label') : null
}
const getTips = el => const getTips = el =>
el el
? el.getAttribute('tips') ? getTipText(el)
? el.getAttribute('tips') ? getTipText(el)
: el.parentNode === document.documentElement : el.parentNode === document.documentElement
? null ? null
: getTips(el.parentNode) : getTips(el.parentNode)

View File

@ -14,6 +14,7 @@ import { messages } from '@/lang'
const i18n = createI18n({ const i18n = createI18n({
locale: 'zh-cn', locale: 'zh-cn',
fallbackLocale: 'zh-cn', fallbackLocale: 'zh-cn',
allowComposition: true,
messages, messages,
}) })

View File

@ -21,6 +21,8 @@ export const getAnalyser = () => {
return analyser return analyser
} }
export const hasInitedAnalyser = () => audioContext != null
export const setResource = src => { export const setResource = src => {
if (audio) audio.src = src if (audio) audio.src = src
} }

View File

@ -1,17 +1,24 @@
import music from '../utils/music' import music from '../utils/music'
import { themes, windowSizeList } from '@renderer/core/share' import { themes, windowSizeList, themeShouldUseDarkColors } from '@renderer/core/share'
export default { export default {
theme(state) { theme(state) {
let theme = themes.find(theme => theme.id == state.setting.themeId) const themeId = state.setting.theme.id == 'auto'
return (theme && theme.className) || '' ? themeShouldUseDarkColors.value
? state.setting.theme.darkId
: state.setting.theme.lightId
: state.setting.theme.id
let theme = themes.find(theme => theme.id == themeId) ?? themes[0]
return theme.className
}, },
font(state) { font(state) {
return state.setting.font return state.setting.font
}, },
themes(state) { themes(state) {
return { return {
active: state.setting.themeId, active: state.setting.theme.id,
lightId: state.setting.theme.lightId,
darkId: state.setting.theme.darkId,
list: themes, list: themes,
} }
}, },
@ -59,4 +66,7 @@ export default {
pactModalVisible(state) { pactModalVisible(state) {
return !state.setting.isAgreePact return !state.setting.isAgreePact
}, },
isShowAnimation(state) {
return state.setting.isShowAnimation
},
} }

View File

@ -1,5 +1,7 @@
import music from '../../utils/music' import music from '../../utils/music'
import { markRawList } from '@renderer/utils/vueTools' import { markRawList } from '@renderer/utils/vueTools'
import { deduplicationList } from '@renderer/utils'
const sourceList = {} const sourceList = {}
const sources = [] const sources = []
const cache = new Map() const cache = new Map()
@ -57,6 +59,7 @@ const actions = {
// : music[source].leaderboard.getList(bangId, page) // : music[source].leaderboard.getList(bangId, page)
// ).then(result => commit('setList', { result, key })) // ).then(result => commit('setList', { result, key }))
return music[source].leaderboard.getList(bangId, page).then(result => { return music[source].leaderboard.getList(bangId, page).then(result => {
result.list = deduplicationList(result.list)
cache.set(key, result) cache.set(key, result)
listInfo.list = markRawList(result.list) listInfo.list = markRawList(result.list)
listInfo.total = result.total listInfo.total = result.total
@ -75,6 +78,7 @@ const actions = {
return cache.has(key) return cache.has(key)
? Promise.resolve(cache.get(key)) ? Promise.resolve(cache.get(key))
: music[source].leaderboard.getList(bangId, page).then(result => { : music[source].leaderboard.getList(bangId, page).then(result => {
result.list = markRawList(deduplicationList(result.list))
cache.set(key, result) cache.set(key, result)
return result return result
}) })
@ -89,7 +93,7 @@ const actions = {
: loadData(id, loadPage).then(result1 => load(++loadPage).then(result2 => [...result1.list, ...result2])) : loadData(id, loadPage).then(result1 => load(++loadPage).then(result2 => [...result1.list, ...result2]))
} }
return load().then(result2 => [...result.list, ...result2]) return load().then(result2 => [...result.list, ...result2])
}) }).then(list => deduplicationList(list))
}, },
} }

View File

@ -36,29 +36,52 @@ const playMusic = () => {
window.eventHub.emit(eventPlayerNames.playMusic) window.eventHub.emit(eventPlayerNames.playMusic)
} }
const filterList = async({ playedList, listInfo, savePath, commit }) => { const filterList = async({ playedList, listInfo, savePath, commit, isCheckFile }) => {
// if (this.list.listName === null) return // if (this.list.listName === null) return
let list // console.log(isCheckFile)
let canPlayList = [] if (isCheckFile) {
const filteredPlayedList = playedList.filter(({ listId, isTempPlay }) => listInfo.id === listId && !isTempPlay).map(({ musicInfo }) => musicInfo) let list
if (listInfo.id == 'download') { let canPlayList = []
list = [] const filteredPlayedList = playedList.filter(({ listId, isTempPlay }) => listInfo.id === listId && !isTempPlay).map(({ musicInfo }) => musicInfo)
for (const item of listInfo.list) { if (listInfo.id == 'download') {
const filePath = path.join(savePath, item.metadata.fileName) list = []
if (!await checkPath(filePath) || !item.isComplate || /\.ape$/.test(filePath)) continue for (const item of listInfo.list) {
const filePath = path.join(savePath, item.metadata.fileName)
if (!await checkPath(filePath) || !item.isComplate || /\.ape$/.test(filePath)) continue
canPlayList.push(item) canPlayList.push(item)
// 排除已播放音乐 // 排除已播放音乐
let index = filteredPlayedList.findIndex(m => m.songmid == item.songmid) let index = filteredPlayedList.findIndex(m => m.songmid == item.songmid)
if (index > -1) { if (index > -1) {
filteredPlayedList.splice(index, 1) filteredPlayedList.splice(index, 1)
continue continue
}
list.push(item)
} }
list.push(item) } else {
list = listInfo.list.filter(s => {
// if (!assertApiSupport(s.source)) return false
canPlayList.push(s)
let index = filteredPlayedList.findIndex(m => m.songmid == s.songmid)
if (index > -1) {
filteredPlayedList.splice(index, 1)
return false
}
return true
})
} }
if (!list.length && playedList.length) {
commit('clearPlayedList')
return canPlayList
}
return list
} else { } else {
list = listInfo.list.filter(s => { let canPlayList = []
const filteredPlayedList = playedList.filter(({ listId, isTempPlay }) => listInfo.id === listId && !isTempPlay).map(({ musicInfo }) => musicInfo)
const list = listInfo.list.filter(s => {
// if (!assertApiSupport(s.source)) return false // if (!assertApiSupport(s.source)) return false
canPlayList.push(s) canPlayList.push(s)
@ -69,12 +92,12 @@ const filterList = async({ playedList, listInfo, savePath, commit }) => {
} }
return true return true
}) })
if (!list.length && playedList.length) {
commit('clearPlayedList')
return canPlayList
}
return list
} }
if (!list.length && playedList.length) {
commit('clearPlayedList')
return canPlayList
}
return list
} }
const getMusicUrl = function(musicInfo, type, onToggleSource, retryedSource = [], originMusic) { const getMusicUrl = function(musicInfo, type, onToggleSource, retryedSource = [], originMusic) {
@ -188,6 +211,7 @@ const actions = {
}, },
async getLrc({ commit, state }, musicInfo) { async getLrc({ commit, state }, musicInfo) {
const lrcInfo = await getStoreLyric(musicInfo) const lrcInfo = await getStoreLyric(musicInfo)
// lrcInfo = {}
// if (lrcRequest && lrcRequest.cancelHttp) lrcRequest.cancelHttp() // if (lrcRequest && lrcRequest.cancelHttp) lrcRequest.cancelHttp()
if (lrcInfo.lyric && lrcInfo.tlyric != null) { if (lrcInfo.lyric && lrcInfo.tlyric != null) {
// if (musicInfo.lrc.startsWith('\ufeff[id:$00000000]')) { // if (musicInfo.lrc.startsWith('\ufeff[id:$00000000]')) {
@ -198,7 +222,15 @@ const actions = {
// commit('setLrc', { musicInfo, lyric: str, tlyric: musicInfo.tlrc, lxlyric: musicInfo.tlrc }) // commit('setLrc', { musicInfo, lyric: str, tlyric: musicInfo.tlrc, lxlyric: musicInfo.tlrc })
// } // }
if ((lrcInfo.lxlyric == null && musicInfo.source != 'kg') || lrcInfo.lxlyric != null) return lrcInfo if (lrcInfo.lxlyric == null) {
switch (musicInfo.source) {
case 'kg':
case 'kw':
break
default:
return lrcInfo
}
} else return lrcInfo
} }
// lrcRequest = music[musicInfo.source].getLyric(musicInfo) // lrcRequest = music[musicInfo.source].getLyric(musicInfo)
@ -212,7 +244,7 @@ const actions = {
}) })
}, },
async playPrev({ state, rootState, commit, getters }) { async playPrev({ state, rootState, commit, getters }, { findNum = 0, excludeList = [] } = {}) {
const currentListId = playInfo.playListId const currentListId = playInfo.playListId
const currentList = getList(currentListId) const currentList = getList(currentListId)
if (playedList.length) { if (playedList.length) {
@ -242,11 +274,13 @@ const actions = {
} }
} }
const isCheckFile = findNum > 2
let filteredList = await filterList({ let filteredList = await filterList({
listInfo: { id: currentListId, list: currentList }, listInfo: { id: currentListId, list: currentList },
playedList, playedList: excludeList.length ? [...playedList, ...excludeList] : playedList,
savePath: rootState.setting.download.savePath, savePath: rootState.setting.download.savePath,
commit, commit,
isCheckFile,
}) })
if (!filteredList.length) return commit('setPlayMusicInfo', { listId: null, musicInfo: null }) if (!filteredList.length) return commit('setPlayMusicInfo', { listId: null, musicInfo: null })
@ -271,14 +305,24 @@ const actions = {
if (nextIndex < 0) return if (nextIndex < 0) return
} }
commit('setPlayMusicInfo', { const nextPlayMusicInfo = {
musicInfo: filteredList[nextIndex], musicInfo: filteredList[nextIndex],
listId: currentListId, listId: currentListId,
}) }
if (currentListId == 'download' && !isCheckFile) {
if (!await checkPath(path.join(rootState.setting.download.savePath, nextPlayMusicInfo.musicInfo.metadata.fileName)) || !nextPlayMusicInfo.musicInfo.isComplate || /\.ape$/.test(nextPlayMusicInfo.musicInfo.metadata.fileName)) {
excludeList.push(nextPlayMusicInfo)
// console.log('findNum', findNum)
return this.dispatch('player/playPrev', { findNum: findNum + 1, excludeList })
}
}
commit('setPlayMusicInfo', nextPlayMusicInfo)
playMusic() playMusic()
}, },
async playNext({ state, rootState, commit, getters }) { async playNext({ state, rootState, commit, getters }, { findNum = 0, excludeList = [] } = {}) {
if (tempPlayList.length) { if (tempPlayList.length) { // 如果稍后播放列表存在歌曲则直接播放改列表的歌曲
const playMusicInfo = tempPlayList[0] const playMusicInfo = tempPlayList[0]
commit('removeTempPlayList', 0) commit('removeTempPlayList', 0)
commit('setPlayMusicInfo', playMusicInfo) commit('setPlayMusicInfo', playMusicInfo)
@ -290,7 +334,7 @@ const actions = {
const currentListId = playInfo.playListId const currentListId = playInfo.playListId
const currentList = getList(currentListId) const currentList = getList(currentListId)
if (playedList.length) { if (playedList.length) { // 移除已播放列表内不存在原列表的歌曲
let currentSongmid let currentSongmid
if (playMusicInfo.isTempPlay) { if (playMusicInfo.isTempPlay) {
const musicInfo = currentList[playInfo.listPlayIndex] const musicInfo = currentList[playInfo.listPlayIndex]
@ -316,11 +360,13 @@ const actions = {
return return
} }
} }
let filteredList = await filterList({ const isCheckFile = findNum > 2 // 针对下载列表,如果超过两次都碰到无效歌曲,则过滤整个列表内的无效歌曲
let filteredList = await filterList({ // 过滤已播放歌曲
listInfo: { id: currentListId, list: currentList }, listInfo: { id: currentListId, list: currentList },
playedList, playedList: excludeList.length ? [...playedList, ...excludeList] : playedList,
savePath: rootState.setting.download.savePath, savePath: rootState.setting.download.savePath,
commit, commit,
isCheckFile,
}) })
if (!filteredList.length) return commit('setPlayMusicInfo', { listId: null, musicInfo: null }) if (!filteredList.length) return commit('setPlayMusicInfo', { listId: null, musicInfo: null })
@ -346,10 +392,19 @@ const actions = {
} }
if (nextIndex < 0) return if (nextIndex < 0) return
commit('setPlayMusicInfo', { const nextPlayMusicInfo = {
musicInfo: filteredList[nextIndex], musicInfo: filteredList[nextIndex],
listId: currentListId, listId: currentListId,
}) }
if (currentListId == 'download' && !isCheckFile) { // 针对下载列表,检查文件是否可以播放
if (!await checkPath(path.join(rootState.setting.download.savePath, nextPlayMusicInfo.musicInfo.metadata.fileName)) || !nextPlayMusicInfo.musicInfo.isComplate || /\.ape$/.test(nextPlayMusicInfo.musicInfo.metadata.fileName)) {
excludeList.push(nextPlayMusicInfo)
// console.log('findNum', findNum)
return this.dispatch('player/playNext', { findNum: findNum + 1, excludeList })
}
}
commit('setPlayMusicInfo', nextPlayMusicInfo)
playMusic() playMusic()
}, },
} }

View File

@ -1,5 +1,6 @@
import music from '../../utils/music' import music from '../../utils/music'
import { markRawList } from '@renderer/utils/vueTools' import { markRawList } from '@renderer/utils/vueTools'
import { deduplicationList } from '@renderer/utils'
const sources = [] const sources = []
const sourceList = {} const sourceList = {}
@ -85,7 +86,7 @@ sources.push({
}) })
// state // state
const state = { const state = window.state = {
sourceList, sourceList,
list: [], list: [],
text: '', text: '',
@ -151,6 +152,7 @@ const mutations = {
}, },
setList(state, datas) { setList(state, datas) {
let source = state.sourceList[datas.source] let source = state.sourceList[datas.source]
datas.list = deduplicationList(datas.list)
source.list = markRawList(datas.list) source.list = markRawList(datas.list)
source.total = datas.total source.total = datas.total
source.allPage = datas.allPage source.allPage = datas.allPage
@ -170,6 +172,7 @@ const mutations = {
total += source.total total += source.total
// limit = Math.max(source.limit, limit) // limit = Math.max(source.limit, limit)
} }
list = deduplicationList(list)
state.allPage = Math.max(...pages) state.allPage = Math.max(...pages)
state.total = total state.total = total
state.limit = limit state.limit = limit

View File

@ -1,5 +1,6 @@
import music from '../../utils/music' import music from '../../utils/music'
import { markRawList } from '@renderer/utils/vueTools' import { markRawList } from '@renderer/utils/vueTools'
import { deduplicationList } from '@renderer/utils'
const sortList = {} const sortList = {}
const sources = [] const sources = []
@ -91,6 +92,7 @@ const actions = {
? Promise.resolve(cache.get(key)) ? Promise.resolve(cache.get(key))
: music[source]?.songList.getListDetail(id, page).then(result => ({ ...result, list: filterList(result.list) })) : music[source]?.songList.getListDetail(id, page).then(result => ({ ...result, list: filterList(result.list) }))
).then(result => { ).then(result => {
result.list = markRawList(deduplicationList(result.list))
commit('setListDetail', { result, key, source, id, page }) commit('setListDetail', { result, key, source, id, page })
return result.list return result.list
}) })
@ -103,9 +105,10 @@ const actions = {
return cache.has(key) return cache.has(key)
? Promise.resolve(cache.get(key)) ? Promise.resolve(cache.get(key))
: music[source]?.songList.getListDetail(id, page).then(result => { : music[source]?.songList.getListDetail(id, page).then(result => {
result.list = markRawList(deduplicationList(result.list))
cache.set(key, result) cache.set(key, result)
return result return result
}) }) ?? Promise.reject(new Error('source not found'))
} }
return loadData(id, 1).then(result => { return loadData(id, 1).then(result => {
if (result.total <= result.limit) return filterList(result.list) if (result.total <= result.limit) return filterList(result.list)
@ -117,7 +120,7 @@ const actions = {
: loadData(id, loadPage).then(result1 => loadDetail(++loadPage).then(result2 => [...result1.list, ...result2])) : loadData(id, loadPage).then(result1 => loadDetail(++loadPage).then(result2 => [...result1.list, ...result2]))
} }
return loadDetail().then(result2 => [...result.list, ...result2]).then(list => filterList(list)) return loadDetail().then(result2 => [...result.list, ...result2]).then(list => filterList(list))
}) }).then(list => deduplicationList(list))
}, },
} }
@ -139,7 +142,7 @@ const mutations = {
cache.set(key, result) cache.set(key, result)
}, },
setListDetail(state, { result, key, source, id, page }) { setListDetail(state, { result, key, source, id, page }) {
state.listDetail.list = markRawList(result.list) state.listDetail.list = result.list
state.listDetail.id = id state.listDetail.id = id
state.listDetail.source = source state.listDetail.source = source
state.listDetail.total = result.total state.listDetail.total = result.total

View File

@ -1,6 +1,6 @@
export default { export default {
setTheme(state, val) { setTheme(state, val) {
state.setting.themeId = val state.setting.theme.id = val
}, },
setSearchSource(state, { searchSource, tempSearchSource }) { setSearchSource(state, { searchSource, tempSearchSource }) {
console.log(searchSource, tempSearchSource) console.log(searchSource, tempSearchSource)
@ -35,6 +35,9 @@ export default {
state.setting.player.volume = val state.setting.player.volume = val
} }
}, },
setPlayDetailLyricFont(state, val) {
state.setting.playDetail.style.fontSize = val
},
setPlayNextMode(state, val) { setPlayNextMode(state, val) {
state.setting.player.togglePlayMethod = val state.setting.player.togglePlayMethod = val
}, },

View File

@ -1,18 +1,75 @@
import { ref, onMounted, onBeforeUnmount, watch, nextTick } from '@renderer/utils/vueTools' import { ref, onMounted, onBeforeUnmount, watch, nextTick } from '@renderer/utils/vueTools'
import { scrollTo } from '@renderer/utils' import { scrollTo, throttle, formatPlayTime2 } from '@renderer/utils'
import { player as eventPlayerNames } from '@renderer/event/names'
export default ({ isPlay, lyric }) => { export default ({ isPlay, lyric }) => {
const dom_lyric = ref(null) const dom_lyric = ref(null)
const dom_lyric_text = ref(null) const dom_lyric_text = ref(null)
const dom_skip_line = ref(null)
const isMsDown = ref(false) const isMsDown = ref(false)
const isStopScroll = ref(false)
const timeStr = ref('--/--')
let msDownY = 0 let msDownY = 0
let msDownScrollY = 0 let msDownScrollY = 0
let isStopScroll = false
let timeout = null let timeout = null
let cancelScrollFn let cancelScrollFn
let dom_lines let dom_lines
let isSetedLines = false let isSetedLines = false
let point = {
x: null,
y: null,
}
let time = -1
let dom_pre_line = null
let isSkipMouseEnter = false
const handleSkipPlay = () => {
if (time == -1) return
handleSkipMouseLeave()
isStopScroll.value = false
window.eventHub.emit(eventPlayerNames.setProgress, time)
if (!isPlay.value) window.eventHub.emit(eventPlayerNames.setPlay)
}
const handleSkipMouseEnter = () => {
isSkipMouseEnter = true
clearLyricScrollTimeout()
}
const handleSkipMouseLeave = () => {
isSkipMouseEnter = false
startLyricScrollTimeout()
}
const setTime = throttle(() => {
if (point.x == null) {
if (!dom_skip_line.value) return
const rect = dom_skip_line.value.getBoundingClientRect()
point.x = rect.x
point.y = rect.y
}
let dom = document.elementFromPoint(point.x, point.y)
if (dom_pre_line === dom) return
if (dom.tagName == 'SPAN') {
dom = dom.parentNode.parentNode
} else if (dom.classList.contains('font')) {
dom = dom.parentNode
}
if (dom.time == null) {
if (lyric.lines.length) {
time = dom.classList.contains('pre') ? 0 : lyric.lines[lyric.lines.length - 1].time ?? 0
if (time) time = time / 1000
timeStr.value = formatPlayTime2(time)
} else {
time = -1
timeStr.value = '--:--'
}
} else {
time = dom.time
if (time) time = time / 1000
timeStr.value = formatPlayTime2(time)
}
dom_pre_line = dom
})
const handleScrollLrc = (duration = 300) => { const handleScrollLrc = (duration = 300) => {
if (!dom_lines?.length || !dom_lyric.value) return if (!dom_lines?.length || !dom_lyric.value) return
@ -20,7 +77,8 @@ export default ({ isPlay, lyric }) => {
cancelScrollFn() cancelScrollFn()
cancelScrollFn = null cancelScrollFn = null
} }
if (isStopScroll) return if (isSkipMouseEnter) return
if (isStopScroll.value) return
let dom_p = dom_lines[lyric.line] let dom_p = dom_lines[lyric.line]
cancelScrollFn = scrollTo(dom_lyric.value, dom_p ? (dom_p.offsetTop - dom_lyric.value.clientHeight * 0.38) : 0, duration) cancelScrollFn = scrollTo(dom_lyric.value, dom_p ? (dom_p.offsetTop - dom_lyric.value.clientHeight * 0.38) : 0, duration)
} }
@ -31,9 +89,10 @@ export default ({ isPlay, lyric }) => {
} }
const startLyricScrollTimeout = () => { const startLyricScrollTimeout = () => {
clearLyricScrollTimeout() clearLyricScrollTimeout()
if (isSkipMouseEnter) return
timeout = setTimeout(() => { timeout = setTimeout(() => {
timeout = null timeout = null
isStopScroll = false isStopScroll.value = false
if (!isPlay.value) return if (!isPlay.value) return
handleScrollLrc() handleScrollLrc()
}, 3000) }, 3000)
@ -53,25 +112,27 @@ export default ({ isPlay, lyric }) => {
} }
const handleMouseMsMove = event => { const handleMouseMsMove = event => {
if (isMsDown.value) { if (isMsDown.value) {
if (!isStopScroll) isStopScroll = true if (!isStopScroll.value) isStopScroll.value = true
if (cancelScrollFn) { if (cancelScrollFn) {
cancelScrollFn() cancelScrollFn()
cancelScrollFn = null cancelScrollFn = null
} }
dom_lyric.value.scrollTop = msDownScrollY + msDownY - event.clientY dom_lyric.value.scrollTop = msDownScrollY + msDownY - event.clientY
startLyricScrollTimeout() startLyricScrollTimeout()
setTime()
} }
} }
const handleWheel = (event) => { const handleWheel = (event) => {
console.log(event.deltaY) console.log(event.deltaY)
if (!isStopScroll) isStopScroll = true if (!isStopScroll.value) isStopScroll.value = true
if (cancelScrollFn) { if (cancelScrollFn) {
cancelScrollFn() cancelScrollFn()
cancelScrollFn = null cancelScrollFn = null
} }
dom_lyric.value.scrollTop = dom_lyric.value.scrollTop + event.deltaY dom_lyric.value.scrollTop = dom_lyric.value.scrollTop + event.deltaY
startLyricScrollTimeout() startLyricScrollTimeout()
setTime()
} }
const setLyric = (lines) => { const setLyric = (lines) => {
@ -139,8 +200,14 @@ export default ({ isPlay, lyric }) => {
return { return {
dom_lyric, dom_lyric,
dom_lyric_text, dom_lyric_text,
dom_skip_line,
isStopScroll,
isMsDown, isMsDown,
timeStr,
handleLyricMouseDown, handleLyricMouseDown,
handleWheel, handleWheel,
handleSkipPlay,
handleSkipMouseEnter,
handleSkipMouseLeave,
} }
} }

View File

@ -560,3 +560,24 @@ export const saveStrToFile = (path, str) => new Promise((resolve, reject) => {
const fileNameRxp = /[\\/:*?#"<>|]/g const fileNameRxp = /[\\/:*?#"<>|]/g
export const filterFileName = name => name.replace(fileNameRxp, '') export const filterFileName = name => name.replace(fileNameRxp, '')
export const getFontSizeWithScreen = (screenWidth = window.innerWidth) => {
return screenWidth <= 1440
? 16
: screenWidth <= 1920
? 18
: screenWidth <= 2560
? 20
: screenWidth <= 2560 ? 20 : 22
}
export const deduplicationList = list => {
const ids = new Set()
return list.filter(s => {
if (ids.has(s.songmid)) return false
ids.add(s.songmid)
return true
})
}

View File

@ -20,7 +20,8 @@ const createAnimation = (dom, duration) => new window.Animation(new window.Keyfr
// https://jsfiddle.net/ceqpnbky/1/ // https://jsfiddle.net/ceqpnbky/1/
module.exports = class FontPlayer { module.exports = class FontPlayer {
constructor({ lyric = '', translationLyric = '', lineClassName = '', fontClassName = '', translationClassName = '', lineModeClassName = '', shadowContent = false, shadowClassName = '' }) { constructor({ time = 0, lyric = '', translationLyric = '', lineClassName = '', fontClassName = '', translationClassName = '', lineModeClassName = '', shadowContent = false, shadowClassName = '' }) {
this.time = time
this.lyric = lyric this.lyric = lyric
this.translationLyric = translationLyric this.translationLyric = translationLyric
@ -35,7 +36,7 @@ module.exports = class FontPlayer {
this.curFontNum = 0 this.curFontNum = 0
this.maxFontNum = 0 this.maxFontNum = 0
this._performanceTime = 0 this._performanceTime = 0
this._performanceOffsetTime = 0 this._startTime = 0
this.fontContent = null this.fontContent = null
@ -51,6 +52,7 @@ module.exports = class FontPlayer {
this.isLineMode = false this.isLineMode = false
this.lineContent = document.createElement('div') this.lineContent = document.createElement('div')
this.lineContent.time = this.time
if (this.lineClassName) this.lineContent.classList.add(this.lineClassName) if (this.lineClassName) this.lineContent.classList.add(this.lineClassName)
this.fontContent = document.createElement('div') this.fontContent = document.createElement('div')
this.fontContent.style = 'position:relative;display:inline-block;' this.fontContent.style = 'position:relative;display:inline-block;'
@ -139,12 +141,12 @@ module.exports = class FontPlayer {
} }
_currentTime() { _currentTime() {
return getNow() - this._performanceTime + this._performanceOffsetTime return getNow() - this._performanceTime + this._startTime
} }
_findcurFontNum(curTime) { _findcurFontNum(curTime, startIndex = 0) {
const length = this.fonts.length const length = this.fonts.length
for (let index = 0; index < length; index++) if (curTime <= this.fonts[index].startTime) return index === 0 ? 0 : index - 1 for (let index = startIndex; index < length; index++) if (curTime < this.fonts[index].startTime) return index == 0 ? 0 : index - 1
return length - 1 return length - 1
} }
@ -193,9 +195,8 @@ module.exports = class FontPlayer {
_refresh() { _refresh() {
this.curFontNum++ this.curFontNum++
// console.log('curFontNum time', this.fonts[this.curFontNum].time) // console.log('curFontNum time', this.fonts[this.curFontNum].time)
if (this.curFontNum === this.maxFontNum) return this._handlePlayMaxFontNum() if (this.curFontNum >= this.maxFontNum) return this._handlePlayMaxFontNum()
let curFont = this.fonts[this.curFontNum] let curFont = this.fonts[this.curFontNum]
let nextFont = this.fonts[this.curFontNum + 1]
// console.log(curFont, nextFont, this.curFontNum, this.maxFontNum) // console.log(curFont, nextFont, this.curFontNum, this.maxFontNum)
const currentTime = this._currentTime() const currentTime = this._currentTime()
// console.log(curFont.text) // console.log(curFont.text)
@ -204,27 +205,38 @@ module.exports = class FontPlayer {
// console.log(currentTime, driftTime) // console.log(currentTime, driftTime)
if (driftTime >= 0 || this.curFontNum == 0) { if (driftTime >= 0 || this.curFontNum == 0) {
let nextFont = this.fonts[this.curFontNum + 1]
this.delay = nextFont.startTime - curFont.startTime - driftTime this.delay = nextFont.startTime - curFont.startTime - driftTime
if (this.delay > 0) { if (this.delay > 0) {
if (this.isPlay) {
this.timeoutTools.start(() => {
if (!this.isPlay) return
this._refresh()
}, this.delay)
}
this._handlePlayFont(curFont, driftTime) this._handlePlayFont(curFont, driftTime)
this.timeoutTools.start(() => { return
if (!this.isPlay) return } else {
this._refresh() let newCurLineNum = this._findcurFontNum(currentTime, this.curFontNum + 1)
}, this.delay) if (newCurLineNum > this.curFontNum) this.curFontNum = newCurLineNum - 1
for (let i = 0; i <= this.curFontNum; i++) this._handlePlayFont(this.fonts[i], 0, true)
this._refresh()
return return
} }
} else if (this.curFontNum == 0) { } else if (this.curFontNum == 0) {
this.curFontNum-- this.curFontNum--
this.waitPlayTimeout.start(() => { if (this.isPlay) {
if (!this.isPlay) return this.waitPlayTimeout.start(() => {
this._refresh() if (!this.isPlay) return
}, -driftTime) this._refresh()
}, -driftTime)
}
return return
} }
this.curFontNum = this._findcurFontNum(currentTime) this.curFontNum = this._findcurFontNum(currentTime, this.curFontNum) - 1
for (let i = 0; i < this.curFontNum; i++) this._handlePlayFont(this.fonts[i], 0, true) for (let i = 0; i <= this.curFontNum; i++) this._handlePlayFont(this.fonts[i], 0, true)
this.curFontNum-- // this.curFontNum--
this._refresh() this._refresh()
} }
@ -235,14 +247,11 @@ module.exports = class FontPlayer {
if (this.isLineMode) return this._handlePlayLine(true) if (this.isLineMode) return this._handlePlayLine(true)
this.isPlay = true this.isPlay = true
this._performanceTime = getNow() - curTime this._performanceTime = getNow()
this._performanceOffsetTime = 0 this._startTime = curTime
if (this._performanceTime < 0) {
this._performanceOffsetTime = -this._performanceTime
this._performanceTime = 0
}
this.curFontNum = this._findcurFontNum(curTime) this.curFontNum = this._findcurFontNum(curTime)
for (let i = this.curFontNum; i > -1; i--) { for (let i = this.curFontNum; i > -1; i--) {
this._handlePlayFont(this.fonts[i], 0, true) this._handlePlayFont(this.fonts[i], 0, true)
} }

View File

@ -7,7 +7,7 @@ module.exports = class Lyric {
constructor({ constructor({
lyric = '', lyric = '',
translationLyric = '', translationLyric = '',
offset = 150, offset = 0,
lineClassName = '', lineClassName = '',
fontClassName = 'font', fontClassName = 'font',
translationClassName = 'translation', translationClassName = 'translation',
@ -61,7 +61,7 @@ module.exports = class Lyric {
font.reset() font.reset()
font.lineContent.classList.remove(this.activeLineClassName) font.lineContent.classList.remove(this.activeLineClassName)
} }
} else if (num > this.playingLineNum + 1) { } else if (num > this.playingLineNum) {
for (let i = Math.max(this.playingLineNum, 0); i < num; i++) { for (let i = Math.max(this.playingLineNum, 0); i < num; i++) {
const font = this._lineFonts[i] const font = this._lineFonts[i]
font.reset() font.reset()
@ -79,7 +79,7 @@ module.exports = class Lyric {
font.lineContent.classList.remove(this.activeLineClassName) font.lineContent.classList.remove(this.activeLineClassName)
font.reset() font.reset()
} }
} else if (num > this.playingLineNum + 1) { } else if (num > this.playingLineNum) {
for (let i = Math.max(this.playingLineNum, 0); i < num; i++) { for (let i = Math.max(this.playingLineNum, 0); i < num; i++) {
const font = this._lineFonts[i] const font = this._lineFonts[i]
font.lineContent.classList.remove(this.activeLineClassName) font.lineContent.classList.remove(this.activeLineClassName)
@ -106,6 +106,7 @@ module.exports = class Lyric {
if (this.isLineMode) { if (this.isLineMode) {
this._lines = lyricLines.map(line => { this._lines = lyricLines.map(line => {
const fontPlayer = new FontPlayer({ const fontPlayer = new FontPlayer({
time: line.time,
lyric: line.text, lyric: line.text,
translationLyric: line.translation, translationLyric: line.translation,
lineClassName: this.lineClassName, lineClassName: this.lineClassName,
@ -127,6 +128,7 @@ module.exports = class Lyric {
} else { } else {
this._lines = lyricLines.map(line => { this._lines = lyricLines.map(line => {
const fontPlayer = new FontPlayer({ const fontPlayer = new FontPlayer({
time: line.time,
lyric: line.text, lyric: line.text,
translationLyric: line.translation, translationLyric: line.translation,
lineClassName: this.lineClassName, lineClassName: this.lineClassName,
@ -164,5 +166,6 @@ module.exports = class Lyric {
this.lyric = lyric this.lyric = lyric
this.translationLyric = translationLyric this.translationLyric = translationLyric
this._init() this._init()
this.linePlayer.offset = this.isLineMode ? this.offset + 90 : this.offset
} }
} }

View File

@ -24,9 +24,8 @@ module.exports = class LinePlayer {
this.curLineNum = 0 this.curLineNum = 0
this.maxLine = 0 this.maxLine = 0
this.offset = offset this.offset = offset
this.isOffseted = false
this._performanceTime = 0 this._performanceTime = 0
this._performanceOffsetTime = 0 this._startTime = 0
this._init() this._init()
} }
@ -39,10 +38,17 @@ module.exports = class LinePlayer {
} }
_initTag() { _initTag() {
this.tags = {}
for (let tag in tagRegMap) { for (let tag in tagRegMap) {
const matches = this.lyric.match(new RegExp(`\\[${tagRegMap[tag]}:([^\\]]*)]`, 'i')) const matches = this.lyric.match(new RegExp(`\\[${tagRegMap[tag]}:([^\\]]*)]`, 'i'))
this.tags[tag] = (matches && matches[1]) || '' this.tags[tag] = (matches && matches[1]) || ''
} }
if (this.tags.offset) {
let offset = parseInt(this.tags.offset)
this.tags.offset = Number.isNaN(offset) ? 0 : offset
} else {
this.tags.offset = 0
}
} }
_initLines() { _initLines() {
@ -93,12 +99,13 @@ module.exports = class LinePlayer {
} }
_currentTime() { _currentTime() {
return getNow() - this._performanceTime + this._performanceOffsetTime return getNow() - this._performanceTime + this._startTime
} }
_findCurLineNum(curTime) { _findCurLineNum(curTime, startIndex = 0) {
if (curTime <= 0) return 0
const length = this.lines.length const length = this.lines.length
for (let index = 0; index < length; index++) if (curTime <= this.lines[index].time) return index === 0 ? 0 : index - 1 for (let index = startIndex; index < length; index++) if (curTime <= this.lines[index].time) return index === 0 ? 0 : index - 1
return length - 1 return length - 1
} }
@ -110,30 +117,35 @@ module.exports = class LinePlayer {
_refresh() { _refresh() {
this.curLineNum++ this.curLineNum++
// console.log('curLineNum time', this.lines[this.curLineNum].time) // console.log('curLineNum time', this.lines[this.curLineNum].time)
if (this.curLineNum >= this.maxLine) return this._handleMaxLine()
let curLine = this.lines[this.curLineNum] let curLine = this.lines[this.curLineNum]
let nextLine = this.lines[this.curLineNum + 1]
const currentTime = this._currentTime() const currentTime = this._currentTime()
const driftTime = currentTime - curLine.time const driftTime = currentTime - curLine.time
if (driftTime >= 0 || this.curLineNum === 0) { if (driftTime >= 0 || this.curLineNum === 0) {
if (this.curLineNum === this.maxLine) return this._handleMaxLine() let nextLine = this.lines[this.curLineNum + 1]
this.delay = nextLine.time - curLine.time - driftTime this.delay = nextLine.time - curLine.time - driftTime
if (this.delay > 0) { if (this.delay > 0) {
if (!this.isOffseted && this.delay >= this.offset) { if (this.isPlay) {
this._performanceOffsetTime += this.offset timeoutTools.start(() => {
this.delay -= this.offset if (!this.isPlay) return
this.isOffseted = true this._refresh()
}, this.delay)
} }
timeoutTools.start(() => {
if (!this.isPlay) return
this._refresh()
}, this.delay)
this.onPlay(this.curLineNum, curLine.text, currentTime) this.onPlay(this.curLineNum, curLine.text, currentTime)
return return
} else {
let newCurLineNum = this._findCurLineNum(currentTime, this.curLineNum + 1)
if (newCurLineNum > this.curLineNum) this.curLineNum = newCurLineNum - 1
this._refresh()
return
} }
} }
this.curLineNum = this._findCurLineNum(currentTime) - 1 this.curLineNum = this._findCurLineNum(currentTime, this.curLineNum) - 1
this._refresh() this._refresh()
} }
@ -142,14 +154,10 @@ module.exports = class LinePlayer {
this.pause() this.pause()
this.isPlay = true this.isPlay = true
this._performanceOffsetTime = 0 this._performanceTime = getNow() - parseInt(this.tags.offset + this.offset)
this._performanceTime = getNow() - curTime this._startTime = curTime
if (this._performanceTime < 0) {
this._performanceOffsetTime = -this._performanceTime
this._performanceTime = 0
}
this.curLineNum = this._findCurLineNum(curTime) - 1 this.curLineNum = this._findCurLineNum(this._currentTime()) - 1
this._refresh() this._refresh()
} }
@ -157,7 +165,6 @@ module.exports = class LinePlayer {
pause() { pause() {
if (!this.isPlay) return if (!this.isPlay) return
this.isPlay = false this.isPlay = false
this.isOffseted = false
timeoutTools.clear() timeoutTools.clear()
if (this.curLineNum === this.maxLine) return if (this.curLineNum === this.maxLine) return
const currentTime = this._currentTime() const currentTime = this._currentTime()

View File

@ -720,7 +720,10 @@ export default {
}, },
getDetailPageUrl(id) { getDetailPageUrl(id) {
if (typeof id == 'string') id = id.replace('id_', '') if (typeof id == 'string') {
if (/^https?:\/\//.test(id)) return id
id = id.replace('id_', '')
}
return `https://www.kugou.com/yy/special/single/${id}.html` return `https://www.kugou.com/yy/special/single/${id}.html`
}, },
} }

View File

@ -40,7 +40,7 @@ const kw = {
comment, comment,
getLyric(songInfo, isGetLyricx) { getLyric(songInfo, isGetLyricx) {
// let singer = songInfo.singer.indexOf('、') > -1 ? songInfo.singer.split('、')[0] : songInfo.singer // let singer = songInfo.singer.indexOf('、') > -1 ? songInfo.singer.split('、')[0] : songInfo.singer
return lyric.getLyric(songInfo.songmid, isGetLyricx) return lyric.getLyric(songInfo, isGetLyricx)
}, },
handleMusicInfo(songInfo) { handleMusicInfo(songInfo) {
return this.getMusicInfo(songInfo).then(info => { return this.getMusicInfo(songInfo).then(info => {

View File

@ -1,6 +1,8 @@
import { httpFetch } from '../../request' import { httpFetch } from '../../request'
import { decodeLyric, lrcTools } from './util'
import { decodeName } from '../../index' import { decodeName } from '../../index'
/*
export default { export default {
formatTime(time) { formatTime(time) {
let m = parseInt(time / 60) let m = parseInt(time / 60)
@ -63,33 +65,146 @@ export default {
return requestObj return requestObj
}, },
} }
*/
const buf_key = Buffer.from('yeelion')
const buf_key_len = buf_key.length
const buildParams = (id, isGetLyricx) => {
let params = `user=12345,web,web,web&requester=localhost&req=1&rid=MUSIC_${id}`
if (isGetLyricx) params += '&lrcx=1'
const buf_str = Buffer.from(params)
const buf_str_len = buf_str.length
const output = new Uint16Array(buf_str_len)
let i = 0
while (i < buf_str_len) {
let j = 0
while (j < buf_key_len && i < buf_str_len) {
output[i] = buf_key[j] ^ buf_str[i]
i++
j++
}
}
return Buffer.from(output).toString('base64')
}
/* export default { // console.log(buildParams('207527604', false))
lrcInfoRxp: /<lyric>(.+?)<\/lyric>[\s\S]+<lyric_zz>(.+?)<\/lyric_zz>/, // console.log(buildParams('207527604', true))
parseLyricInfo(str) {
let result = str.match(this.lrcInfoRxp) const timeExp = /^\[([\d:.]*)\]{1}/g
return result ? { lyric: result[1], lyric_zz: result[2] } : null export default {
sortLrcArr(arr) {
const lrcSet = new Set()
let lrc = []
let lrcT = []
for (const item of arr) {
if (lrcSet.has(item.time)) {
if (lrc.length < 2) continue
const tItem = lrc.pop()
tItem.time = lrc[lrc.length - 1].time
lrcT.push(tItem)
lrc.push(item)
} else {
lrc.push(item)
lrcSet.add(item.time)
}
}
if (lrcT.length) {
if (lrc.length * 0.4 < lrcT.length) { // 翻译数量需大于歌词数量的0.4倍,否则认为没有翻译
const tItem = lrc.pop()
tItem.time = lrc[lrc.length - 1].time
lrcT.push(tItem)
} else {
lrc = arr
lrcT = []
}
}
return {
lrc,
lrcT,
}
}, },
getLyric(songId, isGetLyricx = false) { transformLrc(tags, lrclist) {
const requestObj = httpFetch(`http://player.kuwo.cn/webmusic/st/getNewMuiseByRid?rid=MUSIC_${songId}`) return `${tags.join('\n')}\n${lrclist ? lrclist.map(l => `[${l.time}]${l.text}\n`).join('') : '暂无歌词'}`
requestObj.promise = requestObj.promise.then(({ statusCode, body }) => { },
console.log(body) parseLrc(lrc) {
if (statusCode != 200) return Promise.reject(new Error(JSON.stringify(body))) const lines = lrc.split(/\r\n|\r|\n/)
let info = this.parseLyricInfo(body) let tags = []
if (!info) return Promise.reject(new Error(JSON.stringify(body))) let lrcArr = []
Object.assign(requestObj, httpFetch(`http://newlyric.kuwo.cn/newlyric.lrc?${isGetLyricx ? info.lyric_zz : info.lyric}`)) for (let i = 0; i < lines.length; i++) {
return requestObj.promise.then(({ statusCode, body, raw }) => { const line = lines[i].trim()
if (statusCode != 200) return Promise.reject(new Error(JSON.stringify(body))) let result = timeExp.exec(line)
return decodeLyric({ lrcBase64: raw.toString('base64'), isGetLyricx }).then(base64Data => { if (result) {
return { const text = line.replace(timeExp, '').trim()
lyric: Buffer.from(base64Data, 'base64').toString(), lrcArr.push({
tlyric: '', time: RegExp.$1,
} text,
}) })
} else if (lrcTools.rxps.tagLine.test(line)) {
tags.push(line)
}
}
const lrcInfo = this.sortLrcArr(lrcArr)
return {
lyric: decodeName(this.transformLrc(tags, lrcInfo.lrc)),
tlyric: lrcInfo.lrcT.length ? decodeName(this.transformLrc(tags, lrcInfo.lrcT)) : '',
}
},
// getLyric2(musicInfo, isGetLyricx = true) {
// const requestObj = httpFetch(`http://newlyric.kuwo.cn/newlyric.lrc?${buildParams(musicInfo.songmid, isGetLyricx)}`)
// requestObj.promise = requestObj.promise.then(({ statusCode, body, raw }) => {
// if (statusCode != 200) return Promise.reject(new Error(JSON.stringify(body)))
// return decodeLyric({ lrcBase64: raw.toString('base64'), isGetLyricx }).then(base64Data => {
// let lrcInfo
// console.log(Buffer.from(base64Data, 'base64').toString())
// try {
// lrcInfo = this.parseLrc(Buffer.from(base64Data, 'base64').toString())
// } catch {
// return Promise.reject(new Error('Get lyric failed'))
// }
// if (lrcInfo.tlyric) lrcInfo.tlyric = lrcInfo.tlyric.replace(lrcTools.rxps.wordTimeAll, '')
// lrcInfo.lxlyric = lrcTools.parse(lrcInfo.lyric)
// // console.log(lrcInfo.lyric)
// // console.log(lrcInfo.tlyric)
// // console.log(lrcInfo.lxlyric)
// // console.log(JSON.stringify(lrcInfo))
// })
// })
// return requestObj
// },
getLyric(musicInfo, isGetLyricx = true) {
// this.getLyric2(musicInfo)
const requestObj = httpFetch(`http://newlyric.kuwo.cn/newlyric.lrc?${buildParams(musicInfo.songmid, isGetLyricx)}`)
requestObj.promise = requestObj.promise.then(({ statusCode, body, raw }) => {
if (statusCode != 200) return Promise.reject(new Error(JSON.stringify(body)))
return decodeLyric({ lrcBase64: raw.toString('base64'), isGetLyricx }).then(base64Data => {
// let lrcInfo
// try {
// lrcInfo = this.parseLrc(Buffer.from(base64Data, 'base64').toString())
// } catch {
// return Promise.reject(new Error('Get lyric failed'))
// }
let lrcInfo
// console.log(Buffer.from(base64Data, 'base64').toString())
try {
lrcInfo = this.parseLrc(Buffer.from(base64Data, 'base64').toString())
} catch (err) {
return Promise.reject(new Error('Get lyric failed'))
}
// console.log(lrcInfo)
if (lrcInfo.tlyric) lrcInfo.tlyric = lrcInfo.tlyric.replace(lrcTools.rxps.wordTimeAll, '')
try {
lrcInfo.lxlyric = lrcTools.parse(lrcInfo.lyric)
} catch {
lrcInfo.lxlyric = ''
}
lrcInfo.lyric = lrcInfo.lyric.replace(lrcTools.rxps.wordTimeAll, '')
// console.log(lrcInfo)
return lrcInfo
}) })
}) })
return requestObj return requestObj
}, },
} }
*/

View File

@ -308,7 +308,8 @@ export default {
return Promise.all([this.getTag(), this.getHotTag()]).then(([tags, hotTag]) => ({ tags, hotTag, source: 'kw' })) return Promise.all([this.getTag(), this.getHotTag()]).then(([tags, hotTag]) => ({ tags, hotTag, source: 'kw' }))
}, },
getDetailPageUrl(id) { getDetailPageUrl(id) {
if (/^digest-/.test(id)) { if ((/[?&:/]/.test(id))) id = id.replace(this.regExps.listDetailLink, '$1')
else if (/^digest-/.test(id)) {
let result = id.split('__') let result = id.split('__')
id = result[1] id = result[1]
} }

View File

@ -1,5 +1,5 @@
import { httpGet, httpFetch } from '../../request' import { httpGet, httpFetch } from '../../request'
// import { rendererInvoke, NAMES } from '../../../../common/ipc' import { rendererInvoke, NAMES } from '@common/ipc'
const kw_token = { const kw_token = {
token: null, token: null,
@ -54,7 +54,7 @@ export const getToken = (retryNum = 0) => new Promise((resolve, reject) => {
}) })
}) })
// export const decodeLyric = base64Data => rendererInvoke(NAMES.mainWindow.handle_kw_decode_lyric, base64Data) export const decodeLyric = base64Data => rendererInvoke(NAMES.mainWindow.handle_kw_decode_lyric, base64Data)
export const tokenRequest = async(url, options = {}) => { export const tokenRequest = async(url, options = {}) => {
let token = kw_token.token let token = kw_token.token
@ -76,3 +76,83 @@ export const tokenRequest = async(url, options = {}) => {
}) })
return requestObj return requestObj
} }
export const lrcTools = {
rxps: {
wordLine: /^(\[\d{1,2}:.*\d{1,4}\])\s*(\S+(?:\s+\S+)*)?\s*/,
tagLine: /\[(ver|ti|ar|al|offset|by|kuwo):\s*(\S+(?:\s+\S+)*)\s*\]/,
wordTimeAll: /<(-?\d+),(-?\d+)(?:,-?\d+)?>/g,
wordTime: /<(-?\d+),(-?\d+)(?:,-?\d+)?>/,
},
offset: 1,
offset2: 1,
isOK: false,
lines: [],
tags: [],
getWordInfo(str, str2) {
const offset = parseInt(str)
const offset2 = parseInt(str2)
const startTime = Math.floor((offset + offset2) / (this.offset * 2))
const timeLength = Math.floor((offset - offset2) / (this.offset2 * 2))
return {
startTime,
timeLength,
}
},
parseLine(line) {
if (line.length < 6) return
let result = this.rxps.wordLine.exec(line)
if (result) {
const time = result[1]
let words = result[2]
if (words == null) {
words = ''
}
const wordTimes = words.match(this.rxps.wordTimeAll)
if (!wordTimes) return
// console.log(wordTimes)
for (const timeStr of wordTimes) {
const result = this.rxps.wordTime.exec(timeStr)
const wordInfo = this.getWordInfo(result[1], result[2])
words = words.replace(timeStr, `<${wordInfo.startTime},${wordInfo.timeLength}>`)
}
this.lines.push(time + words)
return
}
result = this.rxps.tagLine.exec(line)
if (!result) return
if (result[1] == 'kuwo') {
let content = result[2]
if (content != null && content.includes('][')) {
content = content.substring(0, content.indexOf(']['))
}
const valueOf = parseInt(content, 8)
this.offset = Math.floor(valueOf / 10)
this.offset2 = Math.floor(valueOf % 10)
if (this.offset == 0 || Number.isNaN(this.offset) || this.offset2 == 0 || Number.isNaN(this.offset2)) {
this.isOK = false
}
} else {
this.tags.push(line)
}
},
parse(lrc) {
// console.log(lrc)
const lines = lrc.split(/\r\n|\r|\n/)
const tools = Object.create(this)
tools.isOK = true
tools.offset = 1
tools.offset2 = 1
tools.lines = []
tools.tags = []
for (const line of lines) {
if (!tools.isOK) return ''
tools.parseLine(line)
}
if (!tools.lines.length) return ''
let lrcs = tools.lines.join('\n')
if (tools.tags.length) lrcs = `${tools.tags.join('\n')}\n${lrcs}`
return lrcs
},
}

View File

@ -357,6 +357,11 @@ export default {
}, },
getDetailPageUrl(id) { getDetailPageUrl(id) {
if (/playlist\/index\.html\?/.test(id)) {
id = id.replace(/.*(?:\?|&)id=(\d+)(?:&.*|$)/, '$1')
} else if (this.regExps.listDetailLink.test(id)) {
id = id.replace(this.regExps.listDetailLink, '$1')
}
return `https://music.migu.cn/v3/music/playlist/${id}` return `https://music.migu.cn/v3/music/playlist/${id}`
}, },
} }

View File

@ -185,11 +185,7 @@ export default {
return location == null ? link : location return location == null ? link : location
}, },
// 获取歌曲列表内的音乐 async getListId(id) {
async getListDetail(id, tryNum = 0) {
if (this._requestObj_listDetail) this._requestObj_listDetail.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num'))
if ((/[?&:/]/.test(id))) { if ((/[?&:/]/.test(id))) {
let regx = /\/\/i\.y\.qq\.com/.test(id) ? this.regExps.listDetailLink1 : this.regExps.listDetailLink2 let regx = /\/\/i\.y\.qq\.com/.test(id) ? this.regExps.listDetailLink1 : this.regExps.listDetailLink2
if (!regx.test(id)) { if (!regx.test(id)) {
@ -200,6 +196,14 @@ export default {
id = id.replace(regx, '$1') id = id.replace(regx, '$1')
// console.log(id) // console.log(id)
} }
return id
},
// 获取歌曲列表内的音乐
async getListDetail(id, tryNum = 0) {
if (this._requestObj_listDetail) this._requestObj_listDetail.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num'))
id = await this.getListId(id)
this._requestObj_listDetail = httpFetch(this.getListDetailUrl(id), { this._requestObj_listDetail = httpFetch(this.getListDetailUrl(id), {
headers: { headers: {
@ -293,7 +297,9 @@ export default {
return Promise.all([this.getTag(), this.getHotTag()]).then(([tags, hotTag]) => ({ tags, hotTag, source: 'tx' })) return Promise.all([this.getTag(), this.getHotTag()]).then(([tags, hotTag]) => ({ tags, hotTag, source: 'tx' }))
}, },
getDetailPageUrl(id) { async getDetailPageUrl(id) {
id = await this.getListId(id)
return `https://y.qq.com/n/ryqq/playlist/${id}` return `https://y.qq.com/n/ryqq/playlist/${id}`
}, },
} }

View File

@ -51,23 +51,24 @@ export default {
async handleParseId(link, retryNum = 0) { async handleParseId(link, retryNum = 0) {
if (this._requestObj_listDetailLink) this._requestObj_listDetailLink.cancelHttp() if (this._requestObj_listDetailLink) this._requestObj_listDetailLink.cancelHttp()
if (retryNum > 2) return Promise.reject(new Error('link try max num')) if (retryNum > 2) throw new Error('link try max num')
this._requestObj_listDetailLink = httpFetch(link) this._requestObj_listDetailLink = httpFetch(link)
const { headers: { location }, statusCode } = await this._requestObj_listDetailLink.promise const { headers: { location }, statusCode } = await this._requestObj_listDetailLink.promise
// console.log(headers) // console.log(headers)
if (statusCode > 400) return this.handleParseId(link, ++retryNum) if (statusCode > 400) return this.handleParseId(link, ++retryNum)
return location == null ? link : location const url = location == null ? link : location
return this.regExps.listDetailLink.test(url)
? url.replace(this.regExps.listDetailLink, '$1')
: url.replace(this.regExps.listDetailLink2, '$1')
}, },
async getListDetail(id, page, tryNum = 0) { // 获取歌曲列表内的音乐 async getListId(id) {
if (this._requestObj_listDetail) this._requestObj_listDetail.cancelHttp() let cookie
if (tryNum > 2) return Promise.reject(new Error('try max num'))
if (/###/.test(id)) { if (/###/.test(id)) {
const [url, token] = id.split('###') const [url, token] = id.split('###')
id = url id = url
this.cookie = `MUSIC_U=${token}` cookie = `MUSIC_U=${token}`
} }
if ((/[?&:/]/.test(id))) { if ((/[?&:/]/.test(id))) {
if (this.regExps.listDetailLink.test(id)) { if (this.regExps.listDetailLink.test(id)) {
@ -79,6 +80,14 @@ export default {
} }
// console.log(id) // console.log(id)
} }
return { id, cookie }
},
async getListDetail(rawId, page, tryNum = 0) { // 获取歌曲列表内的音乐
if (this._requestObj_listDetail) this._requestObj_listDetail.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num'))
const { id, cookie } = await this.getListId(rawId)
if (cookie) this.cookie = cookie
this._requestObj_listDetail = httpFetch('https://music.163.com/api/linux/forward', { this._requestObj_listDetail = httpFetch('https://music.163.com/api/linux/forward', {
method: 'post', method: 'post',
@ -290,7 +299,8 @@ export default {
return Promise.all([this.getTag(), this.getHotTag()]).then(([tags, hotTag]) => ({ tags, hotTag, source: 'wy' })) return Promise.all([this.getTag(), this.getHotTag()]).then(([tags, hotTag]) => ({ tags, hotTag, source: 'wy' }))
}, },
getDetailPageUrl(id) { async getDetailPageUrl(rawId) {
const { id } = await this.getListId(rawId)
return `https://music.163.com/#/playlist?id=${id}` return `https://music.163.com/#/playlist?id=${id}`
}, },
} }

View File

@ -323,3 +323,10 @@ export const setTaskbarThumbnailClip = (clip) => {
export const setTaskbarThumbarButtons = (buttons) => { export const setTaskbarThumbarButtons = (buttons) => {
rendererSend(NAMES.mainWindow.taskbar_set_thumbar_buttons, buttons) rendererSend(NAMES.mainWindow.taskbar_set_thumbar_buttons, buttons)
} }
export const onSystemThemeChange = callback => {
rendererOn(NAMES.mainWindow.system_theme_change, callback)
return () => {
rendererOff(callback)
}
}

View File

@ -20,10 +20,10 @@ div(:class="$style.download")
div.list-item(@click="handleDoubleClick($event, index)" @contextmenu="handleListItemRigthClick($event, index)" div.list-item(@click="handleDoubleClick($event, index)" @contextmenu="handleListItemRigthClick($event, index)"
:class="[{[$style.active]: playListIndex == index }, { selected: selectedIndex == index }, { active: selectedData.includes(item) }]") :class="[{[$style.active]: playListIndex == index }, { selected: selectedIndex == index }, { active: selectedData.includes(item) }]")
div.list-item-cell.nobreak.center(style="width: 5%; padding-left: 3px; padding-right: 3px;" @click.stop) {{index + 1}} div.list-item-cell.nobreak.center(style="width: 5%; padding-left: 3px; padding-right: 3px;" @click.stop) {{index + 1}}
div.list-item-cell.auto(:tips="item.name") div.list-item-cell.auto(:aria-label="item.name")
span.select {{item.name}} span.select {{item.name}}
div.list-item-cell(style="width: 20%;") {{item.progress.progress}}% div.list-item-cell(style="width: 20%;") {{item.progress.progress}}%
div.list-item-cell(style="width: 22%;" :tips="item.statusText") {{item.statusText}} div.list-item-cell(style="width: 22%;" :aria-label="item.statusText") {{item.statusText}}
div.list-item-cell(style="width: 10%;") {{item.metadata.type && item.metadata.type.toUpperCase()}} div.list-item-cell(style="width: 10%;") {{item.metadata.type && item.metadata.type.toUpperCase()}}
div.list-item-cell(style="width: 13%; padding-left: 0; padding-right: 0;") div.list-item-cell(style="width: 13%; padding-left: 0; padding-right: 0;")
material-list-buttons(:index="index" :download-btn="false" :file-btn="item.status != downloadStatus.ERROR" remove-btn material-list-buttons(:index="index" :download-btn="false" :file-btn="item.status != downloadStatus.ERROR" remove-btn
@ -36,10 +36,10 @@ div(:class="$style.download")
<script> <script>
import { mapGetters, mapActions, mapMutations } from 'vuex' import { mapGetters, mapActions, mapMutations } from 'vuex'
import { checkPath, openDirInExplorer, openUrl } from '@renderer/utils' import { checkPath, openDirInExplorer, openUrl, getFontSizeWithScreen } from '@renderer/utils'
import musicSdk from '@renderer/utils/music' import musicSdk from '@renderer/utils/music'
import path from 'path' import path from 'path'
import { windowSizeList } from '@renderer/core/share' import { windowSizeList, isFullscreen } from '@renderer/core/share'
import { playInfo, playMusicInfo } from '@renderer/core/share/player' import { playInfo, playMusicInfo } from '@renderer/core/share/player'
import { downloadList, downloadStatus } from '@renderer/core/share/download' import { downloadList, downloadStatus } from '@renderer/core/share/download'
@ -51,6 +51,7 @@ export default {
playInfo, playInfo,
downloadList, downloadList,
downloadStatus, downloadStatus,
isFullscreen,
} }
}, },
data() { data() {
@ -181,7 +182,7 @@ export default {
] ]
}, },
listItemHeight() { listItemHeight() {
return parseInt(windowSizeList.find(item => item.id == this.setting.windowSizeId).fontSize) / 16 * 37 return Math.ceil((this.isFullscreen ? getFontSizeWithScreen() : parseInt(windowSizeList.find(item => item.id == this.setting.windowSizeId).fontSize)) * 2.3)
}, },
}, },
watch: { watch: {

View File

@ -6,7 +6,7 @@
</div> </div>
<ul class="scroll" :class="$style.listsContent" ref="dom_lists_list"> <ul class="scroll" :class="$style.listsContent" ref="dom_lists_list">
<li :class="[$style.listsItem, { [$style.active]: item.id == tabId }, { [$style.clicked]: boardListData.rightClickItemIndex == index }]" <li :class="[$style.listsItem, { [$style.active]: item.id == tabId }, { [$style.clicked]: boardListData.rightClickItemIndex == index }]"
:tips="item.name" v-for="(item, index) in boardList" :key="item.id" :aria-label="item.name" v-for="(item, index) in boardList" :key="item.id"
@click="handleToggleList(item.id)" @contextmenu="handleListsItemRigthClick($event, index)"> @click="handleToggleList(item.id)" @contextmenu="handleListsItemRigthClick($event, index)">
<span :class="$style.listsLabel">{{item.name}}</span> <span :class="$style.listsLabel">{{item.name}}</span>
</li> </li>

View File

@ -50,10 +50,10 @@ div(:class="$style.search")
dl(:class="$style.noitemList" v-if="setting.search.isShowHistorySearch && historyList.length") dl(:class="$style.noitemList" v-if="setting.search.isShowHistorySearch && historyList.length")
dt(:class="$style.noitemListTitle") dt(:class="$style.noitemListTitle")
span {{$t('history_search')}} span {{$t('history_search')}}
span(:class="$style.historyClearBtn" @click="clearHistory" :tips="$t('history_clear')") span(:class="$style.historyClearBtn" @click="clearHistory" :aria-label="$t('history_clear')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 512 512' space='preserve') svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 512 512' space='preserve')
use(xlink:href='#icon-eraser') use(xlink:href='#icon-eraser')
dd(:class="$style.noitemListItem" v-for="(item, index) in historyList" @contextmenu="removeHistory(index)" :key="index + item" @click="handleNoitemSearch(item)" :tips="$t('history_remove')") {{item}} dd(:class="$style.noitemListItem" v-for="(item, index) in historyList" @contextmenu="removeHistory(index)" :key="index + item" @click="handleNoitemSearch(item)" :aria-label="$t('history_remove')") {{item}}
div(v-else :class="$style.noitem_list") div(v-else :class="$style.noitem_list")
p {{$t('search__welcome')}} p {{$t('search__welcome')}}
//- common-flow-btn(:show="isShowEditBtn && (searchSourceId == 'all' || assertApiSupport(searchSourceId))" :remove-btn="false" @btn-click="handleFlowBtnClick") //- common-flow-btn(:show="isShowEditBtn && (searchSourceId == 'all' || assertApiSupport(searchSourceId))" :remove-btn="false" @btn-click="handleFlowBtnClick")

View File

@ -18,12 +18,12 @@ div(:class="$style.list")
:class="[{ [$style.active]: playerInfo.isPlayList && playerInfo.playIndex === index }, { selected: selectedIndex == index || rightClickSelectedIndex == index }, { active: selectedList.includes(item) }, { [$style.disabled]: !assertApiSupport(item.source) }]" :class="[{ [$style.active]: playerInfo.isPlayList && playerInfo.playIndex === index }, { selected: selectedIndex == index || rightClickSelectedIndex == index }, { active: selectedList.includes(item) }, { [$style.disabled]: !assertApiSupport(item.source) }]"
@contextmenu="handleListItemRightClick($event, index)") @contextmenu="handleListItemRightClick($event, index)")
div.list-item-cell.nobreak.center(style="flex: 0 0 5%; padding-left: 3px; padding-right: 3px;" :class="$style.noSelect" @click.stop) {{index + 1}} div.list-item-cell.nobreak.center(style="flex: 0 0 5%; padding-left: 3px; padding-right: 3px;" :class="$style.noSelect" @click.stop) {{index + 1}}
div.list-item-cell.auto(:tips="item.name + (isShowSource ? ` - ${item.source}` : '')") div.list-item-cell.auto(:aria-label="item.name + (isShowSource ? ` - ${item.source}` : '')")
span.select {{item.name}} span.select {{item.name}}
span(:class="[$style.labelSource, $style.noSelect]" v-if="isShowSource") {{item.source}} span(:class="[$style.labelSource, $style.noSelect]" v-if="isShowSource") {{item.source}}
div.list-item-cell(style="flex: 0 0 22%;" :tips="item.singer") div.list-item-cell(style="flex: 0 0 22%;" :aria-label="item.singer")
span.select {{item.singer}} span.select {{item.singer}}
div.list-item-cell(style="flex: 0 0 22%;" :tips="item.albumName") div.list-item-cell(style="flex: 0 0 22%;" :aria-label="item.albumName")
span.select {{item.albumName}} span.select {{item.albumName}}
div.list-item-cell(style="flex: 0 0 9%;") div.list-item-cell(style="flex: 0 0 9%;")
span(:class="[$style.time, $style.noSelect]") {{item.interval || '--/--'}} span(:class="[$style.time, $style.noSelect]") {{item.interval || '--/--'}}

View File

@ -1,5 +1,6 @@
import { computed, watch, ref, onBeforeUnmount } from '@renderer/utils/vueTools' import { computed, watch, ref, onBeforeUnmount } from '@renderer/utils/vueTools'
import { windowSizeList } from '@renderer/core/share' import { windowSizeList, isFullscreen } from '@renderer/core/share'
import { getFontSizeWithScreen } from '@renderer/utils'
const useKeyEvent = ({ handleSelectAllData }) => { const useKeyEvent = ({ handleSelectAllData }) => {
const keyEvent = { const keyEvent = {
@ -48,7 +49,7 @@ export default ({ list, setting }) => {
let lastSelectIndex = -1 let lastSelectIndex = -1
const listItemHeight = computed(() => { const listItemHeight = computed(() => {
return parseInt(windowSizeList.find(item => item.id == setting.value.windowSizeId).fontSize) / 16 * 37 return Math.ceil((isFullscreen.value ? getFontSizeWithScreen() : parseInt(windowSizeList.find(item => item.id == setting.value.windowSizeId).fontSize)) * 2.3)
}) })
const removeAllSelect = () => { const removeAllSelect = () => {

View File

@ -2,31 +2,31 @@
<div :class="$style.lists" ref="dom_lists"> <div :class="$style.lists" ref="dom_lists">
<div :class="$style.listHeader"> <div :class="$style.listHeader">
<h2 :class="$style.listsTitle">{{$t('my_list')}}</h2> <h2 :class="$style.listsTitle">{{$t('my_list')}}</h2>
<button :class="$style.listsAdd" @click="handleShowNewList" :tips="$t('lists__new_list_btn')"> <button :class="$style.listsAdd" @click="handleShowNewList" :aria-label="$t('lists__new_list_btn')">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="70%" viewBox="0 0 24 24" space="preserve"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="70%" viewBox="0 0 24 24" space="preserve">
<use xlink:href="#icon-list-add"></use> <use xlink:href="#icon-list-add"></use>
</svg> </svg>
</button> </button>
</div> </div>
<ul class="scroll" :class="[$style.listsContent, { [$style.sortable]: keyEvent.isModDown }]" ref="dom_lists_list"> <ul class="scroll" :class="[$style.listsContent, { [$style.sortable]: keyEvent.isModDown }]" ref="dom_lists_list">
<li class="default-list" :class="[$style.listsItem, {[$style.active]: defaultList.id == listId}]" :tips="defaultList.name" <li class="default-list" :class="[$style.listsItem, {[$style.active]: defaultList.id == listId}]" :aria-label="defaultList.name" :aria-selected="defaultList.id == listId"
@contextmenu="handleListsItemRigthClick($event, -2)" @click="handleListToggle(defaultList.id)" @contextmenu="handleListsItemRigthClick($event, -2)" @click="handleListToggle(defaultList.id)"
> >
<span :class="$style.listsLabel">{{defaultList.name}}</span> <span :class="$style.listsLabel">{{defaultList.name}}</span>
</li> </li>
<li class="default-list" :class="[$style.listsItem, {[$style.active]: loveList.id == listId}]" :tips="loveList.name" <li class="default-list" :class="[$style.listsItem, {[$style.active]: loveList.id == listId}]" :aria-label="loveList.name" :aria-selected="loveList.id == listId"
@contextmenu="handleListsItemRigthClick($event, -1)" @click="handleListToggle(loveList.id)"> @contextmenu="handleListsItemRigthClick($event, -1)" @click="handleListToggle(loveList.id)">
<span :class="$style.listsLabel">{{loveList.name}}</span> <span :class="$style.listsLabel">{{loveList.name}}</span>
</li> </li>
<li class="user-list" <li class="user-list"
:class="[$style.listsItem, {[$style.active]: item.id == listId}, {[$style.clicked]: listsData.rightClickItemIndex == index}, {[$style.fetching]: fetchingListStatus[item.id]}]" :data-index="index" :class="[$style.listsItem, {[$style.active]: item.id == listId}, {[$style.clicked]: listsData.rightClickItemIndex == index}, {[$style.fetching]: fetchingListStatus[item.id]}]" :data-index="index"
@contextmenu="handleListsItemRigthClick($event, index)" :tips="item.name" v-for="(item, index) in userLists" :key="item.id" @contextmenu="handleListsItemRigthClick($event, index)" :aria-label="item.name" v-for="(item, index) in userLists" :key="item.id" :aria-selected="defaultList.id == listId"
> >
<span :class="$style.listsLabel" @click="handleListToggle(item.id, index + 2)">{{item.name}}</span> <span :class="$style.listsLabel" @click="handleListToggle(item.id, index + 2)">{{item.name}}</span>
<input class="key-bind" :class="$style.listsInput" @contextmenu.stop type="text" <input class="key-bind" :class="$style.listsInput" @contextmenu.stop type="text"
@keyup.enter="handleListsSave(index, $event)" @blur="handleListsSave(index, $event)" :value="item.name" :placeholder="item.name"/> @keyup.enter="handleListsSave(index, $event)" @blur="handleListsSave(index, $event)" :value="item.name" :placeholder="item.name"/>
</li> </li>
<transition enter-active-class="animated-fast slideInLeft" leave-active-class="animated-fast fadeOut" @after-leave="handleListsNewAfterLeave"> <transition enter-active-class="animated-fast slideInLeft" leave-active-class="animated-fast fadeOut" @after-leave="handleListsNewAfterLeave" @after-enter="$refs.dom_listsNewInput.focus()">
<li :class="[$style.listsItem, $style.listsNew, listsData.isNewLeave ? $style.newLeave : null]" v-if="listsData.isShowNewList"> <li :class="[$style.listsItem, $style.listsNew, listsData.isNewLeave ? $style.newLeave : null]" v-if="listsData.isShowNewList">
<input class="key-bind" :class="$style.listsInput" @contextmenu.stop ref="dom_listsNewInput" type="text" @keyup.enter="handleListsCreate" <input class="key-bind" :class="$style.listsInput" @contextmenu.stop ref="dom_listsNewInput" type="text" @keyup.enter="handleListsCreate"
@blur="handleListsCreate" :placeholder="$t('lists__new_list_input')"/> @blur="handleListsCreate" :placeholder="$t('lists__new_list_input')"/>
@ -263,9 +263,6 @@ export default {
}, },
handleShowNewList() { handleShowNewList() {
this.listsData.isShowNewList = true this.listsData.isShowNewList = true
this.$nextTick(() => {
this.$refs.dom_listsNewInput.focus()
})
}, },
handleListsNewAfterLeave() { handleListsNewAfterLeave() {
this.listsData.isNewLeave = false this.listsData.isNewLeave = false
@ -406,15 +403,15 @@ export default {
} }
return false return false
}, },
openSourceDetailPage(index) { async openSourceDetailPage(index) {
const { source, sourceListId } = this.userLists[index] const { source, sourceListId } = this.userLists[index]
if (!sourceListId) return if (!sourceListId) return
let url let url
if (/board__/.test(sourceListId)) { if (/board__/.test(sourceListId)) {
const id = sourceListId.replace(/board__/, '') const id = sourceListId.replace(/board__/, '')
url = musicSdk[source].leaderboard.getDetailPageUrl(id) url = musicSdk[source].leaderboard.getDetailPageUrl(id)
} else { } else if (musicSdk[source].songList?.getDetailPageUrl) {
url = musicSdk[source].songList.getDetailPageUrl(sourceListId) url = await musicSdk[source].songList.getDetailPageUrl(sourceListId)
} }
if (!url) return if (!url) return
openUrl(url) openUrl(url)

View File

@ -3,12 +3,12 @@
<!-- <div class="scroll" :class="$style.toc"> <!-- <div class="scroll" :class="$style.toc">
<ul :class="$style.tocList"> <ul :class="$style.tocList">
<li :class="$style.tocListItem" v-for="h2 in toc.list" :key="h2.id"> <li :class="$style.tocListItem" v-for="h2 in toc.list" :key="h2.id">
<h2 :class="[$style.tocH2, toc.activeId == h2.id ? $style.active : null]" :tips="h2.title"> <h2 :class="[$style.tocH2, toc.activeId == h2.id ? $style.active : null]" :aria-label="h2.title">
<a :href="'#' + h2.id" @click.stop="toc.activeId = h2.id">{{h2.title}}</a> <a :href="'#' + h2.id" @click.stop="toc.activeId = h2.id">{{h2.title}}</a>
</h2> </h2>
<ul :class="$style.tocList" v-if="h2.children.length"> <ul :class="$style.tocList" v-if="h2.children.length">
<li :class="$style.tocSubListItem" v-for="h3 in h2.children" :key="h3.id"> <li :class="$style.tocSubListItem" v-for="h3 in h2.children" :key="h3.id">
<h3 :class="[$style.tocH3, toc.activeId == h3.id ? $style.active : null]" :tips="h3.title"> <h3 :class="[$style.tocH3, toc.activeId == h3.id ? $style.active : null]" :aria-label="h3.title">
<a :href="'#' + h3.id" @click.stop="toc.activeId = h3.id">{{h3.title}}</a> <a :href="'#' + h3.id" @click.stop="toc.activeId = h3.id">{{h3.title}}</a>
</h3> </h3>
</li> </li>
@ -20,6 +20,7 @@
<dl ref="dom_setting_list"> <dl ref="dom_setting_list">
<SettingBasic /> <SettingBasic />
<SettingPlay /> <SettingPlay />
<SettingPlayDetail />
<SettingDesktopLyric /> <SettingDesktopLyric />
<SettingSearch /> <SettingSearch />
<SettingList /> <SettingList />
@ -43,6 +44,7 @@ import { currentStting } from './setting'
import SettingBasic from './components/SettingBasic' import SettingBasic from './components/SettingBasic'
import SettingPlay from './components/SettingPlay' import SettingPlay from './components/SettingPlay'
import SettingPlayDetail from './components/SettingPlayDetail'
import SettingDesktopLyric from './components/SettingDesktopLyric' import SettingDesktopLyric from './components/SettingDesktopLyric'
import SettingSearch from './components/SettingSearch' import SettingSearch from './components/SettingSearch'
import SettingList from './components/SettingList' import SettingList from './components/SettingList'
@ -61,6 +63,7 @@ export default {
components: { components: {
SettingBasic, SettingBasic,
SettingPlay, SettingPlay,
SettingPlayDetail,
SettingDesktopLyric, SettingDesktopLyric,
SettingSearch, SettingSearch,
SettingList, SettingList,
@ -94,6 +97,9 @@ export default {
watch(() => setting.value.player.mediaDeviceId, val => { watch(() => setting.value.player.mediaDeviceId, val => {
currentStting.value.player.mediaDeviceId = val currentStting.value.player.mediaDeviceId = val
}) })
watch(() => setting.value.playDetail.style.fontSize, val => {
currentStting.value.playDetail.style.fontSize = val
})
watch(() => setting.value.player.isMute, val => { watch(() => setting.value.player.isMute, val => {
currentStting.value.player.isMute = val currentStting.value.player.isMute = val
}) })
@ -109,6 +115,9 @@ export default {
watch(() => setting.value.player.togglePlayMethod, val => { watch(() => setting.value.player.togglePlayMethod, val => {
currentStting.value.player.togglePlayMethod = val currentStting.value.player.togglePlayMethod = val
}) })
watch(() => setting.value.player.audioVisualization, val => {
currentStting.value.player.audioVisualization = val
})
}, },
data() { data() {
return { return {

View File

@ -3,15 +3,15 @@ dt#about {{$t('setting__about')}}
dd dd
p.small p.small
| 本软件完全免费代码已开源开源地址 | 本软件完全免费代码已开源开源地址
span.hover.underline(:tips="$t('setting__click_open')" @click="openUrl('https://github.com/lyswhut/lx-music-desktop#readme')") https://github.com/lyswhut/lx-music-desktop span.hover.underline(:aria-label="$t('setting__click_open')" @click="openUrl('https://github.com/lyswhut/lx-music-desktop#readme')") https://github.com/lyswhut/lx-music-desktop
p.small p.small
| 最新版网盘下载地址网盘内有WindowsMAC版 | 最新版网盘下载地址网盘内有WindowsMAC版
span.hover.underline(:tips="$t('setting__click_open')" @click="openUrl('https://www.lanzoui.com/b0bf2cfa/')") 网盘地址 span.hover.underline(:aria-label="$t('setting__click_open')" @click="openUrl('https://www.lanzoui.com/b0bf2cfa/')") 网盘地址
| &nbsp;&nbsp;密码 | &nbsp;&nbsp;密码
span.hover(:tips="$t('setting__click_copy')" @click="clipboardWriteText('glqw')") glqw span.hover(:aria-label="$t('setting__click_copy')" @click="clipboardWriteText('glqw')") glqw
p.small p.small
| 软件的常见问题可转至 | 软件的常见问题可转至
span.hover.underline(:tips="$t('setting__click_open')" @click="openUrl('https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md')") 常见问题 span.hover.underline(:aria-label="$t('setting__click_open')" @click="openUrl('https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md')") 常见问题
p.small p.small
strong 本软件没有客服 strong 本软件没有客服
| 但我们整理了一些常见的使用问题 | 但我们整理了一些常见的使用问题
@ -19,11 +19,11 @@ dd
| 地阅读常见问题后 | 地阅读常见问题后
p.small p.small
| 仍有问题可加企鹅群&nbsp; | 仍有问题可加企鹅群&nbsp;
span.hover(:tips="$t('setting__click_open')" @click="openUrl('https://jq.qq.com/?_wv=1027&k=51ECeq2')") 830125506 span.hover(:aria-label="$t('setting__click_open')" @click="openUrl('https://jq.qq.com/?_wv=1027&k=51ECeq2')") 830125506
| &nbsp;反馈 | &nbsp;反馈
strong (为免满人无事勿加入群先看群公告) strong (为免满人无事勿加入群先看群公告)
| 或到 GitHub 提交&nbsp; | 或到 GitHub 提交&nbsp;
span.hover.underline(:tips="$t('setting__click_open')" @click="openUrl('https://github.com/lyswhut/lx-music-desktop/issues')") issue span.hover.underline(:aria-label="$t('setting__click_open')" @click="openUrl('https://github.com/lyswhut/lx-music-desktop/issues')") issue
br br
p.small p.small
@ -32,7 +32,7 @@ dd
| |
p p
| 可以加入测试企鹅群&nbsp; | 可以加入测试企鹅群&nbsp;
span.hover(:tips="$t('setting__click_open')" @click="openUrl('https://qm.qq.com/cgi-bin/qm/qr?k=zR6aYosQoKb07g4FGFZdO9n9zL1dhFpE&jump_from=webapi')") 768786588 span.hover(:aria-label="$t('setting__click_open')" @click="openUrl('https://qm.qq.com/cgi-bin/qm/qr?k=zR6aYosQoKb07g4FGFZdO9n9zL1dhFpE&jump_from=webapi')") 768786588
| &nbsp;注意测试版的功可能会不稳定 | &nbsp;注意测试版的功可能会不稳定
strong 打算潜水的勿加 strong 打算潜水的勿加
@ -43,7 +43,7 @@ dd
| 你已签署本软件的&nbsp; | 你已签署本软件的&nbsp;
base-btn(min @click="handleShowPact") 许可协议 base-btn(min @click="handleShowPact") 许可协议
| 协议的在线版本在&nbsp; | 协议的在线版本在&nbsp;
strong.hover.underline(:tips="$t('setting__click_open')" @click="openUrl('https://github.com/lyswhut/lx-music-desktop#%E9%A1%B9%E7%9B%AE%E5%8D%8F%E8%AE%AE')") 这里 strong.hover.underline(:aria-label="$t('setting__click_open')" @click="openUrl('https://github.com/lyswhut/lx-music-desktop#%E9%A1%B9%E7%9B%AE%E5%8D%8F%E8%AE%AE')") 这里
| &nbsp; | &nbsp;
br br

View File

@ -4,9 +4,15 @@ dd
h3#basic_theme {{$t('setting__basic_theme')}} h3#basic_theme {{$t('setting__basic_theme')}}
div div
ul(:class="$style.theme") ul(:class="$style.theme")
li(v-for="theme in themes.list" :key="theme.id" :tips="$t('theme_' + theme.className)" @click="currentStting.themeId = theme.id" :class="[theme.className, {[$style.active]: themes.active == theme.id}]") li(v-for="theme in themes.list" :key="theme.id" :aria-label="$t('theme_' + theme.className)" @click="currentStting.theme.id = theme.id" :class="[theme.className, {[$style.active]: themes.active == theme.id}]")
span div(:class="$style.bg")
label {{$t('theme_' + theme.className)}} label {{$t('theme_' + theme.className)}}
li(:aria-label="$t('theme_auto_tip')" @click="handleSetThemeAuto" @contextmenu="isShowThemeSelectorModal = true" :class="[$style.auto, themeClassName, {[$style.active]: themes.active == 'auto'}]")
div(:class="$style.bg")
div(:class="$style.bgContent")
div(:class="[$style.light, themes.lightTheme.className]")
div(:class="[$style.dark, themes.darkTheme.className]")
label {{$t('theme_auto')}}
dd dd
div div
@ -19,7 +25,7 @@ dd
p.gap-top p.gap-top
base-btn.btn(min @click="isShowPlayTimeoutModal = true") {{$t('setting__play_timeout')}} {{ timeLabel ? ` (${timeLabel})` : '' }} base-btn.btn(min @click="isShowPlayTimeoutModal = true") {{$t('setting__play_timeout')}} {{ timeLabel ? ` (${timeLabel})` : '' }}
dd(:tips="$t('setting__basic_source_title')") dd(:aria-label="$t('setting__basic_source_title')")
h3#basic_source {{$t('setting__basic_source')}} h3#basic_source {{$t('setting__basic_source')}}
div div
.gap-top(v-for="item in apiSources" :key="item.id") .gap-top(v-for="item in apiSources" :key="item.id")
@ -28,19 +34,19 @@ dd(:tips="$t('setting__basic_source_title')")
p.gap-top p.gap-top
base-btn.btn(min @click="isShowUserApiModal = true") {{$t('setting__basic_source_user_api_btn')}} base-btn.btn(min @click="isShowUserApiModal = true") {{$t('setting__basic_source_user_api_btn')}}
dd(:tips="$t('setting__basic_window_size_title')") dd(:aria-label="$t('setting__basic_window_size_title')")
h3#basic_window_size {{$t('setting__basic_window_size')}} h3#basic_window_size {{$t('setting__basic_window_size')}}
div div
base-checkbox.gap-left(v-for="(item, index) in windowSizeList" :id="`setting_window_size_${item.id}`" name="setting_window_size" base-checkbox.gap-left(v-for="(item, index) in windowSizeList" :id="`setting_window_size_${item.id}`" name="setting_window_size"
need v-model="currentStting.windowSizeId" :value="item.id" :label="$t('setting__basic_window_size_' + item.name)" :key="item.id") need v-model="currentStting.windowSizeId" :disabled="isFullscreen" :value="item.id" :label="$t('setting__basic_window_size_' + item.name)" :key="item.id")
dd(:tips="$t('setting__basic_lang_title')") dd(:aria-label="$t('setting__basic_lang_title')")
h3#basic_lang {{$t('setting__basic_lang')}} h3#basic_lang {{$t('setting__basic_lang')}}
div div
base-checkbox.gap-left(v-for="item in langList" :key="item.locale" :id="`setting_lang_${item.locale}`" name="setting_lang" base-checkbox.gap-left(v-for="item in langList" :key="item.locale" :id="`setting_lang_${item.locale}`" name="setting_lang"
need v-model="currentStting.langId" :value="item.locale" :label="item.name") need v-model="currentStting.langId" :value="item.locale" :label="item.name")
dd(:tips="$t('setting__basic_sourcename_title')") dd(:aria-label="$t('setting__basic_sourcename_title')")
h3#basic_sourcename {{$t('setting__basic_sourcename')}} h3#basic_sourcename {{$t('setting__basic_sourcename')}}
div div
base-checkbox.gap-left(v-for="item in sourceNameTypes" :key="item.id" :id="`setting_abasic_sourcename_${item.id}`" base-checkbox.gap-left(v-for="item in sourceNameTypes" :key="item.id" :id="`setting_abasic_sourcename_${item.id}`"
@ -55,20 +61,23 @@ dd
div div
base-selection.gap-teft(:list="fontList" v-model="currentStting.font" item-key="id" item-name="label") base-selection.gap-teft(:list="fontList" v-model="currentStting.font" item-key="id" item-name="label")
ThemeSelectorModal(v-model="isShowThemeSelectorModal")
play-timeout-modal(v-model="isShowPlayTimeoutModal") play-timeout-modal(v-model="isShowPlayTimeoutModal")
user-api-modal(v-model="isShowUserApiModal") user-api-modal(v-model="isShowUserApiModal")
</template> </template>
<script> <script>
import { computed, ref, useI18n, watch } from '@renderer/utils/vueTools' import { computed, ref, useI18n, watch, useRefGetter } from '@renderer/utils/vueTools'
import { themes as themeList, windowSizeList, apiSource, userApi } from '@renderer/core/share' import { themes as themeList, windowSizeList, apiSource, userApi, isFullscreen } from '@renderer/core/share'
import { langList } from '@/lang' import { langList } from '@/lang'
import { currentStting } from '../setting' import { currentStting } from '../setting'
import { setWindowSize } from '@renderer/utils' import { setWindowSize } from '@renderer/utils'
import apiSourceInfo from '@renderer/utils/music/api-source-info' import apiSourceInfo from '@renderer/utils/music/api-source-info'
import { useTimeout } from '@renderer/utils/timeoutStop' import { useTimeout } from '@renderer/utils/timeoutStop'
import { getSystemFonts } from '@renderer/utils/tools' import { getSystemFonts } from '@renderer/utils/tools'
import { dialog } from '@renderer/plugins/Dialog'
import ThemeSelectorModal from './ThemeSelectorModal'
import PlayTimeoutModal from './PlayTimeoutModal' import PlayTimeoutModal from './PlayTimeoutModal'
import UserApiModal from './UserApiModal' import UserApiModal from './UserApiModal'
@ -76,19 +85,34 @@ import UserApiModal from './UserApiModal'
export default { export default {
name: 'SettingBasic', name: 'SettingBasic',
components: { components: {
ThemeSelectorModal,
PlayTimeoutModal, PlayTimeoutModal,
UserApiModal, UserApiModal,
}, },
setup() { setup() {
const { t, locale } = useI18n() const { t, locale } = useI18n()
const themeClassName = useRefGetter('theme')
const themes = computed(() => { const themes = computed(() => {
return { return {
active: currentStting.value.themeId, active: currentStting.value.theme.id,
lightTheme: themeList.find(t => t.id == currentStting.value.theme.lightId) ?? themeList[0],
darkTheme: themeList.find(t => t.id == currentStting.value.theme.darkId) ?? themeList[0],
list: themeList, list: themeList,
} }
}) })
const isShowThemeSelectorModal = ref(false)
const handleSetThemeAuto = () => {
if (currentStting.value.theme.id == 'auto') return
currentStting.value.theme.id = 'auto'
if (window.localStorage.getItem('theme-auto-tip') != 'true') {
window.localStorage.setItem('theme-auto-tip', 'true')
dialog({
message: t('setting__basic_theme_auto_tip'),
confirmButtonText: t('ok'),
})
}
}
watch(() => currentStting.value.apiSource, visible => { watch(() => currentStting.value.apiSource, visible => {
apiSource.value = visible apiSource.value = visible
@ -161,6 +185,9 @@ export default {
return { return {
currentStting, currentStting,
themes, themes,
themeClassName,
isShowThemeSelectorModal,
handleSetThemeAuto,
isShowPlayTimeoutModal, isShowPlayTimeoutModal,
timeLabel, timeLabel,
apiSources, apiSources,
@ -170,6 +197,7 @@ export default {
sourceNameTypes, sourceNameTypes,
controlBtnPositionList, controlBtnPositionList,
fontList, fontList,
isFullscreen,
} }
}, },
} }
@ -199,7 +227,7 @@ export default {
margin-right: 0; margin-right: 0;
} }
span { .bg {
display: block; display: block;
width: 36px; width: 36px;
height: 36px; height: 36px;
@ -229,7 +257,7 @@ export default {
each(@themes, { each(@themes, {
&:global(.@{value}) { &:global(.@{value}) {
span { .bg {
&:after { &:after {
background-color: ~'@{color-@{value}-theme}'; background-color: ~'@{color-@{value}-theme}';
background-image: ~'@{color-@{value}-theme-bgimg}'; background-image: ~'@{color-@{value}-theme-bgimg}';
@ -237,6 +265,68 @@ export default {
} }
} }
}) })
&.auto {
>.bg {
&:after {
content: none;
}
}
.bgContent {
position: relative;
height: 100%;
overflow: hidden;
border-radius: 5px;
}
.light, .dark {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
&:after {
display: block;
content: ' ';
width: 100%;
height: 100%;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
}
}
.light {
&:after {
clip-path: polygon(0 0, 100% 0, 0 100%);
}
each(@themes, {
&:global(.@{value}) {
svg {
fill: ~'@{color-@{value}-theme}';
}
&:after {
background-color: ~'@{color-@{value}-theme}';
background-image: ~'@{color-@{value}-theme-bgimg}';
}
}
})
}
.dark {
&:after {
clip-path: polygon(0 100%, 100% 0, 100% 100%);
}
each(@themes, {
&:global(.@{value}) {
svg {
fill: ~'@{color-@{value}-theme}';
}
&:after {
background-color: ~'@{color-@{value}-theme}';
background-image: ~'@{color-@{value}-theme-bgimg}';
}
}
})
}
}
} }
} }
@ -247,7 +337,7 @@ each(@themes, {
&.active { &.active {
&:global(.@{value}) { &:global(.@{value}) {
color: ~'@{color-@{value}-theme}'; color: ~'@{color-@{value}-theme}';
span { .bg {
border-color: ~'@{color-@{value}-theme}'; border-color: ~'@{color-@{value}-theme}';
} }
} }

View File

@ -2,22 +2,22 @@
dt#download {{$t('setting__download')}} dt#download {{$t('setting__download')}}
dd dd
base-checkbox(id="setting_download_enable" v-model="currentStting.download.enable" :label="$t('setting__download_enable')") base-checkbox(id="setting_download_enable" v-model="currentStting.download.enable" :label="$t('setting__download_enable')")
dd(:tips="$t('setting__download_path_title')") dd(:aria-label="$t('setting__download_path_title')")
h3#download_path {{$t('setting__download_path')}} h3#download_path {{$t('setting__download_path')}}
div div
p p
| {{$t('setting__download_path_label')}} | {{$t('setting__download_path_label')}}
span.auto-hidden.hover(:class="$style.savePath" @click="openDirInExplorer(currentStting.download.savePath)" :tips="$t('setting__download_path_open_label')") {{currentStting.download.savePath}} span.auto-hidden.hover(:class="$style.savePath" @click="openDirInExplorer(currentStting.download.savePath)" :aria-label="$t('setting__download_path_open_label')") {{currentStting.download.savePath}}
p p
base-btn.btn(min @click="handleChangeSavePath") {{$t('setting__download_path_change_btn')}} base-btn.btn(min @click="handleChangeSavePath") {{$t('setting__download_path_change_btn')}}
dd dd
h3#download_use_other_source h3#download_use_other_source
| {{$t('setting__download_use_other_source')}} | {{$t('setting__download_use_other_source')}}
svg-icon(class="help-icon" name="help-circle-outline" :tips="$t('setting__download_use_other_source_tip')") svg-icon(class="help-icon" name="help-circle-outline" :aria-label="$t('setting__download_use_other_source_tip')")
div div
base-checkbox(id="setting_download_isUseOtherSource" v-model="currentStting.download.isUseOtherSource" :label="$t('setting__is_enable')") base-checkbox(id="setting_download_isUseOtherSource" v-model="currentStting.download.isUseOtherSource" :label="$t('setting__is_enable')")
div div
dd(:tips="$t('setting__download_name_title')") dd(:aria-label="$t('setting__download_name_title')")
h3#download_name {{$t('setting__download_name')}} h3#download_name {{$t('setting__download_name')}}
div div
base-checkbox.gap-left(:id="`setting_download_musicName_${item.value}`" name="setting_download_musicName" :value="item.value" :key="item.value" need base-checkbox.gap-left(:id="`setting_download_musicName_${item.value}`" name="setting_download_musicName" :value="item.value" :key="item.value" need
@ -28,7 +28,7 @@ dd
base-checkbox(id="setting_download_isEmbedPic" v-model="currentStting.download.isEmbedPic" :label="$t('setting__download_embed_pic')") base-checkbox(id="setting_download_isEmbedPic" v-model="currentStting.download.isEmbedPic" :label="$t('setting__download_embed_pic')")
.gap-top .gap-top
base-checkbox(id="setting_download_isEmbedLyric" v-model="currentStting.download.isEmbedLyric" :label="$t('setting__download_embed_lyric')") base-checkbox(id="setting_download_isEmbedLyric" v-model="currentStting.download.isEmbedLyric" :label="$t('setting__download_embed_lyric')")
dd(:tips="$t('setting__download_lyric_title')") dd(:aria-label="$t('setting__download_lyric_title')")
h3#download_lyric {{$t('setting__download_lyric')}} h3#download_lyric {{$t('setting__download_lyric')}}
div div
base-checkbox(id="setting_download_isDownloadLrc" v-model="currentStting.download.isDownloadLrc" :label="$t('setting__is_enable')") base-checkbox(id="setting_download_isDownloadLrc" v-model="currentStting.download.isDownloadLrc" :label="$t('setting__is_enable')")

View File

@ -7,7 +7,7 @@ dd
base-checkbox(id="setting_list_scroll_enable" v-model="currentStting.list.isSaveScrollLocation" :label="$t('setting__list_scroll')") base-checkbox(id="setting_list_scroll_enable" v-model="currentStting.list.isSaveScrollLocation" :label="$t('setting__list_scroll')")
.gap-top .gap-top
base-checkbox(id="setting_list_clickAction_enable" v-model="currentStting.list.isClickPlayList" :label="$t('setting__list_click_action')") base-checkbox(id="setting_list_clickAction_enable" v-model="currentStting.list.isClickPlayList" :label="$t('setting__list_click_action')")
dd(:tips="$t('setting__basic_sourcename_title')") dd(:aria-label="$t('setting__basic_sourcename_title')")
h3#list_addMusicLocationType {{$t('setting__list_add_music_location_type')}} h3#list_addMusicLocationType {{$t('setting__list_add_music_location_type')}}
div div
base-checkbox.gap-left(id="setting_list_add_music_location_type_top" base-checkbox.gap-left(id="setting_list_add_music_location_type_top"

View File

@ -8,7 +8,7 @@ dd
dd dd
h3#other_resource_cache h3#other_resource_cache
| {{$t('setting__other_resource_cache')}} | {{$t('setting__other_resource_cache')}}
svg-icon(class="help-icon" name="help-circle-outline" :tips="$t('setting__other_resource_cache_tip')") svg-icon(class="help-icon" name="help-circle-outline" :aria-label="$t('setting__other_resource_cache_tip')")
div div
p p
| {{$t('setting__other_resource_cache_label')}} | {{$t('setting__other_resource_cache_label')}}
@ -18,7 +18,7 @@ dd
dd dd
h3#other_play_list_cache h3#other_play_list_cache
| {{$t('setting__other_play_list_cache')}} | {{$t('setting__other_play_list_cache')}}
svg-icon(class="help-icon" name="help-circle-outline" :tips="$t('setting__other_play_list_cache_tip')") svg-icon(class="help-icon" name="help-circle-outline" :aria-label="$t('setting__other_play_list_cache_tip')")
div div
base-btn.btn(min :disabled="isDisabledListCacheClear" @click="clearListCache") {{$t('setting__other_play_list_cache_clear_btn')}} base-btn.btn(min :disabled="isDisabledListCacheClear" @click="clearListCache") {{$t('setting__other_play_list_cache_clear_btn')}}

Some files were not shown because too many files have changed in this diff Show More