Compare commits

...

386 Commits

Author SHA1 Message Date
lyswhut
78c5250f1b 发布v0.18.2 2020-05-02 12:44:00 +08:00
lyswhut
9797d98fac 修复开启托盘时,可能导致无法自动更新的问题 2020-05-02 12:43:00 +08:00
lyswhut
43c2dc3dec 发布v0.18.1 2020-05-02 12:22:47 +08:00
lyswhut
50e140e606 修复导入设置某些设置未立即生效的问题 2020-05-02 12:14:28 +08:00
lyswhut
3f88701751 win下的托盘图标使用更大的图片 2020-05-02 11:53:03 +08:00
lyswhut
7d89d53b1b 加长阅读时间 2020-05-01 22:26:13 +08:00
lyswhut
09f7989cab 修复无法全选的问题 2020-05-01 21:03:43 +08:00
lyswhut
6255cfd401 将electron移到devDependencies 2020-05-01 20:24:28 +08:00
lyswhut
e7e4897dcd 发布v0.18.0版本 2020-05-01 20:15:57 +08:00
lyswhut
9639143893 更新依赖 2020-05-01 20:14:14 +08:00
lyswhut
3be3b0ca36 修复mac下只能全选一次的问题 2020-05-01 20:02:18 +08:00
lyswhut
afc81b6308 将托盘挂到全局变量中 2020-05-01 18:28:33 +08:00
lyswhut
36d140d4bd 修复Mac下的虾米验证、托盘问题 2020-05-01 18:22:19 +08:00
lyswhut
edff998be9 调试 2020-05-01 14:37:07 +08:00
lyswhut
5a856e468e 解决 MAC 首次加载页面无法拖动滑块的问题 2020-05-01 13:42:43 +08:00
lyswhut
d2845f91b5 修正dmg配置 2020-05-01 00:23:35 +08:00
lyswhut
9040c13c3f 修改验证窗口属性 2020-05-01 00:01:31 +08:00
lyswhut
7ed95ff523 修复mac下开启托盘时无法从dock退出的问题 2020-04-30 23:41:45 +08:00
lyswhut
aed9889a2e 修复 mac 托盘图标问题 2020-04-30 21:49:35 +08:00
lyswhut
0090f484d8 尝试新增包到bintray 2020-04-30 18:14:17 +08:00
lyswhut
0c576c65a8 转义歌词字符 2020-04-30 17:03:51 +08:00
lyswhut
b94cde5fbe 更新依赖 2020-04-30 13:24:13 +08:00
lyswhut
df2582d1de 禁止图片拖动 2020-04-30 10:24:40 +08:00
lyswhut
f237a9aab5 修复开始任务时对任务开始失败时的错误处理 2020-04-30 00:19:14 +08:00
lyswhut
12c370d7cb 修复可能导致无法操作任务的问题 2020-04-29 23:34:24 +08:00
lyswhut
015c17e2dc 优化下载列表流畅度 2020-04-29 19:18:53 +08:00
lyswhut
d8a3466914 修复播放下载列表无法切换歌曲的问题 2020-04-29 16:09:44 +08:00
lyswhut
05d2ffe8fa 更新源码使用方法 2020-04-29 12:37:44 +08:00
lyswhut
a62d8304e2 分离可用音质控制逻辑 2020-04-29 12:33:41 +08:00
lyswhut
e51ee8c1ad 添加其他动画 2020-04-28 14:28:51 +08:00
lyswhut
6d0f668f88 更新许可协议 2020-04-28 13:48:46 +08:00
lyswhut
ed5e590859 更新依赖 2020-04-28 13:36:04 +08:00
lyswhut
346fa2c004 新增播放详情页 2020-04-28 13:33:55 +08:00
lyswhut
db04b0c285 优化列表选择逻辑 2020-04-27 22:29:49 +08:00
lyswhut
07800bdb83 添加描述 2020-04-27 22:10:14 +08:00
lyswhut
ad8b338bd9 修复潜在的bug 2020-04-27 21:22:29 +08:00
lyswhut
031c0c263d 修改绑定的键 2020-04-27 11:29:23 +08:00
lyswhut
d54053003e 调整颜色 2020-04-26 23:54:12 +08:00
lyswhut
adb453c216 新增“超大”、“巨大”窗口尺寸 2020-04-26 23:31:38 +08:00
lyswhut
ef04e43323 修复协议弹窗在导入数据时不倒计时的问题 2020-04-26 22:44:47 +08:00
lyswhut
9ff5147418 替换程序中所有同步API的调用 2020-04-26 22:08:23 +08:00
lyswhut
cdbedf3d64 移除列表选择框,新增键盘选择机制 2020-04-26 22:06:13 +08:00
lyswhut
3d52fa059e 更新依赖 2020-04-26 13:29:33 +08:00
lyswhut
ef46d1a308 减淡颜色 2020-04-26 13:20:59 +08:00
lyswhut
d57cfa90fc 新增新皮肤“粉妆玉琢”、“青出于黑” 2020-04-26 13:01:17 +08:00
lyswhut
7aece7dd7e 隐藏多余提示 2020-04-23 21:43:55 +08:00
lyswhut
ccf362db41 新增虾米音源 2020-04-23 12:50:46 +08:00
lyswhut
7bd4002f09 新增使用协议 2020-04-23 12:12:46 +08:00
lyswhut
9460b12cf9 移除多余代码 2020-04-22 22:52:41 +08:00
lyswhut
8cac592182 移除多余依赖 2020-04-22 22:40:58 +08:00
lyswhut
83b25b0c33 修复提示颜色适应 2020-04-19 19:20:00 +08:00
lyswhut
1832ec753c 修复官方歌单的打开 2020-04-19 14:11:20 +08:00
lyswhut
0c4f943d72 添加支持对酷狗码打开的打开 2020-04-19 13:56:08 +08:00
lyswhut
116ee25e28 更新faq 2020-04-16 13:10:45 +08:00
lyswhut
6149c2a8a8 更新关于软件描述 2020-04-15 20:57:53 +08:00
lyswhut
b2e475b857 修正播放列表浮动按钮错误的文字提示 2020-04-15 08:56:45 +08:00
lyswhut
f7e4e98b01 更新依赖 2020-04-15 08:55:44 +08:00
lyswhut
c4a9919652 更新关于软件 2020-04-13 23:06:14 +08:00
lyswhut
5c1fb7d4e8 调换位置 2020-04-13 13:30:28 +08:00
lyswhut
b1d0a4e7ad 更新网盘链接 2020-04-13 11:46:15 +08:00
lyswhut
75db995342 优化聚合热门搜索排序 2020-04-12 18:25:18 +08:00
lyswhut
c318856534 下载列表的歌曲下载、播放将随设置中的保存路径改变而改变 2020-04-12 12:32:44 +08:00
lyswhut
cf42e1846e 修复打开歌单详情的情况下切到其他界面再切回来报错的问题 2020-04-12 11:09:24 +08:00
lyswhut
56a5ebbb9d 更新 readme 2020-04-11 22:41:24 +08:00
lyswhut
7431e1e605 更新依赖 2020-04-11 12:33:03 +08:00
lyswhut
4b6a460e69 更新依赖 2020-04-09 20:45:46 +08:00
lyswhut
f6f0c2f1dc 添加加群说明 2020-04-09 12:33:33 +08:00
lyswhut
90357ae386 修复CheckBox bug 2020-04-09 12:31:05 +08:00
lyswhut
bdc6b08234 修复歌曲切换方式无法取消勾选的问题 2020-04-09 11:59:12 +08:00
lyswhut
1ba68d67ba 暂停不覆盖歌词 2020-04-09 10:58:06 +08:00
lyswhut
d0518e6dfe 兼容另一种用户歌单打开方式 2020-04-08 16:54:29 +08:00
lyswhut
3be6a78568 移除多余代码 2020-04-08 11:44:35 +08:00
lyswhut
a6b8881e30 修复无法打开官方歌单的问题 2020-04-08 11:22:35 +08:00
lyswhut
23c057ac08 去重 2020-04-06 17:54:26 +08:00
lyswhut
07460ba06f 兼容其他歌单分享方式 2020-04-06 17:42:26 +08:00
lyswhut
bcbedbe053 优化逻辑 2020-04-06 14:18:40 +08:00
lyswhut
ba26037c7f 修正拼写错误 2020-04-06 14:00:20 +08:00
lyswhut
60243215a0 加强兼容性 2020-04-06 01:33:14 +08:00
lyswhut
9c3ab28da9 新增打开酷狗源用户歌单 2020-04-06 01:28:10 +08:00
lyswhut
2b1076f09e 修复npm安全漏洞警告,优化编译时cli的显示状态 2020-04-05 15:28:36 +08:00
lyswhut
241859232b npm audit fix 2020-04-05 13:13:55 +08:00
lyswhut
456fdc0fca 修复最小化托盘时再次运行程序时无法恢复窗口显示的问题 2020-04-03 21:45:10 +08:00
lyswhut
be84bd27d3 修复启用托盘后无法退出程序的问题 2020-04-03 21:30:48 +08:00
lyswhut
2e84788d53 修复窗口还原时闪烁的问题 2020-04-03 21:19:44 +08:00
lyswhut
76b3b3b23d 修复托盘创建显示隐藏Bug 2020-04-03 20:42:02 +08:00
lyswhut
426c88c4ed 更新更新日志 2020-04-03 20:15:52 +08:00
lyswhut
d625b0d567 Merge branch 'master' into dev 2020-04-03 20:13:54 +08:00
lyswhut
e350c5f070 切换换行符 2020-04-03 20:09:43 +08:00
Lasy
1e481edc67 增加“关闭时最小化到托盘”的设置 (#166)
添加托盘设置,调整主进程代码架构
2020-04-03 20:00:43 +08:00
lyswhut
12f02dda98 更新依赖 2020-03-31 15:49:44 +08:00
lyswhut
ef50d90a18 修复开发模式下页面无法刷新的问题 2020-03-31 15:46:19 +08:00
lyswhut
25ae72cd26 更新Windows 7下界面异常描述 2020-03-30 19:37:26 +08:00
lyswhut
eb8f83786c 更新更新日志 2020-03-29 23:15:44 +08:00
lyswhut
cd7b075016 添加空格 2020-03-29 23:12:28 +08:00
lyswhut
19068dc6b0 Merge branch 'master' into dev 2020-03-29 23:02:12 +08:00
lyswhut
93ba0bd807 修改换行符 2020-03-29 22:55:19 +08:00
Words
15a76486ef English updates and a Chinese string fault (#163)
* Update English language files

- 修正:`backup_part_export_list_desc`的文字描述问题
- 修正:英文翻译问题

Co-authored-by: Madoka☆Magica! <adsh@pku.edu.cn>
Co-authored-by: lyswhut <lyswhut@qq.com>
2020-03-29 22:47:05 +08:00
lyswhut
f01e367785 限制导航及窗口打开 2020-03-29 14:48:02 +08:00
lyswhut
9b536f8dc1 优化程序启动窗口显示机制 2020-03-28 10:50:31 +08:00
lyswhut
664a0e4ad8 完善符号转义 2020-03-28 10:42:46 +08:00
lyswhut
9ed64354fc 更新依赖到最新 2020-03-25 21:16:27 +08:00
lyswhut
f59a160249 修复字段为空时写入undefined的问题 2020-03-24 11:53:36 +08:00
lyswhut
cdd7774a73 添加软件启动时是否自动聚焦搜索框的设置 2020-03-24 09:47:36 +08:00
lyswhut
a10777d598 完善部分翻译 2020-03-24 09:10:23 +08:00
lyswhut
a4c5670409 新增FLAC格式音乐标签信息写入与封面嵌入 2020-03-22 18:33:51 +08:00
lyswhut
343c53b41c 更新win7界面显示异常标题 2020-03-21 23:28:44 +08:00
lyswhut
4c97bc34ce 修复下载管理刷新URL失败时不标记任务下载失败的问题 2020-03-21 23:08:46 +08:00
lyswhut
83f0ca918a 新增FLAC格式音乐标签信息写入 2020-03-21 22:31:02 +08:00
lyswhut
bc7c3968b6 更新地址协议 2020-03-20 20:15:21 +08:00
lyswhut
1aba217b1a 修复网易源某些歌曲提示没有可播放的音质的问题 2020-03-19 17:23:53 +08:00
lyswhut
a95e87a08f 更新依赖 2020-03-19 13:54:54 +08:00
lyswhut
b75e299b2f Merge branch 'master' into dev 2020-03-17 09:55:59 +08:00
lyswhut
3e63b49c3c 更新软件调用摄像头的说明 2020-03-17 09:34:43 +08:00
lyswhut
1eaf2372bb 略微减少列表行高 2020-03-16 18:27:16 +08:00
lyswhut
8b8400e5be 略微加深音量条底色 2020-03-16 14:40:51 +08:00
lyswhut
c6551c9ea3 优化子节点判断 2020-03-16 12:51:14 +08:00
lyswhut
b04c676288 更换反馈方式 2020-03-15 21:15:15 +08:00
lyswhut
01e2a2bdb2 发布0.17.0版本 2020-03-15 20:10:58 +08:00
lyswhut
670533807a 新增热搜词、历史搜索记录 2020-03-15 17:17:41 +08:00
lyswhut
9d1270374e 优化右上角最小化/关闭按钮布局 2020-03-15 00:15:52 +08:00
lyswhut
353e8bd0de 支持打开短链接歌单 2020-03-14 23:36:38 +08:00
lyswhut
9ca0ec82a8 修复下载列表标签切换及全选bug 2020-03-14 23:13:36 +08:00
lyswhut
bb5fd44750 更新歌单打开描述 2020-03-14 22:56:12 +08:00
lyswhut
1e435b0ff8 支持直接打开酷我手机版分享出来的歌单地址 2020-03-14 22:54:19 +08:00
lyswhut
bb080c2d18 优化歌单打开机制 2020-03-14 20:58:03 +08:00
lyswhut
91063075d3 新增发现新版本更新失败弹窗的忽略提醒按钮 2020-03-14 18:08:02 +08:00
lyswhut
ccaf485c91 修复数据导入Bug 2020-03-14 00:49:13 +08:00
lyswhut
125207226b 改变下载任务插入方式 2020-03-13 19:44:55 +08:00
lyswhut
393affbd90 优化选择器显示 2020-03-13 16:37:00 +08:00
lyswhut
4f06fe45d3 新增音源别名 2020-03-13 16:16:42 +08:00
lyswhut
ba1a981c87 修改默认窗口大小为重 2020-03-13 14:45:06 +08:00
lyswhut
6d51240a27 修复选择器bug,新增较小、较大窗口设置 2020-03-13 13:38:02 +08:00
lyswhut
527143405f 恢复更新超时时间 2020-03-13 12:24:50 +08:00
lyswhut
2808fdd3c0 添加供应链攻击科普链接 2020-03-13 12:22:19 +08:00
lyswhut
a2c84392f4 新增软件界面元素大小调整 2020-03-12 23:33:19 +08:00
lyswhut
3fdff4e6eb 完善翻译 2020-03-12 21:12:12 +08:00
lyswhut
dd41e00691 移除窗口恢复事件 2020-03-12 19:02:59 +08:00
lyswhut
29bcaa9558 更新依赖 2020-03-12 16:59:16 +08:00
lyswhut
3be3e8546f 修复鼠标右击复制列表内容时会复制音质标签文字的问题 2020-03-12 16:06:12 +08:00
lyswhut
0bbb760e61 完善音频输出功能 2020-03-10 23:31:25 +08:00
lyswhut
4f238f4f62 完善音频输出设置功能 2020-03-10 12:40:47 +08:00
lyswhut
a244c6d5e3 Merge branch 'md' into dev 2020-03-10 12:20:14 +08:00
lyswhut
aa5765971e 完善选择器 2020-03-07 13:21:13 +08:00
lyswhut
2a66edd1c5 完善默认值显示 2020-03-07 11:55:31 +08:00
lyswhut
a6e86e8d6b 修复歌单详情处于加载状态时无法返回的问题 2020-03-07 10:52:43 +08:00
lyswhut
f30cb9da59 Merge branch 'dev' into md 2020-03-03 12:31:40 +08:00
lyswhut
e94d17a922 添加启动参数说明 2020-03-02 16:21:40 +08:00
lyswhut
53f5c168ad 新增启动参数 search 2020-03-02 15:57:26 +08:00
lyswhut
4533f7b1d5 优化播放进度条的动画效果 2020-03-02 15:50:05 +08:00
lyswhut
b9398e882a 初步完善 2020-02-28 13:19:49 +08:00
lyswhut
66f19d72da 更新依赖 2020-02-28 13:18:44 +08:00
lyswhut
35e85cfdcb 修复文件导入顺序导致的恢复设置语言报错 2020-02-27 23:25:21 +08:00
lyswhut
6d6b63cce1 新增对主题名字的翻译 2020-02-27 23:21:28 +08:00
lyswhut
a7d5e77a1c 更新无法打开外部歌单描述 2020-02-26 11:16:51 +08:00
lyswhut
0332b22c4a 补上翻译 2020-02-23 23:56:09 +08:00
lyswhut
09a7974818 支持搜索源翻译 2020-02-19 22:11:48 +08:00
lyswhut
66d2f0d550 修复歌单标签翻译读取bug 2020-02-19 21:51:41 +08:00
lyswhut
d2556fed20 优化月里嫦娥皮肤侧栏鼠标悬浮颜色 2020-02-19 21:44:50 +08:00
lyswhut
49cd8536a7 新增无法打开外部歌单FAQ 2020-02-19 21:15:27 +08:00
lyswhut
b8f15bcca9 新增多语言设置 2020-02-19 20:41:34 +08:00
lyswhut
a17ba9f62c update issue template 2020-02-18 12:28:20 +08:00
lyswhut
a9aa9a2e20 发布0.16.0版本 2020-02-16 11:09:25 +08:00
lyswhut
43da07ca4c 优化版本对比机制 2020-02-15 22:16:16 +08:00
lyswhut
1b22abcd50 优化版本号对比机制 2020-02-15 21:02:47 +08:00
lyswhut
f85c83169d 设置响应时间限制 2020-02-15 19:39:50 +08:00
lyswhut
ecff344473 更新electron到8.0.1 2020-02-15 18:08:49 +08:00
lyswhut
cde380782f 从request模块切换到needle 2020-02-15 18:08:04 +08:00
lyswhut
d557149326 加载新列表时清空当前列表 2020-02-15 18:07:12 +08:00
lyswhut
ecdcb040ee 优化列表加载交互 2020-02-15 12:59:35 +08:00
lyswhut
e58471fe37 修复QQ源歌单热门标签歌单列表无法加载问题 2020-02-15 12:00:53 +08:00
lyswhut
30e66473db 修复酷狗歌单热门标签列表无法加载问题 2020-02-15 00:15:11 +08:00
lyswhut
5357ed3746 修复无结果时的错误处理 2020-02-14 23:57:21 +08:00
lyswhut
337f443cbf 优化音质标签判断显示逻辑 2020-02-14 19:25:23 +08:00
lyswhut
e7ad1b8496 优化各个主题音质标签颜色 2020-02-14 19:24:56 +08:00
lyswhut
aea01de449 更新软件界面无法显示解决方案 2020-02-13 15:22:34 +08:00
lyswhut
b7fb75a49e 更新issue模板 2020-02-13 14:54:50 +08:00
lyswhut
57752d09cd 调整火影主题翻页器背景颜色 2020-02-12 15:03:23 +08:00
lyswhut
bd395f557a 添加单独win x64 x32安装包构建 2020-02-11 17:08:25 +08:00
lyswhut
2d865d8fed 添加单独win x64 x32安装包构建 2020-02-11 16:45:14 +08:00
lyswhut
5bff74e7a9 移除多个空格 2020-02-10 18:12:38 +08:00
lyswhut
a9e3723764 新增在搜索框鼠标右击时自动粘贴剪贴板的文本到搜索框中 2020-02-10 18:05:16 +08:00
lyswhut
b811227c41 更新electron到 8.0.0 2020-02-10 17:25:58 +08:00
lyswhut
4896c4dda5 新增在列表可选内容区域鼠标右击时自动复制列表已选文字的功能 2020-02-10 17:25:07 +08:00
lyswhut
b4ee1ade2d 修正描述 2020-02-07 23:09:23 +08:00
lyswhut
a6e2308282 修正邮箱地址 2020-02-03 23:01:49 +08:00
lyswhut
f56c42f353 修复某些情况下暂停任务会自动开始任务的问题 2020-02-01 14:45:52 +08:00
lyswhut
5edf51810d 新增下载失败时显示搜索按钮 2020-02-01 14:44:15 +08:00
lyswhut
81985b0e31 添加主进程代码编译错误输出 2020-01-31 12:00:10 +08:00
lyswhut
163830f1bd 更新electron到7.1.11 2020-01-31 11:59:20 +08:00
lyswhut
867ae2991c 允许选中列表内的部分文字,优化界面 2020-01-30 11:54:04 +08:00
lyswhut
ee3d04ba23 更新更新提示 2020-01-29 20:45:26 +08:00
lyswhut
71dd8c14f8 更新软件启动后,界面无法显示的解决方案 2020-01-29 20:33:45 +08:00
lyswhut
88dc9c2a74 修复大窗口时设置的音乐来源选项不换行的问题 2020-01-28 13:49:05 +08:00
lyswhut
98f012de4d 更新electron到 7.1.10 2020-01-28 12:39:42 +08:00
lyswhut
c85a036ac6 修复启用透明窗口鼠标不穿透的bug 2020-01-28 12:38:29 +08:00
lyswhut
1f8b632d95 修正架构包名 2020-01-23 17:50:44 +08:00
lyswhut
59dd56e542 发布0.15.0版本 2020-01-23 01:32:22 +08:00
lyswhut
1bfa8dcb3f bug 修复 2020-01-23 01:26:46 +08:00
lyswhut
1b381af7a7 修复歌曲下载列表无法加载的问题 2020-01-23 01:01:02 +08:00
lyswhut
2bc4912416 发布0.14.1版本 2020-01-22 22:31:39 +08:00
lyswhut
474f0d8291 修正描述 2020-01-22 22:31:04 +08:00
lyswhut
231fe54408 修复由于旧版配置文件迁移出错导致的软件界面无法显示的问题 2020-01-22 22:30:50 +08:00
lyswhut
78bef70735 发布0.14.0版本 2020-01-22 19:50:34 +08:00
lyswhut
ce7247e209 新增获取版本信息的备用链接 2020-01-22 10:12:10 +08:00
lyswhut
1d807e2a89 更新Windows 7 下界面异常问题描述 2020-01-20 13:46:41 +08:00
lyswhut
749141c10e 适配输入框提示文本颜色 2020-01-19 18:05:16 +08:00
lyswhut
e2c7c0596c 修复标签列表颜色适配 2020-01-19 17:43:40 +08:00
lyswhut
392cd72f83 优化新年快乐皮肤 2020-01-19 17:32:43 +08:00
lyswhut
340a9d937b 优化音质标签颜色 2020-01-19 17:26:53 +08:00
lyswhut
5d7ad836db 新增“新年快乐”主题 2020-01-19 16:00:01 +08:00
lyswhut
f8e449b017 新增不强制win7开启透明效果即可使用 2020-01-19 13:56:54 +08:00
lyswhut
bb0b59080a 更新依赖到最新 2020-01-17 21:52:36 +08:00
lyswhut
95b08c2406 修复无搜索结果的处理 2020-01-17 21:26:31 +08:00
lyswhut
3ef5e0e55a 更新 Electron 到 7.1.8 2020-01-14 13:06:45 +08:00
lyswhut
ae9b4bc2f5 更换migu搜索接口 2020-01-13 13:20:04 +08:00
lyswhut
914e738931 修复在线音乐列表获取失败时无限循环请求的问题 2020-01-13 12:49:09 +08:00
lyswhut
e4c19c0f2a 恢复QQ音乐源128k音质试听 2020-01-11 17:45:22 +08:00
lyswhut
54354518d0 简化写法 2020-01-10 22:19:24 +08:00
lyswhut
d36e21ab56 更新软件依赖到最新 2020-01-10 22:14:21 +08:00
lyswhut
b496e3c667 更换songmid的id值 2020-01-10 22:13:53 +08:00
lyswhut
d10434285f 更新关于软件说明 2020-01-10 22:13:10 +08:00
lyswhut
d6c8886fdb 更新“软件启动后,界面无法显示”的问题解决方案 2020-01-07 13:11:36 +08:00
lyswhut
51e3c8605c 调整列表字体颜色深度 2020-01-06 12:01:33 +08:00
lyswhut
d4d6b9c9aa 修复咪咕源无法搜索的问题 2020-01-06 00:30:52 +08:00
lyswhut
0c9c3c61fc 更新关于软件说明 2020-01-03 15:47:51 +08:00
lyswhut
0228850d6d 新增各大平台歌单热门标签显示 2019-12-31 16:12:46 +08:00
lyswhut
529078dc67 当歌曲有无损音质时隐藏高品质标签 2019-12-29 13:53:54 +08:00
lyswhut
aadb68ba08 减淡各个主题的歌曲列表分隔线颜色 2019-12-28 17:26:23 +08:00
lyswhut
c81171a39e 监听所有未捕获错误事件并记录到日志中 2019-12-27 21:42:19 +08:00
lyswhut
a50ecff189 将软件设置与播放列表分离存储成两个文件 2019-12-23 19:24:35 +08:00
lyswhut
73321829ae 给代理添加提示 2019-12-23 16:09:52 +08:00
lyswhut
e88c837482 添加软件无法联网帮助 2019-12-22 14:34:52 +08:00
lyswhut
2b263cae87 修复更新弹窗底部文字颜色适配问题 2019-12-17 12:56:25 +08:00
lyswhut
e93052f0cb 发布0.13.1版本 2019-12-16 21:19:27 +08:00
lyswhut
9539083f7e 降级electron到7.1.2 2019-12-16 21:04:22 +08:00
lyswhut
8f26eb1c69 修复全局更新弹窗无法遮盖搜索框的问题 2019-12-16 00:24:32 +08:00
lyswhut
a126e5b4c8 发布0.13.0版本 2019-12-15 21:33:46 +08:00
lyswhut
033c3cd8e9 多修复选框不定状态到选中的Bug 2019-12-15 21:23:52 +08:00
lyswhut
50d029c671 移除Q音源的试听与下载 2019-12-15 21:03:19 +08:00
lyswhut
6a3f7e6150 修复搜索建议列表被弹出层覆盖的问题 2019-12-15 19:18:31 +08:00
lyswhut
aadfbefbe9 更新electron及vue到最新 2019-12-14 14:01:53 +08:00
lyswhut
edbcaf0f90 优化“信口雌黄”皮肤配色 2019-12-14 13:54:22 +08:00
lyswhut
207427e993 新增“离开搜索界面时清空搜索列表”设置 2019-12-13 13:34:35 +08:00
lyswhut
e109dd876a 修正缓存描述 2019-12-13 13:09:44 +08:00
lyswhut
64ac69c48b 聚合搜索新增音源显示 2019-12-11 16:05:07 +08:00
lyswhut
652b06c116 更新依赖到最新 2019-12-06 20:55:12 +08:00
lyswhut
654035a469 update issue template 2019-12-04 10:45:31 +08:00
lyswhut
cd50e0086b update issue template 2019-12-03 13:03:09 +08:00
lyswhut
66a119e9dc 新增搜索框搜索建议键盘上下方向键选择功能 2019-12-02 20:46:27 +08:00
lyswhut
14ec7d48f6 更新接口文字描述 2019-12-01 14:29:26 +08:00
lyswhut
5ad5b211e9 发布0.12.1版本 2019-12-01 13:51:13 +08:00
lyswhut
aac4b6ccfe 将所有外部链接从默认浏览器打开 2019-12-01 13:45:48 +08:00
lyswhut
cfc6967fdc 优化链接点击效果 2019-12-01 13:42:16 +08:00
lyswhut
8a0b960985 更新依赖到最新 2019-12-01 12:38:54 +08:00
lyswhut
bd260c3dc5 移除多余的字 2019-12-01 12:34:48 +08:00
lyswhut
203dff8d98 修复酷我源排行榜、歌单详情列表里的歌曲音质匹配问题 2019-11-30 18:36:44 +08:00
lyswhut
ca56089a3e 取消对导出数据的格式化 2019-11-29 21:47:13 +08:00
lyswhut
a012d1b927 更新依赖到最新 2019-11-29 21:35:42 +08:00
lyswhut
0af322a3a8 优化tab布局 2019-11-29 21:33:11 +08:00
lyswhut
8d743803c8 优化滚动方法 2019-11-29 00:01:04 +08:00
lyswhut
63fed77c5b 更新安装失败、缺少dll帮助 2019-11-25 16:58:16 +08:00
lyswhut
f2077dca04 新增数据传输结束时文件下载完成情况的判断 2019-11-25 11:44:31 +08:00
lyswhut
cccfb6a08a 歌曲无封面时下载报错的问题 2019-11-25 11:41:51 +08:00
lyswhut
fa6dead83d 更新依赖到最新 2019-11-24 19:38:45 +08:00
lyswhut
605418d750 更新electron到7.1.2 2019-11-21 20:20:31 +08:00
lyswhut
8aa869b2e4 更新appImage包的分类 2019-11-20 13:19:26 +08:00
lyswhut
558b6e0ccf 发布0.12.0版本 2019-11-17 12:29:23 +08:00
lyswhut
6a695368da 修复Linux图标显示问题 2019-11-17 12:28:01 +08:00
lyswhut
ce89f29aeb 更新Linux图标配置 2019-11-17 10:50:10 +08:00
lyswhut
6d935d8589 更换下载库 2019-11-16 20:16:09 +08:00
lyswhut
967093328f 添加数据保存节流 2019-11-16 20:15:27 +08:00
lyswhut
48e1da128d 修复封面下载出错问题 2019-11-16 20:13:48 +08:00
lyswhut
369de684a6 add electron version tag 2019-11-16 20:12:42 +08:00
lyswhut
d5f65eea06 更新常见问题链接 2019-11-13 21:11:14 +08:00
lyswhut
8e5deb5bf4 添加软件启动后,界面无法显示的问题 2019-11-11 23:36:22 +08:00
lyswhut
df653258bb 更换成commonjs写法 2019-11-10 15:39:40 +08:00
lyswhut
ed28a59fcb 发布0.11.0版本 2019-11-10 13:29:54 +08:00
lyswhut
adc46411b2 新增杀毒软件提示有病毒或恶意行为说明 2019-11-10 13:27:53 +08:00
lyswhut
fcc4d666ec 移除残留remote模块的使用 2019-11-10 01:50:56 +08:00
lyswhut
9d89f5d48d 更新electron到7.1.1 2019-11-09 02:14:49 +08:00
lyswhut
88c62e01ad 新增下载管理的任务状态分类 2019-11-08 21:07:37 +08:00
lyswhut
1823598a8b 新增歌曲缓冲定时器 2019-11-07 21:56:00 +08:00
lyswhut
3fd22988c1 修正获取新版信息失败提示内容 2019-11-06 23:18:27 +08:00
lyswhut
a3ade4cde2 默认使用临时接口 2019-11-06 21:53:12 +08:00
lyswhut
55c2286eca 更新electron到7.1.0 2019-11-06 01:04:08 +08:00
lyswhut
1afa050698 移除log 2019-11-03 17:29:00 +08:00
lyswhut
dfa15c6952 优化更新弹窗机制及其内容描述 2019-11-03 17:18:44 +08:00
lyswhut
a2af321c5e 分离主进程与渲染进程的功能,禁用remote模块 2019-11-03 09:53:19 +08:00
lyswhut
fddfaf23ce 更新配置 2019-11-02 11:51:49 +08:00
lyswhut
ae0ee2048a 新增nodejs版本输出 2019-11-02 11:42:31 +08:00
lyswhut
6659d1fe43 更新nodejs版本配置 2019-11-02 11:22:27 +08:00
lyswhut
f601e61f8d 发布0.10.0版本 2019-11-02 11:00:21 +08:00
lyswhut
f8ed7a112a 更新依赖到最新,更新electron到7.0.1 2019-11-02 10:59:12 +08:00
lyswhut
b0d6181bd7 完善token获取机制 2019-10-31 20:26:53 +08:00
lyswhut
82843b14b4 更新依赖 2019-10-31 00:42:48 +08:00
lyswhut
0df3ca3c5f 新增音乐sdk初始化方法 2019-10-30 22:29:14 +08:00
lyswhut
c881f8e886 更新描述 2019-10-30 01:43:27 +08:00
lyswhut
f1bf274de1 修复咪咕源无法播放的问题 2019-10-29 13:17:06 +08:00
lyswhut
91e4e9c2d5 简化写法 2019-10-28 22:48:30 +08:00
lyswhut
63a5f94a67 修复酷我源搜索提示、排行榜接口挂掉的问题 2019-10-28 22:42:01 +08:00
lyswhut
1bd8694262 减少播放时对CPU与GPU的使用 2019-10-27 17:35:29 +08:00
lyswhut
501231adff 更新依赖到最新,更新electron到7.0.0 2019-10-27 17:34:11 +08:00
lyswhut
3a033b66c4 发布0.9.1版本 2019-10-27 12:00:04 +08:00
lyswhut
46aacba037 修复没有配置文件时程序启动出错的问题 2019-10-27 11:58:55 +08:00
lyswhut
10ef80da75 发布0.9.0版本 2019-10-27 10:54:16 +08:00
lyswhut
75b7897ad6 更新安装版安装失败问题解决方法 2019-10-27 10:47:13 +08:00
lyswhut
14423e0eb3 修复搜索框bug 2019-10-27 10:00:43 +08:00
lyswhut
fe44581a61 修复搜索提示失效的问题 2019-10-25 21:36:07 +08:00
lyswhut
0d612bd6df 修改滚动高度 2019-10-24 15:16:59 +08:00
lyswhut
b7eba95ab2 新增定位当前播放歌曲 2019-10-24 14:04:44 +08:00
lyswhut
59091a5ea9 更改URL重试刷新次数 2019-10-22 01:36:25 +08:00
lyswhut
31e8b466cf 新增窗口大小设置 2019-10-22 01:25:03 +08:00
lyswhut
c97bc5edfc 修复单选框组件v-model为数字时的 bug 2019-10-21 23:05:26 +08:00
lyswhut
74f8e9544b 发布0.8.2版本 2019-10-20 18:21:43 +08:00
lyswhut
0a5e7c44ff 容旧版酷我源搜索列表过滤128k音质的bug 2019-10-20 18:20:58 +08:00
lyswhut
5c32160a58 发布0.8.1版本 2019-10-20 10:11:36 +08:00
lyswhut
2fc81c03ec 修复酷我源搜索歌曲结果处理问题 2019-10-20 02:48:28 +08:00
lyswhut
cc485df42f 移除192k试听 2019-10-20 00:32:36 +08:00
lyswhut
d575dd24ca 发布0.8.0版本 2019-10-19 12:08:53 +08:00
lyswhut
e47586cea3 更新请求头 2019-10-19 12:04:15 +08:00
lyswhut
1ce63c7550 修复酷狗歌曲无封面时报错的问题 2019-10-19 11:48:19 +08:00
lyswhut
36d69da3b7 缓存歌单详情列表数据 2019-10-19 11:47:15 +08:00
lyswhut
529be08420 优化音量条交互视觉效果 2019-10-18 21:47:10 +08:00
lyswhut
ffe11c7015 更新与分离常见问题 2019-10-18 18:04:28 +08:00
lyswhut
76b796bde7 修复歌单地址无效时无限重复请求的bug 2019-10-18 17:46:33 +08:00
lyswhut
f81715f07f 更新歌单导入描述 2019-10-18 13:32:01 +08:00
lyswhut
ec3f667b75 修复音量调整Bug,新增拖动调整音量 2019-10-18 12:03:01 +08:00
lyswhut
87f0b4fd17 修复传参问题 2019-10-17 22:41:24 +08:00
lyswhut
c7e95c5166 新增各平台通过输入歌单链接或歌单ID打开歌单详情列表 2019-10-17 17:01:49 +08:00
lyswhut
6ea0837cce 新增腾讯、网易云歌单链接打开 2019-10-17 14:05:29 +08:00
lyswhut
59627e048f 分离搜索框业务逻辑,优化搜索框搜索体验 2019-10-17 10:58:09 +08:00
lyswhut
45fea5726b 修复歌曲音质不存在时仍然播放或下载的Bug 2019-10-16 21:26:59 +08:00
lyswhut
e539f29f6d 修复音质过滤导致的歌单无法显示问题 2019-10-16 21:00:46 +08:00
lyswhut
1d2810e98f 新增网易云源歌单 2019-10-16 19:54:13 +08:00
lyswhut
1cbafc9750 新增网易云源歌曲搜索 2019-10-16 10:38:42 +08:00
lyswhut
e675f7ac06 更新描述 2019-10-16 00:59:19 +08:00
lyswhut
9dc9d3b1b7 修复网易云128k直接试听 2019-10-16 00:40:36 +08:00
lyswhut
a104ce688e 修复默认列表没有创建时无法显示收藏列表的Bug 2019-10-13 09:20:44 +08:00
lyswhut
2dd1ceb0b9 更新依赖到最新 2019-10-11 22:52:27 +08:00
lyswhut
9379328d96 修复QQ源歌单无法翻页Bug 2019-10-10 18:43:04 +08:00
lyswhut
d8969c327c 改变请求头 2019-10-10 13:24:14 +08:00
lyswhut
c3624fb7d6 Create LICENSE 2019-10-08 10:20:46 +08:00
lyswhut
b2de230100 remove license file 2019-10-08 10:19:46 +08:00
lyswhut
9d0bf50931 发布0.7.0版本 2019-10-07 22:22:32 +08:00
lyswhut
843e453b21 新增我的音乐列表歌曲源显示 2019-10-07 22:17:13 +08:00
lyswhut
f7546b0090 新增咪咕源搜索、歌单、排行榜 2019-10-07 21:51:16 +08:00
lyswhut
ed12aac7d8 新增QQ音乐源搜索及其128k试听 2019-10-07 13:07:16 +08:00
lyswhut
15f4053241 修复试听音乐时试听列表id传递错误的Bug 2019-10-07 01:35:20 +08:00
lyswhut
7a334f0099 新增缓存清理功能 2019-10-06 02:14:05 +08:00
lyswhut
4a510393ab 更新依赖到最新 2019-10-05 19:35:44 +08:00
lyswhut
52b0638258 修复strMediaMid获取错误 2019-10-05 16:10:40 +08:00
lyswhut
b198b1945d 尝试优化选我的音乐列表内容很多时多选的卡顿问题 2019-10-04 18:04:01 +08:00
lyswhut
10784e95bb 优化选择框动画效果 2019-10-04 10:13:26 +08:00
lyswhut
f4c65516d0 修复对多列表滚动条位置恢复的支持 2019-10-03 18:42:26 +08:00
lyswhut
6498305ec8 把我喜欢列表改名成我的收藏 2019-10-03 18:14:59 +08:00
lyswhut
7a91e6efef 完成我喜欢播放列表 2019-10-03 18:11:21 +08:00
lyswhut
4204b62f8f 重构列表功能,使其支持多列表,新增我喜欢列表 2019-10-03 02:00:31 +08:00
lyswhut
70b718d372 发布0.6.2版本 2019-10-01 16:36:44 +08:00
lyswhut
2b5d6f3592 移除static全局路径 2019-10-01 13:04:02 +08:00
lyswhut
8e48d5ba68 解除酷狗封面、歌词对接口服务器的依赖 2019-10-01 12:06:18 +08:00
lyswhut
3cec62d1a9 Merge branch 'openKg' into dev 2019-10-01 10:40:19 +08:00
lyswhut
b1650bcbee Merge branch 'master' into dev 2019-09-30 20:21:57 +08:00
lyswhut
492e3d8965 恢复酷狗其他音质播放 2019-09-30 20:20:28 +08:00
lyswhut
6d7be78b70 开放腾讯源128k音源直接播放 2019-09-30 15:24:00 +08:00
lyswhut
bac9439f8e 新增QQ音乐源搜索 2019-09-30 14:49:27 +08:00
lyswhut
5b612bfa0f 修复百度源搜索结果为空时的接口处理Bug 2019-09-30 13:14:55 +08:00
lyswhut
1f77b3dd4b 新增QQ音乐源歌单 2019-09-30 13:04:35 +08:00
lyswhut
b0d7a53d15 Update issue templates 2019-09-29 17:09:36 +08:00
lyswhut
c029eccdd3 修复当试听列表为空时,无法切到其他界面的Bug 2019-09-29 15:48:47 +08:00
lyswhut
7236f8929c 修正火影皮肤名字 2019-09-28 17:17:35 +08:00
lyswhut
aa114f86e3 发布0.6.1版本 2019-09-28 10:45:26 +08:00
lyswhut
95db349973 移除酷我音源 ape 音质 2019-09-27 11:51:20 +08:00
lyswhut
2acef590b4 更新常见问题内容 2019-09-27 10:55:56 +08:00
lyswhut
29855b2b6e 修复请求超时的逻辑处理Bug 2019-09-27 10:53:24 +08:00
lyswhut
ea10dd68a3 更新常见问题 2019-09-26 13:34:07 +08:00
lyswhut
e1942c66ac 更新依赖到最新 2019-09-26 00:07:13 +08:00
lyswhut
6bd18e8078 优化更新弹窗 2019-09-26 00:06:02 +08:00
lyswhut
f3dd306f54 优化播放组件渲染性能 2019-09-25 13:05:30 +08:00
lyswhut
3cf39ea48b 优化列表展开动画 2019-09-25 09:02:12 +08:00
lyswhut
d57f212c62 分离背景位置控制 2019-09-25 00:57:12 +08:00
lyswhut
b273c52124 移除192k音质 2019-09-24 13:34:14 +08:00
lyswhut
ce0a9afc33 调整皮肤tablist颜色 2019-09-24 13:27:05 +08:00
lyswhut
8437fe1716 新增 《海贼王》皮肤 2019-09-23 22:12:14 +08:00
lyswhut
f63f645696 新增DNS缓存功能 2019-09-23 13:24:32 +08:00
lyswhut
f7267806df 新增列表滚动条位置恢复 2019-09-22 11:33:37 +08:00
lyswhut
bebf1906a2 修正fetch单词的拼写错误 2019-09-22 10:08:37 +08:00
246 changed files with 20378 additions and 7772 deletions

22
.github/ISSUE_TEMPLATE/----.md vendored Normal file
View File

@@ -0,0 +1,22 @@
---
name: 功能请求(请先查看常见问题及搜索issue列表中有无你要提的问题)
about: 为这个项目提出一个想法
title: 例如添加xxx功能、优化xxx功能
labels: ''
assignees: ''
---
**解决方案检查**
<!-- 请确保你已从以下渠道寻找过解决方案,然后将 [ ] 替换成 [x] -->
- [ ] 我已阅读常见问题(<https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md>)
- [ ] 我已搜索issue列表(<https://github.com/lyswhut/lx-music-desktop/issues?utf8=✓&q=>)
**描述您想要的解决方案**
简洁明了地描述您要发生的事情。
**描述您考虑过的替代方案**
对您考虑过的所有替代解决方案或功能的简洁明了的描述。
**其他内容**
在此处添加有关功能请求的任何其他上下文或屏幕截图(直接把图片拖到编辑框即可添加图片)。

36
.github/ISSUE_TEMPLATE/--bug.md vendored Normal file
View File

@@ -0,0 +1,36 @@
---
name: 报告Bug(请先查看常见问题及搜索issue列表中有无你要提的问题)
about: 创建报告以帮助我们改进
title: 例如:音乐无法播放
labels: ''
assignees: ''
---
**解决方案检查**
<!-- 请确保你已从以下渠道寻找过解决方案,然后将 [ ] 替换成 [x] -->
- [ ] 我已阅读常见问题(<https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md>)
- [ ] 我已搜索issue列表(<https://github.com/lyswhut/lx-music-desktop/issues?utf8=✓&q=>)
**描述错误**
清楚简洁地说明错误是什么。
**重现**
重现行为的步骤:
1.转到“ ...”
2.点击“ ....”
3.向下滚动到“ ....”
4.看到错误
**预期行为**
对您期望发生的事情的简洁明了的描述。
**截图**
如果适用,请添加屏幕截图以帮助解释您的问题(直接把图片拖到编辑框即可添加图片)。
**环境:**
  -操作系统及版本:[例如Windows 10 64位 18362.156]
  -软件安装包及版本:[例如Windows 64位绿色版 1.0.0]
**其他内容**
在此处添加有关该问题的任何其他上下文。

View File

@@ -1,19 +1,16 @@
language: node_js
node_js: 12
matrix:
include:
- os: osx
osx_image: xcode10.2
language: node_js
node_js: "12"
env:
- ELECTRON_CACHE=$HOME/.cache/electron
- ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
- os: linux
language: node_js
node_js: "12"
dist: trusty
services: docker
language: generic
cache:
directories:
@@ -26,6 +23,8 @@ notifications:
email: false
script:
- node --version
- npm --version
- |
if [ "$TRAVIS_OS_NAME" == "linux" ]; then
npm install && npm run publish:gh:linux
@@ -41,3 +40,4 @@ before_cache:
branches:
only:
- master
- dev

View File

@@ -6,6 +6,387 @@ 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/).
## [0.18.2](https://github.com/lyswhut/lx-music-desktop/compare/v0.18.1...v0.18.2) - 2020-05-02
### 修复
- 修复开启托盘时,可能导致无法自动更新的问题
## [0.18.1](https://github.com/lyswhut/lx-music-desktop/compare/v0.18.0...v0.18.1) - 2020-05-02
### 优化
- win下的托盘图标使用更大的图片
- 加长软件协议的强制停留时间
### 修复
- 修复导入设置某些设置未立即生效的问题
## [0.18.0](https://github.com/lyswhut/lx-music-desktop/compare/v0.17.0...v0.18.0) - 2020-05-01
### 新增
- 新增FLAC格式音乐标签信息写入与封面嵌入因128k以外的音质已失效目前该功能用不上了
- 添加软件启动时是否自动聚焦搜索框的设置
- 新增托盘设置,默认关闭,可到设置开启,感谢 @LasyIsLazy 提交的PR
- 新增打开酷狗源用户歌单
- 新增使用协议
- 新增虾米音源
- 新增新皮肤“粉妆玉琢”、“青出于黑”,可去体验下~
- 新增“超大”、“巨大”窗口尺寸
- 新增播放详情页(退出详情页可点击右上角退出按钮或者在播放详情页任意地方**鼠标快速右击两次**
### 优化
- 略微加深音量条底色
- 优化其他界面细节
- 优化英语翻译,感谢 @CPCer
- 优化程序的流畅度
### 更变
- 下载列表的歌曲下载、播放将随设置中的保存路径改变而改变,不再固定指向其初始位置
- 移除列表多选框,现在多选需要键盘配合,想要多选前需按下`Shift``Ctrl`键然后再鼠标点击想要选中的内容即可触发多选机制,其中`Shift`键用于连续选择,`Ctrl`键用于不连续选择,`Ctrl+a`用于快速全选。例子一想要选中1-5项则先按下`Shift`键后鼠标点击第一项再点击第五项即可完成选择例子二想要选中1项与第3项则先按下`Ctrl`键后,鼠标点击第一项,再点击第三项即可完成选择;例子三:想要选中当前列表的全部内容,键盘先按下`Ctrl`键不放,然后按`a`键,即可完成选择。用`Shift``Ctrl`选择时,鼠标点击未选中的内容会将其选中,点击已选择的内容会将其取消选择,若想全部取消选择,在不按`Shift``Alt`键的情况下,随意点击列表里的一项内容即可全部取消选择。(P.S`Ctrl`键对应Mac OS上的`Command`键)
- 现在进度条的封面图左击改为打开播放详情页,在列表定位歌曲改为右击
### 修复
- 修复网易源某些歌曲提示没有可播放的音质的问题
- 修复下载管理刷新URL失败时不标记任务下载失败的问题
- 修复列表导出的文字描述,感谢 @CPCer
- 修复歌曲切换方式无法取消勾选的问题
- 修复打开歌单详情的情况下切到其他界面再切回来报错的问题
- 修正播放列表浮动按钮错误的文字提示
### 移除
- 因128k以外的音质失效So 禁止所有128k外的音质下载
### 其他
更新 Electron 到 8.2.5
## [0.17.0](https://github.com/lyswhut/lx-music-desktop/compare/v0.16.0...v0.17.0) - 2020-03-15
### 新增
- 新增多语言设置目前软件内置了简体中文、繁体中文、英语三种语言欢迎提交PR翻译更多语言
- 新增无法打开外部歌单FAQ
- 新增启动参数`search`,使用例子:`.\lx-music-desktop.exe -search="突然的自我 - 伍佰"`
- 新增音频输出设置
- 新增软件内的包括字体在内的界面内容大小调整,现在当窗口大小切换到“较小/大/较大”时,软件内的元素将会适当减小或加大,窗口大小的“小”与“中”内的元素将保持之前的大小暂不做改变
- 新增音源别名,默认将显示别名,想要显示回原名可到设置切换(免责声明:别名仅是本软件用于描述各音源的标签,其名字归版权方所有)
- 新增发现新版本更新失败弹窗的忽略提醒按钮,忽略提醒后,以后同一个版本再失败时将不会弹窗提醒,但仍可到设置-版本更新手动点开更新弹窗查看或恢复提醒
- 新增热搜词,默认关闭,可到设置开启
- 新增历史搜索记录,默认关闭,可到设置开启(右击单个历史记录标签可移除所点击的记录)
### 优化
- 优化月里嫦娥皮肤侧栏鼠标悬浮颜色
- 优化播放进度条的动画效果
- 现在添加下载任务时,后面添加的任务会在列表顶部插入
- 优化歌单打开机制,现在歌单加载失败时会提示加载失败了,并且支持直接打开企鹅、酷我手机分享出来的歌单了
- 优化右上角最小化/关闭按钮布局
### 修复
- 修复歌单详情处于加载状态时无法返回的问题
- 修复鼠标右击复制列表内容时会复制音质标签的问题
- 修复`0.6.2`及以前的版本导出的“所有数据”内的歌曲列表无法导入的问题
- 修复下载列表在某些情况下无法取消全选的问题
### 其他
- 更新Electron到 8.1.1
## [0.16.0](https://github.com/lyswhut/lx-music-desktop/compare/v0.15.0...v0.16.0) - 2020-02-16
### 新增
- 允许选中列表内歌曲名、歌手名、专辑名内的文字,选中后可使用键盘快捷键进行复制
- 新增在列表可选内容区域**鼠标右击**时自动复制列表已选文字的功能
- 新增在搜索框**鼠标右击**时自动粘贴剪贴板的文本到搜索框中
- 任务下载失败时将显示搜索按钮,方便在其他源搜索该歌曲
### 优化
- 优化木叶之村主题翻页器背景颜色
- 优化各个主题音质标签颜色
- 优化其他一些界面细节及用户交互效果
### 修复
- 修复启用透明窗口鼠标不穿透的bug
- 修复大窗口时设置的音乐来源选项不换行的问题
- 修复某些情况下暂停任务会自动开始任务的问题
- 修复移除暂停、错误的任务时不删除未下载完成的文件的问题
- 修复酷狗源歌单热门标签歌单列表无法加载问题
- 修复QQ源歌单热门标签歌单列表无法加载问题
### 其他
- 更新electron到 8.0.1
## [0.15.0](https://github.com/lyswhut/lx-music-desktop/compare/v0.14.1...v0.15.0) - 2020-01-23
洛雪提前祝大家新年快乐、身体健康、阖家幸福!
### 修复
- 修复歌曲下载列表无法加载的问题
- 修复歌曲下载任务数大于最大下载任务数的问题
- 修复某些情况下歌曲下载错误的问题
- 修复下载列表数据没有被迁移直接被丢弃的问题
## [0.14.1](https://github.com/lyswhut/lx-music-desktop/compare/v0.14.0...v0.14.1) - 2020-01-22
洛雪提前祝大家新年快乐、身体健康、阖家幸福!
### 修复
- 修复由于旧版配置文件迁移出错导致的软件界面无法显示的问题
## [0.14.0](https://github.com/lyswhut/lx-music-desktop/compare/v0.13.1...v0.14.0) - 2020-01-22
洛雪提前祝大家新年快乐、身体健康、阖家幸福!
### 新增
- 新增各大平台歌单热门标签显示(显示在歌单界面的第一个下拉标签菜单中)
- 恢复QQ音乐源128k音质试听
- 新增不强制win7开启透明效果即可使用但要配置运行参数`-nt`,例如:`.\lx-music-desktop.exe -nt`,添加方法可自行百度“给快捷方式加参数”
- 新增“新年快乐”主题,可自行切换体验
### 优化
- 减淡各个主题的歌曲列表分隔线颜色
- 在线音乐列表音质标签优化,当歌曲有无损音质时隐藏高品质标签
- 更新改进的歌词播放插件,现在歌词的播放显示将更准确
### 修复
- 修复咪咕源无法搜索的问题
- 修复更新弹窗底部文字颜色没有适配当前主题颜色的问题
- 修复导入设置窗口大小、代理设置不立即生效的问题
- 修复在线音乐列表获取失败时无限循环请求的问题
### 其他
- 将软件设置与播放列表分离存储成两个文件
- 更新 Electron 到 7.1.9
## [0.13.1](https://github.com/lyswhut/lx-music-desktop/compare/v0.13.0...v0.13.1) - 2019-12-16
### 修复
- 修复全局更新弹窗无法遮盖搜索框的问题
### 其他
- 由于electron 7.1.3 - 7.1.5 的自动更新功能存在Bug现降级到7.1.2
## [0.13.0](https://github.com/lyswhut/lx-music-desktop/compare/v0.12.1...v0.13.0) - 2019-12-15
### 新增
- 新增搜索框搜索建议键盘上下方向键选择功能
- 聚合搜索新增音源显示
- 新增“离开搜索界面时清空搜索列表”设置选项,默认关闭,可到设置-强迫症设置开启
### 优化
- 优化“信口雌黄”皮肤配色
### 修复
- 修复存在弹出层时,搜索建议列表被弹出层覆盖的问题
- 修复搜索、排行榜、歌单列表多选框从不定状态到选中的Bug
### 移除
- 因Q音接口失效移除Q音源的试听与下载
### 其他
- 更新electron到7.1.5
- 更新vue到2.6.11
## [0.12.1](https://github.com/lyswhut/lx-music-desktop/compare/v0.12.0...v0.12.1) - 2019-12-01
### 优化
- 优化定位歌曲时的列表滚动机制
- 优化链接点击效果
### 修复
- 修复使用酷我源下载歌曲时,当歌曲无封面时下载报错的问题
- 修复酷我源排行榜、歌单详情列表里的歌曲音质匹配问题(原来无论歌曲有无高品、无损都会显示有)
- 禁止外部链接在软件内打开,将所有外部链接从默认浏览器打开
### 其他
- 更新electron到7.1.2
## [0.12.0](https://github.com/lyswhut/lx-music-desktop/compare/v0.11.0...v0.12.0) - 2019-11-17
由于新下载库仍然没有完成但下载功能已经可用so 移除之前使用的第三方下载库,暂时把新下载库的下载模块直接加入本程序,若出现下载问题欢迎反馈!
### 新增
- 新增下载功能对代理设置的支持,现在若在软件设置了代理服务器,下载功能也将会走代理网络了
### 优化
- 新下载模块将对恢复下载的任务进行字节校验用于解决下载进度超过100%后仍然下载的问题
- 注意:目前仍然无法暂停处于**链接获取**状态中的任务
### 修复
- 修复Linux deb版本`.desktop`桌面文件缺少图标的问题,新增中文名称显示、软件分类,感谢@lowy的反馈
- 修复下载列表歌曲状态分类列表操作Bug
- 修复歌曲封面下载失败时仍然执行嵌入封面操作导致报错的问题
- 跳过重复添加**相同歌曲名与扩展名的歌曲**例如你之前下载了A歌曲的128k音质现在想要下载它的320k音质但由于两者都是MP3格式会因为重名导致之前的128k音质被覆盖但列表中仍然显示两种音质的问题但实际上都是指向后面的320k音质
## [0.11.0](https://github.com/lyswhut/lx-music-desktop/compare/v0.10.0...v0.11.0) - 2019-11-10
### 新增
- 新增歌曲缓冲定时器,尝试用于解决网络正常但是歌曲缓冲过久的问题
- 新增下载管理的任务状态分类
- 添加**杀毒软件提示有病毒或恶意行为**的说明,可到**常见问题**拉到最后查看(常见问题可在开源地址找到)
### 优化
- 优化更新弹窗机制及其内容描述,对于可以自动更新的版本,现在可以看到软件的下载进度了
## [0.10.0](https://github.com/lyswhut/lx-music-desktop/compare/v0.9.1...v0.10.0) - 2019-11-02
#### 优化
- 大幅减少程序**播放时**对CPU与GPU的使用经测试CPU使用减少60%以上GPU使用减少90%以上这应该能解决MAC系统上的温度上涨的问题
#### 修复
- 修复酷我源**搜索提示**、**排行榜**无法获取的问题
- 修复咪咕源无法播放的问题
## [0.9.1](https://github.com/lyswhut/lx-music-desktop/compare/v0.9.0...v0.9.1) - 2019-10-27
#### 修复
- 修复没有配置文件时程序启动出错的问题
## [0.9.0](https://github.com/lyswhut/lx-music-desktop/compare/v0.8.2...v0.9.0) - 2019-10-27
#### 新增
- 新增窗口大小设置,若觉得软件窗口小可以到设置页调大点
- 新增定位当前播放歌曲,点击播放栏左侧的**歌曲图片**可在播放列表定位当前播放的歌曲(该功能对播放下载列表的歌曲无效)
#### 修复
- 修复搜索提示失效的问题
- 修复从歌单或列表点击搜索按钮搜索目标歌曲时,搜索框未聚焦仍然弹出候选搜索列表的问题
## [0.8.2](https://github.com/lyswhut/lx-music-desktop/compare/v0.8.1...v0.8.2) - 2019-10-20
#### 修复
- 兼容旧版酷我源搜索列表过滤128k音质的bug0.8.1版本仅修复了酷我源的歌曲过滤问题,该修复仅对以后添加的歌曲有效,如果是之前添加的歌曲仍会出现这个问题,现修复对之前旧列表数据的兼容处理)
## [0.8.1](https://github.com/lyswhut/lx-music-desktop/compare/v0.8.0...v0.8.1) - 2019-10-20
#### 修复
- 修复酷我源搜索歌曲结果未添加128k音质导致播放128k音质时显示“该歌曲没有可播放的音频”的问题
## [0.8.0](https://github.com/lyswhut/lx-music-desktop/compare/v0.7.0...v0.8.0) - 2019-10-19
#### 新增
- 新增网易云源歌曲搜索
- 新增网易云源歌单
- 新增各平台通过输入歌单链接或歌单ID打开歌单详情列表目前只适配了**网页版歌单链接**其他方式的歌单链接可能无法解析但你可想办法获取歌单ID后输入打开。注各平台歌单ID均为纯数字若遇到链接里存在歌单ID但无法解析的歌单链接可以到GitHub提交issue或发送邮件或加群830125506反馈
- 新增音量调整滑动功能,现在支持鼠标左右拖动调整音量了
#### 优化
- 优化搜索框搜索体验
- 优化音量条交互视觉效果
- 缓存歌单详情列表数据
#### 修复
- 修复QQ源歌单无法翻页Bug
- 修复默认列表没有创建时无法显示收藏列表的Bug
- 修复网易云128k直接试听
- 修复歌曲音质不存在时仍然播放或下载的Bug
- 修复调整音量时,调整的位置与鼠标点击的位置不一致的问题
## [0.7.0](https://github.com/lyswhut/lx-music-desktop/compare/v0.6.2...v0.7.0) - 2019-10-07
#### 新增
- 新增“我的收藏”本地播放列表
- 新增缓存清理功能,可到**设置-其他**查看与清理软件缓存
- 新增QQ音乐源搜索
- 新增咪咕源搜索
- 新增咪咕源歌单
- 新增咪咕源排行榜
- 新增我的音乐列表歌曲源显示,默认关闭,可到**设置-列表设置**开启
#### 优化
- 优化选择框动画效果
- 尝试优化选我的音乐列表内容很多时多选的卡顿问题
#### 修复
- 修复列表延迟显示的Bug
- 修复QQ音源128k音质试听
## [0.6.2](https://github.com/lyswhut/lx-music-desktop/compare/v0.6.1...v0.6.2) - 2019-10-01
祝贺祖国成立70周年~
#### 新增
- 新增QQ音乐源歌单
#### 修复
- 修正火影皮肤名字
- 修复当试听列表为空时无法切到其他界面的Bug
- 修复百度源搜索结果为空时的接口处理Bug
- 恢复**酷狗**其他音质播放
## [0.6.1](https://github.com/lyswhut/lx-music-desktop/compare/v0.6.0...v0.6.1) - 2019-09-28
### 新增
- 新增试听列表**滚动条位置恢复**设置(可自动恢复到上次离开时的列表滚动位置),本功能默认开启,若不需要可到设置-列表设置将其关闭
- 新增 **《海贼王》** 皮肤,喜欢个性化的可以试试~
### 优化
- 新增DNS解析缓存加快请求速度
- 优化代码逻辑,减少软件对系统资源的占用
- 优化新版本信息检测,尽量减少弹出版本获取失败弹窗弹出的概率
- 优化下拉列表动画效果
### 修复
- 修复请求超时的逻辑处理Bug尝试修复请求无法取消导致的正在播放的歌曲与界面显示的信息不一致的问题
- 修复其他一些小Bug
### 移除
- 移除 `192k` 音质
- 移除酷我音源 `ape` 音质,无损推荐 `flac` 格式
## [0.6.0](https://github.com/lyswhut/lx-music-desktop/compare/v0.5.5...v0.6.0) - 2019-09-21
### 新增

107
FAQ.md Normal file
View File

@@ -0,0 +1,107 @@
# lx-music-desktop 常见问题
在阅读本常见问题后仍然无法解决你的问题请提交issue或者加企鹅群`830125506`反馈(无事勿加,入群先看群公告),反馈时请**注明**已阅读常见问题!
## 软件为什么没有桌面歌词与自定义列表功能
洛雪音乐的最初定位不是作为播放器开发的,它主要用于**查找歌曲**,软件的播放功能仅用于试听,不建议用作为常用播放器使用,因此无桌面、界面歌词,不可自定义列表等功能。
## 歌曲无法试听与下载
该问题解决顺序如下:
1. 尝试更新到最新版本
2. 尝试切换其他歌曲(或直接搜索该歌曲),若全部歌曲都无法试听与下载则进行下一步
3. 尝试到 设置-音乐来源 切换到其他接口
4. 尝试切换网络,比如用手机开热点(目前存在某些网络无法访问接口服务器的情况)
5. 若还不行请到这个链接查看详情:<https://github.com/lyswhut/lx-music-desktop/issues/5>
6. 若没有在第5条链接中的第一条评论中看到接口无法使用的说明则应该是你网络无法访问接口服务器的问题如果接口有问题我会在那里说明。
想要知道是不是自己网络的问题可以看看`http://ts.tempmusic.tk`能不能在浏览器打开浏览器显示404是正常的如果不是404那就证明所在网络无法访问接口服务器。
若网页无法打开或打来不是404则应该是DNS的问题可以尝试以下办法
1. 将DNS改成自动获取试试
2. 手动把DNS改一下不要用360的DNS可以把DNS改成`114.114.114.114``8.8.8.8`
## 软件安装包说明
软件发布页及网盘中有多个类型的安装文件,以下是对这些类型文件的说明:
文件名带 `win_` 的是在Windows系统上运行的版本<br>
其中安装版Setup可自动更新软件<br>
绿色版green为免安装版自动更新功能不可用
**`.dmg`** 结尾的文件为 MAC 版本;
**`.AppImage`**、**`.deb`** 结尾的为 Linux 版本。
带有`x64`的为64位的系统版本`x86`的为32位的系统版本若两个都带有的则为集合版安装时会自动根据系统位数选择对应的版本安装。
## 软件更新
软件启动时若发现新版本时会自动从本仓库下载安装包,下载完毕会弹窗提示更新。<br>
若下载未完成时软件被关闭,下次启动软件会再次自动下载。<br>
若还是**更新失败**可能是无法访问GitHub导致的这时需要手动更新即下载最新安装包直接覆盖安装即可。<br>
注意:**绿色版**的软件自动更新功能**不可用**,建议使用安装版!!<br>
注意:**Mac版**、**Linux deb**版不支持自动更新!
## Windows 7 下界面异常(界面显示不完整)
由于软件默认使用了透明窗口根据Electron官方文档的[说明](https://electronjs.org/docs/api/frameless-window#%E5%B1%80%E9%99%90%E6%80%A7)
> 在 windows 操作系统上, 当 DWM 被禁用时, 透明窗口将无法工作。
因此,当 win7 没有使用**AERO**主题时界面将会显示异常开启AERO的方法请自行百度`win7开启aero效果`(开启后可看到任务栏变透明)。<br>
`0.14.0`版本起不再强制要求开启透明效果,若你实在不想开启(若非电脑配置太低,墙裂建议开启!),可通过添加运行参数`-nt`来运行程序即可,例如:`.\lx-music-desktop.exe -nt`,添加方法可自行百度“给快捷方式加参数”,该参数的作用是用来控制程序是否使用非透明窗口运行。
对于一些完全无法正常显示界面的情况,请阅读下面的 **软件启动后,界面无法显示**
## 软件启动后,界面无法显示
软件启动后,可以在任务栏看到软件,但软件界面在桌面上无任何显示。<br>
原始问题看:<https://github.com/electron/electron/issues/19569#issuecomment-522231083><br>
解决办法:下载`.NET Framework 4.7.1`或**更高**版本安装即可(建议安装最新版,若安装过程中遇到问题可尝试自行百度解决)。<br>
微软官方下载地址:<https://dotnet.microsoft.com/download/dotnet-framework><br>
下载`Runtime(运行时)`版即可,安装完成后可能需要重启才生效。
若还是不行可尝试以下操作:
- 更新显卡驱动
- 尝试将绿色版的软件放在**桌面**或**我的文档**运行
## 安装版安装失败,提示安装程序并未成功地运行完成
对于部分电脑出现安装失败的问题,可以做出以下尝试:
- 若你之前可以安装成功,但现在安装失败,就去**控制面板-程序和功能**或用第三方卸载工具看下有没有之前的版本残留,若同时在不同路径下安装了多个版本就可能会出现该问题,这种情况卸载掉所有版本重新安装即可
- 清理安装路径下的残留文件
- 以管理员权限打开`cmd`,输入`sfc /scannow`回车等待检查完成重启电脑
- 若还是不行我也没办法了。。
## 缺少`xxx.dll`
这个是电脑缺少某些dll导致的正常的系统是没有这个问题的可以尝试如下几个解决办法
- 以管理员权限打开`cmd`,输入`sfc /scannow`回车等待检查完成重启电脑
- 若上面的方法**修复、重启**电脑后仍然不行,就自行百度弹出的**错误信息**看下别人是怎么解决的
## 杀毒软件提示有病毒或恶意行为
本人只能保证我写的代码不包含任何**恶意代码**、**收集用户信息**的行为并且软件代码已开源请自行查阅软件安装包也是由CI拉取源代码构建构建日志[windows包](https://ci.appveyor.com/project/lyswhut/lx-music-desktop)、[Mac/Linux包](https://travis-ci.org/lyswhut/lx-music-desktop)<br>
尽管如此但这不意味着软件是100%安全的,由于软件使用了第三方依赖,当这些依赖存在恶意行为时([供应链攻击](https://docs.microsoft.com/zh-cn/windows/security/threat-protection/intelligence/supply-chain-malware)),软件也将会受到牵连,所以我只能尽量选择使用较多人用、信任度较高的依赖。<br>
当然,以上说明建立的前提是在你所用的安装包是从**本项目主页上写的链接**下载的,或者有相关能力者还可以下载源代码自己构建安装包。
`0.17.0`起,由于加入了音频输出设备切换功能,该功能调用了 [MediaDevices.enumerateDevices()](https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices/enumerateDevices),可能导致安全软件提示洛雪要访问摄像头(目前发现卡巴斯基会提示),但实际上没有用到摄像头,并且摄像头的提示灯也不会亮,你可以选择阻止访问。
最后,若出现杀毒软件报毒、存在恶意行为,请自行判断选择是否继续使用本软件!
## 软件无法联网
软件的排行榜、歌单、搜索列表**都**无法加载:
- 检查是否在设置界面开启了代理(当代理乱设置时软件将无法联网)
- 检查软件是否被第三方软件/防火墙阻止联网
## 无法打开外部歌单
对于分享出来的歌单若打开失败可尝试先在浏览器中打开后再从浏览器地址栏复制URL地址到软件打开<br>
或者如果你知道歌单 id 也可以直接输入歌单 id 打开。<br>

View File

@@ -4,6 +4,7 @@
<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://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>
<!-- <a href="https://github.com/lyswhut/lx-music-desktop/blob/master/LICENSE"><img src="https://img.shields.io/github/license/lyswhut/lx-music-desktop" alt="License"></a> -->
@@ -35,8 +36,8 @@
所用技术栈:
- Electron 6.x
- Vue 2.x
- Electron 8
- Vue 2
已支持的平台:
@@ -46,17 +47,12 @@
软件变化请查看:[更新日志](https://github.com/lyswhut/lx-music-desktop/blob/master/CHANGELOG.md)<br>
软件下载请转到:[发布页面](https://github.com/lyswhut/lx-music-desktop/releases)<br>
或者到网盘下载网盘内有MAC、windows版`https://www.lanzous.com/b906260/` 密码:`glqw`<br>
使用常见问题请转至:[常见问题](https://github.com/lyswhut/lx-music-desktop#常见问题)
#### TODO
- [ ] 新增拉取各大平台歌单
- [ ] 新增聚合搜索
或者到网盘下载网盘内有MAC、windows版`https://t-s.lanzous.com/b0bf2cfa/` 密码:`glqw`<br>
使用常见问题请转至:[常见问题](https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md)
### 源码使用方法
环境要求Node.js 12.x
环境要求Node.js 12.x+
```bash
# 开发模式
@@ -65,50 +61,46 @@ npm run dev
# 构建免安装版
npm run pack:dir
# 构建安装包(windows版
# 构建安装包(Windows版
npm run pack
# 构建安装包Mac版
npm run pack:mac
# 构建安装包Linux版
npm run pack:linux
```
### UI界面
<p><a href="https://github.com/lyswhut/lx-music-desktop"><img width="100%" src="https://github.com/lyswhut/lx-music-desktop/blob/master/doc/images/app.png" alt="lx-music UI"></a></p>
### 启动参数
目前软件已支持的启动参数如下:
- `-search` 启动软件时自动在搜索框搜索指定的内容,例如:`-search="突然的自我 - 伍佰"`
- `-nt` 以非透明模式启动对于未开启AERO效果的win7系统可加此参数启动以确保界面正常显示
### 常见问题
#### 歌曲无法试听与下载
常见问题已移至:<https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md>
该问题解决顺序如下:
### 项目协议
1. 尝试更新到最新版本
2. 尝试切换其他歌曲(或直接搜索该歌曲),若全部歌曲都无法试听与下载则进行下一步
3. 尝试到设置-接口来源切换到其他接口
4. 尝试切换网络(比如切到移动网络)
5. 若还不行请到这个链接查看详情:<https://github.com/lyswhut/lx-music-desktop/issues/5>
本项目基于 [Apache License 2.0](https://github.com/lyswhut/lx-music-desktop/blob/master/LICENSE) 许可证发行,以下协议是对于 Apache License 2.0 的补充,如有冲突,以以下协议为准。
#### Windows 7 下界面异常
词语约定:本协议中的“本项目”指洛雪音乐桌面版项目;“使用者”指签署本协议的使用者;“官方音乐平台”指对本项目内置的包括酷我、酷狗、咪咕等音乐源的官方平台统称;“版权数据”指包括但不限于图像、音频、名字等在内的他人拥有所属版权的数据。
当 win7 没有开启**透明效果**时界面将会显示异常,开启方法请自行百度
1. 本项目的数据来源原理是从各官方音乐平台的公开服务器中拉取数据,经过对数据简单地筛选与合并后进行展示,因此本项目不对数据的准确性负责。
2. 使用本项目的过程中可能会产生版权数据,对于这些版权数据,本项目不拥有它们的所有权,为了避免造成侵权,使用者务必在**24小时**内清除使用本项目的过程中所产生的版权数据。
3. 本项目内的官方音乐平台别名为本项目内对官方音乐平台的一个称呼,不包含恶意,如果官方音乐平台觉得不妥,可联系本项目更改或移除。
4. 本项目内使用的部分包括但不限于字体、图片等资源来源于互联网,如果出现侵权可联系本项目移除。
5. 由于使用本项目产生的包括由于本协议或由于使用或无法使用本项目而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害(包括但不限于因商誉损失、停工、计算机故障或故障引起的损害赔偿,或任何及所有其他商业损害或损失)由使用者负责。
6. 本项目完全免费,且开源发布于 GitHub 面向全世界人用作对技术的学习交流,本项目不对项目内的技术可能存在违反当地法律法规的行为作保证,**禁止在违反当地法律法规的情况下使用本项目**,对于使用者在明知或不知当地法律法规不允许的情况下使用本项目所造成的任何违法违规行为由使用者承担,本项目不承担由此造成的任何直接、间接、特殊、偶然或结果性责任。
#### 软件更新
若你使用了本项目,将代表你接收以上协议。
软件启动时若发现新版本时会自动从本仓库下载安装包,下载完毕会弹窗提示更新<br>
下载未完成时软件被关闭,下次启动软件会再次自动下载。<br>
若还是更新失败可能是无法访问GitHub导致的这时需要手动更新即下载最新安装包直接覆盖安装即可。<br>
注意:**绿色版**的软件更新功能**不可用**,为了能及时地获取更新,建议使用安装版!!<br>
注意:**Mac版**、**Linux deb**版不支持自动更新!
### 致谢
感谢 [@messoer](https://github.com/messoer) 曾经提供的部分音乐API
### 免责声明
本项目**不开发或者破解直接获取音频数据**的功能,所有音频数据均来自**第三方接口**<br>
本软件仅用于**测试 `electron 6.x` 在各种系统上的兼容性**及用于**对比各大音乐平台歌单、排行榜等数据列表的差异性**,使用本软件产生的**任何涉及版权相关的数据**请于**24小时内删除**。<br>
本软件仅用于学习交流使用,禁止用于商业用途,使用本软件所造成的的后果由使用者承担!<br>
若对此有疑问请 mail to: lyswhut@qq.com
### 许可证
[Apache License 2.0](https://github.com/lyswhut/lx-music-desktop/blob/master/LICENSE)
音乐平台不易,请尊重版权,支持正版<br>
对此有疑问请 mail to: lyswhut+qq.com (请将`+`替换成`@`)

View File

@@ -9,6 +9,8 @@ module.exports = {
},
resolve: {
alias: {
'@': path.join(__dirname, '../../src/main'),
events: path.join(__dirname, '../../src/main/events'),
common: path.join(__dirname, '../../src/common'),
},
extensions: ['*', '.js', '.json', '.node'],

View File

@@ -1,6 +1,7 @@
const path = require('path')
const merge = require('webpack-merge')
const webpack = require('webpack')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const baseConfig = require('./webpack.config.base')
@@ -18,5 +19,17 @@ module.exports = merge(baseConfig, {
__static: `"${path.join(__dirname, '../../src/static').replace(/\\/g, '\\\\')}"`,
}),
new webpack.NoEmitOnErrorsPlugin(),
new FriendlyErrorsPlugin({
onErrors(severity, errors) { // Silent warning from electron-debug
if (severity != 'warning') return
for (let i = errors.length; i > -1; i--) {
const error = errors[i]
if (error.file == './node_modules/electron-debug/index.js') errors.splice(i, 1)
}
// You can listen to errors transformed and prioritized by the plugin
// severity can be 'error' or 'warning'
},
}),
],
})

View File

@@ -3,7 +3,7 @@ process.env.NODE_ENV = 'production'
const chalk = require('chalk')
const del = require('del')
const webpack = require('webpack')
const Multispinner = require('multispinner')
const Spinnies = require('spinnies')
const mainConfig = require('./main/webpack.config.prod')
const rendererConfig = require('./renderer/webpack.config.prod')
@@ -15,40 +15,44 @@ const okayLog = chalk.bgGreen.white(' OKAY ') + ' '
function build() {
del.sync(['dist/electron', 'build'])
const tasks = ['main', 'renderer']
const m = new Multispinner(tasks, {
preText: 'building',
postText: 'process',
})
const spinners = new Spinnies({ color: 'blue' })
spinners.add('main', { text: 'main building' })
spinners.add('renderer', { text: 'renderer building' })
let results = ''
m.on('success', () => {
// m.on('success', () => {
// process.stdout.write('\x1B[2J\x1B[0f')
// console.log(`\n\n${results}`)
// console.log(`${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n`)
// process.exit()
// })
function handleSuccess() {
process.stdout.write('\x1B[2J\x1B[0f')
console.log(`\n\n${results}`)
console.log(`${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n`)
process.exit()
})
}
pack(mainConfig).then(result => {
results += result + '\n\n'
m.success('main')
}).catch(err => {
m.error('main')
console.log(`\n ${errorLog}failed to build main process`)
console.error(`\n${err}\n`)
process.exit(1)
})
pack(rendererConfig).then(result => {
results += result + '\n\n'
m.success('renderer')
}).catch(err => {
m.error('renderer')
console.log(`\n ${errorLog}failed to build renderer process`)
console.error(`\n${err}\n`)
process.exit(1)
})
Promise.all([
pack(mainConfig).then(result => {
results += result + '\n\n'
spinners.succeed('main', { text: 'main build success!' })
}).catch(err => {
spinners.fail('main', { text: 'main build fail :(' })
console.log(`\n ${errorLog}failed to build main process`)
console.error(`\n${err}\n`)
process.exit(1)
}),
pack(rendererConfig).then(result => {
results += result + '\n\n'
spinners.succeed('renderer', { text: 'renderer build success!' })
}).catch(err => {
spinners.fail('renderer', { text: 'renderer build fail :(' })
console.log(`\n ${errorLog}failed to build renderer process`)
console.error(`\n${err}\n`)
process.exit(1)
}),
]).then(handleSuccess)
}
function pack(config) {

View File

@@ -7,13 +7,12 @@ const path = require('path')
const { spawn } = require('child_process')
const webpack = require('webpack')
const WebpackDevServer = require('webpack-dev-server')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpackHotMiddleware = require('webpack-hot-middleware')
const mainConfig = require('./main/webpack.config.dev')
const rendererConfig = require('./renderer/webpack.config.dev')
const { logStats } = require('./utils')
let electronProcess = null
let manualRestart = false
let hotMiddleware
@@ -31,7 +30,7 @@ function startRenderer() {
compiler.hooks.compilation.tap('compilation', compilation => {
// console.log(Object.keys(compilation.hooks))
compilation.hooks.htmlWebpackPluginAfterEmit.tapAsync('html-webpack-plugin-after-emit', (data, cb) => {
HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync('html-webpack-plugin-after-emit', (data, cb) => {
hotMiddleware.publish({ action: 'reload' })
cb()
})
@@ -59,7 +58,7 @@ function startRenderer() {
resolve()
})
},
}
},
)
server.listen(9080)
@@ -73,7 +72,6 @@ function startMain() {
const compiler = webpack(mainConfig)
compiler.hooks.watchRun.tapAsync('watch-run', (compilation, done) => {
logStats('Main', chalk.white.bold('compiling...'))
hotMiddleware.publish({ action: 'compiling' })
done()
})
@@ -137,34 +135,24 @@ function electronLog(data, color) {
}
}
function greeting() {
/* const cols = process.stdout.columns
let text = ''
if (cols > 104) text = 'electron-vue'
else if (cols > 76) text = 'electron-|vue'
else text = false
if (text) {
say(text, {
colors: ['yellow'],
font: 'simple3d',
space: false,
})
} else console.log(chalk.yellow.bold('\n electron-vue')) */
console.log(chalk.blue('getting ready...') + '\n')
}
function init() {
greeting()
const Spinnies = require('spinnies')
const spinners = new Spinnies({ color: 'blue' })
spinners.add('main', { text: 'main compiling' })
spinners.add('renderer', { text: 'renderer compiling' })
function handleSuccess(name) {
spinners.succeed(name, { text: name + ' compile success!' })
}
function handleFail(name) {
spinners.fail(name, { text: name + ' compile fail!' })
}
Promise.all([startRenderer(), startMain()])
.then(() => {
startElectron()
})
.catch(err => {
console.error(err)
})
Promise.all([
startRenderer().then(() => handleSuccess('renderer')).catch(() => handleFail('renderer')),
startMain().then(() => handleSuccess('main')).catch(() => handleFail('main')),
]).then(startElectron).catch(err => {
console.error(err)
})
}
init()

View File

@@ -1,45 +1,49 @@
{\rtf1\adeflang1025\ansi\ansicpg936\uc2\adeff31507\deff0\stshfdbch31505\stshfloch31506\stshfhich31506\stshfbi31507\deflang1033\deflangfe2052\themelang1033\themelangfe2052\themelangcs0{\fonttbl{\f2\fbidi \fmodern\fcharset0\fprq1{\*\panose 02070309020205020404}Courier New;}
{\rtf1\adeflang1025\ansi\ansicpg936\uc2\adeff0\deff0\stshfdbch31505\stshfloch31506\stshfhich31506\stshfbi0\deflang1033\deflangfe2052\themelang1033\themelangfe2052\themelangcs0{\fonttbl{\f0\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f2\fbidi \fmodern\fcharset0\fprq1{\*\panose 02070309020205020404}Courier New;}
{\f13\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}\'cb\'ce\'cc\'e5{\*\falt SimSun};}{\f34\fbidi \froman\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria Math;}
{\f44\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}@\'cb\'ce\'cc\'e5;}{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}
{\f44\fbidi \fnil\fcharset134\fprq2{\*\panose 00000000000000000000}@\'cb\'ce\'cc\'e5;}{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}
{\fdbmajor\f31501\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}\'b5\'c8\'cf\'df Light;}{\fhimajor\f31502\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}\'b5\'c8\'cf\'df Light;}
{\fbimajor\f31503\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\flominor\f31504\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}
{\fdbminor\f31505\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}\'b5\'c8\'cf\'df{\*\falt DengXian};}{\fhiminor\f31506\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}\'b5\'c8\'cf\'df{\*\falt DengXian};}
{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f65\fbidi \fmodern\fcharset238\fprq1 Courier New CE;}{\f66\fbidi \fmodern\fcharset204\fprq1 Courier New Cyr;}
{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f45\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\f46\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
{\f48\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f49\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f50\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f51\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
{\f52\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f53\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f65\fbidi \fmodern\fcharset238\fprq1 Courier New CE;}{\f66\fbidi \fmodern\fcharset204\fprq1 Courier New Cyr;}
{\f68\fbidi \fmodern\fcharset161\fprq1 Courier New Greek;}{\f69\fbidi \fmodern\fcharset162\fprq1 Courier New Tur;}{\f70\fbidi \fmodern\fcharset177\fprq1 Courier New (Hebrew);}{\f71\fbidi \fmodern\fcharset178\fprq1 Courier New (Arabic);}
{\f72\fbidi \fmodern\fcharset186\fprq1 Courier New Baltic;}{\f73\fbidi \fmodern\fcharset163\fprq1 Courier New (Vietnamese);}{\f177\fbidi \fnil\fcharset0\fprq2 SimSun Western{\*\falt SimSun};}{\f487\fbidi \fnil\fcharset0\fprq2 @SimSun Western;}
{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}
{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbmajor\f31520\fbidi \fnil\fcharset0\fprq2 DengXian Light Western;}
{\fdbmajor\f31518\fbidi \fnil\fcharset238\fprq2 DengXian Light CE;}{\fdbmajor\f31519\fbidi \fnil\fcharset204\fprq2 DengXian Light Cyr;}{\fdbmajor\f31521\fbidi \fnil\fcharset161\fprq2 DengXian Light Greek;}
{\fhimajor\f31530\fbidi \fnil\fcharset0\fprq2 DengXian Light Western;}{\fhimajor\f31528\fbidi \fnil\fcharset238\fprq2 DengXian Light CE;}{\fhimajor\f31529\fbidi \fnil\fcharset204\fprq2 DengXian Light Cyr;}
{\fhimajor\f31531\fbidi \fnil\fcharset161\fprq2 DengXian Light Greek;}{\fbimajor\f31538\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}
{\flominor\f31548\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}
{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbminor\f31560\fbidi \fnil\fcharset0\fprq2 DengXian Western{\*\falt DengXian};}
{\fdbminor\f31558\fbidi \fnil\fcharset238\fprq2 DengXian CE{\*\falt DengXian};}{\fdbminor\f31559\fbidi \fnil\fcharset204\fprq2 DengXian Cyr{\*\falt DengXian};}{\fdbminor\f31561\fbidi \fnil\fcharset161\fprq2 DengXian Greek{\*\falt DengXian};}
{\fhiminor\f31570\fbidi \fnil\fcharset0\fprq2 DengXian Western{\*\falt DengXian};}{\fhiminor\f31568\fbidi \fnil\fcharset238\fprq2 DengXian CE{\*\falt DengXian};}{\fhiminor\f31569\fbidi \fnil\fcharset204\fprq2 DengXian Cyr{\*\falt DengXian};}
{\fhiminor\f31571\fbidi \fnil\fcharset161\fprq2 DengXian Greek{\*\falt DengXian};}{\fbiminor\f31578\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbiminor\f31579\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
{\fbiminor\f31581\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbiminor\f31582\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbiminor\f31583\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
{\fbiminor\f31584\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbiminor\f31585\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbiminor\f31586\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}
{\f45\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\f46\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\f48\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f49\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}
{\f50\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f51\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\f52\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}
{\f53\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}}{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;
\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;\red0\green0\blue0;\red0\green0\blue0;}
{\*\defchp \fs21\kerning2\loch\af31506\hich\af31506\dbch\af31505 }{\*\defpap \ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\stylesheet{
\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \fs21\lang1033\langfe2052\kerning2\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp2052
{\f72\fbidi \fmodern\fcharset186\fprq1 Courier New Baltic;}{\f73\fbidi \fmodern\fcharset163\fprq1 Courier New (Vietnamese);}{\f177\fbidi \fnil\fcharset0\fprq2 SimSun Western{\*\falt SimSun};}{\f385\fbidi \froman\fcharset238\fprq2 Cambria Math CE;}
{\f386\fbidi \froman\fcharset204\fprq2 Cambria Math Cyr;}{\f388\fbidi \froman\fcharset161\fprq2 Cambria Math Greek;}{\f389\fbidi \froman\fcharset162\fprq2 Cambria Math Tur;}{\f392\fbidi \froman\fcharset186\fprq2 Cambria Math Baltic;}
{\f393\fbidi \froman\fcharset163\fprq2 Cambria Math (Vietnamese);}{\f487\fbidi \fnil\fcharset0\fprq2 @SimSun Western;}{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}
{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}
{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}
{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbmajor\f31520\fbidi \fnil\fcharset0\fprq2 DengXian Light Western;}{\fdbmajor\f31518\fbidi \fnil\fcharset238\fprq2 DengXian Light CE;}
{\fdbmajor\f31519\fbidi \fnil\fcharset204\fprq2 DengXian Light Cyr;}{\fdbmajor\f31521\fbidi \fnil\fcharset161\fprq2 DengXian Light Greek;}{\fhimajor\f31530\fbidi \fnil\fcharset0\fprq2 DengXian Light Western;}
{\fhimajor\f31528\fbidi \fnil\fcharset238\fprq2 DengXian Light CE;}{\fhimajor\f31529\fbidi \fnil\fcharset204\fprq2 DengXian Light Cyr;}{\fhimajor\f31531\fbidi \fnil\fcharset161\fprq2 DengXian Light Greek;}
{\fbimajor\f31538\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}
{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\flominor\f31548\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}
{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}
{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}
{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbminor\f31560\fbidi \fnil\fcharset0\fprq2 DengXian Western{\*\falt DengXian};}{\fdbminor\f31558\fbidi \fnil\fcharset238\fprq2 DengXian CE{\*\falt DengXian};}
{\fdbminor\f31559\fbidi \fnil\fcharset204\fprq2 DengXian Cyr{\*\falt DengXian};}{\fdbminor\f31561\fbidi \fnil\fcharset161\fprq2 DengXian Greek{\*\falt DengXian};}{\fhiminor\f31570\fbidi \fnil\fcharset0\fprq2 DengXian Western{\*\falt DengXian};}
{\fhiminor\f31568\fbidi \fnil\fcharset238\fprq2 DengXian CE{\*\falt DengXian};}{\fhiminor\f31569\fbidi \fnil\fcharset204\fprq2 DengXian Cyr{\*\falt DengXian};}{\fhiminor\f31571\fbidi \fnil\fcharset161\fprq2 DengXian Greek{\*\falt DengXian};}
{\fbiminor\f31578\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbiminor\f31579\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fbiminor\f31581\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}
{\fbiminor\f31582\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbiminor\f31583\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fbiminor\f31584\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
{\fbiminor\f31585\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbiminor\f31586\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}}{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;
\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;
\red192\green192\blue192;\red0\green0\blue0;\red0\green0\blue0;\chyperlink\ctint255\cshade255\red5\green99\blue193;\red96\green94\blue92;\red225\green223\blue221;\cfollowedhyperlink\ctint255\cshade255\red149\green79\blue114;}{\*\defchp
\fs21\kerning2\loch\af31506\hich\af31506\dbch\af31505 }{\*\defpap \ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\stylesheet{
\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0 \fs21\lang1033\langfe2052\kerning2\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp2052
\snext0 \sqformat \spriority0 Normal;}{\*\cs10 \additive \ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\*
\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv
\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \fs21\lang1033\langfe2052\kerning2\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp2052
\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs21\alang1025 \ltrch\fcs0 \fs21\lang1033\langfe2052\kerning2\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp2052
\snext11 \ssemihidden \sunhideused Normal Table;}{\s15\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af2\afs22\alang1025 \ltrch\fcs0
\fs21\lang1033\langfe2052\kerning2\loch\f31505\hich\af2\dbch\af31505\cgrid\langnp1033\langfenp2052 \sbasedon0 \snext15 \slink16 \sunhideused Plain Text;}{\*\cs16 \additive \rtlch\fcs1 \af2 \ltrch\fcs0 \loch\f31505\hich\af2 \sbasedon10 \slink15 \slocked
\'b4\'bf\'ce\'c4\'b1\'be \'d7\'d6\'b7\'fb;}}{\*\rsidtbl \rsid927107\rsid3950508\rsid11081282\rsid12910709\rsid13643782\rsid14384001}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0
\mnaryLim1}{\info{\author lysyw}{\operator lysyw}{\creatim\yr2019\mo8\dy17\hr10\min22}{\revtim\yr2019\mo8\dy17\hr10\min22}{\version2}{\edmins0}{\nofpages1}{\nofwords8}{\nofchars49}{\nofcharsws56}{\vern111}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.co
m/office/word/2003/wordml}}\paperw11906\paperh16838\margl2253\margr2253\margt1440\margb1440\gutter0\ltrsect
\'b4\'bf\'ce\'c4\'b1\'be \'d7\'d6\'b7\'fb;}{\*\cs17 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf19 \sbasedon10 \sunhideused \styrsid9533173 Hyperlink;}{\*\cs18 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \cf20\chshdng0\chcfpat0\chcbpat21
\sbasedon10 \ssemihidden \sunhideused \styrsid9533173 Unresolved Mention;}{\*\cs19 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf22 \sbasedon10 \ssemihidden \sunhideused \styrsid9533173 FollowedHyperlink;}}{\*\pgptbl {\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}}
{\*\rsidtbl \rsid927107\rsid1398824\rsid2109456\rsid3950508\rsid4133944\rsid4355753\rsid9533173\rsid10447395\rsid11081282\rsid12910709\rsid13643782\rsid14384001\rsid15226681}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0
\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\author lysyw}{\operator lysyw}{\creatim\yr2019\mo8\dy17\hr10\min22}{\revtim\yr2020\mo4\dy28\hr13\min46}{\version8}{\edmins3}{\nofpages1}{\nofwords135}{\nofchars772}{\nofcharsws906}{\vern1}}
{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}}\paperw11906\paperh16838\margl2253\margr2253\margt1440\margb1440\gutter0\ltrsect
\deftab420\ftnbj\aenddoc\trackmoves0\trackformatting1\donotembedsysfont1\relyonvml0\donotembedlingdata0\grfdocevents0\validatexml1\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors1\formshade\horzdoc\dgmargin\dghspace180\dgvspace156
\dghorigin2253\dgvorigin1440\dghshow0\dgvshow2\jcompress\lnongrid
\viewkind1\viewscale100\splytwnine\ftnlytwnine\htmautsp\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct\asianbrkrule\rsidroot3950508\newtblstyruls
\viewkind1\viewscale120\splytwnine\ftnlytwnine\htmautsp\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct\asianbrkrule\rsidroot3950508\newtblstyruls
\nogrowautofit\usenormstyforlist\noindnmbrts\felnbrelev\nocxsptable\indrlsweleven\noafcnsttbl\afelev\utinl\hwelev\spltpgpar\notcvasp\notbrkcnstfrctbl\notvatxbx\krnprsnet\cachedcolbal \nouicompat {\upr{\*\fchars
!%),.:\'3b>?]\'7d\'a1\'e9\'a1\'a7\'a1\'e3\'a1\'a4\'a1\'a6\'a1\'a5\'a8\'44\'a1\'ac\'a1\'af\'a1\'b1\'a1\'ad\'a1\'eb\'a1\'e4\'a1\'e5?\'a1\'e6\'a1\'c3\'a1\'a2\'a1\'a3\'a1\'a8\'a1\'b5\'a1\'b7\'a1\'b9\'a1\'bb\'a1\'bf\'a1\'b3\'a1\'bd\'a8\'95\'a6\'e1\'a6\'e3\'a6\'e7\'a6\'e5\'a6\'eb\'a9\'77\'a9\'79\'a9\'7b\'a3\'a1\'a3\'a2\'a3\'a5\'a3\'a7\'a3\'a9\'a3\'ac\'a3\'ae\'a3\'ba\'a3\'bb\'a3\'bf\'a3\'dd\'a3\'e0\'a3\'fc\'a3\'fd\'a1\'ab\'a1\'e9
}{\*\ud\uc0{\*\fchars
@@ -49,18 +53,67 @@ $([\'7b{\uc2\u163 \'a1\'ea\u165 \'a3\'a4\'a1\'a4\'a1\'ae\'a1\'b0\'a1\'b4\'a1\'b6
\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\headery851\footery992\colsx425\endnhere\sectlinegrid312\sectspecifyl\sectrsid2109456\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta \dbch .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang
{\pntxta \dbch .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta \dbch .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta \dbch )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}{\*\pnseclvl6
\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}{\*\pnseclvl9
\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}\pard\plain \ltrpar\s15\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid12910709 \rtlch\fcs1 \af2\afs22\alang1025 \ltrch\fcs0
\fs21\lang1033\langfe2052\kerning2\loch\af31505\hich\af2\dbch\af31505\cgrid\langnp1033\langfenp2052 {\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'b3\'cc\'d0\'f2
\'bd\'f6\'d3\'c3\'d3\'da\'d1\'a7\'cf\'b0\'bd\'bb\'c1\'f7\'ca\'b9\'d3\'c3\'a3\'a1}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709 \loch\af13\hich\af13\dbch\f13 \'c7\'eb\'ce\'f0\'d3\'c3\'d3\'da\'c9\'cc\'d2\'b5\'d3\'c3\'cd\'be\'a3\'a1\'a3\'a1}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709 \loch\af13\hich\af13\dbch\f13 \'ca\'b9\'d3\'c3\'b1\'be\'c8\'ed\'bc\'fe\'d4\'ec\'b3\'c9\'b5\'c4\'d2\'bb\'c7\'d0\'ba\'f3\'b9\'fb\'d3\'c9\'ca\'b9\'d3\'c3\'d5\'df
\'b3\'d0\'b5\'a3\'a3\'a1}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709
\par
\par \hich\af13\dbch\af13\loch\f13 By: }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709 \loch\af13\hich\af13\dbch\f13 \'c2\'e4\'d1\'a9\'ce\'de\'ba\'db}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709
\par
\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}\pard\plain \ltrpar\s15\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid1398824 \rtlch\fcs1 \af2\afs22\alang1025 \ltrch\fcs0
\fs21\lang1033\langfe2052\kerning2\loch\af31505\hich\af2\dbch\af31505\cgrid\langnp1033\langfenp2052 {\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'a3\'a8
\'c8\'ed\'bc\'fe\'a3\'a9\'bb\'f9\'d3\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid4133944 \hich\af13\dbch\af13\loch\f13 Apache License 2.0}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \hich\af13\dbch\af13\loch\f13 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'d0\'ed\'bf\'c9\'d6\'a4\'b7\'a2
\'d0\'d0\'a3\'ac\'d4\'da\'ca\'b9\'d3\'c3\'b1\'be\'c8\'ed\'bc\'fe\'c7\'b0\'a3\'ac\'c4\'e3\'a3\'a8\'ca\'b9\'d3\'c3\'d5\'df\'a3\'a9\'d0\'e8\'c7\'a9\'ca\'f0\'b1\'be\'d0\'ad\'d2\'e9\'b2\'c5\'bf\'c9\'bc\'cc\'d0\'f8\'ca\'b9\'d3\'c3\'a3\'ac\'d2\'d4\'cf\'c2
\'d0\'ad\'d2\'e9\'ca\'c7\'b6\'d4\'d3\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \hich\af13\dbch\af13\loch\f13 Apache Li\hich\af13\dbch\af13\loch\f13 cense 2.0 }{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'b5\'c4\'b2\'b9\'b3\'e4\'a3\'ac\'c8\'e7\'d3\'d0\'b3\'e5\'cd\'bb\'a3\'ac\'d2\'d4\'d2\'d4\'cf\'c2\'d0\'ad\'d2\'e9\'ce\'aa\'d7\'bc\'a1\'a3}{\rtlch\fcs1 \af13
\ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15226681
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid10447395\charrsid1398824
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'b4\'ca\'d3\'ef\'d4\'bc\'b6\'a8\'a3\'ba\'b1\'be\'d0\'ad\'d2\'e9\'d6\'d0\'b5\'c4\'a1\'b0\'b1\'be\'c8\'ed\'bc\'fe\'a1\'b1\'d6\'b8
\'c2\'e5\'d1\'a9\'d2\'f4\'c0\'d6\'d7\'c0\'c3\'e6\'b0\'e6\'cf\'ee\'c4\'bf\'a3\'bb\'a1\'b0\'ca\'b9\'d3\'c3\'d5\'df\'a1\'b1\'d6\'b8\'c7\'a9\'ca\'f0\'b1\'be\'d0\'ad\'d2\'e9\'b5\'c4\'ca\'b9\'d3\'c3\'d5\'df\'a3\'bb\'a1\'b0\'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6
\'c6\'bd\'cc\'a8\'a1\'b1\'d6\'b8\'b6\'d4\'b1\'be\'c8\'ed\'bc\'fe\'c4\'da\'d6\'c3\'b5\'c4\'b0\'fc\'c0\'a8\'bf\'e1\'ce\'d2\'a1\'a2\'bf\'e1\'b9\'b7\'a1\'a2\'df\'e4\'b9\'be\'b5\'c8\'d2\'f4\'c0\'d6\'d4\'b4\'b5\'c4\'b9\'d9\'b7\'bd\'c6\'bd\'cc\'a8\'cd\'b3
\'b3\'c6\'a3\'bb\'a1\'b0\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd\'a1\'b1\'d6\'b8\'b0\'fc\'c0\'a8\'b5\'ab\'b2\'bb\'cf\'de\'d3\'da\'cd\'bc\'cf\'f1\'a1\'a2\'d2\'f4\'c6\'b5\'a1\'a2\'c3\'fb\'d7\'d6\'b5\'c8\'d4\'da\'c4\'da\'b5\'c4\'cb\'fb\'c8\'cb\'d3\'b5\'d3\'d0
\'cb\'f9\'ca\'f4\'b0\'e6\'c8\'a8\'b5\'c4\'ca\'fd\'be\'dd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824
\par
\par \hich\af13\dbch\af13\loch\f13 1}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'a1\'a2\'b1\'be\'c8\'ed\'bc\'fe\'b5\'c4\'ca\'fd\'be\'dd\'c0\'b4\'d4\'b4\'d4\'ad\'c0\'ed\'ca\'c7
\'b4\'d3\'b8\'f7\'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'b5\'c4\'b9\'ab\'bf\'aa\'b7\'fe\'ce\'f1\'c6\'f7\'d6\'d0\'c0\'ad\'c8\'a1\'ca\'fd\'be\'dd\'a3\'ac\'be\'ad\'b9\'fd\'b6\'d4\'ca\'fd\'be\'dd\'bc\'f2\'b5\'a5\'b5\'d8\'c9\'b8\'d1\'a1\'d3\'eb
\'ba\'cf\'b2\'a2\'ba\'f3\'bd\'f8\'d0\'d0\'d5\'b9\'ca\'be\'a3\'ac\'d2\'f2\'b4\'cb\'b1\'be\'c8\'ed\'bc\'fe\'b2\'bb\'b6\'d4\'ca\'fd\'be\'dd\'b5\'c4\'d7\'bc\'c8\'b7\'d0\'d4\'b8\'ba\'d4\'f0\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824
\par \hich\af13\dbch\af13\loch\f13 2}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'a1\'a2\'ca\'b9\'d3\'c3\'b1\'be\'c8\'ed\'bc\'fe\'b5\'c4\'b9\'fd\'b3\'cc\'d6\'d0\'bf\'c9\'c4\'dc
\'bb\'e1\'b2\'fa\'c9\'fa\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd\'a3\'ac\'b6\'d4\'d3\'da\'d5\'e2\'d0\'a9\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd\'a3\'ac\'b1\'be\'c8\'ed\'bc\'fe\'b2\'bb\'d3\'b5\'d3\'d0\'cb\'fc\loch\af13\hich\af13\dbch\f13 \'c3\'c7\'b5\'c4\'cb\'f9\'d3\'d0
\'c8\'a8\'a3\'ac\'ce\'aa\'c1\'cb\'b1\'dc\'c3\'e2\'d4\'ec\'b3\'c9\'c7\'d6\'c8\'a8\'a3\'ac\'ca\'b9\'d3\'c3\'d5\'df\'ce\'f1\'b1\'d8\'d4\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824
\hich\af13\dbch\af13\loch\f13 24}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'d0\'a1\'ca\'b1\'c4\'da\'c7\'e5\'b3\'fd\'ca\'b9\'d3\'c3\'b1\'be\'c8\'ed\'bc\'fe\'b5\'c4\'b9\'fd
\'b3\'cc\'d6\'d0\'cb\'f9\'b2\'fa\'c9\'fa\'b5\'c4\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824
\par \hich\af13\dbch\af13\loch\f13 3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'a1\'a2\'b1\'be\'c8\'ed\'bc\'fe\'c4\'da\'b5\'c4\'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8
\'b1\'f0\'c3\'fb\'ce\'aa\'b1\'be\'c8\'ed\'bc\'fe\'c4\'da\'b6\'d4\'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'b5\'c4\'d2\'bb\'b8\'f6\'b3\'c6\'ba\'f4\'a3\'ac\'b2\'bb\'b0\'fc\'ba\'ac\'b6\'f1\'d2\'e2\'a3\'ac\'c8\'e7\'b9\'fb\'b9\'d9\'b7\'bd\'d2\'f4
\'c0\'d6\'c6\'bd\'cc\'a8\'be\'f5\'b5\'c3\'b2\'bb\'cd\'d7\'a3\'ac\'bf\'c9\'c1\'aa\'cf\'b5\'b1\'be\'c8\'ed\'bc\'fe\'b8\'fc\'b8\'c4\'bb\'f2\'d2\'c6\'b3\'fd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824
\par \hich\af13\dbch\af13\loch\f13 4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'a1\'a2\'b1\'be\'c8\'ed\'bc\'fe\'c4\'da\'ca\'b9\'d3\'c3\'b5\'c4\'b2\'bf\'b7\'d6\'b0\'fc\'c0\'a8
\'b5\'ab\'b2\'bb\'cf\'de\'d3\'da\'d7\'d6\'cc\'e5\'a1\'a2\'cd\'bc\'c6\'ac\'b5\'c8\'d7\'ca\'d4\'b4\'c0\'b4\'d4\'b4\'d3\'da\'bb\'a5\'c1\'aa\'cd\'f8\'a3\'ac\'c8\'e7\'b9\'fb\'b3\'f6\'cf\'d6\'c7\'d6\'c8\'a8\'bf\'c9\'c1\'aa\'cf\'b5\'b1\'be\'c8\'ed\'bc\'fe
\'d2\'c6\'b3\'fd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824
\par \hich\af13\dbch\af13\loch\f13 5}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'a1\'a2\'d3\'c9\'d3\'da\'ca\'b9\'d3\'c3\'b1\'be\'c8\'ed\'bc\'fe\'b2\'fa\'c9\'fa\'b5\'c4\'b0\'fc
\'c0\'a8\'d3\'c9\'d3\'da\'b1\'be\'d0\'ad\'d2\'e9\'bb\'f2\'d3\'c9\'d3\'da\'ca\'b9\'d3\'c3\'bb\'f2\'ce\'de\'b7\'a8\'ca\'b9\'d3\'c3\'b1\'be\'c8\'ed\'bc\'fe\'b6\'f8\'d2\'fd\'c6\'f0\'b5\'c4\'c8\'ce\'ba\'ce\'d0\'d4\'d6\'ca\'b5\'c4\'c8\'ce\'ba\'ce\'d6\'b1
\'bd\'d3\'a1\'a2\'bc\'e4\'bd\'d3\'a1\'a2\'cc\'d8\'ca\'e2\'a1\'a2\'c5\'bc\'c8\'bb\'bb\'f2\'bd\'e1\'b9\'fb\'d0\'d4\'cb\'f0\'ba\'a6\'a3\'a8\'b0\'fc\'c0\'a8\'b5\'ab\'b2\'bb\'cf\'de\'d3\'da\'d2\'f2\'c9\'cc\'d3\'fe\'cb\'f0\'ca\'a7\'a1\'a2\'cd\'a3\'b9\'a4
\'a1\'a2\'bc\'c6\'cb\'e3\'bb\'fa\'b9\'ca\'d5\'cf\'bb\'f2\'b9\'ca\'d5\'cf\'d2\'fd\'c6\'f0\'b5\'c4\'cb\'f0\'ba\'a6\'c5\'e2\'b3\'a5\'a3\'ac\'bb\'f2\'c8\'ce\'ba\'ce\'bc\'b0\loch\af13\hich\af13\dbch\f13 \'cb\'f9\'d3\'d0\'c6\'e4\'cb\'fb\'c9\'cc\'d2\'b5\'cb\'f0
\'ba\'a6\'bb\'f2\'cb\'f0\'ca\'a7\'a3\'a9\'d3\'c9\'ca\'b9\'d3\'c3\'d5\'df\'b8\'ba\'d4\'f0\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824
\par \hich\af13\dbch\af13\loch\f13 6}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'a1\'a2\'b1\'be\'cf\'ee\'c4\'bf\'cd\'ea\'c8\'ab\'c3\'e2\'b7\'d1\'a3\'ac\'c7\'d2\'bf\'aa\'d4\'b4
\'b7\'a2\'b2\'bc\'d3\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \hich\af13\dbch\af13\loch\f13 GitHub }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824
\loch\af13\hich\af13\dbch\f13 \'c3\'e6\'cf\'f2\'c8\'ab\'ca\'c0\'bd\'e7\'c8\'cb\'d3\'c3\'d7\'f7\'b6\'d4\'bc\'bc\'ca\'f5\'b5\'c4\'d1\'a7\'cf\'b0\'bd\'bb\'c1\'f7\'a3\'ac\'b1\'be\'c8\'ed\'bc\'fe\'b2\'bb\'b6\'d4\'cf\'ee\'c4\'bf\'c4\'da\'b5\'c4\'bc\'bc\'ca\'f5
\'bf\'c9\'c4\'dc\'b4\'e6\'d4\'da\'ce\'a5\'b7\'b4\'b5\'b1\'b5\'d8\'b7\'a8\'c2\'c9\'b7\'a8\'b9\'e6\'b5\'c4\'d0\'d0\'ce\'aa\'d7\'f7\'b1\'a3\'d6\'a4\'a3\'ac\'bd\'fb\'d6\'b9\'d4\'da\'ce\'a5\'b7\'b4\'b5\'b1\'b5\'d8\'b7\'a8\'c2\'c9\'b7\'a8\'b9\'e6\'b5\'c4
\'c7\'e9\'bf\'f6\'cf\'c2\'ca\'b9\'d3\'c3\'b1\'be\'c8\'ed\'bc\'fe\'a3\'ac\'b6\'d4\'d3\'da\'ca\'b9\'d3\'c3\'d5\'df\'d4\'da\'c3\'f7\'d6\'aa\'bb\'f2\'b2\'bb\'d6\'aa\'b5\'b1\'b5\'d8\'b7\'a8\'c2\'c9\'b7\'a8\'b9\'e6\'b2\'bb\'d4\'ca\'d0\'ed\'b5\'c4\'c7\'e9
\'bf\'f6\'cf\'c2\'ca\'b9\'d3\'c3\'b1\'be\'c8\'ed\'bc\'fe\'cb\'f9\'d4\'ec\'b3\'c9\'b5\'c4\'c8\'ce\'ba\'ce\'ce\'a5\'b7\'a8\'ce\'a5\'b9\'e6\'d0\'d0\'ce\'aa\'d3\'c9\'ca\'b9\'d3\'c3\'d5\'df\'b3\'d0\'b5\'a3\'a3\'ac\'b1\'be\'c8\'ed\'bc\'fe\'b2\'bb\'b3\'d0
\'b5\'a3\'d3\'c9\'b4\'cb\'d4\'ec\'b3\'c9\'b5\'c4\'c8\'ce\'ba\'ce\'d6\'b1\'bd\'d3\'a1\'a2\'bc\'e4\'bd\'d3\'a1\'a2\'cc\'d8\'ca\'e2\'a1\'a2\'c5\'bc\'c8\'bb\'bb\'f2\'bd\'e1\'b9\'fb\'d0\'d4\'d4\'f0\'c8\'ce\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid1398824
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15226681
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid10447395\charrsid1398824
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \hich\af13\dbch\af13\loch\f13 * }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13
\'c8\'f4\'d0\'ad\'d2\'e9\'b8\'fc\'d0\'c2\'a3\'ac\'cb\'a1\'b2\'bb\'c1\'ed\'d0\'d0\'cd\'a8\'d6\'aa\'a3\'ac\'bf\'c9\'b5\'bd\'bf\'aa\'d4\'b4\'b5\'d8\'d6\'b7\'b2\'e9\'bf\'b4\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid15226681
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \hich\af13\dbch\af13\loch\f13 * }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13
\'b1\'be\'c8\'ed\'bc\'fe\'b5\'c4\'b3\'f5\'d6\'d4\'ca\'c7\'b0\'ef\'d6\'fa\'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'bc\'f2\'bb\'af\'ca\'fd\'be\'dd\'ba\'f3\'b4\'fa\'ce\'aa\'d5\'b9\'ca\'be\'a3\'ac\'b0\'ef\'d6\'fa\'ca\'b9\'d3\'c3\'d5\'df\'b8\'f9
\'be\'dd\'b8\'e8\'c7\'fa\loch\af13\hich\af13\dbch\f13 \'c3\'fb\'a1\'a2\'d2\'d5\'ca\'f5\'bc\'d2\'b5\'c8\'b9\'d8\'bc\'fc\'d7\'d6\'bf\'ec\'cb\'d9\'b5\'d8\'b6\'a8\'ce\'bb\'cb\'f9\'d0\'e8\'c4\'da\'c8\'dd\'cb\'f9\'d4\'da\'b5\'c4\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8
\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid15226681
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \hich\af13\dbch\af13\loch\f13 * }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13
\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'b2\'bb\'d2\'d7\'a3\'ac\'bd\'a8\'d2\'e9\'b5\'bd\'b6\'d4\'d3\'a6\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'d6\'a7\'b3\'d6\'d5\'fd\'b0\'e6\'d7\'ca\'d4\'b4\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid12910709
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid9533173\charrsid15226681
\par }\pard \ltrpar\s15\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid12910709 {\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709 \hich\af13\dbch\af13\loch\f13 By: }{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709 \loch\af13\hich\af13\dbch\f13 \'c2\'e4\'d1\'a9\'ce\'de\'ba\'db}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709
\par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a
9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad
5689811a183c61a50f98f4babebc2837878049899a52a57be670674cb23d8e90721f90a4d2fa3802cb35762680fd800ecd7551dc18eb899138e3c943d7e503b6
@@ -114,7 +167,7 @@ e4350d363f2451eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8
617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169
6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363
656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e}
{\*\latentstyles\lsdstimax377\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 1;
{\*\latentstyles\lsdstimax376\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 1;
\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 2;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 3;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 4;
\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 5;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 6;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 7;
\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 8;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 1;
@@ -143,18 +196,8 @@ e4350d363f2451eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Cite;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Code;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Definition;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Keyboard;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Preformatted;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Sample;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Typewriter;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Variable;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation subject;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 No List;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 1;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 2;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 3;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 3;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 4;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 3;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 7;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 3;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 7;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 3;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Contemporary;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Elegant;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Professional;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Subtle 1;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Subtle 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Balloon Text;
\lsdpriority39 \lsdlocked0 Table Grid;\lsdsemihidden1 \lsdlocked0 Placeholder Text;\lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing;\lsdpriority60 \lsdlocked0 Light Shading;\lsdpriority61 \lsdlocked0 Light List;\lsdpriority62 \lsdlocked0 Light Grid;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Balloon Text;\lsdpriority39 \lsdlocked0 Table Grid;
\lsdsemihidden1 \lsdlocked0 Placeholder Text;\lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing;\lsdpriority60 \lsdlocked0 Light Shading;\lsdpriority61 \lsdlocked0 Light List;\lsdpriority62 \lsdlocked0 Light Grid;
\lsdpriority63 \lsdlocked0 Medium Shading 1;\lsdpriority64 \lsdlocked0 Medium Shading 2;\lsdpriority65 \lsdlocked0 Medium List 1;\lsdpriority66 \lsdlocked0 Medium List 2;\lsdpriority67 \lsdlocked0 Medium Grid 1;\lsdpriority68 \lsdlocked0 Medium Grid 2;
\lsdpriority69 \lsdlocked0 Medium Grid 3;\lsdpriority70 \lsdlocked0 Dark List;\lsdpriority71 \lsdlocked0 Colorful Shading;\lsdpriority72 \lsdlocked0 Colorful List;\lsdpriority73 \lsdlocked0 Colorful Grid;\lsdpriority60 \lsdlocked0 Light Shading Accent 1;
\lsdpriority61 \lsdlocked0 Light List Accent 1;\lsdpriority62 \lsdlocked0 Light Grid Accent 1;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 1;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 1;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 1;
@@ -202,8 +245,8 @@ e4350d363f2451eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8
\lsdpriority49 \lsdlocked0 List Table 4 Accent 5;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 5;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 5;
\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 List Table 2 Accent 6;\lsdpriority48 \lsdlocked0 List Table 3 Accent 6;\lsdpriority49 \lsdlocked0 List Table 4 Accent 6;
\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Mention;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hashtag;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Unresolved Mention;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Link;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Link Error;}}{\*\datastore 0105000002000000180000004d73786d6c322e534158584d4c5265616465722e362e3000000000000000000000060000
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hashtag;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Unresolved Mention;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Link;}}{\*\datastore 01050000
02000000180000004d73786d6c322e534158584d4c5265616465722e362e3000000000000000000000060000
d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff090006000000000000000000000001000000010000000000000000100000feffffff00000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
@@ -212,8 +255,8 @@ fffffffffffffffffdfffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e5000000000000000000000000908a
0599a254d501feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000
ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e5000000000000000000000000b026
8d59201dd601feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000105000000000000}}

View File

@@ -1,5 +1,18 @@
This program is only for learning to communicate!
Do not use for commercial purposes! !
All consequences of using this software are borne by the user!
This project (software) is issued based on the Apache License 2.0 license. Before using this software, you (users) need to sign this agreement before you can continue to use it. The following agreement is a supplement to the Apache License 2.0. In case of conflict, use the following agreement Shall prevail.
Terms agreed: "this software" in this agreement refers to the Luo Xue Music desktop version of the project; "user" refers to the user who signed this agreement; "official music platform" refers to the built-in software including Kuwo, Kugou, Migu The official platforms of music sources, etc. are collectively referred to; "copyright data" refers to data including but not limited to images, audio, names, etc. that others own the copyright.
1. The principle of the data source of this software is to pull data from the public servers of each official music platform. After simply filtering and merging the data for display, this software is not responsible for the accuracy of the data.
2. Copyright data may be generated during the use of this software. For this copyright data, the software does not own their ownership. In order to avoid infringement, users must clear the copyright generated during the use of this software within 24 hours data.
3. The alias of the official music platform in this software is a name for the official music platform in the software. It does not contain maliciousness. If the official music platform feels inappropriate, you can contact the software to change or remove it.
4. The parts used in this software include but are not limited to fonts, pictures and other resources from the Internet. If there is infringement, you can contact this software to remove it.
5. Any direct, indirect, special, accidental or consequential damage of any nature arising from the use of this software, including this agreement or the use or inability to use this software (including but not limited to due to loss of goodwill, work stoppage, Compensation for computer failure or damage caused by the failure, or any and all other commercial damage or loss) is the responsibility of the user.
6. This project is completely free, and the open source is published on GitHub. It is used by people all over the world to learn and exchange technology. This software does not guarantee that the technology in the project may violate local laws and regulations. It is prohibited to violate local laws and regulations. Using this software, the user shall be responsible for any violations caused by the user's use of the software without knowing or not permitted by local laws and regulations. The software shall not be responsible for any direct, indirect, special, accidental Or consequential responsibility.
* If the agreement is updated without prior notice, you can check it at the open source address.
* The original intention of this software is to help the official music platform to simplify the data generation for display, and to help users quickly locate the music platform where the desired content is based on the song title, artist and other keywords.
* Music platform is not easy, it is recommended to support genuine resources to the corresponding music platform.
By: lyswhut

View File

@@ -1,5 +1,18 @@
程序仅用于学习交流使用!
请勿用于商业用途!!
使用本软件造成的一切后果由使用者承担!
项目软件基于Apache License 2.0 许可证发行,在使用本软件前,你(使用者)需签署本协议才可继续使用,以下协议是对于 Apache License 2.0 的补充,如有冲突,以以下协议为准。
词语约定:本协议中的“本软件”指洛雪音乐桌面版项目;“使用者”指签署本协议的使用者;“官方音乐平台”指对本软件内置的包括酷我、酷狗、咪咕等音乐源的官方平台统称;“版权数据”指包括但不限于图像、音频、名字等在内的他人拥有所属版权的数据。
1、本软件的数据来源原理是从各官方音乐平台的公开服务器中拉取数据经过对数据简单地筛选与合并后进行展示因此本软件不对数据的准确性负责。
2、使用本软件的过程中可能会产生版权数据对于这些版权数据本软件不拥有它们的所有权为了避免造成侵权使用者务必在24小时内清除使用本软件的过程中所产生的版权数据。
3、本软件内的官方音乐平台别名为本软件内对官方音乐平台的一个称呼不包含恶意如果官方音乐平台觉得不妥可联系本软件更改或移除。
4、本软件内使用的部分包括但不限于字体、图片等资源来源于互联网如果出现侵权可联系本软件移除。
5、由于使用本软件产生的包括由于本协议或由于使用或无法使用本软件而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害包括但不限于因商誉损失、停工、计算机故障或故障引起的损害赔偿或任何及所有其他商业损害或损失由使用者负责。
6、本项目完全免费且开源发布于 GitHub 面向全世界人用作对技术的学习交流,本软件不对项目内的技术可能存在违反当地法律法规的行为作保证,禁止在违反当地法律法规的情况下使用本软件,对于使用者在明知或不知当地法律法规不允许的情况下使用本软件所造成的任何违法违规行为由使用者承担,本软件不承担由此造成的任何直接、间接、特殊、偶然或结果性责任。
* 若协议更新,恕不另行通知,可到开源地址查看。
* 本软件的初衷是帮助官方音乐平台简化数据后代为展示,帮助使用者根据歌曲名、艺术家等关键字快速地定位所需内容所在的音乐平台。
* 音乐平台不易,建议到对应音乐平台支持正版资源。
By: 落雪无痕

12771
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,17 @@
{
"name": "lx-music-desktop",
"version": "0.6.0",
"version": "0.18.2",
"description": "一个免费的音乐下载助手",
"main": "./dist/electron/main.js",
"productName": "lx-music-desktop",
"scripts": {
"pack": "node build-config/pack.js && npm run pack:win",
"pack:win": "npm run pack:win:setup && npm run pack:win:7z",
"pack:win:setup": "cross-env TARGET=win_安装版 ARCH=x64_x86 electron-builder -w=nsis --x64 --ia32",
"pack:win:portable": "npm run pack:win:portable:x64_x86 && npm run pack:win:portable:x64 && npm run pack:win:portable:x86",
"pack:win:portable:x64_x86": "cross-env TARGET=便携版 ARCH=x64_x86 electron-builder -w=portable --x64 --ia32",
"pack:win": "npm run pack:win:setup:x86_64 && npm run pack:win:7z",
"pack:win:setup:x86_64": "cross-env TARGET=win_安装版 ARCH=x86_64 electron-builder -w=nsis --x64 --ia32",
"pack:win:setup:x64": "cross-env TARGET=win_安装版 ARCH=x64 electron-builder -w=nsis --x64",
"pack:win:setup:x86": "cross-env TARGET=win_安装版 ARCH=x86 electron-builder -w=nsis --ia32",
"pack:win:portable": "npm run pack:win:portable:x86_64 && npm run pack:win:portable:x64 && npm run pack:win:portable:x86",
"pack:win:portable:x86_64": "cross-env TARGET=便携版 ARCH=x86_64 electron-builder -w=portable --x64 --ia32",
"pack:win:portable:x64": "cross-env TARGET=便携版 ARCH=x64 electron-builder -w=portable --x64",
"pack:win:portable:x86": "cross-env TARGET=便携版 ARCH=x86 electron-builder -w=portable --ia32",
"pack:win:7z": "npm run pack:win:7z:x64 && npm run pack:win:7z:x86",
@@ -18,9 +20,9 @@
"publish": "node publish",
"publish:gh": "node build-config/pack.js && npm run publish:win",
"publish:win": "npm run publish:win:7z && npm run publish:win:setup",
"publish:win:setup": "cross-env TARGET=Setup ARCH=x64_x86 electron-builder -w=nsis --x64 --ia32 -p onTagOrDraft",
"publish:win:portable": "npm run publish:win:portable:x64_x86 && npm run publish:win:portable:x64 && npm run publish:win:portable:x86",
"publish:win:portable:x64_x86": "cross-env TARGET=portable ARCH=x64_x86 electron-builder -w=portable --x64 --ia32 -p onTagOrDraft",
"publish:win:setup": "cross-env TARGET=Setup ARCH=x86_64 electron-builder -w=nsis --x64 --ia32 -p onTagOrDraft",
"publish:win:portable": "npm run publish:win:portable:x86_64 && npm run publish:win:portable:x64 && npm run publish:win:portable:x86",
"publish:win:portable:x86_64": "cross-env TARGET=portable ARCH=x86_64 electron-builder -w=portable --x64 --ia32 -p onTagOrDraft",
"publish:win:portable:x64": "cross-env TARGET=portable ARCH=x64 electron-builder -w=portable --x64 -p onTagOrDraft",
"publish:win:portable:x86": "cross-env TARGET=portable ARCH=x86 electron-builder -w=portable --ia32 -p onTagOrDraft",
"publish:win:7z": "npm run publish:win:7z:x64 && npm run publish:win:7z:x86",
@@ -36,6 +38,7 @@
"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",
"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",
@@ -49,7 +52,7 @@
"lint:fix": "eslint --ext .js,.vue -f ./node_modules/eslint-formatter-friendly --fix src"
},
"browserslist": [
"Chrome >= 76"
"Electron 8.2.5"
],
"engines": {
"node": ">= 12"
@@ -57,7 +60,7 @@
"build": {
"appId": "cn.toside.music.desktop",
"directories": {
"output": "build"
"output": "./build"
},
"files": [
"dist/electron/**/*"
@@ -71,12 +74,17 @@
"artifactName": "${productName} v${version} ${env.ARCH} ${env.TARGET}.${ext}"
},
"mac": {
"icon": "./resources/icons/512x512.png",
"icon": "./resources/icons/512x512.icns",
"category": "public.app-category.music"
},
"linux": {
"maintainer": "lyswhut <lyswuhut@qq.com>",
"artifactName": "${productName} v${version} ${env.ARCH}.${ext}"
"maintainer": "lyswhut <lyswhut@qq.com>",
"artifactName": "${productName} v${version} ${env.ARCH}.${ext}",
"icon": "./resources/icons",
"category": "Utility;AudioVideo;Audio;Player;Music;",
"desktop": {
"Name[zh_CN]": "洛雪音乐助手"
}
},
"nsis": {
"oneClick": false,
@@ -87,31 +95,40 @@
"shortcutName": "lx-music"
},
"dmg": {
"window": {
"width": 600,
"height": 400
},
"contents": [
{
"x": 110,
"y": 150,
"x": 106,
"y": 252,
"name": "lx-music"
},
{
"x": 240,
"y": 150,
"x": 490,
"y": 252,
"type": "link",
"path": "/Applications",
"name": "lx-music"
"path": "/Applications"
}
],
"title": "洛雪音乐助手 v${version}"
},
"appImage": {
"license": "./licenses/license_zh.txt",
"category": "Audio"
"category": "Utility;AudioVideo;Audio;Player;Music;"
},
"publish": [
{
"provider": "github",
"owner": "lyswhut",
"repo": "lx-music-desktop"
},
{
"package": "lx-music-desktop",
"repo": "lx-music-desktop",
"user": "lyswhut",
"provider": "bintray"
}
]
},
@@ -126,7 +143,7 @@
],
"author": {
"name": "lyswhut",
"email": "lyswuhut@qq.com"
"email": "lyswhut@qq.com"
},
"license": "Apache-2.0",
"bugs": {
@@ -134,82 +151,84 @@
},
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
"devDependencies": {
"@babel/core": "^7.6.0",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/polyfill": "^7.6.0",
"@babel/preset-env": "^7.6.0",
"autoprefixer": "^9.6.1",
"babel-eslint": "^10.0.3",
"babel-loader": "^8.0.6",
"@babel/core": "^7.9.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-eslint": "^10.1.0",
"babel-loader": "^8.1.0",
"babel-minify-webpack-plugin": "^0.3.1",
"babel-preset-minify": "^0.5.1",
"cfonts": "^2.4.5",
"chalk": "^2.4.2",
"browserslist": "^4.12.0",
"cfonts": "^2.8.1",
"chalk": "^4.0.0",
"changelog-parser": "^2.8.0",
"copy-webpack-plugin": "^5.0.4",
"core-js": "^3.2.1",
"cos-nodejs-sdk-v5": "^2.5.12",
"cross-env": "^6.0.0",
"css-loader": "^3.2.0",
"copy-webpack-plugin": "^5.1.1",
"core-js": "^3.6.5",
"cos-nodejs-sdk-v5": "^2.5.20",
"cross-env": "^7.0.2",
"css-loader": "^3.5.3",
"del": "^5.1.0",
"electron": "^6.0.10",
"electron-builder": "^21.2.0",
"electron": "^8.2.5",
"electron-builder": "^22.6.0",
"electron-debug": "^3.0.1",
"electron-devtools-installer": "^2.2.4",
"eslint": "^6.4.0",
"eslint-config-standard": "^14.1.0",
"electron-devtools-installer": "^3.0.0",
"eslint": "^6.8.0",
"eslint-config-standard": "^14.1.1",
"eslint-formatter-friendly": "^7.0.0",
"eslint-loader": "^3.0.0",
"eslint-plugin-html": "^6.0.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-node": "^10.0.0",
"eslint-loader": "^4.0.2",
"eslint-plugin-html": "^6.0.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",
"file-loader": "^4.2.0",
"file-loader": "^6.0.0",
"friendly-errors-webpack-plugin": "^1.7.0",
"html-webpack-plugin": "^3.2.0",
"less": "^3.10.3",
"less-loader": "^5.0.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.8.0",
"multispinner": "^0.2.1",
"mini-css-extract-plugin": "^0.9.0",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"postcss-loader": "^3.0.0",
"postcss-pxtorem": "^5.1.1",
"pug": "^2.0.4",
"pug-loader": "^2.4.0",
"pug-plain-loader": "^1.0.0",
"raw-loader": "^3.1.0",
"rimraf": "^3.0.0",
"raw-loader": "^4.0.1",
"rimraf": "^3.0.2",
"spinnies": "^0.5.1",
"stylus": "^0.54.7",
"stylus-loader": "^3.0.2",
"terser-webpack-plugin": "^2.1.0",
"url-loader": "^2.1.0",
"vue-loader": "^15.7.1",
"terser-webpack-plugin": "^2.3.6",
"url-loader": "^4.1.0",
"vue-loader": "^15.9.1",
"vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.6.10",
"webpack": "^4.40.2",
"webpack-cli": "^3.3.9",
"webpack-dev-server": "^3.8.1",
"vue-template-compiler": "^2.6.11",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3",
"webpack-hot-middleware": "^2.25.0",
"webpack-merge": "^4.2.2"
},
"dependencies": {
"axios": "^0.19.0",
"crypto-js": "^3.1.9-1",
"electron-log": "^3.0.8",
"electron-store": "^5.0.0",
"electron-updater": "^4.1.2",
"flac-metadata": "^0.1.1",
"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",
"js-htmlencode": "^0.3.0",
"lrc-file-parser": "^0.1.12",
"node-downloader-helper": "^1.0.10",
"node-id3": "^0.1.11",
"request": "^2.88.0",
"vue": "^2.6.10",
"vue-electron": "^1.0.6",
"vue-router": "^3.1.3",
"vuex": "^3.1.1",
"vuex-electron": "^1.0.3",
"lrc-file-parser": "^1.0.3",
"mousetrap": "^1.6.5",
"needle": "^2.4.1",
"node-id3": "^0.1.16",
"request": "^2.88.2",
"vue": "^2.6.11",
"vue-i18n": "^8.17.4",
"vue-router": "^3.1.6",
"vuex": "^3.3.0",
"vuex-router-sync": "^5.0.0"
}
}

View File

@@ -1,7 +1,33 @@
const autoprefixer = require('autoprefixer')
const pxtorem = require('postcss-pxtorem')
module.exports = {
plugins: [
pxtorem({
rootValue: 16,
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',
'height', 'width',
'max-width', 'max-height',
'min-width', 'min-height',
'flex', '::-webkit-scrollbar',
'top', 'left', 'bottom', 'right',
'border-radius',
],
selectorBlackList: ['html', 'ignore-to-rem'],
replace: true,
mediaQuery: false,
minPixelValue: 0,
exclude: [/node_modules/i],
}),
autoprefixer(),
],
}

View File

@@ -1,13 +1,3 @@
### 新增
- 新增音乐**聚合搜索**,目前支持酷我、酷狗、百度源搜索
- 新增代理功能
### 优化
- 优化从《梦里嫦娥》皮肤切换到其他皮肤时侧栏动画的切换效果
### 修复
- 修复试听列表没有歌曲时会显示列表加载中的Bug
- 修复切换歌单列表详情时的UI Bug
- 修复开启托盘时,可能导致无法自动更新的问题

View File

@@ -1,7 +1,95 @@
{
"version": "0.6.0",
"desc": "<h3>新增</h3>\n<ul>\n<li>新增音乐<strong>聚合搜索</strong>,目前支持酷我、酷狗、百度源搜索</li>\n<li>新增代理功能</li>\n</ul>\n<h3>优化</h3>\n<ul>\n<li>优化从《梦里嫦娥》皮肤切换到其他皮肤时侧栏动画的切换效果</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>修复试听列表没有歌曲时会显示列表加载中的Bug</li>\n<li>修复切换歌单列表详情时的UI Bug</li>\n</ul>\n",
"version": "0.18.2",
"desc": "<h3>修复</h3>\n<ul>\n<li>修复开启托盘时,可能导致无法自动更新的问题</li>\n</ul>\n",
"history": [
{
"version": "0.18.1",
"desc": "<h3>优化</h3>\n<ul>\n<li>win下的托盘图标使用更大的图片</li>\n<li>加长软件协议的强制停留时间</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>修复导入设置某些设置未立即生效的问题</li>\n</ul>\n"
},
{
"version": "0.18.0",
"desc": "<h3>新增</h3>\n<ul>\n<li>新增FLAC格式音乐标签信息写入与封面嵌入因128k以外的音质已失效目前该功能用不上了</li>\n<li>添加软件启动时是否自动聚焦搜索框的设置</li>\n<li>新增托盘设置,默认关闭,可到设置开启,感谢 @LasyIsLazy 提交的PR</li>\n<li>新增打开酷狗源用户歌单</li>\n<li>新增使用协议</li>\n<li>新增虾米音源</li>\n<li>新增新皮肤“粉妆玉琢”、“青出于黑”,可去体验下~</li>\n<li>新增“超大”、“巨大”窗口尺寸</li>\n<li>新增播放详情页(退出详情页可点击右上角退出按钮或者在播放详情页任意地方<strong>鼠标快速右击两次</strong></li>\n</ul>\n<h3>优化</h3>\n<ul>\n<li>略微加深音量条底色</li>\n<li>优化其他界面细节</li>\n<li>优化英语翻译,感谢 @CPCer</li>\n<li>优化程序的流畅度</li>\n</ul>\n<h3>更变</h3>\n<ul>\n<li>下载列表的歌曲下载、播放将随设置中的保存路径改变而改变,不再固定指向其初始位置</li>\n<li>移除列表多选框,现在多选需要键盘配合,想要多选前需按下<code>Shift</code>或<code>Ctrl</code>键然后再鼠标点击想要选中的内容即可触发多选机制,其中<code>Shift</code>键用于连续选择,<code>Ctrl</code>键用于不连续选择,<code>Ctrl+a</code>用于快速全选。例子一想要选中1-5项则先按下<code>Shift</code>键后鼠标点击第一项再点击第五项即可完成选择例子二想要选中1项与第3项则先按下<code>Ctrl</code>键后,鼠标点击第一项,再点击第三项即可完成选择;例子三:想要选中当前列表的全部内容,键盘先按下<code>Ctrl</code>键不放,然后按<code>a</code>键,即可完成选择。用<code>Shift</code>或<code>Ctrl</code>选择时,鼠标点击未选中的内容会将其选中,点击已选择的内容会将其取消选择,若想全部取消选择,在不按<code>Shift</code>或<code>Alt</code>键的情况下,随意点击列表里的一项内容即可全部取消选择。(P.S<code>Ctrl</code>键对应Mac OS上的<code>Command</code>键)</li>\n<li>现在进度条的封面图左击改为打开播放详情页,在列表定位歌曲改为右击</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>修复网易源某些歌曲提示没有可播放的音质的问题</li>\n<li>修复下载管理刷新URL失败时不标记任务下载失败的问题</li>\n<li>修复列表导出的文字描述,感谢 @CPCer</li>\n<li>修复歌曲切换方式无法取消勾选的问题</li>\n<li>修复打开歌单详情的情况下切到其他界面再切回来报错的问题</li>\n<li>修正播放列表浮动按钮错误的文字提示</li>\n</ul>\n<h3>移除</h3>\n<ul>\n<li>因128k以外的音质失效So 禁止所有128k外的音质下载</li>\n</ul>\n<h3>其他</h3>\n<p>更新 Electron 到 8.2.5</p>\n"
},
{
"version": "0.17.0",
"desc": "<h3>新增</h3>\n<ul>\n<li>新增多语言设置目前软件内置了简体中文、繁体中文、英语三种语言欢迎提交PR翻译更多语言</li>\n<li>新增无法打开外部歌单FAQ</li>\n<li>新增启动参数<code>search</code>,使用例子:<code>.\\lx-music-desktop.exe -search=&quot;突然的自我 - 伍佰&quot;</code></li>\n<li>新增音频输出设置</li>\n<li>新增软件内的包括字体在内的界面内容大小调整,现在当窗口大小切换到“较小/大/较大”时,软件内的元素将会适当减小或加大,窗口大小的“小”与“中”内的元素将保持之前的大小暂不做改变</li>\n<li>新增音源别名,默认将显示别名,想要显示回原名可到设置切换(免责声明:别名仅是本软件用于描述各音源的标签,其名字归版权方所有)</li>\n<li>新增发现新版本更新失败弹窗的忽略提醒按钮,忽略提醒后,以后同一个版本再失败时将不会弹窗提醒,但仍可到设置-版本更新手动点开更新弹窗查看或恢复提醒</li>\n<li>新增热搜词,默认关闭,可到设置开启</li>\n<li>新增历史搜索记录,默认关闭,可到设置开启(右击单个历史记录标签可移除所点击的记录)</li>\n</ul>\n<h3>优化</h3>\n<ul>\n<li>优化月里嫦娥皮肤侧栏鼠标悬浮颜色</li>\n<li>优化播放进度条的动画效果</li>\n<li>现在添加下载任务时,后面添加的任务会在列表顶部插入</li>\n<li>优化歌单打开机制,现在歌单加载失败时会提示加载失败了,并且支持直接打开企鹅、酷我手机分享出来的歌单了</li>\n<li>优化右上角最小化/关闭按钮布局</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>修复歌单详情处于加载状态时无法返回的问题</li>\n<li>修复鼠标右击复制列表内容时会复制音质标签的问题</li>\n<li>修复<code>0.6.2</code>及以前的版本导出的“所有数据”内的歌曲列表无法导入的问题</li>\n<li>修复下载列表在某些情况下无法取消全选的问题</li>\n</ul>\n<h3>其他</h3>\n<ul>\n<li>更新Electron到 8.1.1</li>\n</ul>\n"
},
{
"version": "0.16.0",
"desc": "<h3>新增</h3>\n<ul>\n<li>允许选中列表内歌曲名、歌手名、专辑名内的文字,选中后可使用键盘快捷键进行复制</li>\n<li>新增在列表可选内容区域<strong>鼠标右击</strong>时自动复制列表已选文字的功能</li>\n<li>新增在搜索框<strong>鼠标右击</strong>时自动粘贴剪贴板的文本到搜索框中</li>\n<li>任务下载失败时将显示搜索按钮,方便在其他源搜索该歌曲</li>\n</ul>\n<h3>优化</h3>\n<ul>\n<li>优化木叶之村主题翻页器背景颜色</li>\n<li>优化各个主题音质标签颜色</li>\n<li>优化其他一些界面细节及用户交互效果</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>修复启用透明窗口鼠标不穿透的bug</li>\n<li>修复大窗口时设置的音乐来源选项不换行的问题</li>\n<li>修复某些情况下暂停任务会自动开始任务的问题</li>\n<li>修复移除暂停、错误的任务时不删除未下载完成的文件的问题</li>\n<li>修复酷狗源歌单热门标签歌单列表无法加载问题</li>\n<li>修复QQ源歌单热门标签歌单列表无法加载问题</li>\n</ul>\n<h3>其他</h3>\n<ul>\n<li>更新electron到 8.0.1</li>\n</ul>\n"
},
{
"version": "0.15.0",
"desc": "<p>洛雪提前祝大家新年快乐、身体健康、阖家幸福!</p>\n<h3>修复</h3>\n<ul>\n<li>修复歌曲下载列表无法加载的问题</li>\n<li>修复歌曲下载任务数大于最大下载任务数的问题</li>\n<li>修复某些情况下歌曲下载错误的问题</li>\n<li>修复下载列表数据没有被迁移直接被丢弃的问题</li>\n</ul>\n"
},
{
"version": "0.14.1",
"desc": "<p>洛雪提前祝大家新年快乐、身体健康、阖家幸福!</p>\n<h3>修复</h3>\n<ul>\n<li>修复由于旧版配置文件迁移出错导致的软件界面无法显示的问题</li>\n</ul>\n"
},
{
"version": "0.14.0",
"desc": "<p>洛雪提前祝大家新年快乐、身体健康、阖家幸福!</p>\n<h3>新增</h3>\n<ul>\n<li>新增各大平台歌单热门标签显示(显示在歌单界面的第一个下拉标签菜单中)</li>\n<li>恢复QQ音乐源128k音质试听</li>\n<li>新增不强制win7开启透明效果即可使用但要配置运行参数<code>-nt</code>,例如:<code>.\\lx-music-desktop.exe -nt</code>,添加方法可自行百度“给快捷方式加参数”</li>\n<li>新增“新年快乐”主题,可自行切换体验</li>\n</ul>\n<h3>优化</h3>\n<ul>\n<li>减淡各个主题的歌曲列表分隔线颜色</li>\n<li>在线音乐列表音质标签优化,当歌曲有无损音质时隐藏高品质标签</li>\n<li>更新改进的歌词播放插件,现在歌词的播放显示将更准确</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>修复咪咕源无法搜索的问题</li>\n<li>修复更新弹窗底部文字颜色没有适配当前主题颜色的问题</li>\n<li>修复导入设置窗口大小、代理设置不立即生效的问题</li>\n<li>修复在线音乐列表获取失败时无限循环请求的问题</li>\n</ul>\n<h3>其他</h3>\n<ul>\n<li>将软件设置与播放列表分离存储成两个文件</li>\n<li>更新 Electron 到 7.1.9</li>\n</ul>\n"
},
{
"version": "0.13.1",
"desc": "<h3>修复</h3>\n<ul>\n<li>修复全局更新弹窗无法遮盖搜索框的问题</li>\n</ul>\n<h3>其他</h3>\n<ul>\n<li>由于electron 7.1.3 - 7.1.5 的自动更新功能存在Bug现降级到7.1.2</li>\n</ul>\n"
},
{
"version": "0.13.0",
"desc": "<h3>新增</h3>\n<ul>\n<li>新增搜索框搜索建议键盘上下方向键选择功能</li>\n<li>聚合搜索新增音源显示</li>\n<li>新增“离开搜索界面时清空搜索列表”设置选项,默认关闭,可到设置-强迫症设置开启</li>\n</ul>\n<h3>优化</h3>\n<ul>\n<li>优化“信口雌黄”皮肤配色</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>修复存在弹出层时,搜索建议列表被弹出层覆盖的问题</li>\n<li>修复搜索、排行榜、歌单列表多选框从不定状态到选中的Bug</li>\n</ul>\n<h3>移除</h3>\n<ul>\n<li>因Q音接口失效移除Q音源的试听与下载</li>\n</ul>\n<h3>其他</h3>\n<ul>\n<li>更新electron到7.1.5</li>\n<li>更新vue到2.6.11</li>\n</ul>\n"
},
{
"version": "0.12.1",
"desc": "<h3>优化</h3>\n<ul>\n<li>优化定位歌曲时的列表滚动机制</li>\n<li>优化链接点击效果</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>修复使用酷我源下载歌曲时,当歌曲无封面时下载报错的问题</li>\n<li>修复酷我源排行榜、歌单详情列表里的歌曲音质匹配问题(原来无论歌曲有无高品、无损都会显示有)</li>\n<li>禁止外部链接在软件内打开,将所有外部链接从默认浏览器打开</li>\n</ul>\n<h3>其他</h3>\n<ul>\n<li>更新electron到7.1.2</li>\n</ul>\n"
},
{
"version": "0.12.0",
"desc": "<p>由于新下载库仍然没有完成但下载功能已经可用so 移除之前使用的第三方下载库,暂时把新下载库的下载模块直接加入本程序,若出现下载问题欢迎反馈!</p>\n<h3>新增</h3>\n<ul>\n<li>新增下载功能对代理设置的支持,现在若在软件设置了代理服务器,下载功能也将会走代理网络了</li>\n</ul>\n<h3>优化</h3>\n<ul>\n<li>新下载模块将对恢复下载的任务进行字节校验用于解决下载进度超过100%后仍然下载的问题</li>\n<li>注意:目前仍然无法暂停处于<strong>链接获取</strong>状态中的任务</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>修复Linux deb版本<code>.desktop</code>桌面文件缺少图标的问题,新增中文名称显示、软件分类,感谢@lowy的反馈</li>\n<li>修复下载列表歌曲状态分类列表操作Bug</li>\n<li>修复歌曲封面下载失败时仍然执行嵌入封面操作导致报错的问题</li>\n<li>跳过重复添加<strong>相同歌曲名与扩展名的歌曲</strong>例如你之前下载了A歌曲的128k音质现在想要下载它的320k音质但由于两者都是MP3格式会因为重名导致之前的128k音质被覆盖但列表中仍然显示两种音质的问题但实际上都是指向后面的320k音质</li>\n</ul>\n"
},
{
"version": "0.11.0",
"desc": "<h3>新增</h3>\n<ul>\n<li>新增歌曲缓冲定时器,尝试用于解决网络正常但是歌曲缓冲过久的问题</li>\n<li>新增下载管理的任务状态分类</li>\n<li>添加<strong>杀毒软件提示有病毒或恶意行为</strong>的说明,可到<strong>常见问题</strong>拉到最后查看(常见问题可在开源地址找到)</li>\n</ul>\n<h3>优化</h3>\n<ul>\n<li>优化更新弹窗机制及其内容描述,对于可以自动更新的版本,现在可以看到软件的下载进度了</li>\n</ul>\n"
},
{
"version": "0.10.0",
"desc": "<h4>优化</h4>\n<ul>\n<li>大幅减少程序<strong>播放时</strong>对CPU与GPU的使用经测试CPU使用减少60%以上GPU使用减少90%以上这应该能解决MAC系统上的温度上涨的问题</li>\n</ul>\n<h4>修复</h4>\n<ul>\n<li>修复酷我源<strong>搜索提示</strong>、<strong>排行榜</strong>无法获取的问题</li>\n<li>修复咪咕源无法播放的问题</li>\n</ul>\n"
},
{
"version": "0.9.1",
"desc": "<h4>修复</h4>\n<ul>\n<li>修复没有配置文件时程序启动出错的问题</li>\n</ul>\n"
},
{
"version": "0.9.0",
"desc": "<h4>新增</h4>\n<ul>\n<li>新增窗口大小设置,若觉得软件窗口小可以到设置页调大点</li>\n<li>新增定位当前播放歌曲,点击播放栏左侧的<strong>歌曲图片</strong>可在播放列表定位当前播放的歌曲(该功能对播放下载列表的歌曲无效)</li>\n</ul>\n<h4>修复</h4>\n<ul>\n<li>修复搜索提示失效的问题</li>\n<li>修复从歌单或列表点击搜索按钮搜索目标歌曲时,搜索框未聚焦仍然弹出候选搜索列表的问题</li>\n</ul>\n"
},
{
"version": "0.8.2",
"desc": "<h4>修复</h4>\n<ul>\n<li>兼容旧版酷我源搜索列表过滤128k音质的bug0.8.1版本仅修复了酷我源的歌曲过滤问题,该修复仅对以后添加的歌曲有效,如果是之前添加的歌曲仍会出现这个问题,现修复对之前旧列表数据的兼容处理)</li>\n</ul>\n"
},
{
"version": "0.8.1",
"desc": "<h4>修复</h4>\n<ul>\n<li>修复酷我源搜索歌曲结果未添加128k音质导致播放128k音质时显示“该歌曲没有可播放的音频”的问题</li>\n</ul>\n"
},
{
"version": "0.8.0",
"desc": "<h4>新增</h4>\n<ul>\n<li>新增网易云源歌曲搜索</li>\n<li>新增网易云源歌单</li>\n<li>新增各平台通过输入歌单链接或歌单ID打开歌单详情列表目前只适配了<strong>网页版歌单链接</strong>其他方式的歌单链接可能无法解析但你可想办法获取歌单ID后输入打开。注各平台歌单ID均为纯数字若遇到链接里存在歌单ID但无法解析的歌单链接可以到GitHub提交issue或发送邮件或加群830125506反馈</li>\n<li>新增音量调整滑动功能,现在支持鼠标左右拖动调整音量了</li>\n</ul>\n<h4>优化</h4>\n<ul>\n<li>优化搜索框搜索体验</li>\n<li>优化音量条交互视觉效果</li>\n<li>缓存歌单详情列表数据</li>\n</ul>\n<h4>修复</h4>\n<ul>\n<li>修复QQ源歌单无法翻页Bug</li>\n<li>修复默认列表没有创建时无法显示收藏列表的Bug</li>\n<li>修复网易云128k直接试听</li>\n<li>修复歌曲音质不存在时仍然播放或下载的Bug</li>\n<li>修复调整音量时,调整的位置与鼠标点击的位置不一致的问题</li>\n</ul>\n"
},
{
"version": "0.7.0",
"desc": "<h4>新增</h4>\n<ul>\n<li>新增“我的收藏”本地播放列表</li>\n<li>新增缓存清理功能,可到<strong>设置-其他</strong>查看与清理软件缓存</li>\n<li>新增QQ音乐源搜索</li>\n<li>新增咪咕源搜索</li>\n<li>新增咪咕源歌单</li>\n<li>新增咪咕源排行榜</li>\n<li>新增我的音乐列表歌曲源显示,默认关闭,可到<strong>设置-列表设置</strong>开启</li>\n</ul>\n<h4>优化</h4>\n<ul>\n<li>优化选择框动画效果</li>\n<li>尝试优化选我的音乐列表内容很多时多选的卡顿问题</li>\n</ul>\n<h4>修复</h4>\n<ul>\n<li>修复列表延迟显示的Bug</li>\n<li>修复QQ音源128k音质试听</li>\n</ul>\n"
},
{
"version": "0.6.2",
"desc": "<p>祝贺祖国成立70周年~</p>\n<h4>新增</h4>\n<ul>\n<li>新增QQ音乐源歌单</li>\n</ul>\n<h4>修复</h4>\n<ul>\n<li>修正火影皮肤名字</li>\n<li>修复当试听列表为空时无法切到其他界面的Bug</li>\n<li>修复百度源搜索结果为空时的接口处理Bug</li>\n<li>恢复<strong>酷狗</strong>其他音质播放</li>\n</ul>\n"
},
{
"version": "0.6.1",
"desc": "<h3>新增</h3>\n<ul>\n<li>新增试听列表<strong>滚动条位置恢复</strong>设置(可自动恢复到上次离开时的列表滚动位置),本功能默认开启,若不需要可到设置-列表设置将其关闭</li>\n<li>新增 <strong>《海贼王》</strong> 皮肤,喜欢个性化的可以试试~</li>\n</ul>\n<h3>优化</h3>\n<ul>\n<li>新增DNS解析缓存加快请求速度</li>\n<li>优化代码逻辑,减少软件对系统资源的占用</li>\n<li>优化新版本信息检测,尽量减少弹出版本获取失败弹窗弹出的概率</li>\n<li>优化下拉列表动画效果</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>修复请求超时的逻辑处理Bug尝试修复请求无法取消导致的正在播放的歌曲与界面显示的信息不一致的问题</li>\n<li>修复其他一些小Bug</li>\n</ul>\n<h3>移除</h3>\n<ul>\n<li>移除 <code>192k</code> 音质</li>\n<li>移除酷我音源 <code>ape</code> 音质,无损推荐 <code>flac</code> 格式</li>\n</ul>\n"
},
{
"version": "0.6.0",
"desc": "<h3>新增</h3>\n<ul>\n<li>新增音乐<strong>聚合搜索</strong>,目前支持酷我、酷狗、百度源搜索</li>\n<li>新增代理功能</li>\n</ul>\n<h3>优化</h3>\n<ul>\n<li>优化从《梦里嫦娥》皮肤切换到其他皮肤时侧栏动画的切换效果</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>修复试听列表没有歌曲时会显示列表加载中的Bug</li>\n<li>修复切换歌单列表详情时的UI Bug</li>\n</ul>\n"
},
{
"version": "0.5.5",
"desc": "<h3>新增</h3>\n<ul>\n<li>月是故乡明,祝大家中秋快乐🥮~~新增个性皮肤**《月里嫦娥》**,时间仓促,皮肤还不是很完善,可以试试喜不喜欢~😉</li>\n<li>新增 MAC 版本退出快捷键支持</li>\n<li>新增点击播放器中的歌曲标题可以复制标题的功能(遇到好听的歌曲方便分享)</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>修复 MAC 系统下软件关闭时再次从 dock 打开时报错的Bug</li>\n<li>修复下载的歌曲文件名中包含命名规则不允许的符号时下载失败的问题(若歌曲名包含这些符号会自动将其移除)</li>\n<li>修复 MAC 版本不能复制粘贴的问题</li>\n</ul>\n"

56
src/common/config.js Normal file
View File

@@ -0,0 +1,56 @@
module.exports = {
windowSizeList: [
{
id: 0,
name: 'smaller',
width: 828,
height: 530,
fontSize: '14px',
},
{
id: 1,
name: 'small',
width: 920,
height: 590,
fontSize: '16px',
},
{
id: 2,
name: 'medium',
width: 1012,
height: 650,
fontSize: '16px',
},
{
id: 3,
name: 'big',
width: 1104,
height: 708,
fontSize: '17px',
},
{
id: 4,
name: 'larger',
width: 1198,
height: 766,
fontSize: '18px',
},
{
id: 5,
name: 'oversized',
width: 1380,
height: 886,
fontSize: '18px',
},
{
id: 6,
name: 'huge',
width: 1656,
height: 1062,
fontSize: '19px',
},
],
navigationUrlWhiteList: [
/^https:\/\/www\.xiami\.com/,
],
}

12
src/common/error.js Normal file
View File

@@ -0,0 +1,12 @@
const { log } = require('./utils')
process.on('uncaughtException', function(err) {
console.error('An uncaught error occurred!')
console.error(err)
log.error(err)
})
process.on('unhandledRejection', (reason, p) => {
console.error('Unhandled Rejection at: Promise ', p)
console.error(' reason: ', reason)
log.error(reason)
})

View File

@@ -1,19 +0,0 @@
const { ipcMain, ipcRenderer } = require('electron')
export const mainSend = (name, params) => {
ipcMain.send(name, params)
}
export const mainOn = (name, callback) => {
ipcMain.on(name, callback)
}
export const rendererSend = (name, params) => {
ipcRenderer.send(name, params)
}
export const rendererOn = (name, callback) => {
ipcRenderer.on(name, callback)
}

31
src/common/ipc.js Normal file
View File

@@ -0,0 +1,31 @@
const { ipcMain, ipcRenderer } = require('electron')
exports.mainOn = (event, callback) => {
ipcMain.on(event, callback)
}
exports.mainOnce = (event, callback) => {
ipcMain.once(event, callback)
}
exports.mainHandle = (name, callback) => {
ipcMain.handle(name, callback)
}
exports.mainHandleOnce = (name, callback) => {
ipcMain.handleOnce(name, callback)
}
exports.rendererSend = (name, params) => {
ipcRenderer.send(name, params)
}
exports.rendererSendSync = (name, params) => ipcRenderer.sendSync(name, params)
exports.rendererInvoke = (name, params) => ipcRenderer.invoke(name, params)
exports.rendererOn = (name, callback) => {
ipcRenderer.on(name, callback)
}
exports.rendererOnce = (name, callback) => {
ipcRenderer.once(name, callback)
}

View File

@@ -10,6 +10,6 @@ html(lang="cn")
//- if htmlWebpackPlugin.options.isProd
//- script.
//- window.__static = '!{require('path').join(htmlWebpackPlugin.options.__dirname, '/static').replace(/\\/g, '\\\\')}'
if !htmlWebpackPlugin.options.browser && htmlWebpackPlugin.options.isProd
//- if !htmlWebpackPlugin.options.browser && htmlWebpackPlugin.options.isProd
script.
window.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
window.__static = require('path').join(__dirname, '/resources').replace(/\\/g, '\\\\')

5
src/main/events/_name.js Normal file
View File

@@ -0,0 +1,5 @@
exports.tray = {
create: 'create',
destroy: 'destroy',
}

View File

@@ -1,5 +1,5 @@
global.lx_event = {}
require('./request')
// require('./appName')
require('./musicMeta')
const Tray = require('./tray')
if (!global.lx_event.tray) global.lx_event.tray = new Tray()

View File

@@ -1,11 +0,0 @@
const { mainOn } = require('../../common/icp')
const { dialog } = require('electron')
module.exports = win => {
mainOn('selectPath', (event, params) => {
let path = dialog.showOpenDialog(win, params.options)
if (path === undefined) return
event.sender.send(params.eventName, path)
})
}

View File

@@ -1,22 +0,0 @@
// const { app } = require('electron')
const { mainOn } = require('../../common/icp')
mainOn('min', event => {
if (global.mainWindow) {
global.mainWindow.minimize()
}
})
// mainOn('max', event => {
// if (global.mainWindow) {
// global.mainWindow.maximize()
// }
// })
mainOn('close', event => {
if (global.mainWindow) {
// global.mainWindowdow.destroy()
// console.log('close')
// app.quit()
global.mainWindow.close()
}
})

14
src/main/events/tray.js Normal file
View File

@@ -0,0 +1,14 @@
const { EventEmitter } = require('events')
const { tray: TRAY_EVENT_NAME } = require('./_name')
class Tray extends EventEmitter {
create() {
this.emit(TRAY_EVENT_NAME.create)
}
destroy() {
this.emit(TRAY_EVENT_NAME.destroy)
}
}
module.exports = Tray

View File

@@ -1,4 +1,4 @@
const { app, BrowserWindow, Menu } = require('electron')
const { app, BrowserWindow, shell } = require('electron')
const path = require('path')
// 单例应用程序
@@ -7,33 +7,72 @@ if (!app.requestSingleInstanceLock()) {
return
}
app.on('second-instance', (event, argv, cwd) => {
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore()
mainWindow.focus()
if (global.mainWindow) {
if (global.mainWindow.isMinimized()) {
global.mainWindow.restore()
} else if (global.mainWindow.isVisible()) {
global.mainWindow.focus()
} else {
global.mainWindow.show()
}
} else {
app.quit()
}
})
const isDev = global.isDev = process.env.NODE_ENV !== 'production'
const { navigationUrlWhiteList } = require('../common/config')
app.on('web-contents-created', (event, contents) => {
contents.on('will-navigate', (event, navigationUrl) => {
if (isDev) return console.log('navigation to url:', navigationUrl)
if (!navigationUrlWhiteList.some(url => url.test(navigationUrl))) return event.preventDefault()
console.log('navigation to url:', navigationUrl)
})
contents.on('new-window', async(event, navigationUrl) => {
event.preventDefault()
if (/^devtools/.test(navigationUrl)) return
console.log(navigationUrl)
await shell.openExternal(navigationUrl)
})
contents.on('will-attach-webview', (event, webPreferences, params) => {
// Strip away preload scripts if unused or verify their location is legitimate
delete webPreferences.preload
delete webPreferences.preloadURL
// Disable Node.js integration
webPreferences.nodeIntegration = false
// Verify URL being loaded
if (!navigationUrlWhiteList.some(url => url.test(params.src))) {
event.preventDefault()
}
})
})
// 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('./events/progressBar')
require('./events/trafficLight')
require('./rendererEvents')
const winEvent = require('./rendererEvents/winEvent')
const autoUpdate = require('./utils/autoUpdate')
const { isLinux, isMac } = require('../common/utils')
const { isMac, isLinux } = require('../common/utils')
const isDev = process.env.NODE_ENV !== 'production'
/**
* Set `__static` path to static files in production
* https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html
*/
let mainWindow
let winURL
let isFirstCheckedUpdate = true
if (isDev) {
global.__static = path.join(__dirname, '../static')
// eslint-disable-next-line no-undef
global.__static = __static
winURL = 'http://localhost:9080'
} else {
global.__static = path.join(__dirname, '/static')
@@ -41,19 +80,22 @@ if (isDev) {
}
function createWindow() {
const windowSizeInfo = getWindowSizeInfo(global.appSetting)
/**
* Initial window options
*/
mainWindow = global.mainWindow = new BrowserWindow({
height: 590,
global.mainWindow = new BrowserWindow({
height: windowSizeInfo.height,
useContentSize: true,
width: 920,
width: windowSizeInfo.width,
frame: false,
transparent: !isLinux,
transparent: !isLinux && !global.envParams.nt,
enableRemoteModule: false,
// icon: path.join(global.__static, isWin ? 'icons/256x256.ico' : 'icons/512x512.png'),
resizable: false,
maximizable: false,
fullscreenable: false,
show: false,
webPreferences: {
// contextIsolation: true,
webSecurity: !isDev,
@@ -61,72 +103,42 @@ function createWindow() {
},
})
mainWindow.loadURL(winURL)
mainWindow.on('close', () => {
mainWindow.setProgressBar(-1)
})
mainWindow.on('closed', () => {
mainWindow = global.mainWindow = null
})
global.mainWindow.loadURL(winURL)
winEvent(global.mainWindow)
// mainWindow.webContents.openDevTools()
if (!isDev) {
autoUpdate(isFirstCheckedUpdate)
isFirstCheckedUpdate = false
}
if (!isDev) autoUpdate()
}
if (isMac) {
const template = [
{
label: app.getName(),
submenu: [
{ label: '关于洛雪音乐', role: 'about' },
{ type: 'separator' },
{ label: '隐藏', role: 'hide' },
{ label: '显示其他', role: 'hideothers' },
{ label: '显示全部', role: 'unhide' },
{ type: 'separator' },
{ label: '退出', accelerator: 'Command+Q', click: () => app.quit() },
],
},
{
label: '窗口',
role: 'window',
submenu: [
{ label: '最小化', role: 'minimize' },
{ label: '关闭', role: 'close' },
],
},
{
label: '编辑',
submenu: [
{ label: '撤销', accelerator: 'CmdOrCtrl+Z', role: 'undo' },
{ label: '恢复', accelerator: 'Shift+CmdOrCtrl+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' },
],
},
]
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
} else {
Menu.setApplicationMenu(null)
function init() {
global.appSetting = getAppSetting()
createWindow()
global.lx_event.tray.create()
}
app.on('ready', createWindow)
app.on('window-all-closed', () => {
if (!isMac) app.quit()
})
app.on('ready', init)
app.on('activate', () => {
if (mainWindow === null) {
createWindow()
if (global.mainWindow) {
if (global.mainWindow.isMinimized()) {
global.mainWindow.restore()
} else if (global.mainWindow.isVisible()) {
global.mainWindow.focus()
} else {
global.mainWindow.show()
}
} else if (global.mainWindow === null) {
init()
}
})
app.on('window-all-closed', () => {
if (isMac) {
global.lx_event.tray.destroy()
} else {
app.quit()
}
})
require('./modules')

View File

@@ -0,0 +1,50 @@
const { app, Menu } = require('electron')
const { isMac } = require('../../common/utils')
if (isMac) {
const template = [
{
label: app.getName(),
submenu: [
{ label: '关于洛雪音乐', role: 'about' },
{ type: 'separator' },
{ label: '隐藏', role: 'hide' },
{ label: '显示其他', role: 'hideothers' },
{ label: '显示全部', role: 'unhide' },
{ type: 'separator' },
{
label: '退出',
accelerator: 'Command+Q',
click() {
global.isQuitting = true
app.quit()
},
},
],
},
{
label: '窗口',
role: 'window',
submenu: [
{ label: '最小化', role: 'minimize' },
{ label: '关闭', role: 'close' },
],
},
{
label: '编辑',
submenu: [
{ label: '撤销', accelerator: 'CmdOrCtrl+Z', role: 'undo' },
{ label: '恢复', accelerator: 'Shift+CmdOrCtrl+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' },
],
},
]
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
} else {
Menu.setApplicationMenu(null)
}

View File

@@ -0,0 +1,2 @@
require('./appMenu')
require('./tray')

50
src/main/modules/tray.js Normal file
View File

@@ -0,0 +1,50 @@
const { app, Tray, Menu } = require('electron')
const { isWin } = require('../../common/utils')
const { tray: TRAY_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()
})
const createTray = () => {
if ((global.tray && !global.tray.isDestroyed()) || !global.appSetting.tray || !global.appSetting.tray.isShow) return
const iconPath = path.join(global.__static, 'images/tray', isWin ? 'trayTemplate@2x.ico' : 'trayTemplate.png')
// 托盘
global.tray = new Tray(iconPath)
global.tray.setToolTip('洛雪音乐助手')
if (isWin) createMenu(global.tray)
global.tray.setIgnoreDoubleClickEvents(true)
global.tray.on('click', () => {
const mainWindow = global.mainWindow
if (!mainWindow) return
mainWindow.isVisible()
? mainWindow.focus()
: mainWindow.show()
})
}
const destroyTray = () => {
if (!global.tray) return
global.tray.destroy()
global.tray = null
}
const createMenu = tray => {
const contextMenu = Menu.buildFromTemplate([
{
label: '退出',
click() {
global.isQuitting = true
app.quit()
},
},
])
tray.setContextMenu(contextMenu)
}

View File

@@ -1,4 +1,4 @@
const { mainOn } = require('../../common/icp')
const { mainOn } = require('../../common/ipc')
const { app } = require('electron')
const { name: defaultName } = require('../../../package.json')

View File

@@ -0,0 +1,8 @@
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,7 @@
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,6 @@
const { mainHandle } = require('../../common/ipc')
mainHandle('getEnvParams', async(event, options) => {
return global.envParams
})

View File

@@ -0,0 +1,17 @@
// require('./request')
// require('./appName')
require('./progressBar')
require('./trafficLight')
require('./musicMeta')
require('./selectDir')
require('./setWindowSize')
require('./showSaveDialog')
require('./clearCache')
require('./getCacheSize')
require('./setIgnoreMouseEvent')
require('./getEnvParams')
require('./tray')
require('./updateSetting')
require('./xm_verify')

View File

@@ -1,4 +1,4 @@
const { mainOn } = require('../../common/icp')
const { mainOn } = require('../../common/ipc')
const { setMeta } = require('../utils/musicMeta')
mainOn('setMusicMeta', (event, { filePath, meta }) => {

View File

@@ -1,4 +1,4 @@
const { mainOn } = require('../../common/icp')
const { mainOn } = require('../../common/ipc')
mainOn('progress', (event, params) => {

View File

@@ -1,13 +1,13 @@
const request = require('request')
const { mainOn } = require('../../common/icp')
const { mainOn } = require('../../common/ipc')
const tasks = []
mainOn('request', (event, options) => {
// console.log(args)
if (!options) return
let index = fatchData(options, (err, resp) => {
let index = fetchData(options, (err, resp) => {
tasks[index] = null
if (err) {
console.log(err)
@@ -27,7 +27,7 @@ mainOn('cancelRequest', (event, index) => {
tasks[index] = null
})
const fatchData = (options, callback) => pushTask(tasks, request(options.url, {
const fetchData = (options, callback) => pushTask(tasks, request(options.url, {
method: options.method,
headers: options.headers,
Origin: options.origin,

View File

@@ -0,0 +1,13 @@
const { mainOn } = require('../../common/ipc')
mainOn('restartWindow', (event, name) => {
console.log(name)
switch (name) {
case 'main':
default:
break
}
})

View File

@@ -0,0 +1,8 @@
const { mainHandle } = 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)
})

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
const { mainHandle } = 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)
})

View File

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

View File

@@ -0,0 +1,12 @@
// 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

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

View File

@@ -0,0 +1,28 @@
const { isMac } = require('../../common/utils')
global.isQuitting = false
global.isTrafficLightClose = false // 是否点击软件上的关闭按钮关闭
module.exports = mainWindow => {
mainWindow.on('close', event => {
if (global.isQuitting || !global.appSetting.tray.isToTray || (isMac && !global.isTrafficLightClose)) {
mainWindow.setProgressBar(-1)
return
}
if (global.isTrafficLightClose) global.isTrafficLightClose = false
event.preventDefault()
mainWindow.hide()
})
mainWindow.on('closed', () => {
mainWindow = global.mainWindow = null
})
// mainWindow.on('restore', () => {
// mainWindow.webContents.send('restore')
// })
mainWindow.once('ready-to-show', () => {
mainWindow.show()
})
}

View File

@@ -0,0 +1,4 @@
const { isMac } = require('../../../common/utils')
// mac下的 BrowserView 无法拖动验证栏,改用 BrowserWindow
require(isMac ? './xm_verify_win' : './xm_verify_view')

View File

@@ -0,0 +1,4 @@
const { isMac } = require('../../../common/utils')
// mac下的 BrowserView 无法拖动验证栏,改用 BrowserWindow
require(isMac ? './xm_verify_win' : './xm_verify_view')

View File

@@ -0,0 +1,66 @@
const { BrowserView } = require('electron')
const { mainHandle } = require('../../../common/ipc')
const { getWindowSizeInfo } = require('../../utils')
let view
let isActioned = false
let rejectFn
const closeView = async() => {
if (!view) return
// await view.webContents.session.clearCache()
if (global.mainWindow) global.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'))
if (view) {
global.mainWindow.removeBrowserView(view)
view.destroy()
}
rejectFn = reject
isActioned = false
view = new BrowserView({
webPreferences: {
enableRemoteModule: false,
disableHtmlFullscreenWindowResize: true,
},
})
view.webContents.on('did-finish-load', () => {
if (/punish\?/.test(view.webContents.getURL())) return
let ses = view.webContents.session
ses.cookies.get({ name: 'x5sec' })
.then(async([x5sec]) => {
isActioned = true
await closeView()
if (!x5sec) return reject(new Error('get x5sec failed'))
resolve(x5sec.value)
}).catch(async err => {
isActioned = true
await closeView()
reject(err)
})
})
global.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, {
httpReferrer: 'https://www.xiami.com/',
})
// view.webContents.openDevTools()
}))
mainHandle('xm_verify_close', async() => {
await closeView()
if (!rejectFn) return
if (!isActioned) rejectFn(new Error('canceled verify'))
rejectFn = null
})

View File

@@ -0,0 +1,77 @@
const { BrowserWindow } = require('electron')
const { mainHandle } = require('../../../common/ipc')
const { getWindowSizeInfo } = require('../../utils')
let win
const closeWin = async() => {
if (!win) return
// await win.webContents.session.clearCache()
// if (global.mainWindow) global.mainWindow.removeBrowserView(win)
if (win.isDestroyed()) {
win = null
return
}
await win.webContents.session.clearStorageData()
win.destroy()
win = null
}
mainHandle('xm_verify_open', (event, url) => new Promise((resolve, reject) => {
if (!global.mainWindow) return reject(new Error('mainwindow is undefined'))
if (win) win.destroy()
let isActioned = false
const mainWindowSizeInfo = global.mainWindow.getBounds()
const windowSizeInfo = getWindowSizeInfo(global.appSetting)
win = new BrowserWindow({
parent: global.mainWindow,
width: 460,
height: 370,
resizable: false,
// transparent: true,
x: mainWindowSizeInfo.x + (windowSizeInfo.width - 460) / 2,
y: mainWindowSizeInfo.y + (windowSizeInfo.height - 320 + 52) / 2,
minimizable: false,
maximizable: false,
// movable: false,
// frame: false,
// modal: true,
webPreferences: {
enableRemoteModule: false,
disableHtmlFullscreenWindowResize: true,
},
})
win.webContents.on('did-finish-load', () => {
if (/punish\?/.test(win.webContents.getURL())) return
let ses = win.webContents.session
ses.cookies.get({ name: 'x5sec' })
.then(async([x5sec]) => {
isActioned = true
await closeWin()
if (!x5sec) return reject(new Error('get x5sec failed'))
resolve(x5sec.value)
}).catch(async err => {
isActioned = true
await closeWin()
reject(err)
})
})
win.webContents.loadURL(url, {
httpReferrer: 'https://www.xiami.com/',
})
win.on('closed', async() => {
await closeWin()
if (isActioned) return
reject(new Error('canceled verify'))
})
// win.webContents.openDevTools()
}))
mainHandle('xm_verify_close', async() => {
await closeWin()
})

View File

@@ -1,10 +1,13 @@
const { log } = require('../../common/utils')
const { autoUpdater } = require('electron-updater')
const { mainOn } = require('../../common/icp')
const { mainOn } = require('../../common/ipc')
autoUpdater.logger = log
// autoUpdater.autoDownload = false
autoUpdater.logger.transports.file.level = 'info'
let isFirstCheckedUpdate = true
log.info('App starting...')
@@ -21,7 +24,7 @@ log.info('App starting...')
function sendStatusToWindow(text) {
log.info(text)
// global.mainWindow.webContents.send('message', text)
// ipcMain.send('message', text)
}
@@ -67,7 +70,7 @@ const handleSendEvent = action => {
}
}
module.exports = isFirstCheckedUpdate => {
module.exports = () => {
if (!isFirstCheckedUpdate) {
if (waitEvent.length) {
waitEvent.forEach((event, index) => {
@@ -79,6 +82,8 @@ module.exports = isFirstCheckedUpdate => {
}
return
}
isFirstCheckedUpdate = false
autoUpdater.on('checking-for-update', () => {
sendStatusToWindow('Checking for update...')
})
@@ -88,20 +93,26 @@ module.exports = isFirstCheckedUpdate => {
})
autoUpdater.on('update-not-available', info => {
sendStatusToWindow('Update not available.')
handleSendEvent({ type: 'update-not-available' })
handleSendEvent({ type: 'update-not-available', info })
})
autoUpdater.on('error', () => {
autoUpdater.on('error', err => {
sendStatusToWindow('Error in auto-updater.')
handleSendEvent({ type: 'update-error' })
handleSendEvent({ type: 'update-error', info: err.message })
})
autoUpdater.on('download-progress', progressObj => {
sendStatusToWindow('Download progress...')
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 })
})
autoUpdater.on('update-downloaded', info => {
sendStatusToWindow('Update downloaded.')
handleSendEvent({ type: 'update-downloaded' })
handleSendEvent({ type: 'update-downloaded', info })
})
mainOn('quit-update', () => {
global.isQuitting = true
setTimeout(() => {
autoUpdater.quitAndInstall(true, true)
}, 1000)

View File

@@ -0,0 +1,281 @@
// Fork from https://github.com/claus/flac-metadata
// 在 flac-metadata 的基础上修复与改进标签与封面写入逻辑
const Transform = require('stream').Transform
const MetaDataBlock = require('./lib/MetaDataBlock')
const MetaDataBlockStreamInfo = require('./lib/MetaDataBlockStreamInfo')
const MetaDataBlockVorbisComment = require('./lib/MetaDataBlockVorbisComment')
const MetaDataBlockPicture = require('./lib/MetaDataBlockPicture')
const STATE_IDLE = 0
const STATE_MARKER = 1
const STATE_MDB_HEADER = 2
const STATE_MDB = 3
const STATE_PASS_THROUGH = 4
class Processor extends Transform {
constructor(options) {
super(options)
// MDB types
this.MDB_TYPE_STREAMINFO = 0
this.MDB_TYPE_PADDING = 1
this.MDB_TYPE_APPLICATION = 2
this.MDB_TYPE_SEEKTABLE = 3
this.MDB_TYPE_VORBIS_COMMENT = 4
this.MDB_TYPE_CUESHEET = 5
this.MDB_TYPE_PICTURE = 6
this.MDB_TYPE_INVALID = 127
this.state = STATE_IDLE
this.isFlac = false
this.buf = null
this.bufPos = 0
this.mdb = null
this.mdbLen = 0
this.mdbLast = false
this.mdbPush = false
this.mdbLastWritten = false
this.parseMetaDataBlocks = false
this.waitWriteVorbis = null
this.waitWritePicture = null
this.tasks = 0
if (!(this instanceof Processor)) return new Processor(options)
if (options && !!options.parseMetaDataBlocks) { this.parseMetaDataBlocks = true }
}
writeMeta({ vorbis, picture }) {
if (vorbis != null) {
this.waitWriteVorbis = vorbis
this.tasks++
}
if (picture != null) {
this.waitWritePicture = picture
this.tasks++
}
}
// clearMeta() {
// this.mdbLastWritten = true
// }
readMeta(callback) {
this.parseMetaDataBlocks = true
this.readCallBack = callback
}
_transform(chunk, enc, done) {
let chunkPos = 0
let chunkLen = chunk.length
let isChunkProcessed = false
let _this = this
function _safePush(minCapacity, persist, validate) {
let slice
let chunkAvailable = chunkLen - chunkPos
let isDone = (chunkAvailable + this.bufPos >= minCapacity)
validate = (typeof validate === 'function') ? validate : function() { return true }
if (isDone) {
// Enough data available
if (persist) {
// Persist the entire block so it can be parsed
if (this.bufPos > 0) {
// Part of this block's data is in backup buffer, copy rest over
chunk.copy(this.buf, this.bufPos, chunkPos, chunkPos + minCapacity - this.bufPos)
slice = this.buf.slice(0, minCapacity)
} else {
// Entire block fits in current chunk
slice = chunk.slice(chunkPos, chunkPos + minCapacity)
}
} else {
slice = chunk.slice(chunkPos, chunkPos + minCapacity - this.bufPos)
}
// Push block after validation
validate(slice, isDone) && _this.push(slice)
chunkPos += minCapacity - this.bufPos
this.bufPos = 0
this.buf = null
} else {
// Not enough data available
if (persist) {
// Copy/append incomplete block to backup buffer
this.buf = this.buf || Buffer.alloc(minCapacity)
chunk.copy(this.buf, this.bufPos, chunkPos, chunkLen)
} else {
// Push incomplete block after validation
slice = chunk.slice(chunkPos, chunkLen)
validate(slice, isDone) && _this.push(slice)
}
this.bufPos += chunkLen - chunkPos
}
return isDone
};
let safePush = _safePush.bind(this)
while (!isChunkProcessed) {
switch (this.state) {
case STATE_IDLE:
this.state = STATE_MARKER
break
case STATE_MARKER:
if (safePush(4, true, this._validateMarker.bind(this))) {
this.state = this.isFlac ? STATE_MDB_HEADER : STATE_PASS_THROUGH
} else {
isChunkProcessed = true
}
break
case STATE_MDB_HEADER:
if (safePush(4, true, this._validateMDBHeader.bind(this))) {
this.state = STATE_MDB
} else {
isChunkProcessed = true
}
break
case STATE_MDB:
if (safePush(this.mdbLen, this.parseMetaDataBlocks, this._validateMDB.bind(this))) {
if (this.mdb.isLast) {
// This MDB has the isLast flag set to true.
// Ignore all following MDBs.
this.mdbLastWritten = true
}
this.readCallBack && this.readCallBack(this.mdb)
if (this.mdbLast) {
this._writeVorbisComment()
this._writePicture()
this.state = STATE_PASS_THROUGH
} else {
this.state = STATE_MDB_HEADER
}
} else {
isChunkProcessed = true
}
break
case STATE_PASS_THROUGH:
safePush(chunkLen - chunkPos, false)
isChunkProcessed = true
break
}
}
done()
}
_validateMarker(slice, isDone) {
this.isFlac = (slice.toString('utf8', 0) === 'fLaC')
// TODO: completely bail out if file is not a FLAC?
return true
}
_validateMDBHeader(slice, isDone) {
// Parse MDB header
let header = slice.readUInt32BE(0)
let type = (header >>> 24) & 0x7f
this.mdbLast = (((header >>> 24) & 0x80) !== 0)
this.mdbLen = header & 0xffffff
// Create appropriate MDB object
// (data is injected later in _validateMDB, if parseMetaDataBlocks option is set to true)
switch (type) {
case this.MDB_TYPE_STREAMINFO:
this.mdb = new MetaDataBlockStreamInfo(this.mdbLast)
break
case this.MDB_TYPE_VORBIS_COMMENT:
if (this.waitWriteVorbis) {
this._writeVorbisComment(slice, header)
this.mdbPush = false
return this.mdbPush
} else {
this.mdb = new MetaDataBlockVorbisComment(this.mdbLast)
this.readCallback && this.readCallback(this.mdb)
}
break
case this.MDB_TYPE_PICTURE:
if (this.waitWritePicture) {
this._writePicture(slice, header)
this.mdbPush = false
return this.mdbPush
} else {
this.mdb = new MetaDataBlockPicture(this.mdbLast)
this.readCallback && this.readCallback(this.mdb)
}
break
case this.MDB_TYPE_PADDING:
case this.MDB_TYPE_APPLICATION:
case this.MDB_TYPE_SEEKTABLE:
case this.MDB_TYPE_CUESHEET:
case this.MDB_TYPE_INVALID:
default:
this.mdb = new MetaDataBlock(this.mdbLast, type)
break
}
// this.emit('preprocess', this.mdb)
if (this.mdbLastWritten) {
// A previous MDB had the isLast flag set to true.
// Ignore all following MDBs.
this.mdb.remove()
} else {
if (this.mdbLast && this.tasks > 0) {
header &= 0x7fffffff
slice.writeUInt32BE(header >>> 0, 0)
}
// The consumer may change the MDB's isLast flag in the preprocess handler.
// Here that flag is updated in the MDB header.
}
this.mdbPush = !this.mdb.removed
return this.mdbPush
}
_validateMDB(slice, isDone) {
// Parse the MDB if parseMetaDataBlocks option is set to true
if (this.parseMetaDataBlocks && isDone) {
this.mdb.parse(slice)
}
return this.mdbPush
}
_flush(done) {
// All chunks have been processed
// Clean up
this.state = STATE_IDLE
this.mdbLastWritten = false
this.isFlac = false
this.bufPos = 0
this.buf = null
this.mdb = null
done()
}
_writeVorbisComment() {
if (this.waitWriteVorbis == null) return
let isLast = this.mdbLast && this.tasks === 1
this.tasks--
this.push(MetaDataBlockVorbisComment.create(isLast, this.waitWriteVorbis.vendor, this.waitWriteVorbis.comments).publish())
this.waitWriteVorbis = null
}
_writePicture() {
if (this.waitWritePicture == null) return
let isLast = this.mdbLast && this.tasks === 1
this.tasks--
this.push(
MetaDataBlockPicture.create(
isLast, this.waitWritePicture.pictureType,
this.waitWritePicture.mimeType, this.waitWritePicture.description,
this.waitWritePicture.width, this.waitWritePicture.height,
this.waitWritePicture.bitsPerPixel, this.waitWritePicture.colors,
this.waitWritePicture.pictureData,
).publish(),
)
this.waitWritePicture = null
}
}
module.exports = Processor

View File

@@ -0,0 +1,25 @@
class MetaDataBlock {
constructor(isLast, type) {
this.isLast = isLast
this.type = type
this.error = null
this.hasData = false
this.removed = false
}
remove() {
this.removed = true
}
parse(buffer) {
}
toString() {
let str = '[MetaDataBlock]'
str += ' type: ' + this.type
str += ', isLast: ' + this.isLast
return str
}
}
module.exports = MetaDataBlock

View File

@@ -0,0 +1,130 @@
const MetaDataBlock = require('./MetaDataBlock')
class MetaDataBlockPicture extends MetaDataBlock {
constructor(isLast) {
super(isLast, 6)
this.pictureType = 0
this.mimeType = ''
this.description = ''
this.width = 0
this.height = 0
this.bitsPerPixel = 0
this.colors = 0
this.pictureData = null
}
static create(isLast, pictureType, mimeType, description, width, height, bitsPerPixel, colors, pictureData) {
let mdb = new MetaDataBlockPicture(isLast)
mdb.pictureType = pictureType
mdb.mimeType = mimeType
mdb.description = description
mdb.width = width
mdb.height = height
mdb.bitsPerPixel = bitsPerPixel
mdb.colors = colors
mdb.pictureData = pictureData
mdb.hasData = true
return mdb
}
parse(buffer) {
try {
let pos = 0
this.pictureType = buffer.readUInt32BE(pos)
pos += 4
let mimeTypeLength = buffer.readUInt32BE(pos)
this.mimeType = buffer.toString('utf8', pos + 4, pos + 4 + mimeTypeLength)
pos += 4 + mimeTypeLength
let descriptionLength = buffer.readUInt32BE(pos)
this.description = buffer.toString('utf8', pos + 4, pos + 4 + descriptionLength)
pos += 4 + descriptionLength
this.width = buffer.readUInt32BE(pos)
this.height = buffer.readUInt32BE(pos + 4)
this.bitsPerPixel = buffer.readUInt32BE(pos + 8)
this.colors = buffer.readUInt32BE(pos + 12)
pos += 16
let pictureDataLength = buffer.readUInt32BE(pos)
this.pictureData = Buffer.alloc(pictureDataLength)
buffer.copy(this.pictureData, 0, pos + 4, pictureDataLength)
this.hasData = true
} catch (e) {
this.error = e
this.hasData = false
}
}
publish() {
let pos = 0
let size = this.getSize()
let buffer = Buffer.alloc(4 + size)
let header = size
header |= (this.type << 24)
header |= (this.isLast ? 0x80000000 : 0)
buffer.writeUInt32BE(header >>> 0, pos)
pos += 4
buffer.writeUInt32BE(this.pictureType, pos)
pos += 4
let mimeTypeLen = Buffer.byteLength(this.mimeType)
buffer.writeUInt32BE(mimeTypeLen, pos)
buffer.write(this.mimeType, pos + 4)
pos += 4 + mimeTypeLen
let descriptionLen = Buffer.byteLength(this.description)
buffer.writeUInt32BE(descriptionLen, pos)
buffer.write(this.description, pos + 4)
pos += 4 + descriptionLen
buffer.writeUInt32BE(this.width, pos)
buffer.writeUInt32BE(this.height, pos + 4)
buffer.writeUInt32BE(this.bitsPerPixel, pos + 8)
buffer.writeUInt32BE(this.colors, pos + 12)
pos += 16
buffer.writeUInt32BE(this.pictureData.length, pos)
this.pictureData.copy(buffer, pos + 4)
return buffer
}
getSize() {
let size = 4
size += 4 + Buffer.byteLength(this.mimeType)
size += 4 + Buffer.byteLength(this.description)
size += 16
size += 4 + this.pictureData.length
return size
}
toString() {
let str = '[MetaDataBlockPicture]'
str += ' type: ' + this.type
str += ', isLast: ' + this.isLast
if (this.error) {
str += '\n ERROR: ' + this.error
}
if (this.hasData) {
str += '\n pictureType: ' + this.pictureType
str += '\n mimeType: ' + this.mimeType
str += '\n description: ' + this.description
str += '\n width: ' + this.width
str += '\n height: ' + this.height
str += '\n bitsPerPixel: ' + this.bitsPerPixel
str += '\n colors: ' + this.colors
str += '\n pictureData: ' + (this.pictureData ? this.pictureData.length : '<null>')
}
return str
}
}
module.exports = MetaDataBlockPicture

View File

@@ -0,0 +1,84 @@
const MetaDataBlock = require('./MetaDataBlock')
function pad(n, width) {
n = '' + n
return (n.length >= width) ? n : new Array(width - n.length + 1).join('0') + n
}
class MetaDataBlockStreamInfo extends MetaDataBlock {
constructor(isLast) {
super(isLast, 0)
this.minBlockSize = 0
this.maxBlockSize = 0
this.minFrameSize = 0
this.maxFrameSize = 0
this.sampleRate = 0
this.channels = 0
this.bitsPerSample = 0
this.samples = 0
this.checksum = null
this.duration = 0
this.durationStr = '0:00.000'
}
remove() {
console.error("WARNING: Can't remove StreamInfo block!")
}
parse(buffer) {
try {
let pos = 0
this.minBlockSize = buffer.readUInt16BE(pos)
this.maxBlockSize = buffer.readUInt16BE(pos + 2)
this.minFrameSize = (buffer.readUInt8(pos + 4) << 16) | buffer.readUInt16BE(pos + 5)
this.maxFrameSize = (buffer.readUInt8(pos + 7) << 16) | buffer.readUInt16BE(pos + 8)
let tmp = buffer.readUInt32BE(pos + 10)
this.sampleRate = tmp >>> 12
this.channels = (tmp >>> 9) & 0x07
this.bitsPerSample = (tmp >>> 4) & 0x1f
this.samples = +((tmp & 0x0f) << 4) + buffer.readUInt32BE(pos + 14)
this.checksum = Buffer.alloc(16)
buffer.copy(this.checksum, 0, 18, 34)
this.duration = this.samples / this.sampleRate
let minutes = '' + Math.floor(this.duration / 60)
let seconds = pad(Math.floor(this.duration % 60), 2)
let milliseconds = pad(Math.round(((this.duration % 60) - Math.floor(this.duration % 60)) * 1000), 3)
this.durationStr = minutes + ':' + seconds + '.' + milliseconds
this.hasData = true
} catch (e) {
this.error = e
this.hasData = false
}
}
toString() {
let str = '[MetaDataBlockStreamInfo]'
str += ' type: ' + this.type
str += ', isLast: ' + this.isLast
if (this.error) {
str += '\n ERROR: ' + this.error
}
if (this.hasData) {
str += '\n minBlockSize: ' + this.minBlockSize
str += '\n maxBlockSize: ' + this.maxBlockSize
str += '\n minFrameSize: ' + this.minFrameSize
str += '\n maxFrameSize: ' + this.maxFrameSize
str += '\n samples: ' + this.samples
str += '\n sampleRate: ' + this.sampleRate
str += '\n channels: ' + (this.channels + 1)
str += '\n bitsPerSample: ' + (this.bitsPerSample + 1)
str += '\n duration: ' + this.durationStr
str += '\n checksum: ' + (this.checksum ? this.checksum.toString('hex') : '<null>')
}
return str
}
}
module.exports = MetaDataBlockStreamInfo

View File

@@ -0,0 +1,105 @@
const MetaDataBlock = require('./MetaDataBlock')
class MetaDataBlockVorbisComment extends MetaDataBlock {
constructor(isLast) {
super(isLast, 4)
this.vendor = ''
this.comments = []
}
static create(isLast, vendor, comments) {
let mdb = new MetaDataBlockVorbisComment(isLast)
mdb.vendor = vendor
mdb.comments = comments
mdb.hasData = true
return mdb
}
parse(buffer) {
try {
let pos = 0
let vendorLen = buffer.readUInt32LE(pos)
let vendor = buffer.toString('utf8', pos + 4, pos + 4 + vendorLen)
this.vendor = vendor
pos += 4 + vendorLen
let commentCount = buffer.readUInt32LE(pos)
pos += 4
while (commentCount-- > 0) {
let commentLen = buffer.readUInt32LE(pos)
let comment = buffer.toString('utf8', pos + 4, pos + 4 + commentLen)
this.comments.push(comment)
pos += 4 + commentLen
}
this.hasData = true
} catch (e) {
this.error = e
this.hasData = false
}
}
publish() {
let pos = 0
let size = this.getSize()
let buffer = Buffer.alloc(4 + size)
let header = size
header |= (this.type << 24)
header |= (this.isLast ? 0x80000000 : 0)
buffer.writeUInt32BE(header >>> 0, pos)
pos += 4
let vendorLen = Buffer.byteLength(this.vendor)
buffer.writeUInt32LE(vendorLen, pos)
buffer.write(this.vendor, pos + 4)
pos += 4 + vendorLen
let commentCount = this.comments.length
buffer.writeUInt32LE(commentCount, pos)
pos += 4
for (let i = 0; i < commentCount; i++) {
let comment = this.comments[i]
let commentLen = Buffer.byteLength(comment)
buffer.writeUInt32LE(commentLen, pos)
buffer.write(comment, pos + 4)
pos += 4 + commentLen
}
return buffer
}
getSize() {
let size = 8 + Buffer.byteLength(this.vendor)
for (let i = 0; i < this.comments.length; i++) {
size += 4 + Buffer.byteLength(this.comments[i])
}
return size
}
toString() {
let str = '[MetaDataBlockVorbisComment]'
str += ' type: ' + this.type
str += ', isLast: ' + this.isLast
if (this.error) {
str += '\n ERROR: ' + this.error
}
if (this.hasData) {
str += '\n vendor: ' + this.vendor
if (this.comments.length) {
str += '\n comments:'
for (let i = 0; i < this.comments.length; i++) {
str += '\n ' + this.comments[i].split('=').join(': ')
}
} else {
str += '\n comments: none'
}
}
return str
}
}
module.exports = MetaDataBlockVorbisComment

View File

@@ -1,38 +1,94 @@
const fs = require('fs')
const flac = require('flac-metadata')
const fsPromises = fs.promises
const path = require('path')
const getImgSize = require('image-size')
const request = require('request')
module.exports = (filenPath, meta) => {
const reader = fs.createReadStream(filenPath)
const tempPath = filenPath + '.lxmtemp'
const writer = fs.createWriteStream(tempPath)
const processor = new flac.Processor()
if (meta.APIC) delete meta.APIC
const FlacProcessor = require('./flac-metadata')
const comments = []
for (const key in meta) {
comments.push(`${key.toUpperCase()}=${meta[key]}`)
const extReg = /^(\.(?:jpe?g|png)).*$/
const vendor = 'reference libFLAC 1.2.1 20070917'
const writeMeta = async(filePath, meta, picPath) => {
const comments = Object.keys(meta).map(key => `${key.toUpperCase()}=${meta[key] || ''}`)
const data = {
vorbis: {
vendor,
comments,
},
}
const vendor = 'lx-music-desktop'
processor.on('preprocess', function(mdb) {
// Remove existing VORBIS_COMMENT block, if any.
if (mdb.type === flac.Processor.MDB_TYPE_VORBIS_COMMENT) {
mdb.remove()
if (picPath) {
const apicData = await fsPromises.readFile(picPath)
let imgSize = getImgSize(apicData)
let mime_type
let bitsPerPixel
if (apicData[0] == 0xff && apicData[1] == 0xd8 && apicData[2] == 0xff) {
mime_type = 'image/jpeg'
bitsPerPixel = 24
} else {
mime_type = 'image/png'
bitsPerPixel = 32
}
// Inject new VORBIS_COMMENT block.
if (mdb.removed || mdb.isLast) {
let mdbVorbis = flac.data.MetaDataBlockVorbisComment.create(mdb.isLast, vendor, comments)
this.push(mdbVorbis.publish())
data.picture = {
pictureType: 3,
mimeType: mime_type,
description: '',
width: imgSize.width,
height: imgSize.height,
bitsPerPixel,
colors: 0,
pictureData: apicData,
}
})
}
reader.pipe(processor).pipe(writer).on('finish', () => {
fs.unlink(filenPath, err => {
const reader = fs.createReadStream(filePath)
const tempPath = filePath + '.lxmtemp'
const writer = fs.createWriteStream(tempPath)
const flacProcessor = new FlacProcessor()
flacProcessor.writeMeta(data)
reader.pipe(flacProcessor).pipe(writer).on('finish', () => {
fs.unlink(filePath, err => {
if (err) return console.log(err.message)
fs.rename(tempPath, filenPath, err => {
fs.rename(tempPath, filePath, err => {
if (err) console.log(err.message)
})
})
})
}
module.exports = (filePath, meta) => {
if (!meta.APIC) return writeMeta(filePath, meta)
const picUrl = meta.APIC
delete meta.APIC
if (!/^http/.test(picUrl)) {
return writeMeta(filePath, meta)
}
let picPath = filePath.replace(/\.flac$/, '') + path.extname(picUrl).replace(extReg, '$1')
request(picUrl)
.on('response', respones => {
if (respones.statusCode !== 200 && respones.statusCode != 206) return writeMeta(filePath, meta)
respones
.pipe(fs.createWriteStream(picPath))
.on('finish', () => {
if (respones.complete) {
writeMeta(filePath, meta, picPath)
} else {
writeMeta(filePath, meta)
}
fs.unlink(picPath, err => {
if (err) console.log(err.message)
})
})
.on('error', err => {
if (err) console.log(err.message)
writeMeta(filePath, meta)
})
})
.on('error', err => {
if (err) console.log(err.message)
writeMeta(filePath, meta)
})
}

35
src/main/utils/index.js Normal file
View File

@@ -0,0 +1,35 @@
const Store = require('electron-store')
const { windowSizeList } = require('../../common/config')
exports.getWindowSizeInfo = ({ windowSizeId = 1 } = {}) => {
return windowSizeList.find(i => i.id === windowSizeId) || windowSizeList[0]
}
exports.getAppSetting = () => {
let electronStore = new Store()
const defaultSetting = {
windowSizeId: 1,
tray: {
isShow: false,
isToTray: false,
},
}
return Object.assign(defaultSetting, electronStore.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)
}
}
return params
}

View File

@@ -6,12 +6,40 @@ const extReg = /^(\.(?:jpe?g|png)).*$/
module.exports = (filePath, meta) => {
if (!meta.APIC) return NodeID3.write(meta, filePath)
if (!/^http/.test(meta.APIC)) {
delete meta.APIC
return NodeID3.write(meta, filePath)
}
let picPath = filePath.replace(/\.mp3$/, '') + path.extname(meta.APIC).replace(extReg, '$1')
request(meta.APIC).pipe(fs.createWriteStream(picPath)).on('finish', () => {
meta.APIC = picPath
NodeID3.write(meta, filePath)
fs.unlink(picPath, err => {
if (err) console.log(err.message)
request(meta.APIC)
.on('response', respones => {
if (respones.statusCode !== 200 && respones.statusCode != 206) {
delete meta.APIC
NodeID3.write(meta, filePath)
return
}
respones
.pipe(fs.createWriteStream(picPath))
.on('finish', () => {
if (respones.complete) {
meta.APIC = picPath
NodeID3.write(meta, filePath)
} else {
delete meta.APIC
NodeID3.write(meta, filePath)
}
fs.unlink(picPath, err => {
if (err) console.log(err.message)
})
}).on('error', err => {
if (err) console.log(err.message)
delete meta.APIC
NodeID3.write(meta, filePath)
})
})
.on('error', err => {
if (err) console.log(err.message)
delete meta.APIC
NodeID3.write(meta, filePath)
})
})
}

View File

@@ -1,149 +1,219 @@
<template lang="pug">
#container(v-if="isProd && !isLinux" :class="theme" @mouseenter="enableIgnoreMouseEvents" @mouseleave="dieableIgnoreMouseEvents")
#container(v-if="isProd && !isNt" :class="[theme, nd ? 'nd' : '']" @mouseenter="enableIgnoreMouseEvents" @mouseleave="dieableIgnoreMouseEvents")
core-aside#left
#right
core-toolbar#toolbar
core-view#view
core-player#player
core-icons
material-xm-verify-modal(v-show="globalObj.xm.isShowVerify" :show="globalObj.xm.isShowVerify" :bg-close="false" @close="handleXMVerifyModalClose")
material-version-modal(v-show="version.showModal")
#container(v-else :class="theme")
material-pact-modal(v-show="!setting.isAgreePact || globalObj.isShowPact")
#container(v-else :class="[theme, nd ? 'nd' : '']")
core-aside#left
#right
core-toolbar#toolbar
core-view#view
core-player#player
core-icons
material-xm-verify-modal(v-show="globalObj.xm.isShowVerify" :show="globalObj.xm.isShowVerify" :bg-close="false" @close="handleXMVerifyModalClose")
material-version-modal(v-show="version.showModal")
material-pact-modal(v-show="!setting.isAgreePact || globalObj.isShowPact")
</template>
<script>
import dnscache from 'dnscache'
import { mapMutations, mapGetters, mapActions } from 'vuex'
import { rendererOn } from '../common/icp'
import { rendererOn, rendererSend, rendererInvoke } from '../common/ipc'
import { isLinux } from '../common/utils'
import music from './utils/music'
import { throttle, openUrl } from './utils'
window.ELECTRON_DISABLE_SECURITY_WARNINGS = process.env.ELECTRON_DISABLE_SECURITY_WARNINGS
let win
let body
if (!isLinux) {
win = require('electron').remote.getCurrentWindow()
body = document.body
}
dnscache({
enable: true,
ttl: 21600,
cachesize: 1000,
})
export default {
data() {
return {
isProd: process.env.NODE_ENV === 'production',
isLinux,
isNt: false,
globalObj: {
apiSource: 'test',
proxy: {
proxy: {},
isShowPact: false,
qualityList: {},
xm: {
isShowVerify: false,
},
},
updateTimeout: null,
envParams: {
nt: false,
},
}
},
computed: {
...mapGetters(['electronStore', 'setting', 'theme', 'version']),
...mapGetters('list', ['defaultList']),
...mapGetters('player', ['isShowPlayerDetail']),
...mapGetters(['setting', 'theme', 'version', 'windowSizeActive']),
...mapGetters('list', ['defaultList', 'loveList']),
...mapGetters('download', {
downloadList: 'list',
downloadStatus: 'downloadStatus',
}),
...mapGetters('search', {
searchHistoryList: 'historyList',
}),
nd() {
return this.isShowPlayerDetail
},
},
created() {
this.saveSetting = throttle(n => {
window.electronStore_config.set('setting', n)
rendererSend('updateAppSetting', n)
})
this.saveDefaultList = throttle(n => {
window.electronStore_list.set('defaultList', n)
}, 500)
this.saveLoveList = throttle(n => {
window.electronStore_list.set('loveList', n)
}, 500)
this.saveDownloadList = throttle(n => {
window.electronStore_list.set('downloadList', n)
}, 1000)
this.saveSearchHistoryList = throttle(n => {
window.electronStore_data.set('searchHistoryList', n)
}, 500)
},
mounted() {
document.body.classList.add(this.isLinux ? 'noTransparent' : 'transparent')
document.body.classList.add(this.isNt ? 'noTransparent' : 'transparent')
window.eventHub.$emit('bindKey')
this.init()
},
watch: {
setting: {
handler(n) {
this.electronStore.set('setting', n)
this.saveSetting(n)
},
deep: true,
},
defaultList: {
handler(n) {
// console.log(n)
this.electronStore.set('list.defaultList', n)
this.saveDefaultList(n)
},
deep: true,
},
loveList: {
handler(n) {
this.saveLoveList(n)
},
deep: true,
},
downloadList: {
handler(n) {
this.electronStore.set('download.list', n)
this.saveDownloadList(n)
},
deep: true,
},
searchHistoryList(n) {
this.saveSearchHistoryList(n)
},
'globalObj.apiSource'(n) {
this.globalObj.qualityList = music.supportQuality[n]
if (n != this.setting.apiSource) {
this.setSetting(Object.assign({}, this.setting, {
apiSource: n,
}))
}
},
'windowSizeActive.fontSize'(n) {
document.documentElement.style.fontSize = n
},
},
methods: {
...mapActions(['getVersionInfo']),
...mapMutations(['setNewVersion', 'setVersionModalVisible']),
...mapMutations('list', ['initDefaultList']),
...mapMutations(['setNewVersion', 'setVersionModalVisible', 'setDownloadProgress', 'setSetting']),
...mapMutations('list', ['initList']),
...mapMutations('download', ['updateDownloadList']),
...mapMutations(['setSetting']),
init() {
if (this.isProd && !isLinux) {
body.addEventListener('mouseenter', this.dieableIgnoreMouseEvents)
body.addEventListener('mouseleave', this.enableIgnoreMouseEvents)
}
document.documentElement.style.fontSize = this.windowSizeActive.fontSize
rendererInvoke('getEnvParams').then(this.handleEnvParamsInit)
document.body.addEventListener('click', this.handleBodyClick, true)
rendererOn('update-available', (e, info) => {
// this.showUpdateModal(true)
this.setNewVersion({
// console.log(info)
this.setVersionModalVisible({ isDownloading: true })
this.getVersionInfo().catch(() => ({
version: info.version,
desc: info.releaseNotes,
})).then(body => {
// console.log(body)
this.setNewVersion(body)
this.$nextTick(() => {
this.setVersionModalVisible({ isShow: true })
})
})
})
rendererOn('update-error', () => {
if (!this.updateTimeout) return
this.setVersionModalVisible({ isError: true })
rendererOn('update-error', (event, err) => {
// console.log(err)
this.clearUpdateTimeout()
this.setVersionModalVisible({ isError: true })
this.$nextTick(() => {
this.showUpdateModal()
})
})
rendererOn('update-downloaded', () => {
this.clearUpdateTimeout()
this.setVersionModalVisible({ isError: false })
this.showUpdateModal()
rendererOn('update-progress', (event, progress) => {
// console.log(progress)
this.setDownloadProgress(progress)
})
rendererOn('update-not-available', () => {
if (!this.updateTimeout) return
if (this.setting.ignoreVersion) this.setSetting(Object.assign({}, this.setting, { ignoreVersion: null }))
rendererOn('update-downloaded', info => {
// console.log(info)
this.clearUpdateTimeout()
this.setVersionModalVisible({ isDownloaded: true })
this.$nextTick(() => {
this.showUpdateModal()
})
})
rendererOn('update-not-available', (e, info) => {
this.clearUpdateTimeout()
this.setNewVersion({
version: this.version.version,
version: info.version,
desc: info.releaseNotes,
})
this.setVersionModalVisible({ isLatestVer: true })
})
// 更新超时定时器
this.updateTimeout = setTimeout(() => {
this.updateTimeout = null
this.setVersionModalVisible({ isError: true })
this.setVersionModalVisible({ isTimeOut: true })
this.$nextTick(() => {
this.showUpdateModal()
})
}, 180000)
}, 60 * 30 * 1000)
this.initData()
this.globalObj.apiSource = this.setting.apiSource
this.globalObj.qualityList = music.supportQuality[this.setting.apiSource]
this.globalObj.proxy = Object.assign({}, this.setting.network.proxy)
window.globalObj = this.globalObj
// 初始化音乐sdk
music.init()
},
enableIgnoreMouseEvents() {
if (isLinux) return
win.setIgnoreMouseEvents(false)
if (this.isNt) return
rendererSend('setIgnoreMouseEvents', false)
// console.log('content enable')
},
dieableIgnoreMouseEvents() {
if (isLinux) return
if (this.isNt) return
// console.log('content disable')
win.setIgnoreMouseEvents(true, { forward: true })
rendererSend('setIgnoreMouseEvents', true)
},
initData() { // 初始化数据
@@ -151,17 +221,12 @@ export default {
this.initDownloadList() // 初始化下载列表
},
initPlayList() {
let defaultList = this.electronStore.get('list.defaultList')
// console.log(defaultList)
if (defaultList) {
// defaultList.list.forEach(m => {
// m.typeUrl = {}
// })
this.initDefaultList(defaultList)
}
let defaultList = window.electronStore_list.get('defaultList')
let loveList = window.electronStore_list.get('loveList')
this.initList({ defaultList, loveList })
},
initDownloadList() {
let downloadList = this.electronStore.get('download.list')
let downloadList = window.electronStore_list.get('downloadList')
if (downloadList) {
downloadList.forEach(item => {
if (item.status == this.downloadStatus.RUN || item.status == this.downloadStatus.WAITING) {
@@ -173,14 +238,31 @@ export default {
}
},
showUpdateModal() {
(this.version.newVersion && this.version.newVersion.history ? Promise.resolve(this.version.newVersion) : this.getVersionInfo().then(body => {
this.setNewVersion(body)
if (body.version !== this.setting.ignoreVersion) this.setSetting(Object.assign({}, this.setting, { ignoreVersion: null }))
return body
})).then(body => {
if (body.version === this.version.version) return
if (this.version.isError && body.version === this.setting.ignoreVersion) return
(this.version.newVersion && this.version.newVersion.history
? Promise.resolve(this.version.newVersion)
: this.getVersionInfo().then(body => {
this.setNewVersion(body)
return body
})
).catch(() => {
if (this.version.newVersion) return this.version.newVersion
this.setVersionModalVisible({ isUnknow: true })
let result = {
version: '0.0.0',
desc: null,
}
this.setNewVersion(result)
return result
}).then(result => {
let newVer = result.version.replace(/\./g, '')
let currentVer = this.version.version.replace(/\./g, '')
let len = Math.max(newVer.length, currentVer.length)
newVer.padStart(len, '0')
currentVer.padStart(len, '0')
if (parseInt(newVer) <= parseInt(currentVer)) return this.setVersionModalVisible({ isLatestVer: true })
if (result.version === this.setting.ignoreVersion) return
// console.log(this.version)
this.$nextTick(() => {
this.setVersionModalVisible({ isShow: true })
})
@@ -191,13 +273,45 @@ export default {
clearTimeout(this.updateTimeout)
this.updateTimeout = null
},
handleBodyClick(event) {
if (event.target.tagName != 'A') return
if (event.target.host == window.location.host) return
event.preventDefault()
if (/^https?:\/\//.test(event.target.href)) openUrl(event.target.href)
},
handleEnvParamsInit(envParams) {
this.envParams = envParams
this.isNt = isLinux || this.envParams.nt
if (this.isNt) {
document.body.classList.remove('transparent')
document.body.classList.add('noTransparent')
}
if (this.isProd && !this.isNt) {
document.body.addEventListener('mouseenter', this.dieableIgnoreMouseEvents)
document.body.addEventListener('mouseleave', this.enableIgnoreMouseEvents)
}
if (this.envParams.search != null) {
this.$router.push({
path: 'search',
query: {
text: this.envParams.search,
},
})
}
},
handleXMVerifyModalClose() {
music.xm.closeVerifyModal()
},
},
beforeDestroy() {
this.clearUpdateTimeout()
if (this.isProd) {
body.removeEventListener('mouseenter', this.dieableIgnoreMouseEvents)
body.removeEventListener('mouseleave', this.enableIgnoreMouseEvents)
document.body.removeEventListener('mouseenter', this.dieableIgnoreMouseEvents)
document.body.removeEventListener('mouseleave', this.enableIgnoreMouseEvents)
}
document.body.removeEventListener('click', this.handleBodyClick)
window.eventHub.$emit('unbindKey')
},
}
</script>
@@ -229,8 +343,8 @@ body {
display: flex;
height: 100%;
overflow: hidden;
background: @color-theme-bgimg center center no-repeat;
background-size: auto 100%;
background: @color-theme-bgimg @color-theme-bgposition no-repeat;
background-size: @color-theme-bgsize;
transition: background-color @transition-theme;
background-color: @color-theme;
}
@@ -258,6 +372,8 @@ each(@themes, {
#container.@{value} {
background-color: ~'@{color-@{value}-theme}';
background-image: ~'@{color-@{value}-theme-bgimg}';
background-size: ~'@{color-@{value}-theme-bgsize}';
background-position: ~'@{color-@{value}-theme-bgposition}';
#right {
background-color: ~'@{color-@{value}-theme_2}';
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

View File

@@ -1,3 +1,321 @@
// https://daneden.github.io/animate.css/
@keyframes bounce {
from,
20%,
53%,
80%,
to {
-webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
40%,
43% {
-webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
-webkit-transform: translate3d(0, -30px, 0);
transform: translate3d(0, -30px, 0);
}
70% {
-webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
-webkit-transform: translate3d(0, -15px, 0);
transform: translate3d(0, -15px, 0);
}
90% {
-webkit-transform: translate3d(0, -4px, 0);
transform: translate3d(0, -4px, 0);
}
}
@keyframes flash {
from,
50%,
to {
opacity: 1;
}
25%,
75% {
opacity: 0;
}
}
/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */
@keyframes pulse {
from {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
50% {
-webkit-transform: scale3d(1.05, 1.05, 1.05);
transform: scale3d(1.05, 1.05, 1.05);
}
to {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
@keyframes rubberBand {
from {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
30% {
-webkit-transform: scale3d(1.25, 0.75, 1);
transform: scale3d(1.25, 0.75, 1);
}
40% {
-webkit-transform: scale3d(0.75, 1.25, 1);
transform: scale3d(0.75, 1.25, 1);
}
50% {
-webkit-transform: scale3d(1.15, 0.85, 1);
transform: scale3d(1.15, 0.85, 1);
}
65% {
-webkit-transform: scale3d(0.95, 1.05, 1);
transform: scale3d(0.95, 1.05, 1);
}
75% {
-webkit-transform: scale3d(1.05, 0.95, 1);
transform: scale3d(1.05, 0.95, 1);
}
to {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
@keyframes shake {
from,
to {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
10%,
30%,
50%,
70%,
90% {
-webkit-transform: translate3d(-10px, 0, 0);
transform: translate3d(-10px, 0, 0);
}
20%,
40%,
60%,
80% {
-webkit-transform: translate3d(10px, 0, 0);
transform: translate3d(10px, 0, 0);
}
}
@keyframes headShake {
0% {
-webkit-transform: translateX(0);
transform: translateX(0);
}
6.5% {
-webkit-transform: translateX(-6px) rotateY(-9deg);
transform: translateX(-6px) rotateY(-9deg);
}
18.5% {
-webkit-transform: translateX(5px) rotateY(7deg);
transform: translateX(5px) rotateY(7deg);
}
31.5% {
-webkit-transform: translateX(-3px) rotateY(-5deg);
transform: translateX(-3px) rotateY(-5deg);
}
43.5% {
-webkit-transform: translateX(2px) rotateY(3deg);
transform: translateX(2px) rotateY(3deg);
}
50% {
-webkit-transform: translateX(0);
transform: translateX(0);
}
}
@keyframes swing {
20% {
-webkit-transform: rotate3d(0, 0, 1, 15deg);
transform: rotate3d(0, 0, 1, 15deg);
}
40% {
-webkit-transform: rotate3d(0, 0, 1, -10deg);
transform: rotate3d(0, 0, 1, -10deg);
}
60% {
-webkit-transform: rotate3d(0, 0, 1, 5deg);
transform: rotate3d(0, 0, 1, 5deg);
}
80% {
-webkit-transform: rotate3d(0, 0, 1, -5deg);
transform: rotate3d(0, 0, 1, -5deg);
}
to {
-webkit-transform: rotate3d(0, 0, 1, 0deg);
transform: rotate3d(0, 0, 1, 0deg);
}
}
@keyframes tada {
from {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
10%,
20% {
-webkit-transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg);
transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg);
}
30%,
50%,
70%,
90% {
-webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg);
transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg);
}
40%,
60%,
80% {
-webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg);
transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg);
}
to {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */
@keyframes wobble {
from {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
15% {
-webkit-transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg);
transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg);
}
30% {
-webkit-transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg);
transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg);
}
45% {
-webkit-transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg);
transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg);
}
60% {
-webkit-transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg);
transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg);
}
75% {
-webkit-transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg);
transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg);
}
to {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
}
@keyframes jello {
from,
11.1%,
to {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
22.2% {
-webkit-transform: skewX(-12.5deg) skewY(-12.5deg);
transform: skewX(-12.5deg) skewY(-12.5deg);
}
33.3% {
-webkit-transform: skewX(6.25deg) skewY(6.25deg);
transform: skewX(6.25deg) skewY(6.25deg);
}
44.4% {
-webkit-transform: skewX(-3.125deg) skewY(-3.125deg);
transform: skewX(-3.125deg) skewY(-3.125deg);
}
55.5% {
-webkit-transform: skewX(1.5625deg) skewY(1.5625deg);
transform: skewX(1.5625deg) skewY(1.5625deg);
}
66.6% {
-webkit-transform: skewX(-0.78125deg) skewY(-0.78125deg);
transform: skewX(-0.78125deg) skewY(-0.78125deg);
}
77.7% {
-webkit-transform: skewX(0.390625deg) skewY(0.390625deg);
transform: skewX(0.390625deg) skewY(0.390625deg);
}
88.8% {
-webkit-transform: skewX(-0.1953125deg) skewY(-0.1953125deg);
transform: skewX(-0.1953125deg) skewY(-0.1953125deg);
}
}
@keyframes heartBeat {
0% {
-webkit-transform: scale(1);
transform: scale(1);
}
14% {
-webkit-transform: scale(1.3);
transform: scale(1.3);
}
28% {
-webkit-transform: scale(1);
transform: scale(1);
}
42% {
-webkit-transform: scale(1.3);
transform: scale(1.3);
}
70% {
-webkit-transform: scale(1);
transform: scale(1);
}
}
@keyframes flipInX {
from {
@@ -533,8 +851,26 @@
transform: translate3d(0, -100%, 0);
}
}
@keyframes jackInTheBox {
from {
opacity: 0;
transform: scale(0.1) rotate(30deg);
transform-origin: center bottom;
}
50% {
transform: rotate(-10deg);
}
70% {
transform: rotate(3deg);
}
to {
opacity: 1;
transform: scale(1);
}
}
.flipInX {
backface-visibility: visible !important;
@@ -600,6 +936,11 @@
.slideInUp {
animation-name: slideInUp;
}
.jackInTheBox {
-webkit-animation-name: jackInTheBox;
animation-name: jackInTheBox;
}
.flipOutX {
@@ -673,6 +1014,47 @@
animation-name: slideOutUp;
}
.bounce {
animation-name: bounce;
transform-origin: center bottom;
}
.flash {
animation-name: flash;
}
.pulse {
animation-name: pulse;
}
.rubberBand {
animation-name: rubberBand;
}
.shake {
animation-name: shake;
}
.headShake {
animation-timing-function: ease-in-out;
animation-name: headShake;
}
.swing {
transform-origin: top center;
animation-name: swing;
}
.tada {
animation-name: tada;
}
.wobble {
animation-name: wobble;
}
.jello {
animation-name: jello;
transform-origin: center;
}
.heartBeat {
animation-name: heartBeat;
animation-duration: 1.3s;
animation-timing-function: ease-in-out;
}
.animated {
animation-duration: 0.5s;
animation-fill-mode: both;

View File

@@ -4,6 +4,10 @@
-webkit-user-drag: none;
}
html {
font-size: 16px;
}
.nobreak {
white-space: nowrap;
}
@@ -20,12 +24,19 @@
word-break: break-all;
}
.select {
user-select: text;
}
.no-select {
user-select: none;
}
table {
width: 100%;
border-spacing: 0;
border-collapse: collapse;
overflow: hidden;
color: rgba(0, 0, 0, 0.87);
color: rgba(0, 0, 0, 0.95);
th {
font-size: 12px;
text-align: left;
@@ -42,21 +53,28 @@ table {
&:hover {
background-color: @color-theme_2-hover;
}
&.active {
background-color: @color-theme_2-active;
}
&:first-child {
border-top: none;
}
td {
padding: 5px;
padding: 6px;
position: relative;
transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
font-size: 13px;
line-height: 1.3;
line-height: 16px;
vertical-align: middle;
}
}
}
}
a {
color: #000;
}
.badge {
display: inline-block;
padding: 0.25em 0.4em;
@@ -91,6 +109,14 @@ table {
color: #fff;
background-color: #32bc63;
}
&.badge-theme-success {
color: @color-badge-success-font;
background-color: @color-badge-success;
}
&.badge-theme-info {
color: @color-badge-info-font;
background-color: @color-badge-info;
}
}
small {
@@ -112,7 +138,17 @@ svg {
transition-property: fill;
}
.hover {
input, textarea {
&::placeholder {
color: @color-theme_2-font-label;
}
}
::selection {
color: @color-theme_2;
background: @color-btn;
}
.hover, a {
cursor: pointer;
transition: color .2s ease;
&:hover {
@@ -124,6 +160,7 @@ svg {
}
.scroll {
overflow: auto;
&::-webkit-scrollbar {
width: 6px;
height: 6px;
@@ -150,7 +187,7 @@ svg {
each(@themes, {
#container.@{value} {
.hover {
.hover, a {
&:hover {
color: ~'@{color-@{value}-theme}';
}
@@ -179,8 +216,32 @@ each(@themes, {
&:hover {
background-color: ~'@{color-@{value}-theme_2-hover}';
}
&.active {
background-color: ~'@{color-@{value}-theme_2-active}';
}
}
}
}
input, textarea {
&::placeholder {
color: ~'@{color-@{value}-theme_2-font-label}';
}
}
::selection {
color: ~'@{color-@{value}-theme_2}';
background: ~'@{color-@{value}-btn}';
}
.badge {
&.badge-theme-success {
color: ~'@{color-@{value}-badge-success-font}';
background-color: ~'@{color-@{value}-badge-success}';
}
&.badge-theme-info {
color: ~'@{color-@{value}-badge-info-font}';
background-color: ~'@{color-@{value}-badge-info}';
}
}
}
})

View File

@@ -1,26 +1,29 @@
@import './colors.less';
@themes: green, yellow, blue, red, purple, orange, grey, midAutumn;
@themes: green, yellow, blue, red, pink, purple, orange, grey, ming, mid_autumn, naruto, happy_new_year;
// Colors
// @color-theme: #03a678;
@color-theme: #4daf7c;
@color-theme-bgimg: none;
@color-theme-bgposition: center;
@color-theme-bgsize: auto auto;
@color-theme-hover: fadeout(lighten(@color-theme, 10%), 30%);
@color-theme-active: fadeout(darken(@color-theme, 20%), 60%);
@color-theme-font: #fff;
@color-theme-font-label: lighten(@color-theme, 35%);
@color-theme_2: rgba(255, 255, 255, .9);
@color-theme_2-background_2: #fff;
@color-theme_2-hover: fadeout(lighten(@color-theme, 10%), 70%);
@color-theme_2-active: fadeout(darken(@color-theme, 5%), 70%);
@color-theme_2-background_1: #fff;
@color-theme_2-background_2: fadeout(@color-theme_2-background_1, 2%);
@color-theme_2-hover: fadeout(lighten(@color-theme, 10%), 80%);
@color-theme_2-active: fadeout(lighten(@color-theme, 10%), 70%);
@color-theme_2-font: darken(@color-theme_2, 70%);
@color-theme_2-font-label: fadeout(@color-theme_2-font, 50%);
@color-theme_2-font-label: fadeout(@color-theme_2-font, 40%);
@color-theme_2-line: lighten(@color-theme, 35%);
@color-theme-sidebar: @color-theme;
@color-btn: fadeout(darken(@color-theme, 5%), 15%);
@color-btn: lighten(@color-theme, 5%);
@color-btn-background: fadeout(lighten(@color-theme, 35%), 70%);
@color-pagination-background: fadeout(lighten(@color-theme, 45%), 30%);
@@ -43,32 +46,48 @@
@color-player-progress-bar1: darken(@color-theme_2, 12%);
@color-player-progress-bar2: lighten(@color-theme, 12%);
@color-player-status-text: lighten(@color-theme_2-font, 10%);
@color-player-detail-play-btn: lighten(@color-theme, 7%);
@color-tab-btn-background: fadeout(lighten(@color-theme, 10%), 80%);
@color-tab-btn-background-hover: @color-theme_2-hover;
@color-tab-border-top: fadeout(lighten(@color-theme, 5%), 50%);
@color-tab-border-bottom: lighten(@color-theme, 5%);
@color-hideBtn: #3bc2b2;
@color-minBtn: #85c43b;
@color-maxBtn: #e7aa36;
@color-closeBtn: #ea6e4d;
@color-hideBtn-hover: fadeout(@color-hideBtn, 10%);
@color-minBtn-hover: fadeout(@color-minBtn, 10%);
@color-maxBtn-hover: fadeout(@color-maxBtn, 10%);
@color-closeBtn-hover: fadeout(@color-closeBtn, 10%);
@color-green-badge-success: #32bc63;
@color-green-badge-success-font: #fff;
@color-green-badge-info: #4baed5;
@color-green-badge-info-font: #fff;
@color-badge-success: @color-theme;
@color-badge-success-font: #fff;
@color-badge-info: #4baed5;
@color-badge-info-font: #fff;
@color-green-theme: #4daf7c;
@color-green-theme-bgimg: none;
@color-green-theme-bgposition: center;
@color-green-theme-bgsize: auto auto;
@color-green-theme-hover: fadeout(lighten(@color-green-theme, 10%), 30%);
@color-green-theme-active: fadeout(darken(@color-green-theme, 20%), 60%);
@color-green-theme-font: #fff;
@color-green-theme-font-label: lighten(@color-green-theme, 35%);
@color-green-theme_2: #fff;
@color-green-theme_2-background_2: #fff;
@color-green-theme_2-hover: fadeout(lighten(@color-green-theme, 10%), 70%);
@color-green-theme_2-active: fadeout(darken(@color-green-theme, 5%), 70%);
@color-green-theme_2-background_1: #fff;
@color-green-theme_2-background_2: fadeout(@color-green-theme_2-background_1, 2%);
@color-green-theme_2-hover: fadeout(lighten(@color-green-theme, 10%), 80%);
@color-green-theme_2-active: fadeout(lighten(@color-green-theme, 10%), 70%);
@color-green-theme_2-font: darken(@color-green-theme_2, 70%);
@color-green-theme_2-font-label: fadeout(@color-green-theme_2-font, 50%);
@color-green-theme_2-line: lighten(@color-green-theme, 35%);
@color-green-theme_2-font-label: fadeout(@color-green-theme_2-font, 40%);
@color-green-theme_2-line: lighten(@color-green-theme, 45%);
@color-green-theme-sidebar: @color-green-theme;
@color-green-btn: fadeout(darken(@color-green-theme, 5%), 15%);
@color-green-btn: lighten(@color-green-theme, 5%);
@color-green-btn-background: fadeout(lighten(@color-green-theme, 35%), 70%);
@color-green-pagination-background: fadeout(lighten(@color-green-theme, 45%), 30%);
@color-green-pagination-hover: fadeout(lighten(@color-green-theme, 10%), 70%);
@@ -85,69 +104,101 @@
@color-green-player-progress-bar1: darken(@color-green-theme_2, 12%);
@color-green-player-progress-bar2: lighten(@color-green-theme, 12%);
@color-green-player-status-text: lighten(@color-green-theme_2-font, 10%);
@color-green-player-detail-play-btn: lighten(@color-green-theme, 7%);
@color-green-tab-btn-background: fadeout(lighten(@color-green-theme, 10%), 80%);
@color-green-tab-btn-background-hover: @color-green-theme_2-hover;
@color-green-tab-border-top: fadeout(lighten(@color-green-theme, 5%), 50%);
@color-green-tab-border-bottom: lighten(@color-green-theme, 5%);
@color-green-hideBtn: #3bc2b2;
@color-green-minBtn: #85c43b;
@color-green-maxBtn: #e7aa36;
@color-green-closeBtn: #ea6e4d;
@color-green-hideBtn-hover: fadeout(@color-hideBtn, 10%);
@color-green-minBtn-hover: fadeout(@color-green-minBtn, 10%);
@color-green-maxBtn-hover: fadeout(@color-green-maxBtn, 10%);
@color-green-closeBtn-hover: fadeout(@color-green-closeBtn, 10%);
@color-green-badge-success: lighten(@color-green-theme, 7%);
@color-green-badge-success-font: #fff;
@color-green-badge-info: #66a2bd;
@color-green-badge-info-font: #fff;
@color-yellow-theme: #f2d35b;
@color-yellow-theme: #e9d460;
@color-yellow-theme-bgimg: none;
@color-yellow-theme-bgposition: center;
@color-yellow-theme-bgsize: auto auto;
@color-yellow-theme-hover: fadeout(lighten(@color-yellow-theme, 10%), 30%);
@color-yellow-theme-active: fadeout(darken(@color-yellow-theme, 20%), 60%);
@color-yellow-theme-font: #fff;
@color-yellow-theme-font-label: lighten(@color-yellow-theme, 35%);
@color-yellow-theme_2: #fff;
@color-yellow-theme_2-background_2: #fff;
@color-yellow-theme_2-background_1: #fff;
@color-yellow-theme_2-background_2: fadeout(@color-yellow-theme_2-background_1, 2%);
@color-yellow-theme_2-hover: fadeout(lighten(@color-yellow-theme, 10%), 70%);
@color-yellow-theme_2-active: fadeout(darken(@color-yellow-theme, 5%), 70%);
@color-yellow-theme_2-active: fadeout(lighten(@color-yellow-theme, 10%), 60%);
@color-yellow-theme_2-font: darken(@color-yellow-theme_2, 70%);
@color-yellow-theme_2-font-label: fadeout(@color-yellow-theme_2-font, 50%);
@color-yellow-theme_2-line: lighten(@color-yellow-theme, 35%);
@color-yellow-theme_2-font-label: fadeout(@color-yellow-theme_2-font, 40%);
@color-yellow-theme_2-line: lighten(@color-yellow-theme, 28%);
@color-yellow-theme-sidebar: @color-yellow-theme;
@color-yellow-btn: fadeout(darken(@color-yellow-theme, 5%), 15%);
@color-yellow-btn-background: fadeout(lighten(@color-yellow-theme, 25%), 70%);
@color-yellow-btn: darken(@color-yellow-theme, 5%);
@color-yellow-btn-background: fadeout(lighten(@color-yellow-theme, 25%), 60%);
@color-yellow-pagination-background: fadeout(lighten(@color-yellow-theme, 30%), 30%);
@color-yellow-pagination-hover: fadeout(lighten(@color-yellow-theme, 5%), 70%);
@color-yellow-pagination-active: fadeout(darken(@color-yellow-theme, 5%), 70%);
@color-yellow-pagination-select: fadeout(lighten(@color-yellow-theme, 5%), 50%);
@color-yellow-search-form-background: fadeout(lighten(@color-yellow-theme, 35%), 10%);
@color-yellow-search-list-hover: fadeout(darken(@color-yellow-theme, 10%), 70%);
@color-yellow-scrollbar-track: fadeout(@color-yellow-theme, 80%);
@color-yellow-scrollbar-thumb: fadeout(@color-yellow-theme, 60%);
@color-yellow-scrollbar-thumb-hover: fadeout(@color-yellow-theme, 40%);
@color-yellow-search-form-background: fadeout(lighten(@color-yellow-theme, 25%), 10%);
@color-yellow-search-list-hover: fadeout(darken(@color-yellow-theme, 10%), 60%);
@color-yellow-scrollbar-track: fadeout(@color-yellow-theme, 60%);
@color-yellow-scrollbar-thumb: fadeout(@color-yellow-theme, 45%);
@color-yellow-scrollbar-thumb-hover: fadeout(@color-yellow-theme, 30%);
@color-yellow-player-pic-c1: fadeout(@color-yellow-theme_2, 50%);
@color-yellow-player-pic-c2: darken(@color-yellow-theme_2, 30%);
@color-yellow-player-progress: darken(@color-yellow-theme_2, 6%);
@color-yellow-player-progress-bar1: darken(@color-yellow-theme_2, 12%);
@color-yellow-player-progress-bar2: lighten(@color-yellow-theme, 12%);
@color-yellow-player-progress-bar2: lighten(@color-yellow-theme, 2%);
@color-yellow-player-status-text: lighten(@color-yellow-theme_2-font, 10%);
@color-yellow-tab-btn-background: fadeout(lighten(@color-yellow-theme, 10%), 80%);
@color-yellow-player-detail-play-btn: lighten(@color-yellow-theme, 7%);
@color-yellow-tab-btn-background: fadeout(lighten(@color-yellow-theme, 10%), 70%);
@color-yellow-tab-btn-background-hover: @color-yellow-theme_2-hover;
@color-yellow-tab-border-top: fadeout(lighten(@color-yellow-theme, 5%), 50%);
@color-yellow-tab-border-bottom: lighten(@color-yellow-theme, 5%);
@color-yellow-tab-border-top: fadeout(lighten(@color-yellow-theme, 5%), 40%);
@color-yellow-tab-border-bottom: @color-yellow-theme;
@color-yellow-hideBtn: #3bc2b2;
@color-yellow-minBtn: #85c43b;
@color-yellow-maxBtn: #e7aa36;
@color-yellow-closeBtn: #ea6e4d;
@color-yellow-hideBtn-hover: fadeout(@color-hideBtn, 10%);
@color-yellow-minBtn-hover: fadeout(@color-yellow-minBtn, 10%);
@color-yellow-maxBtn-hover: fadeout(@color-yellow-maxBtn, 10%);
@color-yellow-closeBtn-hover: fadeout(@color-yellow-closeBtn, 10%);
@color-yellow-badge-success: lighten(@color-yellow-theme, 10%);
@color-yellow-badge-success-font: #fff;
@color-yellow-badge-info: #7cd295;
@color-yellow-badge-info-font: #fff;
@color-orange-theme: #f5ab35;
@color-orange-theme-bgimg: none;
@color-orange-theme-bgposition: center;
@color-orange-theme-bgsize: auto auto;
@color-orange-theme-hover: fadeout(lighten(@color-orange-theme, 10%), 30%);
@color-orange-theme-active: fadeout(darken(@color-orange-theme, 20%), 60%);
@color-orange-theme-font: #fff;
@color-orange-theme-font-label: lighten(@color-orange-theme, 35%);
@color-orange-theme_2: #fff;
@color-orange-theme_2-background_2: #fff;
@color-orange-theme_2-hover: fadeout(lighten(@color-orange-theme, 10%), 70%);
@color-orange-theme_2-background_1: #fff;
@color-orange-theme_2-background_2: fadeout(@color-orange-theme_2-background_1, 2%);
@color-orange-theme_2-hover: fadeout(darken(@color-orange-theme, 5%), 80%);
@color-orange-theme_2-active: fadeout(darken(@color-orange-theme, 5%), 70%);
@color-orange-theme_2-font: darken(@color-orange-theme_2, 70%);
@color-orange-theme_2-font-label: fadeout(@color-orange-theme_2-font, 50%);
@color-orange-theme_2-line: lighten(@color-orange-theme, 35%);
@color-orange-theme_2-font-label: fadeout(@color-orange-theme_2-font, 40%);
@color-orange-theme_2-line: lighten(@color-orange-theme, 36%);
@color-orange-theme-sidebar: @color-orange-theme;
@color-orange-btn: fadeout(darken(@color-orange-theme, 5%), 15%);
@color-orange-btn-background: fadeout(lighten(@color-orange-theme, 35%), 70%);
@color-orange-pagination-background: fadeout(lighten(@color-orange-theme, 35%), 30%);
@color-orange-btn: lighten(@color-orange-theme, 5%);
@color-orange-btn-background: fadeout(lighten(@color-orange-theme, 25%), 60%);
@color-orange-pagination-background: fadeout(lighten(@color-orange-theme, 25%), 40%);
@color-orange-pagination-hover: fadeout(lighten(@color-orange-theme, 10%), 70%);
@color-orange-pagination-active: fadeout(darken(@color-orange-theme, 10%), 70%);
@color-orange-pagination-select: fadeout(lighten(@color-orange-theme, 10%), 50%);
@color-orange-search-form-background: fadeout(lighten(@color-orange-theme, 35%), 10%);
@color-orange-search-form-background: fadeout(lighten(@color-orange-theme, 30%), 10%);
@color-orange-search-list-hover: fadeout(darken(@color-orange-theme, 10%), 70%);
@color-orange-scrollbar-track: fadeout(@color-orange-theme, 80%);
@color-orange-scrollbar-thumb: fadeout(@color-orange-theme, 60%);
@@ -158,27 +209,43 @@
@color-orange-player-progress-bar1: darken(@color-orange-theme_2, 12%);
@color-orange-player-progress-bar2: lighten(@color-orange-theme, 12%);
@color-orange-player-status-text: lighten(@color-orange-theme_2-font, 10%);
@color-orange-player-detail-play-btn: lighten(@color-orange-theme, 7%);
@color-orange-tab-btn-background: fadeout(lighten(@color-orange-theme, 10%), 80%);
@color-orange-tab-btn-background-hover: @color-orange-theme_2-hover;
@color-orange-tab-border-top: fadeout(lighten(@color-orange-theme, 5%), 50%);
@color-orange-tab-border-bottom: lighten(@color-orange-theme, 5%);
@color-orange-hideBtn: #3bc2b2;
@color-orange-minBtn: #85c43b;
@color-orange-maxBtn: #e7aa36;
@color-orange-closeBtn: #ea6e4d;
@color-orange-hideBtn-hover: fadeout(@color-hideBtn, 10%);
@color-orange-minBtn-hover: fadeout(@color-orange-minBtn, 10%);
@color-orange-maxBtn-hover: fadeout(@color-orange-maxBtn, 10%);
@color-orange-closeBtn-hover: fadeout(@color-orange-closeBtn, 10%);
@color-orange-badge-success: lighten(@color-orange-theme, 10%);
@color-orange-badge-success-font: #fff;
@color-orange-badge-info: #9ed458;
@color-orange-badge-info-font: #fff;
@color-blue-theme: #3498db;
@color-blue-theme-bgimg: none;
@color-blue-theme-bgposition: center;
@color-blue-theme-bgsize: auto auto;
@color-blue-theme-hover: fadeout(lighten(@color-blue-theme, 10%), 30%);
@color-blue-theme-active: fadeout(darken(@color-blue-theme, 20%), 60%);
@color-blue-theme-font: #fff;
@color-blue-theme-font-label: lighten(@color-blue-theme, 35%);
@color-blue-theme_2: #fff;
@color-blue-theme_2-background_2: #fff;
@color-blue-theme_2-hover: fadeout(lighten(@color-blue-theme, 10%), 70%);
@color-blue-theme_2-active: fadeout(darken(@color-blue-theme, 5%), 70%);
@color-blue-theme_2-background_1: #fff;
@color-blue-theme_2-background_2: fadeout(@color-blue-theme_2-background_1, 2%);
@color-blue-theme_2-hover: fadeout(lighten(@color-blue-theme, 10%), 80%);
@color-blue-theme_2-active: fadeout(lighten(@color-blue-theme, 10%), 70%);
@color-blue-theme_2-font: darken(@color-blue-theme_2, 70%);
@color-blue-theme_2-font-label: fadeout(@color-blue-theme_2-font, 50%);
@color-blue-theme_2-line: lighten(@color-blue-theme, 35%);
@color-blue-theme_2-font-label: fadeout(@color-blue-theme_2-font, 40%);
@color-blue-theme_2-line: lighten(@color-blue-theme, 42%);
@color-blue-theme-sidebar: @color-blue-theme;
@color-blue-btn: fadeout(darken(@color-blue-theme, 5%), 15%);
@color-blue-btn-background: fadeout(lighten(@color-blue-theme, 35%), 70%);
@color-blue-btn: lighten(@color-blue-theme, 5%);
@color-blue-btn-background: fadeout(lighten(@color-blue-theme, 35%), 50%);
@color-blue-pagination-background: fadeout(lighten(@color-blue-theme, 40%), 30%);
@color-blue-pagination-hover: fadeout(lighten(@color-blue-theme, 15%), 70%);
@color-blue-pagination-active: fadeout(darken(@color-blue-theme, 15%), 70%);
@@ -194,26 +261,42 @@
@color-blue-player-progress-bar1: darken(@color-blue-theme_2, 12%);
@color-blue-player-progress-bar2: lighten(@color-blue-theme, 12%);
@color-blue-player-status-text: lighten(@color-blue-theme_2-font, 10%);
@color-blue-player-detail-play-btn: lighten(@color-blue-theme, 7%);
@color-blue-tab-btn-background: fadeout(lighten(@color-blue-theme, 10%), 80%);
@color-blue-tab-btn-background-hover: @color-blue-theme_2-hover;
@color-blue-tab-border-top: fadeout(lighten(@color-blue-theme, 5%), 50%);
@color-blue-tab-border-bottom: lighten(@color-blue-theme, 5%);
@color-blue-hideBtn: #3bc2b2;
@color-blue-minBtn: #85c43b;
@color-blue-maxBtn: #e7aa36;
@color-blue-closeBtn: #ea6e4d;
@color-blue-hideBtn-hover: fadeout(@color-hideBtn, 10%);
@color-blue-minBtn-hover: fadeout(@color-blue-minBtn, 10%);
@color-blue-maxBtn-hover: fadeout(@color-blue-maxBtn, 10%);
@color-blue-closeBtn-hover: fadeout(@color-blue-closeBtn, 10%);
@color-blue-badge-success: #5cbf9b;
@color-blue-badge-success-font: #fff;
@color-blue-badge-info: lighten(@color-blue-theme, 10%);
@color-blue-badge-info-font: #fff;
@color-red-theme: #d64541;
@color-red-theme-bgimg: none;
@color-red-theme-bgposition: center;
@color-red-theme-bgsize: auto auto;
@color-red-theme-hover: fadeout(lighten(@color-red-theme, 10%), 30%);
@color-red-theme-active: fadeout(darken(@color-red-theme, 20%), 60%);
@color-red-theme-font: #fff;
@color-red-theme-font-label: lighten(@color-red-theme, 35%);
@color-red-theme_2: #fff;
@color-red-theme_2-hover: fadeout(lighten(@color-red-theme, 10%), 70%);
@color-red-theme_2-active: fadeout(darken(@color-red-theme, 5%), 70%);
@color-red-theme_2-background_2: #fff;
@color-red-theme_2-hover: fadeout(lighten(@color-red-theme, 10%), 80%);
@color-red-theme_2-active: fadeout(lighten(@color-red-theme, 10%), 70%);
@color-red-theme_2-background_1: #fff;
@color-red-theme_2-background_2: fadeout(@color-red-theme_2-background_1, 2%);
@color-red-theme_2-font: darken(@color-red-theme_2, 70%);
@color-red-theme_2-font-label: fadeout(@color-red-theme_2-font, 50%);
@color-red-theme_2-line: lighten(@color-red-theme, 35%);
@color-red-theme_2-font-label: fadeout(@color-red-theme_2-font, 40%);
@color-red-theme_2-line: lighten(@color-red-theme, 42%);
@color-red-theme-sidebar: @color-red-theme;
@color-red-btn: fadeout(darken(@color-red-theme, 5%), 15%);
@color-red-btn: lighten(@color-red-theme, 5%);
@color-red-btn-background: fadeout(lighten(@color-red-theme, 35%), 70%);
@color-red-pagination-background: fadeout(lighten(@color-red-theme, 40%), 30%);
@color-red-pagination-hover: fadeout(lighten(@color-red-theme, 15%), 70%);
@@ -230,28 +313,96 @@
@color-red-player-progress-bar1: darken(@color-red-theme_2, 12%);
@color-red-player-progress-bar2: lighten(@color-red-theme, 12%);
@color-red-player-status-text: lighten(@color-red-theme_2-font, 10%);
@color-red-player-detail-play-btn: lighten(@color-red-theme, 7%);
@color-red-tab-border-top: fadeout(lighten(@color-red-theme, 25%), 70%);
@color-red-tab-border-bottom: lighten(@color-red-theme, 35%);
@color-red-tab-btn-background: fadeout(lighten(@color-red-theme, 10%), 80%);
@color-red-tab-btn-background-hover: @color-red-theme_2-hover;
@color-red-tab-border-top: fadeout(lighten(@color-red-theme, 5%), 50%);
@color-red-tab-border-bottom: lighten(@color-red-theme, 5%);
@color-red-hideBtn: #3bc2b2;
@color-red-minBtn: #85c43b;
@color-red-maxBtn: #e7aa36;
@color-red-closeBtn: #ea6e4d;
@color-red-hideBtn-hover: fadeout(@color-hideBtn, 10%);
@color-red-minBtn-hover: fadeout(@color-red-minBtn, 10%);
@color-red-maxBtn-hover: fadeout(@color-red-maxBtn, 10%);
@color-red-closeBtn-hover: fadeout(@color-red-closeBtn, 10%);
@color-red-badge-success: lighten(@color-red-theme, 10%);
@color-red-badge-success-font: #fff;
@color-red-badge-info: #dfbb6b;
@color-red-badge-info-font: #fff;
@color-pink-theme: #f1828d;
@color-pink-theme-bgimg: none;
@color-pink-theme-bgposition: center;
@color-pink-theme-bgsize: auto auto;
@color-pink-theme-hover: fadeout(lighten(@color-pink-theme, 10%), 30%);
@color-pink-theme-active: fadeout(darken(@color-pink-theme, 20%), 60%);
@color-pink-theme-font: #fff;
@color-pink-theme-font-label: lighten(@color-pink-theme, 35%);
@color-pink-theme_2: #fff;
@color-pink-theme_2-background_1: #fff;
@color-pink-theme_2-background_2: fadeout(@color-pink-theme_2-background_1, 2%);
@color-pink-theme_2-hover: fadeout(lighten(@color-pink-theme, 10%), 75%);
@color-pink-theme_2-active: fadeout(lighten(@color-pink-theme, 10%), 60%);
@color-pink-theme_2-font: darken(@color-pink-theme_2, 70%);
@color-pink-theme_2-font-label: fadeout(@color-pink-theme_2-font, 40%);
@color-pink-theme_2-line: lighten(@color-pink-theme, 25%);
@color-pink-theme-sidebar: @color-pink-theme;
@color-pink-btn: darken(@color-pink-theme, 3%);
@color-pink-btn-background: fadeout(lighten(@color-pink-theme, 20%), 50%);
@color-pink-pagination-background: fadeout(lighten(@color-pink-theme, 23%), 25%);
@color-pink-pagination-hover: fadeout(lighten(@color-pink-theme, 5%), 70%);
@color-pink-pagination-active: fadeout(darken(@color-pink-theme, 5%), 70%);
@color-pink-pagination-select: fadeout(lighten(@color-pink-theme, 5%), 50%);
@color-pink-search-form-background: fadeout(lighten(@color-pink-theme, 20%), 10%);
@color-pink-search-list-hover: fadeout(darken(@color-pink-theme, 10%), 60%);
@color-pink-scrollbar-track: fadeout(@color-pink-theme, 60%);
@color-pink-scrollbar-thumb: fadeout(@color-pink-theme, 45%);
@color-pink-scrollbar-thumb-hover: fadeout(@color-pink-theme, 30%);
@color-pink-player-pic-c1: fadeout(@color-pink-theme_2, 50%);
@color-pink-player-pic-c2: darken(@color-pink-theme_2, 30%);
@color-pink-player-progress: darken(@color-pink-theme_2, 6%);
@color-pink-player-progress-bar1: darken(@color-pink-theme_2, 12%);
@color-pink-player-progress-bar2: lighten(@color-pink-theme, 2%);
@color-pink-player-status-text: lighten(@color-pink-theme_2-font, 10%);
@color-pink-player-detail-play-btn: lighten(@color-pink-theme, 7%);
@color-pink-tab-btn-background: fadeout(lighten(@color-pink-theme, 10%), 70%);
@color-pink-tab-btn-background-hover: @color-pink-theme_2-hover;
@color-pink-tab-border-top: fadeout(lighten(@color-pink-theme, 5%), 40%);
@color-pink-tab-border-bottom: @color-pink-theme;
@color-pink-hideBtn: #3bc2b2;
@color-pink-minBtn: #85c43b;
@color-pink-maxBtn: #e7aa36;
@color-pink-closeBtn: #ea6e4d;
@color-pink-hideBtn-hover: fadeout(@color-hideBtn, 10%);
@color-pink-minBtn-hover: fadeout(@color-pink-minBtn, 10%);
@color-pink-maxBtn-hover: fadeout(@color-pink-maxBtn, 10%);
@color-pink-closeBtn-hover: fadeout(@color-pink-closeBtn, 10%);
@color-pink-badge-success: lighten(@color-pink-theme, 10%);
@color-pink-badge-success-font: #fff;
@color-pink-badge-info: #f5b684;
@color-pink-badge-info-font: #fff;
@color-purple-theme: #9b59b6;
@color-purple-theme-bgimg: none;
@color-purple-theme-bgposition: center;
@color-purple-theme-bgsize: auto auto;
@color-purple-theme-hover: fadeout(lighten(@color-purple-theme, 10%), 30%);
@color-purple-theme-active: fadeout(darken(@color-purple-theme, 20%), 60%);
@color-purple-theme-font: #fff;
@color-purple-theme-font-label: lighten(@color-purple-theme, 35%);
@color-purple-theme_2: #fff;
@color-purple-theme_2-background_2: #fff;
@color-purple-theme_2-hover: fadeout(lighten(@color-purple-theme, 10%), 70%);
@color-purple-theme_2-active: fadeout(darken(@color-purple-theme, 5%), 70%);
@color-purple-theme_2-background_1: #fff;
@color-purple-theme_2-background_2: fadeout(@color-purple-theme_2-background_1, 2%);
@color-purple-theme_2-hover: fadeout(lighten(@color-purple-theme, 10%), 80%);
@color-purple-theme_2-active: fadeout(lighten(@color-purple-theme, 10%), 70%);
@color-purple-theme_2-font: darken(@color-purple-theme_2, 70%);
@color-purple-theme_2-font-label: fadeout(@color-purple-theme_2-font, 50%);
@color-purple-theme_2-line: lighten(@color-purple-theme, 35%);
@color-purple-theme_2-font-label: fadeout(@color-purple-theme_2-font, 40%);
@color-purple-theme_2-line: lighten(@color-purple-theme, 43%);
@color-purple-theme-sidebar: @color-purple-theme;
@color-purple-btn: fadeout(darken(@color-purple-theme, 5%), 15%);
@color-purple-btn: lighten(@color-purple-theme, 5%);
@color-purple-btn-background: fadeout(lighten(@color-purple-theme, 35%), 70%);
@color-purple-pagination-background: fadeout(lighten(@color-purple-theme, 40%), 30%);
@color-purple-pagination-hover: fadeout(lighten(@color-purple-theme, 15%), 70%);
@@ -268,26 +419,42 @@
@color-purple-player-progress-bar1: darken(@color-purple-theme_2, 12%);
@color-purple-player-progress-bar2: lighten(@color-purple-theme, 12%);
@color-purple-player-status-text: lighten(@color-purple-theme_2-font, 10%);
@color-purple-player-detail-play-btn: lighten(@color-purple-theme, 7%);
@color-purple-tab-btn-background: fadeout(lighten(@color-purple-theme, 10%), 80%);
@color-purple-tab-btn-background-hover: @color-purple-theme_2-hover;
@color-purple-tab-border-top: fadeout(lighten(@color-purple-theme, 5%), 50%);
@color-purple-tab-border-bottom: lighten(@color-purple-theme, 5%);
@color-purple-hideBtn: #3bc2b2;
@color-purple-minBtn: #85c43b;
@color-purple-maxBtn: #e7aa36;
@color-purple-closeBtn: #ea6e4d;
@color-purple-hideBtn-hover: fadeout(@color-hideBtn, 10%);
@color-purple-minBtn-hover: fadeout(@color-purple-minBtn, 10%);
@color-purple-maxBtn-hover: fadeout(@color-purple-maxBtn, 10%);
@color-purple-closeBtn-hover: fadeout(@color-purple-closeBtn, 10%);
@color-purple-badge-success: lighten(@color-purple-theme, 15%);
@color-purple-badge-success-font: #fff;
@color-purple-badge-info: #e5a39f;
@color-purple-badge-info-font: #fff;
@color-grey-theme: #6c7a89;
@color-grey-theme-bgimg: none;
@color-grey-theme-bgposition: center;
@color-grey-theme-bgsize: auto auto;
@color-grey-theme-hover: fadeout(lighten(@color-grey-theme, 10%), 30%);
@color-grey-theme-active: fadeout(darken(@color-grey-theme, 20%), 60%);
@color-grey-theme-font: #fff;
@color-grey-theme-font-label: lighten(@color-grey-theme, 35%);
@color-grey-theme_2: #fff;
@color-grey-theme_2-background_2: #fff;
@color-grey-theme_2-hover: fadeout(lighten(@color-grey-theme, 10%), 70%);
@color-grey-theme_2-active: fadeout(darken(@color-grey-theme, 5%), 70%);
@color-grey-theme_2-background_1: #fff;
@color-grey-theme_2-background_2: fadeout(@color-grey-theme_2-background_1, 2%);
@color-grey-theme_2-hover: fadeout(lighten(@color-grey-theme, 10%), 80%);
@color-grey-theme_2-active: fadeout(lighten(@color-grey-theme, 10%), 70%);
@color-grey-theme_2-font: darken(@color-grey-theme_2, 70%);
@color-grey-theme_2-font-label: fadeout(@color-grey-theme_2-font, 50%);
@color-grey-theme_2-line: lighten(@color-grey-theme, 35%);
@color-grey-theme_2-font-label: fadeout(@color-grey-theme_2-font, 40%);
@color-grey-theme_2-line: lighten(@color-grey-theme, 47%);
@color-grey-theme-sidebar: @color-grey-theme;
@color-grey-btn: fadeout(darken(@color-grey-theme, 5%), 15%);
@color-grey-btn: lighten(@color-grey-theme, 5%);
@color-grey-btn-background: fadeout(lighten(@color-grey-theme, 35%), 70%);
@color-grey-pagination-background: fadeout(lighten(@color-grey-theme, 45%), 30%);
@color-grey-pagination-hover: fadeout(lighten(@color-grey-theme, 10%), 70%);
@@ -304,50 +471,239 @@
@color-grey-player-progress-bar1: darken(@color-grey-theme_2, 12%);
@color-grey-player-progress-bar2: lighten(@color-grey-theme, 12%);
@color-grey-player-status-text: lighten(@color-grey-theme_2-font, 10%);
@color-grey-player-detail-play-btn: lighten(@color-grey-theme, 7%);
@color-grey-tab-btn-background: fadeout(lighten(@color-grey-theme, 10%), 80%);
@color-grey-tab-btn-background-hover: @color-grey-theme_2-hover;
@color-grey-tab-border-top: fadeout(lighten(@color-grey-theme, 5%), 50%);
@color-grey-tab-border-bottom: lighten(@color-grey-theme, 5%);
@color-grey-hideBtn: #3bc2b2;
@color-grey-minBtn: #85c43b;
@color-grey-maxBtn: #e7aa36;
@color-grey-closeBtn: #ea6e4d;
@color-grey-hideBtn-hover: fadeout(@color-hideBtn, 10%);
@color-grey-minBtn-hover: fadeout(@color-grey-minBtn, 10%);
@color-grey-maxBtn-hover: fadeout(@color-grey-maxBtn, 10%);
@color-grey-closeBtn-hover: fadeout(@color-grey-closeBtn, 10%);
@color-grey-badge-success: lighten(@color-grey-theme, 15%);
@color-grey-badge-success-font: #fff;
@color-grey-badge-info: #b19b9f;
@color-grey-badge-info-font: #fff;
@color-ming-theme: #336e7b;
@color-ming-theme-bgimg: none;
@color-ming-theme-bgposition: center;
@color-ming-theme-bgsize: auto auto;
@color-ming-theme-hover: fadeout(lighten(@color-ming-theme, 7%), 30%);
@color-ming-theme-active: fadeout(darken(@color-ming-theme, 20%), 60%);
@color-ming-theme-font: #fff;
@color-ming-theme-font-label: lighten(@color-ming-theme, 35%);
@color-ming-theme_2: #fff;
@color-ming-theme_2-hover: fadeout(lighten(@color-ming-theme, 10%), 85%);
@color-ming-theme_2-active: fadeout(lighten(@color-ming-theme, 10%), 75%);
@color-ming-theme_2-background_1: #fff;
@color-ming-theme_2-background_2: fadeout(@color-ming-theme_2-background_1, 2%);
@color-ming-theme_2-font: darken(@color-ming-theme_2, 70%);
@color-ming-theme_2-font-label: fadeout(@color-ming-theme_2-font, 40%);
@color-ming-theme_2-line: lighten(@color-ming-theme, 60%);
@color-ming-theme-sidebar: @color-ming-theme;
@color-ming-btn: lighten(@color-ming-theme, 5%);
@color-ming-btn-background: fadeout(lighten(@color-ming-theme, 35%), 75%);
@color-ming-pagination-background: fadeout(lighten(@color-ming-theme, 50%), 35%);
@color-ming-pagination-hover: fadeout(lighten(@color-ming-theme, 15%), 70%);
@color-ming-pagination-active: fadeout(darken(@color-ming-theme, 15%), 70%);
@color-ming-pagination-select: fadeout(lighten(@color-ming-theme, 15%), 50%);
@color-ming-search-form-background: fadeout(lighten(@color-ming-theme, 45%), 15%);
@color-ming-search-list-hover: fadeout(darken(@color-ming-theme, 10%), 70%);
@color-ming-scrollbar-track: fadeout(@color-ming-theme, 80%);
@color-ming-scrollbar-thumb: fadeout(@color-ming-theme, 60%);
@color-ming-scrollbar-thumb-hover: fadeout(@color-ming-theme, 40%);
@color-ming-player-pic-c1: fadeout(@color-ming-theme_2, 50%);
@color-ming-player-pic-c2: darken(@color-ming-theme_2, 30%);
@color-ming-player-progress: darken(@color-ming-theme_2, 6%);
@color-ming-player-progress-bar1: darken(@color-ming-theme_2, 12%);
@color-ming-player-progress-bar2: lighten(@color-ming-theme, 12%);
@color-ming-player-status-text: lighten(@color-ming-theme_2-font, 10%);
@color-ming-player-detail-play-btn: lighten(@color-ming-theme, 10%);
@color-ming-tab-border-top: fadeout(lighten(@color-ming-theme, 25%), 70%);
@color-ming-tab-border-bottom: lighten(@color-ming-theme, 35%);
@color-ming-tab-btn-background: fadeout(lighten(@color-ming-theme, 10%), 80%);
@color-ming-tab-btn-background-hover: @color-ming-theme_2-hover;
@color-ming-tab-border-top: fadeout(lighten(@color-ming-theme, 5%), 50%);
@color-ming-tab-border-bottom: lighten(@color-ming-theme, 5%);
@color-ming-hideBtn: #3bc2b2;
@color-ming-minBtn: #85c43b;
@color-ming-maxBtn: #e7aa36;
@color-ming-closeBtn: #ea6e4d;
@color-ming-hideBtn-hover: fadeout(@color-hideBtn, 10%);
@color-ming-minBtn-hover: fadeout(@color-ming-minBtn, 10%);
@color-ming-maxBtn-hover: fadeout(@color-ming-maxBtn, 10%);
@color-ming-closeBtn-hover: fadeout(@color-ming-closeBtn, 10%);
@color-ming-badge-success: lighten(@color-ming-theme, 10%);
@color-ming-badge-success-font: #fff;
@color-ming-badge-info: #6376a2;
@color-ming-badge-info-font: #fff;
@color-mid_autumn-theme: rgba(74, 55, 82, 1);
@color-mid_autumn-theme-bgimg: url(../images/jqbg.jpg);
@color-mid_autumn-theme-bgposition: center;
@color-mid_autumn-theme-bgsize: auto 100%;
@color-mid_autumn-theme-hover: fadeout(lighten(@color-mid_autumn-theme, 10%), 40%);
@color-mid_autumn-theme-active: fadeout(lighten(@color-mid_autumn-theme, 15%), 60%);
@color-mid_autumn-theme-font: rgba(246, 233, 255, 0.9);
@color-mid_autumn-theme-font-label: fadeout(lighten(@color-mid_autumn-theme, 20%), 20%);
@color-mid_autumn-theme_2: rgba(255, 255, 255, .93);
@color-mid_autumn-theme_2-background_1: #eeedef;
@color-mid_autumn-theme_2-background_2: fadeout(@color-mid_autumn-theme_2-background_1, 2%);
@color-mid_autumn-theme_2-hover: fadeout(lighten(@color-mid_autumn-theme, 15%), 80%);
@color-mid_autumn-theme_2-active: fadeout(lighten(@color-mid_autumn-theme, 15%), 70%);
@color-mid_autumn-theme_2-font: darken(@color-mid_autumn-theme_2, 70%);
@color-mid_autumn-theme_2-font-label: desaturate(lighten(@color-mid_autumn-theme, 30%), 45%);
@color-mid_autumn-theme_2-line: lighten(@color-mid_autumn-theme, 63%);
@color-mid_autumn-theme-sidebar: rgba(255, 255, 255, 0);
@color-mid_autumn-btn: lighten(@color-mid_autumn-theme, 10%);
@color-mid_autumn-btn-background: fadeout(lighten(@color-mid_autumn-theme, 35%), 70%);
@color-mid_autumn-pagination-background: fadeout(lighten(@color-mid_autumn-theme, 45%), 50%);
@color-mid_autumn-pagination-hover: fadeout(lighten(@color-mid_autumn-theme, 10%), 70%);
@color-mid_autumn-pagination-active: fadeout(darken(@color-mid_autumn-theme, 10%), 70%);
@color-mid_autumn-pagination-select: fadeout(lighten(@color-mid_autumn-theme, 10%), 55%);
@color-mid_autumn-search-form-background: fadeout(lighten(@color-mid_autumn-theme, 50%), 10%);
@color-mid_autumn-search-list-hover: fadeout(darken(@color-mid_autumn-theme, 10%), 70%);
@color-mid_autumn-scrollbar-track: fadeout(@color-mid_autumn-theme, 80%);
@color-mid_autumn-scrollbar-thumb: fadeout(@color-mid_autumn-theme, 60%);
@color-mid_autumn-scrollbar-thumb-hover: fadeout(@color-mid_autumn-theme, 40%);
@color-mid_autumn-player-pic-c1: fadeout(@color-mid_autumn-theme_2, 50%);
@color-mid_autumn-player-pic-c2: darken(@color-mid_autumn-theme_2, 30%);
@color-mid_autumn-player-progress: darken(@color-mid_autumn-theme_2, 10%);
@color-mid_autumn-player-progress-bar1: darken(@color-mid_autumn-theme_2, 12%);
@color-mid_autumn-player-progress-bar2: lighten(@color-mid_autumn-theme, 12%);
@color-mid_autumn-player-status-text: lighten(@color-mid_autumn-theme_2-font, 10%);
@color-mid_autumn-player-detail-play-btn: lighten(@color-mid_autumn-theme, 7%);
@color-mid_autumn-tab-btn-background: fadeout(lighten(@color-mid_autumn-theme, 10%), 80%);
@color-mid_autumn-tab-btn-background-hover: @color-mid_autumn-theme_2-hover;
@color-mid_autumn-tab-border-top: fadeout(lighten(@color-mid_autumn-theme, 5%), 50%);
@color-mid_autumn-tab-border-bottom: lighten(@color-mid_autumn-theme, 5%);
@color-mid_autumn-hideBtn: #3bc2b2;
@color-mid_autumn-minBtn: #85c43b;
@color-mid_autumn-maxBtn: #e7aa36;
@color-mid_autumn-closeBtn: #ea6e4d;
@color-mid_autumn-hideBtn-hover: fadeout(@color-hideBtn, 10%);
@color-mid_autumn-minBtn-hover: fadeout(@color-mid_autumn-minBtn, 10%);
@color-mid_autumn-maxBtn-hover: fadeout(@color-mid_autumn-maxBtn, 10%);
@color-mid_autumn-closeBtn-hover: fadeout(@color-mid_autumn-closeBtn, 10%);
@color-mid_autumn-badge-success: lighten(@color-mid_autumn-theme, 25%);
@color-mid_autumn-badge-success-font: #fff;
@color-mid_autumn-badge-info: #af9479;
@color-mid_autumn-badge-info-font: #fff;
@color-naruto-theme: rgb(87, 144, 167);
@color-naruto-theme-bgimg: url(../images/myzcbg.jpeg);
@color-naruto-theme-bgposition: center;
@color-naruto-theme-bgsize: auto 100%;
@color-naruto-theme-hover: fadeout(lighten(@color-naruto-theme, 10%), 45%);
@color-naruto-theme-active: fadeout(lighten(@color-naruto-theme, 15%), 60%);
@color-naruto-theme-font: rgba(67, 118, 148, 0.9);
@color-naruto-theme-font-label: fadeout(darken(@color-naruto-theme, 20%), 20%);
@color-naruto-theme_2: rgba(255, 255, 255, 0.8);
@color-naruto-theme_2-background_1: #e9faff;
@color-naruto-theme_2-background_2: fadeout(@color-naruto-theme_2-background_1, 2%);
@color-naruto-theme_2-hover: fadeout(lighten(@color-naruto-theme, 10%), 75%);
@color-naruto-theme_2-active: fadeout(lighten(@color-naruto-theme, 10%), 65%);
@color-naruto-theme_2-font: darken(@color-naruto-theme_2, 80%);
@color-naruto-theme_2-font-label: desaturate(lighten(@color-naruto-theme, 10%), 45%);
@color-naruto-theme_2-line: fadeout(lighten(@color-naruto-theme, 36%), 70%);
@color-naruto-theme-sidebar: rgba(255, 255, 255, .3);
@color-naruto-btn: lighten(@color-naruto-theme, 2%);
@color-naruto-btn-background: fadeout(lighten(@color-naruto-theme, 35%), 70%);
@color-naruto-pagination-background: fadeout(lighten(@color-naruto-theme, 20%), 70%);
@color-naruto-pagination-hover: fadeout(lighten(@color-naruto-theme, 10%), 60%);
@color-naruto-pagination-active: fadeout(darken(@color-naruto-theme, 10%), 70%);
@color-naruto-pagination-select: fadeout(lighten(@color-naruto-theme, 10%), 50%);
@color-naruto-search-form-background: fadeout(lighten(@color-naruto-theme, 30%), 20%);
@color-naruto-search-list-hover: fadeout(darken(@color-naruto-theme, 10%), 50%);
@color-naruto-scrollbar-track: fadeout(@color-naruto-theme, 80%);
@color-naruto-scrollbar-thumb: fadeout(@color-naruto-theme, 60%);
@color-naruto-scrollbar-thumb-hover: fadeout(@color-naruto-theme, 40%);
@color-naruto-player-pic-c1: fadeout(@color-naruto-theme_2, 50%);
@color-naruto-player-pic-c2: darken(@color-naruto-theme_2, 30%);
@color-naruto-player-progress: darken(@color-naruto-theme_2, 10%);
@color-naruto-player-progress-bar1: darken(@color-naruto-theme_2, 12%);
@color-naruto-player-progress-bar2: lighten(@color-naruto-theme, 12%);
@color-naruto-player-status-text: lighten(@color-naruto-theme_2-font, 10%);
@color-naruto-player-detail-play-btn: lighten(@color-naruto-theme, 7%);
@color-naruto-tab-btn-background: fadeout(lighten(@color-naruto-theme, 10%), 80%);
@color-naruto-tab-btn-background-hover: @color-naruto-theme_2-hover;
@color-naruto-tab-border-top: fadeout(lighten(@color-naruto-theme, 5%), 50%);
@color-naruto-tab-border-bottom: lighten(@color-naruto-theme, 5%);
@color-naruto-hideBtn: #3bc2b2;
@color-naruto-minBtn: #85c43b;
@color-naruto-maxBtn: #e7aa36;
@color-naruto-closeBtn: #ea6e4d;
@color-naruto-hideBtn-hover: fadeout(@color-hideBtn, 10%);
@color-naruto-minBtn-hover: fadeout(@color-naruto-minBtn, 10%);
@color-naruto-maxBtn-hover: fadeout(@color-naruto-maxBtn, 10%);
@color-naruto-closeBtn-hover: fadeout(@color-naruto-closeBtn, 10%);
@color-naruto-badge-success: #7fb575;
@color-naruto-badge-success-font: #fff;
@color-naruto-badge-info: lighten(@color-naruto-theme, 10%);
@color-naruto-badge-info-font: #fff;
@color-happy_new_year-theme: rgb(252, 57, 57);
@color-happy_new_year-theme-bgimg: url(../images/xnkl.jpg);
@color-happy_new_year-theme-bgposition: center;
@color-happy_new_year-theme-bgsize: auto 100%;
@color-happy_new_year-theme-hover: fadeout(lighten(@color-happy_new_year-theme, 10%), 45%);
@color-happy_new_year-theme-active: fadeout(lighten(@color-happy_new_year-theme, 15%), 60%);
@color-happy_new_year-theme-font: rgba(255, 255, 255, 0.9);
@color-happy_new_year-theme-font-label: fadeout(lighten(@color-happy_new_year-theme, 20%), 20%);
@color-happy_new_year-theme_2: rgba(255, 199, 199, 0.8);
@color-happy_new_year-theme_2-background_1: #f2a4a4;
@color-happy_new_year-theme_2-background_2: fadeout(@color-happy_new_year-theme_2-background_1, 2%);
@color-happy_new_year-theme_2-hover: fadeout(lighten(@color-happy_new_year-theme, 10%), 65%);
@color-happy_new_year-theme_2-active: fadeout(lighten(@color-happy_new_year-theme, 10%), 55%);
@color-happy_new_year-theme_2-font: darken(@color-happy_new_year-theme_2, 90%);
@color-happy_new_year-theme_2-font-label: desaturate(darken(@color-happy_new_year-theme, 5%), 50%);
@color-happy_new_year-theme_2-line: fadeout(lighten(@color-happy_new_year-theme, 16%), 70%);
@color-happy_new_year-theme-sidebar: rgba(119, 37, 18, 0.1);
@color-happy_new_year-btn: desaturate(@color-happy_new_year-theme, 10%);
@color-happy_new_year-btn-background: fadeout(lighten(@color-happy_new_year-theme, 15%), 60%);
@color-happy_new_year-pagination-background: fadeout(lighten(@color-happy_new_year-theme, 15%), 40%);
@color-happy_new_year-pagination-hover: fadeout(lighten(@color-happy_new_year-theme, 10%), 70%);
@color-happy_new_year-pagination-active: fadeout(darken(@color-happy_new_year-theme, 10%), 70%);
@color-happy_new_year-pagination-select: fadeout(lighten(@color-happy_new_year-theme, 10%), 55%);
@color-happy_new_year-search-form-background: fadeout(lighten(@color-happy_new_year-theme, 15%), 20%);
@color-happy_new_year-search-list-hover: fadeout(darken(@color-happy_new_year-theme, 10%), 50%);
@color-happy_new_year-scrollbar-track: fadeout(@color-happy_new_year-theme, 80%);
@color-happy_new_year-scrollbar-thumb: fadeout(@color-happy_new_year-theme, 60%);
@color-happy_new_year-scrollbar-thumb-hover: fadeout(@color-happy_new_year-theme, 40%);
@color-happy_new_year-player-pic-c1: fadeout(@color-happy_new_year-theme_2, 50%);
@color-happy_new_year-player-pic-c2: darken(@color-happy_new_year-theme_2, 30%);
@color-happy_new_year-player-progress: darken(@color-happy_new_year-theme_2, 10%);
@color-happy_new_year-player-progress-bar1: darken(@color-happy_new_year-theme_2, 12%);
@color-happy_new_year-player-progress-bar2: lighten(@color-happy_new_year-theme, 9%);
@color-happy_new_year-player-status-text: lighten(@color-happy_new_year-theme_2-font, 10%);
@color-happy_new_year-player-detail-play-btn: lighten(@color-happy_new_year-theme, 7%);
@color-happy_new_year-tab-btn-background: fadeout(lighten(@color-happy_new_year-theme, 10%), 80%);
@color-happy_new_year-tab-btn-background-hover: @color-happy_new_year-theme_2-hover;
@color-happy_new_year-tab-border-top: fadeout(lighten(@color-happy_new_year-theme, 5%), 50%);
@color-happy_new_year-tab-border-bottom: lighten(@color-happy_new_year-theme, 5%);
@color-happy_new_year-hideBtn: #64a130;
@color-happy_new_year-minBtn: #c4943b;
@color-happy_new_year-maxBtn: #e7aa36;
@color-happy_new_year-closeBtn: #ea6e4d;
@color-happy_new_year-hideBtn-hover: fadeout(@color-hideBtn, 10%);
@color-happy_new_year-minBtn-hover: fadeout(@color-happy_new_year-minBtn, 10%);
@color-happy_new_year-maxBtn-hover: fadeout(@color-happy_new_year-maxBtn, 10%);
@color-happy_new_year-closeBtn-hover: fadeout(@color-happy_new_year-closeBtn, 10%);
@color-happy_new_year-badge-success: @color-happy_new_year-theme;
@color-happy_new_year-badge-success-font: #fff;
@color-happy_new_year-badge-info: #c4943b;
@color-happy_new_year-badge-info-font: #fff;
@color-midAutumn-theme: rgba(74, 55, 82, 1);
@color-midAutumn-theme-bgimg: url(../images/jqbg.jpg);
@color-midAutumn-theme-hover: fadeout(lighten(@color-midAutumn-theme, 10%), 30%);
@color-midAutumn-theme-active: fadeout(lighten(@color-midAutumn-theme, 15%), 60%);
@color-midAutumn-theme-font: rgba(246, 233, 255, 0.9);
@color-midAutumn-theme-font-label: fadeout(lighten(@color-midAutumn-theme, 20%), 20%);
@color-midAutumn-theme_2: rgba(255, 255, 255, .93);
@color-midAutumn-theme_2-background_2: #eeedef;
@color-midAutumn-theme_2-hover: fadeout(lighten(@color-midAutumn-theme, 10%), 65%);
@color-midAutumn-theme_2-active: fadeout(darken(@color-midAutumn-theme, 5%), 70%);
@color-midAutumn-theme_2-font: darken(@color-midAutumn-theme_2, 70%);
@color-midAutumn-theme_2-font-label: lighten(@color-midAutumn-theme, 40%);
@color-midAutumn-theme_2-line: lighten(@color-midAutumn-theme, 54%);
@color-midAutumn-theme-sidebar: rgba(255, 255, 255, 0);
@color-midAutumn-btn: fadeout(darken(@color-midAutumn-theme, 5%), 15%);
@color-midAutumn-btn-background: fadeout(lighten(@color-midAutumn-theme, 35%), 70%);
@color-midAutumn-pagination-background: fadeout(lighten(@color-midAutumn-theme, 45%), 50%);
@color-midAutumn-pagination-hover: fadeout(lighten(@color-midAutumn-theme, 10%), 70%);
@color-midAutumn-pagination-active: fadeout(darken(@color-midAutumn-theme, 10%), 70%);
@color-midAutumn-pagination-select: fadeout(lighten(@color-midAutumn-theme, 10%), 55%);
@color-midAutumn-search-form-background: fadeout(lighten(@color-midAutumn-theme, 50%), 10%);
@color-midAutumn-search-list-hover: fadeout(darken(@color-midAutumn-theme, 10%), 70%);
@color-midAutumn-scrollbar-track: fadeout(@color-midAutumn-theme, 80%);
@color-midAutumn-scrollbar-thumb: fadeout(@color-midAutumn-theme, 60%);
@color-midAutumn-scrollbar-thumb-hover: fadeout(@color-midAutumn-theme, 40%);
@color-midAutumn-player-pic-c1: fadeout(@color-midAutumn-theme_2, 50%);
@color-midAutumn-player-pic-c2: darken(@color-midAutumn-theme_2, 30%);
@color-midAutumn-player-progress: darken(@color-midAutumn-theme_2, 6%);
@color-midAutumn-player-progress-bar1: darken(@color-midAutumn-theme_2, 12%);
@color-midAutumn-player-progress-bar2: lighten(@color-midAutumn-theme, 12%);
@color-midAutumn-player-status-text: lighten(@color-midAutumn-theme_2-font, 10%);
@color-midAutumn-tab-btn-background: fadeout(lighten(@color-midAutumn-theme, 10%), 80%);
@color-midAutumn-tab-btn-background-hover: @color-midAutumn-theme_2-hover;
@color-midAutumn-tab-border-top: fadeout(lighten(@color-midAutumn-theme, 5%), 50%);
@color-midAutumn-tab-border-bottom: lighten(@color-midAutumn-theme, 5%);
// Width
@width-app-left: 180px;
@width-app-left: 20%;
// Height
@height-toolbar: 50px;
@@ -362,3 +718,5 @@
@radius-progress-border: 5px;
@transition-theme: .4s ease;
@form-radius: 3px;

View File

@@ -1,33 +1,35 @@
<template lang="pug">
div(:class="$style.aside")
div(:class="$style.logo")
div(:class="['animated', logoAnimate, $style.logo]")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='100%' height='100%' viewBox='0 0 127 61' space='preserve')
use(xlink:href='#icon-logo')
div(:class="$style.menu")
dl
dt 在线音乐
dt {{$t('core.aside.online_music')}}
dd
router-link(:active-class="$style.active" to="search") 搜索
router-link(:active-class="$style.active" to="search") {{$t('core.aside.search')}}
dd
router-link(:active-class="$style.active" to="songList") 歌单
router-link(:active-class="$style.active" to="songList") {{$t('core.aside.song_list')}}
dd
router-link(:active-class="$style.active" to="leaderboard") 排行榜
router-link(:active-class="$style.active" to="leaderboard") {{$t('core.aside.leaderboard')}}
dl
dt 我的音乐
dt {{$t('core.aside.my_music')}}
dd
router-link(:active-class="$style.active" to="list") {{defaultList.name}}
router-link(:active-class="$style.active" v-for="item in userList" :to="`list?id=${item._id}`" :key="item._id") {{item.name}}
router-link(:active-class="($route.query.id === defaultList.id || $route.query.id == '') ? $style.active : ''" :to="`list?id=${defaultList.id || ''}`") {{$t('core.aside.default_list')}}
router-link(:active-class="$route.query.id === loveList.id ? $style.active : ''" :to="`list?id=${loveList.id}`") {{$t('core.aside.love_list')}}
router-link(:active-class="$route.query.id === item.id ? $style.active : ''" v-for="item in userList" :to="`list?id=${item._id}`" :key="item._id") {{item.name}}
dl
dt 其他
dt {{$t('core.aside.other')}}
dd
router-link(:active-class="$style.active" to="download") 下载管理
router-link(:active-class="$style.active" to="download") {{$t('core.aside.download')}}
dd
router-link(:active-class="$style.active" to="setting") 设置
router-link(:active-class="$style.active" to="setting") {{$t('core.aside.setting')}}
</template>
<script>
import { mapGetters } from 'vuex'
// import { getRandom } from '../../utils'
export default {
props: {
list: {
@@ -40,12 +42,34 @@ export default {
data() {
return {
active: 'search',
animates: [
'bounce',
// 'flash',
// 'pulse',
'rubberBand',
// 'shake',
// 'headShake',
'swing',
'tada',
// 'wobble',
'jello',
// 'heartBeat',
],
logoAnimate: '',
}
},
computed: {
...mapGetters('list', ['defaultList', 'userList']),
...mapGetters('list', ['defaultList', 'loveList', 'userList']),
},
methods: {},
// mounted() {
// this.logoAnimate = this.animates[getRandom(0, this.animates.length)]
// },
// methods: {
// handleMouseEnter() {
// console.log('object')
// this.logoAnimate = this.animates[getRandom(0, this.animates.length)]
// },
// },
}
</script>
@@ -65,12 +89,18 @@ export default {
display: flex;
flex-flow: column nowrap;
}
:global(.nd) {
.aside {
-webkit-app-region: no-drag;
}
}
.logo {
box-sizing: border-box;
padding: 20px;
height: 100px;
padding: 12% 13%;
// height: 120px;
color: @color-theme-font;
flex: none;
// -webkit-app-region: no-drag;
}
.menu {
@@ -114,6 +144,9 @@ export default {
&:hover:not(.active) {
background-color: @color-theme-hover;
}
&:hover:not(.active) {
background-color: @color-theme-active;
}
}
}
}
@@ -140,6 +173,9 @@ each(@themes, {
&:hover:not(.active) {
background-color: ~'@{color-@{value}-theme-hover}';
}
&:active:not(.active) {
background-color: ~'@{color-@{value}-theme-active}';
}
}
}
}

View File

@@ -14,6 +14,7 @@ svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/19
g#icon-addTo
path(fill='currentColor' d='M37.059,16H26V4.941C26,2.224,23.718,0,21,0s-5,2.224-5,4.941V16H4.941C2.224,16,0,18.282,0,21s2.224,5,4.941,5H16v11.059C16,39.776,18.282,42,21,42s5-2.224,5-4.941V26h11.059C39.776,26,42,23.718,42,21S39.776,16,37.059,16z')
g#icon-delete
// 0 0 212.982 212.982
path(fill='currentColor' d='M131.804,106.491l75.936-75.936c6.99-6.99,6.99-18.323,0-25.312c-6.99-6.99-18.322-6.99-25.312,0l-75.937,75.937L30.554,5.242c-6.99-6.99-18.322-6.99-25.312,0c-6.989,6.99-6.989,18.323,0,25.312l75.937,75.936L5.242,182.427c-6.989,6.99-6.989,18.323,0,25.312c6.99,6.99,18.322,6.99,25.312,0l75.937-75.937l75.937,75.937c6.989,6.99,18.322,6.99,25.312,0c6.99-6.99,6.99-18.322,0-25.312L131.804,106.491z')
g#icon-left
// 451.847 451.847
@@ -69,5 +70,14 @@ svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/19
g#icon-down
// 0 0 451.847 451.847
path(fill='currentColor' d='M225.923,354.706c-8.098,0-16.195-3.092-22.369-9.263L9.27,151.157c-12.359-12.359-12.359-32.397,0-44.751c12.354-12.354,32.388-12.354,44.748,0l171.905,171.915l171.906-171.909c12.359-12.354,32.391-12.354,44.744,0c12.365,12.354,12.365,32.392,0,44.751L248.292,345.449C242.115,351.621,234.018,354.706,225.923,354.706z')
g#icon-back
// 0 0 512 512
path(fill='currentColor' 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')
g#icon-refresh
// 0 0 512 512
path(fill='currentColor' d='M440.65 12.57l4 82.77A247.16 247.16 0 0 0 255.83 8C134.73 8 33.91 94.92 12.29 209.82A12 12 0 0 0 24.09 224h49.05a12 12 0 0 0 11.67-9.26 175.91 175.91 0 0 1 317-56.94l-101.46-4.86a12 12 0 0 0-12.57 12v47.41a12 12 0 0 0 12 12H500a12 12 0 0 0 12-12V12a12 12 0 0 0-12-12h-47.37a12 12 0 0 0-11.98 12.57zM255.83 432a175.61 175.61 0 0 1-146-77.8l101.8 4.87a12 12 0 0 0 12.57-12v-47.4a12 12 0 0 0-12-12H12a12 12 0 0 0-12 12V500a12 12 0 0 0 12 12h47.35a12 12 0 0 0 12-12.6l-4.15-82.57A247.17 247.17 0 0 0 255.83 504c121.11 0 221.93-86.92 243.55-201.82a12 12 0 0 0-11.8-14.18h-49.05a12 12 0 0 0-11.67 9.26A175.86 175.86 0 0 1 255.83 432z')
g#icon-eraser
// 0 0 512 512
path(fill='currentColor' d='M497.941 273.941c18.745-18.745 18.745-49.137 0-67.882l-160-160c-18.745-18.745-49.136-18.746-67.883 0l-256 256c-18.745 18.745-18.745 49.137 0 67.882l96 96A48.004 48.004 0 0 0 144 480h356c6.627 0 12-5.373 12-12v-40c0-6.627-5.373-12-12-12H355.883l142.058-142.059zm-302.627-62.627l137.373 137.373L265.373 416H150.628l-80-80 124.686-124.686z')
</template>

View File

@@ -1,37 +1,45 @@
<template lang="pug">
div(:class="$style.player")
div(:class="$style.left")
div(:class="$style.left" @contextmenu="handleToMusicLocation" @click="showPlayerDetail")
img(v-if="musicInfo.img" :src="musicInfo.img" @error="imgError")
svg(v-else version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='100%' height='100%' viewBox='0 0 60 60' space='preserve')
use(:xlink:href='`#${$style.iconPic}`')
div(:class="$style.right")
div(:class="$style.right" v-if="!isShowPlayerDetail")
div(:class="$style.column1")
div(:class="$style.container")
div(:class="$style.title" @click="handleCopy(title)" :title="title + '(点击复制)'") {{title}}
div(:class="$style.title" @click="handleCopy(title)" :title="title + $t('core.player.copy_title')") {{title}}
div(:class="$style.volumeContent")
div(:class="$style.volume" @click.stop='handleChangeVolume' :title="`当前音量:${volumeStr}%`")
div(:class="$style.volumeBar" :style="{ width: volumeStr + '%' }")
div(:class="$style.volume")
div(:class="$style.volumeBar" :style="{ transform: `scaleX(${volume || 0})` }")
div(:class="$style.volumeMask" @mousedown="handleVolumeMsDown" ref="dom_volumeMask" :title="`${$t('core.player.volume')}${parseInt(volume * 100)}%`")
//- div(:class="$style.playBtn" @click='handleNext' title="音量")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 291.063 291.064' space='preserve')
use(xlink:href='#icon-sound')
div(:class="$style.playBtn" @click='handleNext' title="下一首")
div(:class="$style.playBtn" @click='handleNext' :title="$t('core.player.next')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve')
use(xlink:href='#icon-nextMusic')
div(:class="$style.playBtn" :title="isPlay ? '暂停' : '播放'" @click='togglePlay')
div(:class="$style.playBtn" :title="isPlay ? $t('core.player.pause') : $t('core.player.play')" @click='togglePlay')
svg(v-if="isPlay" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 277.338 277.338' space='preserve')
use(xlink:href='#icon-pause')
svg(v-else version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 170 170' space='preserve')
use(xlink:href='#icon-play')
div(:class="$style.column2" @click='setProgess' ref="dom_progress")
div(:class="$style.progress")
//- div(:class="[$style.progressBar, $style.progressBar1]" :style="{ width: progress + '%' }")
div(:class="[$style.progressBar, $style.progressBar2]" :style="{ width: (progress * 100 || 0) + '%' }")
div(:class="$style.column2")
div(:class="$style.progress" v-if="!isShowPlayerDetail")
//- div(:class="[$style.progressBar, $style.progressBar1]" :style="{ transform: `scaleX(${progress || 0})` }")
div(:class="[$style.progressBar, $style.progressBar2, isActiveTransition ? $style.barTransition : '']" @transitionend="handleTransitionEnd" :style="{ transform: `scaleX(${progress || 0})` }")
div(:class="$style.progressMask" @click='handleSetProgress' ref="dom_progress")
div(:class="$style.column3")
span {{nowPlayTimeStr}}
span(:class="$style.statusText") {{status}}
span(:class="$style.statusText") {{statusText}}
span {{maxPlayTimeStr}}
//- transition(enter-active-class="animated lightSpeedIn"
transition(enter-active-class="animated lightSpeedIn"
leave-active-class="animated slideOutDown")
core-player-detail(v-if="isShowPlayerDetail" :musicInfo="musicInfo"
:lyric="lyric" :list="list" :listId="listId"
:playInfo="{ nowPlayTimeStr, maxPlayTimeStr, progress, nowPlayTime, status }"
:isPlay="isPlay" @action="handlePlayDetailAction")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' style="display: none;")
defs
g(:id="$style.iconPic")
@@ -47,10 +55,12 @@ div(:class="$style.player")
<script>
import Lyric from 'lrc-file-parser'
import { rendererSend } from '../../../common/icp'
import { formatPlayTime2, getRandom, checkPath, setTitle, clipboardWriteText } from '../../utils'
import { rendererSend } from '../../../common/ipc'
import { formatPlayTime2, getRandom, checkPath, setTitle, clipboardWriteText, debounce, assertApiSupport } from '../../utils'
import { mapGetters, mapActions, mapMutations } from 'vuex'
import { requestMsg } from '../../utils/message'
import { isMac } from '../../../common/utils'
import path from 'path'
export default {
data() {
@@ -61,37 +71,50 @@ export default {
nowPlayTime: 0,
maxPlayTime: 0,
isPlay: false,
status: '^-^',
status: '',
statusText: '^-^',
musicInfo: {
songmid: null,
img: null,
lrc: null,
url: null,
name: '^',
singer: '^',
name: '',
singer: '',
album: '',
},
targetSong: null,
pregessWidth: 0,
lyric: {
lrc: null,
lines: [],
text: '',
line: 0,
},
delayNextTimeout: null,
audioErrorTime: 0,
retryNum: 0,
isMac,
volumeEvent: {
isMsDown: false,
msDownX: 0,
msDownVolume: 0,
},
isActiveTransition: false,
mediaBuffer: {
timeout: null,
playTime: 0,
},
}
},
computed: {
...mapGetters(['setting']),
...mapGetters('player', ['list', 'playIndex', 'changePlay', 'listId']),
...mapGetters('player', ['list', 'playIndex', 'changePlay', 'listId', 'isShowPlayerDetail']),
// pic() {
// return this.musicInfo.img ? this.musicInfo.img : ''
// },
title() {
return this.musicInfo.name
? `${this.musicInfo.name} - ${this.musicInfo.singer}`
: ''
: '^-^'
},
nowPlayTimeStr() {
return this.nowPlayTime ? formatPlayTime2(this.nowPlayTime) : '00:00'
@@ -100,21 +123,26 @@ export default {
return this.maxPlayTime ? formatPlayTime2(this.maxPlayTime) : '00:00'
},
progress() {
// return 50
return this.nowPlayTime / this.maxPlayTime || 0
},
isAPITemp() {
return this.setting.apiSource == 'temp'
},
volumeStr() {
return parseInt(this.volume * 100)
},
},
mounted() {
this.init()
this.$nextTick(() => {
this.setProgessWidth()
this.setProgressWidth()
})
this.handleSaveVolume = debounce(volume => {
this.setVolume(volume)
}, 300)
document.addEventListener('mousemove', this.handleVolumeMsMove)
document.addEventListener('mouseup', this.handleVolumeMsUp)
window.addEventListener('resize', this.handleResize)
},
beforeDestroy() {
document.removeEventListener('mousemove', this.handleVolumeMsMove)
document.removeEventListener('mouseup', this.handleVolumeMsUp)
window.removeEventListener('resize', this.handleResize)
},
watch: {
changePlay(n) {
@@ -129,6 +157,9 @@ export default {
'setting.player.togglePlayMethod'(n) {
this.audio.loop = n === 'singleLoop'
},
'setting.player.mediaDeviceId'(n) {
this.setMediaDevice()
},
list(n, o) {
if (n === o) {
let index = this.listId == 'download'
@@ -155,6 +186,9 @@ export default {
volume(n) {
this.handleSaveVolume(n)
},
nowPlayTime(n, o) {
if (Math.abs(n - o) > 2) this.isActiveTransition = true
},
},
methods: {
...mapActions('player', ['getUrl', 'getPic', 'getLrc']),
@@ -162,11 +196,14 @@ export default {
'setPlayIndex',
'fixPlayIndex',
'resetChangePlay',
'visiblePlayerDetail',
]),
...mapMutations(['setVolume']),
...mapMutations('list', ['updateMusicInfo']),
...mapMutations(['setMediaDeviceId']),
init() {
this.audio = document.createElement('audio')
this.setMediaDevice()
this.volume = this.audio.volume = this.setting.player.volume
this.audio.controls = false
this.audio.autoplay = true
@@ -175,19 +212,20 @@ export default {
this.audio.addEventListener('playing', () => {
console.log('开始播放')
this.status = '播放中...'
this.statusText = this.$t('core.player.playing')
this.status = ''
this.startPlay()
})
this.audio.addEventListener('pause', () => {
console.log('暂停播放')
this.lyric.lrc.pause()
window.lrc.pause()
this.stopPlay()
this.status = '暂停播放'
// this.status = this.statusText = this.$t('core.player.stop')
})
this.audio.addEventListener('ended', () => {
console.log('播放完毕')
this.stopPlay()
this.status = '播放完毕'
this.status = this.statusText = this.$t('core.player.end')
this.handleNext()
})
this.audio.addEventListener('error', () => {
@@ -195,17 +233,17 @@ export default {
if (!this.musicInfo.songmid) return
console.log('出错')
this.stopPlay()
if (this.listId != 'download' && this.audio.error.code !== 1 && this.retryNum < 3) { // 若音频URL无效则尝试刷新3次URL
if (this.listId != 'download' && this.audio.error.code !== 1 && this.retryNum < 2) { // 若音频URL无效则尝试刷新2次URL
// console.log(this.retryNum)
this.audioErrorTime = this.audio.currentTime // 记录出错的播放时间
if (!this.audioErrorTime) this.audioErrorTime = this.audio.currentTime // 记录出错的播放时间
this.retryNum++
this.setUrl(this.list[this.playIndex], true)
this.status = 'URL过期正在刷新URL...'
this.status = this.statusText = this.$t('core.player.refresh_url')
return
}
this.sendProgressEvent(this.progress, 'error')
this.status = '音频加载出错5 秒后切换下一首'
this.status = this.statusText = this.$t('core.player.error')
this.addDelayNextTimeout()
})
this.audio.addEventListener('loadeddata', () => {
@@ -214,26 +252,37 @@ export default {
this.audio.currentTime = this.audioErrorTime
this.audioErrorTime = 0
}
if (!this.targetSong.interval && this.listId != 'download') this.updateMusicInfo({ index: this.playIndex, data: { interval: formatPlayTime2(this.maxPlayTime) } })
this.status = '音乐加载中...'
if (!this.targetSong.interval && this.listId != 'download') this.updateMusicInfo({ id: 'default', index: this.playIndex, data: { interval: formatPlayTime2(this.maxPlayTime) } })
this.status = this.statusText = this.$t('core.player.loading')
})
this.audio.addEventListener('loadstart', () => {
this.status = '音乐加载中...'
this.status = this.statusText = this.$t('core.player.loading')
})
this.audio.addEventListener('canplay', () => {
console.log('加载完成开始播放')
// if (this.musicInfo.lrc) this.lyric.lrc.play(this.audio.currentTime * 1000)
this.status = '音乐加载中...'
if (this.mediaBuffer.playTime) {
let playTime = this.mediaBuffer.playTime
this.mediaBuffer.playTime = 0
this.audio.currentTime = playTime
}
if (this.mediaBuffer.timeout) {
this.clearBufferTimeout()
}
// if (this.musicInfo.lrc) window.lrc.play(this.audio.currentTime * 1000)
this.status = this.statusText = this.$t('core.player.loading')
})
// this.audio.addEventListener('canplaythrough', () => {
// console.log('音乐加载完毕')
// // if (this.musicInfo.lyric.orgLrc) this.musicInfo.lyric.lrc.play(this.audio.currentTime * 1000)
// this.status = '播放中...'
// })
// this.audio.addEventListener('emptied', () => {
// console.log('媒介资源元素突然为空,网络错误 or 切换歌曲?')
// this.status = '媒介资源元素突然为空,网络错误?'
// this.status = this.statusText = '播放中...'
// })
this.audio.addEventListener('emptied', () => {
this.mediaBuffer.playTime = 0
this.clearBufferTimeout()
// console.log('媒介资源元素突然为空,网络错误 or 切换歌曲?')
// this.status = this.statusText = '媒介资源元素突然为空,网络错误?'
})
this.audio.addEventListener('timeupdate', () => {
this.nowPlayTime = this.audio.currentTime
@@ -241,21 +290,28 @@ export default {
this.audio.addEventListener('waiting', () => {
// this.musicInfo.lyric.lrc.pause()
// console.log('缓冲中...')
this.stopPlay()
this.status = '缓冲中...'
this.startBuffering()
this.status = this.statusText = this.$t('core.player.buffering')
})
this.lyric.lrc = new Lyric({
window.lrc = new Lyric({
onPlay: (line, text) => {
this.lyric.text = text
this.lyric.line = line
this.status = text
this.statusText = text
// console.log(line, text)
},
offset: 150,
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,
})
},
play() {
async play() {
console.log('play', this.playIndex)
this.checkDelayNextTimeout()
let targetSong = this.targetSong = this.list[this.playIndex]
@@ -263,21 +319,24 @@ export default {
this.audioErrorTime = 0
if (this.listId == 'download') {
console.log(targetSong.filePath)
if (!checkPath(targetSong.filePath) || !targetSong.isComplate || /\.ape$/.test(targetSong.filePath)) {
const filePath = path.join(this.setting.download.savePath, targetSong.fileName)
// console.log(filePath)
if (!await checkPath(filePath) || !targetSong.isComplate || /\.ape$/.test(filePath)) {
return this.list.length == 1 ? null : this.handleNext()
}
this.musicInfo.songmid = targetSong.musicInfo.songmid
this.musicInfo.singer = targetSong.musicInfo.singer
this.musicInfo.name = targetSong.musicInfo.name
this.audio.src = targetSong.filePath
// console.log(targetSong.filePath)
this.musicInfo.album = targetSong.albumName
this.audio.src = filePath
// console.log(filePath)
this.setImg(targetSong.musicInfo)
this.setLrc(targetSong.musicInfo)
} else {
this.musicInfo.songmid = targetSong.songmid
this.musicInfo.singer = targetSong.singer
this.musicInfo.name = targetSong.name
this.musicInfo.album = targetSong.albumName
this.setUrl(targetSong)
this.setImg(targetSong)
this.setLrc(targetSong)
@@ -297,97 +356,132 @@ export default {
this.handleNext()
}, 5000)
},
handleNext() {
async filterList() {
// if (this.list.listName === null) return
let list
if (this.listId == 'download') {
list = this.list.filter(s => !(!checkPath(s.filePath) || !s.isComplate || /\.ape$/.test(s.filePath)))
} else if (this.isAPITemp) {
list = this.list.filter(s => s.source == 'kw')
list = []
for (const item of this.list) {
const filePath = path.join(this.setting.download.savePath, item.fileName)
if (!await checkPath(filePath) || !item.isComplate || /\.ape$/.test(filePath)) continue
list.push(item)
}
} else {
list = this.list
list = this.list.filter(s => this.assertApiSupport(s.source))
}
if (!list.length) return this.setPlayIndex(-1)
let playIndex = this.list === list ? this.playIndex : list.indexOf(this.list[this.playIndex])
return list
},
async handlePrev() {
// console.log(playIndex)
let list = await this.filterList()
if (!list.length) return this.setPlayIndex(-1)
let playIndex = list.indexOf(this.list[this.playIndex])
let index
switch (this.setting.player.togglePlayMethod) {
case 'listLoop':
index = this.hanldeListLoop(list, playIndex)
break
case 'random':
index = this.hanldeListRandom(list, playIndex)
break
case 'listLoop':
case 'list':
index = this.hanldeListNext(list, playIndex)
index = playIndex === 0 ? list.length - 1 : playIndex - 1
break
default:
return
}
if (index < 0) return
if (this.list !== list) index = this.list.indexOf(list[index])
index = this.list.indexOf(list[index])
this.setPlayIndex(index)
},
hanldeListLoop(list, index) {
return index === list.length - 1 ? 0 : index + 1
},
hanldeListNext(list, index) {
return index === list.length - 1 ? -1 : index + 1
async handleNext() {
// if (this.list.listName === null) return
let list = await this.filterList()
if (!list.length) return this.setPlayIndex(-1)
let playIndex = list.indexOf(this.list[this.playIndex])
// console.log(playIndex)
let index
switch (this.setting.player.togglePlayMethod) {
case 'listLoop':
index = playIndex === list.length - 1 ? 0 : playIndex + 1
break
case 'random':
index = this.hanldeListRandom(list, playIndex)
break
case 'list':
index = playIndex === list.length - 1 ? -1 : playIndex + 1
break
default:
return
}
if (index < 0) return
index = this.list.indexOf(list[index])
this.setPlayIndex(index)
},
hanldeListRandom(list, index) {
return getRandom(0, list.length)
},
startPlay() {
this.isPlay = true
if (this.musicInfo.lrc) this.lyric.lrc.play(this.audio.currentTime * 1000)
if (this.musicInfo.lrc) window.lrc.play(this.audio.currentTime * 1000)
this.setAppTitle()
this.sendProgressEvent(this.progress, 'normal')
},
stopPlay() {
this.isPlay = false
this.lyric.lrc.pause()
window.lrc.pause()
this.sendProgressEvent(this.progress, 'paused')
this.clearAppTitle()
},
setProgess(e) {
handleSetProgress(event) {
this.setProgress(event.offsetX / this.pregessWidth)
},
setProgress(pregress) {
if (!this.audio.src) return
this.audio.currentTime =
(e.offsetX / this.pregessWidth) * this.maxPlayTime
const time = pregress * this.maxPlayTime
if (this.audioErrorTime) this.audioErrorTime = time
if (this.mediaBuffer.playTime) {
this.clearBufferTimeout()
this.mediaBuffer.playTime = time
this.startBuffering()
}
this.audio.currentTime = time
if (!this.isPlay) this.audio.play()
},
setProgessWidth() {
setProgressWidth() {
this.pregessWidth = parseInt(
window.getComputedStyle(this.$refs.dom_progress, null).width
window.getComputedStyle(this.$refs.dom_progress, null).width,
)
},
togglePlay() {
if (!this.audio.src) return
this.isPlay ? this.audio.pause() : this.audio.play()
if (this.isPlay) {
this.audio.pause()
this.clearBufferTimeout()
} else {
this.audio.play()
}
},
imgError(e) {
// e.target.src = 'https://y.gtimg.cn/music/photo_new/T002R500x500M000002BMEC42fM8S3.jpg'
this.musicInfo.img = null
},
getPlayType(highQuality, songInfo) {
switch (songInfo.source) {
case 'wy':
case 'kg':
return '128k'
}
let type = songInfo._types['192k'] ? '192k' : '128k'
if (highQuality && songInfo._types['320k']) type = '320k'
let type = '128k'
let list = window.globalObj.qualityList[songInfo.source]
if (highQuality && songInfo._types['320k'] && list && list.includes('320k')) type = '320k'
return type
},
setUrl(targetSong, isRefresh) {
setUrl(targetSong, isRefresh, isRetryed = false) {
let type = this.getPlayType(this.setting.player.highQuality, targetSong)
this.musicInfo.url = targetSong.typeUrl[type]
this.status = '歌曲链接获取中...'
this.status = this.statusText = this.$t('core.player.geting_url')
return this.getUrl({ musicInfo: targetSong, type, isRefresh }).then(() => {
this.audio.src = this.musicInfo.url = targetSong.typeUrl[type]
}).catch(err => {
if (err.message == requestMsg.cancelRequest) return
this.status = err.message
if (!isRetryed) return this.setUrl(targetSong, isRefresh, true)
this.status = this.statusText = err.message
this.addDelayNextTimeout()
return Promise.reject(err)
})
@@ -412,25 +506,28 @@ export default {
lrcP
.then(() => {
this.lyric.lrc.setLyric(this.musicInfo.lrc)
if (this.isPlay && (this.musicInfo.url || this.listId == 'download')) this.lyric.lrc.play(this.audio.currentTime * 1000)
window.lrc.setLyric(this.musicInfo.lrc)
if (this.isPlay && (this.musicInfo.url || this.listId == 'download')) window.lrc.play(this.audio.currentTime * 1000)
})
.catch(() => {
this.status = '歌词获取失败'
this.status = this.statusText = this.$t('core.player.lyric_error')
})
},
handleRemoveMusic() {
this.stopPlay()
this.audio.src = null
this.audio.removeAttribute('src')
this.status = '^-^'
this.statusText = '^-^'
this.musicInfo.img = null
this.musicInfo.name = this.musicInfo.singer = '^'
this.status = this.musicInfo.name = this.musicInfo.singer = ''
this.musicInfo.songmid = null
this.musicInfo.lrc = null
this.musicInfo.url = null
this.nowPlayTime = 0
this.maxPlayTime = 0
this.lyric.lines = []
this.lyric.line = 0
this.lyric.text = 0
},
sendProgressEvent(status, mode) {
// console.log(status)
@@ -445,20 +542,112 @@ export default {
clearAppTitle() {
setTitle()
},
handleChangeVolume(e) {
handleVolumeMsDown(e) {
this.volumeEvent.isMsDown = true
this.volumeEvent.msDownX = e.clientX
let val = e.offsetX / 70
if (val < 0) val = 0
if (val > 1) val = 1
if (val > 0.97) val = 1
this.volume = val
this.volumeEvent.msDownVolume = val
// console.log(val)
if (this.audio) this.audio.volume = this.volume
},
handleSaveVolume(volume) {
this.setVolume(volume)
handleVolumeMsUp(e) {
this.volumeEvent.isMsDown = false
},
handleVolumeMsMove(e) {
if (!this.volumeEvent.isMsDown) return
let val = this.volumeEvent.msDownVolume + (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)
},
handleCopy(text) {
clipboardWriteText(text)
},
handleResize() {
this.setProgressWidth()
},
handleToMusicLocation() {
if (this.listId == 'download') return
if (this.playIndex == -1) return
this.$router.push({
path: 'list',
query: {
id: this.listId,
scrollIndex: this.playIndex,
},
})
},
showPlayerDetail() {
this.visiblePlayerDetail(true)
},
handleTransitionEnd(e) {
// console.log(e)
this.isActiveTransition = false
},
startBuffering() {
console.log('start t')
if (this.mediaBuffer.timeout) return
this.mediaBuffer.timeout = setTimeout(() => {
this.mediaBuffer.timeout = null
if (!this.mediaBuffer.playTime) this.mediaBuffer.playTime = this.audio.currentTime
let skipTime = this.audio.currentTime + getRandom(3, 6)
if (skipTime > this.maxPlayTime) skipTime = (this.maxPlayTime - this.audio.currentTime) / 2
if (skipTime - this.mediaBuffer.playTime < 1 || this.maxPlayTime - skipTime < 1) {
this.mediaBuffer.playTime = 0
this.handleNext()
return
}
this.startBuffering()
this.audio.currentTime = skipTime
console.log(this.mediaBuffer.playTime)
console.log(this.audio.currentTime)
}, 3000)
},
clearBufferTimeout() {
console.log('clear t')
if (!this.mediaBuffer.timeout) return
clearTimeout(this.mediaBuffer.timeout)
this.mediaBuffer.timeout = null
this.mediaBuffer.playTime = 0
},
async setMediaDevice() {
let mediaDeviceId = this.setting.player.mediaDeviceId
if (!mediaDeviceId) return
const devices = await navigator.mediaDevices.enumerateDevices()
let device = devices.find(device => device.deviceId === mediaDeviceId)
if (!device) return this.setMediaDeviceId('default')
// console.log(device)
this.audio.setSinkId(device.deviceId).catch((err) => {
console.log(err)
this.setMediaDeviceId('default')
})
},
handlePlayDetailAction({ type, data }) {
switch (type) {
case 'prev':
this.handlePrev()
break
case 'togglePlay':
this.togglePlay()
break
case 'next':
this.handleNext()
break
case 'progress':
this.setProgress(data)
break
case 'volume':
break
}
},
assertApiSupport(source) {
return assertApiSupport(source)
},
},
}
</script>
@@ -475,17 +664,27 @@ export default {
border-top: 2px solid @color-theme;
box-sizing: border-box;
display: flex;
z-index: 1;
z-index: 2;
* {
box-sizing: border-box;
}
}
.left {
width: @height-player;
width: @height-player - 2;
height: @height-player - 2;
color: @color-theme;
transition: @transition-theme;
transition-property: color;
flex: none;
padding: 2px;
opacity: 1;
transition: @transition-theme;
transition-property: opacity;
cursor: pointer;
&:hover {
opacity: .8;
}
svg {
fill: currentColor;
@@ -495,13 +694,13 @@ export default {
max-height: 100%;
transition: @transition-theme;
transition-property: border-color;
border: 2px solid @color-theme_2-background_2;
// border: 2px solid @color-theme_2-background_1;
}
}
.right {
flex: auto;
// margin-left: 10px;
padding: 5px 10px;
padding: 5px 10px 5px 8px;
display: flex;
flex-flow: column nowrap;
}
@@ -530,21 +729,27 @@ export default {
}
.volume-content {
position: relative;
width: 100px;
display: flex;
align-items: center;
padding: 0 15px;
opacity: .5;
transition: opacity @transition-theme;
&:hover {
opacity: 1;
}
}
.volume {
cursor: pointer;
// cursor: pointer;
width: 100%;
height: 0.25em;
border-radius: 10px;
// overflow: hidden;
transition: @transition-theme;
transition-property: background-color;
background-color: @color-player-progress;
background-color: @color-player-progress-bar1;
// background-color: #f5f5f5;
position: relative;
border-radius: @radius-progress-border;
@@ -554,13 +759,26 @@ export default {
position: absolute;
left: 0;
top: 0;
width: 0;
transform: scaleX(0);
transform-origin: 0;
transition-property: transform;
transition-timing-function: ease;
width: 100%;
height: 100%;
border-radius: @radius-progress-border;
transition-duration: 0.2s;
background-color: @color-theme;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.2);
opacity: .5;
}
.volume-mask {
position: absolute;
left: 15px;
right: 15px;
top: 0;
width: 70%;
height: 100%;
cursor: pointer;
}
.play-btn {
@@ -575,7 +793,7 @@ export default {
transition: @transition-theme;
transition-property: color;
color: @color-theme;
transition: opacity 0.2s ease;
transition: opacity 0.1s ease;
opacity: 1;
cursor: pointer;
@@ -591,7 +809,7 @@ export default {
.column2 {
flex: none;
padding: 3px 0;
cursor: pointer;
position: relative;
}
.progress {
@@ -606,25 +824,38 @@ export default {
position: relative;
border-radius: @radius-progress-border;
}
.progress-mask {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
cursor: pointer;
}
.progress-bar {
position: absolute;
left: 0;
top: 0;
width: 0;
width: 100%;
height: 100%;
transform-origin: 0;
border-radius: @radius-progress-border;
}
.progress-bar1 {
transition-duration: 0.6s;
background-color: @color-player-progress-bar1;
}
.progress-bar2 {
transition-duration: 0.2s;
background-color: @color-player-progress-bar2;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
}
.bar-transition {
transition-property: transform;
transition-timing-function: ease-out;
transition-duration: 0.2s;
}
.column3 {
transition: @transition-theme;
transition-property: color;
@@ -672,9 +903,9 @@ each(@themes, {
}
.left {
color: ~'@{color-@{value}-theme}';
img {
border-color: ~'@{color-@{value}-theme_2-background_2}';
}
// img {
// border-color: ~'@{color-@{value}-theme_2-background_1}';
// }
}
.play-btn {
color: ~'@{color-@{value}-theme}';
@@ -683,12 +914,11 @@ each(@themes, {
}
}
.volume {
background-color: ~'@{color-@{value}-player-progress}';
background-color: ~'@{color-@{value}-player-progress-bar1}';
}
.volume-bar {
background-color: ~'@{color-@{value}-theme}';
box-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
}
@@ -700,7 +930,6 @@ each(@themes, {
}
.progress-bar2 {
background-color: ~'@{color-@{value}-player-progress-bar2}';
box-shadow: 0 0 2px rgba(0, 0, 0, 0.2);
}
.column3 {

View File

@@ -0,0 +1,705 @@
<template lang="pug">
div(:class="$style.container" @contextmenu="handleContextMenu")
//- div(:class="$style.bg" :style="bgStyle")
//- div(:class="$style.bg2")
div(:class="$style.header")
div(:class="$style.control")
button(type="button" :class="$style.hide" :title="$t('core.player.hide_detail')" @click="visiblePlayerDetail(false)")
button(type="button" :class="$style.min" :title="$t('core.toolbar.min')" @click="min")
//- button(type="button" :class="$style.max" @click="max")
button(type="button" :class="$style.close" :title="$t('core.toolbar.close')" @click="close")
div(:class="$style.main")
div(:class="$style.left")
div(:class="$style.info")
div(:class="$style.img")
img(:src="musicInfo.img" v-if="musicInfo.img")
div(:class="$style.description")
p {{$t('core.player.name')}}{{musicInfo.name}}
p {{$t('core.player.singer')}}{{musicInfo.singer}}
p(v-if="musicInfo.album") {{$t('core.player.album')}}{{musicInfo.album}}
//- div(:class="$style.list")
ul
div(:class="$style.right")
div(:class="[$style.lyric, lyricEvent.isMsDown ? $style.draging : null]" @mousedown="handleLyricMouseDown" ref="dom_lyric")
div(:class="$style.lyricSpace")
p(v-for="(info, index) in lyric.lines" :key="index" :class="lyric.line == index ? $style.lrcActive : null") {{info.text}}
div(:class="$style.lyricSpace")
div(:class="$style.footer")
div(:class="$style.left")
div(:class="$style.progressContainer")
div(:class="$style.progressContent")
div(:class="$style.progress")
//- div(:class="[$style.progressBar, $style.progressBar1]" :style="{ transform: `scaleX(${progress || 0})` }")
div(:class="[$style.progressBar, $style.progressBar2, isActiveTransition ? $style.barTransition : '']" @transitionend="handleTransitionEnd" :style="{ transform: `scaleX(${playInfo.progress || 0})` }")
div(:class="$style.progressMask" @click='setProgress' ref="dom_progress")
div(:class="$style.timeLabel")
span(style="margin-left: 15px") {{playInfo.status}}
div
span {{playInfo.nowPlayTimeStr}}
span(style="margin: 0 5px;") /
span {{playInfo.maxPlayTimeStr}}
div(:class="$style.playControl")
//- div(:class="$style.playBtn")
//- div(:class="$style.playBtn")
div(:class="$style.playBtn" @click="$emit('action', { type: 'prev' })" style="transform: rotate(180deg);" :title="$t('core.player.prev')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve')
use(xlink:href='#icon-nextMusic')
div(:class="$style.playBtn" :title="isPlay ? $t('core.player.pause') : $t('core.player.play')" @click="$emit('action', { type: 'togglePlay' })")
svg(v-if="isPlay" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 277.338 277.338' space='preserve')
use(xlink:href='#icon-pause')
svg(v-else version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 170 170' space='preserve')
use(xlink:href='#icon-play')
div(:class="$style.playBtn" @click="$emit('action', { type: 'next' })" :title="$t('core.player.next')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve')
use(xlink:href='#icon-nextMusic')
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
import { rendererSend } from 'common/ipc'
import { scrollTo } from '../../utils'
let cancelScrollFn = null
export default {
props: {
musicInfo: {
type: Object,
default() {
return {
songmid: null,
img: null,
lrc: null,
url: null,
name: '^',
singer: '^',
}
},
},
lyric: {
type: Object,
default() {
return {
line: 0,
text: '',
lines: [],
}
},
},
playInfo: {
type: Object,
default() {
return {
nowPlayTimeStr: '00:00',
maxPlayTimeStr: '00:00',
progress: 0,
nowPlayTime: 0,
status: 0,
}
},
},
list: {
type: Array,
default() {
return []
},
},
listId: {
type: String,
default() {
return ''
},
},
isPlay: {
type: Boolean,
default: false,
},
},
watch: {
'musicInfo.img': {
handler(n) {
if (n) {
this.bgStyle.backgroundImage = `url(${n})`
}
},
immediate: true,
},
'lyric.lines': {
handler() {
this.$nextTick(() => {
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('p')
this.handleScrollLrc()
})
},
immediate: true,
},
'lyric.line': {
handler(n) {
this.handleScrollLrc()
},
immediate: true,
},
'playInfo.nowPlayTime'(n, o) {
if (Math.abs(n - o) > 2) this.isActiveTransition = true
},
},
data() {
return {
bgStyle: {
backgroundImage: null,
},
dom_lines: [],
isActiveTransition: false,
pregessWidth: 0,
clickTime: 0,
volumeEvent: {
isMsDown: false,
msDownX: 0,
msDownValue: 0,
},
lyricEvent: {
isMsDown: false,
msDownY: 0,
msDownScrollY: 0,
isStopScroll: false,
timeout: null,
},
}
},
mounted() {
this.$nextTick(() => {
this.setProgressWidth()
})
document.addEventListener('mousemove', this.handleMouseMsMove)
document.addEventListener('mouseup', this.handleMouseMsUp)
window.addEventListener('resize', this.handleResize)
},
beforeDestroy() {
this.clearLyricScrollTimeout()
document.removeEventListener('mousemove', this.handleMouseMsMove)
document.removeEventListener('mouseup', this.handleMouseMsUp)
window.removeEventListener('resize', this.handleResize)
},
computed: {
...mapGetters('player', ['isShowPlayerDetail']),
},
methods: {
...mapMutations('player', [
'visiblePlayerDetail',
]),
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.38) : 0)
},
handleTransitionEnd() {
this.isActiveTransition = false
},
setProgress(event) {
this.$emit('action', {
type: 'progress',
data: event.offsetX / this.pregessWidth,
})
},
setProgressWidth() {
this.pregessWidth = parseInt(
window.getComputedStyle(this.$refs.dom_progress, null).width,
)
},
handleContextMenu() {
if (window.performance.now() - this.clickTime > 400) {
this.clickTime = window.performance.now()
return
}
this.clickTime = 0
this.visiblePlayerDetail(false)
},
handleLyricMouseDown(e) {
// console.log(e)
this.lyricEvent.isMsDown = true
this.lyricEvent.msDownY = e.clientY
this.lyricEvent.msDownScrollY = this.$refs.dom_lyric.scrollTop
},
handleMouseMsUp(e) {
this.lyricEvent.isMsDown = this.volumeEvent.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()
}
// 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)
},
clearLyricScrollTimeout() {
if (!this.lyricEvent.timeout) return
clearTimeout(this.lyricEvent.timeout)
this.lyricEvent.timeout = null
},
min() {
rendererSend('min')
},
max() {
rendererSend('max')
},
close() {
rendererSend('close')
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
@control-btn-width: @height-toolbar * .5;
.container {
position: absolute;
display: flex;
flex-flow: column nowrap;
width: 100%;
height: 100%;
top: 0;
left: 0;
background-color: #fff;
z-index: 10;
// -webkit-app-region: drag;
overflow: hidden;
border-radius: 4px;
color: @color-theme_2-font;
border-left: 12px solid @color-theme;
}
.bg {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
background-size: 110% 110%;
filter: blur(60px);
z-index: -1;
}
.bg2 {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: -1;
background-color: rgba(255, 255, 255, .8);
}
.header {
position: relative;
flex: 0 0 @height-toolbar;
-webkit-app-region: drag;
}
.control {
position: absolute;
right: 0;
top: 0;
display: flex;
align-items: center;
height: @height-toolbar;
-webkit-app-region: no-drag;
padding: 0 @control-btn-width * 0.6;
&:hover {
button:before {
opacity: 1;
}
}
button {
position: relative;
width: @control-btn-width;
height: @control-btn-width;
background: none;
border: none;
display: flex;
justify-content: center;
align-items: center;
outline: none;
padding: 0;
cursor: pointer;
+ button {
margin-left: @control-btn-width * .4;
}
&:after {
content: ' ';
display: block;
border-radius: 50%;
width: 14px;
height: 14px;
transition: background-color 0.2s ease-in-out;
}
&:before {
display: block;
position: absolute;
opacity: 0;
transition: opacity @transition-theme;
}
&.hide:after {
background-color: @color-hideBtn;
}
&.min:after {
background-color: @color-minBtn;
}
&.max:after {
background-color: @color-maxBtn;
}
&.close:after {
background-color: @color-closeBtn;
}
&.hide:hover:after {
background-color: @color-hideBtn-hover;
}
&.min:hover:after {
background-color: @color-minBtn-hover;
opacity: 1;
}
&.max:hover:after {
background-color: @color-maxBtn-hover;
}
&.close:hover:after {
background-color: @color-closeBtn-hover;
}
}
}
.hide {
&:before {
content: '';
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
font-size: 14px;
line-height: 1;
color: #fff;
}
}
.min {
&:before {
content: ' ';
width: 8px;
height: 2px;
left: @control-btn-width / 2 - 4;
top: @control-btn-width / 2 - 1;
background-color: #fff;
}
}
.close {
&:before {
content: '×';
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
font-size: 14px;
line-height: 1;
color: #fff;
}
}
.main {
flex: auto;
min-height: 0;
overflow: hidden;
display: flex;
padding: 0 30px;
}
.left {
flex: 0 0 40%;
overflow: hidden;
}
.info {
display: flex;
flex-flow: column nowrap;
align-items: center;
}
.img {
width: 300px;
height: 300px;
display: flex;
justify-content: flex-start;
align-items: center;
img {
max-width: 100%;
max-height: 100%;
border: 5px solid @color-theme-hover;
// border: 5px solid #fff;
}
}
.description {
width: 300px;
padding: 15px 0;
overflow: hidden;
p {
line-height: 1.5;
font-size: 14px;
overflow-wrap: break-word;
}
}
.right {
flex: 0 0 60%;
// padding: 0 30px;
position: relative;
&:before {
position: absolute;
top: 0;
left: 0;
content: ' ';
height: 100px;
width: 100%;
background-image: linear-gradient(0deg,rgba(255,255,255,0) 0%,#fff 95%);
pointer-events: none;
}
&:after {
position: absolute;
bottom: 0;
left: 0;
content: ' ';
height: 100px;
width: 100%;
background-image: linear-gradient(-180deg,rgba(255,255,255,0) 0%,#fff 95%);
pointer-events: none;
}
}
.lyric {
text-align: center;
height: 100%;
overflow: hidden;
font-size: 16px;
cursor: grab;
&.draging {
cursor: grabbing;
}
p {
padding: 8px 0;
line-height: 1.2;
overflow-wrap: break-word;
transition: @transition-theme;
transition-property: color, font-size;
}
}
.lyric-space {
height: 70%;
}
.lrc-active {
color: @color-theme;
font-size: 1.2em;
}
.footer {
flex: 0 0 100px;
overflow: hidden;
display: flex;
align-items: center;
}
.left {
flex: auto;
display: flex;
flex-flow: column nowrap;
align-items: center;
padding-top: 13px;
}
.progress-container {
width: 100%;
position: relative;
padding: 3px 0;
}
.progress-content {
position: relative;
height: 15px;
padding: 5px 0;
width: 100%;
}
.progress {
height: 100%;
width: 100%;
border-radius: 0.2rem;
// overflow: hidden;
transition: @transition-theme;
transition-property: background-color;
background-color: @color-player-progress;
// background-color: #f5f5f5;
position: relative;
border-radius: @radius-progress-border;
}
.progress-mask {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
cursor: pointer;
}
.progress-bar {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
transform-origin: 0;
border-top-right-radius: @radius-progress-border;
border-bottom-right-radius: @radius-progress-border;
}
.progress-bar1 {
background-color: @color-player-progress-bar1;
}
.progress-bar2 {
background-color: @color-player-progress-bar2;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
border-top-right-radius: 20px;
border-bottom-right-radius: 20px;
}
.bar-transition {
transition-property: transform;
transition-timing-function: ease-out;
transition-duration: 0.2s;
}
.time-label {
width: 100%;
display: flex;
justify-content: space-between;
span {
font-size: 13px;
}
}
.play-control {
flex: none;
height: 100%;
display: flex;
justify-content: flex-end;
align-items: center;
padding: 0 25px;
color: @color-btn;
}
.play-btn {
height: 40%;
padding: 5px;
cursor: pointer;
flex: none;
transition: @transition-theme;
transition-property: color;
color: @color-player-detail-play-btn;
transition: opacity 0.1s ease;
opacity: 1;
cursor: pointer;
+.play-btn {
margin-left: 10px;
}
svg {
fill: currentColor;
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.2));
}
&:hover {
opacity: 0.8;
}
&:active {
opacity: 0.6;
}
}
each(@themes, {
:global(#container.@{value}) {
.container {
border-left-color: ~'@{color-@{value}-theme}';
}
.control {
button {
// &.hide:after {
// background-color: ~'@{color-@{value}-hideBtn}';
// }
&.hide:after {
background-color: ~'@{color-@{value}-hideBtn}';
}
&.min:after {
background-color: ~'@{color-@{value}-minBtn}';
}
&.max:after {
background-color: ~'@{color-@{value}-maxBtn}';
}
&.close:after {
background-color: ~'@{color-@{value}-closeBtn}';
}
&.hide:hover:after {
background-color: ~'@{color-@{value}-hideBtn-hover}';
}
&.min:hover:after {
background-color: ~'@{color-@{value}-minBtn-hover}';
}
&.max:hover:after {
background-color: ~'@{color-@{value}-maxBtn-hover}';
}
&.close:hover:after {
background-color: ~'@{color-@{value}-closeBtn-hover}';
}
}
}
.img {
img {
border-color: ~'@{color-@{value}-theme-hover}';
}
}
.lrc-active {
color: ~'@{color-@{value}-theme}';
}
.progress {
background-color: ~'@{color-@{value}-player-progress}';
}
.progress-bar1 {
background-color: ~'@{color-@{value}-player-progress-bar1}';
}
.progress-bar2 {
background-color: ~'@{color-@{value}-player-progress-bar2}';
}
.play-btn {
color: ~'@{color-@{value}-player-detail-play-btn}';
}
}
})
</style>

View File

@@ -2,31 +2,116 @@
div(:class="$style.toolbar")
//- img(v-if="icon")
//- h1 {{title}}
material-search-input(:class="$style.input")
material-search-input(:class="$style.input"
@event="handleEvent" :list="tipList" :visibleList="visibleList"
v-model="searchText")
div(:class="$style.control")
button(type="button" :class="$style.min" title="最小化" @click="min")
button(type="button" :class="$style.min" :title="$t('core.toolbar.min')" @click="min")
//- button(type="button" :class="$style.max" @click="max")
button(type="button" :class="$style.close" title="关闭" @click="close")
button(type="button" :class="$style.close" :title="$t('core.toolbar.close')" @click="close")
</template>
<script>
import { rendererSend } from 'common/icp'
// import { mapGetters } from 'vuex'
import { rendererSend } from 'common/ipc'
import { mapGetters, mapMutations } from 'vuex'
import music from '../../utils/music'
import { debounce } from '../../utils'
export default {
// props: {
// color: {
// type: String,
// default: color,
// },
// icon: {
// type: Boolean,
// default: false,
// },
// },
data() {
return {
searchText: '',
visibleList: false,
tipList: [],
tipSearch: null,
isFocused: false,
}
},
computed: {
// ...mapGetters(['theme']),
...mapGetters(['route', 'setting']),
...mapGetters('search', {
storeSearchText: 'searchText',
}),
source() {
return this.setting.search.tempSearchSource
},
isAutoClearSearchInput() {
return this.setting.odc.isAutoClearSearchInput
},
isAutoClearSearchList() {
return this.setting.odc.isAutoClearSearchList
},
},
watch: {
route(n) {
if (n.name != 'search') {
if (this.isAutoClearSearchInput && this.searchText) this.searchText = ''
if (this.isAutoClearSearchList) this.clearSearchList()
}
},
storeSearchText(n) {
if (n !== this.searchText) this.searchText = n
},
searchText(n) {
this.handleTipSearch()
},
},
mounted() {
this.tipSearch = debounce(() => {
if (this.searchText === '') {
this.tipList.splice(0, this.tipList.length)
music[this.source].tempSearch.cancelTempSearch()
return
}
music[this.source].tempSearch.search(this.searchText).then(list => {
this.tipList = list
}).catch(() => {})
}, 50)
},
methods: {
...mapMutations('search', {
clearSearchList: 'clearList',
}),
handleEvent({ action, data }) {
switch (action) {
case 'focus':
this.isFocused = true
if (!this.visibleList) this.visibleList = true
if (this.searchText) this.handleTipSearch()
break
case 'blur':
this.isFocused = false
setTimeout(() => {
if (this.visibleList) this.visibleList = false
}, 50)
break
case 'submit':
this.handleSearch()
break
case 'listClick':
this.searchText = this.tipList[data]
this.$nextTick(() => {
this.handleSearch()
})
}
},
handleTipSearch() {
if (!this.visibleList && this.isFocused) this.visibleList = true
this.tipSearch()
},
handleSearch() {
if (this.visibleList) this.visibleList = false
setTimeout(() => {
this.$router.push({
path: 'search',
query: {
text: this.searchText,
},
}).catch(_ => _)
}, 200)
},
min() {
rendererSend('min')
},
@@ -44,6 +129,8 @@ export default {
<style lang="less" module>
@import '../../assets/styles/layout.less';
@control-btn-width: @height-toolbar * .5;
.toolbar {
display: flex;
height: @height-toolbar;
@@ -51,11 +138,19 @@ export default {
align-items: center;
padding-left: 15px;
-webkit-app-region: drag;
z-index: 1;
z-index: 2;
position: relative;
}
:global(.nd) {
.toolbar {
-webkit-app-region: no-drag;
}
}
.input {
-webkit-app-region: no-drag;
position: absolute;
left: 15px;
top: 13px;
}
// img {
@@ -73,8 +168,10 @@ export default {
.control {
display: flex;
align-items: center;
height: 100%;
-webkit-app-region: no-drag;
padding: 0 @control-btn-width * 0.6;
&:hover {
button:before {
opacity: 1;
@@ -83,7 +180,8 @@ export default {
button {
position: relative;
width: @height-toolbar;
width: @control-btn-width;
height: @control-btn-width;
background: none;
border: none;
display: flex;
@@ -92,6 +190,9 @@ export default {
outline: none;
padding: 0;
cursor: pointer;
+ button {
margin-left: @control-btn-width * 0.4;
}
&:after {
content: ' ';
@@ -120,14 +221,14 @@ export default {
}
&.min:hover:after {
background-color: lighten(@color-minBtn, 10%);
background-color: @color-minBtn-hover;
opacity: 1;
}
&.max:hover:after {
background-color: lighten(@color-maxBtn, 10%);
background-color: @color-maxBtn-hover;
}
&.close:hover:after {
background-color: lighten(@color-closeBtn, 10%);
background-color: @color-closeBtn-hover;
}
}
}
@@ -136,8 +237,8 @@ export default {
content: ' ';
width: 8px;
height: 2px;
left: @height-toolbar / 2 - 4;
top: @height-toolbar / 2 - 1;
left: @control-btn-width / 2 - 4;
top: @control-btn-width / 2 - 1;
background-color: #fff;
}
}
@@ -152,4 +253,31 @@ export default {
color: #fff;
}
}
each(@themes, {
:global(#container.@{value}) {
.control {
button {
&.min:after {
background-color: ~'@{color-@{value}-minBtn}';
}
&.max:after {
background-color: ~'@{color-@{value}-maxBtn}';
}
&.close:after {
background-color: ~'@{color-@{value}-closeBtn}';
}
&.min:hover:after {
background-color: ~'@{color-@{value}-minBtn-hover}';
}
&.max:hover:after {
background-color: ~'@{color-@{value}-maxBtn-hover}';
}
&.close:hover:after {
background-color: ~'@{color-@{value}-closeBtn-hover}';
}
}
}
}
})
</style>

View File

@@ -13,6 +13,7 @@ div(:class="$style.view")
.view {
position: relative;
z-index: 1;
> * {
position: absolute;
width: 100%;

View File

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

View File

@@ -1,5 +1,5 @@
<template lang="pug">
button(:class="[$style.btn, min ? $style.min : '']" :disabled="disabled" @click="$emit('click', $event)")
button(:class="[$style.btn, min ? $style.min : null]" :disabled="disabled" @click="$emit('click', $event)")
slot
</template>
@@ -24,13 +24,14 @@ export default {
.btn {
display: inline-block;
border: none;
border-radius: 3px;
border-radius: @form-radius;
cursor: pointer;
padding: 8px 15px;
color: @color-btn;
outline: none;
transition: background-color 0.2s ease;
background-color: @color-btn-background;
font-size: 14px;
&[disabled] {
opacity: .4;
}

View File

@@ -1,16 +1,16 @@
<template lang="pug">
div(:class="$style.checkbox")
input(:type="need ? 'radio' : 'checkbox'" :id="id" :disabled="disabled" :value="value" :name="name" @change="change" v-model="bool")
input(:type="need ? 'radio' : 'checkbox'" :class="$style.input" :id="id" :disabled="disabled" :value="value" :name="name" @change="change" v-model="bool")
label(:for="id" :class="$style.content")
div(v-if="indeterminate")
svg(v-show="indeterminate" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' width="100%" viewBox='0 32 448 448' space='preserve')
div(v-if="indeterminate" :class="$style.container")
svg(v-show="indeterminate" :class="$style.icon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' width="100%" viewBox='0 32 448 448' space='preserve')
use(xlink:href='#icon-check-indeterminate')
svg(v-show="!indeterminate" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' width="100%" viewBox='0 0 448 512' space='preserve')
svg(v-show="!indeterminate" :class="$style.icon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' width="100%" viewBox='0 0 448 512' space='preserve')
use(xlink:href='#icon-check-true')
div(v-else)
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' width="100%" viewBox='0 32 448 448' space='preserve')
div(v-else :class="$style.container")
svg(version='1.1' :class="$style.icon" xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' width="100%" viewBox='0 32 448 448' space='preserve')
use(xlink:href='#icon-check-true')
span(v-if="label != null" v-html="label")
span(v-if="label != null" v-html="label" :class="$style.label")
</template>
<script>
@@ -33,7 +33,9 @@ export default {
type: Boolean,
default: false,
},
label: {},
label: {
type: String,
},
disabled: {
type: Boolean,
default: false,
@@ -67,17 +69,25 @@ export default {
} else {
checked.splice(index, 1)
}
} else if (typeof this.checked == 'string') {
checked = this.bool ? this.value : ''
} else if (typeof this.checked == 'boolean') {
} else {
let bool = this.bool
if (this.indeterminate) {
bool = true
this.$nextTick(() => {
this.bool = true
})
switch (typeof this.checked) {
case 'boolean':
if (this.indeterminate) {
bool = true
this.$nextTick(() => {
this.bool = bool
})
}
checked = bool
break
case 'number':
checked = this.value
break
default:
checked = bool ? this.value : ''
break
}
checked = bool
}
this.$emit('input', checked)
this.$emit('change', checked)
@@ -88,14 +98,14 @@ export default {
bool = value.includes(this.value)
} else {
switch (typeof value) {
case 'string':
bool = value === this.value
break
case 'boolean':
bool = value
break
// case 'string':
// case 'number':
default:
return
bool = value === this.value
break
}
}
@@ -112,26 +122,25 @@ export default {
.checkbox {
display: inline-block;
// font-size: 56px;
> input {
display: none;
&[disabled] {
+ .content {
opacity: .5;
}
}
.input {
display: none;
&[disabled] {
+ .content {
opacity: .5;
}
&:checked {
+ .content {
> div {
&:after {
border-color: @color-theme;
}
svg {
transform: scale(1);
opacity: 1;
}
}
&:checked {
+ .content {
.container {
&:after {
border-color: @color-theme;
}
}
.icon {
transform: scale(1);
// opacity: 1;
}
}
}
}
@@ -139,41 +148,44 @@ export default {
display: flex;
justify-content: center;
> div {
flex: none;
position: relative;
display: inline-block;
width: 1em;
height: 1em;
cursor: pointer;
display: flex;
color: @color-theme;
// border: 1px solid #ccc;
&:after {
position: absolute;
content: ' ';
top: 0;
bottom: 0;
left: 0;
right: 0;
border: 1px solid @color-theme_2-font-label;
transition: border-color 0.2s ease;
border-radius: 15%;
}
svg {
transition: 0.2s ease;
transition-property: transform, opacity;
transform: scale(0.5);
opacity: 0;
}
}
.container {
flex: none;
position: relative;
display: inline-block;
width: 1em;
height: 1em;
cursor: pointer;
display: flex;
color: @color-theme;
margin-top: .25em;
// border: 1px solid #ccc;
&:after {
position: absolute;
content: ' ';
top: 0;
bottom: 0;
left: 0;
right: 0;
border: 1px solid @color-theme_2-font-label;
transition: border-color 0.2s ease;
border-radius: 2px;
}
}
.icon {
transition: 0.3s ease;
transition-property: transform;
transform: scale(0);
border-radius: 2px;
// opacity: 0;
}
> span {
flex: auto;
line-height: 1;
margin-left: 5px;
cursor: pointer;
}
.label {
flex: auto;
line-height: 1;
margin-left: 5px;
line-height: 1.5;
cursor: pointer;
}
each(@themes, {
@@ -182,7 +194,7 @@ each(@themes, {
> input {
&:checked {
+ .content {
> div {
.container {
&:after {
border-color: ~'@{color-@{value}-theme}';
}
@@ -192,7 +204,7 @@ each(@themes, {
}
}
.content {
> div {
.container {
color: ~'@{color-@{value}-theme}';
// border: 1px solid #ccc;
&:after {

View File

@@ -5,7 +5,7 @@ material-modal(:show="show" :bg-close="bgClose" @close="handleClose")
| {{ info.name }}
br
| {{ info.singer }}
material-btn(:class="$style.btn" :title="!checkSource(type.type) && '目前酷狗音源仅支持下载128k音质'" :disabled="!checkSource(type.type)" :key="type.type" @click="handleClick(type.type)" v-for="type in info.types") {{getTypeName(type.type)}} {{ type.type.toUpperCase() }}{{ type.size && ` - ${type.size.toUpperCase()}` }}
material-btn(:class="$style.btn" :title="!checkSource(type.type) && $t('material.download_modal.btn_tip')" :disabled="!checkSource(type.type)" :key="type.type" @click="handleClick(type.type)" v-for="type in info.types") {{getTypeName(type.type)}} {{ type.type.toUpperCase() }}{{ type.size && ` - ${type.size.toUpperCase()}` }}
</template>
@@ -29,6 +29,9 @@ export default {
info() {
return this.musicInfo || {}
},
qualityList() {
return window.globalObj.qualityList[this.musicInfo.source]
},
},
methods: {
handleClick(type) {
@@ -41,23 +44,17 @@ export default {
switch (type) {
case 'flac':
case 'ape':
return '无损音质'
case 'wav':
return this.$t('material.download_modal.lossless')
case '320k':
return '高品音质'
return this.$t('material.download_modal.high_quality')
case '192k':
case '128k':
return '普通音质'
return this.$t('material.download_modal.normal')
}
},
checkSource(type) {
switch (this.musicInfo.source) {
case 'wy':
case 'kg':
return type == '128k'
default:
return true
}
return this.qualityList.includes(type)
},
},
}

View File

@@ -2,13 +2,13 @@
material-modal(:show="show" :bg-close="bgClose" @close="handleClose")
main(:class="$style.main")
h2
| 已选择 {{list.length}} 首歌曲
| {{$t('material.download_multiple_modal.tip', { len: list.length })}}
br
| 请选择要优先下载的音质
material-btn(:class="$style.btn" @click="handleClick('128k')") 普通音质 - 128K
material-btn(:class="$style.btn" @click="handleClick('320k')") 高品音质 - 320K
material-btn(:class="$style.btn" @click="handleClick('ape')") 无损音质 - APE
material-btn(:class="$style.btn" @click="handleClick('flac')") 无损音质 - FLAC
| {{$t('material.download_multiple_modal.tip2')}}
material-btn(:class="$style.btn" @click="handleClick('128k')") {{$t('material.download_multiple_modal.normal')}} - 128K
material-btn(:class="$style.btn" @click="handleClick('320k')") {{$t('material.download_multiple_modal.high_quality')}} - 320K
//- material-btn(:class="$style.btn" @click="handleClick('ape')") 无损音质 - APE
material-btn(:class="$style.btn" @click="handleClick('flac')") {{$t('material.download_multiple_modal.lossless')}} - FLAC/WAV
</template>
<script>

View File

@@ -1,22 +1,22 @@
<template lang="pug">
transition(enter-active-class="animated-fast zoomIn" leave-active-class="animated zoomOut")
div(:class="$style.btns" v-show="show")
button(type="button" v-if="playBtn" title="播放" @click.stop="handleClick('play')")
button(type="button" v-if="playBtn" :title="$t('material.flow_btn.play')" @click.stop="handleClick('play')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 287.386 287.386' space='preserve')
use(xlink:href='#icon-testPlay')
button(type="button" v-if="addBtn" title="添加" @click.stop="handleClick('add')")
button(type="button" v-if="addBtn" :title="$t('material.flow_btn.add')" @click.stop="handleClick('add')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 42 42' space='preserve')
use(xlink:href='#icon-addTo')
button(type="button" v-if="downloadBtn" title="下载" @click.stop="handleClick('download')")
button(type="button" v-if="downloadBtn" :title="$t('material.flow_btn.download')" @click.stop="handleClick('download')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 475.078 475.077' space='preserve')
use(xlink:href='#icon-download')
button(type="button" v-if="startBtn" title="开始" @click.stop="handleClick('start')")
button(type="button" v-if="startBtn" :title="$t('material.flow_btn.start')" @click.stop="handleClick('start')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 170 170' space='preserve')
use(xlink:href='#icon-play')
button(type="button" v-if="pauseBtn" title="暂停" @click.stop="handleClick('pause')")
button(type="button" v-if="pauseBtn" :title="$t('material.flow_btn.pause')" @click.stop="handleClick('pause')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 277.338 277.338' space='preserve')
use(xlink:href='#icon-pause')
button(type="button" v-if="removeBtn" title="移除" @click.stop="handleClick('remove')")
button(type="button" v-if="removeBtn" :title="$t('material.flow_btn.remove')" @click.stop="handleClick('remove')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 212.982 212.982' space='preserve')
use(xlink:href='#icon-delete')
@@ -78,10 +78,10 @@ export default {
button {
background-color: transparent;
border: none;
border-radius: 3px;
margin-right: 5px;
border-radius: @form-radius;
margin-right: 2px;
cursor: pointer;
padding: 4px 7px;
padding: 6px 10px;
color: @color-btn;
outline: none;
transition: background-color 0.2s ease;
@@ -91,7 +91,7 @@ export default {
}
svg {
height: 1.2em;
width: 18px;
}
&:hover {

View File

@@ -49,7 +49,7 @@ export default {
.input {
display: inline-block;
border: none;
border-radius: 3px;
border-radius: @form-radius;
padding: 7px 8px;
color: @color-btn;
outline: none;

View File

@@ -1,29 +1,32 @@
<template lang="pug">
div(:class="$style.btns")
button(type="button" v-if="playBtn" title="播放" @click.stop="handleClick('play')")
button(type="button" v-if="playBtn" :title="$t('material.list_buttons.play')" @click.stop="handleClick('play')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 287.386 287.386' space='preserve')
use(xlink:href='#icon-testPlay')
button(type="button" v-if="downloadBtn" title="下载" @click.stop="handleClick('download')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 475.078 475.077' space='preserve')
use(xlink:href='#icon-download')
button(type="button" title="添加" v-if="userInfo" @click.stop="handleClick('add')")
button(type="button" v-if="listAddBtn" :title="$t('material.list_buttons.add_to')" @click.stop="handleClick('listAdd')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 42 42' space='preserve')
use(xlink:href='#icon-addTo')
button(type="button" v-if="startBtn" title="开始" @click.stop="handleClick('start')")
button(type="button" v-if="downloadBtn" :title="$t('material.list_buttons.download')" @click.stop="handleClick('download')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 475.078 475.077' space='preserve')
use(xlink:href='#icon-download')
button(type="button" :title="$t('material.list_buttons.add')" v-if="userInfo" @click.stop="handleClick('add')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 42 42' space='preserve')
use(xlink:href='#icon-addTo')
button(type="button" v-if="startBtn" :title="$t('material.list_buttons.start')" @click.stop="handleClick('start')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 170 170' space='preserve')
use(xlink:href='#icon-play')
button(type="button" v-if="pauseBtn" title="暂停" @click.stop="handleClick('pause')")
button(type="button" v-if="pauseBtn" :title="$t('material.list_buttons.pause')" @click.stop="handleClick('pause')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 277.338 277.338' space='preserve')
use(xlink:href='#icon-pause')
button(type="button" v-if="fileBtn" title="定位文件" @click.stop="handleClick('file')")
button(type="button" v-if="fileBtn" :title="$t('material.list_buttons.file')" @click.stop="handleClick('file')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='-61 0 512 512' space='preserve')
use(xlink:href='#icon-musicFile')
button(type="button" v-if="removeBtn" title="移除" @click.stop="handleClick('remove')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 212.982 212.982' space='preserve')
use(xlink:href='#icon-delete')
button(type="button" v-if="searchBtn" title="搜索" @click.stop="handleClick('search')")
button(type="button" v-if="searchBtn" :title="$t('material.list_buttons.search')" @click.stop="handleClick('search')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 30.239 30.239' space='preserve')
use(xlink:href='#icon-search')
button(type="button" v-if="removeBtn" :title="$t('material.list_buttons.remove')" @click.stop="handleClick('remove')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 212.982 212.982' space='preserve')
use(xlink:href='#icon-delete')
</template>
@@ -56,6 +59,10 @@ export default {
type: Boolean,
default: true,
},
listAddBtn: {
type: Boolean,
default: true,
},
searchBtn: {
type: Boolean,
default: false,
@@ -81,10 +88,12 @@ export default {
@import '../../assets/styles/layout.less';
.btns {
line-height: 1;
button {
background-color: transparent;
border: none;
border-radius: 3px;
border-radius: @form-radius;
margin-right: 5px;
cursor: pointer;
padding: 4px 7px;
@@ -97,7 +106,7 @@ export default {
}
svg {
height: 1.2em;
height: 16px;
}
&:hover {

View File

@@ -6,7 +6,7 @@ transition(enter-active-class="animated fadeIn"
:leave-active-class="outClass")
div(:class="$style.content" v-show="show" @click.stop)
header(:class="$style.header")
button(type="button" @click="close")
button(type="button" @click="close" v-if="closeBtn")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 212.982 212.982' space='preserve')
use(xlink:href='#icon-delete')
slot
@@ -21,6 +21,10 @@ export default {
type: Boolean,
default: false,
},
closeBtn: {
type: Boolean,
default: true,
},
bgClose: {
type: Boolean,
default: false,
@@ -48,6 +52,7 @@ export default {
'slideInLeft',
'slideInRight',
'slideInUp',
'jackInTheBox',
],
animateOut: [
'flipOutX',
@@ -80,25 +85,31 @@ export default {
...mapGetters(['setting']),
},
watch: {
'setting.randomAnimate': {
handler(n) {
if (n) {
this.unwatchFn = this.$watch('show', function(n) {
if (n) {
this.inClass = 'animated ' + this.animateIn[getRandom(0, this.animateIn.length)]
this.outClass = 'animated ' + this.animateOut[getRandom(0, this.animateOut.length)]
}
})
} else {
this.unwatchFn && this.unwatchFn()
}
},
immediate: true,
'setting.randomAnimate'(n) {
n ? this.createWatch() : this.removeWatch()
},
},
mounted() {
if (this.setting.randomAnimate) this.createWatch()
},
beforeDestroy() {
this.removeWatch()
},
methods: {
createWatch() {
this.removeWatch()
this.unwatchFn = this.$watch('show', function(n) {
this.inClass = 'animated ' + this.animateIn[getRandom(0, this.animateIn.length)]
this.outClass = 'animated ' + this.animateOut[getRandom(0, this.animateOut.length)]
})
this.inClass = 'animated ' + this.animateIn[getRandom(0, this.animateIn.length)]
this.outClass = 'animated ' + this.animateOut[getRandom(0, this.animateOut.length)]
},
removeWatch() {
if (!this.unwatchFn) return
this.unwatchFn()
this.unwatchFn = null
},
close() {
this.$emit('close')
},
@@ -127,14 +138,14 @@ export default {
border-radius: 5px;
box-shadow: 0 0 3px rgba(0, 0, 0, .3);
overflow: hidden;
max-height: 70%;
max-height: 80%;
max-width: 70%;
position: relative;
display: flex;
flex-flow: column nowrap;
> * {
background-color: @color-theme_2;
background-color: @color-theme_2-background_2;
}
}
@@ -142,7 +153,9 @@ export default {
flex: none;
background-color: @color-theme;
display: flex;
align-items: center;
justify-content: flex-end;
height: 18px;
button {
border: none;
@@ -176,7 +189,7 @@ each(@themes, {
.content {
box-shadow: 0 0 3px rgba(0, 0, 0, .3);
> * {
background-color: ~'@{color-@{value}-theme_2}';
background-color: ~'@{color-@{value}-theme_2-background_2}';
}
}

View File

@@ -0,0 +1,177 @@
<template lang="pug">
material-modal(:show="!setting.isAgreePact || globalObj.isShowPact" @close="handleClose(false)" :bgClose="setting.isAgreePact" :close-btn="setting.isAgreePact")
main(:class="$style.main")
h2 许可协议
div.select.scroll(:class="$style.content")
p
| 本项目软件基于
strong.hover.underline(@click="openUrl('http://www.apache.org/licenses/LICENSE-2.0')") Apache License 2.0
| &nbsp;许可证发行在使用本软件前使用者需签署本协议才可继续使用以下协议是对于 Apache License 2.0 的补充如有冲突以以下协议为准
br
p 词语约定本协议中的本软件指洛雪音乐桌面版项目使用者指签署本协议的使用者官方音乐平台指对本软件内置的包括酷我酷狗咪咕等音乐源的官方平台统称版权数据指包括但不限于图像音频名字等在内的他人拥有所属版权的数据
br
p
strong 1
| 本软件的数据来源原理是从各官方音乐平台的公开服务器中拉取数据经过对数据简单地筛选与合并后进行展示因此本软件不对数据的准确性负责
p
strong 2
| 使用本软件的过程中可能会产生版权数据对于这些版权数据本软件不拥有它们的所有权为了避免造成侵权使用者务必在
strong 24小时内
| 清除使用本软件的过程中所产生的版权数据
p
strong 3
| 本软件内的官方音乐平台别名为本软件内对官方音乐平台的一个称呼不包含恶意如果官方音乐平台觉得不妥可联系本软件更改或移除
p
strong 4
| 本软件内使用的部分包括但不限于字体图片等资源来源于互联网如果出现侵权可联系本软件移除
p
strong 5
| 由于使用本软件产生的包括由于本协议或由于使用或无法使用本软件而引起的任何性质的任何直接间接特殊偶然或结果性损害包括但不限于因商誉损失停工计算机故障或故障引起的损害赔偿或任何及所有其他商业损害或损失由使用者负责
p
strong 6
| 本项目完全免费且开源发布于&nbsp;
span.hover.underline(@click="openUrl('https://github.com/lyswhut/lx-music-desktop#readme')") GitHub
| &nbsp;面向全世界人用作对技术的学习交流本软件不对项目内的技术可能存在违反当地法律法规的行为作保证
strong 禁止在违反当地法律法规的情况下使用本软件
|对于使用者在明知或不知当地法律法规不允许的情况下使用本软件所造成的任何违法违规行为由使用者承担本软件不承担由此造成的任何直接间接特殊偶然或结果性责任
br
p
strong *
| &nbsp;若协议更新恕不另行通知可到开源地址查看
p
strong *
| &nbsp;本软件的初衷是帮助官方音乐平台简化数据后代为展示帮助使用者根据歌曲名艺术家等关键字快速地定位所需内容所在的音乐平台
p
strong *
| &nbsp;音乐平台不易建议到对应音乐平台支持正版资源
br
p(v-if="!setting.isAgreePact")
strong 若你使用者接受以上协议请点击下面的接受按钮签署本协议若不接受请点击不接受后退出软件并清除本软件的所有数据
div(:class="$style.btns" v-if="!setting.isAgreePact")
material-btn(:class="$style.btn" @click="handleClose(true)") 不接受
material-btn(:class="$style.btn" :disabled="!btnEnable" @click="handleClick()") 接受 {{timeStr}}
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
import { rendererSend } from '../../../common/ipc'
import { openUrl } from '../../utils'
export default {
data() {
return {
time: 20,
globalObj: {
isShowPact: false,
},
}
},
computed: {
...mapGetters(['setting']),
btnEnable() {
return this.time == 0
},
timeStr() {
return this.btnEnable ? '' : `(${this.time})`
},
},
watch: {
'setting.isAgreePact'(n) {
if (n) return
this.time = 10
this.startTimeout()
},
},
mounted() {
this.$nextTick(() => {
this.globalObj = window.globalObj
if (!this.setting.isAgreePact) {
this.startTimeout()
}
})
},
methods: {
...mapMutations(['setAgreePact']),
handleClick() {
this.setAgreePact()
},
handleClose(isExit) {
if (isExit) return rendererSend('close', true)
this.globalObj.isShowPact = false
},
openUrl(url) {
openUrl(url)
},
startTimeout() {
setTimeout(() => {
if (--this.time > 0) this.startTimeout()
}, 1000)
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.main {
padding: 15px;
max-width: 550px;
min-width: 200px;
min-height: 0;
display: flex;
flex-flow: column nowrap;
justify-content: center;
h2 {
font-size: 16px;
color: @color-theme_2-font;
line-height: 1.3;
text-align: center;
}
}
.content {
flex: auto;
margin: 15px 0;
padding-right: 5px;
h3 {
font-weight: bold;
line-height: 2;
}
p {
line-height: 1.5;
font-size: 14px;
text-align: justify;
}
}
.btns {
display: flex;
align-items: center;
justify-content: space-between;
}
.btn {
display: block;
width: 48%;
&:last-child {
margin-bottom: 0;
}
}
each(@themes, {
:global(#container.@{value}) {
.main {
h2 {
color: ~'@{color-@{value}-theme_2-font}';
}
}
.name {
color: ~'@{color-@{value}-theme}';
}
}
})
</style>

View File

@@ -6,20 +6,20 @@ div(:class="$style.pagination" v-if="allPage > 1")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 451.846 451.847' space='preserve')
use(xlink:href='#icon-left')
li(v-else)
button(type="button" @click="handleClick(page - 1)" title="上一页")
button(type="button" @click="handleClick(page - 1)" :title="$t('material.pagination.prev')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 451.846 451.847' space='preserve')
use(xlink:href='#icon-left')
li(v-if="allPage > btnLength && page > pageEvg+1" :class="$style.first")
button(type="button" @click="handleClick(1)" title=" 1 ")
button(type="button" @click="handleClick(1)" :title="$t('material.pagination.page', { num: 1 })")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 451.846 451.847' space='preserve')
use(xlink:href='#icon-first')
li(v-for="(p, index) in pages" :key="index" :class="{[$style.active] : p == page}")
span(v-if="p === page" v-text="page")
button(v-else type="button" @click="handleClick(p)" v-text="p" :title="`第 ${p} 页`")
button(v-else type="button" @click="handleClick(p)" v-text="p" :title="$t('material.pagination.page', { num: p })")
li(v-if="allPage > btnLength && allPage - page > pageEvg" :class="$style.last")
button(type="button" @click="handleClick(allPage)" :title="`第 ${allPage} 页`")
button(type="button" @click="handleClick(allPage)" :title="$t('material.pagination.page', { num: allPage })")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 451.846 451.847' space='preserve')
use(xlink:href='#icon-last')
@@ -29,7 +29,7 @@ div(:class="$style.pagination" v-if="allPage > 1")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 451.846 451.847' space='preserve')
use(xlink:href='#icon-right')
li(v-else)
button(type="button" @click="handleClick(page + 1)" title="下一页")
button(type="button" @click="handleClick(page + 1)" :title="$t('material.pagination.next')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 451.846 451.847' space='preserve')
use(xlink:href='#icon-right')

View File

@@ -1,96 +1,99 @@
<template lang="pug">
div(:class="[$style.search, focus ? $style.active : '']")
div(:class="[$style.search, focus ? $style.active : '', big ? $style.big : '', small ? $style.small : '']")
div(:class="$style.form")
input(placeholder="Search for something..." v-model.trim="text"
@focus="handleFocus" @blur="handleBlur" @input="handleInput"
@keyup.enter="handleSearch")
input(:placeholder="placeholder" v-model.trim="text" ref="dom_input"
@focus="handleFocus" @blur="handleBlur" @input="$emit('input', text)"
@change="sendEvent('change')"
@keyup.enter="handleSearch"
@keyup.40.prevent="handleKeyDown"
@keyup.38.prevent="handleKeyUp"
@contextmenu="handleContextMenu")
button(type="button" @click="handleSearch")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 30.239 30.239' space='preserve')
use(xlink:href='#icon-search')
slot
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 30.239 30.239' space='preserve')
use(xlink:href='#icon-search')
//- transition(name="custom-classes-transition"
//- enter-active-class="animated flipInX"
//- leave-active-class="animated flipOutX")
div(:class="$style.list" :style="listStyle")
div(v-if="list" :class="$style.list" :style="listStyle")
ul(ref="dom_list")
li(v-for="(item, index) in list" :key="item" @click="handleTemplistClick(index)")
li(v-for="(item, index) in list" :key="item" :class="selectIndex === index ? $style.select : null" @mouseenter="selectIndex = index" @click="handleTemplistClick(index)")
span {{item}}
</template>
<script>
import { mapGetters } from 'vuex'
import music from '../../utils/music'
import { clipboardReadText } from '../../utils'
export default {
props: {
placeholder: {
type: String,
default: 'Search for something...',
},
list: {
type: Array,
},
visibleList: {
type: Boolean,
default: false,
},
value: {
type: String,
default: '',
},
big: {
type: Boolean,
default: false,
},
small: {
type: Boolean,
default: false,
},
},
data() {
return {
isShow: false,
text: '',
list: [],
index: null,
selectIndex: -1,
focus: false,
listStyle: {
height: 0,
},
}
},
computed: {
...mapGetters(['route', 'setting']),
...mapGetters('search', ['searchText']),
isAutoClearInput() {
return this.setting.odc.isAutoClearSearchInput
},
source() {
return this.setting.search.tempSearchSource
},
},
watch: {
list(n) {
if (!this.visibleList) return
if (this.selectIndex > -1) this.selectIndex = -1
this.$nextTick(() => {
this.listStyle.height = this.$refs.dom_list.scrollHeight + 'px'
})
},
'searchText'(n) {
if (n !== this.text) this.text = n
value(n) {
this.text = n
},
route(n) {
if (this.isAutoClearInput && n.name != 'search' && this.text) this.text = ''
visibleList(n) {
n ? this.showList() : this.hideList()
},
},
mounted() {
if (this.$store.getters.setting.search.isFocusSearchBox) this.$refs.dom_input.focus()
},
methods: {
handleTemplistClick(index) {
this.text = this.list[index]
this.handleSearch()
this.sendEvent('listClick', index)
},
handleFocus() {
this.focus = true
if (this.text) this.handleInput()
this.showList()
this.sendEvent('focus')
},
handleBlur() {
setTimeout(() => {
this.focus = false
this.hideList()
}, 200)
this.focus = false
this.sendEvent('blur')
},
handleSearch() {
this.hideList()
this.$router.push({
path: 'search',
query: {
text: this.text,
},
}).catch(_ => _)
},
handleInput() {
if (this.text === '') {
this.list.splice(0, this.list.length)
music[this.source].tempSearch.cancelTempSearch()
return
}
if (!this.isShow) this.showList()
music[this.source].tempSearch.search(this.text).then(list => {
this.list = list
}).catch(() => {})
if (this.selectIndex < 0) return this.sendEvent('submit')
this.sendEvent('listClick', this.selectIndex)
},
showList() {
this.isShow = true
@@ -99,6 +102,30 @@ export default {
hideList() {
this.isShow = false
this.listStyle.height = 0
this.$nextTick(() => {
this.selectIndex = -1
})
},
sendEvent(action, data) {
this.$emit('event', {
action,
data,
})
},
handleKeyDown() {
this.selectIndex = this.selectIndex + 1 < this.list.length ? this.selectIndex + 1 : 0
},
handleKeyUp() {
this.selectIndex = this.selectIndex - 1 < -1 ? this.list.length - 1 : this.selectIndex - 1
},
handleContextMenu() {
let str = clipboardReadText()
str = str.trim()
str = str.replace(/\t|\r\n|\n|\r/g, ' ')
str = str.replace(/\s+/g, ' ')
let dom_input = this.$refs.dom_input
this.text = `${this.text.substring(0, dom_input.selectionStart)}${str}${this.text.substring(dom_input.selectionEnd, this.text.length)}`
this.$emit('input', this.text)
},
},
}
@@ -109,10 +136,7 @@ export default {
@import '../../assets/styles/layout.less';
.search {
position: absolute;
left: 15px;
top: 13px;
border-radius: 3px;
border-radius: @form-radius;
transition: box-shadow .4s ease, background-color @transition-theme;
display: flex;
flex-flow: column nowrap;
@@ -190,7 +214,7 @@ export default {
.mixin-ellipsis-2;
}
&:hover {
&.select {
background-color: @color-search-list-hover;
}
&:last-child {
@@ -201,6 +225,19 @@ export default {
}
}
.big {
width: 500px;
// input {
// line-height: 30px;
// }
.form {
height: 30px;
button {
padding: 6px 10px;
}
}
}
each(@themes, {
:global(#container.@{value}) {
@@ -229,7 +266,7 @@ each(@themes, {
}
.list {
li {
&:hover {
&.select {
background-color: ~'@{color-@{value}-search-list-hover}';
}
}

View File

@@ -1,12 +1,12 @@
<template lang="pug">
div(:class="$style.select")
div(:class="[$style.select, show ? $style.active : '']")
div(:class="$style.label" ref="dom_btn" @click="handleShow") {{value ? itemName ? list.find(l => l.id == value).name : value : ''}}
ul(:class="$style.list" ref="dom_list" :style="listStyle")
ul(:class="$style.list")
li(v-for="item in list" @click="handleClick(itemKey ? item[itemKey] : item)") {{itemName ? item[itemName] : item}}
</template>
<script>
// import { isChildren } from '../../utils'
export default {
props: {
list: {
@@ -28,25 +28,8 @@ export default {
data() {
return {
show: false,
listStyle: {
height: 0,
opacity: 0,
},
}
},
watch: {
show(n) {
this.$nextTick(() => {
if (n) {
this.listStyle.height = this.$refs.dom_list.scrollHeight + 'px'
this.listStyle.opacity = 1
} else {
this.listStyle.height = 0
this.listStyle.opacity = 0
}
})
},
},
mounted() {
document.addEventListener('click', this.handleHide)
},
@@ -80,6 +63,13 @@ export default {
.select {
font-size: 12px;
position: relative;
&.active {
.list {
transform: scaleY(1);
opacity: 1;
}
}
}
.label {
@@ -93,6 +83,7 @@ export default {
border-top-left-radius: 4px;
color: @color-btn;
cursor: pointer;
.mixin-ellipsis-1;
&:hover {
background-color: @color-theme_2-hover;
@@ -104,16 +95,18 @@ export default {
.list {
position: absolute;
width: 100%;
top: 100%;
left: 0;
border-bottom: 2px solid @color-tab-border-bottom;
border-left: 2px solid @color-tab-border-bottom;
border-bottom-left-radius: 4px;
background-color: @color-theme_2;
overflow: hidden;
background-color: @color-theme_2-background_2;
opacity: 0;
transform: scaleY(0);
transform-origin: 0 0 0;
transition: .25s ease;
transition-property: height, opacity;
transition-property: transform, opacity;
z-index: 10;
li {
@@ -125,6 +118,7 @@ export default {
transition: background-color @transition-theme;
background-color: @color-btn-background;
box-sizing: border-box;
.mixin-ellipsis-1;
&:hover {
background-color: @color-theme_2-hover;
@@ -153,6 +147,7 @@ each(@themes, {
.list {
border-bottom-color: ~'@{color-@{value}-tab-border-bottom}';
border-left-color: ~'@{color-@{value}-tab-border-bottom}';
background-color: ~'@{color-@{value}-theme_2-background_2}';
li {
// color: ~'@{color-@{value}-btn}';
background-color: ~'@{color-@{value}-btn-background}';

View File

@@ -0,0 +1,223 @@
<template lang="pug">
div(:class="[$style.select, show ? $style.active : '']")
div(:class="$style.label" ref="dom_btn" @click="handleShow")
span {{label}}
div(:class="$style.icon")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 451.847 451.847' space='preserve')
use(xlink:href='#icon-down')
ul.scroll(:class="$style.list" :style="listStyles" ref="dom_list")
li(v-for="item in list" :class="(itemKey ? item[itemKey] : item) == value ? $style.active : null" @click="handleClick(item)" :title="itemName ? item[itemName] : item") {{itemName ? item[itemName] : item}}
</template>
<script>
export default {
props: {
list: {
type: Array,
default() {
return []
},
},
value: {
type: [String, Number],
},
itemName: {
type: String,
},
itemKey: {
type: String,
},
},
data() {
return {
show: false,
listStyles: {
transform: 'scaleY(0) translateY(0)',
},
}
},
mounted() {
document.addEventListener('click', this.handleHide)
},
beforeDestroy() {
document.removeEventListener('click', this.handleHide)
},
computed: {
label() {
if (!this.value) return ''
if (!this.itemName) return this.value
const item = this.list.find(l => l[this.itemKey] == this.value)
if (!item) return ''
return item[this.itemName]
},
},
methods: {
handleHide(e) {
// if (e && e.target.parentNode != this.$refs.dom_list && this.show) return this.show = false
if (e && (e.target == this.$refs.dom_btn || this.$refs.dom_btn.contains(e.target))) return
this.listStyles.transform = 'scaleY(0) translateY(0)'
setTimeout(() => {
this.show = false
}, 50)
},
handleClick(item) {
// console.log(this.value)
if (item === this.value) return
this.$emit('input', this.itemKey ? item[this.itemKey] : item)
this.$emit('change', item)
},
handleShow() {
this.show = true
this.listStyles.transform = `scaleY(1) translateY(${this.handleGetOffset()}px)`
},
handleGetOffset() {
const listHeight = this.$refs.dom_list.clientHeight
const dom_select = this.$refs.dom_list.offsetParent
const dom_container = dom_select.offsetParent
const containerHeight = dom_container.clientHeight
if (containerHeight < listHeight) return 0
const offsetHeight = (dom_container.scrollTop + containerHeight) - (dom_select.offsetTop + listHeight)
if (offsetHeight > 0) return 0
return offsetHeight - 5
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
@selection-height: 28px;
.select {
display: inline-block;
font-size: 12px;
position: relative;
width: 300px;
&.active {
.label {
background-color: @color-btn-background;
}
.list {
opacity: 1;
}
.icon {
svg{
transform: rotate(180deg);
}
}
}
}
.label {
background-color: @color-btn-background;
padding: 0 10px;
transition: background-color @transition-theme;
height: @selection-height;
line-height: 27px;
box-sizing: border-box;
color: @color-btn;
border-radius: @form-radius;
cursor: pointer;
display: flex;
span {
flex: auto;
.mixin-ellipsis-1;
}
.icon {
flex: none;
margin-left: 7px;
line-height: 0;
svg {
width: 1em;
transition: transform .2s ease;
transform: rotate(0);
}
}
&:hover {
background-color: @color-theme_2-hover;
}
&:active {
background-color: @color-theme_2-active;
}
}
.list {
position: absolute;
top: 0;
left: 0;
width: 100%;
background-color: @color-theme_2-background_2;
opacity: 0;
transform: scaleY(0) translateY(0);
transform-origin: 0 @selection-height / 2 0;
transition: .25s ease;
transition-property: transform, opacity;
z-index: 10;
border-radius: @form-radius;
box-shadow: 0 0 4px rgba(0, 0, 0, .15);
overflow: auto;
max-height: 200px;
li {
cursor: pointer;
padding: 0 10px;
line-height: @selection-height;
// color: @color-btn;
outline: none;
transition: background-color @transition-theme;
background-color: @color-btn-background;
box-sizing: border-box;
.mixin-ellipsis-1;
&:hover {
background-color: @color-theme_2-hover;
}
&:active {
background-color: @color-theme_2-active;
}
&.active {
color: @color-btn;
}
}
}
each(@themes, {
:global(#container.@{value}) {
.label {
color: ~'@{color-@{value}-btn}';
background-color: ~'@{color-@{value}-btn-background}';
&:hover {
background-color: ~'@{color-@{value}-theme_2-hover}';
}
&:active {
background-color: ~'@{color-@{value}-theme_2-active}';
}
}
.list {
background-color: ~'@{color-@{value}-theme_2-background_2}';
li {
// color: ~'@{color-@{value}-btn}';
background-color: ~'@{color-@{value}-btn-background}';
&:hover {
background-color: ~'@{color-@{value}-theme_2-hover}';
}
&:active {
background-color: ~'@{color-@{value}-theme_2-active}';
}
&.active {
color: ~'@{color-@{value}-btn}';
}
}
}
}
})
</style>

View File

@@ -1,47 +1,52 @@
<template lang="pug">
div(:class="$style.songList")
transition(enter-active-class="animated fadeIn" leave-active-class="animated fadeOut")
div(v-if="list.length" :class="$style.list")
transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut")
div(v-show="list.length" :class="$style.list")
div(:class="$style.thead")
table
thead
tr
th.nobreak.center(style="width: 37px;")
material-checkbox(id="search_select_all" v-model="isSelectAll" @change="handleSelectAllData"
:indeterminate="isIndeterminate" :title="isSelectAll && !isIndeterminate ? '全不选' : '全选'")
th.nobreak(style="width: 25%;") 歌曲名
th.nobreak(style="width: 20%;") 歌手
th.nobreak(style="width: 22%;") 专辑
th.nobreak(style="width: 18%;") 操作
th.nobreak(style="width: 10%;") 时长
th.nobreak.center(style="width: 10px;") #
th.nobreak(style="width: 25%;") {{$t('material.song_list.name')}}
th.nobreak(style="width: 20%;") {{$t('material.song_list.singer')}}
th.nobreak(style="width: 20%;") {{$t('material.song_list.album')}}
th.nobreak(style="width: 20%;") {{$t('material.song_list.action')}}
th.nobreak(style="width: 10%;") {{$t('material.song_list.time')}}
div.scroll(:class="$style.tbody" ref="dom_scrollContent")
table
tbody
tr(v-for='(item, index) in list' :key='item.songmid' @click="handleDoubleClick(index)")
td.nobreak.center(style="width: 37px;" @click.stop)
material-checkbox(:id="index.toString()" v-model="selectdList" @change="handleChangeSelect" :value="item")
tbody(@contextmenu="handleContextMenu" ref="dom_tbody")
tr(v-for='(item, index) in list' :key='item.songmid' @click="handleDoubleClick($event, index)")
td.nobreak.center(style="width: 37px;" :class="$style.noSelect" @click.stop) {{index + 1}}
td.break(style="width: 25%;")
| {{item.name}}
span.badge.badge-info(v-if="item._types['320k']") 高品质
span.badge.badge-success(v-if="item._types.ape || item._types.flac") 无损
td.break(style="width: 20%;") {{item.singer}}
td.break(style="width: 22%;") {{item.albumName}}
td(style="width: 18%;")
material-list-buttons(:index="index" :search-btn="true" :play-btn="item.source == 'kw' || (!isAPITemp && item.source != 'tx' && item.source != 'wy')" :download-btn="item.source == 'kw' || (!isAPITemp && item.source != 'tx' && item.source != 'wy')" :remove-btn="false" @btn-click="handleListBtnClick")
span.select {{item.name}}
span.badge.badge-theme-success(:class="[$style.labelQuality, $style.noSelect]" v-if="item._types.ape || item._types.flac || item._types.wav") {{$t('material.song_list.lossless')}}
span.badge.badge-theme-info(:class="[$style.labelQuality, $style.noSelect]" v-else-if="item._types['320k']") {{$t('material.song_list.high_quality')}}
td.break(style="width: 20%;")
span.select {{item.singer}}
td.break(style="width: 20%;")
span.select {{item.albumName}}
td(style="width: 20%; padding-left: 0; padding-right: 0;")
material-list-buttons(:index="index" :search-btn="true"
:remove-btn="false" @btn-click="handleListBtnClick"
:listAdd-btn="assertApiSupport(item.source)"
:play-btn="assertApiSupport(item.source)"
:download-btn="assertApiSupport(item.source)")
//- button.btn-info(type='button' v-if="item._types['128k'] || item._types['192k'] || item._types['320k'] || item._types.flac" @click.stop='openDownloadModal(index)') 下载
//- button.btn-secondary(type='button' v-if="item._types['128k'] || item._types['192k'] || item._types['320k']" @click.stop='testPlay(index)') 试听
//- button.btn-success(type='button' v-if="(item._types['128k'] || item._types['192k'] || item._types['320k']) && userInfo" @click.stop='showListModal(index)')
td(style="width: 10%;") {{item.interval || '--/--'}}
td(style="width: 10%;")
span(:class="[$style.time, $style.noSelect]") {{item.interval || '--/--'}}
div(:class="$style.pagination")
material-pagination(:count="total" :limit="limit" :page="page" @btn-click="handleTogglePage")
div(v-else :class="$style.noitem")
transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut")
div(v-show="!list.length" :class="$style.noitem")
p(v-html="noItem")
material-flow-btn(:show="isShowEditBtn && (source == 'kw' || !isAPITemp)" :remove-btn="false" @btn-click="handleFlowBtnClick")
material-flow-btn(:show="isShowEditBtn && assertApiSupport(source)" :remove-btn="false" @btn-click="handleFlowBtnClick")
</template>
<script>
import { mapGetters } from 'vuex'
import { scrollTo } from '../../utils'
import { scrollTo, clipboardWriteText, assertApiSupport } from '../../utils'
export default {
name: 'MaterialSongList',
model: {
@@ -81,37 +86,28 @@ export default {
},
computed: {
...mapGetters(['setting']),
isAPITemp() {
return this.setting.apiSource == 'temp'
},
},
watch: {
selectdList(n) {
const len = n.length
if (len) {
this.isSelectAll = true
this.isIndeterminate = len !== this.list.length
this.isShowEditBtn = true
} else {
this.isSelectAll = false
this.isShowEditBtn = false
}
},
selectdData(n) {
const len = n.length
if (len) {
this.isSelectAll = true
this.isIndeterminate = len !== this.list.length
this.isShowEditBtn = true
this.selectdList = [...n]
} else {
this.isSelectAll = false
this.isShowEditBtn = false
this.resetSelect()
this.removeAllSelect()
}
},
list(n) {
this.resetSelect()
this.removeAllSelect()
if (!this.list.length) return
this.$nextTick(() => scrollTo(this.$refs.dom_scrollContent, 0))
},
@@ -120,14 +116,77 @@ export default {
return {
clickTime: 0,
clickIndex: -1,
isSelectAll: false,
isIndeterminate: false,
isShowEditBtn: false,
selectdList: [],
keyEvent: {
isShiftDown: false,
isModDown: false,
isADown: false,
aDownTimeout: null,
},
}
},
created() {
this.listenEvent()
},
beforeDestroy() {
this.unlistenEvent()
},
methods: {
handleDoubleClick(index) {
listenEvent() {
window.eventHub.$on('key_shift_down', this.handle_key_shift_down)
window.eventHub.$on('key_shift_up', this.handle_key_shift_up)
window.eventHub.$on('key_mod_down', this.handle_key_mod_down)
window.eventHub.$on('key_mod_up', this.handle_key_mod_up)
window.eventHub.$on('key_mod+a_down', this.handle_key_mod_a_down)
window.eventHub.$on('key_mod+a_up', this.handle_key_mod_a_up)
},
unlistenEvent() {
window.eventHub.$off('key_shift_down', this.handle_key_shift_down)
window.eventHub.$off('key_shift_up', this.handle_key_shift_up)
window.eventHub.$off('key_mod_down', this.handle_key_mod_down)
window.eventHub.$off('key_mod_up', this.handle_key_mod_up)
window.eventHub.$off('key_mod+a_down', this.handle_key_mod_a_down)
window.eventHub.$off('key_mod+a_up', this.handle_key_mod_a_up)
},
handle_key_shift_down() {
if (!this.keyEvent.isShiftDown) this.keyEvent.isShiftDown = true
},
handle_key_shift_up() {
if (this.keyEvent.isShiftDown) this.keyEvent.isShiftDown = false
},
handle_key_mod_down() {
if (!this.keyEvent.isModDown) this.keyEvent.isModDown = true
},
handle_key_mod_up() {
if (this.keyEvent.isModDown) this.keyEvent.isModDown = false
},
handle_key_mod_a_down() {
if (!this.keyEvent.isADown) {
this.keyEvent.isModDown = false
this.keyEvent.isADown = true
this.handleSelectAllData()
if (this.keyEvent.aDownTimeout) clearTimeout(this.keyEvent.aDownTimeout)
this.keyEvent.aDownTimeout = setTimeout(() => {
this.keyEvent.aDownTimeout = null
this.keyEvent.isADown = false
}, 500)
}
},
handle_key_mod_a_up() {
if (this.keyEvent.isADown) {
if (this.keyEvent.aDownTimeout) {
clearTimeout(this.keyEvent.aDownTimeout)
this.keyEvent.aDownTimeout = null
}
this.keyEvent.isADown = false
}
},
handleDoubleClick(event, index) {
if (event.target.classList.contains('select')) return
this.handleSelectData(event, index)
if (
window.performance.now() - this.clickTime > 400 ||
this.clickIndex !== index
@@ -136,21 +195,71 @@ export default {
this.clickIndex = index
return
}
this.emitEvent((this.source == 'kw' || (!this.isAPITemp && this.list[index].source != 'tx' && this.list[index].source != 'wy')) ? 'testPlay' : 'search', index)
this.emitEvent(this.assertApiSupport(this.source) ? 'testPlay' : 'search', index)
this.clickTime = 0
this.clickIndex = -1
},
handleSelectData(event, clickIndex) {
if (this.keyEvent.isShiftDown) {
if (this.selectdList.length) {
let lastSelectIndex = this.list.indexOf(this.selectdList[this.selectdList.length - 1])
this.removeAllSelect()
if (lastSelectIndex != clickIndex) {
let isNeedReverse = false
if (clickIndex < lastSelectIndex) {
let temp = lastSelectIndex
lastSelectIndex = clickIndex
clickIndex = temp
isNeedReverse = true
}
this.selectdList = this.list.slice(lastSelectIndex, clickIndex + 1)
if (isNeedReverse) this.selectdList.reverse()
let nodes = this.$refs.dom_tbody.childNodes
do {
nodes[lastSelectIndex].classList.add('active')
lastSelectIndex++
} while (lastSelectIndex <= clickIndex)
}
} else {
event.currentTarget.classList.add('active')
this.selectdList.push(this.list[clickIndex])
}
} else if (this.keyEvent.isModDown) {
let item = this.list[clickIndex]
let index = this.selectdList.indexOf(item)
if (index < 0) {
this.selectdList.push(item)
event.currentTarget.classList.add('active')
} else {
this.selectdList.splice(index, 1)
event.currentTarget.classList.remove('active')
}
} else if (this.selectdList.length) {
this.removeAllSelect()
} else return
this.$emit('input', [...this.selectdList])
},
removeAllSelect() {
this.selectdList = []
let dom_tbody = this.$refs.dom_tbody
if (!dom_tbody) return
let nodes = dom_tbody.querySelectorAll('.active')
for (const node of nodes) {
if (node.parentNode == dom_tbody) node.classList.remove('active')
}
},
handleListBtnClick(info) {
this.emitEvent('listBtnClick', info)
},
handleSelectAllData(isSelect) {
this.selectdList = isSelect ? [...this.list] : []
handleSelectAllData() {
this.removeAllSelect()
this.selectdList = [...this.list]
let nodes = this.$refs.dom_tbody.childNodes
for (const node of nodes) {
node.classList.add('active')
}
this.$emit('input', [...this.selectdList])
},
resetSelect() {
this.selectdList = false
this.selectdList = []
},
handleTogglePage(page) {
this.emitEvent('togglePage', page)
},
@@ -163,6 +272,21 @@ export default {
handleChangeSelect() {
this.$emit('input', [...this.selectdList])
},
handleContextMenu(event) {
if (!event.target.classList.contains('select')) return
let classList = this.$refs.dom_scrollContent.classList
classList.add(this.$style.copying)
window.requestAnimationFrame(() => {
let str = window.getSelection().toString()
classList.remove(this.$style.copying)
str = str.trim()
if (!str.length) return
clipboardWriteText(str)
})
},
assertApiSupport(source) {
return assertApiSupport(source)
},
},
}
</script>
@@ -175,6 +299,7 @@ export default {
height: 100%;
display: flex;
flex-flow: column nowrap;
position: relative;
}
.list {
@@ -186,6 +311,10 @@ export default {
}
.thead {
flex: none;
tr > th:first-child {
color: @color-theme_2-font-label;
// padding-left: 10px;
}
}
.tbody {
flex: auto;
@@ -193,13 +322,21 @@ export default {
td {
font-size: 12px;
:global(.badge) {
margin-right: 3px;
&:first-child {
margin-left: 3px;
}
&:last-child {
margin-right: 0;
}
margin-left: 3px;
}
&:first-child {
// padding-left: 10px;
font-size: 11px;
color: @color-theme_2-font-label;
}
}
:global(.badge) {
opacity: .85;
}
&.copying {
.no-select {
display: none;
}
}
}
@@ -210,8 +347,11 @@ export default {
// transform: translateX(-50%);
}
.noitem {
position: relative;
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
display: flex;
flex-flow: column nowrap;
justify-content: center;
@@ -225,6 +365,13 @@ export default {
each(@themes, {
:global(#container.@{value}) {
.tbody {
td {
&:first-child {
color: ~'@{color-@{value}-theme_2-font-label}';
}
}
}
.noitem {
p {
color: ~'@{color-@{value}-theme_2-font-label}';

View File

@@ -110,13 +110,14 @@ export default {
display: inline-block;
border: none;
cursor: pointer;
padding: 5px 10px 7px;
padding: 0 10px;
font-size: 12px;
min-width: 56px;
// color: @color-btn;
outline: none;
transition: background-color @transition-theme;
background-color: @color-tab-btn-background;
line-height: 28px;
}
&:hover {
// border-left-color: @color-theme_2-hover;
@@ -133,7 +134,7 @@ export default {
}
}
&.active {
border-bottom-color: @color-theme_2-background_2;
border-bottom-color: @color-theme_2-background_1;
border-top-color: @color-tab-border-bottom;
border-left-color: @color-tab-border-bottom;
border-right-color: @color-tab-border-bottom;
@@ -144,7 +145,7 @@ export default {
width: 0;
}
button {
background-color: @color-theme_2-background_2;
background-color: @color-theme_2-background_1;
}
}
}
@@ -209,7 +210,7 @@ each(@themes, {
border-left-color: ~'@{color-@{value}-tab-border-bottom}';
border-right-color: ~'@{color-@{value}-tab-border-bottom}';
button {
background-color: ~'@{color-@{value}-theme_2-background_2}';
background-color: ~'@{color-@{value}-theme_2-background_1}';
}
}
}

View File

@@ -1,19 +1,19 @@
<template lang="pug">
div(:class="$style.tagList")
div(:class="[$style.tagList, show ? $style.active : '']")
div(:class="$style.label" ref="dom_btn" @click="handleShow")
span {{value.name}}
div(:class="[$style.icon, show ? $style.active : '']")
div(:class="$style.icon")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 451.847 451.847' space='preserve')
use(xlink:href='#icon-down')
div.scroll(:class="$style.list" @click.stop ref="dom_list" :style="listStyle")
div(:class="$style.tag" @click="handleClick(null)") 默认
div.scroll(:class="$style.list" :style="{ width: listWidth + 'PX' }" @click.stop ref="dom_list")
div(:class="$style.tag" @click="handleClick(null)") {{$t('material.tag_list.default')}}
dl(v-for="type in list")
dt(:class="$style.type") {{type.name}}
dd(:class="$style.tag" v-for="tag in type.list" @click="handleClick(tag)") {{tag.name}}
</template>
<script>
import { isChildren } from '../../utils'
import { mapGetters } from 'vuex'
export default {
props: {
list: {
@@ -22,37 +22,22 @@ export default {
return []
},
},
listWidth: {
type: Number,
default: 645,
},
value: {
type: Object,
},
},
computed: {
...mapGetters(['setting']),
},
data() {
return {
show: false,
listStyle: {
height: 0,
opacity: 0,
},
}
},
watch: {
show(n) {
this.$nextTick(() => {
if (n) {
let sh = this.$refs.dom_list.scrollHeight
this.listStyle.height = (sh > 250 ? 250 : sh) + 'px'
this.listStyle.opacity = 1
this.listStyle.overflow = 'auto'
} else {
this.listStyle.height = 0
this.listStyle.opacity = 0
}
})
},
list() {
this.$refs.dom_list.scrollTop = 0
},
},
mounted() {
document.addEventListener('click', this.handleHide)
},
@@ -62,7 +47,7 @@ export default {
methods: {
handleHide(e) {
// if (e && e.target.parentNode != this.$refs.dom_list && this.show) return this.show = false
if (e && (e.target == this.$refs.dom_btn || isChildren(this.$refs.dom_btn, e.target))) return
if (e && (e.target == this.$refs.dom_btn || this.$refs.dom_btn.contains(e.target))) return
setTimeout(() => {
this.show = false
}, 50)
@@ -70,7 +55,7 @@ export default {
handleClick(item) {
if (!item) {
item = {
name: '默认',
name: this.$t('material.tag_list.default'),
id: null,
}
}
@@ -93,6 +78,20 @@ export default {
.tag-list {
font-size: 12px;
position: relative;
&.active {
.label {
.icon {
svg{
transform: rotate(180deg);
}
}
}
.list {
opacity: 1;
transform: scaleY(1);
}
}
}
.label {
@@ -117,15 +116,10 @@ export default {
margin-left: 7px;
line-height: 0;
svg {
width: 1em;
width: .9em;
transition: transform .2s ease;
transform: rotate(0);
}
&.active {
svg{
transform: rotate(180deg);
}
}
}
&:hover {
@@ -139,16 +133,19 @@ export default {
.list {
position: absolute;
top: 100%;
width: 646px;
width: 645px;
left: 0;
border-bottom: 2px solid @color-tab-border-bottom;
border-right: 2px solid @color-tab-border-bottom;
border-bottom-right-radius: 5px;
background-color: @color-theme_2;
overflow: hidden;
background-color: @color-theme_2-background_2;
opacity: 0;
transform: scaleY(0);
overflow-y: auto;
transform-origin: 0 0 0;
max-height: 250px;
transition: .25s ease;
transition-property: height, opacity;
transition-property: transform, opacity;
z-index: 10;
padding: 10px;
box-sizing: border-box;
@@ -175,7 +172,7 @@ export default {
.type {
padding-top: 10px;
padding-bottom: 3px;
color: #999;
color: @color-theme_2-font-label;
}
.tag {
@@ -212,6 +209,7 @@ each(@themes, {
.list {
border-bottom-color: ~'@{color-@{value}-tab-border-bottom}';
border-right-color: ~'@{color-@{value}-tab-border-bottom}';
background-color: ~'@{color-@{value}-theme_2-background_2}';
// border-left-color: ~'@{color-@{value}-tab-border-bottom}';
li {
// color: ~'@{color-@{value}-btn}';
@@ -234,6 +232,10 @@ each(@themes, {
background-color: ~'@{color-@{value}-theme_2-active}';
}
}
.type {
color: ~'@{color-@{value}-theme_2-font-label}';
}
}
})

View File

@@ -1,7 +1,31 @@
<template lang="pug">
material-modal(:show="version.showModal" @close="handleClose")
main(:class="$style.main" v-if="version.newVersion")
h2 {{ version.isError ? (isUnknow ? '❓ 版本信息获取失败 ❓' : '🌟发现新版本🌟') : '🚀程序更新🚀'}}
material-modal(:show="version.showModal" @close="handleClose" v-if="version.newVersion")
main(:class="$style.main" v-if="version.isDownloaded")
h2 🚀程序更新🚀
div.scroll(:class="$style.info")
div(:class="$style.current")
h3 最新版本{{version.newVersion.version}}
h3 当前版本{{version.version}}
h3 版本变化
p(:class="$style.desc" v-html="version.newVersion.desc")
div(:class="[$style.history, $style.desc]" v-if="history.length")
h3 历史版本
div(:class="$style.item" v-for="ver in history")
h4 v{{ver.version}}
p(v-html="ver.desc")
div(:class="$style.footer")
div(:class="$style.desc")
p 新版本已下载完毕
p
| 你可以选择
strong 立即重启更新
| 或稍后
strong 关闭程序时
| 自动更新~
material-btn(:class="$style.btn" @click.onec="handleRestartClick") 立即重启更新
main(:class="$style.main" v-else-if="version.isError && !version.isUnknow && version.newVersion.version != version.version")
h2 版本更新出错
div.scroll(:class="$style.info")
div(:class="$style.current")
@@ -15,41 +39,102 @@ material-modal(:show="version.showModal" @close="handleClose")
h4 v{{ver.version}}
p(v-html="ver.desc")
div(:class="$style.footer" v-if="version.isError")
div(:class="$style.desc" v-if="!isUnknow")
p 发现有新版本啦但是自动更新功能出问题了
p
| 你现在可以选择继续使用当前版本或
strong 去发布页下载新版本
| 推荐到
strong 网盘
| 下载
div(:class="$style.btns")
material-btn(:class="$style.btn" @click.onec="handleIgnoreClick") 忽略该版本
material-btn(:class="$style.btn" @click.onec="handleOpenPageClick") 去下载新版本
div(:class="$style.footer" v-else)
div(:class="$style.footer")
div(:class="$style.desc")
p 新版本已下载完毕
p 发现有新版本啦但是自动更新功能出问题了
p
| 你可以选择
strong 立即重启更新
| 或稍后
strong 关闭程序时
| 自动更新~
material-btn(:class="$style.btn" @click.onec="handleRestartClick") 立即重启更新
| 你可以&nbsp;
strong.hover.underline(@click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" title="点击打开") 软件发布页
| &nbsp;&nbsp;
strong.hover.underline(@click="handleOpenUrl('https://www.lanzous.com/b906260/')" title="点击打开") 网盘
| (密码
strong.hover(@click="handleCopy('glqw')" title="点击复制") glqw
| )&nbsp;下载新版本
p
| 国内Windows/MAC用户推荐到
strong 网盘
| 下载
material-btn(:class="$style.btn" @click.onec="handleIgnoreClick") {{ isIgnored ? '恢复当前版本的更新失败提醒' : '忽略当前版本的更新失败提醒'}}
main(:class="$style.main" v-else-if="version.isDownloading && version.isTimeOut && !version.isUnknow")
h2 新版本下载超时
div(:class="$style.desc")
p 你当前所在网络访问GitHub较慢导致新版本下载超时已经下了半个钟了😳建议手动更新版本
p
| 你可以去
material-btn(min @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" title="点击打开") 软件发布页
|
material-btn(min @click="handleOpenUrl('https://www.lanzous.com/b906260/')" title="点击打开") 网盘
| (密码
strong.hover(@click="handleCopy('glqw')" title="点击复制") glqw
| )下载新版本
p
| 国内Windows/MAC用户推荐到
strong 网盘
| 下载
p 当前下载进度{{progress}}
main(:class="$style.main" v-else-if="version.isUnknow")
h2 获取最新版本信息失败
div.scroll(:class="$style.info")
div(:class="$style.current")
h3 当前版本{{version.version}}
div(:class="$style.desc")
p 更新信息获取失败可能是无法访问Github导致的请手动检查更新
p
| 检查方法打开
material-btn(min @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" title="点击打开") 软件发布页
|
material-btn(min @click="handleOpenUrl('https://www.lanzous.com/b906260/')" title="点击打开") 网盘
| (密码
strong.hover(@click="handleCopy('glqw')" title="点击复制") glqw
| )查看它们的
strong 版本号
| 与当前版本({{version.version}})对比是否一样
p 若一样则不必理会该弹窗直接关闭即可否则请手动下载新版本更新
main(:class="$style.main" v-else)
h2 🌟发现新版本🌟
div.scroll(:class="$style.info")
div(:class="$style.current")
h3 最新版本{{version.newVersion.version}}
h3 当前版本{{version.version}}
h3 版本变化
p(:class="$style.desc" v-html="version.newVersion.desc")
div(:class="[$style.history, $style.desc]" v-if="history.length")
h3 历史版本
div(:class="$style.item" v-for="ver in history")
h4 v{{ver.version}}
p(v-html="ver.desc")
div(:class="$style.footer")
div(:class="$style.desc")
p 发现有新版本啦正在努力更新中若下载太慢可以手动更新哦~
p
| 你也可以关闭本弹窗继续使用软件还可在
strong 设置-软件更新
| 重新打开本弹窗
p
| 手动更新可以去&nbsp;
strong.hover.underline(@click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" title="点击打开") 软件发布页
| &nbsp;&nbsp;
strong.hover.underline(@click="handleOpenUrl('https://www.lanzous.com/b906260/')" title="点击打开") 网盘
| (密码
strong.hover(@click="handleCopy('glqw')" title="点击复制") glqw
| )&nbsp;下载
p 国内Windows/MAC用户推荐到网盘下载
p 当前下载进度{{progress}}
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
import { rendererSend } from '../../../common/icp'
import { checkVersion, openUrl } from '../../utils'
import { rendererSend } from '../../../common/ipc'
import { checkVersion, openUrl, clipboardWriteText, sizeFormate } from '../../utils'
export default {
computed: {
...mapGetters(['version', 'setting']),
history() {
if (!this.version.newVersion) return []
if (!this.version.newVersion || !this.version.newVersion.history) return []
let arr = []
let currentVer = this.version.version
this.version.newVersion.history.forEach(ver => {
@@ -58,30 +143,37 @@ export default {
return arr
},
isUnknow() {
return this.version.newVersion.version == '0.0.0'
progress() {
return this.version.downloadProgress
? `${this.version.downloadProgress.percent.toFixed(2)}% - ${sizeFormate(this.version.downloadProgress.transferred)}/${sizeFormate(this.version.downloadProgress.total)} - ${sizeFormate(this.version.downloadProgress.bytesPerSecond)}/s`
: '初始化中...'
},
isIgnored() {
return this.setting.ignoreVersion == this.version.newVersion.version
},
},
methods: {
...mapMutations(['setVersionModalVisible', 'setSetting']),
...mapMutations(['setVersionModalVisible', 'setIgnoreVersion']),
handleClose() {
this.setVersionModalVisible({
isShow: false,
})
},
handleIgnoreClick(event) {
this.handleClose()
// event.target.disabled = true
this.setSetting(Object.assign({}, this.setting, { ignoreVersion: this.version.newVersion.version }))
},
handleOpenPageClick() {
openUrl('https://github.com/lyswhut/lx-music-desktop#readme')
handleOpenUrl(url) {
openUrl(url)
},
handleRestartClick(event) {
this.handleClose()
event.target.disabled = true
rendererSend('quit-update')
},
handleCopy(text) {
clipboardWriteText(text)
},
handleIgnoreClick() {
this.setIgnoreVersion(this.isIgnored ? null : this.version.newVersion.version)
this.handleClose()
},
},
}
</script>
@@ -123,6 +215,7 @@ export default {
line-height: 1.5;
overflow-y: auto;
height: 100%;
padding-right: 5px;
}
.current {
> p {
@@ -141,6 +234,10 @@ export default {
list-style: initial;
padding-inline-start: 30px;
}
p {
font-size: 14px;
line-height: 1.5;
}
}
.history {
@@ -168,24 +265,23 @@ export default {
.footer {
flex: 0 0 none;
.desc {
padding-top: 10px;
font-size: 12px;
padding: 10px 0;
color: @color-theme;
line-height: 1.2;
strong {
text-decoration: underline;
line-height: 1.25;
p {
font-size: 12px;
color: @color-theme;
line-height: 1.25;
}
}
}
.btn {
margin-top: 10px;
display: block;
width: 100%;
}
.btns {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 0 10px;
}
each(@themes, {
:global(#container.@{value}) {
@@ -197,6 +293,9 @@ each(@themes, {
.footer {
.desc {
color: ~'@{color-@{value}-theme}';
p {
color: ~'@{color-@{value}-theme}';
}
}
}
}

View File

@@ -0,0 +1,103 @@
<template lang="pug">
material-modal(:show="show" :bg-close="bgClose" @close="handleClose")
main(:class="$style.main")
h2
| {{$t('material.list_add_modal.title_first')}}&nbsp;
span(:class="$style.name") {{this.musicInfo && `${musicInfo.name}`}}
| &nbsp;{{$t('material.list_add_modal.title_last')}}
material-btn(:class="$style.btn" :title="$t('material.list_add_modal.btn_title', { name: item.name })" :key="item.id" @click="handleClick(index)" v-for="(item, index) in lists") {{item.name}}
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
export default {
props: {
show: {
type: Boolean,
default: false,
},
musicInfo: {
type: Object,
},
bgClose: {
type: Boolean,
default: true,
},
excludeListId: {
type: Array,
default() {
return []
},
},
},
computed: {
...mapGetters('list', ['defaultList', 'loveList', 'userList']),
lists() {
return [
this.defaultList,
this.loveList,
...this.userList,
].filter(l => !this.excludeListId.includes(l.id))
},
},
methods: {
...mapMutations('list', ['listAdd']),
handleClick(index) {
this.listAdd({ id: this.lists[index].id, musicInfo: this.musicInfo })
this.$nextTick(() => {
this.handleClose()
})
},
handleClose() {
this.$emit('close')
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.main {
padding: 15px;
max-width: 300px;
min-width: 200px;
display: flex;
flex-flow: column nowrap;
justify-content: center;
h2 {
font-size: 13px;
color: @color-theme_2-font;
line-height: 1.3;
text-align: center;
margin-bottom: 15px;
}
}
.name {
color: @color-theme;
}
.btn {
display: block;
margin-bottom: 15px;
&:last-child {
margin-bottom: 0;
}
}
each(@themes, {
:global(#container.@{value}) {
.main {
h2 {
color: ~'@{color-@{value}-theme_2-font}';
}
}
.name {
color: ~'@{color-@{value}-theme}';
}
}
})
</style>

View File

@@ -0,0 +1,97 @@
<template lang="pug">
material-modal(:show="show" :bg-close="bgClose" @close="handleClose")
main(:class="$style.main")
h2 {{$t('material.list_add_multiple_modal.title', { num: musicList.length })}}
material-btn(:class="$style.btn" :title="$t('material.list_add_multiple_modal.btn_title', { name: item.name })" :key="item.id" @click="handleClick(index)" v-for="(item, index) in lists") {{item.name}}
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
export default {
props: {
show: {
type: Boolean,
default: false,
},
musicList: {
type: Array,
default() {
return []
},
},
bgClose: {
type: Boolean,
default: true,
},
excludeListId: {
type: Array,
default() {
return []
},
},
},
computed: {
...mapGetters('list', ['defaultList', 'loveList', 'userList']),
lists() {
return [
this.defaultList,
this.loveList,
...this.userList,
].filter(l => !this.excludeListId.includes(l.id))
},
},
methods: {
...mapMutations('list', ['listAddMultiple']),
handleClick(index) {
this.listAddMultiple({ id: this.lists[index].id, list: this.musicList })
this.$nextTick(() => {
this.handleClose(true)
})
},
handleClose(isSelect = false) {
this.$emit('close', isSelect)
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.main {
padding: 15px;
max-width: 300px;
min-width: 200px;
display: flex;
flex-flow: column nowrap;
justify-content: center;
h2 {
font-size: 13px;
color: @color-theme_2-font;
line-height: 1.3;
text-align: center;
margin-bottom: 15px;
}
}
.btn {
display: block;
margin-bottom: 15px;
&:last-child {
margin-bottom: 0;
}
}
each(@themes, {
:global(#container.@{value}) {
.main {
h2 {
color: ~'@{color-@{value}-theme_2-font}';
}
}
}
})
</style>

View File

@@ -0,0 +1,50 @@
<template lang="pug">
material-modal(:show="show" :bg-close="bgClose" @close="handleClose")
main.ignore-to-rem(:class="$style.main")
h2 {{$t('material.xm_verify_modal.title')}}
</template>
<script>
export default {
props: {
show: {
type: Boolean,
default: false,
},
bgClose: {
type: Boolean,
default: true,
},
},
methods: {
handleClose() {
this.$emit('close')
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.main {
background: #fff !important;
&:global(.ignore-to-rem) {
padding: 15px;
width: 360px;
height: 330px;
h2 {
font-size: 16px;
}
}
h2 {
font-size: 13px;
color: @color-theme_2-font;
line-height: 1.3;
text-align: center;
}
}
</style>

View File

@@ -0,0 +1,46 @@
import mousetrap from 'mousetrap'
let eventHub
const bindKey = () => {
mousetrap.reset()
mousetrap.bind('shift', (event, combo) => {
eventHub.$emit('key_shift_down', { event, combo })
return false
}, 'keydown')
mousetrap.bind('shift', (event, combo) => {
eventHub.$emit('key_shift_up', { event, combo })
return false
}, 'keyup')
mousetrap.bind('mod', (event, combo) => {
eventHub.$emit('key_mod_down', { event, combo })
return false
}, 'keydown')
mousetrap.bind('mod', (event, combo) => {
eventHub.$emit('key_mod_up', { event, combo })
return false
}, 'keyup')
mousetrap.bind('mod+a', (event, combo) => {
eventHub.$emit('key_mod+a_down', { event, combo })
return false
}, 'keydown')
mousetrap.bind('mod+a', (event, combo) => {
eventHub.$emit('key_mod+a_up', { event, combo })
return false
}, 'keyup')
}
const unbindKey = () => {
mousetrap.unbind('shift', 'keydown')
mousetrap.unbind('shift', 'keyup')
mousetrap.unbind('mod', 'keydown')
mousetrap.unbind('mod', 'keyup')
mousetrap.unbind('mod+a', 'keydown')
mousetrap.unbind('mod+a', 'keyup')
}
export default () => {
eventHub = window.eventHub
eventHub.$on('bindKey', bindKey)
eventHub.$on('unbindKey', unbindKey)
}

View File

@@ -0,0 +1,12 @@
{
"online_music": "在线音乐",
"search": "搜索",
"song_list": "歌单",
"leaderboard": "排行榜",
"my_music": "我的音乐",
"default_list": "试听列表",
"love_list": "收藏列表",
"other": "其他",
"download": "下载管理",
"setting": "设置"
}

View File

@@ -0,0 +1,21 @@
{
"copy_title": "(点击复制)",
"volume": "当前音量:",
"pause": "暂停",
"play": "播放",
"prev": "上一首",
"next": "下一首",
"playing": "播放中...",
"stop": "暂停播放",
"end": "播放完毕",
"refresh_url": "URL过期正在刷新URL...",
"error": "音频加载出错5 秒后切换下一首",
"loading": "音乐加载中...",
"buffering": "缓冲中...",
"geting_url": "歌曲链接获取中...",
"lyric_error": "歌词获取失败",
"hide_detail": "隐藏详情页",
"name": "歌曲名:",
"singer": "艺术家:",
"album": "专辑名:"
}

View File

@@ -0,0 +1,4 @@
{
"min": "最小化",
"close": "关闭"
}

View File

@@ -0,0 +1,6 @@
{
"btn_tip": "该音质不可用",
"lossless": "无损音质",
"high_quality": "高品音质",
"normal": "普通音质"
}

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