Compare commits

...

144 Commits

Author SHA1 Message Date
lyswhut
b3d13c3a90 Merge branch 'dev' 2020-09-18 20:24:48 +08:00
lyswhut
24ee42e0c7 修正更新描述 2020-09-18 20:24:09 +08:00
lyswhut
3a18eccdd6 更新travis-ci地址 2020-09-18 20:11:40 +08:00
lyswhut
a3b763ecc9 发布 v1.1.0 版本 2020-09-18 20:08:04 +08:00
lyswhut
822c74ad2f 更新依赖到最新 2020-09-18 20:02:12 +08:00
lyswhut
0a71e34459 播放状态栏新增桌面歌词的开关、播放模式的切换、歌曲的收藏按钮 2020-09-18 12:03:41 +08:00
lyswhut
25b68081ea 迁移travis-ci.org到travis-ci.com 2020-09-16 08:54:57 +08:00
lyswhut
ac0e1e7698 移除对百度音乐的支持 2020-09-05 15:13:49 +08:00
lyswhut
4be5bcc979 新增酷狗歌词翻译 2020-09-05 03:50:30 +08:00
lyswhut
f3c2d7e43a 更新依赖到最新 2020-09-05 03:46:33 +08:00
lyswhut
9fa0700f72 更新css-loader 2020-09-02 14:00:01 +08:00
lyswhut
868da542ab 更新网盘地址 2020-08-30 13:24:23 +08:00
lyswhut
04c810e292 添加没有写入权限的提示 2020-08-30 12:25:02 +08:00
lyswhut
86478c0268 更新electron到 10.1.0 2020-08-29 14:44:37 +08:00
lyswhut
b453a8934f 新增显示切换动画设置 2020-08-24 00:44:55 +08:00
lyswhut
669c1fe490 新增显示歌词翻译设置,默认开启 2020-08-23 15:45:08 +08:00
lyswhut
c15895ab45 新增 不允许将歌词窗口拖出主屏幕之外 的设置项 2020-08-23 12:13:17 +08:00
lyswhut
44acc7d5ac 移除默认的全局声音媒体快捷键接管 2020-08-22 14:17:34 +08:00
lyswhut
341df0dab7 在歌单详情界面新增播放当前歌单按钮、收藏歌单按钮 2020-08-22 14:06:16 +08:00
lyswhut
8df3ff3536 更新依赖 2020-08-22 12:49:43 +08:00
lyswhut
85c683b714 优化随机播放机制 2020-08-10 22:24:38 +08:00
lyswhut
6606edafb9 修复窗口在较小模式下最小化/关闭按钮不居中的问题 2020-08-10 09:51:31 +08:00
lyswhut
91ff7b658a 更新依赖 2020-08-09 12:39:49 +08:00
lyswhut
27c500e950 移除无用代码 2020-08-09 12:39:24 +08:00
lyswhut
078245cb96 修复我的列表搜索对最后一个字符的匹配问题 2020-08-04 09:05:23 +08:00
lyswhut
e54597b944 为区分静音状态,静音时音量条会变淡,调整音量条时将会取消静音 2020-08-04 08:54:26 +08:00
lyswhut
1af9b3a018 静音时隐藏音量条 2020-08-02 14:40:38 +08:00
lyswhut
315d041fc7 桌面歌词当前播放行改为上下居中 2020-08-01 23:26:35 +08:00
lyswhut
f7f1cdab34 优化逻辑 2020-07-31 11:00:55 +08:00
lyswhut
597f0da42f 移除多余脚本命令 2020-07-27 12:06:54 +08:00
lyswhut
497add09b3 修复使用全局快捷键还原窗口时,窗口没有获取焦点的问题 2020-07-27 11:38:59 +08:00
lyswhut
ff05225b56 修复git文件名大小写问题 2020-07-26 16:25:32 +08:00
lyswhut
ac053e9ac9 移除多余文件 2020-07-26 16:21:53 +08:00
lyswhut
9aeae1e460 移除重复的文件 2020-07-26 16:19:52 +08:00
lyswhut
d710f204b3 更新faq 2020-07-26 12:33:23 +08:00
lyswhut
be016aa4bb 发布v1.0.1版本 2020-07-25 19:49:04 +08:00
lyswhut
04e62cba24 更新到v1.0.1版本 2020-07-25 19:42:02 +08:00
lyswhut
4d476dc641 修复在 Windows 系统下缩放比非100%时,拖动桌面歌词会自动加大桌面歌词窗口的问题 2020-07-25 19:40:44 +08:00
lyswhut
85c3a812da 对我的列表歌曲搜索结果进行相似度排序 2020-07-25 19:36:46 +08:00
lyswhut
e985e70303 更新多选描述 2020-07-24 22:18:27 +08:00
lyswhut
9dbf96c259 发布 1.0.0 版本 2020-07-24 20:49:44 +08:00
lyswhut
6eba7c9a2f 更新文档 2020-07-24 20:48:20 +08:00
lyswhut
c1966e9301 更新FAQ 2020-07-24 20:45:50 +08:00
lyswhut
a6fb72f1de 调整主题颜色 2020-07-24 20:28:07 +08:00
lyswhut
a695a69f0f 更新依赖 2020-07-23 00:42:17 +08:00
lyswhut
a8632297eb 新增在歌单详情列表按退格键可快速返回歌单列表 2020-07-19 13:40:09 +08:00
lyswhut
ca4218b309 点击确定时打开备份文件位置 2020-07-18 22:49:28 +08:00
lyswhut
65df71239a 更新package-lock.json 2020-07-14 20:57:29 +08:00
lyswhut
fdd8f3b214 更新依赖 2020-07-14 20:41:10 +08:00
lyswhut
1887573301 更新electron到9.1.0 2020-07-08 02:01:46 +08:00
lyswhut
1efd56f48b 新增当列表文件损坏时对损坏文件的备份 2020-07-06 18:15:03 +08:00
lyswhut
bcd13b6f27 压缩备份数据文件 2020-07-06 16:14:18 +08:00
lyswhut
e4e880edd9 优化歌单操作体验 2020-07-04 21:14:29 +08:00
lyswhut
75da9c2e73 修复无法开启托盘的bug 2020-07-04 17:32:43 +08:00
lyswhut
7a933dbd5e 修正桌面歌词透明度按钮描述 2020-07-04 13:17:42 +08:00
lyswhut
4b097b83b1 更新依赖到最新 2020-07-04 11:58:39 +08:00
lyswhut
b3f9a691f5 重新提交重命名后的文件 2020-07-04 11:50:55 +08:00
lyswhut
50cd94a280 Merge branch 'master' into dev 2020-07-04 11:32:51 +08:00
lyswhut
82982b3cda 调整图标 2020-07-04 01:50:08 +08:00
lyswhut
a5243ff58c 支持flac文件歌词写入 2020-07-03 09:16:20 +08:00
lyswhut
3b25e0a112 新增将歌词嵌入音频文件中 2020-07-02 12:03:18 +08:00
lyswhut
d94d880569 修复搜索界面批量添加歌曲时程序异常的bug 2020-07-01 23:46:52 +08:00
lyswhut
8eec07b5bd 更改默认搜索设置为聚合搜索 2020-07-01 17:07:25 +08:00
lyswhut
9f72a51458 新增开关下载功能控制 2020-07-01 17:04:43 +08:00
lyswhut
9fd181ee06 新增托盘图标样式设置 2020-06-29 01:06:06 +08:00
lyswhut
f18875682b 修复透明度按钮提示错误的问题 2020-06-28 12:04:00 +08:00
lyswhut
897a17e303 修复版本对比问题 2020-06-27 21:22:02 +08:00
lyswhut
c9d0aafbfa 优化右击菜单逻辑 2020-06-27 13:43:27 +08:00
lyswhut
fbd2a57a35 新增我的列表内歌曲搜索 2020-06-26 16:24:23 +08:00
lyswhut
ccd5c91283 添加右击菜单对不可用音源的处理 2020-06-26 12:21:07 +08:00
lyswhut
c78b9f07a2 修复聚合搜索音源标签的皮肤配色问题 2020-06-26 12:17:11 +08:00
lyswhut
ffc3300fe4 移除无用代码 2020-06-26 01:12:08 +08:00
lyswhut
b02d32929c 优化默认设置 2020-06-26 00:17:09 +08:00
lyswhut
e29d3e3eaa 隐藏下载弹窗中不可用的音质 2020-06-25 23:39:48 +08:00
lyswhut
5d91643637 歌曲列表右击菜单新增跳转到当前歌曲源官方详情页菜单 2020-06-25 23:10:38 +08:00
lyswhut
9eec1adf77 支持加载大于1000首歌的歌单 2020-06-25 21:29:05 +08:00
lyswhut
9cebaa0d97 新增歌词区域触摸板、鼠标滚轮等对歌词滚动的支持 2020-06-25 19:39:12 +08:00
lyswhut
48cfebd5ae 改进聚合搜索的搜索结果排序 2020-06-25 19:37:54 +08:00
lyswhut
0c3899840b 添加静音处理 2020-06-25 15:18:56 +08:00
lyswhut
93bab6bbdd 优化逻辑 2020-06-24 22:55:47 +08:00
lyswhut
69368b884f 修复默认快捷键绑定问题 2020-06-24 20:20:19 +08:00
lyswhut
f59b2d26ad 优化播放控制按钮交互效果 2020-06-24 16:18:05 +08:00
lyswhut
32ffe33e1a 修复禁用快捷键的情况下设置全局快捷键的bug,优化启用/禁用视觉效果 2020-06-24 16:17:24 +08:00
lyswhut
cc5d119ba4 新增首次运行时自动根据当前系统使用的语言设置软件显示的语言 2020-06-24 16:12:02 +08:00
lyswhut
637727faa0 修改播放界面布局 2020-06-24 14:18:32 +08:00
lyswhut
9b82cdfe53 新增快捷键设置 2020-06-24 01:20:56 +08:00
lyswhut
2424c76c50 修复Linux下无法ctrl+a全选的问题 2020-06-20 15:32:39 +08:00
lyswhut
f0dc76c7a4 修复某些情况下从托盘还原窗口后无法操作的问题 2020-06-20 13:15:23 +08:00
lyswhut
798f5cd0a6 优化界面显示时的效果 2020-06-15 20:28:41 +08:00
lyswhut
a904896269 修复进度条开始部分无法选择的问题 2020-06-14 18:07:41 +08:00
lyswhut
be482bd683 优化打开歌单输入框宽度 2020-06-14 00:22:30 +08:00
lyswhut
e9a624c896 优化选择逻辑 2020-06-13 16:11:43 +08:00
lyswhut
b3a13a25f0 优化UI 2020-06-12 19:15:40 +08:00
lyswhut
30e55f5acb 修复事件绑定bug 2020-06-11 22:24:55 +08:00
lyswhut
7a67263cf9 添加阴影 2020-06-11 22:18:54 +08:00
lyswhut
b811906bae 新增软件最小化、关闭按钮位置设置 2020-06-11 21:41:15 +08:00
lyswhut
2a81d41c2c 新增桌面歌词 2020-06-11 21:16:59 +08:00
dependabot[bot]
51d0037a4b Bump websocket-extensions from 0.1.3 to 0.1.4 (#225)
Bumps [websocket-extensions](https://github.com/faye/websocket-extensions-node) from 0.1.3 to 0.1.4.
- [Release notes](https://github.com/faye/websocket-extensions-node/releases)
- [Changelog](https://github.com/faye/websocket-extensions-node/blob/master/CHANGELOG.md)
- [Commits](https://github.com/faye/websocket-extensions-node/compare/0.1.3...0.1.4)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-06-07 21:19:55 +08:00
lyswhut
25fc0226aa 移除多余代码 2020-05-24 15:12:35 +08:00
lyswhut
cfef06a15b 新增我的列表右键菜单复制歌曲名选项 2020-05-23 22:44:19 +08:00
lyswhut
5ce6aafc07 修改排行榜顺序 2020-05-23 22:31:24 +08:00
lyswhut
fe833033df 改变排行榜布局,新增更多排行榜 2020-05-23 22:24:20 +08:00
lyswhut
a47be148c8 更新electron到 v9 2020-05-23 16:32:28 +08:00
lyswhut
af426d28c1 修复错误更新试听列表外的歌曲时间的问题 2020-05-20 23:54:49 +08:00
lyswhut
0332b81d58 完善自定义列表、新增右键菜单 2020-05-20 22:52:37 +08:00
lyswhut
88b416c949 更新描述 2020-05-16 20:44:12 +08:00
lyswhut
66c4e62d9b 更新配置 2020-05-16 20:39:59 +08:00
lyswhut
9091c2c346 Merge branch 'v1' into dev 2020-05-16 20:29:27 +08:00
lyswhut
e29ca2e79c 整理配置逻辑 2020-05-16 20:19:58 +08:00
lyswhut
afc3f48f51 新增自定义列表 2020-05-16 19:21:21 +08:00
lyswhut
eed8b36dc1 Merge branch 'dev' into v1 2020-05-15 11:51:48 +08:00
lyswhut
b20942d254 修改按键绑定机制 2020-05-15 11:50:22 +08:00
lyswhut
e707beffd6 Merge branch 'dev' into v1 2020-05-14 13:24:34 +08:00
lyswhut
747c75fb18 更新依赖 2020-05-14 09:19:19 +08:00
lyswhut
42f0a9df4b 改进媒体设备改变暂停播放逻辑 2020-05-13 23:17:51 +08:00
lyswhut
10ee6bcc7f 新增酷我歌单显示歌曲文件大小 2020-05-10 18:55:20 +08:00
lyswhut
bcba194a31 Merge branch 'dev' into v1 2020-05-09 20:42:04 +08:00
lyswhut
8605f63ac6 完善音频输出设备改变暂停机制 2020-05-09 20:41:48 +08:00
lyswhut
a02827a7ac Merge branch 'dev' into v1 2020-05-08 20:24:12 +08:00
lyswhut
16903b0f5a 修复可能导致渲染进程无响应的问题 2020-05-08 20:23:23 +08:00
lyswhut
26bdf25423 新增所选音频输出设备被移除时是否暂停播放 2020-05-08 19:14:44 +08:00
lyswhut
3c0f4be533 Merge branch 'dev' into v1 2020-05-07 23:12:11 +08:00
lyswhut
ecf9dce758 修复 tab 组件边框溢出问题 2020-05-07 23:11:59 +08:00
lyswhut
90e40d5245 Merge branch 'dev' into v1 2020-05-07 20:38:03 +08:00
lyswhut
b76424ba9b 修改设置-列表-是否显示歌曲源的默认设置为选中 2020-05-06 18:22:24 +08:00
lyswhut
ded75563e9 更新图标 2020-05-05 20:56:25 +08:00
lyswhut
1eb9920bcf 更新图标 2020-05-04 12:19:57 +08:00
lyswhut
7e029b5495 修复某些情况下无法开始下载任务的问题 2020-05-04 01:53:37 +08:00
lyswhut
37f9491d5e 修改布局 2020-05-04 01:19:49 +08:00
lyswhut
6aec78e153 调整代码顺序 2020-05-03 12:49:57 +08:00
lyswhut
685e34bc83 修复某些情况下可能导致的音源输出问题 2020-05-03 12:30:29 +08:00
lyswhut
fb828788df 更新图标 2020-05-03 12:12:49 +08:00
lyswhut
eff49af00c 改为构建 x64 pacman 包 2020-05-02 21:37:54 +08:00
lyswhut
7f02b85eb8 修复报错 2020-05-02 21:20:28 +08:00
lyswhut
1de84c66d6 更新配置 2020-05-02 21:15:34 +08:00
lyswhut
e382ccff9b 安装rpm 2020-05-02 20:57:48 +08:00
lyswhut
33e6202835 新增rpm、pacman包的打包 2020-05-02 20:42:53 +08:00
lyswhut
bd8b8d5b64 配置图标 2020-05-02 20:22:05 +08:00
lyswhut
db9dc18118 为appImage配置图标 2020-05-02 20:10:53 +08:00
lyswhut
7cc373c07f 更新版本 2020-05-02 20:03:07 +08:00
lyswhut
aa87640687 修复Linux版开启托盘无法退出的问题 2020-05-02 20:01:36 +08:00
lyswhut
6724912c73 修改布局 2020-05-02 19:52:43 +08:00
lyswhut
2cabe6e58e 修复多选机制问题 2020-05-02 18:49:24 +08:00
lyswhut
33383cd3bd 初步完善 2020-05-02 18:21:20 +08:00
280 changed files with 16809 additions and 6236 deletions

View File

@@ -17,6 +17,8 @@
]
],
"plugins": [
"@babel/plugin-syntax-dynamic-import"
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-transform-modules-umd",
"@babel/plugin-transform-runtime"
]
}

View File

@@ -11,6 +11,12 @@ matrix:
- os: linux
dist: trusty
sudo: required
addons:
apt:
packages:
- rpm
- bsdtar
cache:
directories:

View File

@@ -6,6 +6,108 @@ Project versioning adheres to [Semantic Versioning](http://semver.org/).
Commit convention is based on [Conventional Commits](http://conventionalcommits.org).
Change log format is based on [Keep a Changelog](http://keepachangelog.com/).
## [1.1.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.0.1...v1.1.0) - 2020-09-18
### 新增
- 在歌单详情界面新增播放当前歌单按钮、收藏歌单按钮,注:播放歌单不会将歌曲添加到试听列表
- 新增`不允许将歌词窗口拖出主屏幕之外`的设置项,默认开启,在连接多个屏幕时想要拖动到其他屏幕时可关闭此设置
- 新增大部分平台的歌词翻译,感谢 @InoriHimea 提供的[krc解码算法](https://github.com/lyswhut/lx-music-desktop/issues/296#issuecomment-683285784)
- 新增`显示歌词翻译`设置,默认开启,仅支持某些平台,注:无论该设置是否开启,嵌入或下载歌词时都不会带上翻译
- 新增`显示切换动画`设置,默认开启,关闭时将基本禁用软件内的所有切换动画
- 播放状态栏新增桌面歌词的开关、播放模式的切换、歌曲的收藏按钮Thanks to @andylow for the [icon](https://github.com/lyswhut/lx-music-desktop/pull/309)!
### 修复
- 修复使用全局快捷键还原窗口时,窗口没有获取焦点的问题
- 修复我的列表搜索对最后一个字符的匹配问题
- 修复窗口在`较小`模式下最小化/关闭按钮不居中的问题
### 优化
- 桌面歌词当前播放行改为上下居中
- 为区分静音状态,静音时音量条会变淡,调整音量条时将会取消静音
- 优化随机播放机制,现在通过`下一曲`切换歌曲时,直到播放完整个列表之前将不会再随机到之前播放过的歌曲,并且通过`上一曲`可以正确播放上一首歌曲
- 当下载目录没有写入权限时将显示没有写入权限的提示
### 移除
- 移除默认的全局声音媒体快捷键接管
- 移除对百度音乐的支持因百度音乐原有的大部分API失效而且该平台相对其他平台来说音乐太少了可有可无以后再看情况恢复
### 其他
- 更新electron到 10.1.2
## [1.0.1](https://github.com/lyswhut/lx-music-desktop/compare/v1.0.0...v1.0.1) - 2020-07-25
### 优化
- 对我的列表歌曲搜索结果进行相似度排序
### 修复
- 修复在 Windows 系统下缩放比非100%时,拖动桌面歌词会自动加大桌面歌词窗口的问题
## [1.0.0](https://github.com/lyswhut/lx-music-desktop/compare/v0.18.2...v1.0.0) - 2020-07-24
### 新增
- 新增`rpm``pacman`包的构建(未测试可用性)
- 新增因系统音频设备列表改变导致的当前音频输出设备改变时是否暂停播放的设置,默认关闭
- 新增歌曲列表右击菜单
- 新增自定义列表,创建列表的按钮在表头`#`左侧,鼠标移上去才会显示;编辑列表名字时,按`ESC`键可快速取消编辑,按回车键或使输入框失去焦点即可保存列表名字,右击列表可编辑已创建的列表,“试听列表”与“我的收藏”两个列表固定不可编辑
- 改变排行榜布局,新增更多排行榜
- 新增我的列表右键菜单复制歌曲名选项
- 新增桌面歌词,默认关闭,可到设置或者托盘菜单开启(建议使用全局快捷键控制);调整字体大小、透明度时,鼠标左击按钮正常调整,右击微调;**Windows 7未开启Aero效果时桌面歌词会有问题**详情看常见问题解决Linux版桌面歌词有问题以后再尝试优化
- 新增“清热板蓝”皮肤
- 新增软件最小化、关闭按钮位置设置MAC版默认为左边非MAC为右边不想用默认的可到设置修改
- 新增快捷键设置,软件内快捷键默认开启,全局快捷键默认关闭(注:若想开启蓝牙耳机切歌需开启全局快捷键,当快捷键被中划线划掉时,表示当前快捷键被其他程序占用导致注册失败)
- 新增首次运行时自动根据当前系统使用的语言设置软件显示的语言
- 新增歌词区域的触摸板、鼠标滚轮等对歌词滚动的支持
- 为了方便支持正版资源,歌曲列表右击菜单新增跳转到当前歌曲源官方详情页菜单(注意:在本版本之前添加的虾米源歌曲无法跳转详情页,需要移除后重新搜索添加)
- 新增我的列表内歌曲搜索,在我的列表按`ctrl+f`将显示搜索框;鼠标滑过或键盘上下方向键选择搜索结果;鼠标点击或按回车键定位选中的歌曲;按`ctrl`键的情况下鼠标点击或按回车键确认定位歌曲时,将会在定位歌曲结束后播放该歌曲(搜索框激活的情况下按`esc`可快速清空搜索框/关闭搜索框)
- 新增托盘图标样式设置,可到设置-其他切换
- 新增开关下载功能控制,默认关闭,可到设置-下载设置开启
- 新增将歌词嵌入音频文件中,默认关闭,可到设置-下载设置开启
- 新增当列表文件损坏时对损坏文件的备份,若出现该情况可打开`%HOMEPATH%\AppData\Roaming\lx-music-desktop`找到`playList.json.bak`尝试手动修复列表文件,列表文件以`JSON`格式存储
- 新增在歌单详情列表按退格Backspace键可快速返回歌单列表
### 优化
- 改进歌曲切换时的歌词滚动效果
- 优化批量添加、删除播放列表的歌曲操作逻辑,大幅提升批量添加、删除列表歌曲的流畅度
- 改进歌单列表展示
- **改进聚合搜索的搜索结果排序**,修复当某些源搜索失败时导致其他源无法显示结果的问题,现在聚合搜索已达到最初的理想效果,为了使排序更精确,**建议同时输入 歌曲名 歌手名 搜索**(歌曲名在前歌手名在后),欢迎体验~
- 压缩备份数据文件大小
### 修复
- 修复按住`Ctrl`等键触发多选机制时不松开按键的情况下切换到其他窗口后再松开按键,这时切回软件不按按键都处在多选模式的问题
- 修复Linux版开启托盘无法退出的问题
- 修复某些情况下可能导致的音源输出问题
- 修复某些情况下无法开始下载任务的问题
- 修复 tab 组件边框溢出问题
- 修复错误更新试听列表外的歌曲时间的问题
- 修复网易音乐源歌单、排行榜歌曲列表加载显示的数量与实际不对的问题同时支持加载大于1000首歌的歌单歌曲大于1000首会分页注意目前软件一下子显示太多歌曲时会卡顿不建议在同一列表内添加太多歌曲
- 修复歌曲图片链接没有扩展名的情况下无法嵌入图片的问题
- 修复无法检测最新版本时弹窗提示的显示
- 修复某些情况下从托盘还原窗口后无法操作的问题
- 修复Linux下无法`ctrl+a`全选的问题
- 修复主题背景图片覆盖不全的问题
- 修复聚合搜索音源标签的皮肤配色问题
### 更变
- 修改设置-列表-是否显示歌曲源的默认设置为选中(该变更不影响之前的设置)
- 移除浮动按钮,现在在多选完成后可鼠标右击随意一项在弹出的右键菜单中进行原来悬浮按钮的操作
- 为了避免出现误会,现在下载弹窗中不可用的音质将直接隐藏
- 更改初始设置的搜索设置为聚合搜索(该变更不影响之前的设置)
### 其他
- 更新 Electron 到 9.1.1
## [0.18.2](https://github.com/lyswhut/lx-music-desktop/compare/v0.18.1...v0.18.2) - 2020-05-02
### 修复

41
FAQ.md
View File

@@ -4,7 +4,7 @@
## 软件为什么没有桌面歌词与自定义列表功能
洛雪音乐的最初定位不是作为播放器开发的,它主要用于**查找歌曲**,软件的播放功能仅用于试听,不建议用作为常用播放器使用,因此无桌面、界面歌词,不可自定义列表等功能
洛雪音乐的最初定位不是作为播放器开发的,它主要用于**查找歌曲**,软件的播放功能仅用于试听,不建议用作为常用播放器使用。
## 歌曲无法试听与下载
@@ -23,6 +23,40 @@
1. 将DNS改成自动获取试试
2. 手动把DNS改一下不要用360的DNS可以把DNS改成`114.114.114.114``8.8.8.8`
## 列表多选
从v0.18.0起,列表多选需要键盘配合,想要多选前需按下`Shift``Ctrl`键然后再鼠标点击想要选中的内容即可触发多选机制,其中`Shift`键用于连续选择,`Ctrl`键用于不连续选择,`Ctrl+a`用于快速全选。
- 例子一想要选中1-5项则先按下`Shift`键后,鼠标点击第一项,再点击第五项即可完成选择;
- 例子二想要选中1项与第3项则先按下`Ctrl`键后,鼠标点击第一项,再点击第三项即可完成选择;
- 例子三:想要选中当前列表的全部内容,键盘先按下`Ctrl`键不放,然后按`a`键,即可完成选择。
`Shift``Ctrl`选择时,鼠标点击未选中的内容会将其选中,点击已选择的内容会将其取消选择,若想全部取消选择,在不按`Shift``Alt`键的情况下,随意点击列表里的一项内容即可全部取消选择。(P.S`Ctrl`键对应Mac OS上的`Command`键)
## 播放整个歌单或排行榜
播放在线列表内的歌曲需要将它们都添加到我的列表才能播放,你可以全选列表内的歌曲然后添加到现有列表或者新创建的列表,然后去播放该列表内的歌曲。
## 桌面歌词显示异常
### Windows 7 系统桌面歌词显示异常
Windows 7 未开启 Aero 效果时桌面歌词会有问题,详情看下面的**Windows 7 下界面异常(界面显示不完整)**方法解决。
### MAC OS 系统、桌面歌词有残留阴影
此问题似乎是Electron的Bug翻阅electron的issue列表发现该Bug以存在很久了遗憾的是没有一直都没有修复由于我没有装MAC平台的电脑没法重现就没再去electron提issue更多信息看
- <https://github.com/electron/electron/issues/21173>
- <https://github.com/electron/electron/issues/14304>
### Linux 系统下桌面歌词窗口异常
目前在 Ubuntu 18.10 下第一次开启桌面歌词时歌词窗口会变白,需要关闭后再开启,
其他 Linux 系统未测试,如有异常也是意料之中,目前不打算去处理 Linux 平台的桌面歌词问题。
## 软件安装包说明
软件发布页及网盘中有多个类型的安装文件,以下是对这些类型文件的说明:
@@ -74,8 +108,7 @@
- 若你之前可以安装成功,但现在安装失败,就去**控制面板-程序和功能**或用第三方卸载工具看下有没有之前的版本残留,若同时在不同路径下安装了多个版本就可能会出现该问题,这种情况卸载掉所有版本重新安装即可
- 清理安装路径下的残留文件
- 以管理员权限打开`cmd`,输入`sfc /scannow`回车等待检查完成重启电脑
- 若还是不行我也没办法了。。
- 清理注册表(建议用清理工具清理)
## 缺少`xxx.dll`
@@ -86,7 +119,7 @@
## 杀毒软件提示有病毒或恶意行为
本人只能保证我写的代码不包含任何**恶意代码**、**收集用户信息**的行为并且软件代码已开源请自行查阅软件安装包也是由CI拉取源代码构建构建日志[windows包](https://ci.appveyor.com/project/lyswhut/lx-music-desktop)、[Mac/Linux包](https://travis-ci.org/lyswhut/lx-music-desktop)<br>
本人只能保证我写的代码不包含任何**恶意代码**、**收集用户信息**的行为并且软件代码已开源请自行查阅软件安装包也是由CI拉取源代码构建构建日志[windows包](https://ci.appveyor.com/project/lyswhut/lx-music-desktop)、[Mac/Linux包](https://travis-ci.com/github/lyswhut/lx-music-desktop)<br>
尽管如此但这不意味着软件是100%安全的,由于软件使用了第三方依赖,当这些依赖存在恶意行为时([供应链攻击](https://docs.microsoft.com/zh-cn/windows/security/threat-protection/intelligence/supply-chain-malware)),软件也将会受到牵连,所以我只能尽量选择使用较多人用、信任度较高的依赖。<br>
当然,以上说明建立的前提是在你所用的安装包是从**本项目主页上写的链接**下载的,或者有相关能力者还可以下载源代码自己构建安装包。

View File

@@ -3,7 +3,7 @@
<p align="center">
<a href="https://github.com/lyswhut/lx-music-desktop/releases"><img src="https://img.shields.io/github/release/lyswhut/lx-music-desktop" alt="Release version"></a>
<a href="https://ci.appveyor.com/project/lyswhut/lx-music-desktop"><img src="https://ci.appveyor.com/api/projects/status/flrsqd5ymp8fnte5?svg=true" alt="Build status"></a>
<a href="https://travis-ci.org/lyswhut/lx-music-desktop"><img src="https://travis-ci.org/lyswhut/lx-music-desktop.svg?branch=master" alt="Build status"></a>
<a href="https://travis-ci.com/lyswhut/lx-music-desktop"><img src="https://travis-ci.com/lyswhut/lx-music-desktop.svg?branch=master" alt="Build status"></a>
<a href="https://electronjs.org/releases/stable"><img src="https://img.shields.io/github/package-json/dependency-version/lyswhut/lx-music-desktop/dev/electron/master" alt="Electron version"></a>
<!-- <a href="https://github.com/lyswhut/lx-music-desktop/releases"><img src="https://img.shields.io/github/downloads/lyswhut/lx-music-desktop/latest/total" alt="Downloads"></a> -->
<a href="https://github.com/lyswhut/lx-music-desktop/tree/dev"><img src="https://img.shields.io/github/package-json/v/lyswhut/lx-music-desktop/dev" alt="Dev branch version"></a>
@@ -36,7 +36,7 @@
所用技术栈:
- Electron 8
- Electron 10
- Vue 2
已支持的平台:
@@ -47,7 +47,7 @@
软件变化请查看:[更新日志](https://github.com/lyswhut/lx-music-desktop/blob/master/CHANGELOG.md)<br>
软件下载请转到:[发布页面](https://github.com/lyswhut/lx-music-desktop/releases)<br>
或者到网盘下载网盘内有MAC、windows版`https://t-s.lanzous.com/b0bf2cfa/` 密码:`glqw`<br>
或者到网盘下载网盘内有MAC、windows版`https://www.lanzoux.com/b0bf2cfa/` 密码:`glqw`<br>
使用常见问题请转至:[常见问题](https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md)
### 源码使用方法

View File

@@ -3,6 +3,6 @@ const isDev = process.env.NODE_ENV === 'development'
module.exports = {
modules: {
localIdentName: isDev ? '[folder]-[name]--[local]--[hash:base64:5]' : '[hash:base64:5]',
exportLocalsConvention: 'camelCase',
},
localsConvention: 'camelCase',
}

View File

@@ -1,5 +1,5 @@
const path = require('path')
const merge = require('webpack-merge')
const { merge } = require('webpack-merge')
const webpack = require('webpack')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')

View File

@@ -1,5 +1,5 @@
const path = require('path')
const merge = require('webpack-merge')
const { merge } = require('webpack-merge')
const webpack = require('webpack')
const baseConfig = require('./webpack.config.base')

View File

@@ -7,17 +7,19 @@ const Spinnies = require('spinnies')
const mainConfig = require('./main/webpack.config.prod')
const rendererConfig = require('./renderer/webpack.config.prod')
const rendererLyricConfig = require('./renderer-lyric/webpack.config.prod')
const errorLog = chalk.bgRed.white(' ERROR ') + ' '
const okayLog = chalk.bgGreen.white(' OKAY ') + ' '
function build() {
del.sync(['dist/electron', 'build'])
del.sync(['dist/electron/**', 'build/**'])
const spinners = new Spinnies({ color: 'blue' })
spinners.add('main', { text: 'main building' })
spinners.add('renderer', { text: 'renderer building' })
spinners.add('renderer-lyric', { text: 'renderer-lyric building' })
let results = ''
// m.on('success', () => {
@@ -52,6 +54,15 @@ function build() {
console.error(`\n${err}\n`)
process.exit(1)
}),
pack(rendererLyricConfig).then(result => {
results += result + '\n\n'
spinners.succeed('renderer-lyric', { text: 'renderer-lyric build success!' })
}).catch(err => {
spinners.fail('renderer-lyric', { text: 'renderer-lyric build fail :(' })
console.log(`\n ${errorLog}failed to build renderer-lyric process`)
console.error(`\n${err}\n`)
process.exit(1)
}),
]).then(handleSuccess)
}

View File

@@ -0,0 +1,133 @@
const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const HTMLPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const vueLoaderConfig = require('../vue-loader.config')
const { mergeCSSLoader } = require('../utils')
const isDev = process.env.NODE_ENV === 'development'
module.exports = {
target: 'electron-renderer',
entry: {
'renderer-lyric': path.join(__dirname, '../../src/renderer-lyric/main.js'),
},
output: {
filename: '[name].js',
libraryTarget: 'commonjs2',
path: path.join(__dirname, '../../dist/electron'),
publicPath: './',
},
resolve: {
alias: {
'@': path.join(__dirname, '../../src/renderer'),
common: path.join(__dirname, '../../src/common'),
},
extensions: ['*', '.js', '.json', '.vue', '.node'],
},
module: {
rules: [
{
test: /\.(vue|js)$/,
use: {
loader: 'eslint-loader',
options: {
formatter: require('eslint-formatter-friendly'),
},
},
exclude: /node_modules/,
enforce: 'pre',
},
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig,
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.css$/,
oneOf: mergeCSSLoader(),
},
{
test: /\.less$/,
oneOf: mergeCSSLoader({
loader: 'less-loader',
options: {
sourceMap: true,
},
}),
},
{
test: /\.styl(:?us)?$/,
oneOf: mergeCSSLoader({
loader: 'stylus-loader',
options: {
sourceMap: true,
},
}),
},
{
test: /\.pug$/,
oneOf: [
// Use pug-plain-loader handle .vue file
{
resourceQuery: /vue/,
use: ['pug-plain-loader'],
},
// Use pug-loader handle .pug file
{
use: ['pug-loader'],
},
],
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'imgs/[name]--[folder].[ext]',
},
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'media/[name]--[folder].[ext]',
},
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'fonts/[name]--[folder].[ext]',
},
},
],
},
performance: {
maxEntrypointSize: 300000,
},
plugins: [
new HTMLPlugin({
filename: 'lyric.html',
template: path.join(__dirname, '../../src/renderer-lyric/index.pug'),
isProd: process.env.NODE_ENV == 'production',
browser: process.browser,
__dirname,
}),
new VueLoaderPlugin(),
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: isDev ? '[name].css' : '[name].[contenthash:8].css',
chunkFilename: isDev ? '[id].css' : '[id].[contenthash:8].css',
}),
],
}

View File

@@ -0,0 +1,28 @@
const path = require('path')
const webpack = require('webpack')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const { merge } = require('webpack-merge')
const baseConfig = require('./webpack.config.base')
module.exports = merge(baseConfig, {
mode: 'development',
devtool: 'eval-source-map',
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
new FriendlyErrorsPlugin(),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"development"',
ELECTRON_DISABLE_SECURITY_WARNINGS: 'true',
},
__static: `"${path.join(__dirname, '../../src/static').replace(/\\/g, '\\\\')}"`,
}),
],
performance: {
hints: false,
},
})

View File

@@ -0,0 +1,56 @@
const path = require('path')
const webpack = require('webpack')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const { merge } = require('webpack-merge')
const baseConfig = require('./webpack.config.base')
const { dependencies } = require('../../package.json')
let whiteListedModules = ['vue']
module.exports = merge(baseConfig, {
mode: 'production',
devtool: false,
externals: [
...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)),
],
plugins: [
new CopyWebpackPlugin({
patterns: [
{
from: path.join(__dirname, '../../src/static'),
to: path.join(__dirname, '../../dist/electron/static'),
},
],
}),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"',
},
}),
new webpack.NamedChunksPlugin(),
],
optimization: {
minimizer: [
new TerserPlugin({
cache: true,
parallel: true,
sourceMap: false, // set to true if you want JS source maps
}),
new OptimizeCSSAssetsPlugin({}),
],
},
performance: {
hints: 'warning',
},
node: {
__dirname: false,
__filename: false,
},
})

View File

@@ -1,9 +1,12 @@
const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const HTMLPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const vueLoaderConfig = require('../vue-loader.config')
const { mergeCSSLoader } = require('../utils')
const isDev = process.env.NODE_ENV === 'development'
module.exports = {
target: 'electron-renderer',
@@ -46,6 +49,28 @@ module.exports = {
loader: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.css$/,
oneOf: mergeCSSLoader(),
},
{
test: /\.less$/,
oneOf: mergeCSSLoader({
loader: 'less-loader',
options: {
sourceMap: true,
},
}),
},
{
test: /\.styl(:?us)?$/,
oneOf: mergeCSSLoader({
loader: 'stylus-loader',
options: {
sourceMap: true,
},
}),
},
{
test: /\.pug$/,
oneOf: [
@@ -92,11 +117,17 @@ module.exports = {
plugins: [
new HTMLPlugin({
filename: 'index.html',
template: path.join(__dirname, '../../src/index.pug'),
template: path.join(__dirname, '../../src/renderer/index.pug'),
isProd: process.env.NODE_ENV == 'production',
browser: process.browser,
__dirname,
}),
new VueLoaderPlugin(),
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: isDev ? '[name].css' : '[name].[contenthash:8].css',
chunkFilename: isDev ? '[id].css' : '[id].[contenthash:8].css',
}),
],
}

View File

@@ -2,41 +2,13 @@ const path = require('path')
const webpack = require('webpack')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const merge = require('webpack-merge')
const { merge } = require('webpack-merge')
const baseConfig = require('./webpack.config.base')
const { mergeCSSLoaderDev } = require('../utils')
module.exports = merge(baseConfig, {
mode: 'development',
devtool: 'eval-source-map',
module: {
rules: [
{
test: /\.css$/,
oneOf: mergeCSSLoaderDev(),
},
{
test: /\.less$/,
oneOf: mergeCSSLoaderDev({
loader: 'less-loader',
options: {
sourceMap: true,
},
}),
},
{
test: /\.styl(:?us)?$/,
oneOf: mergeCSSLoaderDev({
loader: 'stylus-loader',
options: {
sourceMap: true,
},
}),
},
],
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),

View File

@@ -1,14 +1,12 @@
const path = require('path')
const webpack = require('webpack')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const merge = require('webpack-merge')
const { merge } = require('webpack-merge')
const baseConfig = require('./webpack.config.base')
const { mergeCSSLoaderProd } = require('../utils')
const { dependencies } = require('../../package.json')
let whiteListedModules = ['vue']
@@ -20,48 +18,20 @@ module.exports = merge(baseConfig, {
externals: [
...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)),
],
module: {
rules: [
{
test: /\.css$/,
oneOf: mergeCSSLoaderProd(),
},
{
test: /\.less$/,
oneOf: mergeCSSLoaderProd({
loader: 'less-loader',
options: {
sourceMap: true,
},
}),
},
{
test: /\.styl(:?us)?$/,
oneOf: mergeCSSLoaderProd({
loader: 'stylus-loader',
options: {
sourceMap: true,
},
}),
},
],
},
plugins: [
new CopyWebpackPlugin([
{
from: path.join(__dirname, '../../src/static'),
to: path.join(__dirname, '../../dist/electron/static'),
ignore: ['.*'],
},
]),
new CopyWebpackPlugin({
patterns: [
{
from: path.join(__dirname, '../../src/static'),
to: path.join(__dirname, '../../dist/electron/static'),
},
],
}),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"',
},
}),
new MiniCssExtractPlugin({
filename: '[name].css',
}),
new webpack.NamedChunksPlugin(),
],
optimization: {

View File

@@ -12,6 +12,7 @@ const webpackHotMiddleware = require('webpack-hot-middleware')
const mainConfig = require('./main/webpack.config.dev')
const rendererConfig = require('./renderer/webpack.config.dev')
const rendererLyricConfig = require('./renderer-lyric/webpack.config.dev')
let electronProcess = null
let manualRestart = false
@@ -65,6 +66,53 @@ function startRenderer() {
})
}
function startRendererLyric() {
return new Promise((resolve, reject) => {
// rendererConfig.entry.renderer = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.renderer)
// rendererConfig.mode = 'development'
const compiler = webpack(rendererLyricConfig)
hotMiddleware = webpackHotMiddleware(compiler, {
log: false,
heartbeat: 2500,
})
compiler.hooks.compilation.tap('compilation', compilation => {
// console.log(Object.keys(compilation.hooks))
HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync('html-webpack-plugin-after-emit', (data, cb) => {
hotMiddleware.publish({ action: 'reload' })
cb()
})
})
compiler.hooks.done.tap('done', stats => {
// logStats('Renderer', 'Compile done')
// logStats('Renderer', stats)
})
const server = new WebpackDevServer(
compiler,
{
contentBase: path.join(__dirname, '../'),
quiet: true,
hot: true,
historyApiFallback: true,
clientLogLevel: 'warning',
overlay: {
errors: true,
},
before(app, ctx) {
app.use(hotMiddleware)
ctx.middleware.waitUntilValid(() => {
resolve()
})
},
},
)
server.listen(9081)
})
}
function startMain() {
return new Promise((resolve, reject) => {
// mainConfig.entry.main = [path.join(__dirname, '../src/main/index.dev.js')].concat(mainConfig.entry.main)
@@ -140,6 +188,7 @@ function init() {
const spinners = new Spinnies({ color: 'blue' })
spinners.add('main', { text: 'main compiling' })
spinners.add('renderer', { text: 'renderer compiling' })
spinners.add('renderer-lyric', { text: 'renderer-lyric compiling' })
function handleSuccess(name) {
spinners.succeed(name, { text: name + ' compile success!' })
}
@@ -149,6 +198,7 @@ function init() {
Promise.all([
startRenderer().then(() => handleSuccess('renderer')).catch(() => handleFail('renderer')),
startRendererLyric().then(() => handleSuccess('renderer-lyric')).catch(() => handleFail('renderer-lyric')),
startMain().then(() => handleSuccess('main')).catch(() => handleFail('main')),
]).then(startElectron).catch(err => {
console.error(err)

View File

@@ -2,62 +2,21 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const cssLoaderConfig = require('./css-loader.config')
const chalk = require('chalk')
// merge css-loader in development
exports.mergeCSSLoaderDev = beforeLoader => {
const loader = [
// 这里匹配 `<style module>`
{
resourceQuery: /module/,
use: [
'vue-style-loader',
{
loader: 'css-loader',
options: Object.assign({
sourceMap: true,
}, cssLoaderConfig),
},
{
loader: 'postcss-loader',
options: {
sourceMap: true,
},
},
],
},
// 这里匹配普通的 `<style>` 或 `<style scoped>`
{
use: [
'vue-style-loader',
{
loader: 'css-loader',
options: {
sourceMap: true,
},
},
{
loader: 'postcss-loader',
options: {
sourceMap: true,
},
},
],
},
]
if (beforeLoader) {
loader[0].use.push(beforeLoader)
loader[1].use.push(beforeLoader)
}
return loader
}
const isDev = process.env.NODE_ENV === 'development'
// merge css-loader in production
exports.mergeCSSLoaderProd = beforeLoader => {
// merge css-loader
exports.mergeCSSLoader = beforeLoader => {
const loader = [
// 这里匹配 `<style module>`
{
resourceQuery: /module/,
use: [
MiniCssExtractPlugin.loader,
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: isDev,
},
},
{
loader: 'css-loader',
options: cssLoaderConfig,
@@ -68,7 +27,12 @@ exports.mergeCSSLoaderProd = beforeLoader => {
// 这里匹配普通的 `<style>` 或 `<style scoped>`
{
use: [
MiniCssExtractPlugin.loader,
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: isDev,
},
},
'css-loader',
'postcss-loader',
],

View File

@@ -1,97 +0,0 @@
const path = require('path')
const webpack = require('webpack')
const HTMLPlugin = require('html-webpack-plugin')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const vueLoaderConfig = require('../vue-loader.config')
module.exports = {
target: 'web',
entry: path.join(__dirname, '../../src/renderer/main.js'),
output: {
path: path.join(__dirname, '../../dist/web'),
},
resolve: {
alias: {
'@': path.join(__dirname, '../../src/renderer'),
common: path.join(__dirname, '../../src/common'),
},
extensions: ['*', '.js', '.vue', '.json', '.css'],
},
module: {
rules: [
{
test: /\.(vue|js)$/,
use: {
loader: 'eslint-loader',
options: {
formatter: require('eslint-formatter-friendly'),
},
},
exclude: /node_modules/,
enforce: 'pre',
},
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig,
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.pug$/,
oneOf: [
// Use pug-plain-loader handle .vue file
{
resourceQuery: /vue/,
use: ['pug-plain-loader'],
},
// Use pug-loader handle .pug file
{
use: ['pug-loader'],
},
],
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: {
loader: 'url-loader',
options: {
limit: 10000,
name: 'imgs/[name].[ext]',
},
},
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
use: {
loader: 'url-loader',
options: {
limit: 10000,
name: 'fonts/[name].[ext]',
},
},
},
],
},
performance: {
maxEntrypointSize: 300000,
},
plugins: [
new VueLoaderPlugin(),
new HTMLPlugin({
filename: 'index.html',
template: path.resolve(__dirname, '../../src/index.pug'),
nodeModules: false,
isProd: process.env.NODE_ENV == 'production',
browser: process.browser,
__dirname,
}),
new webpack.DefinePlugin({
'process.env.IS_WEB': 'true',
}),
],
}

View File

@@ -1,57 +0,0 @@
const path = require('path')
const webpack = require('webpack')
const merge = require('webpack-merge')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const baseConfig = require('./webpack.config.base')
const { mergeCSSLoaderDev } = require('../utils')
module.exports = merge(baseConfig, {
mode: 'development',
devtool: 'eval-source-map',
output: {
filename: '[name].js',
path: path.join(__dirname, '../../dist/web'),
},
module: {
rules: [
{
test: /\.css$/,
oneOf: mergeCSSLoaderDev(),
},
{
test: /\.less$/,
oneOf: mergeCSSLoaderDev({
loader: 'less-loader',
options: {
sourceMap: true,
},
}),
},
{
test: /\.styl$/,
oneOf: mergeCSSLoaderDev({
loader: 'stylus-loader',
options: {
sourceMap: true,
},
}),
},
],
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"development"',
},
}),
new FriendlyErrorsPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
],
performance: {
hints: false,
},
})

View File

@@ -1,94 +0,0 @@
const path = require('path')
const webpack = require('webpack')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const merge = require('webpack-merge')
const TerserPlugin = require('terser-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const baseConfig = require('./webpack.config.base')
const { mergeCSSLoaderProd } = require('../utils')
module.exports = merge(baseConfig, {
mode: 'production',
devtool: false,
output: {
filename: '[name].[chunkhash:8].js',
},
module: {
rules: [
{
test: /\.css$/,
oneOf: mergeCSSLoaderProd(),
},
{
test: /\.less$/,
oneOf: mergeCSSLoaderProd({
loader: 'less-loader',
options: {
sourceMap: true,
},
}),
},
{
test: /\.styl$/,
oneOf: mergeCSSLoaderProd({
loader: 'stylus-loader',
options: {
sourceMap: true,
},
}),
},
],
},
plugins: [
new CopyWebpackPlugin([
{
from: path.join(__dirname, '../../src/static'),
to: path.join(__dirname, '../dist/web/static'),
ignore: ['.*'],
},
]),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"',
},
}),
new MiniCssExtractPlugin({
filename: '[name].[contentHash:8].css',
}),
new webpack.NamedChunksPlugin(),
],
optimization: {
minimizer: [
new TerserPlugin({
cache: true,
parallel: true,
sourceMap: false, // set to true if you want JS source maps
}),
new OptimizeCSSAssetsPlugin({}),
],
splitChunks: {
cacheGroups: {
// chunks: 'all',
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
enforce: true,
chunks: 'all',
},
styles: {
name: 'styles',
test: /\.(css|less)$/,
chunks: 'all',
enforce: true,
},
},
},
runtimeChunk: true,
},
performance: {
hints: 'warning',
},
})

7636
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "lx-music-desktop",
"version": "0.18.2",
"version": "1.1.0",
"description": "一个免费的音乐下载助手",
"main": "./dist/electron/main.js",
"productName": "lx-music-desktop",
@@ -32,27 +32,34 @@
"publish:mac": "npm run publish:mac:dmg",
"publish:mac:dmg": "electron-builder -m=dmg -p onTagOrDraft",
"publish:gh:linux": "node build-config/pack.js && npm run publish:linux",
"publish:linux": "npm run publish:linux:deb && npm run publish:linux:appImage",
"publish:linux": "npm run publish:linux:deb && npm run publish:linux:appImage && npm run publish:linux:rpm && npm run publish:linux:pacman",
"publish:linux:appImage": "cross-env ARCH=x64 electron-builder -l=AppImage -p onTagOrDraft",
"publish:linux:deb": "npm run publish:linux:deb:x64 && npm run publish:linux:deb:x86",
"publish:linux:deb:x64": "cross-env ARCH=x64 electron-builder -l=deb --x64 -p onTagOrDraft",
"publish:linux:deb:x86": "cross-env ARCH=x86 electron-builder -l=deb --ia32 -p onTagOrDraft",
"pack:linux": "node build-config/pack.js && electron-builder -l",
"publish:linux:rpm": "cross-env ARCH=x64 electron-builder -l=rpm --x64 -p onTagOrDraft",
"publish:linux:pacman": "cross-env ARCH=x64 electron-builder -l=pacman --x64 -p onTagOrDraft",
"pack:linux": "node build-config/pack.js && npm run pack:linux:deb && npm run pack:linux:appImage && npm run pack:linux:rpm && npm run pack:linux:pacman",
"pack:linux:appImage": "cross-env ARCH=x64 electron-builder -l=AppImage",
"pack:linux:deb": "npm run pack:linux:deb:x64 && npm run pack:linux:deb:x86",
"pack:linux:deb:x64": "cross-env ARCH=x64 electron-builder -l=deb --x64",
"pack:linux:deb:x86": "cross-env ARCH=x86 electron-builder -l=deb --ia32",
"pack:linux:rpm": "cross-env ARCH=x64 electron-builder -l=rpm --x64",
"pack:linux:pacman": "cross-env ARCH=x64 electron-builder -l=pacman --x64",
"pack:mac": "node build-config/pack.js && electron-builder -m=dmg",
"pack:dir": "node build-config/pack.js && electron-builder --dir",
"dev": "node build-config/runner-dev.js",
"clean:electron": "rimraf dist/electron",
"clean:web": "rimraf dist/web",
"clean": "rimraf dist && rimraf build",
"build:main": "cross-env NODE_ENV=production webpack --config build-config/main/webpack.config.prod.js --progress --hide-modules",
"build:renderer": "cross-env NODE_ENV=production webpack --config build-config/renderer/webpack.config.prod.js --progress --hide-modules",
"build:web": "npm run clean:web && cross-env NODE_ENV=production webpack --config build-config/web/webpack.config.prod.js --progress --hide-modules",
"build": "npm run clean:electron && npm run build:main && npm run build:renderer",
"build:renderer-lyric": "cross-env NODE_ENV=production webpack --config build-config/renderer-lyric/webpack.config.prod.js --progress --hide-modules",
"build": "npm run clean:electron && npm run build:main && npm run build:renderer && npm run build:renderer-lyric",
"lint": "eslint --ext .js,.vue -f ./node_modules/eslint-formatter-friendly src",
"lint:fix": "eslint --ext .js,.vue -f ./node_modules/eslint-formatter-friendly --fix src"
},
"browserslist": [
"Electron 8.2.5"
"Electron 10.1.2"
],
"engines": {
"node": ">= 12"
@@ -92,7 +99,7 @@
"allowToChangeInstallationDirectory": true,
"differentialPackage": true,
"license": "./licenses/license.rtf",
"shortcutName": "lx-music"
"shortcutName": "LX Music"
},
"dmg": {
"window": {
@@ -103,7 +110,7 @@
{
"x": 106,
"y": 252,
"name": "lx-music"
"name": "LX Music"
},
{
"x": 490,
@@ -151,84 +158,82 @@
},
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
"devDependencies": {
"@babel/core": "^7.9.6",
"@babel/core": "^7.11.6",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/polyfill": "^7.8.7",
"@babel/preset-env": "^7.9.6",
"autoprefixer": "^9.7.6",
"@babel/plugin-transform-modules-umd": "^7.10.4",
"@babel/plugin-transform-runtime": "^7.11.5",
"@babel/polyfill": "^7.11.5",
"@babel/preset-env": "^7.11.5",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.1.0",
"babel-minify-webpack-plugin": "^0.3.1",
"babel-preset-minify": "^0.5.1",
"browserslist": "^4.12.0",
"cfonts": "^2.8.1",
"chalk": "^4.0.0",
"cfonts": "^2.8.6",
"chalk": "^4.1.0",
"changelog-parser": "^2.8.0",
"copy-webpack-plugin": "^5.1.1",
"copy-webpack-plugin": "^6.1.0",
"core-js": "^3.6.5",
"cos-nodejs-sdk-v5": "^2.5.20",
"cross-env": "^7.0.2",
"css-loader": "^3.5.3",
"css-loader": "^4.3.0",
"del": "^5.1.0",
"electron": "^8.2.5",
"electron-builder": "^22.6.0",
"electron-debug": "^3.0.1",
"electron-devtools-installer": "^3.0.0",
"eslint": "^6.8.0",
"electron": "^10.1.2",
"electron-builder": "^22.8.1",
"electron-debug": "^3.1.0",
"electron-devtools-installer": "^3.1.1",
"eslint": "^7.9.0",
"eslint-config-standard": "^14.1.1",
"eslint-formatter-friendly": "^7.0.0",
"eslint-loader": "^4.0.2",
"eslint-plugin-html": "^6.0.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-html": "^6.1.0",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",
"file-loader": "^6.0.0",
"file-loader": "^6.1.0",
"friendly-errors-webpack-plugin": "^1.7.0",
"html-webpack-plugin": "^4.3.0",
"less": "^3.11.1",
"less-loader": "^6.0.0",
"markdown-it": "^10.0.0",
"mini-css-extract-plugin": "^0.9.0",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"postcss-loader": "^3.0.0",
"html-webpack-plugin": "^4.4.1",
"less": "^3.12.2",
"less-loader": "^7.0.1",
"markdown-it": "^11.0.1",
"mini-css-extract-plugin": "^0.11.2",
"optimize-css-assets-webpack-plugin": "^5.0.4",
"postcss-loader": "^4.0.2",
"postcss-pxtorem": "^5.1.1",
"pug": "^2.0.4",
"pug": "^3.0.0",
"pug-loader": "^2.4.0",
"pug-plain-loader": "^1.0.0",
"raw-loader": "^4.0.1",
"rimraf": "^3.0.2",
"spinnies": "^0.5.1",
"stylus": "^0.54.7",
"stylus": "^0.54.8",
"stylus-loader": "^3.0.2",
"terser-webpack-plugin": "^2.3.6",
"terser-webpack-plugin": "^4.2.1",
"url-loader": "^4.1.0",
"vue-loader": "^15.9.1",
"vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.6.11",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3",
"vue-loader": "^15.9.3",
"vue-template-compiler": "^2.6.12",
"webpack": "^4.44.2",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0",
"webpack-hot-middleware": "^2.25.0",
"webpack-merge": "^4.2.2"
"webpack-merge": "^5.1.4"
},
"dependencies": {
"crypto-js": "^4.0.0",
"dnscache": "^1.0.2",
"electron-log": "^4.1.1",
"electron-store": "^5.1.1",
"electron-updater": "^4.3.1",
"image-size": "^0.8.3",
"electron-log": "^4.2.4",
"electron-store": "^6.0.0",
"electron-updater": "^4.3.5",
"iconv-lite": "^0.6.2",
"image-size": "^0.9.1",
"js-htmlencode": "^0.3.0",
"lrc-file-parser": "^1.0.3",
"mousetrap": "^1.6.5",
"needle": "^2.4.1",
"node-id3": "^0.1.16",
"lrc-file-parser": "^1.0.5",
"needle": "^2.5.2",
"node-id3": "^0.1.18",
"request": "^2.88.2",
"vue": "^2.6.11",
"vue-i18n": "^8.17.4",
"vue-router": "^3.1.6",
"vuex": "^3.3.0",
"vue": "^2.6.12",
"vue-i18n": "^8.21.1",
"vue-router": "^3.4.3",
"vuex": "^3.5.1",
"vuex-router-sync": "^5.0.0"
}
}

View File

@@ -1,4 +1,4 @@
const autoprefixer = require('autoprefixer')
// const autoprefixer = require('autoprefixer')
const pxtorem = require('postcss-pxtorem')
module.exports = {
@@ -8,16 +8,11 @@ module.exports = {
unitPrecision: 5,
propList: [
'font', 'font-size',
'line-height',
'letter-spacing',
'padding', 'margin',
'padding-left', 'padding-right',
'padding-top', 'padding-bottom',
'margin-left', 'margin-right',
'margin-top', 'margin-bottom',
'padding-*', 'margin-*',
'height', 'width',
'max-width', 'max-height',
'min-width', 'min-height',
'*-height', '*-width',
'flex', '::-webkit-scrollbar',
'top', 'left', 'bottom', 'right',
'border-radius',
@@ -28,6 +23,6 @@ module.exports = {
minPixelValue: 0,
exclude: [/node_modules/i],
}),
autoprefixer(),
// autoprefixer(),
],
}

View File

@@ -1,3 +1,30 @@
### 新增
- 在歌单详情界面新增播放当前歌单按钮、收藏歌单按钮,注:播放歌单不会将歌曲添加到试听列表
- 新增`不允许将歌词窗口拖出主屏幕之外`的设置项,默认开启,在连接多个屏幕时想要拖动到其他屏幕时可关闭此设置
- 新增大部分平台的歌词翻译,感谢 @InoriHimea 提供的[krc解码算法](https://github.com/lyswhut/lx-music-desktop/issues/296#issuecomment-683285784)
- 新增`显示歌词翻译`设置,默认开启,仅支持某些平台,注:无论该设置是否开启,嵌入或下载歌词时都不会带上翻译
- 新增`显示切换动画`设置,默认开启,关闭时将基本禁用软件内的所有切换动画
- 播放状态栏新增桌面歌词的开关、播放模式的切换、歌曲的收藏按钮Thanks to @andylow for the [icon](https://github.com/lyswhut/lx-music-desktop/pull/309)!
### 修复
- 修复开启托盘时,可能导致无法自动更新的问题
- 修复使用全局快捷键还原窗口时,窗口没有获取焦点的问题
- 修复我的列表搜索对最后一个字符的匹配问题
- 修复窗口在`较小`模式下最小化/关闭按钮不居中的问题
### 优化
- 桌面歌词当前播放行改为上下居中
- 为区分静音状态,静音时音量条会变淡,调整音量条时将会取消静音
- 优化随机播放机制,现在通过`下一曲`切换歌曲时,直到播放完整个列表之前将不会再随机到之前播放过的歌曲,并且通过`上一曲`可以正确播放上一首歌曲
- 当下载目录没有写入权限时将显示没有写入权限的提示
### 移除
- 移除默认的全局声音媒体快捷键接管
- 移除对百度音乐的支持因百度音乐原有的大部分API失效而且该平台相对其他平台来说音乐太少了可有可无以后再看情况恢复
### 其他
- 更新electron到 10.1.2

View File

@@ -25,7 +25,7 @@ const updateChangeLog = async(newVerNum, newChangeLog) => {
let changeLog = fs.readFileSync(changelogPath, 'utf-8')
const prevVer = await getPrevVer()
const log = `## [${newVerNum}](${pkg.repository.url.replace(/^git\+(http.+)\.git$/, '$1')}/compare/v${prevVer}...v${newVerNum}) - ${formatTime()}\n\n${newChangeLog}`
fs.writeFileSync(changelogPath, changeLog.replace(new RegExp(`(## [?0.1.1]?)`), log + '\n$1'), 'utf-8')
fs.writeFileSync(changelogPath, changeLog.replace(new RegExp('(## [?0.1.1]?)'), log + '\n$1'), 'utf-8')
}
const renderChangeLog = md => md_renderer(md)
@@ -49,7 +49,7 @@ module.exports = async newVerNum => {
console.log(chalk.blue('new version: ') + chalk.green(newVerNum))
fs.writeFileSync(jp('../version.json'), JSON.stringify(version, null, 2) + '\n', 'utf-8')
fs.writeFileSync(jp('../version.json'), JSON.stringify(version) + '\n', 'utf-8')
fs.writeFileSync(jp(pkgDir), JSON.stringify(pkg, null, 2) + '\n', 'utf-8')

File diff suppressed because one or more lines are too long

View File

@@ -17,35 +17,35 @@ module.exports = {
{
id: 2,
name: 'medium',
width: 1012,
width: 1018,
height: 650,
fontSize: '16px',
},
{
id: 3,
name: 'big',
width: 1104,
width: 1114,
height: 708,
fontSize: '17px',
},
{
id: 4,
name: 'larger',
width: 1198,
width: 1202,
height: 766,
fontSize: '18px',
fontSize: '17px',
},
{
id: 5,
name: 'oversized',
width: 1380,
width: 1382,
height: 886,
fontSize: '18px',
},
{
id: 6,
name: 'huge',
width: 1656,
width: 1686,
height: 1062,
fontSize: '19px',
},

View File

@@ -0,0 +1,89 @@
const { player: hotKeyPlayer, common: hotKeyCommon, desktop_lyric: hotKeyDesktopLyric } = require('./hotKey')
module.exports = {
local: {
enable: true,
keys: {
'mod+f5': {
type: hotKeyPlayer.toggle_play.type,
name: hotKeyPlayer.toggle_play.name,
action: hotKeyPlayer.toggle_play.action,
},
'mod+arrowleft': {
type: hotKeyPlayer.prev.type,
name: hotKeyPlayer.prev.name,
action: hotKeyPlayer.prev.action,
},
'mod+arrowright': {
type: hotKeyPlayer.next.type,
name: hotKeyPlayer.next.name,
action: hotKeyPlayer.next.action,
},
f1: {
type: hotKeyCommon.focusSearchInput.type,
name: hotKeyCommon.focusSearchInput.name,
action: hotKeyCommon.focusSearchInput.action,
},
},
},
global: {
enable: false,
keys: {
MediaPlayPause: {
type: hotKeyPlayer.toggle_play.type,
name: null,
action: hotKeyPlayer.toggle_play.action,
},
MediaPreviousTrack: {
type: hotKeyPlayer.prev.type,
name: null,
action: hotKeyPlayer.prev.action,
},
MediaNextTrack: {
type: hotKeyPlayer.next.type,
name: null,
action: hotKeyPlayer.next.action,
},
'mod+alt+f5': {
type: hotKeyPlayer.toggle_play.type,
name: hotKeyPlayer.toggle_play.name,
action: hotKeyPlayer.toggle_play.action,
},
'mod+alt+arrowleft': {
type: hotKeyPlayer.prev.type,
name: hotKeyPlayer.prev.name,
action: hotKeyPlayer.prev.action,
},
'mod+alt+arrowright': {
type: hotKeyPlayer.next.type,
name: hotKeyPlayer.next.name,
action: hotKeyPlayer.next.action,
},
'mod+alt+arrowup': {
type: hotKeyPlayer.volume_up.type,
name: hotKeyPlayer.volume_up.name,
action: hotKeyPlayer.volume_up.action,
},
'mod+alt+arrowdown': {
type: hotKeyPlayer.volume_down.type,
name: hotKeyPlayer.volume_down.name,
action: hotKeyPlayer.volume_down.action,
},
'mod+alt+0': {
type: hotKeyDesktopLyric.toggle_visible.type,
name: hotKeyDesktopLyric.toggle_visible.name,
action: hotKeyDesktopLyric.toggle_visible.action,
},
'mod+alt+-': {
type: hotKeyDesktopLyric.toggle_lock.type,
name: hotKeyDesktopLyric.toggle_lock.name,
action: hotKeyDesktopLyric.toggle_lock.action,
},
'mod+alt+=': {
type: hotKeyDesktopLyric.toggle_always_top.type,
name: hotKeyDesktopLyric.toggle_always_top.name,
action: hotKeyDesktopLyric.toggle_always_top.action,
},
},
},
}

View File

@@ -0,0 +1,107 @@
const path = require('path')
const os = require('os')
const { isMac } = require('./utils')
const defaultSetting = {
version: '1.0.38',
player: {
togglePlayMethod: 'listLoop',
highQuality: false,
isShowTaskProgess: true,
volume: 1,
isMute: false,
mediaDeviceId: 'default',
isMediaDeviceRemovedStopPlay: false,
isShowLyricTransition: true,
},
desktopLyric: {
enable: false,
isLock: false,
isAlwaysOnTop: false,
width: 380,
height: 420,
x: null,
y: null,
theme: 0,
isLockScreen: true,
style: {
fontSize: 120,
opacity: 95,
isZoomActiveLrc: true,
},
},
list: {
isShowAlbumName: true,
isShowSource: true,
prevSelectListId: 'default',
isSaveScrollLocation: true,
},
download: {
enable: false,
savePath: path.join(os.homedir(), 'Desktop'),
fileName: '歌名 - 歌手',
maxDownloadNum: 3,
isDownloadLrc: false,
isEmbedPic: true,
isEmbedLyric: false,
},
leaderboard: {
source: 'kw',
tabId: 'kw__16',
},
songList: {
source: 'kg',
sortId: '5',
tagInfo: {
name: '默认',
id: null,
},
},
odc: {
isAutoClearSearchInput: false,
isAutoClearSearchList: false,
},
search: {
searchSource: 'all',
tempSearchSource: 'kw',
isShowHotSearch: false,
isShowHistorySearch: false,
isFocusSearchBox: false,
},
network: {
proxy: {
enable: false,
host: '',
port: '',
username: '',
password: '',
},
},
tray: {
isShow: false,
isToTray: false,
themeId: 0,
},
windowSizeId: 2,
themeId: 0,
langId: null,
sourceId: 'kw',
apiSource: 'temp',
sourceNameType: 'alias',
isShowAnimation: true,
randomAnimate: true,
ignoreVersion: null,
isAgreePact: false,
controlBtnPosition: isMac ? 'left' : 'right',
}
const overwriteSetting = {
}
// 使用新年皮肤
if (new Date().getMonth() < 2) defaultSetting.themeId = 9
exports.defaultSetting = defaultSetting
exports.overwriteSetting = overwriteSetting

83
src/common/hotKey.js Normal file
View File

@@ -0,0 +1,83 @@
const names = require('../main/events/_name')
const hotKey = {
common: {
min: {
name: 'min',
action: 'min',
},
min_toggle: {
name: 'toggle_min',
action: 'toggle_min',
},
hide_toggle: {
name: 'toggle_hide',
action: 'toggle_hide',
},
close: {
name: 'toggle_close',
action: 'toggle_close',
},
focusSearchInput: {
name: 'focus_search_input',
action: 'focus_search_input',
},
},
player: {
toggle_play: {
name: 'toggle_play',
action: 'toggle_play',
},
next: {
name: 'next',
action: 'next',
},
prev: {
name: 'prev',
action: 'prev',
},
volume_up: {
name: 'volume_up',
action: 'volume_up',
},
volume_down: {
name: 'volume_down',
action: 'volume_down',
},
volume_mute: {
name: 'volume_mute',
action: 'volume_mute',
},
},
desktop_lyric: {
toggle_visible: {
name: 'toggle_visible',
action: 'toggle_visible',
},
toggle_lock: {
name: 'toggle_lock',
action: 'toggle_lock',
},
toggle_always_top: {
name: 'toggle_always_top',
action: 'toggle_always_top',
},
},
}
const keyName = {
player: names.mainWindow.name,
desktop_lyric: names.winLyric.name,
}
for (const type of Object.keys(hotKey)) {
let item = hotKey[type]
for (const key of Object.keys(item)) {
item[key].action = `${type}_${item[key].action}`
item[key].name = `${type}_${item[key].name}`
item[key].type = keyName[type]
}
}
exports.common = hotKey.common
exports.player = hotKey.player
exports.desktop_lyric = hotKey.desktop_lyric

View File

@@ -1,4 +1,5 @@
const { ipcMain, ipcRenderer } = require('electron')
const names = require('./ipcNames')
exports.mainOn = (event, callback) => {
@@ -15,6 +16,9 @@ exports.mainHandleOnce = (name, callback) => {
ipcMain.handleOnce(name, callback)
}
exports.mainSend = (window, name, params) => {
window.webContents.send(name, params)
}
exports.rendererSend = (name, params) => {
ipcRenderer.send(name, params)
@@ -29,3 +33,5 @@ exports.rendererOn = (name, callback) => {
exports.rendererOnce = (name, callback) => {
ipcRenderer.once(name, callback)
}
exports.NAMES = names

80
src/common/ipcNames.js Normal file
View File

@@ -0,0 +1,80 @@
const names = {
mainWindow: {
focus: 'focus',
close: 'close',
min: 'min',
max: 'max',
set_app_name: 'set_app_name',
clear_cache: 'clear_cache',
get_cache_size: 'get_cache_size',
get_env_params: 'get_env_params',
set_music_meta: 'set_music_meta',
progress: 'progress',
change_tray: 'change_tray',
quit_update: 'quit_update',
update_available: 'update_available',
update_error: 'update_error',
update_progress: 'update_progress',
update_downloaded: 'update_downloaded',
update_not_available: 'update_not_available',
set_ignore_mouse_events: 'set_ignore_mouse_events',
set_app_setting: 'set_app_setting',
set_window_size: 'set_window_size',
show_save_dialog: 'show_save_dialog',
handle_request: 'handle_request',
cancel_request: 'cancel_request',
handle_xm_verify_open: 'handle_xm_verify_open',
handle_xm_verify_close: 'handle_xm_verify_close',
select_dir: 'select_dir',
restart_window: 'restart_window',
handle_kw_decode_lyric: 'handle_kw_decode_lyric',
get_lyric_info: 'get_lyric_info',
set_lyric_info: 'set_lyric_info',
set_config: 'set_config',
set_hot_key_config: 'set_hot_key_config',
key_down: 'key_down',
quit: 'quit',
min_toggle: 'min_toggle',
hide_toggle: 'hide_toggle',
get_data_path: 'get_data_path',
show_dialog: 'show_dialog',
get_setting: 'get_setting',
get_playlist: 'get_playlist',
save_playlist: 'save_playlist',
get_data: 'get_data',
set_data: 'set_data',
save_data: 'save_data',
get_hot_key: 'get_hot_key',
},
winLyric: {
close: 'close',
set_lyric_info: 'set_lyric_info',
get_lyric_info: 'get_lyric_info',
set_lyric_config: 'set_lyric_config',
get_lyric_config: 'get_lyric_config',
set_win_bounds: 'set_win_bounds',
key_down: 'key_down',
},
hotKey: {
enable: 'enable',
status: 'status',
set_config: 'set_config',
},
}
for (const item of Object.keys(names)) {
let name = names[item]
for (const key of Object.keys(name)) {
name[key] = `${item}_${name[key]}`
}
}
exports.mainWindow = names.mainWindow
exports.winLyric = names.winLyric
exports.hotKey = names.hotKey

View File

@@ -1,8 +1,227 @@
const log = require('electron-log')
const Store = require('electron-store')
const { defaultSetting, overwriteSetting } = require('./defaultSetting')
const apiSource = require('../renderer/utils/music/api-source-info')
const defaultHotKey = require('./defaultHotKey')
const { dialog, app } = require('electron')
const path = require('path')
const fs = require('fs')
exports.isLinux = process.platform == 'linux'
exports.isWin = process.platform == 'win32'
exports.isMac = process.platform == 'darwin'
/**
* 生成节流函数
* @param {*} fn
* @param {*} delay
*/
exports.throttle = (fn, delay = 100) => {
let timer = null
let _args = null
return function(...args) {
_args = args
if (timer) return
timer = setTimeout(() => {
timer = null
fn.apply(this, _args)
}, delay)
}
}
/**
* 生成防抖函数
* @param {*} fn
* @param {*} delay
*/
exports.debounce = (fn, delay = 100) => {
let timer = null
let _args = null
return function(...args) {
_args = args
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
timer = null
fn.apply(this, _args)
}, delay)
}
}
exports.log = log
// https://stackoverflow.com/a/53387532
exports.compareVer = (currentVer, targetVer) => {
// treat non-numerical characters as lower version
// replacing them with a negative number based on charcode of each character
const fix = s => `.${s.toLowerCase().charCodeAt(0) - 2147483647}.`
currentVer = ('' + currentVer).replace(/[^0-9.]/g, fix).split('.')
targetVer = ('' + targetVer).replace(/[^0-9.]/g, fix).split('.')
let c = Math.max(currentVer.length, targetVer.length)
for (let i = 0; i < c; i++) {
// convert to integer the most efficient way
currentVer[i] = ~~currentVer[i]
targetVer[i] = ~~targetVer[i]
if (currentVer[i] > targetVer[i]) return 1
else if (currentVer[i] < targetVer[i]) return -1
}
return 0
}
exports.isObject = item => item && typeof item === 'object' && !Array.isArray(item)
/**
* 对象深度合并
* @param {} target 要合并源对象
* @param {} source 要合并目标对象
*/
exports.objectDeepMerge = (target, source, mergedObj) => {
if (!mergedObj) {
mergedObj = new Set()
mergedObj.add(target)
}
let base = {}
Object.keys(source).forEach(item => {
if (exports.isObject(source[item])) {
if (mergedObj.has(source[item])) return
if (!exports.isObject(target[item])) target[item] = {}
mergedObj.add(source[item])
exports.objectDeepMerge(target[item], source[item], mergedObj)
return
}
base[item] = source[item]
})
Object.assign(target, base)
}
exports.mergeSetting = (setting, version) => {
let defaultSettingCopy = JSON.parse(JSON.stringify(defaultSetting))
let overwriteSettingCopy = JSON.parse(JSON.stringify(overwriteSetting))
const defaultVersion = defaultSettingCopy.version
if (!version) {
if (setting) {
version = setting.version
delete setting.version
}
}
if (!setting) {
setting = defaultSettingCopy
} else if (exports.compareVer(version, defaultVersion) < 0) {
exports.objectDeepMerge(defaultSettingCopy, setting)
exports.objectDeepMerge(defaultSettingCopy, overwriteSettingCopy)
setting = defaultSettingCopy
}
if (!apiSource.some(api => api.id === setting.apiSource && !api.disabled)) {
let api = apiSource.find(api => !api.disabled)
if (api) setting.apiSource = api.id
}
return { setting, version: defaultVersion }
}
/**
* 初始化设置
* @param {*} setting
*/
exports.initSetting = () => {
let electronStore_list
try {
electronStore_list = new Store({
name: 'playList',
clearInvalidConfig: false,
})
} catch (error) {
log.error(error)
const backPath = path.join(app.getPath('userData'), 'playList.json.bak')
fs.copyFileSync(path.join(app.getPath('userData'), 'playList.json'), backPath)
dialog.showMessageBoxSync({
type: 'error',
message: 'Playlist data loading error',
detail: `We have helped you back up the old list file to ${backPath}\nYou can try to repair and restore it manually\n\nError detail: ${error.message}`,
})
electronStore_list = new Store({
name: 'playList',
})
}
const electronStore_config = new Store({
name: 'config',
})
let setting = electronStore_config.get('setting')
if (setting) {
let version = electronStore_config.get('version')
if (!version) { // 迁移配置
version = electronStore_config.get('setting.version')
electronStore_config.set('version', version)
electronStore_config.delete('setting.version')
const list = electronStore_config.get('list')
if (list) {
if (list.defaultList) electronStore_list.set('defaultList', list.defaultList)
if (list.loveList) electronStore_list.set('loveList', list.loveList)
electronStore_config.delete('list')
}
const downloadList = electronStore_config.get('download')
if (downloadList) {
if (downloadList.list) electronStore_list.set('downloadList', downloadList.list)
electronStore_config.delete('download')
}
}
// 迁移列表滚动位置设置 ~0.18.3
if (setting.list.scroll) {
let scroll = setting.list.scroll
electronStore_list.set('defaultList.location', scroll.locations.defaultList || 0)
electronStore_list.set('loveList.location', scroll.locations.loveList || 0)
electronStore_config.delete('setting.list.scroll')
electronStore_config.set('setting.list.isSaveScrollLocation', scroll.enable)
}
}
const { version: settingVersion, setting: newSetting } = exports.mergeSetting(setting, electronStore_config.get('version'))
// 重置 ^0.18.2 排行榜ID
if (!newSetting.leaderboard.tabId.includes('__')) newSetting.leaderboard.tabId = 'kw__16'
// newSetting.controlBtnPosition = 'right'
electronStore_config.set('version', settingVersion)
electronStore_config.set('setting', newSetting)
return { version: settingVersion, setting: newSetting }
}
/**
* 初始化快捷键设置
*/
exports.initHotKey = () => {
const electronStore_hotKey = new Store({
name: 'hotKey',
})
let localConfig = electronStore_hotKey.get('local')
if (!localConfig) {
localConfig = defaultHotKey.local
electronStore_hotKey.set('local', localConfig)
}
let globalConfig = electronStore_hotKey.get('global')
// 移除v1.0.1及之前设置的全局声音媒体快捷键接管
if (globalConfig && globalConfig.keys.VolumeUp) {
delete globalConfig.keys.VolumeUp
delete globalConfig.keys.VolumeDown
delete globalConfig.keys.VolumeMute
electronStore_hotKey.set('global', globalConfig)
}
if (!globalConfig) {
globalConfig = defaultHotKey.global
electronStore_hotKey.set('global', globalConfig)
}
return {
global: globalConfig,
local: localConfig,
}
}

17
src/main/env/cmdParams.js vendored Normal file
View File

@@ -0,0 +1,17 @@
const parseEnv = () => {
const params = {}
const rx = /^-\w+/
for (let param of process.argv) {
if (!rx.test(param)) continue
param = param.substring(1)
let index = param.indexOf('=')
if (index < 0) {
params[param] = true
} else {
params[param.substring(0, index)] = param.substring(index + 1)
}
}
return params
}
global.envParams.cmdParams = parseEnv()

4
src/main/env/index.js vendored Normal file
View File

@@ -0,0 +1,4 @@
global.envParams = {}
require('./cmdParams')
require('./screen')

11
src/main/env/screen.js vendored Normal file
View File

@@ -0,0 +1,11 @@
const { app, screen } = require('electron')
const initScreenParams = () => {
global.envParams.workAreaSize = screen.getPrimaryDisplay().workAreaSize
}
app.on('ready', () => {
screen.on('display-metrics-changed', initScreenParams)
initScreenParams()
})

34
src/main/event.js Normal file
View File

@@ -0,0 +1,34 @@
const { common: COMMON_EVENT_NAME, mainWindow: MAIN_WINDOW_EVENT_NAME } = require('./events/_name')
const { mainSend, NAMES: { mainWindow: ipcMainWindowNames } } = require('./../common/ipc')
const { getAppHotKeyConfig } = require('./utils')
global.lx_event.common.on(COMMON_EVENT_NAME.config, name => {
if (MAIN_WINDOW_EVENT_NAME.name === name) return
if (global.modules.mainWindow) mainSend(global.modules.mainWindow, ipcMainWindowNames.set_config, global.appSetting)
})
global.lx_event.common.on(COMMON_EVENT_NAME.initHotKey, () => {
global.appHotKey.config = getAppHotKeyConfig()
})
global.lx_event.mainWindow.on(MAIN_WINDOW_EVENT_NAME.quit, () => {
global.isTrafficLightClose = false
global.isQuitting = true
global.modules.mainWindow.close()
})
global.lx_event.mainWindow.on(MAIN_WINDOW_EVENT_NAME.toggle_minimize, () => {
if (global.modules.mainWindow.isMinimized()) {
if (!global.modules.mainWindow.isVisible()) {
global.modules.mainWindow.show()
}
global.modules.mainWindow.restore()
global.modules.mainWindow.focus()
} else {
global.modules.mainWindow.minimize()
}
})
global.lx_event.mainWindow.on(MAIN_WINDOW_EVENT_NAME.toggle_hide, () => {
global.modules.mainWindow.isVisible()
? global.modules.mainWindow.hide()
: global.modules.mainWindow.show()
})

18
src/main/events/Common.js Normal file
View File

@@ -0,0 +1,18 @@
const { EventEmitter } = require('events')
const { common: COMMON_EVENT_NAME } = require('./_name')
const { updateSetting } = require('../utils')
class Common extends EventEmitter {
initSetting() {
this.emit(COMMON_EVENT_NAME.initConfig)
this.emit(COMMON_EVENT_NAME.config, null)
}
setAppConfig(config, name) {
if (config) updateSetting(config)
this.emit(COMMON_EVENT_NAME.config, name)
}
}
module.exports = Common

21
src/main/events/HotKey.js Normal file
View File

@@ -0,0 +1,21 @@
const { EventEmitter } = require('events')
const { hotKey: HOT_KEY_EVENT_NAME } = require('./_name')
const { saveAppHotKeyConfig } = require('../utils')
class HotKey extends EventEmitter {
init() {
this.emit(HOT_KEY_EVENT_NAME.init)
}
saveConfig(config, source) {
if (config) saveAppHotKeyConfig(config)
this.emit(HOT_KEY_EVENT_NAME.config, config, source)
}
keyDown(info) {
this.emit(HOT_KEY_EVENT_NAME.keyDown, info)
}
}
module.exports = HotKey

View File

@@ -0,0 +1,22 @@
const { EventEmitter } = require('events')
const { mainWindow: MAIN_WINDOW_EVENT_NAME } = require('./_name')
class MainWindow extends EventEmitter {
setLyricInfo(info) {
this.emit(MAIN_WINDOW_EVENT_NAME.setLyricInfo, info)
}
quit() {
this.emit(MAIN_WINDOW_EVENT_NAME.quit)
}
toggleMinimize() {
this.emit(MAIN_WINDOW_EVENT_NAME.toggle_minimize)
}
toggleHide() {
this.emit(MAIN_WINDOW_EVENT_NAME.toggle_hide)
}
}
module.exports = MainWindow

View File

@@ -0,0 +1,15 @@
const { EventEmitter } = require('events')
const { winLyric: WIN_LYRIC_EVENT_NAME } = require('./_name')
// const { updateSetting } = require('../utils')
class WinLyric extends EventEmitter {
create() {
this.emit(WIN_LYRIC_EVENT_NAME.create)
}
close() {
this.emit(WIN_LYRIC_EVENT_NAME.close)
}
}
module.exports = WinLyric

View File

@@ -1,5 +1,32 @@
exports.common = {
initConfig: 'initConfig',
config: 'config',
}
exports.mainWindow = {
name: 'mainWindow',
setLyricInfo: 'setLyricInfo',
destroy: 'destroy',
quit: 'quit',
toggle_minimize: 'toggle_minimize',
toggle_hide: 'toggle_hide',
}
exports.tray = {
name: 'tray',
create: 'create',
destroy: 'destroy',
}
exports.winLyric = {
name: 'winLyric',
create: 'create',
close: 'close',
}
exports.hotKey = {
name: 'hotKey',
init: 'init',
config: 'config',
keyDown: 'keyDown',
}

View File

@@ -1,5 +1,13 @@
global.lx_event = {}
const Tray = require('./tray')
const Common = require('./Common')
const MainWindow = require('./MainWindow')
const Tray = require('./Tray')
const WinLyric = require('./WinLyric')
const HotKey = require('./HotKey')
if (!global.lx_event.common) global.lx_event.common = new Common()
if (!global.lx_event.mainWindow) global.lx_event.mainWindow = new MainWindow()
if (!global.lx_event.tray) global.lx_event.tray = new Tray()
if (!global.lx_event.winLyric) global.lx_event.winLyric = new WinLyric()
if (!global.lx_event.hotKey) global.lx_event.hotKey = new HotKey()

View File

@@ -6,14 +6,16 @@ if (!app.requestSingleInstanceLock()) {
app.quit()
return
}
if (!global.modules) global.modules = {}
app.on('second-instance', (event, argv, cwd) => {
if (global.mainWindow) {
if (global.mainWindow.isMinimized()) {
global.mainWindow.restore()
} else if (global.mainWindow.isVisible()) {
global.mainWindow.focus()
if (global.modules.mainWindow) {
if (global.modules.mainWindow.isMinimized()) {
global.modules.mainWindow.restore()
}
if (global.modules.mainWindow.isVisible()) {
global.modules.mainWindow.focus()
} else {
global.mainWindow.show()
global.modules.mainWindow.show()
}
} else {
app.quit()
@@ -21,7 +23,20 @@ app.on('second-instance', (event, argv, cwd) => {
})
const isDev = global.isDev = process.env.NODE_ENV !== 'production'
require('./env')
const { navigationUrlWhiteList } = require('../common/config')
const { getWindowSizeInfo } = require('./utils')
const { isMac, isLinux, initSetting, initHotKey } = require('../common/utils')
// https://github.com/electron/electron/issues/22691
app.commandLine.appendSwitch('wm-window-animations-disabled')
// https://github.com/electron/electron/issues/18397
// 开发模式下为true时 多次引入native模块会导致渲染进程卡死
// https://github.com/electron/electron/issues/22791
app.allowRendererProcessReuse = !isDev
app.on('web-contents-created', (event, contents) => {
contents.on('will-navigate', (event, navigationUrl) => {
@@ -50,22 +65,13 @@ app.on('web-contents-created', (event, contents) => {
})
})
// https://github.com/electron/electron/issues/22691
app.commandLine.appendSwitch('wm-window-animations-disabled')
// https://github.com/electron/electron/issues/18397
app.allowRendererProcessReuse = !isDev
const { getAppSetting, parseEnv, getWindowSizeInfo } = require('./utils')
global.envParams = parseEnv()
require('../common/error')
require('./events')
require('./event')
require('./rendererEvents')
const winEvent = require('./rendererEvents/winEvent')
const autoUpdate = require('./utils/autoUpdate')
const { isMac, isLinux } = require('../common/utils')
let winURL
@@ -84,7 +90,7 @@ function createWindow() {
/**
* Initial window options
*/
global.mainWindow = new BrowserWindow({
global.modules.mainWindow = new BrowserWindow({
height: windowSizeInfo.height,
useContentSize: true,
width: windowSizeInfo.width,
@@ -103,32 +109,42 @@ function createWindow() {
},
})
global.mainWindow.loadURL(winURL)
global.modules.mainWindow.loadURL(winURL)
winEvent(global.mainWindow)
// mainWindow.webContents.openDevTools()
winEvent(global.modules.mainWindow)
// global.modules.mainWindow.webContents.openDevTools()
if (!isDev) autoUpdate()
}
global.appHotKey = {
enable: true,
config: {},
state: null,
}
function init() {
global.appSetting = getAppSetting()
const info = initSetting()
global.appSetting = info.setting
global.appSettingVersion = info.version
global.appHotKey.config = initHotKey()
global.lx_event.common.initSetting()
global.lx_event.hotKey.init()
createWindow()
global.lx_event.tray.create()
}
app.on('ready', init)
app.on('activate', () => {
if (global.mainWindow) {
if (global.mainWindow.isMinimized()) {
global.mainWindow.restore()
} else if (global.mainWindow.isVisible()) {
global.mainWindow.focus()
if (global.modules.mainWindow) {
if (global.modules.mainWindow.isMinimized()) {
global.modules.mainWindow.restore()
} else if (global.modules.mainWindow.isVisible()) {
global.modules.mainWindow.focus()
} else {
global.mainWindow.show()
global.modules.mainWindow.show()
}
} else if (global.mainWindow === null) {
} else if (global.modules.mainWindow === null) {
init()
}
})

View File

@@ -9,8 +9,6 @@ if (isMac) {
{ label: '关于洛雪音乐', role: 'about' },
{ type: 'separator' },
{ label: '隐藏', role: 'hide' },
{ label: '显示其他', role: 'hideothers' },
{ label: '显示全部', role: 'unhide' },
{ type: 'separator' },
{
label: '退出',
@@ -26,20 +24,20 @@ if (isMac) {
label: '窗口',
role: 'window',
submenu: [
{ label: '最小化', role: 'minimize' },
{ label: '最小化', role: 'minimize', accelerator: 'Command+W' },
{ label: '关闭', role: 'close' },
],
},
{
label: '编辑',
submenu: [
{ label: '撤销', accelerator: 'CmdOrCtrl+Z', role: 'undo' },
{ label: '恢复', accelerator: 'Shift+CmdOrCtrl+Z', role: 'redo' },
{ label: '撤销', accelerator: 'Command+Z', role: 'undo' },
{ label: '恢复', accelerator: 'Shift+Command+Z', role: 'redo' },
{ type: 'separator' },
{ label: '剪切', accelerator: 'CmdOrCtrl+X', role: 'cut' },
{ label: '复制', accelerator: 'CmdOrCtrl+C', role: 'copy' },
{ label: '粘贴', accelerator: 'CmdOrCtrl+V', role: 'paste' },
{ label: '选择全部', accelerator: 'CmdOrCtrl+A', role: 'selectAll' },
{ label: '剪切', accelerator: 'Command+X', role: 'cut' },
{ label: '复制', accelerator: 'Command+C', role: 'copy' },
{ label: '粘贴', accelerator: 'Command+V', role: 'paste' },
{ label: '选择全部', accelerator: 'Command+A', role: 'selectAll' },
],
},
]

View File

@@ -0,0 +1,12 @@
const { app } = require('electron')
const { init, unRegisterHotkeyAll } = require('./utils')
const { hotKey: HOT_KEY_EVENT_NAME } = require('../../events/_name')
global.appHotKey.state = {}
app.on('will-quit', unRegisterHotkeyAll)
global.lx_event.hotKey.on(HOT_KEY_EVENT_NAME.init, init)
require('./rendererEvent')

View File

@@ -0,0 +1,28 @@
const {
mainHandle,
NAMES: {
hotKey: ipcHotKeyNames,
},
} = require('../../../common/ipc')
const { init, registerHotkey, unRegisterHotkey, unRegisterHotkeyAll } = require('./utils')
mainHandle(ipcHotKeyNames.set_config, async(event, { action, data, source }) => {
switch (action) {
case 'config':
global.lx_event.hotKey.saveConfig(data, source)
return true
case 'enable':
data ? init(true) : unRegisterHotkeyAll()
return true
case 'register':
return registerHotkey(data)
case 'unregister':
return unRegisterHotkey(data)
}
})
mainHandle(ipcHotKeyNames.status, async() => global.appHotKey.state)
mainHandle(ipcHotKeyNames.enable, async(event, flag) => {
flag ? init() : unRegisterHotkeyAll()
})

View File

@@ -0,0 +1,65 @@
const { globalShortcut } = require('electron')
const { log } = require('../../../common/utils')
const handleKeyDown = key => {
if (!global.appHotKey.enable) return
global.lx_event.hotKey.keyDown({ type: 'global', key })
}
const transformedKeyRxp = /(^|\+)[a-z]/g
const transformedKey = key => {
if (key.includes('arrow')) key = key.replace(/arrow/g, '')
return key.replace('mod', 'CommandOrControl').replace(transformedKeyRxp, l => l.toUpperCase())
}
exports.registerHotkey = ({ key, info }) => {
if (global.appHotKey.state[key] && global.appHotKey.state[key].status) return true
let transKey = transformedKey(key)
// console.log('Register key:', transKey)
if (!global.appHotKey.state[key]) {
global.appHotKey.state[key] = {
status: false,
info: null,
}
}
global.appHotKey.state[key].info = info
let status = global.appHotKey.state[key].status = globalShortcut.isRegistered(transKey) ? false : globalShortcut.register(transKey, () => {
handleKeyDown(key)
})
return status
}
exports.unRegisterHotkey = key => {
let transKey = transformedKey(key)
// console.log('Unregister key:', transKey)
globalShortcut.unregister(transKey)
delete global.appHotKey.state[key]
}
exports.unRegisterHotkeyAll = () => {
global.appHotKey.state = {}
globalShortcut.unregisterAll()
}
exports.handleKeyDown = handleKeyDown
exports.transformedKey = transformedKey
const handleRegisterHotkey = data => {
let ret = exports.registerHotkey(data)
if (!ret) log.info('Register hot key failed:', data.key)
}
exports.init = (isForce = false) => {
exports.unRegisterHotkeyAll()
if (!isForce && !global.appHotKey.config.global.enable) return
global.appHotKey.state = {}
// console.log(global.appHotKey.config.global.keys)
for (const key of Object.keys(global.appHotKey.config.global.keys)) {
try {
handleRegisterHotkey({ key, info: global.appHotKey.config.global.keys[key] })
} catch (err) {
log.info(err)
}
}
}

View File

@@ -1,2 +1,4 @@
require('./appMenu')
require('./winLyric')
require('./tray')
require('./hotKey')

View File

@@ -1,27 +1,49 @@
const { app, Tray, Menu } = require('electron')
const { isWin } = require('../../common/utils')
const { tray: TRAY_EVENT_NAME } = require('../events/_name')
const { tray: TRAY_EVENT_NAME, common: COMMON_EVENT_NAME } = require('../events/_name')
const path = require('path')
global.lx_event.tray.on(TRAY_EVENT_NAME.create, () => {
createTray()
})
global.lx_event.tray.on(TRAY_EVENT_NAME.destroy, () => {
destroyTray()
let isEnableTray = null
let themeId = null
const themeList = [
{
id: 0,
fileName: 'tray0Template',
isNative: true,
},
{
id: 1,
fileName: 'tray1Template',
isNative: false,
},
]
global.lx_event.common.on(COMMON_EVENT_NAME.config, sourceName => {
if (sourceName === TRAY_EVENT_NAME.name) return
if (themeId !== global.appSetting.tray.themeId) {
themeId = global.appSetting.tray.themeId
setTrayImage(themeId)
}
if (isEnableTray !== global.appSetting.tray.isShow) {
isEnableTray = global.appSetting.tray.isShow
global.appSetting.tray.isShow ? createTray() : destroyTray()
}
createMenu(global.modules.tray)
})
const createTray = () => {
if ((global.tray && !global.tray.isDestroyed()) || !global.appSetting.tray || !global.appSetting.tray.isShow) return
if ((global.modules.tray && !global.modules.tray.isDestroyed()) || !global.appSetting.tray || !global.appSetting.tray.isShow) return
const iconPath = path.join(global.__static, 'images/tray', isWin ? 'trayTemplate@2x.ico' : 'trayTemplate.png')
themeId = global.appSetting.tray.themeId
let themeName = (themeList.find(item => item.id === themeId) || themeList[0]).fileName
const iconPath = path.join(global.__static, 'images/tray', isWin ? themeName + '@2x.ico' : themeName + '.png')
// 托盘
global.tray = new Tray(iconPath)
global.modules.tray = new Tray(iconPath)
global.tray.setToolTip('洛雪音乐助手')
if (isWin) createMenu(global.tray)
global.tray.setIgnoreDoubleClickEvents(true)
global.tray.on('click', () => {
const mainWindow = global.mainWindow
global.modules.tray.setToolTip('洛雪音乐助手')
createMenu(global.modules.tray)
global.modules.tray.setIgnoreDoubleClickEvents(true)
global.modules.tray.on('click', () => {
const mainWindow = global.modules.mainWindow
if (!mainWindow) return
mainWindow.isVisible()
? mainWindow.focus()
@@ -30,21 +52,61 @@ const createTray = () => {
}
const destroyTray = () => {
if (!global.tray) return
global.tray.destroy()
global.tray = null
if (!global.modules.tray) return
global.modules.tray.destroy()
global.modules.tray = null
}
const createMenu = tray => {
const contextMenu = Menu.buildFromTemplate([
{
label: '退出',
click() {
global.isQuitting = true
app.quit()
},
if (!global.modules.tray || !isWin) return
let menu = []
menu.push(global.appSetting.desktopLyric.enable ? {
label: '关闭桌面歌词',
click() {
global.lx_event.common.setAppConfig({ desktopLyric: { enable: false } }, TRAY_EVENT_NAME.name)
},
])
} : {
label: '开启桌面歌词',
click() {
global.lx_event.common.setAppConfig({ desktopLyric: { enable: true } }, TRAY_EVENT_NAME.name)
},
})
menu.push(global.appSetting.desktopLyric.isLock ? {
label: '解锁桌面歌词',
click() {
global.lx_event.common.setAppConfig({ desktopLyric: { isLock: false } }, TRAY_EVENT_NAME.name)
},
} : {
label: '锁定桌面歌词',
click() {
global.lx_event.common.setAppConfig({ desktopLyric: { isLock: true } }, TRAY_EVENT_NAME.name)
},
})
menu.push(global.appSetting.desktopLyric.isAlwaysOnTop ? {
label: '取消置顶',
click() {
global.lx_event.common.setAppConfig({ desktopLyric: { isAlwaysOnTop: false } }, TRAY_EVENT_NAME.name)
},
} : {
label: '置顶歌词',
click() {
global.lx_event.common.setAppConfig({ desktopLyric: { isAlwaysOnTop: true } }, TRAY_EVENT_NAME.name)
},
})
menu.push({
label: '退出',
click() {
global.isQuitting = true
app.quit()
},
})
const contextMenu = Menu.buildFromTemplate(menu)
tray.setContextMenu(contextMenu)
}
const setTrayImage = themeId => {
if (!global.modules.tray) return
let themeName = (themeList.find(item => item.id === themeId) || themeList[0]).fileName
const iconPath = path.join(global.__static, 'images/tray', isWin ? themeName + '@2x.ico' : themeName + '.png')
global.modules.tray.setImage(iconPath)
}

View File

@@ -0,0 +1,80 @@
const { common: COMMON_EVENT_NAME, winLyric: WIN_LYRIC_EVENT_NAME, hotKey: HOT_KEY_EVENT_NAME, mainWindow: MAIN_WINDOW_EVENT_NAME } = require('../../events/_name')
const { mainSend, NAMES: { winLyric: ipcWinLyricNames } } = require('../../../common/ipc')
const { desktop_lyric } = require('../../../common/hotKey')
const { setLyricWindow } = require('./utils')
let isLock = null
let isEnable = null
let isAlwaysOnTop = null
let isLockScreen = null
const setLrcConfig = () => {
let desktopLyric = global.appSetting.desktopLyric
if (global.modules.lyricWindow) {
mainSend(global.modules.lyricWindow, ipcWinLyricNames.set_lyric_config, {
config: desktopLyric,
languageId: global.appSetting.langId,
isShowLyricTransition: global.appSetting.player.isShowLyricTransition,
})
if (isLock != desktopLyric.isLock) {
isLock = desktopLyric.isLock
if (desktopLyric.isLock) {
global.modules.lyricWindow.setIgnoreMouseEvents(true, { forward: false })
} else {
global.modules.lyricWindow.setIgnoreMouseEvents(false)
}
}
if (isAlwaysOnTop != desktopLyric.isAlwaysOnTop) {
isAlwaysOnTop = desktopLyric.isAlwaysOnTop
global.modules.lyricWindow.setAlwaysOnTop(desktopLyric.isAlwaysOnTop, 'screen-saver')
}
}
if (isEnable != desktopLyric.enable) {
isEnable = desktopLyric.enable
if (desktopLyric.enable) {
global.lx_event.winLyric.create()
} else {
global.lx_event.winLyric.close()
}
}
if (isLockScreen != desktopLyric.isLockScreen) {
isLockScreen = desktopLyric.isLockScreen
if (desktopLyric.isLockScreen) {
setLyricWindow({
x: desktopLyric.x,
y: desktopLyric.y,
w: desktopLyric.width,
h: desktopLyric.height,
})
}
}
}
global.lx_event.common.on(COMMON_EVENT_NAME.config, name => {
if (WIN_LYRIC_EVENT_NAME.name === name) return
setLrcConfig()
})
global.lx_event.mainWindow.on(MAIN_WINDOW_EVENT_NAME.setLyricInfo, info => {
if (!global.modules.lyricWindow) return
mainSend(global.modules.lyricWindow, ipcWinLyricNames.set_lyric_info, info)
})
global.lx_event.hotKey.on(HOT_KEY_EVENT_NAME.keyDown, ({ type, key }) => {
let info = global.appHotKey.config.global.keys[key]
if (!info || info.type != WIN_LYRIC_EVENT_NAME.name) return
let desktopLyricSetting = JSON.parse(JSON.stringify(global.appSetting.desktopLyric))
let settingKey
switch (info.action) {
case desktop_lyric.toggle_visible.action:
settingKey = 'enable'
break
case desktop_lyric.toggle_lock.action:
settingKey = 'isLock'
break
case desktop_lyric.toggle_always_top.action:
settingKey = 'isAlwaysOnTop'
break
}
desktopLyricSetting[settingKey] = !desktopLyricSetting[settingKey]
global.lx_event.common.setAppConfig({ desktopLyric: desktopLyricSetting }, null)
})

View File

@@ -0,0 +1,125 @@
const { BrowserWindow } = require('electron')
const { winLyric: WIN_LYRIC_EVENT_NAME } = require('../../events/_name')
const { debounce } = require('../../../common/utils')
require('./event')
require('./rendererEvent')
global.lx_event.winLyric.on(WIN_LYRIC_EVENT_NAME.create, () => {
createWindow()
})
global.lx_event.winLyric.on(WIN_LYRIC_EVENT_NAME.close, () => {
closeWindow()
})
let winURL = global.isDev ? 'http://localhost:9081/lyric.html' : `file://${__dirname}/lyric.html`
const setLyricsConfig = debounce(config => {
// if (x != null) bounds.x = x
// if (y != null) bounds.y = y
// if (width != null) bounds.width = width
// if (height != null) bounds.height = height
global.lx_event.common.setAppConfig({ desktopLyric: config }, WIN_LYRIC_EVENT_NAME.name)
}, 500)
const winEvent = lyricWindow => {
// let bounds
// lyricWindow.on('close', event => {
// if (global.isQuitting || !global.appSetting.tray.isToTray || (!isWin && !global.isTrafficLightClose)) {
// lyricWindow.setProgressBar(-1)
// return
// }
// if (global.isTrafficLightClose) global.isTrafficLightClose = false
// event.preventDefault()
// lyricWindow.hide()
// })
lyricWindow.on('closed', () => {
lyricWindow = global.modules.lyricWindow = null
})
lyricWindow.on('move', event => {
// bounds = lyricWindow.getBounds()
// console.log(bounds)
setLyricsConfig(lyricWindow.getBounds())
})
lyricWindow.on('resize', event => {
// bounds = lyricWindow.getBounds()
// console.log(bounds)
setLyricsConfig(lyricWindow.getBounds())
})
// lyricWindow.on('restore', () => {
// lyricWindow.webContents.send('restore')
// })
// lyricWindow.on('focus', () => {
// lyricWindow.webContents.send('focus')
// })
lyricWindow.once('ready-to-show', () => {
lyricWindow.show()
if (global.appSetting.desktopLyric.isLock) {
global.modules.lyricWindow.setIgnoreMouseEvents(true, { forward: false })
}
})
}
let offset = 8
const createWindow = () => {
if (global.modules.lyricWindow) return
if (!global.appSetting.desktopLyric.enable) return
// const windowSizeInfo = getWindowSizeInfo(global.appSetting)
let { x, y, width, height, isAlwaysOnTop } = global.appSetting.desktopLyric
let { width: screenWidth, height: screenHeight } = global.envParams.workAreaSize
screenWidth += offset * 2
screenHeight += offset * 2
if (x == null) {
x = screenWidth - width - offset
y = screenHeight - height - offset
}
if (global.appSetting.desktopLyric.isLockScreen) {
x = Math.max(-offset, screenWidth < (width + x) ? screenWidth - width : x)
y = Math.max(-offset, screenHeight < (height + y) ? screenHeight - height : y)
}
/**
* Initial window options
*/
global.modules.lyricWindow = new BrowserWindow({
height: Math.max(height > screenHeight ? screenHeight : height, 80),
width: Math.max(width > screenWidth ? screenWidth : width, 380),
x,
y,
minWidth: 380,
minHeight: 80,
useContentSize: true,
frame: false,
transparent: true,
enableRemoteModule: false,
// icon: path.join(global.__static, isWin ? 'icons/256x256.ico' : 'icons/512x512.png'),
resizable: false,
minimizable: false,
maximizable: false,
fullscreenable: false,
show: false,
alwaysOnTop: isAlwaysOnTop,
skipTaskbar: true,
webPreferences: {
// contextIsolation: true,
webSecurity: !global.isDev,
nodeIntegration: true,
},
})
global.modules.lyricWindow.loadURL(winURL)
winEvent(global.modules.lyricWindow)
// mainWindow.webContents.openDevTools()
}
const closeWindow = () => {
if (!global.modules.lyricWindow) return
global.modules.lyricWindow.close()
}

View File

@@ -0,0 +1,32 @@
const {
mainOn,
mainHandle,
mainSend,
NAMES: {
mainWindow: ipcMainWindowNames,
winLyric: ipcWinLyricNames,
},
} = require('../../../common/ipc')
const { winLyric: WIN_LYRIC_EVENT_NAME } = require('../../events/_name')
const { setLyricWindow } = require('./utils')
mainOn(ipcWinLyricNames.get_lyric_info, (event, action) => {
if (!global.modules.mainWindow) return
mainSend(global.modules.mainWindow, ipcMainWindowNames.get_lyric_info, {
name: ipcWinLyricNames.set_lyric_info,
modal: 'lyricWindow',
action,
})
})
mainOn(ipcWinLyricNames.set_lyric_config, (event, config) => {
global.lx_event.common.setAppConfig({ desktopLyric: config }, WIN_LYRIC_EVENT_NAME.name)
})
mainHandle(ipcWinLyricNames.get_lyric_config, async() => {
return { config: global.appSetting.desktopLyric, languageId: global.appSetting.langId, isShowLyricTransition: global.appSetting.player.isShowLyricTransition }
})
mainOn(ipcWinLyricNames.set_win_bounds, (event, options) => {
setLyricWindow(options)
})

View File

@@ -0,0 +1,56 @@
// 设置窗口位置、大小
let bounds
let winX
let winY
let wasW
let wasY
let offset = 8
exports.setLyricWindow = ({ x = 0, y = 0, w = 0, h = 0 }) => {
if (!global.modules.lyricWindow) return
bounds = global.modules.lyricWindow.getBounds()
wasW = global.envParams.workAreaSize.width
wasY = global.envParams.workAreaSize.height + offset
bounds.width = w
bounds.height = h
if (bounds.width > wasW - offset) {
bounds.width = wasW - offset
} else if (bounds.width < 380) {
bounds.width = 380
}
if (bounds.height > wasY) {
bounds.height = wasY + offset
} else if (bounds.height < 80) {
bounds.height = 80
}
if (global.appSetting.desktopLyric.isLockScreen) {
if (x != 0) {
winX = bounds.x + x
if (winX > wasW - bounds.width + offset) {
winX = wasW - bounds.width + offset
} else if (winX < -offset) {
winX = -offset
}
bounds.x = winX
}
if (y != 0) {
winY = bounds.y + y
if (winY > wasY - bounds.height) {
winY = wasY - bounds.height
} else if (winY < -offset) {
winY = -offset
}
bounds.y = winY
}
} else {
if (x != 0) {
bounds.x = bounds.x + x
}
if (y != 0) {
bounds.y = bounds.y + y
}
}
// console.log(bounds, x, y, w, h)
global.modules.lyricWindow.setBounds(bounds)
}

View File

@@ -1,9 +1,9 @@
const { mainOn } = require('../../common/ipc')
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
const { app } = require('electron')
const { name: defaultName } = require('../../../package.json')
mainOn('appName', (event, params) => {
mainOn(ipcMainWindowNames.set_app_name, (event, params) => {
if (params == null) {
app.setName(defaultName)
} else {

View File

@@ -0,0 +1,9 @@
const { mainWindow: MAIN_WINDOW_EVENT_NAME } = require('../events/_name')
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames }, mainHandle } = require('../../common/ipc')
mainOn(ipcMainWindowNames.set_app_setting, (event, config) => {
if (!config) return
global.lx_event.common.setAppConfig(config, MAIN_WINDOW_EVENT_NAME.name)
})
mainHandle(ipcMainWindowNames.get_setting, async() => ({ setting: global.appSetting, version: global.appSettingVersion }))

View File

@@ -0,0 +1,11 @@
const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
mainHandle(ipcMainWindowNames.clear_cache, async(event, options) => {
if (!global.modules.mainWindow) throw new Error('mainWindow is undefined')
return global.modules.mainWindow.webContents.session.clearCache()
})
mainHandle(ipcMainWindowNames.get_cache_size, async(event, options) => {
if (!global.modules.mainWindow) throw new Error('mainWindow is undefined')
return global.modules.mainWindow.webContents.session.getCacheSize()
})

View File

@@ -1,8 +0,0 @@
const { mainHandle } = require('../../common/ipc')
mainHandle('clearCache', async(event, options) => {
if (!global.mainWindow) throw new Error('mainwindow is undefined')
return global.mainWindow.webContents.session.clearCache()
})

View File

@@ -0,0 +1,12 @@
const Store = require('electron-store')
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames }, mainHandle } = require('../../common/ipc')
const electronStore_data = new Store({
name: 'data',
})
mainHandle(ipcMainWindowNames.get_data, async(event, path) => electronStore_data.get(path))
mainOn(ipcMainWindowNames.save_data, (event, { path, data }) => electronStore_data.get(path, data))

View File

@@ -1,7 +0,0 @@
const { mainHandle } = require('../../common/ipc')
mainHandle('getCacheSize', async(event, options) => {
if (!global.mainWindow) throw new Error('mainwindow is undefined')
return global.mainWindow.webContents.session.getCacheSize()
})

View File

@@ -0,0 +1,5 @@
const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
const { app } = require('electron')
mainHandle(ipcMainWindowNames.get_data_path, async() => app.getPath('userData'))

View File

@@ -1,6 +1,6 @@
const { mainHandle } = require('../../common/ipc')
const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
mainHandle('getEnvParams', async(event, options) => {
return global.envParams
mainHandle(ipcMainWindowNames.get_env_params, async(event, options) => {
return global.envParams.cmdParams
})

View File

@@ -0,0 +1,41 @@
const Store = require('electron-store')
const { mainSend, NAMES: { mainWindow: ipcMainWindowNames }, mainOn, mainHandle } = require('../../common/ipc')
const { mainWindow: MAIN_WINDOW_EVENT_NAME, hotKey: HOT_KEY_EVENT_NAME } = require('../events/_name')
const electronStore_hotKey = new Store({
name: 'hotKey',
})
// const { registerHotkey, unRegisterHotkey } = require('../modules/hotKey/utils')
// mainHandle(ipcMainWindowNames.set_hot_key_config, async(event, { action, data }) => {
// switch (action) {
// case 'config':
// global.lx_event.hotKey.saveConfig(data.data, MAIN_WINDOW_EVENT_NAME.source)
// return
// case 'register':
// return registerHotkey(data)
// case 'unregister':
// return unRegisterHotkey(data)
// }
// })
mainHandle(ipcMainWindowNames.get_hot_key, async() => ({
local: electronStore_hotKey.get('local'),
global: electronStore_hotKey.get('global'),
}))
mainOn(ipcMainWindowNames.quit, () => global.lx_event.mainWindow.quit())
mainOn(ipcMainWindowNames.min_toggle, () => global.lx_event.mainWindow.toggleMinimize())
mainOn(ipcMainWindowNames.hide_toggle, () => global.lx_event.mainWindow.toggleHide())
global.lx_event.hotKey.on(HOT_KEY_EVENT_NAME.config, (config, source) => {
if (!global.modules.mainWindow || source === MAIN_WINDOW_EVENT_NAME.name) return
mainSend(global.modules.mainWindow, ipcMainWindowNames.set_hot_key_config, { config, source })
})
global.lx_event.hotKey.on(HOT_KEY_EVENT_NAME.keyDown, ({ type, key }) => {
let info = global.appHotKey.config.global.keys[key]
if (!info || info.type != MAIN_WINDOW_EVENT_NAME.name) return
mainSend(global.modules.mainWindow, ipcMainWindowNames.key_down, { type, key })
})

View File

@@ -7,11 +7,17 @@ require('./musicMeta')
require('./selectDir')
require('./setWindowSize')
require('./showSaveDialog')
require('./clearCache')
require('./getCacheSize')
require('./cache')
require('./setIgnoreMouseEvent')
require('./getEnvParams')
require('./tray')
require('./updateSetting')
require('./appSetting')
require('./setLyricInfo')
require('./hotKey')
require('./getDataPath')
require('./showDialog')
require('./playList')
require('./data')
require('./xm_verify')
require('./kw_decodeLyric')

View File

@@ -0,0 +1,45 @@
const { inflate } = require('zlib')
const iconv = require('iconv-lite')
const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
const handleInflate = data => new Promise((resolve, reject) => {
inflate(data, (err, result) => {
if (err) return reject(err)
resolve(result)
})
})
const buf_key = Buffer.from('yeelion')
const buf_key_len = buf_key.length
const decodeLyric = async(buf, isGetLyricx) => {
// const info = buf.slice(0, index).toString()
// if (!info.startsWith('tp=content')) return null
// const isLyric = info.includes('\r\nlrcx=0\r\n')
if (buf.toString('utf8', 0, 10) != 'tp=content') return ''
// const index = buf.indexOf('\r\n\r\n') + 4
const lrcData = await handleInflate(buf.slice(buf.indexOf('\r\n\r\n') + 4))
if (!isGetLyricx) return iconv.decode(lrcData, 'gb18030')
const buf_str = Buffer.from(lrcData.toString(), 'base64')
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_str[i] ^ buf_key[j]
i++
j++
}
}
return iconv.decode(Buffer.from(output), 'gb18030')
}
mainHandle(ipcMainWindowNames.handle_kw_decode_lyric, async(event, { lrcBase64, isGetLyricx }) => {
if (!global.modules.mainWindow) throw new Error('mainWindow is undefined')
const lrc = await decodeLyric(Buffer.from(lrcBase64, 'base64'), isGetLyricx)
return Buffer.from(lrc).toString('base64')
})

View File

@@ -1,6 +1,6 @@
const { mainOn } = require('../../common/ipc')
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
const { setMeta } = require('../utils/musicMeta')
mainOn('setMusicMeta', (event, { filePath, meta }) => {
mainOn(ipcMainWindowNames.set_music_meta, (event, { filePath, meta }) => {
setMeta(filePath, meta)
})

View File

@@ -0,0 +1,23 @@
const Store = require('electron-store')
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames }, mainHandle } = require('../../common/ipc')
const electronStore_list = new Store({
name: 'playList',
})
mainHandle(ipcMainWindowNames.get_playlist, async(event, isIgnoredError = false) => {
let electronStore_list = new Store({
name: 'playList',
clearInvalidConfig: !isIgnoredError,
})
return {
defaultList: electronStore_list.get('defaultList'),
loveList: electronStore_list.get('loveList'),
userList: electronStore_list.get('userList'),
downloadList: electronStore_list.get('downloadList'),
}
})
mainOn(ipcMainWindowNames.save_playlist, (event, { type, data }) => electronStore_list.set(type, data))

View File

@@ -1,9 +1,9 @@
const { mainOn } = require('../../common/ipc')
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
mainOn('progress', (event, params) => {
mainOn(ipcMainWindowNames.progress, (event, params) => {
// console.log(params)
global.mainWindow && global.mainWindow.setProgressBar(params.status, {
global.modules.mainWindow && global.modules.mainWindow.setProgressBar(params.status, {
mode: params.mode || 'normal',
})
})

View File

@@ -1,10 +1,10 @@
const request = require('request')
const { mainOn } = require('../../common/ipc')
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
const tasks = []
mainOn('request', (event, options) => {
mainOn(ipcMainWindowNames.handle_request, (event, options) => {
// console.log(args)
if (!options) return
let index = fetchData(options, (err, resp) => {
@@ -19,7 +19,7 @@ mainOn('request', (event, options) => {
event.returnValue = index
})
mainOn('cancelRequest', (event, index) => {
mainOn(ipcMainWindowNames.cancel_request, (event, index) => {
if (index == null) return
let r = tasks[index]
if (r == null) return

View File

@@ -1,7 +1,7 @@
const { mainOn } = require('../../common/ipc')
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
mainOn('restartWindow', (event, name) => {
mainOn(ipcMainWindowNames.restart_window, (event, name) => {
console.log(name)
switch (name) {
case 'main':

View File

@@ -1,8 +1,8 @@
const { mainHandle } = require('../../common/ipc')
const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
const { dialog } = require('electron')
mainHandle('selectDir', async(event, options) => {
if (!global.mainWindow) throw new Error('mainwindow is undefined')
return dialog.showOpenDialog(global.mainWindow, options)
mainHandle(ipcMainWindowNames.select_dir, async(event, options) => {
if (!global.modules.mainWindow) throw new Error('mainWindow is undefined')
return dialog.showOpenDialog(global.modules.mainWindow, options)
})

View File

@@ -1,8 +1,8 @@
const { mainOn } = require('../../common/ipc')
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
mainOn('setIgnoreMouseEvents', (event, isIgnored) => {
if (!global.mainWindow) return
mainOn(ipcMainWindowNames.set_ignore_mouse_events, (event, isIgnored) => {
if (!global.modules.mainWindow) return
isIgnored
? global.mainWindow.setIgnoreMouseEvents(true, { forward: true })
: global.mainWindow.setIgnoreMouseEvents(false)
? global.modules.mainWindow.setIgnoreMouseEvents(true, { forward: true })
: global.modules.mainWindow.setIgnoreMouseEvents(false)
})

View File

@@ -0,0 +1,10 @@
const { mainOn, mainSend, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
mainOn(ipcMainWindowNames.set_lyric_info, (event, info) => {
if (info.info == null) {
global.lx_event.mainWindow.setLyricInfo(info)
return
}
mainSend(global.modules[info.info.modal], info.info.name, info)
})

View File

@@ -1,7 +1,7 @@
const { mainOn } = require('../../common/ipc')
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
mainOn('setWindowSize', (event, options) => {
if (!global.mainWindow) return
global.mainWindow.setBounds(options)
mainOn(ipcMainWindowNames.set_window_size, (event, options) => {
if (!global.modules.mainWindow) return
global.modules.mainWindow.setBounds(options)
})

View File

@@ -0,0 +1,12 @@
const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
const { dialog } = require('electron')
mainHandle(ipcMainWindowNames.show_dialog, async(event, { type, message, detail }) => {
if (!global.modules.mainWindow) throw new Error('mainWindow is undefined')
dialog.showMessageBoxSync(global.modules.mainWindow, {
type,
message,
detail,
})
})

View File

@@ -1,8 +1,8 @@
const { mainHandle } = require('../../common/ipc')
const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
const { dialog } = require('electron')
mainHandle('showSaveDialog', async(event, options) => {
if (!global.mainWindow) throw new Error('mainwindow is undefined')
return dialog.showSaveDialog(global.mainWindow, options)
mainHandle(ipcMainWindowNames.show_save_dialog, async(event, options) => {
if (!global.modules.mainWindow) throw new Error('mainWindow is undefined')
return dialog.showSaveDialog(global.modules.mainWindow, options)
})

View File

@@ -1,18 +1,18 @@
const { app } = require('electron')
const { mainOn } = require('../../common/ipc')
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
mainOn('min', event => {
if (global.mainWindow) {
global.mainWindow.minimize()
mainOn(ipcMainWindowNames.min, event => {
if (global.modules.mainWindow) {
global.modules.mainWindow.minimize()
}
})
mainOn('max', event => {
if (global.mainWindow) {
global.mainWindow.maximize()
mainOn(ipcMainWindowNames.max, event => {
if (global.modules.mainWindow) {
global.modules.mainWindow.maximize()
}
})
mainOn('close', (event, isForce) => {
mainOn(ipcMainWindowNames.close, (event, isForce) => {
if (isForce) return app.exit(0)
global.isTrafficLightClose = true
if (global.mainWindow) global.mainWindow.close()
if (global.modules.mainWindow) global.modules.mainWindow.close()
})

View File

@@ -1,12 +0,0 @@
// const { app } = require('electron')
const { mainOn } = require('../../common/ipc')
mainOn('changeTray', (event, setting) => {
global.appSetting.tray = setting
if (setting.isToTray) {
global.lx_event.tray.create()
} else {
global.lx_event.tray.destroy()
}
})

View File

@@ -1,7 +0,0 @@
const { mainOn } = require('../../common/ipc')
mainOn('updateAppSetting', (event, setting) => {
if (!setting) return
global.appSetting = setting
})

View File

@@ -1,11 +1,13 @@
const { isMac } = require('../../common/utils')
const { isWin } = require('../../common/utils')
const { mainSend, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
global.isQuitting = false
global.isTrafficLightClose = false // 是否点击软件上的关闭按钮关闭
module.exports = mainWindow => {
mainWindow.on('close', event => {
if (global.isQuitting || !global.appSetting.tray.isToTray || (isMac && !global.isTrafficLightClose)) {
if (global.isQuitting || !global.appSetting.tray.isToTray || (!isWin && !global.isTrafficLightClose)) {
mainWindow.setProgressBar(-1)
global.lx_event.winLyric.close()
return
}
@@ -15,12 +17,15 @@ module.exports = mainWindow => {
})
mainWindow.on('closed', () => {
mainWindow = global.mainWindow = null
mainWindow = global.modules.mainWindow = null
})
// mainWindow.on('restore', () => {
// mainWindow.webContents.send('restore')
// })
mainWindow.on('focus', () => {
mainSend(mainWindow, ipcMainWindowNames.focus)
})
mainWindow.once('ready-to-show', () => {
mainWindow.show()

View File

@@ -1,5 +1,5 @@
const { BrowserView } = require('electron')
const { mainHandle } = require('../../../common/ipc')
const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../../common/ipc')
const { getWindowSizeInfo } = require('../../utils')
let view
@@ -9,16 +9,16 @@ let rejectFn
const closeView = async() => {
if (!view) return
// await view.webContents.session.clearCache()
if (global.mainWindow) global.mainWindow.removeBrowserView(view)
if (global.modules.mainWindow) global.modules.mainWindow.removeBrowserView(view)
await view.webContents.session.clearStorageData()
view.destroy()
view = null
}
mainHandle('xm_verify_open', (event, url) => new Promise((resolve, reject) => {
if (!global.mainWindow) return reject(new Error('mainwindow is undefined'))
mainHandle(ipcMainWindowNames.handle_xm_verify_open, (event, url) => new Promise((resolve, reject) => {
if (!global.modules.mainWindow) return reject(new Error('mainWindow is undefined'))
if (view) {
global.mainWindow.removeBrowserView(view)
global.modules.mainWindow.removeBrowserView(view)
view.destroy()
}
@@ -48,7 +48,7 @@ mainHandle('xm_verify_open', (event, url) => new Promise((resolve, reject) => {
})
})
global.mainWindow.setBrowserView(view)
global.modules.mainWindow.setBrowserView(view)
const windowSizeInfo = getWindowSizeInfo(global.appSetting)
view.setBounds({ x: (windowSizeInfo.width - 380) / 2, y: ((windowSizeInfo.height - 320 + 52) / 2), width: 380, height: 320 })
view.webContents.loadURL(url, {
@@ -57,7 +57,7 @@ mainHandle('xm_verify_open', (event, url) => new Promise((resolve, reject) => {
// view.webContents.openDevTools()
}))
mainHandle('xm_verify_close', async() => {
mainHandle(ipcMainWindowNames.handle_xm_verify_close, async() => {
await closeView()
if (!rejectFn) return
if (!isActioned) rejectFn(new Error('canceled verify'))

View File

@@ -1,5 +1,5 @@
const { BrowserWindow } = require('electron')
const { mainHandle } = require('../../../common/ipc')
const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../../common/ipc')
const { getWindowSizeInfo } = require('../../utils')
let win
@@ -7,7 +7,7 @@ let win
const closeWin = async() => {
if (!win) return
// await win.webContents.session.clearCache()
// if (global.mainWindow) global.mainWindow.removeBrowserView(win)
// if (global.modules.mainWindow) global.modules.mainWindow.removeBrowserView(win)
if (win.isDestroyed()) {
win = null
return
@@ -17,16 +17,16 @@ const closeWin = async() => {
win = null
}
mainHandle('xm_verify_open', (event, url) => new Promise((resolve, reject) => {
if (!global.mainWindow) return reject(new Error('mainwindow is undefined'))
mainHandle(ipcMainWindowNames.handle_xm_verify_open, (event, url) => new Promise((resolve, reject) => {
if (!global.modules.mainWindow) return reject(new Error('mainWindow is undefined'))
if (win) win.destroy()
let isActioned = false
const mainWindowSizeInfo = global.mainWindow.getBounds()
const mainWindowSizeInfo = global.modules.mainWindow.getBounds()
const windowSizeInfo = getWindowSizeInfo(global.appSetting)
win = new BrowserWindow({
parent: global.mainWindow,
parent: global.modules.mainWindow,
width: 460,
height: 370,
resizable: false,
@@ -72,6 +72,6 @@ mainHandle('xm_verify_open', (event, url) => new Promise((resolve, reject) => {
// win.webContents.openDevTools()
}))
mainHandle('xm_verify_close', async() => {
mainHandle(ipcMainWindowNames.handle_xm_verify_close, async() => {
await closeWin()
})

View File

@@ -1,6 +1,6 @@
const { log } = require('../../common/utils')
const { autoUpdater } = require('electron-updater')
const { mainOn } = require('../../common/ipc')
const { mainOn, mainSend, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
autoUpdater.logger = log
// autoUpdater.autoDownload = false
@@ -61,9 +61,10 @@ function sendStatusToWindow(text) {
let waitEvent = []
const handleSendEvent = action => {
if (global.mainWindow) {
if (global.modules.mainWindow) {
setTimeout(() => { // 延迟发送事件,过早发送可能渲染进程还没启动完成
global.mainWindow.webContents.send(action.type, action.info)
if (!global.modules.mainWindow) return
mainSend(global.modules.mainWindow, action.type, action.info)
}, 2000)
} else {
waitEvent.push(action)
@@ -75,7 +76,8 @@ module.exports = () => {
if (waitEvent.length) {
waitEvent.forEach((event, index) => {
setTimeout(() => { // 延迟发送事件,过早发送可能渲染进程还没启动完成
global.mainWindow.webContents.send(event.type, event.info)
if (!global.modules.mainWindow) return
mainSend(global.modules.mainWindow, event.type, event.info)
}, 2000 * (index + 1))
})
waitEvent = []
@@ -89,28 +91,28 @@ module.exports = () => {
})
autoUpdater.on('update-available', info => {
sendStatusToWindow('Update available.')
handleSendEvent({ type: 'update-available', info })
handleSendEvent({ type: ipcMainWindowNames.update_available, info })
})
autoUpdater.on('update-not-available', info => {
sendStatusToWindow('Update not available.')
handleSendEvent({ type: 'update-not-available', info })
handleSendEvent({ type: ipcMainWindowNames.update_not_available, info })
})
autoUpdater.on('error', err => {
sendStatusToWindow('Error in auto-updater.')
handleSendEvent({ type: 'update-error', info: err.message })
handleSendEvent({ type: ipcMainWindowNames.update_error, info: err.message })
})
autoUpdater.on('download-progress', progressObj => {
let log_message = 'Download speed: ' + progressObj.bytesPerSecond
log_message = log_message + ' - Downloaded ' + progressObj.percent + '%'
log_message = log_message + ' (' + progressObj.transferred + '/' + progressObj.total + ')'
sendStatusToWindow(log_message)
handleSendEvent({ type: 'update-progress', info: progressObj })
handleSendEvent({ type: ipcMainWindowNames.update_progress, info: progressObj })
})
autoUpdater.on('update-downloaded', info => {
sendStatusToWindow('Update downloaded.')
handleSendEvent({ type: 'update-downloaded', info })
handleSendEvent({ type: ipcMainWindowNames.update_downloaded, info })
})
mainOn('quit-update', () => {
mainOn(ipcMainWindowNames.quit_update, () => {
global.isQuitting = true
setTimeout(() => {

View File

@@ -64,7 +64,8 @@ module.exports = (filePath, meta) => {
if (!/^http/.test(picUrl)) {
return writeMeta(filePath, meta)
}
let picPath = filePath.replace(/\.flac$/, '') + path.extname(picUrl).replace(extReg, '$1')
let ext = path.extname(picUrl)
let picPath = filePath.replace(/\.flac$/, '') + (ext ? ext.replace(extReg, '$1') : '.jpg')
request(picUrl)
.on('response', respones => {

View File

@@ -1,35 +1,41 @@
const Store = require('electron-store')
const { windowSizeList } = require('../../common/config')
const { objectDeepMerge, throttle } = require('../../common/utils')
exports.getWindowSizeInfo = ({ windowSizeId = 1 } = {}) => {
return windowSizeList.find(i => i.id === windowSizeId) || windowSizeList[0]
}
const electronStore_config = new Store({
name: 'config',
})
exports.getAppSetting = () => {
let electronStore = new Store()
const defaultSetting = {
windowSizeId: 1,
tray: {
isShow: false,
isToTray: false,
},
}
return Object.assign(defaultSetting, electronStore.get('setting') || {})
return electronStore_config.get('setting')
}
exports.parseEnv = () => {
const params = {}
const rx = /^-\w+/
for (let param of process.argv) {
if (!rx.test(param)) continue
param = param.substring(1)
let index = param.indexOf('=')
if (index < 0) {
params[param] = true
} else {
params[param.substring(0, index)] = param.substring(index + 1)
}
const electronStore_hotKey = new Store({
name: 'hotKey',
})
exports.getAppHotKeyConfig = () => {
return {
global: electronStore_hotKey.get('global'),
local: electronStore_hotKey.get('local'),
}
return params
}
const saveHotKeyConfig = throttle(config => {
for (const key of Object.keys(config)) {
global.appHotKey.config[key] = config[key]
electronStore_hotKey.set(key, config[key])
}
})
exports.saveAppHotKeyConfig = config => {
saveHotKeyConfig(config)
}
const saveSetting = throttle(n => {
electronStore_config.set('setting', n)
})
exports.updateSetting = settings => {
objectDeepMerge(global.appSetting, settings)
saveSetting(global.appSetting)
}

View File

@@ -4,18 +4,30 @@ const fs = require('fs')
const request = require('request')
const extReg = /^(\.(?:jpe?g|png)).*$/
const handleWriteMeta = (meta, filePath) => {
if (meta.lyrics) {
meta.unsynchronisedLyrics = {
language: 'zho',
text: meta.lyrics,
}
delete meta.lyrics
}
NodeID3.write(meta, filePath)
}
module.exports = (filePath, meta) => {
if (!meta.APIC) return NodeID3.write(meta, filePath)
if (!meta.APIC) return handleWriteMeta(meta, filePath)
if (!/^http/.test(meta.APIC)) {
delete meta.APIC
return NodeID3.write(meta, filePath)
return handleWriteMeta(meta, filePath)
}
let picPath = filePath.replace(/\.mp3$/, '') + path.extname(meta.APIC).replace(extReg, '$1')
let ext = path.extname(meta.APIC)
let picPath = filePath.replace(/\.mp3$/, '') + (ext ? ext.replace(extReg, '$1') : '.jpg')
request(meta.APIC)
.on('response', respones => {
if (respones.statusCode !== 200 && respones.statusCode != 206) {
delete meta.APIC
NodeID3.write(meta, filePath)
handleWriteMeta(meta, filePath)
return
}
respones
@@ -23,10 +35,10 @@ module.exports = (filePath, meta) => {
.on('finish', () => {
if (respones.complete) {
meta.APIC = picPath
NodeID3.write(meta, filePath)
handleWriteMeta(meta, filePath)
} else {
delete meta.APIC
NodeID3.write(meta, filePath)
handleWriteMeta(meta, filePath)
}
fs.unlink(picPath, err => {
if (err) console.log(err.message)
@@ -34,12 +46,12 @@ module.exports = (filePath, meta) => {
}).on('error', err => {
if (err) console.log(err.message)
delete meta.APIC
NodeID3.write(meta, filePath)
handleWriteMeta(meta, filePath)
})
})
.on('error', err => {
if (err) console.log(err.message)
delete meta.APIC
NodeID3.write(meta, filePath)
handleWriteMeta(meta, filePath)
})
}

335
src/renderer-lyric/App.vue Normal file
View File

@@ -0,0 +1,335 @@
<template lang="pug">
#container(:class="[theme, lrcConfig.isLock ? 'lock' : null]")
#main
transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut")
.control-bar(v-show="!lrcConfig.isLock")
core-control-bar(:lrcConfig="lrcConfig" :themes="themeList")
core-lyric(:lrcConfig="lrcConfig" :isShowLyricTransition="isShowLyricTransition")
div.resize-left(@mousedown.self="handleMouseDown('left', $event)")
div.resize-top(@mousedown.self="handleMouseDown('top', $event)")
div.resize-right(@mousedown.self="handleMouseDown('right', $event)")
div.resize-bottom(@mousedown.self="handleMouseDown('bottom', $event)")
div.resize-top-left(@mousedown.self="handleMouseDown('top-left', $event)")
div.resize-top-right(@mousedown.self="handleMouseDown('top-right', $event)")
div.resize-bottom-left(@mousedown.self="handleMouseDown('bottom-left', $event)")
div.resize-bottom-right(@mousedown.self="handleMouseDown('bottom-right', $event)")
core-icons
</template>
<script>
import { rendererOn, rendererInvoke, rendererSend, NAMES } from '../common/ipc'
window.ELECTRON_DISABLE_SECURITY_WARNINGS = process.env.ELECTRON_DISABLE_SECURITY_WARNINGS
export default {
data() {
return {
resize: {
origin: null,
msDownX: 0,
msDownY: 0,
},
lrcConfig: {
enable: false,
isLock: true,
isAlwaysOnTop: false,
width: 600,
height: 700,
x: -1,
y: -1,
theme: 0,
style: {
fontSize: 125,
opacity: 80,
isZoomActiveLrc: true,
},
},
isShowLyricTransition: true,
themeList: [
{
id: 0,
name: '绿意盎然',
className: 'green',
},
{
id: 1,
name: '绿意盎然',
className: 'yellow',
},
{
id: 2,
name: '绿意盎然',
className: 'blue',
},
{
id: 3,
name: '绿意盎然',
className: 'red',
},
{
id: 4,
name: '绿意盎然',
className: 'pink',
},
{
id: 5,
name: '绿意盎然',
className: 'purple',
},
{
id: 6,
name: '绿意盎然',
className: 'orange',
},
{
id: 7,
name: '绿意盎然',
className: 'grey',
},
{
id: 8,
name: '绿意盎然',
className: 'ming',
},
{
id: 9,
name: '绿意盎然',
className: 'blue2',
},
],
}
},
computed: {
theme() {
let theme = this.themeList.find(t => t.id == this.lrcConfig.theme) || this.themeList[0]
return theme.className
},
},
created() {
rendererOn(NAMES.winLyric.set_lyric_config, (event, config) => this.handleUpdateConfig(config))
rendererInvoke(NAMES.winLyric.get_lyric_config).then(config => this.handleUpdateConfig(config))
},
mounted() {
document.addEventListener('mousemove', this.handleMouseMove)
document.addEventListener('mouseup', this.handleMouseUp)
},
beforeDestroy() {
document.removeEventListener('mousemove', this.handleMouseMove)
document.removeEventListener('mouseup', this.handleMouseUp)
},
methods: {
handleUpdateConfig({ config, languageId, isShowLyricTransition }) {
this.lrcConfig = config
this.isShowLyricTransition = isShowLyricTransition
if (this.$i18n.locale !== languageId && languageId != null) this.$i18n.locale = languageId
},
handleMouseDown(origin, event) {
this.handleMouseUp()
this.resize.origin = origin
this.resize.msDownX = event.clientX
this.resize.msDownY = event.clientY
},
handleMouseUp() {
this.resize.origin = null
},
handleMouseMove(event) {
if (!this.resize.origin) return
// if (!event.target.classList.contains('resize-' + this.resize.origin)) return
// console.log(event.target)
let bounds = {
w: 0,
h: 0,
}
let temp
switch (this.resize.origin) {
case 'left':
temp = event.clientX - this.resize.msDownX
bounds.w = -temp
bounds.x = temp
break
case 'right':
bounds.w = event.clientX - this.resize.msDownX
this.resize.msDownX += bounds.w
break
case 'top':
temp = event.clientY - this.resize.msDownY
bounds.y = temp
bounds.h = -temp
break
case 'bottom':
bounds.h = event.clientY - this.resize.msDownY
this.resize.msDownY += bounds.h
break
case 'top-left':
temp = event.clientX - this.resize.msDownX
bounds.w = -temp
bounds.x = temp
temp = event.clientY - this.resize.msDownY
bounds.y = temp
bounds.h = -temp
break
case 'top-right':
temp = event.clientY - this.resize.msDownY
bounds.y = temp
bounds.h = -temp
bounds.w = event.clientX - this.resize.msDownX
this.resize.msDownX += bounds.w
break
case 'bottom-left':
temp = event.clientX - this.resize.msDownX
bounds.w = -temp
bounds.x = temp
bounds.h = event.clientY - this.resize.msDownY
this.resize.msDownY += bounds.h
break
case 'bottom-right':
bounds.w = event.clientX - this.resize.msDownX
this.resize.msDownX += bounds.w
bounds.h = event.clientY - this.resize.msDownY
this.resize.msDownY += bounds.h
break
}
// console.log(bounds)
bounds.w = window.innerWidth + bounds.w
bounds.h = window.innerHeight + bounds.h
rendererSend(NAMES.winLyric.set_win_bounds, bounds)
},
handleMouseOver() {
// this.handleMouseUp()
},
},
}
</script>
<style lang="less">
@import './assets/styles/index.less';
@import './assets/styles/layout.less';
body {
user-select: none;
height: 100vh;
box-sizing: border-box;
color: #fff;
opacity: .8;
}
#container {
padding: 8px;
box-sizing: border-box;
height: 100%;
&.lock {
#main {
background-color: transparent;
}
}
}
.resize-left {
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 10px;
cursor: ew-resize;
// background-color: rgba(0, 0, 0, 1);
}
.resize-right {
position: absolute;
right: 0;
top: 0;
height: 100%;
width: 10px;
cursor: ew-resize;
}
.resize-top {
position: absolute;
left: 0;
top: 0;
height: 10px;
width: 100%;
cursor: ns-resize;
}
.resize-bottom {
position: absolute;
left: 0;
bottom: 0;
height: 10px;
width: 100%;
cursor: ns-resize;
}
.resize-top-left {
position: absolute;
left: 0;
top: 0;
width: 14px;
height: 14px;
cursor: nwse-resize;
// background-color: rgba(0, 0, 0, 1);
}
.resize-top-right {
position: absolute;
right: 0;
top: 0;
width: 14px;
height: 14px;
cursor: nesw-resize;
// background-color: rgba(0, 0, 0, 1);
}
.resize-top-left {
position: absolute;
left: 0;
top: 0;
width: 14px;
height: 14px;
cursor: nwse-resize;
// background-color: rgba(0, 0, 0, 1);
}
.resize-bottom-left {
position: absolute;
left: 0;
bottom: 0;
width: 14px;
height: 14px;
cursor: nesw-resize;
// background-color: rgba(0, 0, 0, 1);
}
.resize-bottom-right {
position: absolute;
right: 0;
bottom: 0;
width: 14px;
height: 14px;
cursor: nwse-resize;
// background-color: rgba(0, 0, 0, 1);
}
#main {
position: relative;
box-sizing: border-box;
height: 100%;
transition: background-color @transition-theme;
min-height: 0;
border-radius: @radius-border;
overflow: hidden;
background-color: rgba(0, 0, 0, .2);
&:hover {
.control-bar {
opacity: 1;
}
}
}
.control-bar {
position: absolute;
border-top-left-radius: @radius-border;
border-top-right-radius: @radius-border;
overflow: hidden;
top: 0;
left: 0;
width: 100%;
opacity: 0;
transition: opacity @transition-theme;
z-index: 1;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,325 @@
@red-50: #ffebee;
@red-100: #ffcdd2;
@red-200: #ef9a9a;
@red-300: #e57373;
@red-400: #ef5350;
@red-500: #f44336;
@red-600: #e53935;
@red-700: #d32f2f;
@red-800: #c62828;
@red-900: #b71c1c;
@red-A100: #ff8a80;
@red-A200: #ff5252;
@red-A400: #ff1744;
@red-A700: #d50000;
@red: @red-500;
@pink-50: #fce4ec;
@pink-100: #f8bbd0;
@pink-200: #f48fb1;
@pink-300: #f06292;
@pink-400: #ec407a;
@pink-500: #e91e63;
@pink-600: #d81b60;
@pink-700: #c2185b;
@pink-800: #ad1457;
@pink-900: #880e4f;
@pink-A100: #ff80ab;
@pink-A200: #ff4081;
@pink-A400: #f50057;
@pink-A700: #c51162;
@pink: @pink-500;
@purple-50: #f3e5f5;
@purple-100: #e1bee7;
@purple-200: #ce93d8;
@purple-300: #ba68c8;
@purple-400: #ab47bc;
@purple-500: #9c27b0;
@purple-600: #8e24aa;
@purple-700: #7b1fa2;
@purple-800: #6a1b9a;
@purple-900: #4a148c;
@purple-A100: #ea80fc;
@purple-A200: #e040fb;
@purple-A400: #d500f9;
@purple-A700: #aa00ff;
@purple: @purple-500;
@deep-purple-50: #ede7f6;
@deep-purple-100: #d1c4e9;
@deep-purple-200: #b39ddb;
@deep-purple-300: #9575cd;
@deep-purple-400: #7e57c2;
@deep-purple-500: #673ab7;
@deep-purple-600: #5e35b1;
@deep-purple-700: #512da8;
@deep-purple-800: #4527a0;
@deep-purple-900: #311b92;
@deep-purple-A100: #b388ff;
@deep-purple-A200: #7c4dff;
@deep-purple-A400: #651fff;
@deep-purple-A700: #6200ea;
@deep-purple: @deep-purple-500;
@indigo-50: #e8eaf6;
@indigo-100: #c5cae9;
@indigo-200: #9fa8da;
@indigo-300: #7986cb;
@indigo-400: #5c6bc0;
@indigo-500: #3f51b5;
@indigo-600: #3949ab;
@indigo-700: #303f9f;
@indigo-800: #283593;
@indigo-900: #1a237e;
@indigo-A100: #8c9eff;
@indigo-A200: #536dfe;
@indigo-A400: #3d5afe;
@indigo-A700: #304ffe;
@indigo: @indigo-500;
@blue-50: #e3f2fd;
@blue-100: #bbdefb;
@blue-200: #90caf9;
@blue-300: #64b5f6;
@blue-400: #42a5f5;
@blue-500: #2196f3;
@blue-600: #1e88e5;
@blue-700: #1976d2;
@blue-800: #1565c0;
@blue-900: #0d47a1;
@blue-A100: #82b1ff;
@blue-A200: #448aff;
@blue-A400: #2979ff;
@blue-A700: #2962ff;
@blue: @blue-500;
@light-blue-50: #e1f5fe;
@light-blue-100: #b3e5fc;
@light-blue-200: #81d4fa;
@light-blue-300: #4fc3f7;
@light-blue-400: #29b6f6;
@light-blue-500: #03a9f4;
@light-blue-600: #039be5;
@light-blue-700: #0288d1;
@light-blue-800: #0277bd;
@light-blue-900: #01579b;
@light-blue-A100: #80d8ff;
@light-blue-A200: #40c4ff;
@light-blue-A400: #00b0ff;
@light-blue-A700: #0091ea;
@light-blue: @light-blue-500;
@cyan-50: #e0f7fa;
@cyan-100: #b2ebf2;
@cyan-200: #80deea;
@cyan-300: #4dd0e1;
@cyan-400: #26c6da;
@cyan-500: #00bcd4;
@cyan-600: #00acc1;
@cyan-700: #0097a7;
@cyan-800: #00838f;
@cyan-900: #006064;
@cyan-A100: #84ffff;
@cyan-A200: #18ffff;
@cyan-A400: #00e5ff;
@cyan-A700: #00b8d4;
@cyan: @cyan-500;
@teal-50: #e0f2f1;
@teal-100: #b2dfdb;
@teal-200: #80cbc4;
@teal-300: #4db6ac;
@teal-400: #26a69a;
@teal-500: #009688;
@teal-600: #00897b;
@teal-700: #00796b;
@teal-800: #00695c;
@teal-900: #004d40;
@teal-A100: #a7ffeb;
@teal-A200: #64ffda;
@teal-A400: #1de9b6;
@teal-A700: #00bfa5;
@teal: @teal-500;
@green-50: #e8f5e9;
@green-100: #c8e6c9;
@green-200: #a5d6a7;
@green-300: #81c784;
@green-400: #66bb6a;
@green-500: #4caf50;
@green-600: #43a047;
@green-700: #388e3c;
@green-800: #2e7d32;
@green-900: #1b5e20;
@green-A100: #b9f6ca;
@green-A200: #69f0ae;
@green-A400: #00e676;
@green-A700: #00c853;
@green: @green-500;
@light-green-50: #f1f8e9;
@light-green-100: #dcedc8;
@light-green-200: #c5e1a5;
@light-green-300: #aed581;
@light-green-400: #9ccc65;
@light-green-500: #8bc34a;
@light-green-600: #7cb342;
@light-green-700: #689f38;
@light-green-800: #558b2f;
@light-green-900: #33691e;
@light-green-A100: #ccff90;
@light-green-A200: #b2ff59;
@light-green-A400: #76ff03;
@light-green-A700: #64dd17;
@light-green: @light-green-500;
@lime-50: #f9fbe7;
@lime-100: #f0f4c3;
@lime-200: #e6ee9c;
@lime-300: #dce775;
@lime-400: #d4e157;
@lime-500: #cddc39;
@lime-600: #c0ca33;
@lime-700: #afb42b;
@lime-800: #9e9d24;
@lime-900: #827717;
@lime-A100: #f4ff81;
@lime-A200: #eeff41;
@lime-A400: #c6ff00;
@lime-A700: #aeea00;
@lime: @lime-500;
@yellow-50: #fffde7;
@yellow-100: #fff9c4;
@yellow-200: #fff59d;
@yellow-300: #fff176;
@yellow-400: #ffee58;
@yellow-500: #fec60a;
@yellow-600: #fdd835;
@yellow-700: #fbc02d;
@yellow-800: #f9a825;
@yellow-900: #f57f17;
@yellow-A100: #ffff8d;
@yellow-A200: #ffff00;
@yellow-A400: #ffea00;
@yellow-A700: #ffd600;
@yellow: @yellow-700;
@amber-50: #fff8e1;
@amber-100: #ffecb3;
@amber-200: #ffe082;
@amber-300: #ffd54f;
@amber-400: #ffca28;
@amber-500: #ffc107;
@amber-600: #ffb300;
@amber-700: #ffa000;
@amber-800: #ff8f00;
@amber-900: #ff6f00;
@amber-A100: #ffe57f;
@amber-A200: #ffd740;
@amber-A400: #ffc400;
@amber-A700: #ffab00;
@amber: @amber-500;
@orange-50: #fff3e0;
@orange-100: #ffe0b2;
@orange-200: #ffcc80;
@orange-300: #ffb74d;
@orange-400: #ffa726;
@orange-500: #ff9800;
@orange-600: #fb8c00;
@orange-700: #f57c00;
@orange-800: #ef6c00;
@orange-900: #e65100;
@orange-A100: #ffd180;
@orange-A200: #ffab40;
@orange-A400: #ff9100;
@orange-A700: #ff6d00;
@orange: @orange-500;
@deep-orange-50: #fbe9e7;
@deep-orange-100: #ffccbc;
@deep-orange-200: #ffab91;
@deep-orange-300: #ff8a65;
@deep-orange-400: #ff7043;
@deep-orange-500: #ff5722;
@deep-orange-600: #f4511e;
@deep-orange-700: #e64a19;
@deep-orange-800: #d84315;
@deep-orange-900: #bf360c;
@deep-orange-A100: #ff9e80;
@deep-orange-A200: #ff6e40;
@deep-orange-A400: #ff3d00;
@deep-orange-A700: #dd2c00;
@deep-orange: @deep-orange-500;
@brown-50: #efebe9;
@brown-100: #d7ccc8;
@brown-200: #bcaaa4;
@brown-300: #a1887f;
@brown-400: #8d6e63;
@brown-500: #795548;
@brown-600: #6d4c41;
@brown-700: #5d4037;
@brown-800: #4e342e;
@brown-900: #3e2723;
@brown-A100: #d7ccc8;
@brown-A200: #bcaaa4;
@brown-A400: #8d6e63;
@brown-A700: #5d4037;
@brown: @brown-500;
@grey-50: #fafafa;
@grey-100: #f5f5f5;
@grey-200: #eeeeee;
@grey-300: #e0e0e0;
@grey-400: #bdbdbd;
@grey-500: #9e9e9e; @rgb-grey-500: "158, 158, 158";
@grey-600: #757575;
@grey-700: #616161;
@grey-800: #424242;
@grey-900: #212121;
@grey-A100: #f5f5f5;
@grey-A200: #eeeeee;
@grey-A400: #bdbdbd;
@grey-A700: #616161;
@grey: @grey-500;
@blue-grey-50: #eceff1;
@blue-grey-100: #cfd8dc;
@blue-grey-200: #b0bec5;
@blue-grey-300: #90a4ae;
@blue-grey-400: #78909c;
@blue-grey-500: #607d8b;
@blue-grey-600: #546e7a;
@blue-grey-700: #455a64;
@blue-grey-800: #37474f;
@blue-grey-900: #263238;
@blue-grey-A100: #cfd8dc;
@blue-grey-A200: #b0bec5;
@blue-grey-A400: #78909c;
@blue-grey-A700: #455a64;
@blue-grey: @blue-grey-500;
@black: #000000; @rgb-black: "0,0,0";
@white: #ffffff; @rgb-white: "255,255,255";

View File

@@ -0,0 +1,56 @@
@import './reset.less';
@import './animate.less';
*, *::after, *::before {
-webkit-user-drag: none;
}
html {
font-size: 16px;
}
.nobreak {
white-space: nowrap;
}
.auto-hidden {
.mixin-ellipsis-1;
}
.center {
text-align: center;
}
.break {
word-break: break-all;
}
.select {
user-select: text;
}
.no-select {
user-select: none;
}
a {
color: #000;
}
small {
font-size: .8em;
}
.small {
font-size: .9em;
}
strong {
font-weight: bold;
}
.underline {
text-decoration: underline;
}
svg {
transition: @transition-theme;
transition-property: fill;
}

View File

@@ -0,0 +1,30 @@
@import './variables.less';
/*自动隐藏文字*/
.mixin-ellipsis-1() {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.mixin-ellipsis() {
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
word-wrap: break-word;
word-break: break-all;
white-space: normal !important;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
}
.mixin-ellipsis-2() {
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
word-wrap: break-word;
word-break: break-all;
white-space: normal !important;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}

View File

@@ -0,0 +1,43 @@
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}

View File

@@ -0,0 +1,82 @@
@import './colors.less';
@themes: green, yellow, blue, red, pink, purple, orange, grey, ming, blue2;
// Colors
// @color-theme: #03a678;
@color-theme: #07c556;
@color-theme_2: rgba(255, 255, 255, 1);
@color-theme-lyric: @color-theme_2;
@color-theme-lyric_2:@color-theme;
@color-green-theme: #07c556;
@color-green-theme_2: rgba(255, 255, 255, 1);
@color-green-theme-lyric: @color-green-theme_2;
@color-green-theme-lyric_2: @color-green-theme;
@color-yellow-theme: #fffa12;
@color-yellow-theme_2: rgba(255, 255, 255, 1);
@color-yellow-theme-lyric: @color-yellow-theme_2;
@color-yellow-theme-lyric_2: @color-yellow-theme;
@color-blue-theme: #19b5fe;
@color-blue-theme_2: rgba(255, 255, 255, 1);
@color-blue-theme-lyric: @color-blue-theme_2;
@color-blue-theme-lyric_2: @color-blue-theme;
@color-red-theme: #ff1222;
@color-red-theme_2: rgba(255, 255, 255, 1);
@color-red-theme-lyric: @color-red-theme_2;
@color-red-theme-lyric_2: @color-red-theme;
@color-pink-theme: #f1828d;
@color-pink-theme_2: rgba(255, 255, 255, 1);
@color-pink-theme-lyric: @color-pink-theme_2;
@color-pink-theme-lyric_2: @color-pink-theme;
@color-purple-theme: #c851d4;
@color-purple-theme_2: rgba(255, 255, 255, 1);
@color-purple-theme-lyric: @color-purple-theme_2;
@color-purple-theme-lyric_2: @color-purple-theme;
@color-orange-theme: #ffad12;
@color-orange-theme_2: rgba(255, 255, 255, 1);
@color-orange-theme-lyric: @color-orange-theme_2;
@color-orange-theme-lyric_2: @color-orange-theme;
@color-grey-theme: #bdc3c7;
@color-grey-theme_2: rgba(255, 255, 255, 1);
@color-grey-theme-lyric: @color-grey-theme_2;
@color-grey-theme-lyric_2: @color-grey-theme;
@color-ming-theme: #336e7b;
@color-ming-theme_2: rgba(255, 255, 255, 1);
@color-ming-theme-lyric: @color-ming-theme_2;
@color-ming-theme-lyric_2: @color-ming-theme;
@color-blue2-theme: #6b7bd8;
@color-blue2-theme_2: rgba(255, 255, 255, 1);
@color-blue2-theme-lyric: @color-blue2-theme_2;
@color-blue2-theme-lyric_2: @color-blue2-theme;
// Width
@width-app-left: 6.6%;
// Height
@height-toolbar: 54px;
@height-player: 60px;
// Shadow
@shadow-app: 8px;
// Radius
@radius-progress-border: 5px;
@radius-border: 4px;
@transition-theme: .4s ease;
@form-radius: 3px;

View File

@@ -0,0 +1,280 @@
<template lang="pug">
div(:class="$style.container")
transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated fadeOut")
div(:class="$style.btns" v-show="!isShowThemeList")
button(:class="$style.btn" @click="handleClose" :title="$t('desktopLyric.close')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='20px' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-close')
button(:class="$style.btn" @click="handleLock" :title="$t('desktopLyric.' + (lrcConfig.isLock ? 'unlock' : 'lock'))")
svg(v-if="config.isLock" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='20px' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-unlock')
svg(v-else version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='20px' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-lock')
button(:class="$style.btn" :title="$t('desktopLyric.theme')" @click="isShowThemeList = true")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='20px' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-theme')
button(:class="$style.btn" @click="handleFontChange('increase', 10)" @contextmenu="handleFontChange('increase', 1)" :title="$t('desktopLyric.font_increase')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='20px' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-font-increase')
button(:class="$style.btn" @click="handleFontChange('decrease', 10)" @contextmenu="handleFontChange('decrease', 1)" :title="$t('desktopLyric.font_decrease')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='20px' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-font-decrease')
button(:class="$style.btn" @click="handleOpactiyChange('increase', 10)" @contextmenu="handleOpactiyChange('increase', 2)" :title="$t('desktopLyric.opactiy_increase')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='20px' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-opactiy-increase')
button(:class="$style.btn" @click="handleOpactiyChange('decrease', 10)" @contextmenu="handleOpactiyChange('decrease', 2)" :title="$t('desktopLyric.opactiy_decrease')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='20px' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-opactiy-decrease')
//- button(:class="$style.btn" v-text="lrcConfig.style.isZoomActiveLrc ? '取消缩放' : '缩放歌词'" @click="handleZoomLrc")
button(:class="$style.btn" @click="handleZoomLrc" :title="$t('desktopLyric.' + (lrcConfig.style.isZoomActiveLrc ? 'lrc_active_zoom_off' : 'lrc_active_zoom_on'))")
svg(v-if="config.style.isZoomActiveLrc" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='20px' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-vibrate-off')
svg(v-else version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='20px' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-vibrate')
button(:class="$style.btn" @click="handleAlwaysOnTop" :title="$t('desktopLyric.' + (lrcConfig.isAlwaysOnTop ? 'win_top_off' : 'win_top_on'))")
svg(v-if="config.isAlwaysOnTop" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='20px' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-top-off')
svg(v-else version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='20px' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-top-on')
//- base-quantity(v-model="config.style.fontSize" :min="30" :step="2" :max="1000" @input="sendEvent")
//- base-quantity(v-model="config.style.opacity" :min="5" :step="5" :max="100" @input="sendEvent")
transition(enter-active-class="animated slideInDown" leave-active-class="animated-fast slideOutUp")
div(:class="$style.themes" v-show="isShowThemeList")
ul(:class="$style.themeList")
li(:class="$style.btnBack")
button(:class="$style.btn" @click="isShowThemeList = false" :title="$t('desktopLyric.back')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='20px' viewBox='0 0 512 512' space='preserve')
use(xlink:href='#icon-back')
li(:class="[$style.themeItem, theme.className, lrcConfig.theme == theme.id ? $style.active : null]" v-for="theme in themes" @click="handleToggleTheme(theme)")
</template>
<script>
import { rendererSend, rendererOn, NAMES } from '../../../common/ipc'
export default {
props: {
lrcConfig: {
type: Object,
default() {
return {
enable: false,
isLock: false,
isAlwaysOnTop: false,
width: 600,
height: 700,
x: -1,
y: -1,
theme: '',
style: {
fontSize: 125,
opacity: 80,
isZoomActiveLrc: true,
},
}
},
},
themes: {
type: Array,
default() {
return []
},
},
},
data() {
return {
config: {
enable: false,
isLock: false,
isAlwaysOnTop: false,
theme: 0,
style: {
fontSize: 125,
opacity: 80,
isZoomActiveLrc: true,
},
},
isShowThemeList: false,
}
},
watch: {
lrcConfig: {
handler(n) {
this.config = JSON.parse(JSON.stringify(n))
},
},
},
mounted() {
this.config = JSON.parse(JSON.stringify(this.lrcConfig))
rendererOn(NAMES.winLyric.key_down, (event, key) => {
})
},
methods: {
sendEvent() {
rendererSend(NAMES.winLyric.set_lyric_config, this.config)
},
handleClose() {
this.config.enable = false
this.sendEvent()
},
handleLock() {
this.config.isLock = true
this.sendEvent()
},
handleAlwaysOnTop() {
this.config.isAlwaysOnTop = !this.config.isAlwaysOnTop
this.sendEvent()
},
handleZoomLrc() {
this.config.style.isZoomActiveLrc = !this.config.style.isZoomActiveLrc
this.sendEvent()
},
handleFontChange(action, step) {
let num
switch (action) {
case 'increase':
num = Math.min(this.config.style.fontSize + step, 1000)
break
case 'decrease':
num = Math.max(this.config.style.fontSize - step, 30)
break
}
if (this.config.style.fontSize == num) return
this.config.style.fontSize = num
this.sendEvent()
},
handleOpactiyChange(action, step) {
let num
switch (action) {
case 'increase':
num = Math.min(this.config.style.opacity + step, 100)
break
case 'decrease':
num = Math.max(this.config.style.opacity - step, 6)
break
}
if (this.config.style.opacity == num) return
this.config.style.opacity = num
this.sendEvent()
},
handleToggleTheme(theme) {
this.config.theme = theme.id
this.sendEvent()
},
// handleUpdateSetting() {
// this.sendEvent()
// },
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
@bar-height: 38px;
.container {
position: relative;
// height: 50px;
transition: opacity @transition-theme;
// opacity: 0;
// &:hover {
// opacity: 1;
// }
}
.btns {
display: flex;
flex-flow: row wrap;
align-items: center;
background-color: rgba(0, 0, 0, 0.7);
}
.btn {
min-height: @bar-height;
padding: 0 10px;
cursor: pointer;
border: none;
outline: none;
background: none;
color: #fff;
transition: color @transition-theme;
&:hover {
color: @color-theme;
}
}
.themes {
width: 100%;
background-color: rgba(0, 0, 0, 0.7);
&:global(.slideInDown), &:global(.slideOutUp) {
position: absolute;
left: 0;
top: 0;
}
}
.theme-list {
display: flex;
flex-flow: row wrap;
min-height: @bar-height;
padding-top: (@bar-height - 24px) / 2;
padding-left: (@bar-height - 24px) / 2;
box-sizing: border-box;
}
.btnBack {
margin-top: -(@bar-height - 24px) / 2;
margin-left: -(@bar-height - 24px) / 2;
}
.theme-item {
display: block;
margin-bottom: (@bar-height - 24px) / 2;
margin-right: (@bar-height - 24px) / 2;
width: 24px;
height: 24px;
border-radius: @radius-border;
border: 2px solid transparent;
box-sizing: border-box;
padding: 2px;
cursor: pointer;
transition: border-color @transition-theme;
&.active {
border-color: @color-theme;
}
&:after {
display: block;
content: ' ';
width: 100%;
height: 100%;
border-radius: @radius-border;
}
each(@themes, {
&:global(.@{value}) {
&:after {
background-color: ~'@{color-@{value}-theme}';
}
}
})
}
each(@themes, {
:global(#container.@{value}) {
.btn {
&:hover {
color: ~'@{color-@{value}-theme}';
}
}
.theme-item {
&.active {
border-color: ~'@{color-@{value}-theme}';
}
}
}
})
</style>

View File

@@ -0,0 +1,59 @@
<template lang="pug">
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' style="display: none;")
defs
g#icon-close(fill='currentColor')
// 0 0 24 24
path(d='M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z')
g#icon-unlock(fill='currentColor')
// 0 0 24 24
path(d='M18,20V10H6V20H18M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V10A2,2 0 0,1 6,8H15V6A3,3 0 0,0 12,3A3,3 0 0,0 9,6H7A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,17A2,2 0 0,1 10,15A2,2 0 0,1 12,13A2,2 0 0,1 14,15A2,2 0 0,1 12,17Z')
g#icon-lock(fill='currentColor')
// 0 0 24 24
path(d='M12,17C10.89,17 10,16.1 10,15C10,13.89 10.89,13 12,13A2,2 0 0,1 14,15A2,2 0 0,1 12,17M18,20V10H6V20H18M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V10C4,8.89 4.89,8 6,8H7V6A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,3A3,3 0 0,0 9,6V8H15V6A3,3 0 0,0 12,3Z')
g#icon-font-decrease(fill='currentColor')
// 0 0 24 24
path(d='M5.12,14L7.5,7.67L9.87,14M6.5,5L1,19H3.25L4.37,16H10.62L11.75,19H14L8.5,5H6.5M18,17L23,11.93L21.59,10.5L19,13.1V7H17V13.1L14.41,10.5L13,11.93L18,17Z')
g#icon-font-increase(fill='currentColor')
// 0 0 24 24
path(d='M5.12,14L7.5,7.67L9.87,14M6.5,5L1,19H3.25L4.37,16H10.62L11.75,19H14L8.5,5H6.5M18,7L13,12.07L14.41,13.5L17,10.9V17H19V10.9L21.59,13.5L23,12.07L18,7Z')
g#icon-opactiy-increase(fill='currentColor')
// 0 0 24 24
path(d="M12,6A6,6 0 0,1 18,12C18,14.22 16.79,16.16 15,17.2V19A1,1 0 0,1 14,20H10A1,1 0 0,1 9,19V17.2C7.21,16.16 6,14.22 6,12A6,6 0 0,1 12,6M14,21V22A1,1 0 0,1 13,23H11A1,1 0 0,1 10,22V21H14M20,11H23V13H20V11M1,11H4V13H1V11M13,1V4H11V1H13M4.92,3.5L7.05,5.64L5.63,7.05L3.5,4.93L4.92,3.5M16.95,5.63L19.07,3.5L20.5,4.93L18.37,7.05L16.95,5.63Z")
//- path(d='M17.66,8L12,2.35L6.34,8C4.78,9.56 4,11.64 4,13.64C4,15.64 4.78,17.75 6.34,19.31C7.9,20.87 9.95,21.66 12,21.66C14.05,21.66 16.1,20.87 17.66,19.31C19.22,17.75 20,15.64 20,13.64C20,11.64 19.22,9.56 17.66,8M6,14C6,12 6.62,10.73 7.76,9.6L12,5.27L16.24,9.65C17.38,10.77 18,12 18,14H6Z')
g#icon-opactiy-decrease(fill='currentColor')
// 0 0 24 24
path(d="M20,11H23V13H20V11M1,11H4V13H1V11M13,1V4H11V1H13M4.92,3.5L7.05,5.64L5.63,7.05L3.5,4.93L4.92,3.5M16.95,5.63L19.07,3.5L20.5,4.93L18.37,7.05L16.95,5.63M12,6A6,6 0 0,1 18,12C18,14.22 16.79,16.16 15,17.2V19A1,1 0 0,1 14,20H10A1,1 0 0,1 9,19V17.2C7.21,16.16 6,14.22 6,12A6,6 0 0,1 12,6M14,21V22A1,1 0 0,1 13,23H11A1,1 0 0,1 10,22V21H14M11,18H13V15.87C14.73,15.43 16,13.86 16,12A4,4 0 0,0 12,8A4,4 0 0,0 8,12C8,13.86 9.27,15.43 11,15.87V18Z")
g#icon-top-on(fill='currentColor')
// 0 0 24 24
path(d="M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z")
//- path(d='M17.66,8L12,2.35L6.34,8C4.78,9.56 4,11.64 4,13.64C4,15.64 4.78,17.75 6.34,19.31C7.9,20.87 9.95,21.66 12,21.66C14.05,21.66 16.1,20.87 17.66,19.31C19.22,17.75 20,15.64 20,13.64C20,11.64 19.22,9.56 17.66,8M6,14C6,12 6.62,10.73 7.76,9.6L12,5.27L16.24,9.65C17.38,10.77 18,12 18,14H6Z')
g#icon-top-off(fill='currentColor')
// 0 0 24 24
path(d="M2,5.27L3.28,4L20,20.72L18.73,22L12.8,16.07V22H11.2V16H6V14L8,12V11.27L2,5.27M16,12L18,14V16H17.82L8,6.18V4H7V2H17V4H16V12Z")
g#icon-theme(fill='currentColor')
// 0 0 24 24
path(d='M17.5,12A1.5,1.5 0 0,1 16,10.5A1.5,1.5 0 0,1 17.5,9A1.5,1.5 0 0,1 19,10.5A1.5,1.5 0 0,1 17.5,12M14.5,8A1.5,1.5 0 0,1 13,6.5A1.5,1.5 0 0,1 14.5,5A1.5,1.5 0 0,1 16,6.5A1.5,1.5 0 0,1 14.5,8M9.5,8A1.5,1.5 0 0,1 8,6.5A1.5,1.5 0 0,1 9.5,5A1.5,1.5 0 0,1 11,6.5A1.5,1.5 0 0,1 9.5,8M6.5,12A1.5,1.5 0 0,1 5,10.5A1.5,1.5 0 0,1 6.5,9A1.5,1.5 0 0,1 8,10.5A1.5,1.5 0 0,1 6.5,12M12,3A9,9 0 0,0 3,12A9,9 0 0,0 12,21A1.5,1.5 0 0,0 13.5,19.5C13.5,19.11 13.35,18.76 13.11,18.5C12.88,18.23 12.73,17.88 12.73,17.5A1.5,1.5 0 0,1 14.23,16H16A5,5 0 0,0 21,11C21,6.58 16.97,3 12,3Z')
g#icon-vibrate(fill='currentColor')
// 0 0 24 24
path(d="M16,19H8V5H16M16.5,3H7.5A1.5,1.5 0 0,0 6,4.5V19.5A1.5,1.5 0 0,0 7.5,21H16.5A1.5,1.5 0 0,0 18,19.5V4.5A1.5,1.5 0 0,0 16.5,3M19,17H21V7H19M22,9V15H24V9M3,17H5V7H3M0,15H2V9H0V15Z")
g#icon-vibrate-off(fill='currentColor')
// 0 0 24 24
path(d="M8.2,5L6.55,3.35C6.81,3.12 7.15,3 7.5,3H16.5A1.5,1.5 0 0,1 18,4.5V14.8L16,12.8V5H8.2M0,15H2V9H0V15M21,17V7H19V15.8L20.2,17H21M3,17H5V7H3V17M18,17.35L22.11,21.46L20.84,22.73L18,19.85C17.83,20.54 17.21,21 16.5,21H7.5A1.5,1.5 0 0,1 6,19.5V7.89L1.11,3L2.39,1.73L6.09,5.44L8,7.34L16,15.34L18,17.34V17.35M16,17.89L8,9.89V19H16V17.89M22,9V15H24V9H22Z")
g#icon-back(fill='currentColor')
// 0 0 512 512
path(d='M511.563,434.259c-1.728-142.329-124.42-258.242-277.087-263.419V95.999c0-17.645-14.342-31.999-31.974-31.999 c-7.931,0-15.591,3.042-21.524,8.562c0,0-134.828,124.829-173.609,163.755C2.623,241.109,0,248.088,0,255.994 c0,7.906,2.623,14.885,7.369,19.687c38.781,38.915,173.609,163.745,173.609,163.745c5.933,5.521,13.593,8.562,21.524,8.562 c17.631,0,31.974-14.354,31.974-31.999v-74.591c153.479,2.156,255.792,50.603,255.792,95.924c0,5.896,4.767,10.666,10.658,10.666 c0.167,0.021,0.333,0.01,0.416,0c5.891,0,10.658-4.771,10.658-10.666C512,436.259,511.854,435.228,511.563,434.259z')
</template>

View File

@@ -0,0 +1,362 @@
<template lang="pug">
div(:class="[$style.lyric, lyricEvent.isMsDown ? $style.draging : null]" :style="lrcStyles" @wheel="handleWheel" @mousedown="handleLyricMouseDown" ref="dom_lyric")
div(:class="$style.lyricSpace")
div(v-for="(info, index) in lyricLines" :key="index" :class="[$style.lineContent, lyric.line == index ? (lrcConfig.style.isZoomActiveLrc ? $style.lrcActiveZoom : $style.lrcActive) : null]")
p(:class="$style.lrcLine") {{info.text}}
div(:class="$style.lyricSpace")
</template>
<script>
import { rendererOn, rendererSend, NAMES } from '../../../common/ipc'
import { scrollTo } from '../../../renderer/utils'
import Lyric from 'lrc-file-parser'
let cancelScrollFn = null
export default {
props: {
lrcConfig: {
type: Object,
default() {
return {
style: {
fontSize: 125,
opacity: 80,
isZoomActiveLrc: true,
},
}
},
},
isShowLyricTransition: {
type: Boolean,
default: true,
},
},
data() {
return {
musicInfo: {
songmid: null,
name: '^',
singer: '^',
album: null,
},
lyric: {
line: 0,
lines: [],
},
dom_lines: [],
clickTime: 0,
lyricEvent: {
isMsDown: false,
msDownY: 0,
msDownScrollY: 0,
isStopScroll: false,
timeout: null,
},
winEvent: {
isMsDown: false,
msDownX: 0,
msDownY: 0,
},
_lyricLines: [],
lyricLines: [],
isSetedLines: false,
isPlay: false,
lyrics: {
lyric: '',
tlyric: '',
},
}
},
computed: {
lrcStyles() {
return {
fontSize: this.lrcConfig.style.fontSize / 100 + 'rem',
opacity: this.lrcConfig.style.opacity / 100,
}
},
},
watch: {
'lyric.lines': {
handler(n, o) {
this.isSetedLines = true
if (o) {
this._lyricLines = n
if (n.length) {
this.lyricLines = n
this.$nextTick(() => {
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('p')
this.handleScrollLrc()
})
} else {
if (cancelScrollFn) {
cancelScrollFn()
cancelScrollFn = null
}
cancelScrollFn = scrollTo(this.$refs.dom_lyric, 0, 300, () => {
if (this.lyricLines === this._lyricLines && this._lyricLines.length) return
this.lyricLines = this._lyricLines
this.$nextTick(() => {
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('p')
this.handleScrollLrc()
})
}, 50)
}
} else {
this.lyricLines = n
this.$nextTick(() => {
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('p')
this.handleScrollLrc()
})
}
},
immediate: true,
},
'lyric.line': {
handler(n) {
if (n < 0) return
if (n == 0 && this.isSetedLines) return this.isSetedLines = false
this.handleScrollLrc()
},
immediate: true,
},
isShowLyricTransition(n) {
console.log(n)
this.setLyric()
rendererSend(NAMES.winLyric.get_lyric_info, 'status')
},
},
created() {
rendererOn(NAMES.winLyric.set_lyric_info, (event, data) => this.handleSetInfo(data))
window.lrc = new Lyric({
onPlay: (line, text) => {
this.lyric.text = text
this.lyric.line = line
// console.log(line, text)
},
onSetLyric: lines => { // listening lyrics seting event
// console.log(lines) // lines is array of all lyric text
this.lyric.lines = lines
this.lyric.line = 0
},
offset: 100,
})
},
mounted() {
document.addEventListener('mousemove', this.handleMouseMsMove)
document.addEventListener('mouseup', this.handleMouseMsUp)
rendererSend(NAMES.winLyric.get_lyric_info, 'info')
},
beforeDestroy() {
this.clearLyricScrollTimeout()
document.removeEventListener('mousemove', this.handleMouseMsMove)
document.removeEventListener('mouseup', this.handleMouseMsUp)
},
methods: {
handleSetInfo({ type, data }) {
// console.log(type, data)
switch (type) {
case 'lyric':
this.lyrics.lyric = data.lrc
this.lyrics.tlyric = data.tlrc
this.setLyric()
break
case 'play':
this.isPlay = true
window.lrc.play(data)
break
case 'pause':
this.isPlay = false
window.lrc.pause()
break
case 'info':
// console.log('info', data)
this.lyrics.lyric = data.lyric
this.lyrics.tlyric = data.tlyric
this.setLyric()
this.$nextTick(() => {
this.lyric.line = data.line
rendererSend(NAMES.winLyric.get_lyric_info, 'status')
})
case 'music_info':
this.musicInfo.name = data.name
this.musicInfo.songmid = data.songmid
this.musicInfo.singer = data.singer
this.musicInfo.album = data.album
break
case 'status':
// console.log('status', data)
this.isPlay = data.isPlay
this.lyric.line = data.line
if (data.isPlay) window.lrc.play(data.played_time)
break
default:
break
}
// console.log(data)
},
handleResize() {
this.setProgressWidth()
},
handleScrollLrc() {
if (!this.dom_lines.length) return
if (cancelScrollFn) {
cancelScrollFn()
cancelScrollFn = null
}
if (this.lyricEvent.isStopScroll) return
let dom_p = this.dom_lines[this.lyric.line]
cancelScrollFn = scrollTo(this.$refs.dom_lyric, dom_p ? (dom_p.offsetTop - this.$refs.dom_lyric.clientHeight * 0.5 + dom_p.clientHeight / 2) : 0)
},
handleLyricMouseDown(e) {
if (e.target.classList.contains(this.$style.lrcLine)) {
this.lyricEvent.isMsDown = true
this.lyricEvent.msDownY = e.clientY
this.lyricEvent.msDownScrollY = this.$refs.dom_lyric.scrollTop
} else {
this.winEvent.isMsDown = true
this.winEvent.msDownX = e.clientX
this.winEvent.msDownY = e.clientY
}
},
handleMouseMsUp(e) {
this.lyricEvent.isMsDown = false
this.winEvent.isMsDown = false
},
handleMouseMsMove(e) {
if (this.lyricEvent.isMsDown) {
if (!this.lyricEvent.isStopScroll) this.lyricEvent.isStopScroll = true
if (cancelScrollFn) {
cancelScrollFn()
cancelScrollFn = null
}
this.$refs.dom_lyric.scrollTop = this.lyricEvent.msDownScrollY + this.lyricEvent.msDownY - e.clientY
this.startLyricScrollTimeout()
} else if (this.winEvent.isMsDown) {
rendererSend(NAMES.winLyric.set_win_bounds, {
x: e.clientX - this.winEvent.msDownX,
y: e.clientY - this.winEvent.msDownY,
w: window.innerWidth,
h: window.innerHeight,
})
}
// if (this.volumeEvent.isMsDown) {
// let val = this.volumeEvent.msDownValue + (e.clientX - this.volumeEvent.msDownX) / 70
// this.volume = val < 0 ? 0 : val > 1 ? 1 : val
// if (this.audio) this.audio.volume = this.volume
// }
// console.log(val)
},
startLyricScrollTimeout() {
this.clearLyricScrollTimeout()
this.lyricEvent.timeout = setTimeout(() => {
this.lyricEvent.timeout = null
this.lyricEvent.isStopScroll = false
if (!this.isPlay) return
this.handleScrollLrc()
}, 3000)
},
handleWheel(event) {
// console.log(event.deltaY)
if (!this.lyricEvent.isStopScroll) this.lyricEvent.isStopScroll = true
if (cancelScrollFn) {
cancelScrollFn()
cancelScrollFn = null
}
this.$refs.dom_lyric.scrollTop = this.$refs.dom_lyric.scrollTop + event.deltaY
this.startLyricScrollTimeout()
},
clearLyricScrollTimeout() {
if (!this.lyricEvent.timeout) return
clearTimeout(this.lyricEvent.timeout)
this.lyricEvent.timeout = null
},
close() {
rendererSend(NAMES.winLyric.close)
},
setLyric() {
window.lrc.setLyric((this.isShowLyricTransition && this.lyrics.tlyric ? this.lyrics.tlyric + '\n' : '') + this.lyrics.lyric)
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.lyric {
text-align: center;
height: 100%;
overflow: hidden;
font-size: 68px;
padding: 0 5px;
opacity: .6;
transition: opacity @transition-theme;
&.draging {
.lrc-line {
cursor: grabbing;
}
}
}
.lrc-line {
display: inline-block;
padding: 8px 0;
line-height: 1.2;
overflow-wrap: break-word;
transition: @transition-theme;
transition-property: color, font-size, text-shadow;
cursor: grab;
// font-weight: bold;
// background-clip: text;
color: @color-theme-lyric;
text-shadow: 1px 1px 2px #000;
// background: linear-gradient(@color-theme-lyric, @color-theme-lyric);
// background-clip: text;
// -webkit-background-clip: text;
// -webkit-text-fill-color: #fff;
// -webkit-text-stroke: thin #124628;
}
.lyric-space {
height: 70%;
}
.lrc-active {
.lrc-line {
color: @color-theme-lyric_2;
// background: linear-gradient(@color-theme-lyric, @color-theme-lyric_2);
// background-clip: text;
// -webkit-background-clip: text;
// -webkit-text-fill-color: @color-theme-lyric_2;
// -webkit-text-stroke: thin #124628;
}
}
.lrc-active-zoom {
.lrc-active;
font-size: 1.2em;
}
.footer {
flex: 0 0 100px;
overflow: hidden;
display: flex;
align-items: center;
}
each(@themes, {
:global(#container.@{value}) {
.lrc-line {
color: ~'@{color-@{value}-theme-lyric}';
}
.lrc-active, .lrc-active-zoom {
.lrc-line {
color: ~'@{color-@{value}-theme-lyric_2}';
}
}
}
})
</style>

View File

@@ -0,0 +1,13 @@
import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'
const requireComponent = require.context('./', true, /\.vue$/)
requireComponent.keys().forEach(fileName => {
const componentConfig = requireComponent(fileName)
const componentName = upperFirst(camelCase(fileName.replace(/^\.\//, '').replace(/\.\w+$/, '')))
Vue.component(componentName, componentConfig.default || componentConfig)
})

View File

@@ -0,0 +1,10 @@
html(lang="cn")
head
meta(charset="UTF-8")
meta(name="viewport" content="width=device-width, initial-scale=1.0")
meta(http-equiv="X-UA-Compatible" content="ie=edge")
title 桌面歌词-洛雪音乐助手
body
#root

View File

@@ -0,0 +1,17 @@
import Vue from 'vue'
import i18n from './plugins/i18n'
import './components'
import App from './App'
import '../common/error'
Vue.config.productionTip = false
new Vue({
i18n,
el: '#root',
render: h => h(App),
})

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