Compare commits

...
400 Commits
Author SHA1 Message Date
lyswhut 1e03791e82 更新依赖
Build / Mac (push) Waiting to run
Build / Linux (push) Waiting to run
Build / Windows (push) Waiting to run
2024-03-23 21:55:50 +08:00
lyswhut d92e34949c 更新依赖&版本号(2.7.0-beta.2) 2024-03-17 16:34:39 +08:00
lyswhut c8423c0b4f 移除未使用的网络代理设置用户名、密码设置 2024-03-14 18:23:54 +08:00
lyswhut 2c1b4acfcc 修复网络代理设置没有对自定义源的网络请求生效的问题(#1814) 2024-03-14 18:23:47 +08:00
lyswhut b14883c9af 新增在线自定义源导入功能,允许通过http/https链接导入自定义源 2024-03-12 14:33:21 +08:00
lyswhut 53245e9e4f 更新依赖 2024-03-11 21:58:02 +08:00
lyswhut 45fc8c77b6 优化本地歌曲换源匹配机制 2024-03-08 11:54:00 +08:00
lyswhut 9d8fe4be06 托盘菜单添加多语言支持(#1802) 2024-03-03 19:20:20 +08:00
lyswhut 03781ea692 修复kg最新评论无法获取的问题(@helloplhm-qwq) 2024-03-02 15:04:05 +08:00
lyswhut 3c28c4861b 修复构建问题 2024-03-01 15:18:25 +08:00
lyswhut 0c986ee68c 更新依赖&版本号(2.7.0-beta.0) 2024-03-01 15:08:46 +08:00
lyswhut 17d45d7406 主题编辑器添加“深色字体”选项(#1799) 2024-03-01 14:56:12 +08:00
lyswhut 07ed408129 修复更新超时弹窗在非更新阶段意外弹出的问题(#1797) 2024-02-29 12:06:06 +08:00
lyswhut c001803d71 主题设置默认折叠其他主题以优化进入设置界面时的性能 2024-02-20 22:57:19 +08:00
lyswhut 4ce54ea395 更新运行项目nodejs版本要求 2024-02-19 12:09:51 +08:00
梓澄qwq 2039431735 update (#1791)
- 支持kw排行榜显示大小
- 支持某些kg逐行歌词
- 修复mg歌单搜索
2024-02-19 12:08:21 +08:00
SamLukeYesandlyswhut 15c9207542 修复 Linux 端 hostnamectl 命令不可用时无法与手机端同步的问题 (#1784)
* Don't fail to sync without hostnamectl

* update

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2024-02-15 00:00:14 +08:00
lyswhut 79d8d79b9d 更新依赖&版本号 2024-02-14 11:13:25 +08:00
lyswhut 806925333f 修复mg歌词在某些情况下获取失败的问题(#1783) 2024-02-14 10:52:03 +08:00
lyswhut 2e196656ba 更新 electron 到 v27.3.1 2024-02-06 11:09:53 +08:00
lyswhut 55204df130 加长自定义源返回的歌词长度 2024-02-06 10:38:24 +08:00
lyswhut 627c50910c 修复某些情况下歌曲加载时间过长时不会自动跳到下一首的问题 2024-02-04 18:05:08 +08:00
lyswhut 959e11b77c 发布v2.6.0 2024-02-01 12:44:58 +08:00
lyswhut 6cc9da338b 更新版本号(2.6.0-beta.10) 2024-01-30 20:17:11 +08:00
lyswhut 1cd93344dd 自定义源新增支持local源的musicUrl、pic、lyric的获取操作 2024-01-30 19:25:01 +08:00
lyswhut 7893814da4 优化自定义源调用处理逻辑 2024-01-30 17:40:23 +08:00
lyswhut da2d3237b6 简化自定义源名称显示 2024-01-30 12:33:25 +08:00
lyswhut 431b04a011 添加创建同名列表时的二次确认(#1621) 2024-01-30 12:33:25 +08:00
lyswhut f13c6f0153 更新action依赖版本 2024-01-30 12:33:25 +08:00
梓澄qwq 160693f608 fix: migu search (#1761) 2024-01-30 12:14:48 +08:00
lyswhut b127a1e98c 修复封面图片嵌入后没有被删除的问题 2024-01-26 16:14:25 +08:00
lyswhut b0a9cd70ac 更新 sqlite3 预编译文件 2024-01-26 16:06:40 +08:00
lyswhut 5955831809 更新依赖&版本号(2.6.0-beta.9) 2024-01-26 15:44:22 +08:00
lyswhut b13f10d624 新增播放歌曲时阻止电脑休眠(#1563) 2024-01-22 12:42:46 +08:00
lyswhut 47635c95e1 添加对下载歌曲时封面图片大小的控制处理(#1609) 2024-01-22 12:35:34 +08:00
lyswhut c238d49584 修复潜在自定义源切换问题 2024-01-22 12:35:34 +08:00
lyswhut 2ff6455231 更新依赖 2024-01-22 12:35:34 +08:00
lyswhut 110db561a9 修复最小化后再隐藏窗口时,托盘菜单的显示主界面功能异常的问题 2024-01-22 12:35:34 +08:00
lyswhut 61013e9d2d 修复临时列表变更会意外触发同步的问题 2024-01-22 12:35:34 +08:00
lyswhut 2de6d1c5f1 更新版本号(2.6.0-beta.8) 2024-01-07 19:11:46 +08:00
lyswhut 991e453881 降级vue修复某些情况下切换界面时出现空白的问题 2024-01-07 19:11:06 +08:00
lyswhut edcf501a22 更新依赖 2024-01-06 20:26:30 +08:00
lyswhut 1fc5cf0aa5 不再丢弃酷我某些带翻译歌词 2024-01-06 20:14:56 +08:00
lyswhut ce072ae9d3 不再丢弃大部分带翻译的酷我歌词 2024-01-01 19:46:31 +08:00
lyswhut a811b1da1a 更新依赖&版本号(2.6.0-beta.7) 2023-12-30 14:20:31 +08:00
lyswhut 1de087eac7 优化任务下载状态显示 2023-12-30 12:31:18 +08:00
lyswhut 2337a3e8a3 修复本地歌曲歌词获取问题 2023-12-28 14:53:12 +08:00
lyswhut 1637ae4d17 修复构建顺序导致的更新信息被覆盖的问题 2023-12-25 14:28:16 +08:00
lyswhut f46a08fe89 更新依赖&更新版本号(2.6.0-beta.6) 2023-12-23 18:20:16 +08:00
lyswhut 2c0d2c4497 优化黑色主题颜色计算 2023-12-17 21:50:12 +08:00
lyswhut f75eeb07be 改进本地音乐在线信息的匹配机制 2023-12-14 20:43:43 +08:00
lyswhut 965a1d03ba 修复下载模块在数据写入速度较慢的情况下出现任务及文件异常的问题 2023-12-13 01:19:17 +08:00
lyswhut 12f947ed2e 注释调试代码 2023-12-12 14:33:09 +08:00
lyswhut 97d684fde1 修复构建 2023-12-12 14:20:39 +08:00
lyswhut 75c1069251 修复构建 2023-12-12 14:14:35 +08:00
lyswhut 050b641d79 更新依赖&版本号(2.6.0-beta.5) 2023-12-12 14:00:22 +08:00
lyswhut e9539b5c5c 添加对wy源某些歌曲有问题的歌词进行修复 2023-12-12 13:44:12 +08:00
lyswhut 6fc5a47970 优化搜索框交互逻辑 2023-12-12 13:25:25 +08:00
lyswhut 11317154f6 优化GitHub actions工作流 2023-12-11 11:31:57 +08:00
lyswhut 98b4abfa19 为播放详情页歌词选择界面添加全选快捷键支持 2023-12-08 16:34:33 +08:00
lyswhut f40f019d2e 禁止默认的全选行为 2023-12-08 16:34:28 +08:00
lyswhut 42109ce000 更新依赖&版本号(2.6.0-beta.4) 2023-12-08 11:52:02 +08:00
lyswhut a38df1280b 修复歌单分类标签列表无法打开的问题 2023-12-08 11:45:29 +08:00
lyswhut e19002caca 构建阶段压缩代码、外置map 2023-12-08 11:13:22 +08:00
lyswhut b67d99e5a5 修复 MacOS 平台在 Finder 打开文件或目录时应用卡死的问题(#1684) 2023-12-08 10:47:41 +08:00
lyswhut 15942a7b96 优化窗口控制按钮聚焦效果显示时机 2023-12-05 13:06:48 +08:00
lyswhut cac2d0e050 优化列表全选机制,修复列表未获得焦点时仍然可以全选的问题 2023-12-05 11:14:44 +08:00
lyswhut 7d504f9231 添加win7_x64架构的安装版安装包构建 2023-12-05 10:43:14 +08:00
lyswhut c0f78b023f 显示源版本号信息 2023-12-04 13:35:21 +08:00
lyswhut 3803ab045c 更新版本号(2.6.0-beta.3) 2023-12-04 12:36:13 +08:00
lyswhut bee2790af3 切换到简易的 store 实现 2023-12-04 12:34:12 +08:00
lyswhut 182a3076fc 更新依赖 2023-12-03 15:34:37 +08:00
lyswhut a9bcb71afe 隐藏源描述 2023-12-03 15:34:32 +08:00
lyswhut 4f5baa1ac8 添加超时 2023-12-03 15:06:04 +08:00
lyswhut 323743e426 更新依赖(v2.6.0-beta.2) 2023-11-25 12:31:45 +08:00
lyswhut 3ba51fd6fe 自定义源列显示源版本号、作者名字,优化自定义源脚本数据存储 2023-11-25 12:19:36 +08:00
lyswhut ff98e06afa 关闭mac下的圆角 2023-11-25 10:29:06 +08:00
lyswhut 6f22c73d99 fix code 2023-11-22 12:59:30 +08:00
lyswhut 87bd27f814 更新依赖 2023-11-22 12:47:55 +08:00
lyswhut 3e7db5617b 修复亮暗主题自动切换功能无效的问题(#1697) 2023-11-22 12:20:15 +08:00
lyswhut 1712e466d5 更新翻译 2023-11-13 13:59:58 +08:00
梓澄 abf49529d6 fix: kg源在海外无法搜索 (#1686) 2023-11-11 13:59:48 +08:00
lyswhut 3e31a78af1 修复win构建 2023-11-11 13:20:58 +08:00
lyswhut 41fc46c8b5 修复构建 2023-11-11 12:56:07 +08:00
lyswhut db8cfec7ad 更新依赖 2023-11-10 15:08:06 +08:00
lyswhut 66376daad4 优化移除源时的行为 2023-11-09 10:54:57 +08:00
lyswhut 182f08d221 优化 2023-11-09 00:19:07 +08:00
lyswhut 887cf39321 修复错误处理问题 2023-11-08 15:27:36 +08:00
lyswhut e2f6ec308c 更新依赖 2023-11-08 13:43:08 +08:00
lyswhut b1f2a27eac 自定义源变更 2023-11-07 22:18:56 +08:00
lyswhut 0c6cedf879 更新依赖 2023-10-25 14:22:41 +08:00
lyswhut 1ff6a00653 移除所有内置源 2023-10-18 19:46:05 +08:00
lyswhut 016821a8ce 更新依赖 2023-10-17 18:14:49 +08:00
lyswhut a786ae24e9 Windows、MacOS平台下的字体列表取消使用原生方式获取(#1596) 2023-10-07 11:17:04 +08:00
lyswhut 799b768367 修复备份文件无法导入json格式的问题 2023-10-05 14:45:37 +08:00
lyswhut 24610f9c0a 更新依赖 2023-10-05 14:45:27 +08:00
lyswhut 172307d53b 更新许可协议的排版,使其看起来更加清晰明了 2023-10-05 14:45:15 +08:00
lyswhut d7816729d0 优化代码逻辑 2023-10-03 15:10:54 +08:00
lyswhut ac7daa70f7 添加win arm 7z构建 2023-09-28 13:15:14 +08:00
lyswhut 0372f0647b 更新正式版构建流程 2023-09-28 12:54:00 +08:00
lyswhut 60f98ac438 发布v2.5.0 2023-09-28 12:02:32 +08:00
lyswhut 6d76814aed 更新依赖 2023-09-28 11:56:01 +08:00
lyswhut 2cf5bb689f 修复点击搜索、排行榜等在线列表歌曲右键菜单歌曲详情页会意外将该歌曲添加不喜欢的问题 2023-09-21 13:19:55 +08:00
lyswhut e18c295ca6 更新版本号 2023-09-20 13:54:48 +08:00
lyswhut 58346d6fc9 优化不喜欢编辑框滚动条显示 2023-09-20 13:53:34 +08:00
lyswhut 5364b7a09b 通过歌曲菜单添加不喜欢歌曲时需要二次确认防止手抖 2023-09-20 13:41:25 +08:00
lyswhut 52630aa735 修复更换语言设置后源名称未更新的问题 2023-09-20 12:23:48 +08:00
lyswhut 89255a8c84 修复dev模式运行问题 2023-09-20 12:22:27 +08:00
lyswhut 6d5cc38235 更新构建设置 2023-09-20 11:09:11 +08:00
lyswhut c4234ddcff 更新版本号 2023-09-17 14:06:51 +08:00
lyswhut 7a9872d42f 更新依赖 2023-09-17 14:00:49 +08:00
lyswhut f5165d517c 修复音频输出设备设置在重启软件后被重置的问题(#1568) 2023-09-17 13:56:10 +08:00
lyswhut 0b125e29b5 新增Scheme URL对播放器的控制操作 2023-09-14 17:19:13 +08:00
lyswhut aabd6d8985 更新依赖 2023-09-14 11:04:58 +08:00
lyswhut 94dccae48a 添加换源超时定时器 2023-09-12 12:52:31 +08:00
lyswhut 74ef112d57 修复同步数据字段顺序不一致的问题 2023-09-10 16:10:51 +08:00
lyswhut 0da63c6ef0 发布v2.4.1 2023-09-09 16:04:01 +08:00
lyswhut 3c145c7dd7 修复 v2.4.0 的默认数据库版本号不对导致无法启动问题 2023-09-09 15:52:51 +08:00
lyswhut 63590119bb 发布v2.4.0 2023-09-09 12:16:16 +08:00
lyswhut 7845d65119 修改code lint时机 2023-09-08 22:16:43 +08:00
lyswhut 2454738224 更新版本号 2023-09-06 21:58:08 +08:00
lyswhut 3685cb8868 修复潜在同步问题 2023-09-06 21:50:58 +08:00
lyswhut fcffcb5b59 更新同步协商流程 2023-09-06 16:01:23 +08:00
lyswhut 19049fb890 修复信息更新问题 2023-09-06 12:21:12 +08:00
lyswhut 50950cc9d2 更新版本号 2023-09-06 12:02:38 +08:00
lyswhut b1f9e525ab 新增同步功能对“不喜欢歌曲”列表的同步 2023-09-05 23:11:09 +08:00
lyswhut fc03cd77fc 移除日志 2023-09-05 13:28:50 +08:00
lyswhut 9a3434b02e 新增设置-播放设置-点击相同列表内的歌曲切歌时是否清空已播放列表 2023-09-05 12:50:16 +08:00
lyswhut eab5e51f66 修复潜在问题 2023-09-05 12:14:25 +08:00
lyswhut ff6b7cbec2 更新better-sqlite3 2023-09-05 00:29:43 +08:00
lyswhut abe62d84ed 修复同步功能列表元数据合并问题 2023-09-04 22:55:36 +08:00
lyswhut 1aa9627e4a 去重 2023-09-04 18:04:54 +08:00
lyswhut 8bbe549b0c 优化 2023-09-04 14:21:08 +08:00
lyswhut baf69e397a 新增“不喜欢歌曲”功能&新增“收藏歌曲”、“取消收藏”、“不喜欢该歌曲”快捷键设置 2023-09-04 14:13:47 +08:00
lyswhut 3ca8f09971 优化歌词滚动 2023-09-03 18:08:10 +08:00
lyswhut 3381ad0e0e 修正事件名字 2023-09-03 17:29:43 +08:00
lyswhut 150342f699 单曲循环模式不应对“稍后播放”的歌曲有效 2023-09-03 00:58:31 +08:00
lyswhut 44830752f7 更新依赖 2023-09-02 13:50:09 +08:00
lyswhut 5e7ef691ae 优化歌词在短时间内快速播放时的滚动效果 2023-09-02 13:39:46 +08:00
lyswhut ed858bafdc 修复kg无评论时的加载处理问题 2023-09-02 13:33:44 +08:00
lyswhut 2fff8652ca lint code 2023-08-30 14:30:47 +08:00
lyswhut 358142c1cc 更新新版本获取地址 2023-08-30 13:01:27 +08:00
lyswhut 903ca18b75 修改code lint的执行时机 2023-08-29 17:52:24 +08:00
lyswhut 6a91d3eec2 更新版本号 2023-08-29 17:50:22 +08:00
lyswhut 0d5e880fb2 优化同步服务逻辑 2023-08-29 17:46:16 +08:00
lyswhut 03f322f92a 优化桌面歌词在开启 缩放当前播放的歌词 并关闭 延迟歌词滚动 时的歌词滚动位置计算问题 2023-08-29 17:45:48 +08:00
lyswhut 5cf2acfff2 修复同步问题 2023-08-29 16:10:26 +08:00
lyswhut 7d17423e4b 新增数据同步服务端模式已认证设备列表管理 2023-08-29 15:40:42 +08:00
lyswhut 5d877d81e3 修复潜在问题 2023-08-29 12:32:31 +08:00
lyswhut a968410431 添加连接超时处理 2023-08-28 16:04:58 +08:00
lyswhut c6f4bfa875 修改同步逻辑 2023-08-28 13:39:23 +08:00
lyswhut 71b84fb10d lint code 2023-08-27 15:09:07 +08:00
lyswhut 0d76c9ccb4 重构同步服务端功能部分代码,使其更易扩展新功能 2023-08-25 19:13:50 +08:00
lyswhut 70815e276e 更新依赖 2023-08-21 20:18:24 +08:00
lyswhut 72fe8f3af1 修复潜在同步问题 2023-08-21 13:57:36 +08:00
lyswhut 53bc50d05c Revert vite build 2023-08-17 17:49:47 +08:00
lyswhut 99eb2856f7 更新版本号 2023-08-15 15:52:41 +08:00
lyswhut 616135ac14 修复某些UI显示异常的问题 2023-08-15 15:51:32 +08:00
lyswhut 5ca490d233 修正描述错误 2023-08-14 16:06:43 +08:00
lyswhut 1c159f59bb 优化同步逻辑 2023-08-14 15:33:59 +08:00
lyswhut 4ad9c81381 点击打开歌单弹窗背景将不再自动关闭弹窗 2023-08-11 22:22:37 +08:00
lyswhut d11eaec7d5 opm 2023-08-11 14:30:01 +08:00
lyswhut a02748d518 更新依赖 2023-08-11 14:21:59 +08:00
lyswhut 0ab2940347 修复本地文件解析异常时的错误处理问题 2023-08-11 14:17:17 +08:00
lyswhut fc2585eb95 更新说明 2023-08-11 13:40:09 +08:00
lyswhut c2789312ba 修复首句歌词被提前播放的问题 2023-08-11 13:28:29 +08:00
lyswhut e1646a3127 修复windows平台下隐藏窗口后再显示时任务栏按钮丢失的问题 2023-08-10 21:08:16 +08:00
lyswhut 0fc1de9f29 移除日志输出 2023-08-09 14:37:54 +08:00
lyswhut 7c5969e580 修补依赖以使vite恢复构建 2023-08-09 14:37:48 +08:00
lyswhut e719999d8a 移除多余代码 2023-08-08 20:05:22 +08:00
lyswhut 59ea4e4b44 修复构建 2023-08-07 18:56:26 +08:00
lyswhut df52e8ede9 更新版本号 2023-08-07 18:50:35 +08:00
lyswhut 028bf5919d 从webpack迁移到vite 2023-08-07 18:49:14 +08:00
彭狸花喵andlyswhut 31e16a7ed7 优化kg评论区at显示 (#1515)
* 优化kg评论区at显示

* lint code

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2023-08-07 18:36:13 +08:00
lyswhut aa6696dfcc 修复tx排行榜、歌单列表歌曲名称显示 2023-08-04 16:25:58 +08:00
彭狸花喵andlyswhut 838355a5e9 修复KGMusic歌单歌曲无扩展信息 & 修复QQMusic搜索歌曲无扩展信息 (#1509)
* fix: QQMusic搜索歌曲扩展信息

* 修复KGMusic调用musicInfo时无括号中数据

* 修复KGMusic歌单歌曲括号中信息

* 修复

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2023-08-03 20:30:48 +08:00
lyswhut f87962f1a0 修复图片显示问题 2023-08-02 20:09:45 +08:00
lyswhut 8e4158d11d 更新默认设置 2023-08-01 11:35:15 +08:00
lyswhut 60f8f10bb5 过滤翻译歌词或罗马音歌词中只有“//”的行(#1499) 2023-07-30 14:45:35 +08:00
lyswhut de5e1d7274 更新依赖 2023-07-30 12:32:20 +08:00
lyswhut cfe78fc387 减少歌词定时器调用阈值 2023-07-30 12:32:12 +08:00
lyswhut 2a8bfac2c5 更新依赖 2023-07-25 15:49:11 +08:00
lyswhut 3c5c684641 优化音效设置-环境音效启用、禁用时的操作效果显示 2023-07-25 15:37:23 +08:00
lyswhut c757ac8517 修复音效文件被意外重复打包的问题 2023-07-24 14:58:55 +08:00
lyswhut 2597ab4ea1 修复某些tx源歌词因数据异常解析失败的问题 2023-07-23 19:57:36 +08:00
Folltosheandlyswhut 783799b2a4 更新酷我评论区带图消息 (#1471)
* 更新酷我评论区带图消息

* 移除多余代码

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2023-07-21 12:31:23 +08:00
lyswhut 87aec33833 添加说明 2023-07-19 12:04:35 +08:00
lyswhut 06087cd853 修复kw音质列表数据 2023-07-19 12:03:20 +08:00
lyswhut afaef43017 更新依赖 2023-07-18 19:04:25 +08:00
lyswhut e7f3f27104 修复搜索框在某些情况下输入内容后搜索时会自动清空的问题(#1472) 2023-07-18 18:39:12 +08:00
lyswhut 6c0e11eebc 添加说明 2023-07-14 15:11:10 +08:00
lyswhut 809752e4ec 修复若路径存在 # 字符时,软件无法启动的问题(#1461) 2023-07-14 15:01:05 +08:00
lyswhut 54f6255fe4 更新依赖 2023-07-11 15:18:08 +08:00
brstligandlyswhut d97a27307c 增加列表中歌曲随机乱序排列,修复专辑名排序无效bug (#1440)
* Update zh-cn.json

* Update ListSortModal.vue

* Add files via upload

* Update ListSortModal.vue

修复无法按专辑名排序bug,因变量名不一致导致

* 完善&优化

* 添加更新日志

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2023-07-11 15:04:13 +08:00
Folltosheandlyswhut 6423066cb2 修复kw搜索联想词 (#1452)
* 修复kw搜索联想词

* 完善

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2023-07-07 23:54:32 +08:00
lyswhut e8d8feb042 更新依赖 2023-07-04 21:34:18 +08:00
lyswhut 816caa6415 修复字体设置某些字体无法应用的问题 2023-07-04 19:47:19 +08:00
Folltoshe a11d123815 修复歌曲排序时默认列表名称显示错误 (#1439) 2023-07-01 10:24:39 +08:00
lyswhut c24d1d1e41 发布v2.3.0 2023-06-29 23:48:02 +08:00
lyswhut 1b2daef90c 更新依赖 2023-06-29 23:30:01 +08:00
lyswhut 6284fd8024 更新依赖 2023-06-27 18:58:51 +08:00
lyswhut 763874ac55 更新依赖 2023-06-26 10:53:46 +08:00
lyswhut 4defaa103e 修复linux arm64下无法启动的问题 2023-06-23 10:28:48 +08:00
Folltoshe 181ac937d2 修复mg歌单搜索 (#1414) 2023-06-21 12:30:06 +08:00
lyswhut f6884524e2 更新依赖 2023-06-21 12:16:06 +08:00
lyswhut 23f118fb1a 过滤嵌入、下载的翻译、罗马音歌词时间标签(#1358) 2023-06-21 11:59:38 +08:00
lyswhut a3b8f31b1e 修复tx翻译歌词解析丢失的问题 2023-06-21 10:58:04 +08:00
lyswhut c6fb2232d0 修复mg歌单搜索歌单播放数量显示问题 2023-06-17 20:05:03 +08:00
lyswhut ef411ddb47 更新依赖 2023-06-16 11:23:11 +08:00
lyswhut 7aed2828bb 修复添加单首歌曲弹窗列表创建按钮无法取消的问题 2023-06-16 11:23:08 +08:00
lyswhut 560445a518 添加提示 2023-06-14 11:46:50 +08:00
Folltoshe 3549ed2da0 修复wy热门评论显示 (#1411) 2023-06-14 11:25:10 +08:00
lyswhut ec9f86c450 修复构建 2023-06-10 23:44:42 +08:00
lyswhut c6e829be4f 更新依赖 2023-06-10 20:40:22 +08:00
lyswhut 5ef84916bb 修复初始化列表信息可能导致崩溃的问题 2023-06-10 20:34:25 +08:00
94403be9a8 预留歌手API (#1407)
* 更新歌手API

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
Co-authored-by: 彭狸花喵 <helloplhm-qwq@outlook.com>
2023-06-10 20:33:21 +08:00
lyswhut 6403b8ce38 code lint 2023-06-09 09:55:54 +08:00
lyswhut 3f34fa26bf 更新依赖 2023-06-09 09:46:35 +08:00
彭狸花喵andFolltoshe b1a6c6b59c 更新kg最新评论接口(cmtlist->newest) & kg源歌手相关 (#1405)
* 修复wy源热搜词失效

* 更新kg最新评论接口(cmtlist->newest) & kg源歌手相关

---------

Co-authored-by: Folltoshe <folltoshe@foxmail.com>
2023-06-09 09:45:01 +08:00
lyswhut 76981a59d4 更新默认设置 2023-06-08 09:57:15 +08:00
Folltosheandlyswhut d93fbd699d 修复wy源热搜词 (#1403)
* 修复wy源热搜词失效

* 添加更新日志

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2023-06-08 09:50:16 +08:00
lyswhut fd2dca0769 移除桌面歌词窗口透明边距 2023-06-05 23:12:43 +08:00
lyswhut 5097102ca1 修复Deepin 20下启用桌面歌词时可能会导致桌面卡死的问题 2023-06-05 22:36:00 +08:00
lyswhut fa3fd8d8a4 Windows、MacOS平台下的字体列表改用原生方式获取 2023-06-05 15:05:03 +08:00
lyswhut 5582e78315 更新版本号 2023-06-04 13:13:02 +08:00
lyswhut b7fd32067f 优化升降调性能占用 2023-06-04 12:53:46 +08:00
彭狸花喵andFolltoshe 2a2fdc57bd 修复tx热门评论昵称被错误切割的问题 (#1397)
* 修复tx热门评论昵称被错误切割的问题

* fix

---------

Co-authored-by: Folltoshe <folltoshe@foxmail.com>
2023-06-04 12:14:34 +08:00
lyswhut 025739e35c 优化&更新依赖 2023-06-02 12:27:41 +08:00
lyswhut c8f6153831 移除升降调预设 2023-06-01 14:35:46 +08:00
lyswhut 3c02e3dddc 更新版本号 2023-05-31 21:41:22 +08:00
lyswhut 40997c34ff 添加音调升降调节 2023-05-31 21:40:25 +08:00
lyswhut 677c3f16e4 播放速率设置面板新增是否音调补偿设置 2023-05-31 20:26:19 +08:00
lyswhut 0adb4f5ae5 修复改变播放速率时会导致歌词报错的问题 2023-05-31 09:07:35 +08:00
lyswhut cbbcf914ad 更新依赖 2023-05-30 14:43:44 +08:00
lyswhut d68a29a5a2 更新依赖 2023-05-25 14:30:20 +08:00
lyswhut 368eeed0d0 修复不同数据类型的列表id合并问题 2023-05-23 14:33:36 +08:00
lyswhut a522b78496 更新依赖 2023-05-23 11:56:54 +08:00
lyswhut dab81116e9 修复播放某些在线音频会没有声音的问题 2023-05-22 15:37:28 +08:00
lyswhut b7f355bd1a 更新翻译 2023-05-21 10:34:56 +08:00
lyswhut dbeaebaee3 更新版本号 2023-05-19 22:06:48 +08:00
lyswhut fe94a1eac9 更新依赖,code lint 2023-05-19 22:01:18 +08:00
lyswhut bd5107bbaf 修复下载时出现302错误的问题 2023-05-18 16:16:58 +08:00
lyswhut 8018d9b418 修复因插入数字类型的ID导致其意外在末尾追加 .0 导致列表数据异常的问题 2023-05-18 15:22:45 +08:00
lyswhut 0454d300ec 修改code判断方式 2023-05-16 17:35:42 +08:00
lyswhut 1e5c9ebc91 更新默认主题设置 2023-05-15 10:33:15 +08:00
lyswhut 5c7e9728e6 修复列表名翻译显示 2023-05-13 12:39:25 +08:00
lyswhut da4be33a04 添加上限设置 2023-05-12 20:13:35 +08:00
lyswhut 47e2a5a460 更新依赖 2023-05-08 18:07:26 +08:00
lyswhut 04337ef4b8 添加音效用户预设 2023-05-08 18:04:04 +08:00
lyswhut df58c8eb80 添加强行构建环境变量参数 2023-05-06 16:36:33 +08:00
lyswhut b0bf1bf4be 添加繁忙重试重试 2023-05-05 19:01:57 +08:00
lyswhut 1a3d84d4c4 更新版本号 2023-05-04 19:15:52 +08:00
lyswhut 735f629ca1 添加更多环境混响音效 2023-05-04 19:14:00 +08:00
lyswhut 9dc927b50b 修复名称显示 2023-05-04 19:13:48 +08:00
lyswhut 47a336eb64 修复 2023-05-03 18:16:53 +08:00
lyswhut 33b66aaea0 改名 2023-05-03 17:56:32 +08:00
lyswhut 7b9eefdf2d 新增音效设置 2023-05-03 17:45:43 +08:00
lyswhut ea5e8b56dd 发布v2.2.2 2023-05-01 17:44:11 +08:00
lyswhut e52fff4d0e 修复添加歌曲弹窗默认列表名字显示问题 2023-05-01 16:15:39 +08:00
lyswhut 8d108509c9 修改默认设置 2023-05-01 16:04:41 +08:00
lyswhut d3cc630501 更新版本号 2023-05-01 15:32:59 +08:00
lyswhut e74f297f9e 修复在低版本Linux amd64系统上无法启动的问题 2023-05-01 14:55:35 +08:00
lyswhut 8be9c75734 修改安装依赖的命令 2023-05-01 12:27:46 +08:00
lyswhut 851851a7e6 更新 2023-05-01 11:47:34 +08:00
lyswhut 1cbe46dc3b 发布v2.2.1 2023-05-01 11:13:24 +08:00
lyswhut 675a943d59 更新依赖 2023-04-29 15:19:57 +08:00
Folltosheandlyswhut 908815def3 更新音源API (#1352)
* 更新API

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2023-04-29 15:09:17 +08:00
lyswhut 200ca1eeda 移除多余翻译 2023-04-29 14:16:38 +08:00
Folltosheandlyswhut b81cc139b3 优化UI (#1353)
* 优化UI

* 更新

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2023-04-29 13:10:56 +08:00
lyswhut f8ae906326 支持wy热门评论翻页 2023-04-28 19:49:53 +08:00
lyswhut e6f55804f4 更新依赖 2023-04-27 14:04:01 +08:00
lyswhut 1432a19d7d 更新版本号 2023-04-24 13:13:10 +08:00
lyswhut 74fbfb130c 修复歌单加载 2023-04-24 13:05:11 +08:00
lyswhut 3cf45f602f 修复对存在错误时间标签的歌词的解析 2023-04-22 18:32:49 +08:00
lyswhut 7a9dcc71fd 更新导航栏样式 2023-04-20 23:35:57 +08:00
lyswhut fb5249a979 修复 2023-04-16 15:02:30 +08:00
lyswhut 3f694b75f8 修复列表加载问题 2023-04-16 14:52:22 +08:00
彭狸花喵 0e1ee66dd6 更新kg获取歌单gid接口 & 修复歌单介绍显示 (#1328) 2023-04-16 13:28:55 +08:00
lyswhut 920e002247 优化搜索框背景配色,使其适应高透明主题 2023-04-15 18:22:54 +08:00
Folltoshe ab0b352d36 修复tx热门评论回复图片显示异常 (#1326) 2023-04-15 17:59:52 +08:00
lyswhut aa4b08a156 更新依赖 2023-04-14 19:46:05 +08:00
lyswhut 66514908d2 更新版本号 2023-04-13 15:07:47 +08:00
lyswhut 60f0801a7c 修复评论图片显示问题 2023-04-13 15:07:33 +08:00
lyswhut 7149d0d43f 优化更新弹窗弹出时机 2023-04-13 14:34:18 +08:00
d970687ebe 音源API更新 & 其他改动 (#1295)
## API

### kg源

- 增加歌单歌曲flac24bit显示
- 修复酷狗码只能导入500首的问题
- 更新排行榜

### mg源

- 修复搜索不显示时长的问题
- 修复评论加载失败的问题
- 更新排行榜

### tx源

- 增加热门评论图片显示
- 更新排行榜

### wy源

- 更新排行榜

## 其他

- 添加自定义源zlib模块的支持

---------

Co-authored-by: 彭狸花喵 <94218819+helloplhm-qwq@users.noreply.github.com>
Co-authored-by: lyswhut <lyswhut@qq.com>
2023-04-13 14:32:59 +08:00
lyswhut d0425fc0d6 更新版本号 2023-04-09 15:36:52 +08:00
lyswhut bdd5b46708 更新依赖 2023-04-09 15:35:00 +08:00
lyswhut 6d949d7e4d 更新依赖 2023-03-31 20:12:14 +08:00
lyswhut 5d72d092fc 优化&修复同步机制 2023-03-30 17:46:37 +08:00
lyswhut 667fc8c8a6 修复缺失的翻译 2023-03-30 17:45:58 +08:00
lyswhut f7a2d9fd06 启用桌面歌词时,取消对歌词窗口的聚焦(#1273) 2023-03-29 12:49:30 +08:00
lyswhut 85946efd0b 修复启用全局快捷键时与Media Session注册冲突的问题 2023-03-27 19:57:31 +08:00
lyswhut f6a853bfe9 发布v2.2.0 2023-03-26 12:14:30 +08:00
lyswhut 4bfbdeb9a5 添加搜索重试 2023-03-26 12:00:29 +08:00
lyswhut 1e53508d67 修复聚合搜索跳转到最后一页后由于列表是空的导致翻页组件隐藏的问题(#1217) 2023-03-26 12:00:20 +08:00
lyswhut b8fca59cbb 更新依赖 2023-03-25 10:34:41 +08:00
lyswhut 9ada6fe4a7 更新默认设置 2023-03-25 10:04:07 +08:00
lyswhut b1593a2053 播放速率的粒度调整为0.01,范围0.6-2.0x 2023-03-25 10:04:07 +08:00
彭狸花喵 22a5125a11 修复一个拼写错误导致kg搜索AlbumID为undefined的问题 (#1255) 2023-03-25 10:03:32 +08:00
lyswhut dfd9a0019b 更新版号 2023-03-23 17:34:01 +08:00
lyswhut e1d81c199b 更新文案 2023-03-23 17:32:15 +08:00
lyswhut e8f574ee9c 修复潜在问题 2023-03-23 17:31:59 +08:00
lyswhut 472af1bca6 更新版本号 2023-03-21 17:56:04 +08:00
lyswhut 671346bcdd 修复连续选择时的初始选择歌曲位置被意外改变的问题 2023-03-20 16:33:18 +08:00
lyswhut 9132abb3fe 移除动画预热 2023-03-18 11:13:04 +08:00
lyswhut 1c681e9f82 修复标签显示 2023-03-17 14:50:07 +08:00
lyswhut 19286ef99d 更新依赖 2023-03-17 14:40:56 +08:00
lyswhut 37ad2f522a 更新日志 2023-03-15 17:47:16 +08:00
lyswhut 1e1aa65fc0 新增设置-其他-列表数据清理功能 2023-03-15 17:44:20 +08:00
lyswhut ec6e067938 新增设置-桌面歌词设置-重置窗口设置功能 2023-03-15 17:44:20 +08:00
Folltosheandlyswhut ea515fdd85 增加kg排行榜flac24bit显示 (#1238)
* 增加kg排行榜flac24bit显示
---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2023-03-15 17:42:40 +08:00
lyswhut c11c202248 更新版本号 2023-03-12 14:13:15 +08:00
lyswhut 1cfee31483 修复企鹅歌单列表歌单名字不显示的问题 2023-03-12 14:12:06 +08:00
lyswhut 00a5aa01c7 更新依赖 2023-03-12 11:31:25 +08:00
lyswhut fc61cf16ed 修复快照被意外删除的问题 2023-03-09 16:53:35 +08:00
lyswhut 0684e29098 更新字段获取 2023-03-07 16:44:09 +08:00
lyswhut 66d29293cd 更新说明 2023-03-05 20:04:18 +08:00
lyswhut e9fd657852 优化tx歌单打开 2023-03-05 18:29:57 +08:00
lyswhut bd7f51358c 更新版本号 2023-03-05 17:34:30 +08:00
lyswhutand彭狸花喵 371b8fad05 kg源数据更新 (#1232)
* kg源评论接口更新,热门评论完整支持
* kg搜索HiRes支持
---------

Co-authored-by: 彭狸花喵 <94218819+helloplhm-qwq@users.noreply.github.com>
2023-03-05 17:27:17 +08:00
lyswhut fe259747dc 音量、播放模式等设置弹出式按钮在鼠标移到按钮上时将自动弹出设置内容 2023-03-05 14:30:15 +08:00
lyswhut d4d94ddbee 修复tx源某些歌单因为歌曲信息缺失导致打开失败的问题 2023-03-04 21:39:08 +08:00
lyswhut f62d773246 更新依赖 2023-03-04 17:48:13 +08:00
lyswhut 20294233e6 快捷键调整音量时每次加减2%音量改为4% 2023-03-04 16:26:39 +08:00
lyswhut cb4ab411f9 新增全屏时自动关闭歌词设置 2023-03-04 16:10:34 +08:00
lyswhut 7f008d1f9c 优化自动重连 2023-03-04 13:22:08 +08:00
lyswhut fe0487c3f9 更新版本号 2023-03-02 17:44:48 +08:00
lyswhut a54e810af1 修复新设备同步时快照key没有被保存导致的下次同步仍然需要选择同步模式的问题 2023-03-01 22:46:24 +08:00
lyswhut 5cc1f20fac 更新依赖 2023-03-01 18:51:17 +08:00
lyswhut da804e1f69 更新名字 2023-03-01 18:47:18 +08:00
lyswhut 3399333360 完善同步 2023-03-01 18:44:51 +08:00
lyswhut e75ab3a7d7 更新版本号 2023-02-28 18:29:42 +08:00
lyswhut 3b70acc3ca 重构数据同步功能,新增客户端模式 2023-02-28 18:01:56 +08:00
lyswhut b42092884e 支持wy源flac hires歌曲类型的显示 2023-02-26 17:20:05 +08:00
lyswhut fea2db0e29 修复使用酷狗码无法打开某些类型的歌单的问题 2023-02-26 16:58:27 +08:00
lyswhut fe7bc924da 修复记住播放进度的情况下,使用Scheme URL打开应用播放的歌曲进度没有被重置的问题 2023-02-22 15:43:45 +08:00
lyswhut 60b357e6f9 修复同步连接的处理问题 2023-02-22 14:44:34 +08:00
lyswhut 5544a469c4 优化wy翻译、罗马音歌词解析 2023-02-19 17:12:19 +08:00
lyswhut 067ca91c4d 发布v2.1.2 2023-02-18 19:57:55 +08:00
lyswhut afd8d692fd 发布v2.1.1 2023-02-18 19:14:01 +08:00
lyswhut a9a8fa2857 发布v2.1.0 2023-02-18 13:50:31 +08:00
lyswhut 4b3a624ed9 更新electron到22.3.0 2023-02-17 23:10:16 +08:00
lyswhut 549ea19c26 移除不需要的代码 2023-02-15 09:12:55 +08:00
lyswhut c0515c9c4f 修复去重列表移除歌曲后列表没有刷新的问题 2023-02-10 21:06:09 +08:00
lyswhut e81d594d01 修复重启后歌词快进不生效的问题 2023-02-10 19:44:00 +08:00
lyswhut 3d469a4986 更新依赖 2023-02-10 18:07:19 +08:00
lyswhut b91bd7ede0 修复竖向桌面歌词无法加粗的问题 2023-02-10 13:09:18 +08:00
lyswhut 6048af99bc 添加下载失败信息状态 2023-02-10 08:58:24 +08:00
lyswhut ef9427122e 新增是否加粗扩展歌词设置 2023-02-09 17:52:58 +08:00
lyswhut 02402f5207 新增设置-下载设置-是否嵌入翻译歌词、罗马音歌词设置 2023-02-09 17:52:23 +08:00
lyswhut 80197ee18c 降级glibc版本编译 2023-02-08 20:23:39 +08:00
lyswhut 8989b05a69 更新版本号 2023-02-08 18:28:30 +08:00
lyswhut f809782559 新增播放速率调整功能 2023-02-08 18:27:35 +08:00
lyswhut 9522e975a0 修复API初始化失败的问题 2023-02-08 18:07:00 +08:00
lyswhut 8854fb69d8 更新native module 2023-02-08 18:06:31 +08:00
lyswhut 10bd75ea7b 更新Electron到v22.2.0 2023-02-08 00:27:31 +08:00
lyswhut f33ffb55a7 修复 2023-02-08 00:11:00 +08:00
lyswhut 3bff44da0c 防止桌面歌词窗口在屏幕分辨率变小时,窗口位置跟随分辨率变化的问题 2023-02-07 11:56:19 +08:00
lyswhut cf960e3695 添加启动时的数据库表及表结构完整性校验 2023-02-06 17:33:21 +08:00
lyswhut c8eac2690f 修复某些kg歌单链接无法打开的问题 2023-02-06 14:21:16 +08:00
lyswhut 8af458e61a 更新版本号 2023-02-05 20:41:33 +08:00
lyswhut 07899327f1 新增是否在更新版本的首次启动时显示更新日志弹窗设置 2023-02-05 20:40:30 +08:00
lyswhut dd5bffe071 为所有文本输入框添加右键快速粘贴的功能 2023-02-05 15:32:11 +08:00
lyswhut 83000b75ca 添加忽略二次提示 2023-02-05 13:38:25 +08:00
lyswhut e6c3815493 完善歌词获取流程 2023-02-05 13:28:43 +08:00
lyswhut 32626ef773 完善lib替换 2023-02-04 18:56:47 +08:00
lyswhut ab1d18debe 添加x源逐字歌词的支持 2023-02-04 18:38:06 +08:00
lyswhut 62330571a3 更新版号 2023-02-03 19:42:07 +08:00
lyswhut a8827c361c 优化 2023-02-03 19:41:34 +08:00
lyswhut 4635481c77 修改列表响应式更新机制,尝试修复偶现的删除歌曲列表未更新的问题 2023-02-03 19:41:22 +08:00
lyswhut c6f9e421c1 更新版本号 2023-02-03 01:23:10 +08:00
lyswhut 448e1cd0c3 修复较旧Linux arm64系统下无法启动软件的问题 2023-02-03 01:00:31 +08:00
lyswhut 5ee7c5a319 优化 2023-02-02 14:10:05 +08:00
lyswhut 89625188f1 更新版本号 2023-02-01 17:30:54 +08:00
lyswhut fdd19068a8 fix 2023-02-01 16:50:13 +08:00
lyswhut ef7033cac0 调整了桌面歌词在启用滚动到顶部时的距离 2023-02-01 16:23:41 +08:00
lyswhut 65840e45cd 优化 2023-02-01 14:12:52 +08:00
lyswhut 6075f413dc lint fix 2023-02-01 12:50:52 +08:00
lyswhut ba3b2f613a 修复更新日志弹窗显示 2023-02-01 12:50:38 +08:00
lyswhut 165832e703 完善支持wy歌词获取 2023-02-01 11:24:58 +08:00
lyswhut 7e27ec488b 添加wy源逐字歌词的支持 2023-01-31 23:40:25 +08:00
lyswhut 4e3356ccb2 更新依赖 2023-01-31 18:35:34 +08:00
lyswhut 6b69cdf12f 优化版本更新操作 2023-01-31 17:48:38 +08:00
lyswhut c24b29a42c 新增桌面歌词设置字体加粗设置&优化歌词字体显示 2023-01-30 17:48:47 +08:00
lyswhut c013d19530 修复播放下载列表的歌曲时,调整歌词偏移时间功能异常的问题 2023-01-25 20:14:49 +08:00
lyswhut 5a1e1864d9 修正错别字 2023-01-18 22:31:10 +08:00
lyswhut 7c56b2fce2 发布v2.0.5 2023-01-18 22:14:29 +08:00
lyswhut 7266352ca0 修复字母被截取的问题 2023-01-18 21:41:42 +08:00
lyswhut c1cff1e3c2 修复macOS下的软件系统菜单中的退出功能不会完全退出软件的问题(#1148) 2023-01-18 17:25:51 +08:00
lyswhut c003efe1e9 fix type 2023-01-18 15:57:06 +08:00
lyswhut 68d847aca5 修复随机播放模式下使用稍后播放功能播放我的列表的歌曲时,切换下一曲永远是当前歌曲的问题(#1147) 2023-01-18 15:56:05 +08:00
lyswhut 82bea640c4 重写桌面歌词窗口坐标的计算逻辑 2023-01-18 15:43:26 +08:00
lyswhut 4fa88dd7a0 修复声音输出设备更改时后的自动暂停播放设置无效的问题 2023-01-18 14:04:04 +08:00
lyswhut 900bdd3afe 发布v2.0.4 2023-01-15 15:15:00 +08:00
lyswhut 8dff457422 优化虚拟列表 2023-01-14 16:48:20 +08:00
lyswhut 850a429243 优化代码逻辑,修复潜在播放问题 2023-01-14 16:03:56 +08:00
lyswhut d3c8f16aed 更新依赖 2023-01-14 00:50:43 +08:00
lyswhut a43e127dd0 修复备份与恢复的列表导入列表信息设置逻辑问题与潜在问题 2023-01-14 00:50:29 +08:00
lyswhut 84e52f5525 修复Linux armv7l系统(如树莓派)下无法启动的问题 2023-01-14 00:06:04 +08:00
lyswhut 1dce2c1f7a 更新 2023-01-12 14:08:28 +08:00
578 changed files with 29053 additions and 15523 deletions
-117
View File
@@ -1,117 +0,0 @@
{
"root": true,
"extends": [
"standard"
],
"plugins": [
"html"
],
"parser": "@babel/eslint-parser",
"parserOptions": {
// "requireConfigFile": false
},
"rules": {
"no-new": "off",
"camelcase": "off",
"no-return-assign": "off",
"space-before-function-paren": ["error", "never"],
"no-var": "error",
"no-fallthrough": "off",
"prefer-promise-reject-errors": "off",
"eqeqeq": "off",
"no-multiple-empty-lines": [1, {"max": 2}],
"comma-dangle": [2, "always-multiline"],
"standard/no-callback-literal": "off",
"prefer-const": "off",
"no-labels": "off",
"node/no-callback-literal": "off"
},
"ignorePatterns": ["vendors", "*.min.js", "dist"],
"overrides": [
{
"files": [ "*.vue" ],
"rules": {
"no-new": "off",
"camelcase": "off",
"no-return-assign": "off",
"space-before-function-paren": ["error", "never"],
"no-var": "error",
"no-fallthrough": "off",
"prefer-promise-reject-errors": "off",
"eqeqeq": "off",
"no-multiple-empty-lines": [1, {"max": 2}],
"comma-dangle": [2, "always-multiline"],
"standard/no-callback-literal": "off",
"prefer-const": "off",
"no-labels": "off",
"node/no-callback-literal": "off",
"vue/multi-word-component-names": "off",
"@typescript-eslint/strict-boolean-expressions": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/space-before-function-paren": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/naming-convention": "off",
"vue/max-attributes-per-line": "off",
"vue/singleline-html-element-content-newline": "off",
"vue/use-v-on-exact": "off",
"@typescript-eslint/restrict-template-expressions": "off",
// "no-undef": "off"
},
"parser": "vue-eslint-parser",
"extends": [
"plugin:vue/base",
// "plugin:vue/strongly-recommended"
"plugin:vue/vue3-recommended",
"standard-with-typescript",
],
"parserOptions": {
"sourceType": "module",
"parser": {
// Script parser for `<script>`
"js": "@typescript-eslint/parser",
// Script parser for `<script lang="ts">`
"ts": "@typescript-eslint/parser"
},
"extraFileExtensions": [".vue"]
}
},
{
"files": [ "*.ts" ],
"rules": {
"no-new": "off",
"camelcase": "off",
"no-return-assign": "off",
"space-before-function-paren": ["error", "never"],
"no-var": "error",
"no-fallthrough": "off",
"prefer-promise-reject-errors": "off",
"eqeqeq": "off",
"no-multiple-empty-lines": [1, {"max": 2}],
"comma-dangle": [2, "always-multiline"],
"standard/no-callback-literal": "off",
"prefer-const": "off",
"no-labels": "off",
"node/no-callback-literal": "off",
"@typescript-eslint/strict-boolean-expressions": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/space-before-function-paren": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/restrict-template-expressions": [1, {
"allowBoolean": true
}],
"@typescript-eslint/naming-convention": "off",
"@typescript-eslint/return-await": "off",
"multiline-ternary": "off",
"@typescript-eslint/comma-dangle": "off",
},
"parser": "@typescript-eslint/parser",
"extends": [
"standard-with-typescript"
],
"parserOptions": {
"project": "./src/**/tsconfig.json"
}
}
]
}
+98
View File
@@ -0,0 +1,98 @@
const baseRule = {
'no-new': 'off',
camelcase: 'off',
'no-return-assign': 'off',
'space-before-function-paren': ['error', 'never'],
'no-var': 'error',
'no-fallthrough': 'off',
eqeqeq: 'off',
'require-atomic-updates': ['error', { allowProperties: true }],
'no-multiple-empty-lines': [1, { max: 2 }],
'comma-dangle': [2, 'always-multiline'],
'standard/no-callback-literal': 'off',
'prefer-const': 'off',
'no-labels': 'off',
'node/no-callback-literal': 'off',
'multiline-ternary': 'off',
}
const typescriptRule = {
...baseRule,
'@typescript-eslint/strict-boolean-expressions': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/space-before-function-paren': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/restrict-template-expressions': [1, {
allowBoolean: true,
allowAny: true,
}],
'@typescript-eslint/restrict-plus-operands': [1, {
allowBoolean: true,
allowAny: true,
}],
'@typescript-eslint/no-misused-promises': [
'error',
{
checksVoidReturn: {
arguments: false,
attributes: false,
},
},
],
'@typescript-eslint/naming-convention': 'off',
'@typescript-eslint/return-await': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/comma-dangle': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
}
const vueRule = {
...typescriptRule,
'vue/multi-word-component-names': 'off',
'vue/max-attributes-per-line': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/use-v-on-exact': 'off',
}
exports.base = {
extends: ['standard'],
rules: baseRule,
parser: '@babel/eslint-parser',
}
exports.html = {
files: ['*.html'],
plugins: ['html'],
}
exports.typescript = {
files: ['*.ts'],
rules: typescriptRule,
parser: '@typescript-eslint/parser',
extends: [
'standard-with-typescript',
],
}
exports.vue = {
files: ['*.vue'],
rules: vueRule,
parser: 'vue-eslint-parser',
extends: [
// 'plugin:vue/vue3-essential',
'plugin:vue/base',
'plugin:vue/vue3-recommended',
'plugin:vue-pug/vue3-recommended',
// "plugin:vue/strongly-recommended"
'standard-with-typescript',
],
parserOptions: {
sourceType: 'module',
parser: {
// Script parser for `<script>`
js: '@typescript-eslint/parser',
// Script parser for `<script lang="ts">`
ts: '@typescript-eslint/parser',
},
extraFileExtensions: ['.vue'],
},
}
+20
View File
@@ -0,0 +1,20 @@
const { base, typescript } = require('./.eslintrc.base.cjs')
module.exports = {
root: true,
...base,
overrides: [
{
...typescript,
parserOptions: {
project: './tsconfig.json',
},
},
],
ignorePatterns: [
'node_modules',
'*.min.js',
'dist',
'build',
],
}
+26
View File
@@ -0,0 +1,26 @@
name: Setup
description: Setup Node Env
runs:
using: composite
steps:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Cache node modules
id: cache-npm
uses: actions/cache@v4
with:
path: |
${{ env.NPM_CACHE }}
${{ env.ELECTRON_CACHE }}
${{ env.ELECTRON_BUILDERCACHE }}
key: ${{ runner.os }}-node-modules-cache-${{ hashFiles('package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-modules-cache-
- name: Install dependencies
run: npm ci
shell: bash
+115 -116
View File
@@ -6,92 +6,117 @@ on:
- beta
jobs:
# CheckCode:
# name: Lint Code
# runs-on: ubuntu-latest
# steps:
# - name: Check out git repository
# uses: actions/checkout@v4
# - name: Install Node.js
# uses: actions/setup-node@v4
# with:
# node-version: '18'
# - name: Cache file
# uses: actions/cache@v4
# with:
# path: |
# node_modules
# $HOME/.cache/electron
# $HOME/.cache/electron-builder
# $HOME/.npm/_prebuilds
# key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
# restore-keys: |
# ${{ runner.os }}-build-
# - name: Install Dependencies
# run: |
# npm ci
# - name: Lint src code
# run: npm run lint
Windows:
name: Windows
runs-on: windows-latest
env:
NPM_CACHE: '%APPDATA%\npm-cache'
ELECTRON_CACHE: '%LOCALAPPDATA%\electron\Cache'
ELECTRON_BUILDERCACHE: '%LOCALAPPDATA%\electron-builder\Cache'
# needs: CheckCode
steps:
- name: Check out git repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
- name: Cache file
uses: actions/cache@v3
with:
path: |
node_modules
%APPDATA%\npm-cache
%LOCALAPPDATA%\electron\Cache
%LOCALAPPDATA%\electron-builder\Cache
key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-
- name: Install Dependencies
run: |
npm install
- name: Setup Node Env
uses: ./.github/actions/setup
- name: Build src code
run: npm run build:src
run: npm run build
- name: Build Package Setup x64
run: npm run pack:win:setup:x64
- name: Upload Artifact Setup x64
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: lx-music-desktop-x64-Setup
path: build/* x64 Setup.exe
path: build/*-x64-Setup.exe
- name: Build Package 7z x64
run: npm run pack:win:7z:x64
- name: Upload Artifact 7z x64
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: lx-music-desktop-win_x64-green
path: build/*win_x64 green.7z
- name: Build Package Setup x86
run: npm run pack:win:setup:x86
- name: Upload Artifact Setup x86
uses: actions/upload-artifact@v3
with:
name: lx-music-desktop-x86-Setup
path: build/* x86 Setup.exe
- name: Build Package 7z x86
run: npm run pack:win:7z:x86
- name: Upload Artifact 7z x86
uses: actions/upload-artifact@v3
with:
name: lx-music-desktop-win_x86-green
path: build/*win_x86 green.7z
path: build/*win_x64-green.7z
- name: Build Package Setup arm64
run: npm run pack:win:setup:arm64
- name: Upload Artifact Setup arm64
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: lx-music-desktop-arm64-Setup
path: build/* arm64 Setup.exe
path: build/*-arm64-Setup.exe
- name: Build Package 7z arm64
run: npm run pack:win:7z:arm64
- name: Upload Artifact 7z arm64
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: lx-music-desktop-win_arm64-green
path: build/*win_arm64 green.7z
path: build/*win_arm64-green.7z
- name: Build Package Setup x86_64
run: npm run pack:win:setup:x86_64
- name: Upload Artifact Setup x86_64
uses: actions/upload-artifact@v3
- name: Install old electron
run: npm install electron@22
- name: Install python setuptools
run: pip.exe install setuptools
- name: Build Package win7 Setup x64
run: npm run pack:win7:setup:x64
- name: Upload Artifact win7 Setup x64
uses: actions/upload-artifact@v4
with:
name: lx-music-desktop-x86_64-Setup
path: build/*x86_64 Setup.exe
name: lx-music-desktop-win7_x64-Setup
path: build/*win7_x64-Setup.exe
- name: Build Package win7 7z x64
run: npm run pack:win7:7z:x64
- name: Upload Artifact win7 7z x64
uses: actions/upload-artifact@v4
with:
name: lx-music-desktop-win7_x64-green
path: build/*win7_x64-green.7z
- name: Build Package win7 7z x86
run: npm run pack:win7:7z:x86
- name: Upload Artifact win7 7z x86
uses: actions/upload-artifact@v4
with:
name: lx-music-desktop-win7_x86-green
path: build/*win7_x86-green.7z
- name: Generate file MD5
run: |
@@ -101,51 +126,38 @@ jobs:
Mac:
name: Mac
runs-on: macos-latest
# needs: CheckCode
env:
NPM_CACHE: $HOME/.npm
ELECTRON_CACHE: $HOME/.cache/electron
ELECTRON_BUILDERCACHE: $HOME/.cache/electron-builder
steps:
- name: Check out git repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
- name: Install python setuptools
run: python3 -m pip install setuptools
- name: Cache file
uses: actions/cache@v3
with:
path: |
node_modules
$HOME/.cache/electron
$HOME/.cache/electron-builder
$HOME/.npm/_prebuilds
key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-
- name: Install Dependencies
run: |
npm install
- name: Setup Node Env
uses: ./.github/actions/setup
- name: Build src code
run: npm run build:src
run: npm run build
- name: Build Package dmg
run: |
npm run pack:mac:dmg
npm run pack:mac:dmg:arm64
env:
ELECTRON_CACHE: $HOME/.cache/electron
ELECTRON_BUILDERCACHE: $HOME/.cache/electron-builder
- name: Upload Artifact dmg
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: lx-music-desktop-mac-dmg
path: |
build/*.dmg
!build/*-arm64.dmg
- name: Upload Artifact dmg
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: lx-music-desktop-mac-dmg-arm64
path: build/*-arm64.dmg
@@ -158,84 +170,71 @@ jobs:
Linux:
name: Linux
runs-on: ubuntu-latest
env:
NPM_CACHE: $HOME/.npm
ELECTRON_CACHE: $HOME/.cache/electron
ELECTRON_BUILDERCACHE: $HOME/.cache/electron-builder
# needs: CheckCode
steps:
- name: Install package
run: sudo apt-get update && sudo apt-get install -y rpm libarchive-tools
- name: Check out git repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
- name: Cache file
uses: actions/cache@v3
with:
path: |
node_modules
$HOME/.cache/electron
$HOME/.cache/electron-builder
$HOME/.npm/_prebuilds
key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-
- name: Install Dependencies
run: |
npm install
- name: Setup Node Env
uses: ./.github/actions/setup
- name: Build src code
run: npm run build:src
run: npm run build
- name: Build Package deb x64
run: npm run pack:linux:deb:x64
- name: Upload Artifact deb x64
uses: actions/upload-artifact@v3
- name: Build Package deb amd64
run: npm run pack:linux:deb:amd64
- name: Upload Artifact deb amd64
uses: actions/upload-artifact@v4
with:
name: lx-music-desktop-deb-x64
path: build/* x64.deb
name: lx-music-desktop-deb-amd64
path: build/*_amd64.deb
- name: Build Package deb arm64
run: npm run pack:linux:deb:arm64
- name: Upload Artifact deb arm64
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: lx-music-desktop-deb-arm64
path: build/* arm64.deb
path: build/*_arm64.deb
- name: Build Package deb armv7l
run: npm run pack:linux:deb:armv7l
- name: Upload Artifact deb armv7l
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: lx-music-desktop-deb-armv7l
path: build/* armv7l.deb
path: build/*_armv7l.deb
- name: Build Package x64 appImage
run: npm run pack:linux:appImage
- name: Upload Artifact x64 appImage
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: lx-music-desktop-x64-appImage
path: build/* x64.AppImage
path: build/*_x64.AppImage
- name: Build Package x64 rpm
run: npm run pack:linux:rpm
- name: Upload Artifact x64 rpm
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: lx-music-desktop-x64-rpm
path: build/* x64.rpm
path: build/*.x64.rpm
- name: Build Package x64 pacman
run: npm run pack:linux:pacman
- name: Upload Artifact x64 pacman
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: lx-music-desktop-x64-pacman
path: build/* x64.pacman
path: build/*_x64.pacman
- name: Generate file MD5
run: |
+28
View File
@@ -0,0 +1,28 @@
name: Run build test
on:
pull_request:
branches:
- dev
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Check out git repository
uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install Dependencies
run: npm ci
- name: Eslint check
run: npm run lint
- name: Test Build
run: npm run build
@@ -0,0 +1,16 @@
name: Publish NPM Version Info
on:
release:
types: [published]
jobs:
dispatch:
runs-on: ubuntu-latest
steps:
- name: Repository Dispatch
uses: peter-evans/repository-dispatch@v2
with:
token: ${{ secrets.PAT }}
repository: lyswhut/lx-music-desktop-version-info
event-type: npm-release
+102 -74
View File
@@ -6,46 +6,99 @@ on:
- master
jobs:
# CheckCode:
# name: Lint Code
# runs-on: ubuntu-latest
# steps:
# - name: Check out git repository
# uses: actions/checkout@v4
# - name: Install Node.js
# uses: actions/setup-node@v4
# with:
# node-version: '18'
# - name: Cache file
# uses: actions/cache@v4
# with:
# path: |
# node_modules
# $HOME/.cache/electron
# $HOME/.cache/electron-builder
# $HOME/.npm/_prebuilds
# key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
# restore-keys: |
# ${{ runner.os }}-build-
# - name: Install Dependencies
# run: |
# npm ci
# - name: Lint src code
# run: npm run lint
Windows:
name: Windows
runs-on: windows-latest
env:
NPM_CACHE: '%APPDATA%\npm-cache'
ELECTRON_CACHE: '%LOCALAPPDATA%\electron\Cache'
ELECTRON_BUILDERCACHE: '%LOCALAPPDATA%\electron-builder\Cache'
# needs: CheckCode
steps:
- name: Check out git repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
- name: Cache file
uses: actions/cache@v3
with:
path: |
node_modules
%APPDATA%\npm-cache
%LOCALAPPDATA%\electron\Cache
%LOCALAPPDATA%\electron-builder\Cache
key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-
- name: Install Dependencies
run: |
npm install
- name: Setup Node Env
uses: ./.github/actions/setup
- name: Build src code
run: npm run build:src
run: npm run build
- name: Release package
run: |
npm run publish:win:setup:x64:always
npm run publish:win:7z:x64
npm run publish:win:setup:x86
npm run publish:win:7z:x86
npm run publish:win:setup:arm64
npm run publish:win:7z:arm64
npm run publish:win:setup:x86_64
npm run publish:win:setup:arm64
npm run publish:win:setup:x64
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BT_TOKEN: ${{ secrets.BT_TOKEN }}
- name: Generate file MD5
run: |
cd build
Get-FileHash *.exe,*.7z -Algorithm MD5 | Format-List
Windows_7:
name: Windows
runs-on: windows-latest
env:
NPM_CACHE: '%APPDATA%\npm-cache'
ELECTRON_CACHE: '%LOCALAPPDATA%\electron\Cache'
ELECTRON_BUILDERCACHE: '%LOCALAPPDATA%\electron-builder\Cache'
# needs: CheckCode
steps:
- name: Check out git repository
uses: actions/checkout@v4
- name: Setup Node Env
uses: ./.github/actions/setup
- name: Build src code
run: npm run build
- name: Prepare win7 electron env
run: |
npm install electron@22
pip.exe install setuptools
- name: Release win7 package
run: |
npm run publish:win7:setup:x64
npm run publish:win7:7z:x64
npm run publish:win7:7z:x86
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BT_TOKEN: ${{ secrets.BT_TOKEN }}
@@ -58,41 +111,29 @@ jobs:
Mac:
name: Mac
runs-on: macos-latest
env:
NPM_CACHE: $HOME/.npm
ELECTRON_CACHE: $HOME/.cache/electron
ELECTRON_BUILDERCACHE: $HOME/.cache/electron-builder
# needs: CheckCode
steps:
- name: Check out git repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
- name: Install python3 setuptools
run: python3 -m pip install setuptools
- name: Cache file
uses: actions/cache@v3
with:
path: |
node_modules
$HOME/.cache/electron
$HOME/.cache/electron-builder
$HOME/.npm/_prebuilds
key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-
- name: Install Dependencies
run: |
npm install
- name: Setup Node Env
uses: ./.github/actions/setup
- name: Build src code
run: npm run build:src
run: npm run build
- name: Release package
run: |
npm run publish:mac:dmg:always
npm run publish:mac:dmg
npm run publish:mac:dmg:arm64
env:
ELECTRON_CACHE: $HOME/.cache/electron
ELECTRON_BUILDERCACHE: $HOME/.cache/electron-builder
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BT_TOKEN: ${{ secrets.BT_TOKEN }}
@@ -104,40 +145,27 @@ jobs:
Linux:
name: Linux
runs-on: ubuntu-latest
env:
NPM_CACHE: $HOME/.npm
ELECTRON_CACHE: $HOME/.cache/electron
ELECTRON_BUILDERCACHE: $HOME/.cache/electron-builder
# needs: CheckCode
steps:
- name: Install package
run: sudo apt-get update && sudo apt-get install -y rpm libarchive-tools
- name: Check out git repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
- name: Cache file
uses: actions/cache@v3
with:
path: |
node_modules
$HOME/.cache/electron
$HOME/.cache/electron-builder
$HOME/.npm/_prebuilds
key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-
- name: Install Dependencies
run: |
npm install
- name: Setup Node Env
uses: ./.github/actions/setup
- name: Build src code
run: npm run build:src
run: npm run build
- name: Release package
run: |
npm run publish:linux:deb:x64:always
npm run publish:linux:deb:amd64
npm run publish:linux:deb:arm64
npm run publish:linux:deb:armv7l
npm run publish:linux:appImage
+13 -1
View File
@@ -4,14 +4,26 @@ module.exports = {
'electron',
'chalk',
'del',
'comlink',
'vue',
'image-size',
'message2call',
'@types/ws',
// 'eslint-config-standard-with-typescript',
// 'typescript', // https://github.com/microsoft/TypeScript/pull/54567
],
// target: 'newest',
// filter: [
// /^vue/,
// 'electron-builder',
// 'electron-updater',
// ],
// target: 'patch',
// filter: [
// 'vue',
// ],
// target: 'minor',
// filter: [
// 'electron',
+2 -1
View File
@@ -12,5 +12,6 @@
],
"i18n-ally.sortKeys": true,
"javascript.preferences.importModuleSpecifier": "non-relative",
"typescript.tsdk": "node_modules/typescript/lib"
"typescript.tsdk": "node_modules/typescript/lib",
"vue.codeActions.enabled": false
}
+316
View File
@@ -6,6 +6,322 @@ Project versioning adheres to [Semantic Versioning](http://semver.org/).
Commit convention is based on [Conventional Commits](http://conventionalcommits.org).
Change log format is based on [Keep a Changelog](http://keepachangelog.com/).
## [2.6.0](https://github.com/lyswhut/lx-music-desktop/compare/v2.5.0...v2.6.0) - 2024-02-01
提交祝大家新年快乐!
更新前需要注意:
由于自定义源的调用方式变更,可能会导致某些第三方源停止工作,如果出现这种情况,你需要将LX回退到 v2.5.0
### 新增
- 若自定义源初始化失败,将会出现弹窗提示初始化失败的详情
- 添加win7_x64架构的安装版安装包构建
- 新增播放歌曲时阻止电脑休眠,默认启用,可到设置-播放设置关闭(#1563)
### 优化
- 更新zh-tw翻译
- 自定义源列显示源版本号、作者名字
- 优化列表全选机制,修复列表未获得焦点时仍然可以全选的问题
- 优化搜索框交互逻辑,防止鼠标操作时意外搜索候选列表的内容
- 添加对wy源某些歌曲有问题的歌词进行修复
- 改进本地音乐在线信息的匹配机制
- 优化任务下载状态显示,现在下载时若数据传输完成但数据写入未完成时会显示相应的状态
- 添加对下载歌曲时封面图片大小的控制处理(#1609)
- 添加创建同名列表时的二次确认(#1621)
### 修复
- 修复备份文件无法导入json格式的问题
- Windows、MacOS平台下的字体列表取消使用原生方式获取以修复某些字体应用后无效的问题(#1596)
- 修复亮暗主题自动切换功能无效的问题(#1697)
- 修复 MacOS 平台在 Finder 打开文件或目录时应用卡死的问题(#1684)
- 修复下载模块在数据写入速度较慢的情况下出现任务及文件异常的问题
- 修复临时列表变更会意外触发同步的问题
- 修复最小化后再隐藏窗口时,托盘菜单的显示主界面功能异常的问题
### 变更
- 播放歌曲时默认会阻止系统进入休眠状态,若你不行软件阻止系统休眠,可以到设置-播放设置取消勾选“播放歌曲时阻止电脑休眠”设置
### 其他
- 移除所有内置源,由于收到腾讯投诉要求停止提供软件内置的连接到他们平台的在线播放及下载服务,所以从即日(2023年10月18日)起LX本身不再提供上述服务
- 更新 electron 到 v25.9.8
- 更新许可协议的排版,使其看起来更加清晰明了,更新数据来源原理说明
### 自定义源的不兼容变更与新增内容(源开发者需要看)
自定义源的调用方式已改变:
- 为了与移动端的调用方式统一,不再推荐使用 `window.lx` 对象(移动端无`window`对象),改用 `globalThis.lx`
- `inited` 事件不再需要传递 `status` 属性,脚本运行过程中,在成功调用 `inited` 事件之前的任何首次未捕获的错误都将视为初始化失败,所以现在若想人为让脚本初始化失败,直接抛出一个错误即可
- 新增 `globalThis.lx.env` 属性,桌面端环境固定为 `desktop`,移动端环境固定为 `mobile`
- 新增 `globalThis.lx.currentScriptInfo` 对象,可以从这里获取解析后的脚本头部注释信息及脚本原始内容,具体可用属性看文档说明
- `globalThis.lx.version` 属性更新到 `2.0.0`
- 自定义源不再使用`script`标签的形式执行,若要获取脚本原始代码字符串需从 `globalThis.lx.currentScriptInfo.rawScript` 属性获取
- 自定义源新增支持`local`源的`musicUrl`、`pic`、`lyric`的获取操作详情看自定义源文档说明
## [2.5.0](https://github.com/lyswhut/lx-music-desktop/compare/v2.4.1...v2.5.0) - 2023-09-28
落雪提前祝大家中秋快乐~🥮😘!
### 不兼容性变更
- 由于微软及Electron即将结束对 Windows 7、Windows 8 的支持,所以从这个版本起,LX的默认 Windows 版也不再支持这些版本的系统,但考虑到仍然有许多人使用 Windows 7,我们特别构建了能在 Windows 7 上使用的免安装版(文件名带win7),需要注意的是这个版本将缺乏安全更新,若非必要情况,不要使用该版本
- 由于微软在 Windows 10 2004版本已删除对32位的OEM支持,所以在这个版本起,LX的默认 Windows 版已不再提供32位的支持
- 更改构建的文件名格式,主要修改linux下deb、rpm文件命名格式
### 新增
- 新增Scheme URL对播放器的控制操作,新增的操作包含 播放、暂停、下一首、上一首等,详情看Scheme URL文档
### 优化
- 通过歌曲菜单添加不喜欢歌曲时需要二次确认防止手抖
### 修复
- 修复音频输出设备设置在重启软件后被重置的问题(#1568)
- 修复更换语言设置后源名称未更新的问题
- 修复点击搜索、排行榜等在线列表歌曲右键菜单歌曲详情页会意外将该歌曲添加不喜欢的问题
### 其他
- 更新 electron 到 v25.8.3
## [2.4.1](https://github.com/lyswhut/lx-music-desktop/compare/v2.4.0...v2.4.1) - 2023-09-09
目前本项目的原始发布地址只有 **GitHub** 及 **蓝奏网盘** ,其他渠道均为第三方转载发布,可信度请自行鉴别。
本项目无微信公众号之类的官方账号,谨防被骗。
### 修复
- 修复 v2.4.0 的默认数据库版本号不对导致首次安装该版本的用户无法再次启动软件的问题
## [2.4.0](https://github.com/lyswhut/lx-music-desktop/compare/v2.3.0...v2.4.0) - 2023-09-09
目前本项目的原始发布地址只有 **GitHub** 及 **蓝奏网盘** ,其他渠道均为第三方转载发布,可信度请自行鉴别。
本项目无微信公众号之类的官方账号,谨防被骗。
### 不兼容性变更
该版本修改了同步协议逻辑,同步功能至少需要PC端v2.4.0或移动端v1.1.0版本或同步服务v2.0.0才能连接使用。
### 新增
- 新增我的列表名右键菜单-排序歌曲-随机乱序功能,使用它可以对选中列表内歌曲进行随机重排(#1440)
- 新增数据同步服务端模式已认证设备列表管理,该功能位置:设置-数据同步-服务端模式-已认证设备列表
- 新增“不喜欢歌曲”功能,可以在我的列表或者在线列表内歌曲的右击菜单使用,还可以去“设置-其他”手动编辑不喜欢规则,注:“上一曲”、“下一曲”功能将跳过符合“不喜欢歌曲”规则的歌曲,但你仍可以手动播放这些歌曲
- 新增同步功能对“不喜欢歌曲”列表的同步
- 新增软件内快捷键“不喜欢该歌曲”设置,全局快捷键“收藏歌曲”、“取消收藏”、“不喜欢该歌曲”设置
- 新增设置-播放设置-点击相同列表内的歌曲切歌时是否清空已播放列表(随机模式下列表内所有歌曲会重新参与随机)选项,默认关闭
### 优化
- 优化音效设置-环境音效启用、禁用时的操作效果显示,修复禁用环境音效时仍然可以调整增益、新增预设的问题
- 过滤翻译歌词或罗马音歌词中只有“//”的行(#1499)
- 点击打开歌单弹窗背景将不再自动关闭弹窗,防止选择输入框里的内容时意外关闭弹窗
- 优化数据传输逻辑,列表同步指令使用队列机制,保证列表同步操作的顺序
- 优化桌面歌词在开启 缩放当前播放的歌词 并关闭 延迟歌词滚动 时的歌词滚动位置计算问题,现在歌词滚动应该可以正确滚动到目标位置了
- 优化歌词在短时间内快速播放时的滚动效果,现在遇到这种情况时滚动将更平滑
### 修复
- 修复字体设置某些字体无法应用的问题
- 修复搜索提示功能失效的问题(#1452, @Folltoshe)
- 修复我的列表名右键菜单-排序歌曲按专辑名排序无效的问题(#1440)
- 修复若路径存在 # 字符时,软件无法启动的问题
- 修复搜索框在某些情况下输入内容后搜索时会自动清空的问题(#1472)
- 修复某些tx源歌词因数据异常解析失败的问题
- 修复windows平台下隐藏窗口后再显示时任务栏按钮丢失的问题
- 修复首句歌词被提前播放的问题
- 修复潜在导致列表数据不同步的问题
- 修复kg无评论时的加载处理问题
### 变更
- 播放模式应该只适用于列表内的歌曲,所以单曲循环模式不应对“稍后播放”的歌曲有效,该行为现在与移动端一致
- 随机模式下,通过点击与播放列表相同的列表切歌时,将不再清空已播放列表,即已播放的歌曲不再重新参与随机,若想恢复之前的行为可以去设置-播放设置启用清空已播放列表选项
### 其他
- 更新 electron 到 v22.3.23
- 重构同步服务端功能部分代码,使其更易扩展新功能
## [2.3.0](https://github.com/lyswhut/lx-music-desktop/compare/v2.2.2...v2.3.0) - 2023-06-29
### 新增
- 新增音效设置(实验性功能),支持10段均衡器设置、内置的一些环境混响音效、音调升降调节、3D立体环绕音效(由于升降调需要实时处理音频数据,这会导致额外的CPU占用,已知问题:如果CPU资源不够时将处理导致任务堆积而出现声音异常,这时需要暂停播放一段时间等堆积的任务处理完毕再播放)
- 播放速率设置面板新增是否音调补偿设置,在调整播放速率后,可以选择是否启用音调补偿,默认启用
### 优化
- Windows、MacOS平台下的字体列表改用原生方式获取,现在Windows平台下能显示当前已安装的更多类型字体了(注:MacOS平台未测,可用性未知)
- 移除桌面歌词窗口透明边距,在Linux下的桌面歌词可以完全拖到贴合屏幕边缘了
- 过滤嵌入、下载的翻译、罗马音歌词时间标签,与主歌词时间不匹配的歌词将被丢弃,防止出现原歌词与翻译歌词顺序错乱的问题(#1358)
### 修复
- 修复列表名翻译显示
- 修复因插入数字类型的ID导致其意外在末尾追加 .0 导致列表数据异常的问题,同时也可能导致同步数据丢失的问题(要完全修复这个问题还需要同时将移动端、同步服务更新到最新版本)
- 修复下载时出现302错误的问题
- 修复播放某些在线音频会没有声音的问题
- 修复改变播放速率时会导致歌词报错的问题
- 修复tx热门评论昵称被错误切割的问题 (#1397, By: @helloplhm-qwq, @Folltoshe)
- 修复wy源热搜词失效的问题(#1401, @Folltoshe)
- 修复Deepin 20下启用桌面歌词时可能会导致桌面卡死的问题(#1288)
- 修复添加单首歌曲弹窗列表创建按钮无法取消的问题
- 修复mg歌单搜索歌单播放数量显示问题
- 修复tx翻译歌词解析丢失的问题(更新版本后需手动清理一次歌词缓存)
### 其他
- 更新 electron 到 v22.3.15
## [2.2.2](https://github.com/lyswhut/lx-music-desktop/compare/v2.2.1...v2.2.2) - 2023-05-01
### 修复
- 修复在低版本Linux amd64系统上无法启动的问题(glibc版本要求过高导致的,采用内置预编译二进制文件的方式解决)
- 修复添加歌曲弹窗默认列表名字显示问题
## [2.2.1](https://github.com/lyswhut/lx-music-desktop/compare/v2.2.0...v2.2.1) - 2023-05-01
### 优化
- 优化对系统Media Session的支持,现在切歌不会再会导致信息丢失的问题了
- 启用桌面歌词时,取消对歌词窗口的聚焦
- 增加kg歌单歌曲flac24bit显示(@helloplhm-qwq)
- 增加tx源热门评论图片显示(@Folltoshe)
- 优化更新弹窗弹出时机
- 优化搜索框背景配色,使其适应高透明主题
- 支持wy热门评论翻页
### 修复
- 修复启用全局快捷键时与Media Session注册冲突的问题,启用全局快捷键时,不再注册媒体控制快捷键
- 修复mg搜索不显示时长的问题(@Folltoshe)
- 修复mg评论加载失败的问题(@Folltoshe)
- 修复对存在错误时间标签的歌词的解析
### 其他
- 自定义源API utils对象新增`zlib.inflate`与`zlib.deflate`方法,API版本更新到 v1.3.0
- 更新kg、tx、wy等平台排行榜列表
- 更新 electron 到 v22.3.7
## [2.2.0](https://github.com/lyswhut/lx-music-desktop/compare/v2.1.2...v2.2.0) - 2023-03-26
从v2.2.0起,我们发布了一个独立版的[数据同步服务](https://github.com/lyswhut/lx-music-sync-server#readme),如果你有服务器,可以将其部署到服务器上作为私人多端同步服务使用,详情看该项目说明
### 不兼容性变更说明
- 同步功能,从这个版本起,数据同步功能至少需要移动端v1.0.0的版本才能连接,连接的地址格式也略有改变,详情看[文档说明](https://lyswhut.github.io/lx-music-doc/desktop/faq/sync)
### 新增
- 重构数据同步功能,新增客户端模式
- 新增全屏时自动关闭歌词设置,默认开启,可以去设置-桌面歌词设置更改
- 新增设置-桌面歌词设置-重置窗口设置功能,点击时会重置桌面歌词窗口大小及位置
- 新增设置-其他-列表数据清理功能,点击时会清空已创建的所有列表及所有收藏的歌曲
### 优化
- 支持wy源flac hires歌曲类型的显示
- 快捷键调整音量时每次加减2%音量改为4%(#1220)
- 音量、播放模式等设置弹出式按钮在鼠标移到按钮上时将自动弹出设置内容,保留点击切换显示/隐藏
- 支持kg源搜索列表、排行榜flac hires歌曲类型的显示(#1231, #1238 By @helloplhm-qwq, @Folltoshe)
- 播放速率的粒度调整为0.01,范围0.6-2.0x
### 修复
- 修复同步连接的处理问题
- 修复记住播放进度的情况下,使用Scheme URL打开应用播放的歌曲进度没有被重置的问题
- 修复使用酷狗码无法打开某些类型的歌单的问题
- 修复tx源某些歌单因为歌曲信息缺失导致打开失败的问题
- 修复连续选择时的初始选择歌曲位置被意外改变的问题
### 其他
- 更新 Electron 到v22.3.4
## [2.1.2](https://github.com/lyswhut/lx-music-desktop/compare/v2.1.1...v2.1.2) - 2023-02-18
### 修复
- 修复处于最新版本时更新弹窗日志内容显示异常的问题
- 修复更新到最新版本后的首次启动时的更新日志未显示的问题
## [2.1.1](https://github.com/lyswhut/lx-music-desktop/compare/v2.1.0...v2.1.1) - 2023-02-18
### 修复
- 修复检查更新日志地址不正确的问题
## [2.1.0](https://github.com/lyswhut/lx-music-desktop/compare/v2.0.5...v2.1.0) - 2023-02-18
由于软件内功能在设计时只考虑简单便捷性,是否对新手友好并不是我们考虑的重点,功能的新增、变更会在更新日志中注明,不会在软件内做指引提示,
因此为了更愉快地使用本软件,我们建议在使用新版本时阅读一遍更新日志以了解软件的变更情况,同时若遇到问题可以去阅读常见问题找解决方案
### 新增
- 新增桌面歌词设置字体加粗设置,可以到设置-桌面歌词设置-加粗字体修改
- 新增是否自动下载更新设置,默认开启,可以去设置-软件更新更改
- 新增当前版本更新日志显示弹窗(建议大家阅读更新日志以了解当前版本的变化),在更新版本后将自动弹出
- 新增是否在更新版本的首次启动时显示更新日志弹窗设置,默认开启,可以去设置-软件更新更改
- 新增播放速率调整功能,可以去播放详情页的控制按钮调整,范围限制为x0.5至x2之间(#13)
- 添加wy、tx源(感谢某位不愿透露姓名的大佬提供的C++算法源码,但由于作者不希望公开,所以将会以预构建二进制文件的形式加入代码仓库中)逐字歌词的支持
- 新增设置-下载设置-是否嵌入翻译歌词、罗马音歌词设置,默认关闭
- 添加启动时的数据库表及表结构完整性校验,若未通过校验,则会显示弹窗提示后将该数据库重命名添加`.bak`后缀后重建数据库启动。对于某些人遇到更新到v2.0.0后出现之前收藏的歌曲全部丢失或者歌曲无法添加到列表的问题,可以通过此特性自动重建数据库并重新迁移数据,不再需要手动去数据目录删除数据库
### 优化
- 微调了桌面歌词逐行字体阴影,使其看起来更匀称
- 调整了桌面歌词在启用滚动到顶部时的距离,现在滚动到顶部的歌词更靠边,不再受字体大小、歌词间距影响
- 优化更新弹窗内容的显示,添加了自动更新失败时的更新指引
- 为所有文本输入框添加右键快速粘贴的功能,右击输入框可以自动粘贴剪贴板的文字,若选中文字时将粘贴并替换选中文字
- 防止桌面歌词窗口在屏幕分辨率变小时,窗口位置跟随分辨率变化的问题,现在若屏幕分辨率变小后窗口位置仍会在原始分辨率的位置(添加这个机制是为了解决屏幕分辨率被临时调整时的位置更新问题,如运行某些低分辨率的全屏游戏、高分辨率外接屏幕休眠时),但若你的分辨率调整不是临时的,因窗口在原始位置导致看不到窗口可以开关桌面歌词即可重新自动调节回屏幕内
### 修复
- 修复播放下载列表的歌曲时,调整歌词偏移时间功能异常的问题
- 修复较旧Linux arm64系统下无法启动软件的问题(将预构建模块的所需glibc版本降级到2.28)(#1161)
- 修改列表响应式更新机制,尝试修复偶现的删除歌曲列表未更新的问题
- 修复某些kg歌单链接无法打开的问题
- 修复将桌面歌词放到屏幕边缘时,偶现的开启桌面歌词后出现歌词窗口位置出现少许偏移的问题,以及将歌词窗口调整到全屏大小后,重开桌面歌词窗口被缩小出现边距的问题
### 其他
- 更新Electron到v22.3.0
## [2.0.5](https://github.com/lyswhut/lx-music-desktop/compare/v2.0.4...v2.0.5) - 2023-01-18
这应该是LX今年的最后一个版本,提前祝大家新年快乐~😘
### 修复
- 修复声音输出设备更改时后的自动暂停播放设置无效的问题
- 重写桌面歌词窗口坐标的计算逻辑,修复桌面歌词移动到最边缘时,某些情况下在启用歌词后会出现窗口偏移的问题(远古bug了)
- 修复随机播放模式下使用稍后播放功能播放我的列表的歌曲时,切换下一曲永远是当前歌曲的问题(#1147)
- 修复macOS下的软件系统菜单中的退出功能不会完全退出软件的问题(#1148)
## [2.0.4](https://github.com/lyswhut/lx-music-desktop/compare/v2.0.3...v2.0.4) - 2023-01-15
### 修复
- 修复备份文件导入指引无法识别v2配置的问题
- 修复从搜索界面进入歌单详情后,若启用强迫症设置的清空功能会导致意外清空搜索框、搜索列表的问题
- 修复桌面歌词在启用卡拉OK歌词后字体边缘可能被截断的问题(特别是纵向歌词某些字的边角被截断导致后面的阴影露出来或阴影不均匀的问题)
- 修复桌面歌词启用歌词缩放后的阴影显示问题
- 修复Linux armv7l系统(如树莓派)下无法启动的问题(与修复Linux arm64的方法一样采用内置预编译模块的方式修复)
- 修复备份与恢复的列表导入列表信息设置逻辑问题与潜在导入问题
## [2.0.3](https://github.com/lyswhut/lx-music-desktop/compare/v2.0.2...v2.0.3) - 2023-01-08
+2 -1
View File
@@ -2,6 +2,7 @@
本文档已迁移到:<https://lyswhut.github.io/lx-music-doc/desktop/faq>
<!--
在阅读本常见问题后,仍然无法解决你的问题,请提交issue或者加企鹅群`830125506`反馈(无事勿加,入群先看群公告),反馈时请**注明**已阅读常见问题!
## ~~软件为什么没有桌面歌词与自定义列表功能~~
@@ -562,4 +563,4 @@ const cancelHttp = window.lx.request(url, options, callback)
- `window.lx.utils.crypto.randomBytes`:生成随机字符串 `randomBytes(size)`
- `window.lx.utils.crypto.rsaEncrypt`:RSA加密 `rsaEncrypt(buffer, key)`
目前仅提供以上工具方法,如果需要其他方法可以开issue讨论。
目前仅提供以上工具方法,如果需要其他方法可以开issue讨论。 -->
+67 -27
View File
@@ -50,6 +50,10 @@
或者到网盘下载(网盘内有MAC、windows版):`https://www.lanzoui.com/b0bf2cfa/` 密码:`glqw`(若链接无法打开请百度:蓝奏云链接打不开)<br>
使用常见问题请转至:[常见问题](https://lyswhut.github.io/lx-music-doc/desktop/faq)
目前本项目的原始发布地址只有**GitHub**及**蓝奏网盘**,其他渠道均为第三方转载发布,与本项目无关!
为了提高使用门槛,本软件内的默认设置、UI操作不以新手友好为目标,所以使用前建议先根据你的喜好浏览调整一遍软件设置,阅读一遍[音乐播放列表机制](https://lyswhut.github.io/lx-music-doc/desktop/faq/playlist)及[可用的鼠标、键盘快捷操作](https://lyswhut.github.io/lx-music-doc/desktop/faq/hotkey)
#### Scheme URL支持
从v1.17.0起支持 Scheme URL,可以使用此功能从浏览器等场景下调用LX Music,我们开发了一个[油猴脚本](https://github.com/lyswhut/lx-music-script#readme)配套使用,<br>
@@ -57,6 +61,10 @@
若你想自己调用LX Music,可以看[Scheme URL支持](https://lyswhut.github.io/lx-music-doc/desktop/scheme-url)
#### 数据同步服务
从v2.2.0起,我们发布了一个独立版的[数据同步服务](https://github.com/lyswhut/lx-music-sync-server#readme),如果你有服务器,可以将其部署到服务器上作为私人多端同步服务使用,详情看该项目说明
#### 启动参数
目前软件已支持的启动参数如下:
@@ -83,25 +91,7 @@
### 源码使用方法
环境要求:Node.js 16+
```bash
# 开发模式
npm run dev
# 构建免安装版
npm run pack:dir
# 构建安装包(Windows版)
npm run pack:win
# 构建安装包(Mac版)
npm run pack:mac
# 构建安装包(Linux版)
npm run pack:linux
```
已迁移至:<https://lyswhut.github.io/lx-music-doc/desktop/use-source-code>
### UI界面
@@ -111,20 +101,70 @@ npm run pack:linux
常见问题已移至:<https://lyswhut.github.io/lx-music-doc/desktop/faq>
### 贡献代码
本项目欢迎PR,但为了PR能顺利合并,需要注意以下几点:
- 对于添加新功能的PR,建议在PR前先创建issue说明,以确认该功能是否确实需要
- 对于修复Bug PR,请提供修复前后的说明及重现方式
- 其他类型的PR则适当附上说明
贡献代码步骤:
1. 参照[源码使用方法](https://lyswhut.github.io/lx-music-doc/desktop/use-source-code)设置开发环境
2. 克隆本仓库代码并切换到`dev`分支开发
3. 提交PR至`dev`分支
### 项目协议
本项目基于 [Apache License 2.0](https://github.com/lyswhut/lx-music-desktop/blob/master/LICENSE) 许可证发行,以下协议是对于 Apache License 2.0 的补充,如有冲突,以以下协议为准。
---
词语约定:本协议中的“本项目”指洛雪音乐桌面版项目;“使用者”指签署本协议的使用者;“官方音乐平台”指对本项目内置的包括酷我、酷狗、咪咕等音乐源的官方平台统称;“版权数据”指包括但不限于图像、音频、名字等在内的他人拥有所属版权的数据。
1. 本项目的数据来源原理是从各官方音乐平台的公开服务器中拉取数据,经过对数据简单地筛选与合并后进行展示,因此本项目不对数据的准确性负责。
2. 使用本项目的过程中可能会产生版权数据,对于这些版权数据,本项目不拥有它们的所有权,为了避免造成侵权,使用者务必在**24小时**内清除使用本项目的过程中所产生的版权数据。
3. 本项目内的官方音乐平台别名为本项目内对官方音乐平台的一个称呼,不包含恶意,如果官方音乐平台觉得不妥,可联系本项目更改或移除。
4. 本项目内使用的部分包括但不限于字体、图片等资源来源于互联网,如果出现侵权可联系本项目移除。
5. 由于使用本项目产生的包括由于本协议或由于使用或无法使用本项目而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害(包括但不限于因商誉损失、停工、计算机故障或故障引起的损害赔偿,或任何及所有其他商业损害或损失)由使用者负责。
6. 本项目完全免费,且开源发布于 GitHub 面向全世界人用作对技术的学习交流,本项目不对项目内的技术可能存在违反当地法律法规的行为作保证,**禁止在违反当地法律法规的情况下使用本项目**,对于使用者在明知或不知当地法律法规不允许的情况下使用本项目所造成的任何违法违规行为由使用者承担,本项目不承担由此造成的任何直接、间接、特殊、偶然或结果性责任。
#### 一、数据来源
若你使用了本项目,将代表你接受以上协议。
1.1 本项目的各官方平台在线数据来源原理是从其公开服务器中拉取数据(与未登录状态在官方平台APP获取的数据相同),经过对数据简单地筛选与合并后进行展示,因此本项目不对数据的合法性、准确性负责。
1.2 本项目本身没有获取某个音频数据的能力,本项目使用的在线音频数据来源来自软件设置内“音乐来源”设置所选择的“源”返回的在线链接。例如播放某首歌,本项目所做的只是将希望播放的歌曲名字、歌手名字等信息传递给“源”,若“源”返回了一个链接,则本项目将认为这就是该歌曲的音频数据而进行使用,至于这是不是正确的音频数据本项目无法校验其准确性,所以使用本项目的过程中可能会出现希望播放的音频与实际播放的音频不对应或者无法播放的问题。
1.3 本项目的非官方平台数据(例如我的收藏列表)来自使用者本地系统或者使用者连接的同步服务,本项目不对这些数据的合法性、准确性负责。
#### 二、版权数据
2.1 使用本项目的过程中可能会产生版权数据。对于这些版权数据,本项目不拥有它们的所有权。为了避免侵权,使用者务必在**24小时内**清除使用本项目的过程中所产生的版权数据。
#### 三、音乐平台别名
3.1 本项目内的官方音乐平台别名为本项目内对官方音乐平台的一个称呼,不包含恶意。如果官方音乐平台觉得不妥,可联系本项目更改或移除。
#### 四、资源使用
4.1 本项目内使用的部分包括但不限于字体、图片等资源来源于互联网。如果出现侵权可联系本项目移除。
#### 五、免责声明
5.1 由于使用本项目产生的包括由于本协议或由于使用或无法使用本项目而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害(包括但不限于因商誉损失、停工、计算机故障或故障引起的损害赔偿,或任何及所有其他商业损害或损失)由使用者负责。
#### 六、使用限制
6.1 本项目完全免费,且开源发布于 GitHub 面向全世界人用作对技术的学习交流。本项目不对项目内的技术可能存在违反当地法律法规的行为作保证。
6.2 **禁止在违反当地法律法规的情况下使用本项目。** 对于使用者在明知或不知当地法律法规不允许的情况下使用本项目所造成的任何违法违规行为由使用者承担,本项目不承担由此造成的任何直接、间接、特殊、偶然或结果性责任。
#### 七、版权保护
7.1 音乐平台不易,请尊重版权,支持正版。
#### 八、非商业性质
8.1 本项目仅用于对技术可行性的探索及研究,不接受任何商业(包括但不限于广告等)合作及捐赠。
#### 九、接受协议
9.1 若你使用了本项目,将代表你接受本协议。
---
音乐平台不易,请尊重版权,支持正版。<br>
若对此有疑问请 mail to: lyswhut+qq.com (请将`+`替换成`@`)
+1 -1
View File
@@ -16,7 +16,7 @@ module.exports = async(context) => {
const resPath = `${appOutDir}/${productFilename}.app/Contents/Resources`
// 创建APP语言包文件
return await Promise.all(
return Promise.all(
Object.entries(macLanguagesInfoPlistStrings).map(([lang, config]) => {
let infos = Object.entries(config).map(([k, v]) => `"${k}" = "${v}";`).join('\n')
return fs.writeFile(`${resPath}/${lang}.lproj/InfoPlist.strings`, infos)
+56 -8
View File
@@ -2,30 +2,78 @@ const fs = require('fs')
const fsPromises = require('fs').promises
const path = require('path')
const { Arch } = require('electron-builder')
const nodeAbi = require('node-abi')
const replaceSqliteLib = async() => {
const better_sqlite3_fileNameMap = {
[Arch.x64]: 'linux-x64',
[Arch.arm64]: 'linux-arm64',
[Arch.armv7l]: 'linux-arm',
}
const qrc_decode_fileNameMap = {
win32: {
[Arch.x64]: 'win32-x64',
[Arch.ia32]: 'win32-ia32',
[Arch.arm64]: 'win32-arm64',
},
linux: {
[Arch.x64]: 'linux-x64',
[Arch.arm64]: 'linux-arm64',
[Arch.armv7l]: 'linux-arm',
},
darwin: {
[Arch.x64]: 'darwin-x64',
[Arch.arm64]: 'darwin-arm64',
},
}
const replaceSqliteLib = async(electronNodeAbi, arch) => {
// console.log(await fs.readdir(path.join(context.appOutDir, './resources/')))
// if (context.electronPlatformName != 'linux' || context.arch != Arch.arm64) return
// https://github.com/lyswhut/lx-music-desktop/issues/1102
// https://github.com/lyswhut/lx-music-desktop/issues/1161
console.log('replace sqlite lib...')
const filePath = path.join(__dirname, './lib/better_sqlite3.linux.arm64.node')
const filePath = path.join(__dirname, `./lib/better_sqlite3_electron-v${electronNodeAbi}-${better_sqlite3_fileNameMap[arch]}.node`)
console.log(filePath)
const targetPath = path.join(__dirname, '../node_modules/better-sqlite3/build/Release/better_sqlite3.node')
await fsPromises.unlink(targetPath).catch(_ => _)
await fsPromises.copyFile(filePath, targetPath)
}
const replaceQrcDecodeLib = async(electronNodeAbi, platform, arch) => {
console.log('replace qrc_decode lib...', platform, electronNodeAbi, qrc_decode_fileNameMap[platform][arch])
const filePath = path.join(__dirname, `./lib/qrc_decode_electron-v${electronNodeAbi}-${qrc_decode_fileNameMap[platform][arch]}.node`)
const targetPath = path.join(__dirname, '../build/Release/qrc_decode.node')
const targetDir = path.dirname(targetPath)
if (fs.existsSync(targetDir)) await fsPromises.unlink(targetPath).catch(_ => _)
else await fsPromises.mkdir(targetDir, { recursive: true })
await fsPromises.copyFile(filePath, targetPath)
}
module.exports = async(context) => {
const { electronPlatformName, arch } = context
if (electronPlatformName !== 'linux') return
const electronVersion = context.packager?.info?._framework?.version ?? require('../package.json').devDependencies.electron.replace(/^[^\d]*?(\d+)/, '$1')
const electronNodeAbi = nodeAbi.getAbi(electronVersion, 'electron')
await replaceQrcDecodeLib(electronNodeAbi, electronPlatformName, arch)
if (electronPlatformName !== 'linux' || process.env.FORCE) return
const bindingFilePath = path.join(__dirname, '../node_modules/better-sqlite3/binding.gyp')
const bindingBakFilePath = path.join(__dirname, '../node_modules/better-sqlite3/binding.gyp.bak')
if (arch == Arch.arm64) {
console.log('rename binding file...')
switch (arch) {
case Arch.x64:
case Arch.arm64:
case Arch.armv7l:
if (fs.existsSync(bindingFilePath)) {
// console.log('rename binding file...')
await fsPromises.rename(bindingFilePath, bindingBakFilePath)
await replaceSqliteLib()
} else {
}
await replaceSqliteLib(electronNodeAbi, arch)
break
default:
if (fs.existsSync(bindingFilePath)) return
console.log('restore binding file...')
// console.log('restore binding file...')
await fsPromises.rename(bindingBakFilePath, bindingFilePath)
break
}
}
+303
View File
@@ -0,0 +1,303 @@
/* eslint-disable no-template-curly-in-string */
const builder = require('electron-builder')
const beforePack = require('./build-before-pack')
const afterPack = require('./build-after-pack')
/**
* @type {import('electron-builder').Configuration}
* @see https://www.electron.build/configuration/configuration
*/
const options = {
appId: 'cn.toside.music.desktop',
beforePack,
afterPack,
protocols: {
name: 'lx-music-protocol',
schemes: [
'lxmusic',
],
},
directories: {
buildResources: './resources',
output: './build',
},
files: [
'!node_modules/**/*',
'node_modules/font-list',
'node_modules/better-sqlite3/lib',
'node_modules/better-sqlite3/package.json',
'node_modules/better-sqlite3/build/Release/better_sqlite3.node',
'node_modules/electron-font-manager/index.js',
'node_modules/electron-font-manager/package.json',
'node_modules/electron-font-manager/build/Release/font_manager.node',
'node_modules/node-gyp-build',
'node_modules/bufferutil',
'node_modules/utf-8-validate',
'build/Release/qrc_decode.node',
'dist/**/*',
],
asar: {
smartUnpack: false,
},
extraResources: [
'./licenses',
],
publish: [
{
provider: 'github',
owner: 'lyswhut',
repo: 'lx-music-desktop',
},
],
}
/**
* @type {import('electron-builder').Configuration}
* @see https://www.electron.build/configuration/configuration
*/
const winOptions = {
win: {
icon: './resources/icons/icon.ico',
legalTrademarks: 'lyswhut',
// artifactName: '${productName}-v${version}-${env.ARCH}-${env.TARGET}.${ext}',
},
nsis: {
oneClick: false,
language: '2052',
allowToChangeInstallationDirectory: true,
differentialPackage: true,
license: './licenses/license.rtf',
shortcutName: 'LX Music',
},
}
/**
* @type {import('electron-builder').Configuration}
* @see https://www.electron.build/configuration/configuration
*/
const linuxOptions = {
linux: {
maintainer: 'lyswhut <lyswhut@qq.com>',
// artifactName: '${productName}-${version}.${env.ARCH}.${ext}',
icon: './resources/icons',
category: 'Utility;AudioVideo;Audio;Player;Music;',
desktop: {
Name: 'LX Music',
'Name[zh_CN]': 'LX Music',
'Name[zh_TW]': 'LX Music',
Encoding: 'UTF-8',
MimeType: 'x-scheme-handler/lxmusic',
StartupNotify: 'false',
},
},
appImage: {
license: './licenses/license_zh.txt',
category: 'Utility;AudioVideo;Audio;Player;Music;',
},
}
/**
* @type {import('electron-builder').Configuration}
* @see https://www.electron.build/configuration/configuration
*/
const macOptions = {
mac: {
icon: './resources/icons/icon.icns',
category: 'public.app-category.music',
// artifactName: '${productName}-${version}.${ext}',
},
dmg: {
window: {
width: 600,
height: 400,
},
contents: [
{
x: 106,
y: 252,
name: 'LX Music',
},
{
x: 490,
y: 252,
type: 'link',
path: '/Applications',
},
],
title: '洛雪音乐助手 v${version}',
},
}
// win: {
// tagret: {
// setup: ['nsis', '${productName}-v${version}-${env.ARCH}-Setup.${ext}'],
// green: ['7z', '${productName}-v${version}-${env.ARCH}-green.${ext}'],
// portable: ['portable', '${productName}-v${version}-${env.ARCH}-portable.${ext}'],
// },
// },
// linux: {
// platform: Platform.WINDOWS,
// arch: {
// x64: builder.Arch.x64,
// arm64: builder.Arch.arm64,
// armv7l: builder.Arch.armv7l,
// },
// tagret: {
// deb: ['deb', '${productName}_${version}_${env.ARCH}.${ext}'],
// appImage: ['AppImage', '${productName}_${version}_${env.ARCH}.${ext}'],
// pacman: ['pacman', '${productName}_${version}_${env.ARCH}.${ext}'],
// rpm: ['rpm', '${productName}-${version}.${env.ARCH}.${ext}'],
// },
// },
// mac: {
// arch: {
// x64: builder.Arch.x64,
// x86: builder.Arch.ia32,
// arm64: builder.Arch.arm64,
// },
// tagret: {
// dmg: ['dmg', '${productName}-${version}-${env.ARCH}.${ext}'],
// },
// },
const createTarget = {
/**
*
* @param {*} arch
* @param {*} packageType
* @returns {{ buildOptions: import('electron-builder').CliOptions, options: import('electron-builder').Configuration }}
*/
win(arch, packageType) {
switch (packageType) {
case 'setup':
winOptions.artifactName = `\${productName}-v\${version}-${arch}-Setup.\${ext}`
return {
buildOptions: { win: ['nsis'] },
options: winOptions,
}
case 'green':
winOptions.artifactName = `\${productName}-v\${version}-win_${arch}-green.\${ext}`
return {
buildOptions: { win: ['7z'] },
options: winOptions,
}
case 'win7_setup':
winOptions.artifactName = `\${productName}-v\${version}-win7_${arch}-Setup.\${ext}`
return {
buildOptions: { win: ['nsis'] },
options: winOptions,
}
case 'win7_green':
winOptions.artifactName = `\${productName}-v\${version}-win7_${arch}-green.\${ext}`
return {
buildOptions: { win: ['7z'] },
options: winOptions,
}
case 'portable':
winOptions.artifactName = `\${productName}-v\${version}-${arch}-portable.\${ext}`
return {
buildOptions: { win: ['portable'] },
options: winOptions,
}
default: throw new Error('Unknown package type: ' + packageType)
}
},
/**
*
* @param {*} arch
* @param {*} packageType
* @returns {{ buildOptions: import('electron-builder').CliOptions, options: import('electron-builder').Configuration }}
*/
linux(arch, packageType) {
switch (packageType) {
case 'deb':
linuxOptions.artifactName = `\${productName}_\${version}_${arch == 'x64' ? 'amd64' : arch}.\${ext}`
return {
buildOptions: { linux: ['deb'] },
options: linuxOptions,
}
case 'appImage':
linuxOptions.artifactName = `\${productName}_\${version}_${arch}.\${ext}`
return {
buildOptions: { linux: ['AppImage'] },
options: linuxOptions,
}
case 'pacman':
linuxOptions.artifactName = `\${productName}_\${version}_${arch}.\${ext}`
return {
buildOptions: { linux: ['pacman'] },
options: linuxOptions,
}
case 'rpm':
linuxOptions.artifactName = `\${productName}-\${version}.${arch}.\${ext}`
return {
buildOptions: { linux: ['rpm'] },
options: linuxOptions,
}
default: throw new Error('Unknown package type: ' + packageType)
}
},
/**
*
* @param {*} arch
* @param {*} packageType
* @returns {{ buildOptions: import('electron-builder').CliOptions, options: import('electron-builder').Configuration }}
*/
mac(arch, packageType) {
switch (packageType) {
case 'dmg':
macOptions.artifactName = `\${productName}-\${version}-${arch}.\${ext}`
return {
buildOptions: { mac: ['dmg'] },
options: macOptions,
}
default: throw new Error('Unknown package type: ' + packageType)
}
},
}
/**
*
* @param {'win' | 'mac' | 'linux' | 'dir'} target 构建目标平台
* @param {'x86_64' | 'x64' | 'x86' | 'arm64' | 'armv7l'} arch 包架构
* @param {*} packageType 包类型
* @param {'onTagOrDraft' | 'always' | 'never'} publishType 发布类型
*/
const build = async(target, arch, packageType, publishType) => {
if (target == 'dir') {
await builder.build({
dir: true,
config: { ...options, ...winOptions, ...linuxOptions, ...macOptions },
})
return
}
const targetInfo = createTarget[target](arch, packageType)
// Promise is returned
await builder.build({
...targetInfo.buildOptions,
publish: publishType ?? 'never',
x64: arch == 'x64' || arch == 'x86_64',
ia32: arch == 'x86' || arch == 'x86_64',
arm64: arch == 'arm64',
armv7l: arch == 'armv7l',
config: { ...options, ...targetInfo.options },
})
// .then((result) => {
// console.log(JSON.stringify(result))
// })
// .catch((error) => {
// console.error(error)
// })
}
const params = {}
for (const param of process.argv.slice(2)) {
const [name, value] = param.split('=')
params[name] = value
}
if (params.target == null) throw new Error('Missing target')
if (params.target != 'dir' && params.arch == null) throw new Error('Missing arch')
if (params.target != 'dir' && params.type == null) throw new Error('Missing type')
console.log(params.target, params.arch, params.type, params.publish ?? '')
build(params.target, params.arch, params.type, params.publish)
+38
View File
@@ -0,0 +1,38 @@
// 修补依赖源码以使vite构建的依赖恢复正常工作
const fs = require('node:fs')
const path = require('node:path')
const rootPath = path.join(__dirname, '../')
const patchs = [
[
path.join(rootPath, './node_modules/ws/package.json'),
'\n "browser": "./browser.js",',
'',
],
[
path.join(rootPath, './node_modules/music-metadata/package.json'),
'"default": "./lib/core.js"',
'"default": "./lib/index.js"',
],
[
path.join(rootPath, './node_modules/strtok3/package.json'),
'"default": "./lib/core.js"',
'"default": "./lib/index.js"',
],
]
;(async() => {
for (const [filePath, fromStr, toStr] of patchs) {
console.log(`Patching ${filePath.replace(rootPath, '')}`)
try {
const file = (await fs.promises.readFile(filePath)).toString()
await fs.promises.writeFile(filePath, file.replace(fromStr, toStr))
} catch (err) {
console.error(`Patch ${filePath.replace(rootPath, '')} failed: ${err.message}`)
}
}
console.log('\nDependencies patch finished.\n')
})()
+53
View File
@@ -0,0 +1,53 @@
const fs = require('fs')
const path = require('path')
const tar = require('tar')
const libDir = path.join(__dirname, 'lib')
const getGzipFiles = async() => {
const names = await fs.promises.readdir(libDir)
// for (const name of names) {
// if (name.endsWith('.node')) await fs.promises.unlink(path.join(libDir, name))
// }
return names.filter(name => name.endsWith('.gz'))
}
const unzip = async(filePath) => {
const targetDir = filePath.replace('.tar.gz', '')
if (fs.existsSync(targetDir)) await fs.promises.rm(targetDir, { recursive: true })
await fs.promises.mkdir(targetDir)
await tar.x({
file: filePath,
strip: 1,
C: targetDir,
})
return targetDir
}
const files = [
'qrc_decode',
'better_sqlite3',
]
const moveFile = async(filePath) => {
const name = 'electron-' + path.basename(filePath).split('-electron-')[1]
for (const fileName of files) {
if (fileName == 'better_sqlite3' && !name.includes('linux')) continue
const targetPath = path.join(libDir, `${fileName}_${name}.node`)
if (fs.existsSync(targetPath)) await fs.promises.unlink(targetPath)
await fs.promises.rename(path.join(filePath, 'Release', fileName + '.node'), targetPath)
}
await fs.promises.rm(filePath, { recursive: true })
}
const run = async() => {
const files = await getGzipFiles()
for (const name of files) {
await moveFile(await unzip(path.join(libDir, name)))
}
for (const name of files) {
await fs.promises.unlink(path.join(libDir, name))
}
}
run()
Binary file not shown.
+10 -6
View File
@@ -1,6 +1,8 @@
const path = require('path')
const ESLintPlugin = require('eslint-webpack-plugin')
const isDev = process.env.NODE_ENV === 'development'
module.exports = {
target: 'electron-main',
output: {
@@ -10,12 +12,14 @@ module.exports = {
},
path: path.join(__dirname, '../../dist'),
},
externals: [
'font-list',
'better-sqlite3',
'bufferutil',
'utf-8-validate',
],
externals: {
'font-list': 'font-list',
'better-sqlite3': 'better-sqlite3',
'electron-font-manager': 'electron-font-manager',
bufferutil: 'bufferutil',
'utf-8-validate': 'utf-8-validate',
'qrc_decode.node': isDev ? path.join(__dirname, '../../build/Release/qrc_decode.node') : path.join('../build/Release/qrc_decode.node'),
},
resolve: {
alias: {
'@main': path.join(__dirname, '../../src/main'),
+1
View File
@@ -12,6 +12,7 @@ const buildConfig = require('../webpack-build-config')
module.exports = merge(baseConfig, {
mode: 'production',
devtool: 'source-map',
entry: {
main: path.join(__dirname, '../../src/main/index.ts'),
// 'dbService.worker': path.join(__dirname, '../../src/main/worker/dbService/index.ts'),
+2
View File
@@ -17,6 +17,7 @@ const { Worker, isMainThread, parentPort } = require('worker_threads')
function build() {
console.time('build')
del.sync(['dist/**', 'build/**'])
const spinners = new Spinnies({ color: 'blue' })
@@ -36,6 +37,7 @@ function build() {
process.stdout.write('\x1B[2J\x1B[0f')
console.log(`\n\n${results}`)
console.log(`${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n`)
console.timeEnd('build')
process.exit()
}
+2
View File
@@ -0,0 +1,2 @@
const { Arch } = require('electron-builder')
require('./build-before-pack')({ electronPlatformName: process.platform, arch: Arch[process.arch] })
@@ -20,26 +20,21 @@ module.exports = {
type: 'commonjs2',
},
path: path.join(__dirname, '../../dist'),
publicPath: 'auto',
publicPath: '',
},
resolve: {
alias: {
'@': path.join(__dirname, '../../src'),
'@root': path.join(__dirname, '../../src'),
'@main': path.join(__dirname, '../../src/main'),
'@renderer': path.join(__dirname, '../../src/renderer'),
'@lyric': path.join(__dirname, '../../src/renderer-lyric'),
'@static': path.join(__dirname, '../../src/static'),
'@common': path.join(__dirname, '../../src/common'),
},
extensions: ['.tsx', '.ts', '.js', '.json', '.vue', '.node'],
extensions: ['.tsx', '.ts', '.js', '.json', '.node'],
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.tsx?$/,
exclude: /node_modules/,
@@ -16,6 +16,7 @@ module.exports = merge(baseConfig, {
},
__VUE_OPTIONS_API__: 'true',
__VUE_PROD_DEVTOOLS__: 'false',
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false',
staticPath: `"${path.join(__dirname, '../../src/static').replace(/\\/g, '\\\\')}"`,
}),
],
@@ -15,10 +15,19 @@ const buildConfig = require('../webpack-build-config')
module.exports = merge(baseConfig, {
mode: 'production',
devtool: false,
devtool: 'source-map',
externals: [
// ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)),
],
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
],
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
@@ -26,6 +35,7 @@ module.exports = merge(baseConfig, {
},
__VUE_OPTIONS_API__: 'true',
__VUE_PROD_DEVTOOLS__: 'false',
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false',
}),
],
optimization: {
@@ -12,26 +12,21 @@ module.exports = {
type: 'commonjs2',
},
path: path.join(__dirname, '../../dist'),
publicPath: 'auto',
publicPath: '',
},
resolve: {
alias: {
'@': path.join(__dirname, '../../src'),
'@root': path.join(__dirname, '../../src'),
'@main': path.join(__dirname, '../../src/main'),
'@renderer': path.join(__dirname, '../../src/renderer'),
'@lyric': path.join(__dirname, '../../src/renderer-lyric'),
'@static': path.join(__dirname, '../../src/static'),
'@common': path.join(__dirname, '../../src/common'),
},
extensions: ['.tsx', '.ts', '.js', '.json', '.vue', '.node'],
extensions: ['.tsx', '.ts', '.js', '.json', '.node'],
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.tsx?$/,
exclude: /node_modules/,
@@ -14,10 +14,19 @@ const buildConfig = require('../webpack-build-config')
module.exports = merge(baseConfig, {
mode: 'production',
devtool: false,
devtool: 'source-map',
externals: [
// ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)),
],
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
],
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
+10 -9
View File
@@ -20,26 +20,21 @@ module.exports = {
type: 'commonjs2',
},
path: path.join(__dirname, '../../dist'),
publicPath: 'auto',
publicPath: '',
},
resolve: {
alias: {
'@': path.join(__dirname, '../../src'),
'@root': path.join(__dirname, '../../src'),
'@main': path.join(__dirname, '../../src/main'),
'@renderer': path.join(__dirname, '../../src/renderer'),
'@lyric': path.join(__dirname, '../../src/renderer-lyric'),
'@static': path.join(__dirname, '../../src/static'),
'@common': path.join(__dirname, '../../src/common'),
},
extensions: ['.tsx', '.ts', '.js', '.json', '.vue', '.node'],
extensions: ['.tsx', '.ts', '.js', '.json', '.node'],
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.tsx?$/,
exclude: /node_modules/,
@@ -49,6 +44,12 @@ module.exports = {
appendTsSuffixTo: [/\.vue$/],
},
},
parser: {
worker: [
'*audioContext.audioWorklet.addModule()',
'...',
],
},
},
{
test: /\.node$/,
@@ -104,7 +105,7 @@ module.exports = {
],
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)$/,
type: 'asset',
parser: {
dataUrlCondition: {
@@ -17,6 +17,7 @@ module.exports = merge(baseConfig, {
// ENVIRONMENT: 'process.env',
__VUE_OPTIONS_API__: 'true',
__VUE_PROD_DEVTOOLS__: 'false',
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false',
staticPath: `"${path.join(__dirname, '../../src/static').replace(/\\/g, '\\\\')}"`,
}),
],
+11 -1
View File
@@ -15,10 +15,19 @@ const buildConfig = require('../webpack-build-config')
module.exports = merge(baseConfig, {
mode: 'production',
devtool: false,
devtool: 'source-map',
externals: [
// ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)),
],
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
],
},
plugins: [
new CopyWebpackPlugin({
patterns: [
@@ -35,6 +44,7 @@ module.exports = merge(baseConfig, {
// ENVIRONMENT: 'process.env',
__VUE_OPTIONS_API__: 'true',
__VUE_PROD_DEVTOOLS__: 'false',
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false',
}),
],
optimization: {
+3
View File
@@ -14,6 +14,8 @@ const mainConfig = require('./main/webpack.config.dev')
const rendererConfig = require('./renderer/webpack.config.dev')
const rendererLyricConfig = require('./renderer-lyric/webpack.config.dev')
const rendererScriptConfig = require('./renderer-scripts/webpack.config.dev')
const { Arch } = require('electron-builder')
const replaceLib = require('./build-before-pack')
let electronProcess = null
let manualRestart = false
@@ -224,6 +226,7 @@ function init() {
function handleFail(name) {
spinners.fail(name, { text: name + ' compile fail!' })
}
replaceLib({ electronPlatformName: process.platform, arch: Arch[process.arch] })
Promise.all([
startRenderer().then(() => handleSuccess('renderer')).catch((err) => {
+1 -1
View File
@@ -1,3 +1,3 @@
module.exports = {
minimize: false,
minimize: true,
}
+5
View File
@@ -9,5 +9,10 @@
"@common/*": ["src/common/*"],
}
},
"vueCompilerOptions": {
"plugins": [
"@vue/language-plugin-pug"
]
},
"exclude": ["node_modules", "build", "dist"]
}
+119 -63
View File
@@ -38,12 +38,12 @@
\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;}{\*\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
{\*\rsidtbl \rsid927107\rsid1398824\rsid2109456\rsid3758332\rsid3950508\rsid4133944\rsid4355753\rsid9533173\rsid10447395\rsid11081282\rsid12910709\rsid13643782\rsid14384001\rsid14511311\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\yr2023\mo10\dy5\hr14\min42}{\version10}{\edmins4}{\nofpages1}{\nofwords193}{\nofchars1105}
{\nofcharsws1296}{\vern77}}{\*\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\viewscale120\splytwnine\ftnlytwnine\htmautsp\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct\asianbrkrule\rsidroot3950508\newtblstyruls
\dghorigin450\dgvorigin0\dghshow0\dgvshow2\jcompress\lnongrid
\viewkind5\viewscale100\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
@@ -53,64 +53,120 @@ $([\'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\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
\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}\pard\plain \ltrpar\s15\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid14511311 \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\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf
\'bb\'f9\'d3\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \hich\af13\dbch\af13\loch\f13 Apache License 2.0 }{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'d0\'ed\'bf\'c9\'d6\'a4\'b7\'a2\'d0\'d0\'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\insrsid14511311\charrsid14511311 \hich\af13\dbch\af13\loch\f13 Apache License 2.0 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \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\insrsid14511311\charrsid14511311
\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
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \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\'cf\'ee\'c4\'bf\'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\'cf\'ee\'c4\'bf\'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\insrsid14511311\charrsid14511311
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'d2\'bb\'a1\'a2\'ca\'fd\'be\'dd\'c0\'b4\'d4\'b4}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par
\par \hich\af13\dbch\af13\loch\f13 1.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'b5\'c4\'b8\'f7\'b9\'d9\'b7\'bd\'c6\'bd\'cc\'a8\'d4\'da\'cf\'df
\'ca\'fd\'be\'dd\'c0\'b4\'d4\'b4\'d4\'ad\'c0\'ed\'ca\'c7\'b4\'d3\'c6\'e4\'b9\'ab\'bf\'aa\'b7\'fe\'ce\'f1\'c6\'f7\'d6\'d0\'c0\'ad\'c8\'a1\'ca\'fd\'be\'dd\'a3\'a8\'d3\'eb\'ce\'b4\'b5\'c7\'c2\'bc\'d7\'b4\'cc\'ac\'d4\'da\'b9\'d9\'b7\'bd\'c6\'bd\'cc\'a8}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \hich\af13\dbch\af13\loch\f13 APP}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13
\'bb\'f1\'c8\'a1\'b5\'c4\'ca\'fd\'be\'dd\'cf\'e0\'cd\'ac\'a3\'a9\'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
\'cf\'ee\'c4\'bf\'b2\'bb\'b6\'d4\'ca\'fd\'be\'dd\'b5\'c4\'ba\'cf\'b7\'a8\'d0\'d4\'a1\'a2\'d7\'bc\'c8\'b7\'d0\'d4\'b8\'ba\'d4\'f0\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par \hich\af13\dbch\af13\loch\f13 1.2 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'b1\'be\'c9\'ed\'c3\'bb\'d3\'d0\'bb\'f1\'c8\'a1\'c4\'b3\'b8\'f6
\'d2\'f4\'c6\'b5\'ca\'fd\'be\'dd\'b5\'c4\'c4\'dc\'c1\'a6\'a3\'ac\'b1\'be\'cf\'ee\'c4\'bf\'ca\'b9\'d3\'c3\'b5\'c4\'d4\'da\'cf\'df\'d2\'f4\'c6\'b5\'ca\'fd\'be\'dd\'c0\'b4\'d4\'b4\'c0\'b4\'d7\'d4\'c8\'ed\'bc\'fe\'c9\'e8\'d6\'c3\'c4\'da}{\rtlch\fcs1 \af13
\ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8220\'a1\'b0}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'d2\'f4\'c0\'d6\'c0\'b4\'d4\'b4}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8221\'a1\'b1}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'c9\'e8\'d6\'c3
\'cb\'f9\'d1\'a1\'d4\'f1\'b5\'c4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8220\'a1\'b0}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\loch\af13\hich\af13\dbch\f13 \'d4\'b4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8221\'a1\'b1}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\loch\af13\hich\af13\dbch\f13 \'b7\'b5\'bb\'d8\'b5\'c4\'d4\'da\'cf\'df\'c1\'b4\'bd\'d3\'a1\'a3\'c0\'fd\'c8\'e7\'b2\'a5\'b7\'c5\'c4\'b3\'ca\'d7\'b8\'e8\'a3\'ac\'b1\'be\'cf\'ee\'c4\'bf\'cb\'f9\'d7\'f6\'b5\'c4\'d6\'bb\'ca\'c7\'bd\'ab\'cf\'a3\'cd\'fb\'b2\'a5
\'b7\'c5\'b5\'c4\'b8\'e8\'c7\'fa\'c3\'fb\'d7\'d6\'a1\'a2\'b8\'e8\'ca\'d6\'c3\'fb\'d7\'d6\'b5\'c8\'d0\'c5\'cf\'a2\'b4\'ab\'b5\'dd\'b8\'f8}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8220\'a1\'b0}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'d4\'b4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8221\'a1\'b1}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'a3\'ac\'c8\'f4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8220\'a1\'b0}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'d4\'b4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8221\'a1\'b1}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b7\'b5\'bb\'d8\'c1\'cb\'d2\'bb\'b8\'f6\'c1\'b4\'bd\'d3\'a3\'ac\'d4\'f2\'b1\'be\'cf\'ee\'c4\'bf\'bd\'ab\'c8\'cf\'ce\'aa\'d5\'e2
\'be\'cd\'ca\'c7\'b8\'c3\'b8\'e8\'c7\'fa\'b5\'c4\'d2\'f4\'c6\'b5\'ca\'fd\'be\'dd\'b6\'f8\'bd\'f8\'d0\'d0\'ca\'b9\'d3\'c3\'a3\'ac\'d6\'c1\'d3\'da\'d5\'e2\'ca\'c7\'b2\'bb\'ca\'c7\'d5\'fd\'c8\'b7\'b5\'c4\'d2\'f4\'c6\'b5\'ca\'fd\'be\'dd\'b1\'be\'cf\'ee
\'c4\'bf\'ce\'de\'b7\'a8\'d0\'a3\'d1\'e9\'c6\'e4\'d7\'bc\'c8\'b7\'d0\'d4\'a3\'ac\'cb\'f9\'d2\'d4\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'b5\'c4\'b9\'fd\'b3\'cc\'d6\'d0\'bf\'c9\'c4\'dc\'bb\'e1\'b3\'f6\'cf\'d6\'cf\'a3\'cd\'fb\'b2\'a5\'b7\'c5\'b5\'c4
\'d2\'f4\'c6\'b5\'d3\'eb\'ca\'b5\'bc\'ca\'b2\'a5\'b7\'c5\'b5\'c4\'d2\'f4\'c6\'b5\'b2\'bb\'b6\'d4\'d3\'a6\'bb\'f2\'d5\'df\'ce\'de\'b7\'a8\'b2\'a5\'b7\'c5\'b5\'c4\'ce\'ca\'cc\'e2\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par \hich\af13\dbch\af13\loch\f13 1.3 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'b5\'c4\'b7\'c7\'b9\'d9\'b7\'bd\'c6\'bd\'cc\'a8\'ca\'fd\'be\'dd
\'a3\'a8\'c0\'fd\'c8\'e7\'ce\'d2\'b5\'c4\'ca\'d5\'b2\'d8\'c1\'d0\'b1\'ed\'a3\'a9\'c0\'b4\'d7\'d4\'ca\'b9\'d3\'c3\'d5\'df\'b1\'be\'b5\'d8\'cf\'b5\'cd\'b3\'bb\'f2\'d5\'df\'ca\'b9\'d3\'c3\'d5\'df\'c1\'ac\'bd\'d3\'b5\'c4\'cd\'ac\'b2\'bd\'b7\'fe\'ce\'f1
\'a3\'ac\'b1\'be\'cf\'ee\'c4\'bf\'b2\'bb\'b6\'d4\'d5\'e2\'d0\'a9\'ca\'fd\'be\'dd\'b5\'c4\'ba\'cf\'b7\'a8\'d0\'d4\'a1\'a2\'d7\'bc\'c8\'b7\'d0\'d4\'b8\'ba\'d4\'f0\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b6\'fe\'a1\'a2\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par
\par \hich\af13\dbch\af13\loch\f13 2.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'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\'a1\'a3\'b6\'d4\'d3\'da\'d5\'e2\'d0\'a9\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd\'a3\'ac\'b1\'be\'cf\'ee\'c4\'bf\'b2\'bb\'d3\'b5\'d3\'d0\'cb\'fc\'c3\'c7\'b5\'c4\'cb\'f9\'d3\'d0\'c8\'a8\'a1\'a3\'ce\'aa
\'c1\'cb\'b1\'dc\'c3\'e2\'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\insrsid14511311\charrsid14511311 \hich\af13\dbch\af13\loch\f13 **24}{\rtlch\fcs1 \af13
\ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'d0\'a1\'ca\'b1\'c4\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\hich\af13\dbch\af13\loch\f13 **}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'c7\'e5\'b3\'fd\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'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\insrsid14511311\charrsid14511311
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'c8\'fd\'a1\'a2\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'b1\'f0\'c3\'fb}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par
\par \hich\af13\dbch\af13\loch\f13 3.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'c4\'da\'b5\'c4\'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8
\'b1\'f0\'c3\'fb\'ce\'aa\'b1\'be\'cf\'ee\'c4\'bf\'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\'a1\'a3\'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\'cf\'ee\'c4\'bf\'b8\'fc\'b8\'c4\'bb\'f2\'d2\'c6\'b3\'fd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'cb\'c4\'a1\'a2\'d7\'ca\'d4\'b4\'ca\'b9\'d3\'c3}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par
\par \hich\af13\dbch\af13\loch\f13 4.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'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\'a1\'a3\'c8\'e7\'b9\'fb\'b3\'f6\'cf\'d6\'c7\'d6\'c8\'a8\'bf\'c9\'c1\'aa\'cf\'b5\'b1\'be\'cf\'ee\'c4\'bf
\'d2\'c6\'b3\'fd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'ce\'e5\'a1\'a2\'c3\'e2\'d4\'f0\'c9\'f9\'c3\'f7}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par
\par \hich\af13\dbch\af13\loch\f13 5.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'d3\'c9\'d3\'da\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'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\'cf\'ee\'c4\'bf\'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
\'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\'cb\'f9\loch\af13\hich\af13\dbch\f13 \'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\insrsid14511311\charrsid14511311
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'c1\'f9\'a1\'a2\'ca\'b9\'d3\'c3\'cf\'de\'d6\'c6}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par
\par \hich\af13\dbch\af13\loch\f13 6.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'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\insrsid14511311\charrsid14511311 \hich\af13\dbch\af13\loch\f13 GitHub }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\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\'a1\'a3\'b1\'be\'cf\'ee\'c4\'bf\'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\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par
\par \hich\af13\dbch\af13\loch\f13 6.2 **}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'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\'cf\'ee\'c4\'bf\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \hich\af13\dbch\af13\loch\f13 ** }{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'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\'cf\'ee\'c4\'bf\'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\'cf\'ee\'c4\'bf
\'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\insrsid14511311\charrsid14511311
\par
\par }\pard \ltrpar\s15\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid3758332 {\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332 \loch\af13\hich\af13\dbch\f13 \'c6\'df
\'a1\'a2\'b0\'e6\'c8\'a8\'b1\'a3\'bb\'a4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332
\par
\par \hich\af13\dbch\af13\loch\f13 7.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332 \loch\af13\hich\af13\dbch\f13 \'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'b2\'bb\'d2\'d7\'a3\'ac\'c7\'eb\'d7\'f0\'d6\'d8\'b0\'e6\'c8\'a8
\'a3\'ac\'d6\'a7\'b3\'d6\'d5\'fd\'b0\'e6\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332 \loch\af13\hich\af13\dbch\f13 \'b0\'cb\'a1\'a2\'b7\'c7\'c9\'cc\'d2\'b5\'d0\'d4\'d6\'ca}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332
\par
\par \hich\af13\dbch\af13\loch\f13 8.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'bd\'f6\'d3\'c3\'d3\'da\'b6\'d4\'bc\'bc\'ca\'f5\'bf\'c9\'d0\'d0\'d0\'d4
\'b5\'c4\'cc\'bd\'cb\'f7\'bc\'b0\'d1\'d0\'be\'bf\'a3\'ac\'b2\'bb\'bd\'d3\'ca\'dc\'c8\'ce\'ba\'ce\'c9\'cc\'d2\'b5\'a3\'a8\'b0\'fc\'c0\'a8\'b5\'ab\'b2\'bb\'cf\'de\'d3\'da\'b9\'e3\'b8\'e6\'b5\'c8\'a3\'a9\'ba\'cf\'d7\'f7\'bc\'b0\'be\'e8\'d4\'f9\'a1\'a3}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332 \loch\af13\hich\af13\dbch\f13 \'be\'c5\'a1\'a2\'bd\'d3\'ca\'dc\'d0\'ad\'d2\'e9}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332
\par
\par \hich\af13\dbch\af13\loch\f13 9.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332 \loch\af13\hich\af13\dbch\f13 \'c8\'f4\'c4\'e3\'ca\'b9\'d3\'c3\'c1\'cb\'b1\'be\'cf\'ee\'c4\'bf\'a3\'ac\'bd\'ab\'b4\'fa\'b1\'ed
\'c4\'e3\'bd\'d3\'ca\'dc\'b1\'be\'d0\'ad\'d2\'e9\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid14511311
\par }\pard \ltrpar\s15\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid14511311 {\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \hich\af13\dbch\af13\loch\f13 * }{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'c8\'f4\'d0\'ad\'d2\'e9\'b8\'fc\'d0\'c2\'a3\'ac\'cb\'a1\loch\af13\hich\af13\dbch\f13 \'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\insrsid9533173
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\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
@@ -255,8 +311,8 @@ fffffffffffffffffdfffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e5000000000000000000000000b026
8d59201dd601feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000
ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e5000000000000000000000000c03d
9b1957f7d901feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000105000000000000}}
+41 -11
View File
@@ -1,18 +1,48 @@
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.
This project is issued based on the Apache License 2.0 license. The following protocols are supplemented by Apache License 2.0.
Words stipulate: "this project" in this agreement refers to the Luoxue Music Desktop Edition project; "user" refers to the user who signed this agreement; the "official music platform" refers to The official platform of the music source is collectively referred to; "copyright data" refers to the data that includes but not limited to images, audio, names, etc.
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. Data source
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.
1.1 The principle of online data sources of various official platforms of this project is to draw data from its open server (the same data obtained from the unsproting state in the official platform app). After simply screening and merging data, this project Responsible for the legitimacy and accuracy of the data.
1.2 The ability of this project itself does not obtain a certain audio data. The online audio data source used in this project comes from the online link of the "Source" selected in the software settings. For example, when playing a song, the project only transmits information such as the song name, singer name and other information to be played to "source". If the "source" returns a link, this project will think that this is the audio data of the song For use, as for whether this is the correct audio data, this project cannot verify its accuracy, so the audio that you want to play may occur during the process of using this project.
1.3 The unofficial platform data (such as my collection list) of this project comes from synchronous services connected by the user's local system or user connection. This project is not responsible for the legality and accuracy of these data.
2. Copyright data
* 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.
2.1 During the process of using this project, copyright data may be generated. For these copyright data, this project does not have their ownership. In order to avoid infringement, users must remove copyright data generated during the process of using this project within ** 24 hours.
3. Alias of Music Platform
3.1 The official music platform in this project is named a name for the official music platform in this project, without maliciousness. If the official music platform feels inappropriate, you can contact this project to change or remove.
Fourth, resource use
4.1 The parts used in this project include, but not limited to fonts, pictures and other resources from the Internet. If infringement occurs, you can contact this project.
5. Disclaimer
5.1 The use of this project includes any direct, indirect, special, accidental, or result damage due to any nature caused by this agreement or use or unable to use this item (including but not limited to the loss of goodwill, stop work, computer, computer Damage compensation caused by faults or faults, or any or all other commercial damage or losses) is responsible for users.
6. Use restrictions
6.1 This project is completely free, and open source is published in GitHub for the learning exchanges of technology for people all over the world. This project does not guarantee that the technology in the project may violate local laws and regulations.
6.2 ** This project is prohibited in violation of local laws and regulations. ** For the user's use of any illegal and illegal acts caused by the use of the project if the user is not allowed to be allowed to the user, it is undertaken by the user. Sexual responsibility.
7. Copyright protection
7.1 Music platform is not easy, please respect the copyright and support genuine.
8. Non -commercial nature
8.1 This project is only used for exploring and research on technical feasibility, and does not accept any business (including but not limited to advertising, etc.) cooperation and donations.
9. Accepting agreement
9.1 If you use this project, it will represent you accept this agreement.
* If the agreement is updated, you will not be notified separately, you can check the open source address.
* The content of this agreement is translated from the Chinese version of the agreement
By: lyswhut
+39 -10
View File
@@ -1,18 +1,47 @@
本项目(软件)基于Apache License 2.0 许可证发行,在使用本软件前,你(使用者)需签署本协议才可继续使用,以下协议是对于 Apache License 2.0 的补充,如有冲突,以以下协议为准。
本项目基于 Apache License 2.0 许可证发行,以下协议是对于 Apache License 2.0 的补充,如有冲突,以以下协议为准。
词语约定:本协议中的“本项目”指洛雪音乐桌面版项目;“使用者”指签署本协议的使用者;“官方音乐平台”指对本项目内置的包括酷我、酷狗、咪咕等音乐源的官方平台统称;“版权数据”指包括但不限于图像、音频、名字等在内的他人拥有所属版权的数据。
词语约定:本协议中的“本软件”指洛雪音乐桌面版项目;“使用者”指签署本协议的使用者;“官方音乐平台”指对本软件内置的包括酷我、酷狗、咪咕等音乐源的官方平台统称;“版权数据”指包括但不限于图像、音频、名字等在内的他人拥有所属版权的数据。
一、数据来源
1、本软件的数据来源原理是从各官方音乐平台的公开服务器中拉取数据,经过对数据简单地筛选与合并后进行展示,因此本软件不对数据的准确性负责。
2、使用本软件的过程中可能会产生版权数据,对于这些版权数据,本软件不拥有它们的所有权,为了避免造成侵权,使用者务必在24小时内清除使用本软件的过程中所产生的版权数据。
3、本软件内的官方音乐平台别名为本软件内对官方音乐平台的一个称呼,不包含恶意,如果官方音乐平台觉得不妥,可联系本软件更改或移除。
4、本软件内使用的部分包括但不限于字体、图片等资源来源于互联网,如果出现侵权可联系本软件移除。
5、由于使用本软件产生的包括由于本协议或由于使用或无法使用本软件而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害(包括但不限于因商誉损失、停工、计算机故障或故障引起的损害赔偿,或任何及所有其他商业损害或损失)由使用者负责。
6、本项目完全免费,且开源发布于 GitHub 面向全世界人用作对技术的学习交流,本软件不对项目内的技术可能存在违反当地法律法规的行为作保证,禁止在违反当地法律法规的情况下使用本软件,对于使用者在明知或不知当地法律法规不允许的情况下使用本软件所造成的任何违法违规行为由使用者承担,本软件不承担由此造成的任何直接、间接、特殊、偶然或结果性责任。
1.1 本项目的各官方平台在线数据来源原理是从其公开服务器中拉取数据(与未登录状态在官方平台APP获取的数据相同),经过对数据简单地筛选与合并后进行展示,因此本项目不对数据的合法性、准确性负责。
1.2 本项目本身没有获取某个音频数据的能力,本项目使用的在线音频数据来源来自软件设置内“音乐来源”设置所选择的“源”返回的在线链接。例如播放某首歌,本项目所做的只是将希望播放的歌曲名字、歌手名字等信息传递给“源”,若“源”返回了一个链接,则本项目将认为这就是该歌曲的音频数据而进行使用,至于这是不是正确的音频数据本项目无法校验其准确性,所以使用本项目的过程中可能会出现希望播放的音频与实际播放的音频不对应或者无法播放的问题。
1.3 本项目的非官方平台数据(例如我的收藏列表)来自使用者本地系统或者使用者连接的同步服务,本项目不对这些数据的合法性、准确性负责。
二、版权数据
2.1 使用本项目的过程中可能会产生版权数据。对于这些版权数据,本项目不拥有它们的所有权。为了避免侵权,使用者务必在**24小时内**清除使用本项目的过程中所产生的版权数据。
三、音乐平台别名
3.1 本项目内的官方音乐平台别名为本项目内对官方音乐平台的一个称呼,不包含恶意。如果官方音乐平台觉得不妥,可联系本项目更改或移除。
四、资源使用
4.1 本项目内使用的部分包括但不限于字体、图片等资源来源于互联网。如果出现侵权可联系本项目移除。
五、免责声明
5.1 由于使用本项目产生的包括由于本协议或由于使用或无法使用本项目而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害(包括但不限于因商誉损失、停工、计算机故障或故障引起的损害赔偿,或任何及所有其他商业损害或损失)由使用者负责。
六、使用限制
6.1 本项目完全免费,且开源发布于 GitHub 面向全世界人用作对技术的学习交流。本项目不对项目内的技术可能存在违反当地法律法规的行为作保证。
6.2 **禁止在违反当地法律法规的情况下使用本项目。** 对于使用者在明知或不知当地法律法规不允许的情况下使用本项目所造成的任何违法违规行为由使用者承担,本项目不承担由此造成的任何直接、间接、特殊、偶然或结果性责任。
七、版权保护
7.1 音乐平台不易,请尊重版权,支持正版。
八、非商业性质
8.1 本项目仅用于对技术可行性的探索及研究,不接受任何商业(包括但不限于广告等)合作及捐赠。
九、接受协议
9.1 若你使用了本项目,将代表你接受本协议。
* 若协议更新,恕不另行通知,可到开源地址查看。
* 本软件的初衷是帮助官方音乐平台简化数据后代为展示,帮助使用者根据歌曲名、艺术家等关键字快速地定位所需内容所在的音乐平台。
* 音乐平台不易,建议到对应音乐平台支持正版资源。
By: 落雪无痕
+7517 -8433
View File
File diff suppressed because it is too large Load Diff
+128 -219
View File
@@ -1,176 +1,80 @@
{
"name": "lx-music-desktop",
"version": "2.0.3",
"version": "2.7.0-beta.2",
"description": "一个免费的音乐查找助手",
"main": "./dist/main.js",
"productName": "lx-music-desktop",
"scripts": {
"pack": "node build-config/pack.js && npm run pack:win:setup:x64",
"pack:win": "node build-config/pack.js && npm run pack:win:setup:x64 && npm run pack:win:setup:x86 && npm run pack:win:setup:arm64 && npm run pack:win:setup:x86_64 && npm run pack:win:7z",
"pack:win:setup:x86_64": "cross-env TARGET=Setup ARCH=x86_64 electron-builder -w=nsis --x64 --ia32 -p never",
"pack:win:setup:x64": "cross-env TARGET=Setup ARCH=x64 electron-builder -w=nsis --x64 -p never",
"pack:win:setup:x86": "cross-env TARGET=Setup ARCH=x86 electron-builder -w=nsis --ia32 -p never",
"pack:win:setup:arm64": "cross-env TARGET=Setup ARCH=arm64 electron-builder -w=nsis --arm64 -p never",
"pack:win:setup:x86_64": "node build-config/build-pack.js target=win arch=x86_64 type=setup",
"pack:win:setup:x64": "node build-config/build-pack.js target=win arch=x64 type=setup",
"pack:win:setup:x86": "node build-config/build-pack.js target=win arch=x86 type=setup",
"pack:win:setup:arm64": "node build-config/build-pack.js target=win arch=arm64 type=setup",
"pack:win:portable": "npm run pack:win:portable:x86_64 && npm run pack:win:portable:x64 && npm run pack:win:portable:x86",
"pack:win:portable:x86_64": "cross-env TARGET=portable ARCH=x86_64 electron-builder -w=portable --x64 --ia32 -p never",
"pack:win:portable:x64": "cross-env TARGET=portable ARCH=x64 electron-builder -w=portable --x64 -p never",
"pack:win:portable:x86": "cross-env TARGET=portable ARCH=x86 electron-builder -w=portable --ia32 -p never",
"pack:win:7z": "npm run pack:win:7z:x64 && npm run pack:win:7z:x86",
"pack:win:7z:x64": "cross-env TARGET=green ARCH=win_x64 electron-builder -w=7z --x64 -p never",
"pack:win:7z:x86": "cross-env TARGET=green ARCH=win_x86 electron-builder -w=7z --ia32 -p never",
"pack:win:7z:arm64": "cross-env TARGET=green ARCH=win_arm64 electron-builder -w=7z --arm64 -p never",
"pack:win:portable:x86_64": "node build-config/build-pack.js target=win arch=x86_64 type=portable",
"pack:win:portable:x64": "node build-config/build-pack.js target=win arch=x64 type=portable",
"pack:win:portable:x86": "node build-config/build-pack.js target=win arch=x86 type=portable",
"pack:win:7z": "npm run pack:win:7z:x64",
"pack:win:7z:x64": "node build-config/build-pack.js target=win arch=x64 type=green",
"pack:win:7z:arm64": "node build-config/build-pack.js target=win arch=arm64 type=green",
"pack:win7:setup:x64": "node build-config/build-pack.js target=win arch=x64 type=win7_setup",
"pack:win7:7z:x64": "node build-config/build-pack.js target=win arch=x64 type=win7_green",
"pack:win7:7z:x86": "node build-config/build-pack.js target=win arch=x86 type=win7_green",
"pack:linux": "node build-config/pack.js && npm run pack:linux:deb && npm run pack:linux:appImage && npm run pack:linux:rpm && npm run pack:linux:pacman",
"pack:linux:appImage": "cross-env ARCH=x64 electron-builder -l=AppImage -p never",
"pack:linux:deb": "npm run pack:linux:deb:x64 && npm run pack:linux:deb:arm64 && npm run pack:linux:deb:armv7l",
"pack:linux:deb:x64": "cross-env ARCH=x64 electron-builder -l=deb --x64 -p never",
"pack:linux:deb:arm64": "cross-env ARCH=arm64 electron-builder -l=deb --arm64 -p never",
"pack:linux:deb:armv7l": "cross-env ARCH=armv7l electron-builder -l=deb --armv7l -p never",
"pack:linux:rpm": "cross-env ARCH=x64 electron-builder -l=rpm --x64 -p never",
"pack:linux:pacman": "cross-env ARCH=x64 electron-builder -l=pacman --x64 -p never",
"pack:linux:appImage": "node build-config/build-pack.js target=linux arch=x64 type=appImage",
"pack:linux:deb": "npm run pack:linux:deb:amd64 && npm run pack:linux:deb:arm64 && npm run pack:linux:deb:armv7l",
"pack:linux:deb:amd64": "node build-config/build-pack.js target=linux arch=x64 type=deb",
"pack:linux:deb:arm64": "node build-config/build-pack.js target=linux arch=arm64 type=deb",
"pack:linux:deb:armv7l": "node build-config/build-pack.js target=linux arch=armv7l type=deb",
"pack:linux:rpm": "node build-config/build-pack.js target=linux arch=x64 type=rpm",
"pack:linux:pacman": "node build-config/build-pack.js target=linux arch=x64 type=pacman",
"pack:mac": "node build-config/pack.js && npm run pack:mac:dmg && npm run pack:mac:dmg:arm64",
"pack:mac:dmg": "cross-env electron-builder -m=dmg -p never",
"pack:mac:dmg:arm64": "cross-env electron-builder -m=dmg --arm64 -p never",
"pack:dir": "node build-config/pack.js && electron-builder --dir",
"pack:mac:dmg": "node build-config/build-pack.js target=mac arch=x64 type=dmg",
"pack:mac:dmg:arm64": "node build-config/build-pack.js target=mac arch=arm64 type=dmg",
"pack:dir": "node build-config/pack.js && node build-config/build-pack.js target=dir",
"publish": "node publish",
"publish:win:setup:x64:always": "cross-env TARGET=Setup ARCH=x64 electron-builder -w=nsis --x64 -p always",
"publish:win:setup:x64": "cross-env TARGET=Setup ARCH=x64 electron-builder -w=nsis --x64 -p always",
"publish:win:setup:x86": "cross-env TARGET=Setup ARCH=x86 electron-builder -w=nsis --ia32 -p onTagOrDraft",
"publish:win:setup:arm64": "cross-env TARGET=Setup ARCH=arm64 electron-builder -w=nsis --arm64 -p onTagOrDraft",
"publish:win:setup:x86_64": "cross-env TARGET=Setup ARCH=x86_64 electron-builder -w=nsis --x64 --ia32 -p onTagOrDraft",
"publish:win:setup:x64": "node build-config/build-pack.js target=win arch=x64 type=setup publish=always",
"publish:win:setup:x86": "node build-config/build-pack.js target=win arch=x86 type=setup publish=always",
"publish:win:setup:arm64": "node build-config/build-pack.js target=win arch=arm64 type=setup publish=always",
"publish:win:setup:x86_64": "node build-config/build-pack.js target=win arch=x86_64 type=setup publish=always",
"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:x64": "cross-env TARGET=green ARCH=win_x64 electron-builder -w=7z --x64 -p onTagOrDraft",
"publish:win:7z:x86": "cross-env TARGET=green ARCH=win_x86 electron-builder -w=7z --ia32 -p onTagOrDraft",
"publish:win:7z:arm64": "cross-env TARGET=green ARCH=win_arm64 electron-builder -w=7z --arm64 -p onTagOrDraft",
"publish:mac:dmg:always": "electron-builder -m=dmg -p always",
"publish:mac:dmg": "electron-builder -m=dmg -p onTagOrDraft",
"publish:mac:dmg:arm64": "electron-builder -m=dmg --arm64 -p onTagOrDraft",
"publish:linux:deb:x64:always": "cross-env ARCH=x64 electron-builder -l=deb --x64 -p always",
"publish:linux:deb:x64": "cross-env ARCH=x64 electron-builder -l=deb --x64 -p onTagOrDraft",
"publish:linux:deb:arm64": "cross-env ARCH=arm64 electron-builder -l=deb --arm64 -p onTagOrDraft",
"publish:linux:deb:armv7l": "cross-env ARCH=armv7l electron-builder -l=deb --armv7l -p onTagOrDraft",
"publish:linux:appImage": "cross-env ARCH=x64 electron-builder -l=AppImage -p onTagOrDraft",
"publish:linux:rpm": "cross-env ARCH=x64 electron-builder -l=rpm --x64 -p onTagOrDraft",
"publish:linux:pacman": "cross-env ARCH=x64 electron-builder -l=pacman --x64 -p onTagOrDraft",
"publish:win:portable:x86_64": "node build-config/build-pack.js target=win arch=x86_64 type=portable publish=always",
"publish:win:portable:x64": "node build-config/build-pack.js target=win arch=x64 type=portable publish=always",
"publish:win:portable:x86": "node build-config/build-pack.js target=win arch=x86 type=portable publish=always",
"publish:win:7z:x64": "node build-config/build-pack.js target=win arch=x64 type=green publish=always",
"publish:win:7z:arm64": "node build-config/build-pack.js target=win arch=arm64 type=green publish=always",
"publish:win7:setup:x64": "node build-config/build-pack.js target=win arch=x64 type=win7_setup publish=always",
"publish:win7:7z:x64": "node build-config/build-pack.js target=win arch=x64 type=win7_green publish=always",
"publish:win7:7z:x86": "node build-config/build-pack.js target=win arch=x86 type=win7_green publish=always",
"publish:mac:dmg": "node build-config/build-pack.js target=mac arch=x64 type=dmg publish=always",
"publish:mac:dmg:arm64": "node build-config/build-pack.js target=mac arch=arm64 type=dmg publish=always",
"publish:linux:deb:amd64": "node build-config/build-pack.js target=linux arch=x64 type=deb publish=always",
"publish:linux:deb:arm64": "node build-config/build-pack.js target=linux arch=arm64 type=deb publish=always",
"publish:linux:deb:armv7l": "node build-config/build-pack.js target=linux arch=armv7l type=deb publish=always",
"publish:linux:appImage": "node build-config/build-pack.js target=linux arch=x64 type=appImage publish=always",
"publish:linux:rpm": "node build-config/build-pack.js target=linux arch=x64 type=rpm publish=always",
"publish:linux:pacman": "node build-config/build-pack.js target=linux arch=x64 type=pacman publish=always",
"dev": "cross-env NODE_OPTIONS=--max-http-header-size=200000 node build-config/runner-dev.js",
"clean:electron": "rimraf dist",
"clean": "rimraf dist && rimraf build",
"build:theme": "node src/common/theme/createThemes.js",
"build:src": "node build-config/pack.js",
"build": "node build-config/pack.js",
"build:main": "cross-env NODE_ENV=production webpack --config build-config/main/webpack.config.prod.js --progress",
"build:renderer": "cross-env NODE_ENV=production webpack --config build-config/renderer/webpack.config.prod.js --progress",
"build:renderer-lyric": "cross-env NODE_ENV=production webpack --config build-config/renderer-lyric/webpack.config.prod.js --progress",
"build:renderer-scripts": "cross-env NODE_ENV=production webpack --config build-config/renderer-scripts/webpack.config.prod.js --progress",
"build": "npm run clean:electron && npm run build:main && npm run build:renderer && npm run build:renderer-lyric && npm run build:renderer-scripts",
"lint": "eslint --ext .ts,.js,.vue -f node_modules/eslint-formatter-friendly src",
"lint:fix": "eslint --ext .ts,.js,.vue -f node_modules/eslint-formatter-friendly --fix src",
"postinstall": "electron-builder install-app-deps",
"lint:fix": "eslint --ext .js,.vue -f node_modules/eslint-formatter-friendly --fix src",
"dp": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://127.0.0.1:1081 npm run pack",
"up": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://127.0.0.1:1081 npm i"
"dp": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://127.0.0.1:2081 npm run pack",
"up": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://127.0.0.1:2081 npm i"
},
"browserslist": [
"Electron 19.1.0"
"Electron 22.3.0"
],
"engines": {
"node": ">= 16",
"node": ">= 18",
"npm": ">=8.5.2"
},
"build": {
"appId": "cn.toside.music.desktop",
"beforePack": "./build-config/build-before-pack.js",
"afterPack": "./build-config/build-after-pack.js",
"protocols": {
"name": "lx-music-protocol",
"schemes": [
"lxmusic"
]
},
"directories": {
"buildResources": "./resources",
"output": "./build"
},
"files": [
"!node_modules/**/*",
"node_modules/font-list",
"node_modules/better-sqlite3/lib",
"node_modules/better-sqlite3/package.json",
"node_modules/better-sqlite3/build/Release/better_sqlite3.node",
"node_modules/node-gyp-build",
"node_modules/bufferutil",
"node_modules/utf-8-validate",
"dist/**/*"
],
"asar": {
"smartUnpack": false
},
"extraResources": [
"./licenses"
],
"win": {
"icon": "./resources/icons/icon.ico",
"legalTrademarks": "lyswhut",
"artifactName": "${productName} v${version} ${env.ARCH} ${env.TARGET}.${ext}"
},
"mac": {
"icon": "./resources/icons/icon.icns",
"category": "public.app-category.music"
},
"linux": {
"maintainer": "lyswhut <lyswhut@qq.com>",
"artifactName": "${productName} v${version} ${env.ARCH}.${ext}",
"icon": "./resources/icons",
"category": "Utility;AudioVideo;Audio;Player;Music;",
"desktop": {
"Name": "LX Music",
"Name[zh_CN]": "LX Music",
"Name[zh_TW]": "LX Music",
"Encoding": "UTF-8",
"MimeType": "x-scheme-handler/lxmusic",
"StartupNotify": "false"
}
},
"nsis": {
"oneClick": false,
"language": "2052",
"allowToChangeInstallationDirectory": true,
"differentialPackage": true,
"license": "./licenses/license.rtf",
"shortcutName": "LX Music"
},
"dmg": {
"window": {
"width": 600,
"height": 400
},
"contents": [
{
"x": 106,
"y": 252,
"name": "LX Music"
},
{
"x": 490,
"y": 252,
"type": "link",
"path": "/Applications"
}
],
"title": "洛雪音乐助手 v${version}"
},
"appImage": {
"license": "./licenses/license_zh.txt",
"category": "Utility;AudioVideo;Audio;Player;Music;"
},
"publish": [
{
"provider": "github",
"owner": "lyswhut",
"repo": "lx-music-desktop"
}
]
},
"macLanguagesInfoPlistStrings": {
"en": {
"CFBundleDisplayName": "LX Music",
@@ -194,115 +98,120 @@
"electron-app",
"vuejs3"
],
"author": "lyswhut <lyswhut@qq.com>",
"author": {
"name": "lyswhut",
"email": "lyswhut@qq.com"
},
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/lyswhut/lx-music-desktop/issues"
},
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
"devDependencies": {
"@babel/core": "^7.20.12",
"@babel/eslint-parser": "^7.19.1",
"@babel/core": "^7.24.3",
"@babel/eslint-parser": "^7.24.1",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-modules-umd": "^7.18.6",
"@babel/plugin-transform-runtime": "^7.19.6",
"@babel/preset-env": "^7.20.2",
"@babel/preset-typescript": "^7.18.6",
"@types/better-sqlite3": "^7.6.3",
"@types/needle": "^3.2.0",
"@types/tunnel": "^0.0.3",
"@typescript-eslint/eslint-plugin": "^5.48.0",
"@typescript-eslint/parser": "^5.48.0",
"@volar/vue-language-plugin-pug": "^1.0.22",
"babel-loader": "^9.1.2",
"browserslist": "^4.21.4",
"@babel/plugin-transform-modules-umd": "^7.24.1",
"@babel/plugin-transform-runtime": "^7.24.3",
"@babel/preset-env": "^7.24.3",
"@babel/preset-typescript": "^7.24.1",
"@tsconfig/recommended": "^1.0.3",
"@types/better-sqlite3": "^7.6.9",
"@types/needle": "^3.3.0",
"@types/tunnel": "^0.0.7",
"@types/ws": "8.5.4",
"@volar/vue-language-plugin-pug": "^1.6.5",
"@vue/language-plugin-pug": "^2.0.7",
"babel-loader": "^9.1.3",
"browserslist": "^4.23.0",
"chalk": "^4.1.2",
"changelog-parser": "^3.0.1",
"copy-webpack-plugin": "^11.0.0",
"core-js": "^3.27.1",
"copy-webpack-plugin": "^12.0.2",
"core-js": "^3.36.1",
"cross-env": "^7.0.3",
"css-loader": "^6.7.3",
"css-minimizer-webpack-plugin": "^4.2.2",
"css-loader": "^6.10.0",
"css-minimizer-webpack-plugin": "^6.0.0",
"del": "^6.1.1",
"electron": "^19.1.9",
"electron-builder": "^24.0.0-alpha.9",
"electron": "^27.3.7",
"electron-builder": "^24.13.3",
"electron-debug": "^3.2.0",
"electron-devtools-installer": "^3.2.0",
"electron-to-chromium": "^1.4.284",
"electron-updater": "^6.0.0-alpha.7",
"eslint": "^8.31.0",
"eslint-config-standard": "^17.0.0",
"eslint-config-standard-with-typescript": "^26.0.0",
"electron-to-chromium": "^1.4.715",
"electron-updater": "^6.1.8",
"eslint": "^8.57.0",
"eslint-config-standard": "^17.1.0",
"eslint-config-standard-with-typescript": "^43.0.1",
"eslint-formatter-friendly": "github:lyswhut/eslint-friendly-formatter#2170d1320e2fad13615a9dcf229669f0bb473a53",
"eslint-plugin-html": "^7.1.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-n": "^15.6.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-vue": "^9.8.0",
"eslint-webpack-plugin": "^3.2.0",
"html-webpack-plugin": "^5.5.0",
"less": "^4.1.3",
"less-loader": "^11.1.0",
"mini-css-extract-plugin": "^2.7.2",
"eslint-plugin-html": "^8.0.0",
"eslint-plugin-vue": "^9.23.0",
"eslint-plugin-vue-pug": "^0.6.2",
"eslint-webpack-plugin": "^4.1.0",
"html-webpack-plugin": "^5.6.0",
"less": "^4.2.0",
"less-loader": "^12.2.0",
"mini-css-extract-plugin": "^2.8.1",
"node-loader": "^2.0.0",
"postcss": "^8.4.21",
"postcss-loader": "^7.0.2",
"postcss-pxtorem": "^6.0.0",
"postcss": "^8.4.38",
"postcss-loader": "^8.1.1",
"postcss-pxtorem": "^6.1.0",
"pug": "^3.0.2",
"pug-plain-loader": "^1.1.0",
"rimraf": "^3.0.2",
"rimraf": "^5.0.5",
"spinnies": "github:lyswhut/spinnies#233305c58694aa3b053e3ab9af9049993f918b9d",
"svg-sprite-loader": "^6.0.11",
"svg-transform-loader": "^2.0.13",
"svgo-loader": "^3.0.3",
"terser": "^5.16.1",
"terser-webpack-plugin": "^5.3.6",
"ts-loader": "^9.4.2",
"typescript": "^4.9.4",
"vue-eslint-parser": "^9.1.0",
"vue-loader": "^17.0.1",
"vue-template-compiler": "^2.7.14",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1",
"svgo-loader": "^4.0.0",
"terser": "^5.29.2",
"terser-webpack-plugin": "^5.3.10",
"ts-loader": "^9.5.1",
"typescript": "^5.4.3",
"vue-eslint-parser": "^9.4.2",
"vue-loader": "^17.4.2",
"vue-template-compiler": "^2.7.16",
"webpack": "^5.91.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.0.4",
"webpack-hot-middleware": "github:lyswhut/webpack-hot-middleware#329c4375134b89d39da23a56a94db651247c74a1",
"webpack-merge": "^5.8.0"
"webpack-merge": "^5.10.0"
},
"dependencies": {
"@simonwep/pickr": "^1.8.2",
"better-sqlite3": "^8.0.1",
"bufferutil": "^4.0.7",
"comlink": "^4.3.1",
"crypto-js": "^4.1.1",
"electron-log": "^4.4.8",
"electron-store": "^8.1.0",
"font-list": "^1.4.5",
"http-terminator": "^3.2.0",
"@simonwep/pickr": "^1.9.0",
"better-sqlite3": "^9.4.3",
"bufferutil": "^4.0.8",
"comlink": "~4.3.1",
"crypto-js": "^4.2.0",
"electron-log": "^5.1.2",
"font-list": "^1.5.1",
"iconv-lite": "^0.6.3",
"image-size": "^1.0.2",
"jschardet": "^3.0.0",
"koa": "^2.14.1",
"long": "^5.2.1",
"music-metadata": "^8.1.0",
"image-size": "^1.1.0",
"jschardet": "^3.1.0",
"long": "^5.2.3",
"message2call": "^0.1.3",
"music-metadata": "^8.1.4",
"needle": "github:lyswhut/needle#93299ac841b7e9a9f82ca7279b88aaaeda404060",
"node-id3": "^0.2.5",
"socket.io": "^4.5.4",
"sortablejs": "^1.15.0",
"node-id3": "^0.2.6",
"sortablejs": "^1.15.2",
"tunnel": "^0.0.6",
"utf-8-validate": "^6.0.0",
"vue": "^3.2.45",
"vue-router": "^4.1.6"
"utf-8-validate": "^6.0.3",
"vue": "~3.3.13",
"vue-router": "^4.3.0",
"ws": "^8.16.0"
},
"overrides": {
"got": "^11",
"json5": "latest",
"minimatch": "latest",
"semver": "latest",
"svg-transform-loader": {
"postcss": "latest"
},
"svg-sprite-loader": {
"postcss": "latest"
},
"svg-baker": {
"postcss": "latest"
}
},
"http-cache-semantics": "latest"
}
}
+26 -4
View File
@@ -1,7 +1,29 @@
### 新增
- 主题编辑器添加“深色字体”选项,启用后将减少字体颜色梯度,各类字体(正文、标签字体等)颜色将更接近,这有助于解决创建全透明主题时可能出现的字体配色问题(#1799)
- 新增在线自定义源导入功能,允许通过http/https链接导入自定义源
### 优化
- 主题设置默认折叠其他主题以优化进入设置界面时的性能
- 不再丢弃kg源逐行歌词(@helloplhm-qwq)
- 支持kw源排行榜显示大小(revert @Folltoshe #1460)
- 托盘菜单添加多语言支持(#1802)
- 优化本地歌曲换源匹配机制
### 修复
- 修复备份文件导入指引无法识别v2配置的问题
- 修复从搜索界面进入歌单详情后,若启用强迫症设置的清空功能会导致意外清空搜索框、搜索列表的问题
- 就放桌面歌词在启用卡拉OK歌词后字体边缘可能被截断的问题
- 修复桌面歌词启用歌词缩放后的阴影显示问题
- 修复某些情况下歌曲加载时间过长时不会自动跳到下一首的问题
- 修复mg歌词在某些情况下获取失败的问题(#1783)
- 修复mg歌单搜索(@helloplhm-qwq)
- 修复kg最新评论无法获取的问题(@helloplhm-qwq)
- 修复更新超时弹窗在非更新阶段意外弹出的问题(#1797)
- 修复网络代理设置没有对自定义源的网络请求生效的问题(#1814)
### 移除
- 移除未使用的网络代理设置用户名、密码设置,实际上在 v1.20.0 起这两个设置就没有在被内部使用
### 其他
- 更新 electron 到 v27.3.7
+1 -1
View File
File diff suppressed because one or more lines are too long
+15
View File
@@ -0,0 +1,15 @@
/* eslint-env node */
const { base, typescript } = require('../../.eslintrc.base.cjs')
module.exports = {
root: true,
...base,
overrides: [
{
...typescript,
parserOptions: {
project: './tsconfig.json',
},
},
],
}
+9 -2
View File
@@ -1,5 +1,9 @@
export const URL_SCHEME_RXP = /^lxmusic:\/\//
export const SPLIT_CHAR = {
DISLIKE_NAME: '@',
DISLIKE_NAME_ALIAS: '#',
} as const
export const STORE_NAMES = {
APP_SETTINGS: 'config_v2',
@@ -10,6 +14,7 @@ export const STORE_NAMES = {
LRC_RAW: 'lyrics',
LRC_EDITED: 'lyrics_edited',
THEME: 'theme',
SOUND_EFFECT: 'sound_effect',
} as const
export const APP_EVENT_NAMES = {
@@ -38,6 +43,8 @@ export const DATA_KEYS = {
leaderboardSetting: 'leaderboardSetting',
songListSetting: 'songListSetting',
searchSetting: 'searchSetting',
lastStartInfo: 'lastStartInfo',
} as const
export const DEFAULT_SETTING = {
@@ -47,8 +54,8 @@ export const DEFAULT_SETTING = {
},
songList: {
source: 'kg',
sortId: '5',
source: 'kw',
sortId: 'new',
tagId: '',
},
+74
View File
@@ -0,0 +1,74 @@
export const ENV_PARAMS = [
'PORT',
'BIND_IP',
'CONFIG_PATH',
'LOG_PATH',
'DATA_PATH',
'PROXY_HEADER',
'MAX_SNAPSHOT_NUM',
'LIST_ADD_MUSIC_LOCATION_TYPE',
'LX_USER_',
] as const
export const LIST_IDS = {
DEFAULT: 'default',
LOVE: 'love',
TEMP: 'temp',
DOWNLOAD: 'download',
PLAY_LATER: null,
} as const
export const SYNC_CODE = {
helloMsg: 'Hello~::^-^::~v4~',
idPrefix: 'OjppZDo6',
authMsg: 'lx-music auth::',
msgAuthFailed: 'Auth failed',
msgBlockedIp: 'Blocked IP',
msgConnect: 'lx-music connect',
authFailed: 'Auth failed',
missingAuthCode: 'Missing auth code',
getServiceIdFailed: 'Get service id failed',
connectServiceFailed: 'Connect service failed',
connecting: 'Connecting...',
unknownServiceAddress: 'Unknown service address',
} as const
export const SYNC_CLOSE_CODE = {
normal: 1000,
failed: 4100,
} as const
export const TRANS_MODE: Readonly<Record<LX.Sync.List.SyncMode, LX.Sync.List.SyncMode>> = {
merge_local_remote: 'merge_remote_local',
merge_remote_local: 'merge_local_remote',
overwrite_local_remote: 'overwrite_remote_local',
overwrite_remote_local: 'overwrite_local_remote',
overwrite_local_remote_full: 'overwrite_remote_local_full',
overwrite_remote_local_full: 'overwrite_local_remote_full',
cancel: 'cancel',
} as const
export const File = {
serverDataPath: 'sync/server',
clientDataPath: 'sync/client',
serverInfoJSON: 'serverInfo.json',
userDir: 'users',
userDevicesJSON: 'devices.json',
listDir: 'list',
listSnapshotDir: 'snapshot',
listSnapshotInfoJSON: 'snapshotInfo.json',
dislikeDir: 'dislike',
dislikeSnapshotDir: 'snapshot',
dislikeSnapshotInfoJSON: 'snapshotInfo.json',
syncAuthKeysJSON: 'syncAuthKey.json',
} as const
export const FeaturesList = [
'list',
'dislike',
] as const
+15 -15
View File
@@ -29,21 +29,21 @@ const local: LX.HotKeyConfig = {
const global: LX.HotKeyConfig = {
enable: false,
keys: {
MediaPlayPause: {
type: HOTKEY_PLAYER.toggle_play.type,
name: '',
action: HOTKEY_PLAYER.toggle_play.action,
},
MediaPreviousTrack: {
type: HOTKEY_PLAYER.prev.type,
name: '',
action: HOTKEY_PLAYER.prev.action,
},
MediaNextTrack: {
type: HOTKEY_PLAYER.next.type,
name: '',
action: HOTKEY_PLAYER.next.action,
},
// MediaPlayPause: {
// type: HOTKEY_PLAYER.toggle_play.type,
// name: '',
// action: HOTKEY_PLAYER.toggle_play.action,
// },
// MediaPreviousTrack: {
// type: HOTKEY_PLAYER.prev.type,
// name: '',
// action: HOTKEY_PLAYER.prev.action,
// },
// MediaNextTrack: {
// type: HOTKEY_PLAYER.next.type,
// name: '',
// action: HOTKEY_PLAYER.next.action,
// },
'mod+alt+f5': {
type: HOTKEY_PLAYER.toggle_play.type,
name: HOTKEY_PLAYER.toggle_play.name,
+49 -16
View File
@@ -1,8 +1,11 @@
import { join } from 'path'
import { homedir } from 'os'
import path from 'node:path'
import os from 'node:os'
const isMac = process.platform == 'darwin'
const isWin = process.platform == 'win32'
const defaultSetting: LX.AppSetting = {
version: '2.0.0',
version: '2.1.0',
'common.windowSizeId': 3,
'common.fontSize': 16,
@@ -14,26 +17,49 @@ const defaultSetting: LX.AppSetting = {
'common.isShowAnimation': true,
'common.randomAnimate': true,
'common.isAgreePact': false,
'common.controlBtnPosition': process.platform === 'darwin' ? 'left' : 'right',
'common.controlBtnPosition': isMac ? 'left' : 'right',
'common.playBarProgressStyle': 'mini',
'common.tryAutoUpdate': true,
'common.showChangeLog': true,
'player.startupAutoPlay': false,
'player.togglePlayMethod': 'listLoop',
'player.highQuality': false,
'player.isShowTaskProgess': true,
'player.volume': 1,
'player.powerSaveBlocker': true,
'player.isMute': false,
'player.playbackRate': 1,
'player.preservesPitch': true,
'player.mediaDeviceId': 'default',
'player.isMediaDeviceRemovedStopPlay': false,
'player.isShowLyricTranslation': false,
'player.isShowLyricRoma': false,
'player.isS2t': false,
'player.isPlayLxlrc': false,
'player.isPlayLxlrc': isWin,
'player.isSavePlayTime': false,
'player.audioVisualization': false,
'player.waitPlayEndStop': true,
'player.waitPlayEndStopTime': '',
'player.autoSkipOnError': true,
'player.isAutoCleanPlayedList': false,
'player.soundEffect.convolution.fileName': '',
'player.soundEffect.convolution.mainGain': 10,
'player.soundEffect.convolution.sendGain': 0,
'player.soundEffect.biquadFilter.hz31': 0,
'player.soundEffect.biquadFilter.hz62': 0,
'player.soundEffect.biquadFilter.hz125': 0,
'player.soundEffect.biquadFilter.hz250': 0,
'player.soundEffect.biquadFilter.hz500': 0,
'player.soundEffect.biquadFilter.hz1000': 0,
'player.soundEffect.biquadFilter.hz2000': 0,
'player.soundEffect.biquadFilter.hz4000': 0,
'player.soundEffect.biquadFilter.hz8000': 0,
'player.soundEffect.biquadFilter.hz16000': 0,
'player.soundEffect.panner.enable': false,
'player.soundEffect.panner.soundR': 5,
'player.soundEffect.panner.speed': 25,
'player.soundEffect.pitchShifter.playbackRate': 1,
'playDetail.isZoomActiveLrc': false,
'playDetail.isShowLyricProgressSetting': false,
@@ -46,11 +72,12 @@ const defaultSetting: LX.AppSetting = {
'desktopLyric.isAlwaysOnTopLoop': false,
'desktopLyric.isShowTaskbar': false,
'desktopLyric.audioVisualization': false,
'desktopLyric.fullscreenHide': true,
'desktopLyric.width': 450,
'desktopLyric.height': 300,
'desktopLyric.x': null,
'desktopLyric.y': null,
'desktopLyric.isLockScreen': true,
'desktopLyric.isLockScreen': isWin,
'desktopLyric.isDelayScroll': true,
'desktopLyric.scrollAlign': 'center',
'desktopLyric.isHoverHide': false,
@@ -58,14 +85,17 @@ const defaultSetting: LX.AppSetting = {
'desktopLyric.style.align': 'center',
'desktopLyric.style.font': '',
'desktopLyric.style.fontSize': 20,
'desktopLyric.style.lineGap': 14,
'desktopLyric.style.lineGap': 15,
'desktopLyric.style.lyricUnplayColor': 'rgba(255, 255, 255, 1)',
'desktopLyric.style.lyricPlayedColor': 'rgba(7, 197, 86, 1)',
'desktopLyric.style.lyricShadowColor': 'rgba(0, 0, 0, 0.15)',
'desktopLyric.style.lyricShadowColor': 'rgba(0, 0, 0, 0.18)',
// 'desktopLyric.style.fontWeight': false,
'desktopLyric.style.opacity': 95,
'desktopLyric.style.ellipsis': false,
'desktopLyric.style.isZoomActiveLrc': true,
'desktopLyric.style.isZoomActiveLrc': false,
'desktopLyric.style.isFontWeightFont': true,
'desktopLyric.style.isFontWeightLine': true,
'desktopLyric.style.isFontWeightExtended': true,
'list.isClickPlayList': false,
'list.isShowSource': true,
@@ -74,7 +104,7 @@ const defaultSetting: LX.AppSetting = {
'list.actionButtonsVisible': false,
'download.enable': false,
'download.savePath': join(homedir(), 'Desktop'),
'download.savePath': path.join(os.homedir(), 'Desktop'),
'download.fileName': '歌名 - 歌手',
'download.maxDownloadNum': 3,
'download.skipExistFile': true,
@@ -84,6 +114,8 @@ const defaultSetting: LX.AppSetting = {
'download.lrcFormat': 'utf8',
'download.isEmbedPic': true,
'download.isEmbedLyric': false,
'download.isEmbedLyricT': false,
'download.isEmbedLyricR': false,
'download.isUseOtherSource': false,
'search.isShowHotSearch': false,
@@ -93,18 +125,19 @@ const defaultSetting: LX.AppSetting = {
'network.proxy.enable': false,
'network.proxy.host': '',
'network.proxy.port': '',
'network.proxy.username': '',
'network.proxy.password': '',
'tray.enable': false,
// 'tray.isToTray': false,
'tray.themeId': 0,
'sync.mode': 'server',
'sync.enable': false,
'sync.port': '23332',
'sync.server.port': '23332',
'sync.server.maxSsnapshotNum': 5,
'sync.client.host': '',
'theme.id': 'blue_plus',
// 'theme.id': 'green',
// 'theme.id': 'blue_plus',
'theme.id': 'green',
'theme.lightId': 'green',
'theme.darkId': 'black',
@@ -117,7 +150,7 @@ const defaultSetting: LX.AppSetting = {
// 使用新年皮肤
if (new Date().getMonth() < 2) {
defaultSetting['theme.id'] = 'happy_new_year'
defaultSetting['desktopLyric.style.lyricPlayedColor'] = 'rgba(255, 18, 34, 1)'
defaultSetting['desktopLyric.style.lyricPlayedColor'] = 'rgba(255, 57, 71, 1)'
}
+1 -1
View File
@@ -6,7 +6,7 @@ const ignoreErrorMessage = [
]
process.on('uncaughtException', err => {
if (ignoreErrorMessage.includes(err.message)) return
if (ignoreErrorMessage.includes(err?.message)) return
console.error('An uncaught error occurred!')
console.error(err)
log.error(err)
+15
View File
@@ -66,6 +66,21 @@ const hotKey = {
action: 'volume_mute',
type: '',
},
music_love: {
name: 'music_love',
action: 'music_love',
type: '',
},
music_unlove: {
name: 'music_unlove',
action: 'music_unlove',
type: '',
},
music_dislike: {
name: 'music_dislike',
action: 'music_dislike',
type: '',
},
},
desktop_lyric: {
toggle_visible: {
+19
View File
@@ -36,6 +36,12 @@ const modules = {
list_music_check_exist: 'list_music_check_exist',
list_music_get_list_ids: 'list_music_get_list_ids',
},
dislike: {
get_dislike_music_infos: 'get_dislike_music_infos',
add_dislike_music_infos: 'add_dislike_music_infos',
overwrite_dislike_music_infos: 'overwrite_dislike_music_infos',
clear_dislike_music_infos: 'clear_dislike_music_infos',
},
winMain: {
focus: 'focus',
close: 'close',
@@ -49,12 +55,15 @@ const modules = {
show_save_dialog: 'show_save_dialog',
show_select_dialog: 'show_select_dialog',
show_dialog: 'show_dialog',
open_dir_in_explorer: 'open_dir_in_explorer',
open_dev_tools: 'open_dev_tools',
set_power_save_blocker: 'set_power_save_blocker',
progress: 'progress',
change_tray: 'change_tray',
quit_update: 'quit_update',
update_check: 'update_check',
update_download_update: 'update_download_update',
update_available: 'update_available',
update_error: 'update_error',
update_progress: 'update_progress',
@@ -72,6 +81,7 @@ const modules = {
// lang_s2t: 'lang_s2t',
handle_kw_decode_lyric: 'handle_kw_decode_lyric',
handle_tx_decode_lyric: 'handle_tx_decode_lyric',
get_lyric_info: 'get_lyric_info',
set_lyric_info: 'set_lyric_info',
set_config: 'set_config',
@@ -88,6 +98,12 @@ const modules = {
get_other_source_count: 'get_other_source_count',
get_data: 'get_data',
save_data: 'save_data',
get_sound_effect_eq_preset: 'get_sound_effect_eq_preset',
save_sound_effect_eq_preset: 'save_sound_effect_eq_preset',
get_sound_effect_convolution_preset: 'get_sound_effect_convolution_preset',
save_sound_effect_convolution_preset: 'save_sound_effect_convolution_preset',
// get_sound_effect_pitch_shifter_preset: 'get_sound_effect_pitch_shifter_preset',
// save_sound_effect_pitch_shifter_preset: 'save_sound_effect_pitch_shifter_preset',
get_hot_key: 'get_hot_key',
import_user_api: 'import_user_api',
@@ -119,6 +135,8 @@ const modules = {
get_music_url_count: 'get_music_url_count',
sync_action: 'sync_action',
sync_get_server_devices: 'sync_get_server_devices',
sync_remove_server_device: 'sync_remove_server_device',
process_new_desktop_lyric_client: 'process_new_desktop_lyric_client',
@@ -172,6 +190,7 @@ for (const moduleName of Object.keys(modules) as Array<keyof typeof modules>) {
export const CMMON_EVENT_NAME = modules.common
export const PLAYER_EVENT_NAME = modules.player
export const DISLIKE_EVENT_NAME = modules.dislike
export const WIN_MAIN_RENDERER_EVENT_NAME = modules.winMain
export const WIN_LYRIC_RENDERER_EVENT_NAME = modules.winLyric
export const HOTKEY_RENDERER_EVENT_NAME = modules.hotKey
+2 -2
View File
@@ -30,7 +30,7 @@ export function mainHandle<V>(name: string, listener: LX.IpcMainInvokeEventListe
export function mainHandle<T, V>(name: string, listener: LX.IpcMainInvokeEventListenerParamsValue<T, V>): void
export function mainHandle<T, V>(name: string, listener: LX.IpcMainInvokeEventListenerParamsValue<T, V>): void {
ipcMain.handle(name, async(event, params) => {
return await listener({ event, params })
return listener({ event, params })
})
}
@@ -40,7 +40,7 @@ export function mainHandleOnce<V>(name: string, listener: LX.IpcMainInvokeEventL
export function mainHandleOnce<T, V>(name: string, listener: LX.IpcMainInvokeEventListenerParamsValue<T, V>): void
export function mainHandleOnce<T, V>(name: string, listener: LX.IpcMainInvokeEventListenerParamsValue<T, V>): void {
ipcMain.handleOnce(name, async(event, params) => {
return await listener({ event, params })
return listener({ event, params })
})
}
export const mainHandleRemove = (name: string) => {
+1 -1
View File
@@ -17,7 +17,7 @@ export async function rendererInvoke<V>(name: string): Promise<V>
export async function rendererInvoke<T>(name: string, params: T): Promise<void>
export async function rendererInvoke<T, V>(name: string, params: T): Promise<V>
export async function rendererInvoke <T, V>(name: string, params?: T): Promise<V> {
return await ipcRenderer.invoke(name, params)
return ipcRenderer.invoke(name, params)
}
export function rendererOn(name: string, listener: LX.IpcRendererEventListener): void
+15
View File
@@ -9,6 +9,7 @@ const defaultThemes = [
id: 'green',
name: '绿意盎然',
isDark: false,
isDarkFont: false,
config: {
primary: 'rgb(77, 175, 124)',
font: 'rgb(33, 33, 33)',
@@ -32,6 +33,7 @@ const defaultThemes = [
id: 'blue',
name: '蓝田生玉',
isDark: false,
isDarkFont: false,
config: {
primary: 'rgb(52, 152, 219)',
font: 'rgb(33, 33, 33)',
@@ -55,6 +57,7 @@ const defaultThemes = [
id: 'blue_plus',
name: '蛋雅深蓝',
isDark: false,
isDarkFont: false,
config: {
primary: 'rgb(77, 131, 175)',
font: 'rgb(33, 33, 33)',
@@ -78,6 +81,7 @@ const defaultThemes = [
id: 'orange',
name: '橙黄橘绿',
isDark: false,
isDarkFont: false,
config: {
primary: 'rgb(245, 171, 53)',
font: 'rgb(33, 33, 33)',
@@ -101,6 +105,7 @@ const defaultThemes = [
id: 'red',
name: '热情似火',
isDark: false,
isDarkFont: false,
config: {
primary: 'rgb(214, 69, 65)',
font: 'rgb(33, 33, 33)',
@@ -124,6 +129,7 @@ const defaultThemes = [
id: 'pink',
name: '粉装玉琢',
isDark: false,
isDarkFont: false,
config: {
primary: 'rgb(241, 130, 141)',
font: 'rgb(33, 33, 33)',
@@ -147,6 +153,7 @@ const defaultThemes = [
id: 'purple',
name: '重斤球紫',
isDark: false,
isDarkFont: false,
config: {
primary: 'rgb(155, 89, 182)',
font: 'rgb(33, 33, 33)',
@@ -170,6 +177,7 @@ const defaultThemes = [
id: 'grey',
name: '灰常美丽',
isDark: false,
isDarkFont: false,
config: {
primary: 'rgb(108, 122, 137)',
font: 'rgb(33, 33, 33)',
@@ -193,6 +201,7 @@ const defaultThemes = [
id: 'ming',
name: '青出于黑',
isDark: false,
isDarkFont: false,
config: {
primary: 'rgb(51, 110, 123)',
font: 'rgb(33, 33, 33)',
@@ -216,6 +225,7 @@ const defaultThemes = [
id: 'blue2',
name: '清热板蓝',
isDark: false,
isDarkFont: false,
config: {
primary: 'rgb(79, 98, 208)',
font: 'rgb(33, 33, 33)',
@@ -239,6 +249,7 @@ const defaultThemes = [
id: 'black',
name: '黑灯瞎火',
isDark: true,
isDarkFont: false,
config: {
primary: 'rgb(150, 150, 150)',
font: 'rgb(229, 229, 229)',
@@ -262,6 +273,7 @@ const defaultThemes = [
id: 'mid_autumn',
name: '月里嫦娥',
isDark: false,
isDarkFont: false,
config: {
primary: 'rgb(74, 55, 82)',
font: 'rgb(33, 33, 33)',
@@ -286,6 +298,7 @@ const defaultThemes = [
id: 'naruto',
name: '木叶之村',
isDark: false,
isDarkFont: false,
config: {
primary: 'rgb(87, 144, 167)',
font: 'rgb(33, 33, 33)',
@@ -309,6 +322,7 @@ const defaultThemes = [
id: 'china_ink',
name: '近墨者黑',
isDark: false,
isDarkFont: false,
config: {
primary: 'rgba(47, 47, 47, 1)',
font: 'rgb(33, 33, 33)',
@@ -333,6 +347,7 @@ const defaultThemes = [
id: 'happy_new_year',
name: '新年快乐',
isDark: false,
isDarkFont: false,
config: {
primary: 'rgb(192, 57, 43)',
font: 'rgb(33, 33, 33)',
+25 -10
View File
@@ -3,6 +3,7 @@
"id": "green",
"name": "绿意盎然",
"isDark": false,
"isDarkFont": false,
"isCustom": false,
"config": {
"themeColors": {
@@ -259,6 +260,7 @@
"id": "blue",
"name": "蓝田生玉",
"isDark": false,
"isDarkFont": false,
"isCustom": false,
"config": {
"themeColors": {
@@ -515,6 +517,7 @@
"id": "blue_plus",
"name": "蛋雅深蓝",
"isDark": false,
"isDarkFont": false,
"isCustom": false,
"config": {
"themeColors": {
@@ -771,6 +774,7 @@
"id": "orange",
"name": "橙黄橘绿",
"isDark": false,
"isDarkFont": false,
"isCustom": false,
"config": {
"themeColors": {
@@ -1027,6 +1031,7 @@
"id": "red",
"name": "热情似火",
"isDark": false,
"isDarkFont": false,
"isCustom": false,
"config": {
"themeColors": {
@@ -1283,6 +1288,7 @@
"id": "pink",
"name": "粉装玉琢",
"isDark": false,
"isDarkFont": false,
"isCustom": false,
"config": {
"themeColors": {
@@ -1539,6 +1545,7 @@
"id": "purple",
"name": "重斤球紫",
"isDark": false,
"isDarkFont": false,
"isCustom": false,
"config": {
"themeColors": {
@@ -1795,6 +1802,7 @@
"id": "grey",
"name": "灰常美丽",
"isDark": false,
"isDarkFont": false,
"isCustom": false,
"config": {
"themeColors": {
@@ -2051,6 +2059,7 @@
"id": "ming",
"name": "青出于黑",
"isDark": false,
"isDarkFont": false,
"isCustom": false,
"config": {
"themeColors": {
@@ -2307,6 +2316,7 @@
"id": "blue2",
"name": "清热板蓝",
"isDark": false,
"isDarkFont": false,
"isCustom": false,
"config": {
"themeColors": {
@@ -2563,6 +2573,7 @@
"id": "black",
"name": "黑灯瞎火",
"isDark": true,
"isDarkFont": false,
"isCustom": false,
"config": {
"themeColors": {
@@ -2766,16 +2777,16 @@
"--color-primary-light-900-alpha-700": "rgba(59, 59, 59, 0.30)",
"--color-primary-light-900-alpha-800": "rgba(59, 59, 59, 0.20)",
"--color-primary-light-900-alpha-900": "rgba(59, 59, 59, 0.10)",
"--color-primary-light-1000": "rgb(47,47,47)",
"--color-primary-light-1000-alpha-100": "rgba(47, 47, 47, 0.90)",
"--color-primary-light-1000-alpha-200": "rgba(47, 47, 47, 0.80)",
"--color-primary-light-1000-alpha-300": "rgba(47, 47, 47, 0.70)",
"--color-primary-light-1000-alpha-400": "rgba(47, 47, 47, 0.60)",
"--color-primary-light-1000-alpha-500": "rgba(47, 47, 47, 0.50)",
"--color-primary-light-1000-alpha-600": "rgba(47, 47, 47, 0.40)",
"--color-primary-light-1000-alpha-700": "rgba(47, 47, 47, 0.30)",
"--color-primary-light-1000-alpha-800": "rgba(47, 47, 47, 0.20)",
"--color-primary-light-1000-alpha-900": "rgba(47, 47, 47, 0.10)",
"--color-primary-light-1000": "rgb(38,38,38)",
"--color-primary-light-1000-alpha-100": "rgba(38, 38, 38, 0.90)",
"--color-primary-light-1000-alpha-200": "rgba(38, 38, 38, 0.80)",
"--color-primary-light-1000-alpha-300": "rgba(38, 38, 38, 0.70)",
"--color-primary-light-1000-alpha-400": "rgba(38, 38, 38, 0.60)",
"--color-primary-light-1000-alpha-500": "rgba(38, 38, 38, 0.50)",
"--color-primary-light-1000-alpha-600": "rgba(38, 38, 38, 0.40)",
"--color-primary-light-1000-alpha-700": "rgba(38, 38, 38, 0.30)",
"--color-primary-light-1000-alpha-800": "rgba(38, 38, 38, 0.20)",
"--color-primary-light-1000-alpha-900": "rgba(38, 38, 38, 0.10)",
"--color-theme": "rgb(59,59,59)",
"--color-1000": "rgb(229, 229, 229)",
"--color-950": "rgb(218,218,218)",
@@ -2819,6 +2830,7 @@
"id": "mid_autumn",
"name": "月里嫦娥",
"isDark": false,
"isDarkFont": false,
"isCustom": false,
"config": {
"themeColors": {
@@ -3075,6 +3087,7 @@
"id": "naruto",
"name": "木叶之村",
"isDark": false,
"isDarkFont": false,
"isCustom": false,
"config": {
"themeColors": {
@@ -3331,6 +3344,7 @@
"id": "china_ink",
"name": "近墨者黑",
"isDark": false,
"isDarkFont": false,
"isCustom": false,
"config": {
"themeColors": {
@@ -3587,6 +3601,7 @@
"id": "happy_new_year",
"name": "新年快乐",
"isDark": false,
"isDarkFont": false,
"isCustom": false,
"config": {
"themeColors": {
+9 -9
View File
@@ -1,6 +1,6 @@
const { RGB_Linear_Shade, RGB_Alpha_Shade } = require('./colorUtils')
exports.createThemeColors = (rgbaColor, fontRgbaColor, isDark) => {
exports.createThemeColors = (rgbaColor, fontRgbaColor, isDark, isDarkFont) => {
const colors = {
'--color-primary': rgbaColor,
}
@@ -22,7 +22,7 @@ exports.createThemeColors = (rgbaColor, fontRgbaColor, isDark) => {
colors[`--color-primary-light-${i * 100}-alpha-${j * 100}`] = RGB_Alpha_Shade(0.1 * j, preColor)
}
}
preColor = RGB_Linear_Shade(isDark ? -0.2 : 1, preColor)
preColor = RGB_Linear_Shade(isDark ? -0.35 : 1, preColor)
colors[`--color-primary-light-${1000}`] = preColor
for (let j = 1; j < 10; j += 1) {
colors[`--color-primary-light-${1000}-alpha-${j * 100}`] = RGB_Alpha_Shade(0.1 * j, preColor)
@@ -30,19 +30,19 @@ exports.createThemeColors = (rgbaColor, fontRgbaColor, isDark) => {
colors['--color-theme'] = isDark ? colors['--color-primary-light-900'] : rgbaColor
return { ...colors, ...createFontColors(fontRgbaColor, isDark) }
return { ...colors, ...createFontColors(fontRgbaColor, isDark, isDarkFont) }
}
const createFontColors = (rgbaColor, isDark) => {
const createFontColors = (rgbaColor, isDark, isDarkFont) => {
// rgb(238, 238, 238)
// let prec = 'rgb(255, 255, 255)'
if (rgbaColor == null) rgbaColor = isDark ? 'rgb(229, 229, 229)' : 'rgb(33, 33, 33)'
if (isDark) return createFontDarkColors(rgbaColor)
rgbaColor ??= isDark ? 'rgb(229, 229, 229)' : 'rgb(33, 33, 33)'
if (isDark) return createFontDarkColors(rgbaColor, isDarkFont)
let colors = {
'--color-1000': rgbaColor,
}
let step = isDark ? -0.05 : 0.05
let step = (isDarkFont ? 0.02 : 0.05) * (isDark ? -1 : 1)
for (let i = 1; i < 21; i += 1) {
colors[`--color-${String(1000 - 50 * i).padStart(3, '0')}`] = RGB_Linear_Shade(step * i, rgbaColor)
}
@@ -50,14 +50,14 @@ const createFontColors = (rgbaColor, isDark) => {
return colors
}
const createFontDarkColors = (rgbaColor) => {
const createFontDarkColors = (rgbaColor, isDarkFont) => {
// rgb(238, 238, 238)
// let prec = 'rgb(255, 255, 255)'
let colors = {
'--color-1000': rgbaColor,
}
const step = -0.05
const step = isDarkFont ? -0.015 : -0.05
let preColor = rgbaColor
for (let i = 1; i < 21; i += 1) {
preColor = RGB_Linear_Shade(step, preColor)
+4 -9
View File
@@ -1,16 +1,11 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"module": "esnext",
"moduleResolution": "nodenext",
"typeRoots": [ /* Specify multiple folders that act like './node_modules/@types'. */
"typeRoots": [
"./types"
],
"paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */
"@common/*": ["common/*"],
},
},
// "include": [
// "**/*.ts",
// "**/*.js",
// "**/*.vue",
// "**/*.json",
// ],
}
+162 -12
View File
@@ -1,4 +1,4 @@
import type { I18n } from '@/lang/i18n'
import type { I18n } from '../../lang/i18n'
declare global {
@@ -68,6 +68,16 @@ declare global {
*/
'common.playBarProgressStyle': 'mini' | 'full' | 'middle'
/**
* 尝试自动更新
*/
'common.tryAutoUpdate': boolean
/**
* 更新版本后是否显示变更日志
*/
'common.showChangeLog': boolean
/**
* 启动时自动播放歌曲
*/
@@ -93,11 +103,26 @@ declare global {
*/
'player.volume': number
/**
* 播放歌曲时是否阻止电脑休眠
*/
'player.powerSaveBlocker': boolean
/**
* 是否静音
*/
'player.isMute': boolean
/**
* 播放速率
*/
'player.playbackRate': number
/**
* 是否自动调整音频的音高以补偿对播放速率设置所做的更改
*/
'player.preservesPitch': boolean
/**
* 音频输出设备id
*/
@@ -148,11 +173,101 @@ declare global {
*/
'player.waitPlayEndStopTime': string
/**
* 环境音效文件名
*/
'player.soundEffect.convolution.fileName': string | null
/**
* 环境音效原始输出增益
*/
'player.soundEffect.convolution.mainGain': number
/**
* 环境音效输出增益
*/
'player.soundEffect.convolution.sendGain': number
/**
* 均衡器 31hz 值
*/
'player.soundEffect.biquadFilter.hz31': number
/**
* 均衡器 62hz 值
*/
'player.soundEffect.biquadFilter.hz62': number
/**
* 均衡器 125hz 值
*/
'player.soundEffect.biquadFilter.hz125': number
/**
* 均衡器 250hz 值
*/
'player.soundEffect.biquadFilter.hz250': number
/**
* 均衡器 500hz 值
*/
'player.soundEffect.biquadFilter.hz500': number
/**
* 均衡器 1000hz 值
*/
'player.soundEffect.biquadFilter.hz1000': number
/**
* 均衡器 2000hz 值
*/
'player.soundEffect.biquadFilter.hz2000': number
/**
* 均衡器 4000hz 值
*/
'player.soundEffect.biquadFilter.hz4000': number
/**
* 均衡器 8000hz 值
*/
'player.soundEffect.biquadFilter.hz8000': number
/**
* 均衡器 16000hz 值
*/
'player.soundEffect.biquadFilter.hz16000': number
/**
* 3D立体环绕是否启用
*/
'player.soundEffect.panner.enable': boolean
/**
* 3D立体环绕声音距离
*/
'player.soundEffect.panner.soundR': number
/**
* 3D立体环绕速度
*/
'player.soundEffect.panner.speed': number
/**
* 升降声调
*/
'player.soundEffect.pitchShifter.playbackRate': number
/**
* 是否启用音频加载失败时自动切歌
*/
'player.autoSkipOnError': boolean
/**
* 点击相同列表内的歌曲切歌时是否清空已播放列表(随机模式下列表内所有歌曲会重新参与随机)
*/
'player.isAutoCleanPlayedList': boolean
/**
* 播放详情页-是否缩放当前播放的歌词行
*/
@@ -204,6 +319,11 @@ declare global {
*/
'desktopLyric.audioVisualization': boolean
/**
* 是否在全屏时隐藏歌词
*/
'desktopLyric.fullscreenHide': boolean
/**
* 桌面歌词窗口宽度
*/
@@ -304,6 +424,21 @@ declare global {
*/
'desktopLyric.style.isZoomActiveLrc': boolean
/**
* 是否加粗逐字歌词字体
*/
'desktopLyric.style.isFontWeightFont': boolean
/**
* 是否加粗逐行歌词字体
*/
'desktopLyric.style.isFontWeightLine': boolean
/**
* 是否加粗翻译、罗马音字体
*/
'desktopLyric.style.isFontWeightExtended': boolean
/**
* 是否启用双击列表里的歌曲时自动切换到当前列表播放(仅对歌单、排行榜有效)
*/
@@ -384,6 +519,16 @@ declare global {
*/
'download.isEmbedLyric': boolean
/**
* 是否在音频文件中嵌入翻译歌词
*/
'download.isEmbedLyricT': boolean
/**
* 是否在音频文件中嵌入罗马音歌词
*/
'download.isEmbedLyricR': boolean
/**
* 歌曲源不可用时,是否启用换源下载
*/
@@ -434,16 +579,6 @@ declare global {
*/
'network.proxy.port': string
/**
* 代理服务器用户名
*/
'network.proxy.username': string
/**
* 代理服务器密码
*/
'network.proxy.password': string
/**
* 是否启用托盘
*/
@@ -459,6 +594,11 @@ declare global {
*/
'tray.themeId': number
/**
* 同步服务模式
*/
'sync.mode': 'server' | 'client'
/**
* 是否启用同步服务
*/
@@ -467,7 +607,17 @@ declare global {
/**
* 同步服务端口号
*/
'sync.port': '23332' | string
'sync.server.port': '23332' | string
/**
* 最大备份快照数
*/
'sync.server.maxSsnapshotNum': number
/**
* 同步服务地址
*/
'sync.client.host': string
/**
* 是否在离开搜索界面时自动清空搜索框
+1
View File
@@ -102,6 +102,7 @@ declare namespace LX {
prev: boolean
}
type UpdateStatus = 'downloaded' | 'downloading' | 'error' | 'checking' | 'idle'
interface VersionInfo {
version: string
desc: string
+7 -2
View File
@@ -26,11 +26,15 @@ declare namespace LX {
// 'desktopLyric.style.fontWeight': LX.AppSetting['desktopLyric.style.fontWeight']
'desktopLyric.style.opacity': LX.AppSetting['desktopLyric.style.opacity']
'desktopLyric.style.ellipsis': LX.AppSetting['desktopLyric.style.ellipsis']
'desktopLyric.style.isFontWeightFont': LX.AppSetting['desktopLyric.style.isFontWeightFont']
'desktopLyric.style.isFontWeightLine': LX.AppSetting['desktopLyric.style.isFontWeightLine']
'desktopLyric.style.isFontWeightExtended': LX.AppSetting['desktopLyric.style.isFontWeightExtended']
'desktopLyric.style.isZoomActiveLrc': LX.AppSetting['desktopLyric.style.isZoomActiveLrc']
'common.langId': LX.AppSetting['common.langId']
'player.isShowLyricTranslation': LX.AppSetting['player.isShowLyricTranslation']
'player.isShowLyricRoma': LX.AppSetting['player.isShowLyricRoma']
'player.isPlayLxlrc': LX.AppSetting['player.isPlayLxlrc']
'player.playbackRate': LX.AppSetting['player.playbackRate']
}
type WinMainActions = 'get_info' | 'get_status' | 'get_analyser_data_array'
@@ -69,6 +73,7 @@ declare namespace LX {
lxlrc: string | null
}>
| LyricAction<'set_offset', number>
| LyricAction<'set_playbackRate', number>
| LyricAction<'set_play', number>
| LyricAction<'set_pause'>
| LyricAction<'set_stop'>
@@ -76,8 +81,8 @@ declare namespace LX {
interface NewBounds {
x?: number | null
y?: number
x: number
y: number
w: number
h: number
}
+37
View File
@@ -0,0 +1,37 @@
declare namespace LX {
namespace Dislike {
// interface ListItemMusicText {
// id?: string
// // type: 'music'
// name: string | null
// singer: string | null
// }
// interface ListItemMusic {
// id?: number
// type: 'musicId'
// musicId: string
// meta: LX.Music.MusicInfo
// }
// type ListItem = ListItemMusicText
// type ListItem = string
// type ListItem = ListItemMusic | ListItemMusicText
interface DislikeMusicInfo {
name: string
singer: string
}
type DislikeRules = string
interface DislikeInfo {
// musicIds: Set<string>
names: Set<string>
musicNames: Set<string>
singerNames: Set<string>
// list: LX.Dislike.ListItem[]
rules: DislikeRules
}
}
}
+30
View File
@@ -0,0 +1,30 @@
declare namespace LX {
namespace Sync {
namespace Dislike {
interface ListInfo {
lastSyncDate?: number
snapshotKey: string
}
interface SyncActionBase <A> {
action: A
}
interface SyncActionData<A, D> extends SyncActionBase<A> {
data: D
}
type SyncAction<A, D = undefined> = D extends undefined ? SyncActionBase<A> : SyncActionData<A, D>
type ActionList = SyncAction<'dislike_data_overwrite', LX.Dislike.DislikeRules>
| SyncAction<'dislike_music_add', LX.Dislike.DislikeMusicInfo[]>
| SyncAction<'dislike_music_clear'>
type SyncMode = 'merge_local_remote'
| 'merge_remote_local'
| 'overwrite_local_remote'
| 'overwrite_remote_local'
// | 'none'
| 'cancel'
}
}
}
+7 -2
View File
@@ -1,10 +1,12 @@
import { type Message } from '@root/lang'
// interface DownloadList {
// }
declare namespace LX {
declare global {
namespace LX {
namespace Download {
type DownloadTaskStatus = 'run'
| 'waiting'
@@ -19,6 +21,7 @@ declare namespace LX {
speed: string
downloaded: number
total: number
writeQueue: number
}
interface DownloadTaskActionBase <A> {
@@ -35,7 +38,7 @@ declare namespace LX {
| DownloadTaskAction<'statusText', string>
| DownloadTaskAction<'progress', ProgressInfo>
| DownloadTaskAction<'error', {
error?: string
error?: keyof Message
message?: string
}>
@@ -48,6 +51,7 @@ declare namespace LX {
total: number
progress: number
speed: string
writeQueue: number
metadata: {
musicInfo: LX.Music.MusicInfoOnline
url: string | null
@@ -64,3 +68,4 @@ declare namespace LX {
}
}
}
}
+4 -3
View File
@@ -12,13 +12,15 @@ declare namespace LX {
interface MyDefaultListInfo {
id: 'default'
name: '试听列表'
name: 'list__name_default'
// name: '试听列表'
// list: LX.Music.MusicInfo[]
}
interface MyLoveListInfo {
id: 'love'
name: '我的收藏'
name: 'list__name_love'
// name: '我的收藏'
// list: LX.Music.MusicInfo[]
}
@@ -137,6 +139,5 @@ declare namespace LX {
userList: UserListInfoFull[]
tempList: LX.Music.MusicInfo[]
}
}
}
+34
View File
@@ -0,0 +1,34 @@
declare namespace LX {
namespace Sync {
namespace List {
interface ListInfo {
lastSyncDate?: number
snapshotKey: string
}
type ActionList = LX.Sync.SyncAction<'list_data_overwrite', LX.List.ListActionDataOverwrite>
| SyncAction<'list_create', LX.List.ListActionAdd>
| SyncAction<'list_remove', LX.List.ListActionRemove>
| SyncAction<'list_update', LX.List.ListActionUpdate>
| SyncAction<'list_update_position', LX.List.ListActionUpdatePosition>
| SyncAction<'list_music_add', LX.List.ListActionMusicAdd>
| SyncAction<'list_music_move', LX.List.ListActionMusicMove>
| SyncAction<'list_music_remove', LX.List.ListActionMusicRemove>
| SyncAction<'list_music_update', LX.List.ListActionMusicUpdate>
| SyncAction<'list_music_update_position', LX.List.ListActionMusicUpdatePosition>
| SyncAction<'list_music_overwrite', LX.List.ListActionMusicOverwrite>
| SyncAction<'list_music_clear', LX.List.ListActionMusicClear>
type ListData = Omit<LX.List.ListDataFull, 'tempList'>
type SyncMode = 'merge_local_remote'
| 'merge_remote_local'
| 'overwrite_local_remote'
| 'overwrite_remote_local'
| 'overwrite_local_remote_full'
| 'overwrite_remote_local_full'
// | 'none'
| 'cancel'
}
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
import {
IAudioMetadata as iAudioMetadata,
type IAudioMetadata as iAudioMetadata,
} from 'music-metadata'
declare global {
+1 -1
View File
@@ -4,7 +4,7 @@
// }
declare module '*.vue' {
import { Component } from 'vue'
import { type Component } from 'vue'
const component: Component
export default component
}
+30
View File
@@ -0,0 +1,30 @@
declare namespace LX {
namespace SoundEffect {
interface EQPreset {
id: string
name: string
hz31: number
hz62: number
hz125: number
hz250: number
hz500: number
hz1000: number
hz2000: number
hz4000: number
hz8000: number
hz16000: number
}
interface ConvolutionPreset {
id: string
name: string
source: string
mainGain: number
sendGain: number
}
// interface PitchShifterPreset {
// id: string
// name: string
// playbackRate: number
// }
}
}
+54 -41
View File
@@ -1,10 +1,15 @@
declare namespace LX {
namespace Sync {
interface Enable {
interface EnableServer {
enable: boolean
port: string
}
interface EnableClient {
enable: boolean
host: string
authCode?: string
}
interface SyncActionBase <A> {
action: A
@@ -14,59 +19,67 @@ declare namespace LX {
}
type SyncAction<A, D = undefined> = D extends undefined ? SyncActionBase<A> : SyncActionData<A, D>
type SyncMainWindowActions = SyncAction<'select_mode', KeyInfo>
| SyncAction<'close_select_mode'>
| SyncAction<'status', Status>
type SyncServiceActions = SyncAction<'select_mode', Mode>
| SyncAction<'get_status'>
| SyncAction<'generate_code'>
| SyncAction<'enable', Enable>
type ActionList = SyncAction<'list_data_overwrite', LX.List.ListActionDataOverwrite>
| SyncAction<'list_create', LX.List.ListActionAdd>
| SyncAction<'list_remove', LX.List.ListActionRemove>
| SyncAction<'list_update', LX.List.ListActionUpdate>
| SyncAction<'list_update_position', LX.List.ListActionUpdatePosition>
| SyncAction<'list_music_add', LX.List.ListActionMusicAdd>
| SyncAction<'list_music_move', LX.List.ListActionMusicMove>
| SyncAction<'list_music_remove', LX.List.ListActionMusicRemove>
| SyncAction<'list_music_update', LX.List.ListActionMusicUpdate>
| SyncAction<'list_music_update_position', LX.List.ListActionMusicUpdatePosition>
| SyncAction<'list_music_overwrite', LX.List.ListActionMusicOverwrite>
| SyncAction<'list_music_clear', LX.List.ListActionMusicClear>
interface List {
action: string
data: any
interface ModeTypes {
list: LX.Sync.List.SyncMode
dislike: LX.Sync.Dislike.SyncMode
}
interface Status {
type ModeType = { [K in keyof ModeTypes]: { type: K, mode: ModeTypes[K] } }[keyof ModeTypes]
type SyncMainWindowActions = SyncAction<'select_mode', { deviceName: string, type: keyof ModeTypes }>
| SyncAction<'close_select_mode'>
| SyncAction<'client_status', ClientStatus>
| SyncAction<'server_status', ServerStatus>
type SyncServiceActions = SyncAction<'select_mode', ModeType>
| SyncAction<'get_server_status'>
| SyncAction<'get_client_status'>
| SyncAction<'generate_code'>
| SyncAction<'enable_server', EnableServer>
| SyncAction<'enable_client', EnableClient>
type ServerDevices = ServerKeyInfo[]
interface ServerStatus {
status: boolean
message: string
address: string[]
code: string
devices: KeyInfo[]
devices: ServerKeyInfo[]
}
interface KeyInfo {
interface ClientStatus {
status: boolean
message: string
address: string[]
}
interface ClientKeyInfo {
clientId: string
key: string
serverName: string
}
interface ServerKeyInfo {
clientId: string
key: string
deviceName: string
connectionTime?: number
lastConnectDate?: number
isMobile: boolean
}
type ListData = Omit<LX.List.ListDataFull, 'tempList'>
type Mode = 'merge_local_remote'
| 'merge_remote_local'
| 'overwrite_local_remote'
| 'overwrite_remote_local'
| 'overwrite_local_remote_full'
| 'overwrite_remote_local_full'
| 'none'
| 'cancel'
interface ListConfig {
skipSnapshot: boolean
}
interface DislikeConfig {
skipSnapshot: boolean
}
type ServerType = 'desktop-app' | 'server'
interface EnabledFeatures {
list?: false | ListConfig
dislike?: false | DislikeConfig
}
type SupportedFeatures = Partial<{ [k in keyof EnabledFeatures]: number }>
}
}
+1
View File
@@ -262,6 +262,7 @@ declare namespace LX {
id: string
name: string
isDark: boolean
isDarkFont: boolean
isCustom: boolean
config: {
themeColors: ThemeColors
+7 -2
View File
@@ -1,7 +1,7 @@
declare namespace LX {
namespace UserApi {
type UserApiSourceInfoType = 'music'
type UserApiSourceInfoActions = 'musicUrl'
type UserApiSourceInfoActions = 'musicUrl' | 'lyric' | 'pic'
interface UserApiSourceInfo {
name: string
@@ -13,15 +13,20 @@ declare namespace LX {
type UserApiSources = Record<LX.Source, UserApiSourceInfo>
interface UserApiInfo {
interface UserApiInfoFull {
id: string
name: string
description: string
script: string
allowShowUpdateAlert: boolean
author?: string
homepage?: string
version?: string
sources?: UserApiSources
}
type UserApiInfo = Omit<UserApiInfoFull, 'script'>
interface UserApiStatus {
status: boolean
message?: string
+15
View File
@@ -5,3 +5,18 @@ type DeepPartial<T> = {
}
type Modify<T, R> = Omit<T, keyof R> & R
// type UndefinedOrNever = undefined
type Actions<T extends { action: string, data?: any }> = {
[U in T as U['action']]: 'data' extends keyof U ? U['data'] : undefined
}
type WarpPromiseValue<T> = T extends ((...args: infer P) => Promise<infer R>)
? ((...args: P) => Promise<R>)
: T extends ((...args: infer P2) => infer R2)
? ((...args: P2) => Promise<R2>)
: Promise<T>
type WarpPromiseRecord<T extends Record<string, any>> = {
[K in keyof T]: WarpPromiseValue<T[K]>
}
+20
View File
@@ -225,3 +225,23 @@ export const arrPushByPosition = <T>(list: T[], newList: T[], position: number)
}
return list
}
// https://stackoverflow.com/a/2450976
export const arrShuffle = <T>(array: T[]) => {
let currentIndex = array.length
let randomIndex
// While there remain elements to shuffle.
while (currentIndex != 0) {
// Pick a remaining element.
randomIndex = Math.floor(Math.random() * currentIndex)
currentIndex--;
// And swap it with the current element.
[array[currentIndex], array[randomIndex]] = [
array[randomIndex], array[currentIndex]]
}
return array
}
+63 -23
View File
@@ -3,11 +3,12 @@ import path from 'path'
import { EventEmitter } from 'events'
import { performance } from 'perf_hooks'
import { STATUS } from './util'
import http from 'http'
import { request, Options as RequestOptions } from './request'
import type http from 'http'
import { request, type Options as RequestOptions } from './request'
export interface Options {
forceResume: boolean
timeout: number
requestOptions: RequestOptions
}
@@ -23,6 +24,7 @@ const defaultRequestOptions: Options['requestOptions'] = {
}
const defaultOptions: Options = {
forceResume: true,
timeout: 20_000,
requestOptions: { ...defaultRequestOptions },
}
@@ -37,6 +39,11 @@ class Task extends EventEmitter {
progress = { total: 0, downloaded: 0, speed: 0, progress: 0 }
statsEstimate = { time: 0, bytes: 0, prevBytes: 0 }
requestInstance: http.ClientRequest | null = null
maxRedirectNum = 2
private redirectNum = 0
private dataWriteQueueLength = 0
private closeWaiting = false
private timeout: null | NodeJS.Timeout = null
constructor(url: string, savePath: string, filename: string, options: Partial<Options> = {}) {
@@ -59,9 +66,14 @@ class Task extends EventEmitter {
async __init() {
const { path, startByte, endByte } = this.chunkInfo
this.redirectNum = 0
this.progress.downloaded = 0
this.progress.progress = 0
this.progress.speed = 0
this.dataWriteQueueLength = 0
this.closeWaiting = false
this.__clearTimeout()
this.__startTimeout()
if (startByte) this.requestOptions.headers!.range = `bytes=${startByte}-${endByte}`
if (!path) return
@@ -112,6 +124,7 @@ class Task extends EventEmitter {
__httpFetch(url: string, options: Options['requestOptions']) {
// console.log(options)
let redirected = false
this.requestInstance = request(url, options)
.on('response', response => {
if (response.statusCode !== 200 && response.statusCode !== 206) {
@@ -125,8 +138,18 @@ class Task extends EventEmitter {
})
return
}
if ((response.statusCode == 301 || response.statusCode == 302) && response.headers.location && this.redirectNum < this.maxRedirectNum) {
console.log('current url:', url)
console.log('redirect to:', response.headers.location)
redirected = true
this.redirectNum++
const location = response.headers.location
this.__httpFetch(location, options)
return
}
this.status = STATUS.failed
this.emit('fail', response)
this.__clearTimeout()
this.__closeRequest()
void this.__closeWriteStream()
return
@@ -139,6 +162,7 @@ class Task extends EventEmitter {
return
}
this.status = STATUS.running
this.__startTimeout()
response
.on('data', this.__handleWriteData.bind(this))
.on('error', err => { this.__handleError(err) })
@@ -153,6 +177,7 @@ class Task extends EventEmitter {
})
.on('error', err => { this.__handleError(err) })
.on('close', () => {
if (redirected) return
void this.__closeWriteStream()
})
.end()
@@ -188,6 +213,7 @@ class Task extends EventEmitter {
this.ws = fs.createWriteStream(this.chunkInfo.path, options)
this.ws.on('finish', () => {
if (this.closeWaiting) return
void this.__closeWriteStream()
})
this.ws.on('error', err => {
@@ -203,6 +229,7 @@ class Task extends EventEmitter {
__handleComplete() {
if (this.status == STATUS.error) return
this.__clearTimeout()
void this.__closeWriteStream().then(() => {
if (this.progress.downloaded == this.progress.total) {
this.status = STATUS.completed
@@ -218,6 +245,7 @@ class Task extends EventEmitter {
__handleError(error: Error) {
if (this.status == STATUS.error) return
this.status = STATUS.error
this.__clearTimeout()
this.__closeRequest()
void this.__closeWriteStream()
if (error.message == 'aborted') return
@@ -231,6 +259,10 @@ class Task extends EventEmitter {
return
}
// console.log('close write stream')
if (this.closeWaiting || this.dataWriteQueueLength) {
this.closeWaiting ||= true
this.ws.on('close', resolve)
} else {
this.ws.close(err => {
if (err) {
this.status = STATUS.error
@@ -241,6 +273,7 @@ class Task extends EventEmitter {
this.ws = null
resolve()
})
}
})
}
@@ -256,7 +289,7 @@ class Task extends EventEmitter {
const result = this.__handleDiffChunk(chunk)
if (result) chunk = result
else {
this.__handleStop().finally(() => {
void this.__handleStop().finally(() => {
// this.__handleError(new Error('Resume failed, response chunk does not match.'))
// Resume failed, response chunk does not match, remove file and restart download
console.log('Resume failed, response chunk does not match.')
@@ -279,12 +312,18 @@ class Task extends EventEmitter {
console.log('cancel write')
return
}
this.dataWriteQueueLength++
this.__startTimeout()
this.__calculateProgress(chunk.length)
this.ws.write(chunk, err => {
if (!err) return
this.dataWriteQueueLength--
if (this.status == STATUS.running) this.__calculateProgress(0)
if (err) {
console.log(err)
this.__handleError(err)
void this.stop()
}
if (this.closeWaiting && !this.dataWriteQueueLength) this.ws?.close()
})
}
@@ -294,36 +333,36 @@ class Task extends EventEmitter {
let chunkLen = chunk.length
let isOk
if (chunkLen >= resumeLastChunkLen) {
isOk = chunk.slice(0, resumeLastChunkLen).toString('hex') === this.resumeLastChunk!.toString('hex')
isOk = chunk.subarray(0, resumeLastChunkLen).toString('hex') === this.resumeLastChunk!.toString('hex')
if (!isOk) return null
this.resumeLastChunk = null
return chunk.slice(resumeLastChunkLen)
return chunk.subarray(resumeLastChunkLen)
} else {
isOk = chunk.slice(0, chunkLen).toString('hex') === this.resumeLastChunk!.slice(0, chunkLen).toString('hex')
isOk = chunk.subarray(0, chunkLen).toString('hex') === this.resumeLastChunk!.subarray(0, chunkLen).toString('hex')
if (!isOk) return null
this.resumeLastChunk = this.resumeLastChunk!.slice(chunkLen)
return chunk.slice(chunkLen)
this.resumeLastChunk = this.resumeLastChunk!.subarray(chunkLen)
return chunk.subarray(chunkLen)
}
}
async __handleStop() {
return new Promise<void>((resolve, reject) => {
this.__clearTimeout()
this.__closeRequest()
if (this.ws) {
this.ws.close(err => {
if (err) {
reject(err)
this.emit('error', err)
return
return this.__closeWriteStream()
}
this.ws = null
resolve()
})
} else {
resolve()
private __clearTimeout() {
if (!this.timeout) return
clearTimeout(this.timeout)
this.timeout = null
}
})
private __startTimeout() {
this.__clearTimeout()
this.timeout = setTimeout(() => {
this.__handleError(new Error('download timeout'))
}, this.options.timeout)
}
__calculateProgress(receivedBytes: number) {
@@ -336,7 +375,7 @@ class Task extends EventEmitter {
// emit the progress every second or if finished
if (progress.downloaded === progress.total || elaspsedTime > 1000) {
if ((progress.downloaded === progress.total && this.dataWriteQueueLength == 0) || elaspsedTime > 1000) {
this.statsEstimate.time = currentTime
this.statsEstimate.bytes = progress.downloaded - this.statsEstimate.prevBytes
this.statsEstimate.prevBytes = progress.downloaded
@@ -345,6 +384,7 @@ class Task extends EventEmitter {
downloaded: progress.downloaded,
progress: progress.progress,
speed: this.statsEstimate.bytes,
writeQueue: this.dataWriteQueueLength,
})
}
}
+3 -2
View File
@@ -1,7 +1,7 @@
import Downloader, { Options as DownloaderOptions } from './Downloader'
import Downloader, { type Options as DownloaderOptions } from './Downloader'
import { getRequestAgent } from './util'
import { sizeFormate } from '@common/utils'
import http from 'http'
import type http from 'http'
// these are the default options
// const options = {
@@ -74,6 +74,7 @@ export const createDownload = ({
speed,
downloaded: stats.downloaded,
total: stats.total,
writeQueue: stats.writeQueue,
})
// if (debugDownload) {
// const downloaded = sizeFormate(stats.downloaded)
+1 -1
View File
@@ -27,7 +27,7 @@ const sendRequest = (url: string, options: Options, callback?: HttpCallback) =>
}
if (options.params) {
(httpOptions.path as string) += `${urlParse.search ? '&' : '?'}${Object.entries(options.params)
(httpOptions.path!) += `${urlParse.search ? '&' : '?'}${Object.entries(options.params)
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
.join('&')}`
}
+46 -46
View File
@@ -4,24 +4,24 @@
// -------------------------------------------------------------------------------------------------
// Change the following variables to customize the appearance of the particles
var numParticles = window.innerWidth / 50;
var maxSpeed = 2.5;
var minSpeed = 1;
var maxSize = 5;
var minSize = 3;
var maxOpacity = 1;
var minOpacity = 0.2;
let numParticles = window.innerWidth / 50
let maxSpeed = 2.5
let minSpeed = 1
let maxSize = 5
let minSize = 3
let maxOpacity = 1
let minOpacity = 0.2
// If the following is true, the canvas resolution will be set to match the full viewport width and height.
// var fixCanvasResolution = true;
var snow_particles = [];
let snow_particles = []
var canvas
let canvas
window.onload = function() {
canvas = document.createElement('canvas')
canvas.style.position = 'fixed';
canvas.style.position = 'fixed'
canvas.style.top = '0px'
canvas.style.left = '0px'
canvas.style.pointerEvents = 'none'
@@ -35,57 +35,57 @@ window.onload = function() {
// canvas.height = window.innerHeight;
// }
InitPoints();
InitPoints()
requestAnimationFrame(Redraw, 15);
window.requestAnimationFrame(Redraw, 15)
window.addEventListener('resize', this.onWindowResize)
}
function onWindowResize (e) {
canvas.width = document.documentElement.clientWidth
canvas.height = document.documentElement.clientHeight
}
// function onWindowResize(e) {
// canvas.width = document.documentElement.clientWidth
// canvas.height = document.documentElement.clientHeight
// }
function Redraw() {
var ctx = canvas.getContext("2d");
ctx.clearRect(0,0,canvas.width,canvas.height);
for (var i = 0; i < snow_particles.length; i++) {
var newYPos = snow_particles[i].yPos + snow_particles[i].speed;
let ctx = canvas.getContext('2d')
ctx.clearRect(0, 0, canvas.width, canvas.height)
for (let i = 0; i < snow_particles.length; i++) {
let newYPos = snow_particles[i].yPos + snow_particles[i].speed
if (newYPos > window.innerHeight) {
newYPos = getRandomInt(-100,-10);
snow_particles[i].xPos = getRandomInt(0, window.innerWidth);
snow_particles[i].speed = Math.random() * (maxSpeed - minSpeed) + minSpeed;
snow_particles[i].opacity = Math.random() * (maxOpacity - minOpacity) + minOpacity;
snow_particles[i].size = Math.random() * (maxSize - minSize) + minSize;
newYPos = getRandomInt(-100, -10)
snow_particles[i].xPos = getRandomInt(0, window.innerWidth)
snow_particles[i].speed = Math.random() * (maxSpeed - minSpeed) + minSpeed
snow_particles[i].opacity = Math.random() * (maxOpacity - minOpacity) + minOpacity
snow_particles[i].size = Math.random() * (maxSize - minSize) + minSize
}
snow_particles[i].yPos = newYPos;
ctx.beginPath();
ctx.arc(snow_particles[i].xPos, newYPos, snow_particles[i].size, 0, 2 * Math.PI);
ctx.fillStyle = "rgba(255, 255, 255, " + snow_particles[i].opacity + ")";
ctx.fill();
snow_particles[i].yPos = newYPos
ctx.beginPath()
ctx.arc(snow_particles[i].xPos, newYPos, snow_particles[i].size, 0, 2 * Math.PI)
ctx.fillStyle = 'rgba(255, 255, 255, ' + snow_particles[i].opacity + ')'
ctx.fill()
}
requestAnimationFrame(Redraw)
window.requestAnimationFrame(Redraw)
}
function InitPoints() {
for (var i = 0; i < numParticles; i++) {
var startX = getRandomInt(0, window.innerWidth);
var startY = getRandomInt(0, window.innerHeight);
var speed = Math.random() * (maxSpeed - minSpeed) + minSpeed;
var opacity = Math.random() * (maxOpacity - minOpacity) + minOpacity;
var size = Math.random() * (maxSize - minSize) + minSize;
for (let i = 0; i < numParticles; i++) {
let startX = getRandomInt(0, window.innerWidth)
let startY = getRandomInt(0, window.innerHeight)
let speed = Math.random() * (maxSpeed - minSpeed) + minSpeed
let opacity = Math.random() * (maxOpacity - minOpacity) + minOpacity
let size = Math.random() * (maxSize - minSize) + minSize
snow_particles.push({
"xPos": startX,
"yPos": startY,
"speed": speed,
"opacity": opacity,
"size": size,
});
xPos: startX,
yPos: startY,
speed,
opacity,
size,
})
}
}
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
min = Math.ceil(min)
max = Math.floor(max)
return Math.floor(Math.random() * (max - min)) + min
}
+2 -1
View File
@@ -39,5 +39,6 @@ export const clipboardReadText = (): string => {
export const encodePath = (path: string) => {
// https://github.com/lyswhut/lx-music-desktop/issues/963
return path.replaceAll('%', '%25')
// https://github.com/lyswhut/lx-music-desktop/issues/1461
return path.replaceAll('%', '%25').replaceAll('#', '%23')
}
+1 -2
View File
@@ -1,6 +1,5 @@
import log from 'electron-log'
import log from 'electron-log/node'
log.transports.file.level = 'info'
export const isLinux = process.platform == 'linux'
export const isWin = process.platform == 'win32'
@@ -1,4 +1,4 @@
const { getNow, TimeoutTools } = require('./utils')
import { getNow, TimeoutTools } from './utils'
// const fontFormateRxp = /(?=<\d+,\d+>).*?/g
const fontSplitRxp = /(?=<\d+,\d+>).*?/g
@@ -25,9 +25,10 @@ const createAnimation = (dom, duration, isVertical) => new window.Animation(new
// https://jsfiddle.net/ceqpnbky/
// https://jsfiddle.net/ceqpnbky/1/
module.exports = class FontPlayer {
export default class FontPlayer {
constructor({
time = 0,
rate = 1,
lyric = '',
lineContentClassName = 'line-content',
lineClassName = 'line',
@@ -43,6 +44,8 @@ module.exports = class FontPlayer {
this.time = time
this.lyric = lyric
this._rate = rate
this.isVertical = isVertical
this.lineContentClassName = lineContentClassName
@@ -66,8 +69,8 @@ module.exports = class FontPlayer {
this.lineContent = null
this.timeoutTools = new TimeoutTools(80)
this.waitPlayTimeout = new TimeoutTools(80)
this.timeoutTools = new TimeoutTools(50)
this.waitPlayTimeout = new TimeoutTools(50)
this._init()
}
@@ -130,18 +133,18 @@ module.exports = class FontPlayer {
// let lineText = ''
let lrcShadowContent
for (const font of fonts) {
if (!timeRxp.test(font)) return this._handleLineParse()
text = font.replace(timeRxp, '')
if (RegExp.$2 == '') return this._handleLineParse()
const time = parseInt(RegExp.$2)
const dom = document.createElement('span')
dom.textContent = text
const animation = createAnimation(dom, time, this.isVertical)
const animation = createAnimation(dom, time / this._rate, this.isVertical)
this.lrcContent.appendChild(dom)
// lineText += text
if (this.shadowContent) {
if (!lrcShadowContent) lrcShadowContent = document.createElement('div')
lrcShadowContent ??= document.createElement('div')
const shadowDom = document.createElement('span')
shadowDom.textContent = text
lrcShadowContent.appendChild(shadowDom)
@@ -186,7 +189,7 @@ module.exports = class FontPlayer {
}
_currentTime() {
return getNow() - this._performanceTime + this._startTime
return (getNow() - this._performanceTime) * this._rate + this._startTime
}
_findcurFontNum(curTime, startIndex = 0) {
@@ -201,7 +204,7 @@ module.exports = class FontPlayer {
const currentTime = this._currentTime()
const driftTime = currentTime - curFont.startTime
if (currentTime > curFont.startTime + curFont.time) {
this._handlePlayFont(curFont, driftTime, true)
this._handlePlayFont(curFont, driftTime / this._rate, true)
this.lineContent.classList.add('played')
this.isPlay = false
this.pause()
@@ -257,13 +260,13 @@ module.exports = class FontPlayer {
if (driftTime >= 0 || this.curFontNum == 0) {
let nextFont = this.fonts[this.curFontNum + 1]
this.delay = nextFont.startTime - curFont.startTime - driftTime
if (this.delay > 0) {
const delay = (nextFont.startTime - curFont.startTime - driftTime) / this._rate
if (delay > 0) {
if (this.isPlay) {
this.timeoutTools.start(() => {
if (!this.isPlay) return
this._refresh()
}, this.delay)
}, delay)
}
this._handlePlayFont(curFont, driftTime)
return
@@ -342,6 +345,13 @@ module.exports = class FontPlayer {
this.curFontNum = this.maxFontNum
}
setPlaybackRate(rate) {
this._rate = rate
if (!this.lines.length) return
if (!this.isPlay) return
this.play(this._currentTime())
}
reset() {
this.pause()
if (this.isLineMode) return this._handlePlayLine(false)
+24 -6
View File
@@ -1,13 +1,14 @@
const LinePlayer = require('./line-player')
const FontPlayer = require('./font-player')
import LinePlayer from './line-player'
import FontPlayer from './font-player'
const fontTimeExp = /<(\d+),(\d+)>/g
module.exports = class Lyric {
export default class Lyric {
constructor({
lyric = '',
extendedLyrics = [],
offset = 0,
rate = 1,
lineContentClassName = 'line-content',
lineClassName = 'line',
shadowClassName = 'shadow',
@@ -25,6 +26,7 @@ module.exports = class Lyric {
this.lyric = lyric
this.extendedLyrics = extendedLyrics
this.offset = offset
this.rate = rate
this.onPlay = onPlay
this.onSetLyric = onSetLyric
this.onUpdateLyric = onUpdateLyric
@@ -50,6 +52,7 @@ module.exports = class Lyric {
this.linePlayer = new LinePlayer({
offset: this.offset,
rate: this.rate,
onPlay: this._handleLinePlayerOnPlay,
onSetLyric: this._handleLinePlayerOnSetLyric,
})
@@ -65,7 +68,7 @@ module.exports = class Lyric {
_handleLinePlayerOnPlay = (num, text, curTime) => {
if (this.isLineMode) {
if (num < this.playingLineNum + 1) {
for (let i = this.playingLineNum; i > num - 1; i--) {
for (let i = this.playingLineNum, minNum = Math.max(num, 0) - 1; i > minNum; i--) {
const font = this._lineFonts[i]
font.reset()
font.lineContent.classList.remove(this.activeLineClassName)
@@ -83,7 +86,7 @@ module.exports = class Lyric {
}
} else {
if (num < this.playingLineNum + 1) {
for (let i = this.playingLineNum; i > num - 1; i--) {
for (let i = this.playingLineNum, minNum = Math.max(num, 0) - 1; i > minNum; i--) {
const font = this._lineFonts[i]
font.lineContent.classList.remove(this.activeLineClassName)
font.reset()
@@ -100,10 +103,12 @@ module.exports = class Lyric {
}
}
this.playingLineNum = num
if (num > -1) {
const font = this._lineFonts[num]
font.lineContent.classList.add(this.activeLineClassName)
font.play(curTime - this._lines[num].time)
this.onPlay(num, this._lines[num].text)
}
this.onPlay(num, this._lines[num]?.text ?? '')
}
_initLines = (lyricLines, offset, isUpdate) => {
@@ -116,6 +121,7 @@ module.exports = class Lyric {
this._lines = lyricLines.map(line => {
const fontPlayer = new FontPlayer({
time: line.time,
rate: this.rate,
lyric: line.text,
extendedLyrics: line.extendedLyrics,
lineContentClassName: this.lineContentClassName,
@@ -141,6 +147,7 @@ module.exports = class Lyric {
this._lines = lyricLines.map(line => {
const fontPlayer = new FontPlayer({
time: line.time,
rate: this.rate,
lyric: line.text,
extendedLyrics: line.extendedLyrics,
lineContentClassName: this.lineContentClassName,
@@ -200,6 +207,17 @@ module.exports = class Lyric {
this._init()
}
setPlaybackRate(rate) {
this.rate = rate
this.linePlayer.setPlaybackRate(rate)
this._initLines(this.initInfo.lines, this.initInfo.offset, true)
if (this.linePlayer.isPlay) {
const num = this.playingLineNum
this.playingLineNum = 0
this._handleLinePlayerOnPlay(num, '', this.linePlayer._currentTime())
} else this.playingLineNum = 0
}
setVertical(isVertical) {
this.isVertical = isVertical
this._initLines(this.initInfo.lines, this.initInfo.offset, true)
@@ -1,7 +1,7 @@
const { getNow, TimeoutTools } = require('./utils')
import { getNow, TimeoutTools } from './utils'
const timeFieldExp = /^(?:\[[\d:.]+\])+/g
const timeExp = /[\d:.]+/g
const timeExp = /\d{1,3}(:\d{1,3}){0,2}(?:\.\d{1,3})/g
const tagRegMap = {
title: 'ti',
artist: 'ar',
@@ -12,6 +12,15 @@ const tagRegMap = {
const timeoutTools = new TimeoutTools()
const t_rxp_1 = /^0+(\d+)/
const t_rxp_2 = /:0+(\d+)/g
const t_rxp_3 = /\.0+(\d+)/
const formatTimeLabel = (label) => {
return label.replace(t_rxp_1, '$1')
.replace(t_rxp_2, ':$1')
.replace(t_rxp_3, '.$1')
}
const parseExtendedLyric = (lrcLinesMap, extendedLyric) => {
const extendedLines = extendedLyric.split(/\r\n|\n|\r/)
for (let i = 0; i < extendedLines.length; i++) {
@@ -20,12 +29,12 @@ const parseExtendedLyric = (lrcLinesMap, extendedLyric) => {
if (result) {
const timeField = result[0]
const text = line.replace(timeFieldExp, '').trim()
if (text) {
// https://github.com/lyswhut/lx-music-desktop/issues/1499
if (text && text != '//') {
const times = timeField.match(timeExp)
if (times == null) continue
for (let time of times) {
if (!time.includes('.')) time += '.0'
const timeStr = time.replace(/(?:\.0+|0+)$/, '')
const timeStr = formatTimeLabel(time)
const targetLine = lrcLinesMap[timeStr]
if (targetLine) targetLine.extendedLyrics.push(text)
}
@@ -34,8 +43,8 @@ const parseExtendedLyric = (lrcLinesMap, extendedLyric) => {
}
}
module.exports = class LinePlayer {
constructor({ offset = 0, onPlay = function() { }, onSetLyric = function() { } } = {}) {
export default class LinePlayer {
constructor({ offset = 0, rate = 1, onPlay = function() { }, onSetLyric = function() { } } = {}) {
this.tags = {}
this.lines = null
this.onPlay = onPlay
@@ -46,6 +55,7 @@ module.exports = class LinePlayer {
this.offset = offset
this._performanceTime = 0
this._startTime = 0
this._rate = rate
}
_init() {
@@ -84,18 +94,16 @@ module.exports = class LinePlayer {
const times = timeField.match(timeExp)
if (times == null) continue
for (let time of times) {
if (!time.includes('.')) time += '.0'
const timeStr = time.replace(/(?:\.0+|0+)$/, '')
const timeStr = formatTimeLabel(time)
if (linesMap[timeStr]) {
linesMap[timeStr].extendedLyrics.push(text)
continue
}
const timeArr = timeStr.split(':')
if (timeArr.length < 3) timeArr.unshift(0)
if (timeArr[2].indexOf('.') > -1) {
timeArr.push(...timeArr[2].split('.'))
timeArr.splice(2, 1)
} else if (!timeArr[2]) timeArr[2] = '0'
if (timeArr.length > 3) continue
else if (timeArr.length < 3) for (let i = 3 - timeArr.length; i--;) timeArr.unshift('0')
if (timeArr[2].indexOf('.') > -1) timeArr.splice(2, 1, ...timeArr[2].split('.'))
linesMap[timeStr] = {
time: parseInt(timeArr[0]) * 60 * 60 * 1000 + parseInt(timeArr[1]) * 60 * 1000 + parseInt(timeArr[2]) * 1000 + parseInt(timeArr[3] || 0),
text,
@@ -115,7 +123,7 @@ module.exports = class LinePlayer {
}
_currentTime() {
return getNow() - this._performanceTime + this._startTime
return (getNow() - this._performanceTime) * this._rate + this._startTime
}
_findCurLineNum(curTime, startIndex = 0) {
@@ -140,16 +148,16 @@ module.exports = class LinePlayer {
const currentTime = this._currentTime()
const driftTime = currentTime - curLine.time
if (driftTime >= 0 || this.curLineNum === 0) {
if (driftTime >= 0) {
let nextLine = this.lines[this.curLineNum + 1]
this.delay = nextLine.time - curLine.time - driftTime
const delay = (nextLine.time - curLine.time - driftTime) / this._rate
if (this.delay > 0) {
if (delay > 0) {
if (this.isPlay) {
timeoutTools.start(() => {
if (!this.isPlay) return
this._refresh()
}, this.delay)
}, delay)
}
this.onPlay(this.curLineNum, curLine.text, currentTime)
return
@@ -159,6 +167,17 @@ module.exports = class LinePlayer {
this._refresh()
return
}
} else if (this.curLineNum == 0) {
let firstLine = this.lines[0]
const delay = (firstLine.time - currentTime) / this._rate
if (this.isPlay) {
timeoutTools.start(() => {
if (!this.isPlay) return
this._refresh()
}, delay)
}
this.onPlay(-1, '', currentTime)
return
}
this.curLineNum = this._findCurLineNum(currentTime, this.curLineNum) - 1
@@ -191,6 +210,13 @@ module.exports = class LinePlayer {
}
}
setPlaybackRate(rate) {
this._rate = rate
if (!this.lines.length) return
if (!this.isPlay) return
this.play(this._currentTime())
}
setLyric(lyric, extendedLyrics) {
// console.log(extendedLyrics)
if (this.isPlay) this.pause()
+4 -3
View File
@@ -1,8 +1,8 @@
const getNow = exports.getNow = typeof performance == 'object' && window.performance.now ? window.performance.now.bind(window.performance) : Date.now.bind(Date)
export const getNow = typeof performance == 'object' && window.performance.now ? window.performance.now.bind(window.performance) : Date.now.bind(Date)
exports.TimeoutTools = class TimeoutTools {
constructor(thresholdTime = 200) {
export class TimeoutTools {
constructor(thresholdTime = 80) {
this.invokeTime = 0
this.animationFrameId = null
this.timeoutId = null
@@ -17,6 +17,7 @@ exports.TimeoutTools = class TimeoutTools {
// console.log('diff', diff)
if (diff > 0) {
if (diff < this.thresholdTime) return this.run()
// console.log('run timeout', diff, diff - this.thresholdTime)
return this.timeoutId = window.setTimeout(() => {
this.timeoutId = null
this.run()
+11 -4
View File
@@ -20,12 +20,12 @@ const oldThemeMap = {
export default (setting: any): Partial<LX.AppSetting> => {
setting = { ...setting }
// 迁移 v2 之前的配置
// 迁移 v2.0.0 之前的配置
if (compareVer(setting.version, '2.0.0') < 0) {
// 迁移列表滚动位置设置 ~0.18.3
if (setting.list?.scroll) {
let scroll = setting.list.scroll
if (setting.list.isSaveScrollLocation) setting.list.isSaveScrollLocation = scroll.enable
setting.list.isSaveScrollLocation &&= scroll.enable
delete setting.list.scroll
}
@@ -117,8 +117,6 @@ export default (setting: any): Partial<LX.AppSetting> => {
setting['network.proxy.enable'] = setting.network?.proxy?.enable
setting['network.proxy.host'] = setting.network?.proxy?.host
setting['network.proxy.port'] = setting.network?.proxy?.port
setting['network.proxy.username'] = setting.network?.proxy?.username
setting['network.proxy.password'] = setting.network?.proxy?.password
setting['tray.enable'] = setting.tray?.enable
setting['tray.themeId'] = setting.tray?.themeId
@@ -133,7 +131,16 @@ export default (setting: any): Partial<LX.AppSetting> => {
setting['odc.isAutoClearSearchInput'] = setting.odc?.isAutoClearSearchInput
setting['odc.isAutoClearSearchList'] = setting.odc?.isAutoClearSearchList
setting.version = '2.0.0'
}
// 迁移 v2.2.0 之前的设置数据
if (compareVer(setting.version, '2.1.0') < 0) {
setting['sync.erver.port'] = setting['sync.port']
setting.version = '2.1.0'
}
return setting
}
+2 -1
View File
@@ -59,7 +59,7 @@ const writeMeta = async(filePath, meta, picPath) => {
module.exports = (filePath, meta) => {
if (!meta.APIC) return writeMeta(filePath, meta)
const picUrl = meta.APIC
let picUrl = meta.APIC
delete meta.APIC
if (!/^http/.test(picUrl)) {
return writeMeta(filePath, meta)
@@ -67,6 +67,7 @@ module.exports = (filePath, meta) => {
let ext = path.extname(picUrl)
let picPath = filePath.replace(/\.flac$/, '') + (ext ? ext.replace(extReg, '$1') : '.jpg')
if (picUrl.includes('music.126.net')) picUrl += `${picUrl.includes('?') ? '&' : '?'}param=500y500`
download(picUrl, picPath).then(success => {
if (success) {
writeMeta(filePath, meta, picPath).finally(() => {
+4 -1
View File
@@ -23,7 +23,10 @@ module.exports = (filePath, meta) => {
}
let ext = path.extname(meta.APIC)
let picPath = filePath.replace(/\.mp3$/, '') + (ext ? ext.replace(extReg, '$1') : '.jpg')
download(meta.APIC, picPath).then(success => {
let picUrl = meta.APIC
if (picUrl.includes('music.126.net')) picUrl += `${picUrl.includes('?') ? '&' : '?'}param=500y500`
download(picUrl, picPath).then(success => {
if (success) {
meta.APIC = picPath
handleWriteMeta(meta, filePath)
+10 -10
View File
@@ -1,8 +1,8 @@
import fs from 'fs'
import crypto from 'crypto'
import { gzip, gunzip } from 'zlib'
import fs from 'node:fs'
import crypto from 'node:crypto'
import { gzip, gunzip } from 'node:zlib'
import path from 'node:path'
import { log } from '@common/utils'
import path from 'path'
export const joinPath = (...paths: string[]): string => path.join(...paths)
@@ -15,7 +15,7 @@ export const dirname = (p: string): string => path.dirname(p)
* @param {*} path 路径
*/
export const checkPath = async(path: string): Promise<boolean> => {
return await new Promise(resolve => {
return new Promise(resolve => {
if (!path) {
resolve(false)
return
@@ -31,7 +31,7 @@ export const checkPath = async(path: string): Promise<boolean> => {
}
export const getFileStats = async(path: string): Promise<fs.Stats | null> => {
return await new Promise(resolve => {
return new Promise(resolve => {
if (!path) {
resolve(null)
return
@@ -97,7 +97,7 @@ export const readFile = async(path: string) => fs.promises.readFile(path)
export const toMD5 = (str: string) => crypto.createHash('md5').update(str).digest('hex')
export const gzipData = async(str: string): Promise<Buffer> => {
return await new Promise((resolve, reject) => {
return new Promise((resolve, reject) => {
gzip(str, (err, result) => {
if (err) {
reject(err)
@@ -109,7 +109,7 @@ export const gzipData = async(str: string): Promise<Buffer> => {
}
export const gunzipData = async(buf: Buffer): Promise<string> => {
return await new Promise((resolve, reject) => {
return new Promise((resolve, reject) => {
gunzip(buf, (err, result) => {
if (err) {
reject(err)
@@ -140,8 +140,8 @@ export const saveLxConfigFile = async(path: string, data: any) => {
export const readLxConfigFile = async(path: string): Promise<any> => {
let isJSON = path.endsWith('.json')
let data: string | Buffer = await fs.promises.readFile(path, isJSON ? 'utf8' : 'binary')
if (!data || isJSON) return data
data = await gunzipData(Buffer.from(data, 'binary'))
if (!data) return data
if (!isJSON) data = await gunzipData(Buffer.from(data, 'binary'))
data = JSON.parse(data)
// 修复v1.14.0出现的导出数据被序列化两次的问题
+147 -49
View File
@@ -8,15 +8,31 @@ const easeInOutQuad = (t: number, b: number, c: number, d: number): number => {
type Noop = () => void
const noop: Noop = () => {}
type ScrollElement<T> = {
lx_scrollLockKey?: number
lx_scrollNextParams?: [ScrollElement<HTMLElement>, number, number, Noop]
lx_scrollTimeout?: number
lx_scrollDelayTimeout?: number
} & T
const handleScrollY = (element: HTMLElement, to: number, duration = 300, fn = noop): Noop => {
const handleScrollY = (element: ScrollElement<HTMLElement>, to: number, duration = 300, fn = noop): Noop => {
if (!element) {
fn()
return noop
}
const clean = () => {
element.lx_scrollLockKey = undefined
element.lx_scrollNextParams = undefined
if (element.lx_scrollTimeout) window.clearTimeout(element.lx_scrollTimeout)
element.lx_scrollTimeout = undefined
}
if (element.lx_scrollLockKey) {
element.lx_scrollNextParams = [element, to, duration, fn]
element.lx_scrollLockKey = -1
return clean
}
// @ts-expect-error
const start = element.scrollTop ?? element.scrollY ?? 0
let cancel = false
if (to > start) {
let maxScrollTop = element.scrollHeight - element.clientHeight
if (to > maxScrollTop) to = maxScrollTop
@@ -34,9 +50,19 @@ const handleScrollY = (element: HTMLElement, to: number, duration = 300, fn = no
}
let currentTime = 0
let val
let val: number
let key = Math.random()
const animateScroll = () => {
element.lx_scrollTimeout = undefined
// if (element.lx_scrollLockKey != key) {
if (element.lx_scrollNextParams && currentTime > duration * 0.75) {
const [_element, to, duration, fn] = element.lx_scrollNextParams
clean()
handleScrollY(_element, to, duration, fn)
return
}
currentTime += increment
val = Math.trunc(easeInOutQuad(currentTime, start, change, duration))
if (element.scrollTo) {
@@ -45,19 +71,23 @@ const handleScrollY = (element: HTMLElement, to: number, duration = 300, fn = no
element.scrollTop = val
}
if (currentTime < duration) {
if (cancel) {
fn()
return
}
window.setTimeout(animateScroll, increment)
element.lx_scrollTimeout = window.setTimeout(animateScroll, increment)
} else {
if (element.lx_scrollNextParams) {
const [_element, to, duration, fn] = element.lx_scrollNextParams
clean()
handleScrollY(_element, to, duration, fn)
} else {
clean()
fn()
}
}
animateScroll()
return () => {
cancel = true
}
element.lx_scrollLockKey = key
animateScroll()
return clean
}
/**
* 设置滚动条位置
@@ -67,16 +97,24 @@ const handleScrollY = (element: HTMLElement, to: number, duration = 300, fn = no
* @param {*} fn 滚动完成后的回调
* @param {*} delay 延迟执行时间
*/
export const scrollTo = (element: HTMLElement, to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
export const scrollTo = (element: ScrollElement<HTMLElement>, to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
let cancelFn: () => void
let timeout: number | null
if (element.lx_scrollDelayTimeout != null) {
window.clearTimeout(element.lx_scrollDelayTimeout)
element.lx_scrollDelayTimeout = undefined
}
if (delay) {
let scrollCancelFn: Noop
cancelFn = () => {
timeout == null ? scrollCancelFn?.() : clearTimeout(timeout)
if (element.lx_scrollDelayTimeout == null) {
scrollCancelFn?.()
} else {
window.clearTimeout(element.lx_scrollDelayTimeout)
element.lx_scrollDelayTimeout = undefined
}
timeout = window.setTimeout(() => {
timeout = null
}
element.lx_scrollDelayTimeout = window.setTimeout(() => {
element.lx_scrollDelayTimeout = undefined
scrollCancelFn = handleScrollY(element, to, duration, fn)
}, delay)
} else {
@@ -84,14 +122,24 @@ export const scrollTo = (element: HTMLElement, to: number, duration = 300, fn =
}
return cancelFn
}
const handleScrollX = (element: HTMLElement, to: number, duration = 300, fn = () => {}): () => void => {
const handleScrollX = (element: ScrollElement<HTMLElement>, to: number, duration = 300, fn = () => {}): () => void => {
if (!element) {
fn()
return noop
}
const clean = () => {
element.lx_scrollLockKey = undefined
element.lx_scrollNextParams = undefined
if (element.lx_scrollTimeout) window.clearTimeout(element.lx_scrollTimeout)
element.lx_scrollTimeout = undefined
}
if (element.lx_scrollLockKey) {
element.lx_scrollNextParams = [element, to, duration, fn]
element.lx_scrollLockKey = -1
return clean
}
// @ts-expect-error
const start = element.scrollLeft || element.scrollX || 0
let cancel = false
if (to > start) {
let maxScrollLeft = element.scrollWidth - element.clientWidth
if (to > maxScrollLeft) to = maxScrollLeft
@@ -109,9 +157,18 @@ const handleScrollX = (element: HTMLElement, to: number, duration = 300, fn = ()
}
let currentTime = 0
let val
let val: number
let key = Math.random()
const animateScroll = () => {
element.lx_scrollTimeout = undefined
if (element.lx_scrollNextParams && currentTime > duration * 0.75) {
const [_element, to, duration, fn] = element.lx_scrollNextParams
clean()
handleScrollY(_element, to, duration, fn)
return
}
currentTime += increment
val = Math.trunc(easeInOutQuad(currentTime, start, change, duration))
if (element.scrollTo) {
@@ -120,19 +177,21 @@ const handleScrollX = (element: HTMLElement, to: number, duration = 300, fn = ()
element.scrollLeft = val
}
if (currentTime < duration) {
if (cancel) {
fn()
return
}
window.setTimeout(animateScroll, increment)
element.lx_scrollTimeout = window.setTimeout(animateScroll, increment)
} else {
if (element.lx_scrollNextParams) {
const [_element, to, duration, fn] = element.lx_scrollNextParams
clean()
handleScrollY(_element, to, duration, fn)
} else {
clean()
fn()
}
}
animateScroll()
return () => {
cancel = true
}
element.lx_scrollLockKey = key
animateScroll()
return clean
}
/**
* 设置滚动条位置
@@ -142,16 +201,24 @@ const handleScrollX = (element: HTMLElement, to: number, duration = 300, fn = ()
* @param {*} fn 滚动完成后的回调
* @param {*} delay 延迟执行时间
*/
export const scrollXTo = (element: HTMLElement, to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
export const scrollXTo = (element: ScrollElement<HTMLElement>, to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
let cancelFn: Noop
let timeout: number | null
if (element.lx_scrollDelayTimeout != null) {
window.clearTimeout(element.lx_scrollDelayTimeout)
element.lx_scrollDelayTimeout = undefined
}
if (delay) {
let scrollCancelFn: Noop
cancelFn = () => {
timeout == null ? scrollCancelFn?.() : clearTimeout(timeout)
if (element.lx_scrollDelayTimeout == null) {
scrollCancelFn?.()
} else {
window.clearTimeout(element.lx_scrollDelayTimeout)
element.lx_scrollDelayTimeout = undefined
}
timeout = window.setTimeout(() => {
timeout = null
}
element.lx_scrollDelayTimeout = window.setTimeout(() => {
element.lx_scrollDelayTimeout = undefined
scrollCancelFn = handleScrollX(element, to, duration, fn)
}, delay)
} else {
@@ -160,14 +227,24 @@ export const scrollXTo = (element: HTMLElement, to: number, duration = 300, fn =
return cancelFn
}
const handleScrollXR = (element: HTMLElement, to: number, duration = 300, fn = () => {}): () => void => {
const handleScrollXR = (element: ScrollElement<HTMLElement>, to: number, duration = 300, fn = () => {}): () => void => {
if (!element) {
fn()
return noop
}
const clean = () => {
element.lx_scrollLockKey = undefined
element.lx_scrollNextParams = undefined
if (element.lx_scrollTimeout) window.clearTimeout(element.lx_scrollTimeout)
element.lx_scrollTimeout = undefined
}
if (element.lx_scrollLockKey) {
element.lx_scrollNextParams = [element, to, duration, fn]
element.lx_scrollLockKey = -1
return clean
}
// @ts-expect-error
const start = element.scrollLeft || element.scrollX as number || 0
let cancel = false
if (to < start) {
let maxScrollLeft = -element.scrollWidth + element.clientWidth
if (to < maxScrollLeft) to = maxScrollLeft
@@ -186,9 +263,18 @@ const handleScrollXR = (element: HTMLElement, to: number, duration = 300, fn = (
}
let currentTime = 0
let val
let val: number
let key = Math.random()
const animateScroll = () => {
element.lx_scrollTimeout = undefined
if (element.lx_scrollNextParams && currentTime > duration * 0.75) {
const [_element, to, duration, fn] = element.lx_scrollNextParams
clean()
handleScrollY(_element, to, duration, fn)
return
}
currentTime += increment
val = Math.trunc(easeInOutQuad(currentTime, start, change, duration))
@@ -198,19 +284,23 @@ const handleScrollXR = (element: HTMLElement, to: number, duration = 300, fn = (
element.scrollLeft = val
}
if (currentTime < duration) {
if (cancel) {
fn()
return
}
window.setTimeout(animateScroll, increment)
element.lx_scrollTimeout = window.setTimeout(animateScroll, increment)
} else {
if (element.lx_scrollNextParams) {
const [_element, to, duration, fn] = element.lx_scrollNextParams
clean()
handleScrollY(_element, to, duration, fn)
} else {
clean()
fn()
}
}
animateScroll()
return () => {
cancel = true
}
element.lx_scrollLockKey = key
animateScroll()
return clean
}
/**
* 设置滚动条位置 (writing-mode: vertical-rl 专用)
@@ -220,16 +310,24 @@ const handleScrollXR = (element: HTMLElement, to: number, duration = 300, fn = (
* @param fn 滚动完成后的回调
* @param delay 延迟执行时间
*/
export const scrollXRTo = (element: HTMLElement, to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
export const scrollXRTo = (element: ScrollElement<HTMLElement>, to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
let cancelFn: Noop
let timeout: number | null
if (element.lx_scrollDelayTimeout != null) {
window.clearTimeout(element.lx_scrollDelayTimeout)
element.lx_scrollDelayTimeout = undefined
}
if (delay) {
let scrollCancelFn: Noop
cancelFn = () => {
timeout == null ? scrollCancelFn?.() : clearTimeout(timeout)
if (element.lx_scrollDelayTimeout == null) {
scrollCancelFn?.()
} else {
window.clearTimeout(element.lx_scrollDelayTimeout)
element.lx_scrollDelayTimeout = undefined
}
timeout = window.setTimeout(() => {
timeout = null
}
element.lx_scrollDelayTimeout = window.setTimeout(() => {
element.lx_scrollDelayTimeout = undefined
scrollCancelFn = handleScrollXR(element, to, duration, fn)
}, delay)
} else {
@@ -244,7 +342,7 @@ export const scrollXRTo = (element: HTMLElement, to: number, duration = 300, fn
*/
let dom_title = document.getElementsByTagName('title')[0]
export const setTitle = (title: string | null) => {
if (!title) title = '洛雪音乐助手'
title ||= '洛雪音乐助手'
dom_title.innerText = title
}

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