mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/lyswhut/lx-music-desktop.git
synced 2026-09-20 08:04:00 +08:00
Compare commits
1040 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ccf6f077d | ||
|
|
5b113c5c22 | ||
|
|
ff53900ea3 | ||
|
|
b0b0403d9a | ||
|
|
21c3b3ab57 | ||
|
|
e85709a341 | ||
|
|
04be42a4d6 | ||
|
|
8196e8a84d | ||
|
|
1f85492690 | ||
|
|
e15178150b | ||
|
|
d83aee2507 | ||
|
|
354a9904e9 | ||
|
|
fe3379ffbb | ||
|
|
0164936038 | ||
|
|
9106d6455f | ||
|
|
5ddea2b1da | ||
|
|
2549c5587e | ||
|
|
c9f28d8105 | ||
|
|
75b2db4749 | ||
|
|
b6f291d721 | ||
|
|
4f7e227392 | ||
|
|
7d95eb34c7 | ||
|
|
7e3ee38920 | ||
|
|
d44c2c26a8 | ||
|
|
d77a41c20f | ||
|
|
c50c747691 | ||
|
|
bcf3dde60d | ||
|
|
33ec3de1b4 | ||
|
|
26be5b5c4f | ||
|
|
56af57c682 | ||
|
|
3a5cb3f7c2 | ||
|
|
724c6be85b | ||
|
|
1c4a56f95b | ||
|
|
9c21e96538 | ||
|
|
67074bd682 | ||
|
|
b535987392 | ||
|
|
d5c0c2e3d3 | ||
|
|
7580c79c00 | ||
|
|
c0a13622b0 | ||
|
|
58d7326f77 | ||
|
|
45d7a12d0d | ||
|
|
40b4f15340 | ||
|
|
9c28ff67c1 | ||
|
|
e3f146dabf | ||
|
|
2b2a6a7f10 | ||
|
|
192574dfe8 | ||
|
|
8e95e06b2d | ||
|
|
1963b3ee71 | ||
|
|
507f66b3b6 | ||
|
|
94bf9eae8b | ||
|
|
0fa32f976c | ||
|
|
198238d4b5 | ||
|
|
66631ac105 | ||
|
|
19c5996351 | ||
|
|
cd3140a9c7 | ||
|
|
56201d6a01 | ||
|
|
654cb41e3e | ||
|
|
fa744d9204 | ||
|
|
3e1c56ff82 | ||
|
|
5d5164c4e9 | ||
|
|
0791e760a4 | ||
|
|
b53fe2dd5e | ||
|
|
1648b0c2dd | ||
|
|
1a5adfabf4 | ||
|
|
c538e9c4fc | ||
|
|
98422b296d | ||
|
|
e35068137d | ||
|
|
83dda92603 | ||
|
|
6bbcb519d7 | ||
|
|
7fa3baacfd | ||
|
|
f94c1ef852 | ||
|
|
cc4cb1a6a7 | ||
|
|
247a322366 | ||
|
|
7ec7023d1b | ||
|
|
b4edd95bab | ||
|
|
6263dbbd0f | ||
|
|
64c45daf16 | ||
|
|
5ed41cb5ac | ||
|
|
29dea5f829 | ||
|
|
e51b21421d | ||
|
|
5cae4ae0bf | ||
|
|
5f2cbb30c2 | ||
|
|
1ddec2cb69 | ||
|
|
96b54cc77a | ||
|
|
4da6524493 | ||
|
|
1364d58065 | ||
|
|
4383966051 | ||
|
|
86451fb5d0 | ||
|
|
0878c7398d | ||
|
|
81643165c2 | ||
|
|
b37802949c | ||
|
|
a53f2388b4 | ||
|
|
e823543cad | ||
|
|
bdf7f903e9 | ||
|
|
336b40a102 | ||
|
|
354922195f | ||
|
|
9c1199fc48 | ||
|
|
ea35ed25d8 | ||
|
|
add05ef913 | ||
|
|
e3aa4110fc | ||
|
|
8a0ed1bc13 | ||
|
|
0db490d444 | ||
|
|
4fd89c8a83 | ||
|
|
4fe17e556e | ||
|
|
e95553163c | ||
|
|
8ae72074ef | ||
|
|
b6c1403f4b | ||
|
|
920d696d0b | ||
|
|
a8dcb0b973 | ||
|
|
895198b7b8 | ||
|
|
144941d991 | ||
|
|
f203517ce8 | ||
|
|
6dd6741237 | ||
|
|
066e9c2fd5 | ||
|
|
f500b9b2d5 | ||
|
|
c80e35872e | ||
|
|
05eda6e406 | ||
|
|
1686182616 | ||
|
|
fd5720451e | ||
|
|
5b9adc8daa | ||
|
|
3efd6867d8 | ||
|
|
cd1a55652f | ||
|
|
47d0a4a343 | ||
|
|
fe76d103f0 | ||
|
|
789a9bfe1a | ||
|
|
391f97d3bb | ||
|
|
d077a03e60 | ||
|
|
3317b6a9e5 | ||
|
|
0b0f0e4649 | ||
|
|
f75e416479 | ||
|
|
d83db6bdf5 | ||
|
|
92ec461a77 | ||
|
|
144fe3b8ac | ||
|
|
af2e650538 | ||
|
|
1c585dc479 | ||
|
|
c6853b197c | ||
|
|
ef591c883a | ||
|
|
4918db9cb1 | ||
|
|
b8250b67c9 | ||
|
|
d6f87894aa | ||
|
|
c808500083 | ||
|
|
3a6c2be48a | ||
|
|
a28d01704a | ||
|
|
8bed654756 | ||
|
|
8845884b24 | ||
|
|
ea5de9a289 | ||
|
|
7922920f51 | ||
|
|
dcf6ab0792 | ||
|
|
57fe510138 | ||
|
|
b4c7a6e448 | ||
|
|
1d8702e95f | ||
|
|
8171fcd346 | ||
|
|
b1ccdeb0d3 | ||
|
|
969b03b364 | ||
|
|
5ed81132fb | ||
|
|
c663cdbe0d | ||
|
|
a4d46fd501 | ||
|
|
c3e1ead90a | ||
|
|
92c10eb534 | ||
|
|
08d0758616 | ||
|
|
68d8369a8b | ||
|
|
af59a4472a | ||
|
|
269546add1 | ||
|
|
65246f8856 | ||
|
|
ab35fe793d | ||
|
|
e7b897e67c | ||
|
|
0b5dbb464d | ||
|
|
60419f4319 | ||
|
|
1ddf0db18d | ||
|
|
0732ca37b8 | ||
|
|
801ea899e6 | ||
|
|
ba5159d2ad | ||
|
|
ab2cf5a50f | ||
|
|
d533c12315 | ||
|
|
1d90719053 | ||
|
|
127d279266 | ||
|
|
a484f66e69 | ||
|
|
b34924e264 | ||
|
|
4d5494c256 | ||
|
|
46b094a57a | ||
|
|
0ef89c30e3 | ||
|
|
dc2a89ce3f | ||
|
|
47adcfec30 | ||
|
|
25e848702f | ||
|
|
756a285978 | ||
|
|
fcf79bda03 | ||
|
|
6b170f3116 | ||
|
|
ea6bc245ff | ||
|
|
61dc344d89 | ||
|
|
fa6b7eacaa | ||
|
|
23dafe539d | ||
|
|
0a654efda0 | ||
|
|
bec10ebe34 | ||
|
|
0e03ca5d48 | ||
|
|
4b1bf29ea0 | ||
|
|
b391b2c16d | ||
|
|
7b9cf93b40 | ||
|
|
dd55e5e086 | ||
|
|
ba2b1dac92 | ||
|
|
56ffeb3008 | ||
|
|
b3757fd6b5 | ||
|
|
e0de81230e | ||
|
|
2bc669c828 | ||
|
|
493f7809a4 | ||
|
|
ef52cd6991 | ||
|
|
480f15b6f6 | ||
|
|
fcfee88ef4 | ||
|
|
19f647e6ab | ||
|
|
27e5ec02f4 | ||
|
|
0808d77a2d | ||
|
|
90d9e9b911 | ||
|
|
ebfbc11c20 | ||
|
|
2764743062 | ||
|
|
c66d7d6187 | ||
|
|
f2b6c0a443 | ||
|
|
aef0ffa506 | ||
|
|
040fcfba4f | ||
|
|
9f72831e4a | ||
|
|
fa0d9ad4c3 | ||
|
|
b6107b7281 | ||
|
|
6ffe61562b | ||
|
|
b5f5e13f22 | ||
|
|
376b7545e1 | ||
|
|
ffaa3e591b | ||
|
|
24d5722ef7 | ||
|
|
9e1fe0a6c1 | ||
|
|
d128aeb6ef | ||
|
|
ef371dedb4 | ||
|
|
00d262f0e6 | ||
|
|
475b8924c3 | ||
|
|
0afb1417a3 | ||
|
|
b6b41660d6 | ||
|
|
c78811fe36 | ||
|
|
a0254eb4f2 | ||
|
|
e9c96ecde2 | ||
|
|
f9cbf9254c | ||
|
|
70d24ffdfd | ||
|
|
84530ca0a2 | ||
|
|
e848fda4e3 | ||
|
|
51964e51c6 | ||
|
|
0c63268df1 | ||
|
|
316f62c3c8 | ||
|
|
9e37800426 | ||
|
|
3e0ee374c9 | ||
|
|
bb411a365c | ||
|
|
da0878a7a7 | ||
|
|
d0328f17e1 | ||
|
|
c6f49984a7 | ||
|
|
f53fb071f0 | ||
|
|
2cc8af71d0 | ||
|
|
006da66028 | ||
|
|
2a74abdef4 | ||
|
|
269b14394d | ||
|
|
d6d6fae2ca | ||
|
|
331afc37df | ||
|
|
041bfdaf3c | ||
|
|
023cd15cd0 | ||
|
|
f2fb37df4c | ||
|
|
c4ae0dddb0 | ||
|
|
f66077f012 | ||
|
|
a31d6bc5ae | ||
|
|
fad13f517b | ||
|
|
ef36517a26 | ||
|
|
085e45a50f | ||
|
|
43b06a686b | ||
|
|
e8dba4d133 | ||
|
|
1af3cd1d77 | ||
|
|
4c44310fea | ||
|
|
3e343a80fd | ||
|
|
313fac9f18 | ||
|
|
2c242c66e5 | ||
|
|
4e2b525c99 | ||
|
|
93c76d4ba0 | ||
|
|
b69ecca252 | ||
|
|
947011103c | ||
|
|
2fd7e4503a | ||
|
|
a5fce15661 | ||
|
|
f1561ddc21 | ||
|
|
74ce094d79 | ||
|
|
f832d70701 | ||
|
|
24521bf50d | ||
|
|
cfe84fc2dd | ||
|
|
8efeb169af | ||
|
|
7b7711d410 | ||
|
|
8b0779ac17 | ||
|
|
1e904e5ebb | ||
|
|
1d3b93d4a3 | ||
|
|
7756b62f4c | ||
|
|
7990e841e0 | ||
|
|
ae348a71e6 | ||
|
|
9a1a537bfd | ||
|
|
6b7d2ea657 | ||
|
|
98ef7c2385 | ||
|
|
093bfd26a3 | ||
|
|
9953c61d62 | ||
|
|
e61072ddc9 | ||
|
|
2269cd54be | ||
|
|
76d5e64097 | ||
|
|
06073f3982 | ||
|
|
0f71a93c5e | ||
|
|
fa41c32a3e | ||
|
|
3b00efdeae | ||
|
|
0f323ce04f | ||
|
|
ad2acc2a16 | ||
|
|
162fea6e28 | ||
|
|
30e939c354 | ||
|
|
94c25b5dcb | ||
|
|
a58d65e8af | ||
|
|
06b453ebe7 | ||
|
|
f00c6b1cb6 | ||
|
|
027466dfc2 | ||
|
|
bc69a98df1 | ||
|
|
ba9fd8d1f1 | ||
|
|
0fcc3f257d | ||
|
|
382235c556 | ||
|
|
0a919d7076 | ||
|
|
0709aa56a4 | ||
|
|
684d90d0e7 | ||
|
|
227c05a696 | ||
|
|
8cf1a4a790 | ||
|
|
75e3976bfa | ||
|
|
e9791fd493 | ||
|
|
f9f445489c | ||
|
|
10e5460e39 | ||
|
|
f1049169b5 | ||
|
|
2056bdb43d | ||
|
|
785bf3ce3a | ||
|
|
c29bf61e59 | ||
|
|
a98c0516a3 | ||
|
|
a6989a2aba | ||
|
|
30e43170f8 | ||
|
|
d8feb3e794 | ||
|
|
99bdf6d173 | ||
|
|
8424a9e4de | ||
|
|
4c0cbe3253 | ||
|
|
462dc5da51 | ||
|
|
40fad5f785 | ||
|
|
c0791acbf6 | ||
|
|
3cb0a56647 | ||
|
|
95f38e8048 | ||
|
|
a3493a6c74 | ||
|
|
3535bb4e4e | ||
|
|
3be6242cb5 | ||
|
|
64a53830e4 | ||
|
|
b3e3860b23 | ||
|
|
4db560fb4b | ||
|
|
b00de4d976 | ||
|
|
0a0d6969ec | ||
|
|
ac162103a7 | ||
|
|
16876502d1 | ||
|
|
4cdd2110a6 | ||
|
|
b820adf0bf | ||
|
|
1cee2febf4 | ||
|
|
43f8d9d72b | ||
|
|
66ffe03224 | ||
|
|
5c96c83f98 | ||
|
|
6d1ddece92 | ||
|
|
897107ca23 | ||
|
|
0baaffdad8 | ||
|
|
784d2fda6d | ||
|
|
19817249db | ||
|
|
240260e598 | ||
|
|
fcf92f06ef | ||
|
|
fe1c2a1896 | ||
|
|
d55c65e275 | ||
|
|
93064bb18b | ||
|
|
dec1b24c86 | ||
|
|
8c76c3d81c | ||
|
|
8d8cabd5b3 | ||
|
|
aa948b0149 | ||
|
|
a25ef6e2a0 | ||
|
|
c404d3ac8d | ||
|
|
8eb71495a1 | ||
|
|
db824c4601 | ||
|
|
029886cc87 | ||
|
|
42604ec737 | ||
|
|
caaa62db30 | ||
|
|
0f135e0587 | ||
|
|
dae6826cc1 | ||
|
|
ddf2ac7ece | ||
|
|
5e011356f2 | ||
|
|
82e047dc66 | ||
|
|
7720b416d5 | ||
|
|
c40dc0fa50 | ||
|
|
36ed7bca66 | ||
|
|
1fa015704a | ||
|
|
7e2097ba6d | ||
|
|
3a630edaa5 | ||
|
|
1b561bbf4c | ||
|
|
37de0b59c3 | ||
|
|
2f528d2a03 | ||
|
|
686d55a672 | ||
|
|
07c58e2ce7 | ||
|
|
6fe794fdf5 | ||
|
|
d0ec90130c | ||
|
|
f7806d9781 | ||
|
|
63219b49f2 | ||
|
|
320e84627c | ||
|
|
fc9e697249 | ||
|
|
a68c626fa2 | ||
|
|
66f17cf05b | ||
|
|
5949e8fb3f | ||
|
|
3bb0fcc4cd | ||
|
|
d32d5d0eb8 | ||
|
|
5ee2c030ec | ||
|
|
58ca7df105 | ||
|
|
fc77dbc73b | ||
|
|
8aedde2422 | ||
|
|
526b5fe140 | ||
|
|
1c4e9ed474 | ||
|
|
21cbb441e2 | ||
|
|
01fedc59e2 | ||
|
|
b3d13c3a90 | ||
|
|
24ee42e0c7 | ||
|
|
3a18eccdd6 | ||
|
|
a3b763ecc9 | ||
|
|
822c74ad2f | ||
|
|
0a71e34459 | ||
|
|
25b68081ea | ||
|
|
ac0e1e7698 | ||
|
|
4be5bcc979 | ||
|
|
f3c2d7e43a | ||
|
|
9fa0700f72 | ||
|
|
868da542ab | ||
|
|
04c810e292 | ||
|
|
86478c0268 | ||
|
|
b453a8934f | ||
|
|
669c1fe490 | ||
|
|
c15895ab45 | ||
|
|
44acc7d5ac | ||
|
|
341df0dab7 | ||
|
|
8df3ff3536 | ||
|
|
85c683b714 | ||
|
|
6606edafb9 | ||
|
|
91ff7b658a | ||
|
|
27c500e950 | ||
|
|
078245cb96 | ||
|
|
e54597b944 | ||
|
|
1af9b3a018 | ||
|
|
315d041fc7 | ||
|
|
f7f1cdab34 | ||
|
|
597f0da42f | ||
|
|
497add09b3 | ||
|
|
ff05225b56 | ||
|
|
ac053e9ac9 | ||
|
|
9aeae1e460 | ||
|
|
d710f204b3 | ||
|
|
be016aa4bb | ||
|
|
04e62cba24 | ||
|
|
4d476dc641 | ||
|
|
85c3a812da | ||
|
|
e985e70303 | ||
|
|
9dbf96c259 | ||
|
|
6eba7c9a2f | ||
|
|
c1966e9301 | ||
|
|
a6fb72f1de | ||
|
|
a695a69f0f | ||
|
|
a8632297eb | ||
|
|
ca4218b309 | ||
|
|
65df71239a | ||
|
|
fdd8f3b214 | ||
|
|
1887573301 | ||
|
|
1efd56f48b | ||
|
|
bcd13b6f27 | ||
|
|
e4e880edd9 | ||
|
|
75da9c2e73 | ||
|
|
7a933dbd5e | ||
|
|
4b097b83b1 | ||
|
|
b3f9a691f5 | ||
|
|
50cd94a280 | ||
|
|
82982b3cda | ||
|
|
a5243ff58c | ||
|
|
3b25e0a112 | ||
|
|
d94d880569 | ||
|
|
8eec07b5bd | ||
|
|
9f72a51458 | ||
|
|
9fd181ee06 | ||
|
|
f18875682b | ||
|
|
897a17e303 | ||
|
|
c9d0aafbfa | ||
|
|
fbd2a57a35 | ||
|
|
ccd5c91283 | ||
|
|
c78b9f07a2 | ||
|
|
ffc3300fe4 | ||
|
|
b02d32929c | ||
|
|
e29d3e3eaa | ||
|
|
5d91643637 | ||
|
|
9eec1adf77 | ||
|
|
9cebaa0d97 | ||
|
|
48cfebd5ae | ||
|
|
0c3899840b | ||
|
|
93bab6bbdd | ||
|
|
69368b884f | ||
|
|
f59b2d26ad | ||
|
|
32ffe33e1a | ||
|
|
cc5d119ba4 | ||
|
|
637727faa0 | ||
|
|
9b82cdfe53 | ||
|
|
2424c76c50 | ||
|
|
f0dc76c7a4 | ||
|
|
798f5cd0a6 | ||
|
|
a904896269 | ||
|
|
be482bd683 | ||
|
|
e9a624c896 | ||
|
|
b3a13a25f0 | ||
|
|
30e55f5acb | ||
|
|
7a67263cf9 | ||
|
|
b811906bae | ||
|
|
2a81d41c2c | ||
|
|
51d0037a4b | ||
|
|
25fc0226aa | ||
|
|
cfef06a15b | ||
|
|
5ce6aafc07 | ||
|
|
fe833033df | ||
|
|
a47be148c8 | ||
|
|
af426d28c1 | ||
|
|
0332b81d58 | ||
|
|
88b416c949 | ||
|
|
66c4e62d9b | ||
|
|
9091c2c346 | ||
|
|
e29ca2e79c | ||
|
|
afc3f48f51 | ||
|
|
eed8b36dc1 | ||
|
|
b20942d254 | ||
|
|
e707beffd6 | ||
|
|
747c75fb18 | ||
|
|
42f0a9df4b | ||
|
|
10ee6bcc7f | ||
|
|
bcba194a31 | ||
|
|
8605f63ac6 | ||
|
|
a02827a7ac | ||
|
|
16903b0f5a | ||
|
|
26bdf25423 | ||
|
|
3c0f4be533 | ||
|
|
ecf9dce758 | ||
|
|
90e40d5245 | ||
|
|
b76424ba9b | ||
|
|
ded75563e9 | ||
|
|
1eb9920bcf | ||
|
|
7e029b5495 | ||
|
|
37f9491d5e | ||
|
|
6aec78e153 | ||
|
|
685e34bc83 | ||
|
|
fb828788df | ||
|
|
eff49af00c | ||
|
|
7f02b85eb8 | ||
|
|
1de84c66d6 | ||
|
|
e382ccff9b | ||
|
|
33e6202835 | ||
|
|
bd8b8d5b64 | ||
|
|
db9dc18118 | ||
|
|
7cc373c07f | ||
|
|
aa87640687 | ||
|
|
6724912c73 | ||
|
|
2cabe6e58e | ||
|
|
33383cd3bd | ||
|
|
78c5250f1b | ||
|
|
9797d98fac | ||
|
|
43c2dc3dec | ||
|
|
50e140e606 | ||
|
|
3f88701751 | ||
|
|
7d89d53b1b | ||
|
|
09f7989cab | ||
|
|
6255cfd401 | ||
|
|
e7e4897dcd | ||
|
|
9639143893 | ||
|
|
3be3b0ca36 | ||
|
|
afc81b6308 | ||
|
|
36d140d4bd | ||
|
|
edff998be9 | ||
|
|
5a856e468e | ||
|
|
d2845f91b5 | ||
|
|
9040c13c3f | ||
|
|
7ed95ff523 | ||
|
|
aed9889a2e | ||
|
|
0090f484d8 | ||
|
|
0c576c65a8 | ||
|
|
b94cde5fbe | ||
|
|
df2582d1de | ||
|
|
f237a9aab5 | ||
|
|
12c370d7cb | ||
|
|
015c17e2dc | ||
|
|
d8a3466914 | ||
|
|
05d2ffe8fa | ||
|
|
a62d8304e2 | ||
|
|
e51ee8c1ad | ||
|
|
6d0f668f88 | ||
|
|
ed5e590859 | ||
|
|
346fa2c004 | ||
|
|
db04b0c285 | ||
|
|
07800bdb83 | ||
|
|
ad8b338bd9 | ||
|
|
031c0c263d | ||
|
|
d54053003e | ||
|
|
adb453c216 | ||
|
|
ef04e43323 | ||
|
|
9ff5147418 | ||
|
|
cdbedf3d64 | ||
|
|
3d52fa059e | ||
|
|
ef46d1a308 | ||
|
|
d57cfa90fc | ||
|
|
7aece7dd7e | ||
|
|
ccf362db41 | ||
|
|
7bd4002f09 | ||
|
|
9460b12cf9 | ||
|
|
8cac592182 | ||
|
|
83b25b0c33 | ||
|
|
1832ec753c | ||
|
|
0c4f943d72 | ||
|
|
116ee25e28 | ||
|
|
6149c2a8a8 | ||
|
|
b2e475b857 | ||
|
|
f7e4e98b01 | ||
|
|
c4a9919652 | ||
|
|
5c1fb7d4e8 | ||
|
|
b1d0a4e7ad | ||
|
|
75db995342 | ||
|
|
c318856534 | ||
|
|
cf42e1846e | ||
|
|
56a5ebbb9d | ||
|
|
7431e1e605 | ||
|
|
4b6a460e69 | ||
|
|
f6f0c2f1dc | ||
|
|
90357ae386 | ||
|
|
bdc6b08234 | ||
|
|
1ba68d67ba | ||
|
|
d0518e6dfe | ||
|
|
3be6a78568 | ||
|
|
a6b8881e30 | ||
|
|
23c057ac08 | ||
|
|
07460ba06f | ||
|
|
bcbedbe053 | ||
|
|
ba26037c7f | ||
|
|
60243215a0 | ||
|
|
9c3ab28da9 | ||
|
|
2b1076f09e | ||
|
|
241859232b | ||
|
|
456fdc0fca | ||
|
|
be84bd27d3 | ||
|
|
2e84788d53 | ||
|
|
76b3b3b23d | ||
|
|
426c88c4ed | ||
|
|
d625b0d567 | ||
|
|
e350c5f070 | ||
|
|
1e481edc67 | ||
|
|
12f02dda98 | ||
|
|
ef50d90a18 | ||
|
|
25ae72cd26 | ||
|
|
eb8f83786c | ||
|
|
cd7b075016 | ||
|
|
19068dc6b0 | ||
|
|
93ba0bd807 | ||
|
|
15a76486ef | ||
|
|
f01e367785 | ||
|
|
9b536f8dc1 | ||
|
|
664a0e4ad8 | ||
|
|
9ed64354fc | ||
|
|
f59a160249 | ||
|
|
cdd7774a73 | ||
|
|
a10777d598 | ||
|
|
a4c5670409 | ||
|
|
343c53b41c | ||
|
|
4c97bc34ce | ||
|
|
83f0ca918a | ||
|
|
bc7c3968b6 | ||
|
|
1aba217b1a | ||
|
|
a95e87a08f | ||
|
|
b75e299b2f | ||
|
|
3e63b49c3c | ||
|
|
1eaf2372bb | ||
|
|
8b8400e5be | ||
|
|
c6551c9ea3 | ||
|
|
b04c676288 | ||
|
|
01e2a2bdb2 | ||
|
|
670533807a | ||
|
|
9d1270374e | ||
|
|
353e8bd0de | ||
|
|
9ca0ec82a8 | ||
|
|
bb5fd44750 | ||
|
|
1e435b0ff8 | ||
|
|
bb080c2d18 | ||
|
|
91063075d3 | ||
|
|
ccaf485c91 | ||
|
|
125207226b | ||
|
|
393affbd90 | ||
|
|
4f06fe45d3 | ||
|
|
ba1a981c87 | ||
|
|
6d51240a27 | ||
|
|
527143405f | ||
|
|
2808fdd3c0 | ||
|
|
a2c84392f4 | ||
|
|
3fdff4e6eb | ||
|
|
dd41e00691 | ||
|
|
29bcaa9558 | ||
|
|
3be3e8546f | ||
|
|
0bbb760e61 | ||
|
|
4f238f4f62 | ||
|
|
a244c6d5e3 | ||
|
|
aa5765971e | ||
|
|
2a66edd1c5 | ||
|
|
a6e86e8d6b | ||
|
|
f30cb9da59 | ||
|
|
e94d17a922 | ||
|
|
53f5c168ad | ||
|
|
4533f7b1d5 | ||
|
|
b9398e882a | ||
|
|
66f19d72da | ||
|
|
35e85cfdcb | ||
|
|
6d6b63cce1 | ||
|
|
a7d5e77a1c | ||
|
|
0332b22c4a | ||
|
|
09a7974818 | ||
|
|
66d2f0d550 | ||
|
|
d2556fed20 | ||
|
|
49cd8536a7 | ||
|
|
b8f15bcca9 | ||
|
|
a17ba9f62c | ||
|
|
a9aa9a2e20 | ||
|
|
43da07ca4c | ||
|
|
1b22abcd50 | ||
|
|
f85c83169d | ||
|
|
ecff344473 | ||
|
|
cde380782f | ||
|
|
d557149326 | ||
|
|
ecdcb040ee | ||
|
|
e58471fe37 | ||
|
|
30e66473db | ||
|
|
5357ed3746 | ||
|
|
337f443cbf | ||
|
|
e7ad1b8496 | ||
|
|
aea01de449 | ||
|
|
b7fb75a49e | ||
|
|
57752d09cd | ||
|
|
bd395f557a | ||
|
|
2d865d8fed | ||
|
|
5bff74e7a9 | ||
|
|
a9e3723764 | ||
|
|
b811227c41 | ||
|
|
4896c4dda5 | ||
|
|
b4ee1ade2d | ||
|
|
a6e2308282 | ||
|
|
f56c42f353 | ||
|
|
5edf51810d | ||
|
|
81985b0e31 | ||
|
|
163830f1bd | ||
|
|
867ae2991c | ||
|
|
ee3d04ba23 | ||
|
|
71dd8c14f8 | ||
|
|
88dc9c2a74 | ||
|
|
98f012de4d | ||
|
|
c85a036ac6 | ||
|
|
1f8b632d95 | ||
|
|
59dd56e542 | ||
|
|
1bfa8dcb3f | ||
|
|
1b381af7a7 | ||
|
|
2bc4912416 | ||
|
|
474f0d8291 | ||
|
|
231fe54408 | ||
|
|
78bef70735 | ||
|
|
ce7247e209 | ||
|
|
1d807e2a89 | ||
|
|
749141c10e | ||
|
|
e2c7c0596c | ||
|
|
392cd72f83 | ||
|
|
340a9d937b | ||
|
|
5d7ad836db | ||
|
|
f8e449b017 | ||
|
|
bb0b59080a | ||
|
|
95b08c2406 | ||
|
|
3ef5e0e55a | ||
|
|
ae9b4bc2f5 | ||
|
|
914e738931 | ||
|
|
e4c19c0f2a | ||
|
|
54354518d0 | ||
|
|
d36e21ab56 | ||
|
|
b496e3c667 | ||
|
|
d10434285f | ||
|
|
d6c8886fdb | ||
|
|
51e3c8605c | ||
|
|
d4d6b9c9aa | ||
|
|
0c9c3c61fc | ||
|
|
0228850d6d | ||
|
|
529078dc67 | ||
|
|
aadb68ba08 | ||
|
|
c81171a39e | ||
|
|
a50ecff189 | ||
|
|
73321829ae | ||
|
|
e88c837482 | ||
|
|
2b263cae87 | ||
|
|
e93052f0cb | ||
|
|
9539083f7e | ||
|
|
8f26eb1c69 | ||
|
|
a126e5b4c8 | ||
|
|
033c3cd8e9 | ||
|
|
50d029c671 | ||
|
|
6a3f7e6150 | ||
|
|
aadfbefbe9 | ||
|
|
edbcaf0f90 | ||
|
|
207427e993 | ||
|
|
e109dd876a | ||
|
|
64ac69c48b | ||
|
|
652b06c116 | ||
|
|
654035a469 | ||
|
|
cd50e0086b | ||
|
|
66a119e9dc | ||
|
|
14ec7d48f6 | ||
|
|
5ad5b211e9 | ||
|
|
aac4b6ccfe | ||
|
|
cfc6967fdc | ||
|
|
8a0b960985 | ||
|
|
bd260c3dc5 | ||
|
|
203dff8d98 | ||
|
|
ca56089a3e | ||
|
|
a012d1b927 | ||
|
|
0af322a3a8 | ||
|
|
8d743803c8 | ||
|
|
63fed77c5b | ||
|
|
f2077dca04 | ||
|
|
cccfb6a08a | ||
|
|
fa6dead83d | ||
|
|
605418d750 | ||
|
|
8aa869b2e4 | ||
|
|
558b6e0ccf | ||
|
|
6a695368da | ||
|
|
ce89f29aeb | ||
|
|
6d935d8589 | ||
|
|
967093328f | ||
|
|
48e1da128d | ||
|
|
369de684a6 | ||
|
|
d5f65eea06 | ||
|
|
8e5deb5bf4 | ||
|
|
df653258bb | ||
|
|
ed28a59fcb | ||
|
|
adc46411b2 | ||
|
|
fcc4d666ec | ||
|
|
9d89f5d48d | ||
|
|
88c62e01ad | ||
|
|
1823598a8b | ||
|
|
3fd22988c1 | ||
|
|
a3ade4cde2 | ||
|
|
55c2286eca | ||
|
|
1afa050698 | ||
|
|
dfa15c6952 | ||
|
|
a2af321c5e | ||
|
|
fddfaf23ce | ||
|
|
ae0ee2048a | ||
|
|
6659d1fe43 | ||
|
|
f601e61f8d | ||
|
|
f8ed7a112a | ||
|
|
b0d6181bd7 | ||
|
|
82843b14b4 | ||
|
|
0df3ca3c5f | ||
|
|
c881f8e886 | ||
|
|
f1bf274de1 | ||
|
|
91e4e9c2d5 | ||
|
|
63a5f94a67 | ||
|
|
1bd8694262 | ||
|
|
501231adff | ||
|
|
3a033b66c4 | ||
|
|
46aacba037 | ||
|
|
10ef80da75 | ||
|
|
75b7897ad6 | ||
|
|
14423e0eb3 | ||
|
|
fe44581a61 | ||
|
|
0d612bd6df | ||
|
|
b7eba95ab2 | ||
|
|
59091a5ea9 | ||
|
|
31e8b466cf | ||
|
|
c97bc5edfc | ||
|
|
74f8e9544b | ||
|
|
0a5e7c44ff | ||
|
|
5c32160a58 | ||
|
|
2fc81c03ec | ||
|
|
cc485df42f | ||
|
|
d575dd24ca | ||
|
|
e47586cea3 | ||
|
|
1ce63c7550 | ||
|
|
36d69da3b7 | ||
|
|
529be08420 | ||
|
|
ffe11c7015 | ||
|
|
76b796bde7 | ||
|
|
f81715f07f | ||
|
|
ec3f667b75 | ||
|
|
87f0b4fd17 | ||
|
|
c7e95c5166 | ||
|
|
6ea0837cce | ||
|
|
59627e048f | ||
|
|
45fea5726b | ||
|
|
e539f29f6d | ||
|
|
1d2810e98f | ||
|
|
1cbafc9750 | ||
|
|
e675f7ac06 | ||
|
|
9dc9d3b1b7 | ||
|
|
a104ce688e | ||
|
|
2dd1ceb0b9 | ||
|
|
9379328d96 | ||
|
|
d8969c327c | ||
|
|
c3624fb7d6 | ||
|
|
b2de230100 | ||
|
|
9d0bf50931 | ||
|
|
843e453b21 | ||
|
|
f7546b0090 | ||
|
|
ed12aac7d8 | ||
|
|
15f4053241 | ||
|
|
7a334f0099 | ||
|
|
4a510393ab | ||
|
|
52b0638258 | ||
|
|
b198b1945d | ||
|
|
10784e95bb | ||
|
|
f4c65516d0 | ||
|
|
6498305ec8 | ||
|
|
7a91e6efef | ||
|
|
4204b62f8f | ||
|
|
70b718d372 | ||
|
|
2b5d6f3592 | ||
|
|
8e48d5ba68 | ||
|
|
3cec62d1a9 | ||
|
|
b1650bcbee | ||
|
|
492e3d8965 | ||
|
|
6d7be78b70 | ||
|
|
bac9439f8e | ||
|
|
5b612bfa0f | ||
|
|
1f77b3dd4b | ||
|
|
b0d7a53d15 | ||
|
|
c029eccdd3 | ||
|
|
7236f8929c | ||
|
|
aa114f86e3 | ||
|
|
95db349973 | ||
|
|
2acef590b4 | ||
|
|
29855b2b6e | ||
|
|
ea10dd68a3 | ||
|
|
e1942c66ac | ||
|
|
6bd18e8078 | ||
|
|
f3dd306f54 | ||
|
|
3cf39ea48b | ||
|
|
d57f212c62 | ||
|
|
b273c52124 | ||
|
|
ce0a9afc33 | ||
|
|
8437fe1716 | ||
|
|
f63f645696 | ||
|
|
f7267806df | ||
|
|
bebf1906a2 | ||
|
|
58e61e0bda | ||
|
|
42aa62cff0 | ||
|
|
bcf7b3fccb | ||
|
|
ff09378c6d | ||
|
|
99b0595e50 | ||
|
|
5321469c1d | ||
|
|
ef9eb83a75 | ||
|
|
c12bbc7f0f | ||
|
|
a16c4c3657 | ||
|
|
c8240448c6 | ||
|
|
d19569131c | ||
|
|
37eec4c971 | ||
|
|
65fe679ee2 | ||
|
|
a16b825c3b | ||
|
|
b9aedc9165 | ||
|
|
8bdbbc562c | ||
|
|
696f6c096c | ||
|
|
d0ef6aef1c | ||
|
|
7204b51439 | ||
|
|
6075a9d22f | ||
|
|
b15cfa1892 | ||
|
|
955375f423 | ||
|
|
2aa13a499a | ||
|
|
fb0e61f3b0 | ||
|
|
16149fdd38 | ||
|
|
a432080141 | ||
|
|
73072daa46 | ||
|
|
2f57345bd2 | ||
|
|
06075d561d | ||
|
|
db8ad7cde5 | ||
|
|
cacf39da69 | ||
|
|
4dc7937321 | ||
|
|
1c462ce152 | ||
|
|
2014d2495e | ||
|
|
dc5493f39f | ||
|
|
67ff5e5a4a | ||
|
|
22d56d31a9 | ||
|
|
785427674a | ||
|
|
581f6c6e1c | ||
|
|
7d0be85e84 | ||
|
|
04e75ab540 | ||
|
|
c117e33d24 | ||
|
|
cafdad6f9f | ||
|
|
05e212682c | ||
|
|
3f876202c1 | ||
|
|
57c75799a9 | ||
|
|
5cbf2d8047 | ||
|
|
3dd9ad9ead | ||
|
|
864bef483f | ||
|
|
9ea9fefe1d | ||
|
|
0b953a1517 | ||
|
|
80db293aca | ||
|
|
809be41e64 | ||
|
|
8a6ee68e58 | ||
|
|
da764721d9 | ||
|
|
0586639e07 | ||
|
|
0aeab00289 | ||
|
|
9564097f46 | ||
|
|
d4750f4c6e | ||
|
|
f1cc2fe72e | ||
|
|
6d90539e89 | ||
|
|
21f5ed6afd | ||
|
|
4cbbf2ac81 | ||
|
|
a035a2525a | ||
|
|
9727601752 | ||
|
|
9e9053c0eb | ||
|
|
507d495f3f | ||
|
|
a2f7547cdb | ||
|
|
d48308d913 | ||
|
|
78c0badd95 | ||
|
|
c81c33a430 | ||
|
|
1786376a9e | ||
|
|
aa973ea984 | ||
|
|
67aeeb0b2f | ||
|
|
ee2b01673b | ||
|
|
c392f7ee8d | ||
|
|
4adc950e29 | ||
|
|
78db5b55ec | ||
|
|
ad40fdcb55 | ||
|
|
c5ca510a75 | ||
|
|
13c64a95fa | ||
|
|
5d74a78bb7 | ||
|
|
d638bae7c4 | ||
|
|
9faf8ed55e | ||
|
|
3aa23b0f9d | ||
|
|
063bbc9f06 | ||
|
|
16bb23b8a7 | ||
|
|
a26bef6317 | ||
|
|
21c41d5af1 | ||
|
|
b270096591 | ||
|
|
be689f088d | ||
|
|
2410409342 | ||
|
|
a24d682747 | ||
|
|
2ada44a2fc | ||
|
|
d6c7fb8dcc | ||
|
|
bb40a8612a | ||
|
|
1182a6eba4 | ||
|
|
0d303889ef |
5
.babelrc
5
.babelrc
@@ -17,6 +17,9 @@
|
||||
]
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-syntax-dynamic-import"
|
||||
"@babel/plugin-syntax-dynamic-import",
|
||||
"@babel/plugin-transform-modules-umd",
|
||||
"@babel/plugin-transform-runtime",
|
||||
"@babel/plugin-proposal-class-properties"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
"no-multiple-empty-lines": [1, {"max": 2}],
|
||||
"comma-dangle": [2, "always-multiline"],
|
||||
"standard/no-callback-literal": "off",
|
||||
"prefer-const": "off"
|
||||
"prefer-const": "off",
|
||||
"no-labels": "off",
|
||||
"node/no-callback-literal": "off"
|
||||
},
|
||||
"settings": {
|
||||
"html/html-extensions": [".html", ".vue"]
|
||||
|
||||
25
.github/ISSUE_TEMPLATE/----.md
vendored
Normal file
25
.github/ISSUE_TEMPLATE/----.md
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
name: 功能请求(请先查看常见问题及搜索issue列表中有无你要提的问题)
|
||||
about: 为这个项目提出一个想法
|
||||
title: 例如:添加xxx功能、优化xxx功能
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**解决方案检查**
|
||||
<!-- 请确保你已从以下渠道寻找过解决方案,然后将 [ ] 替换成 [x] -->
|
||||
- [ ] 我已阅读常见问题(<https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md>)
|
||||
- [ ] 我已搜索issue列表(<https://github.com/lyswhut/lx-music-desktop/issues?utf8=✓&q=>)
|
||||
|
||||
**描述您想要的解决方案**
|
||||
<!-- 简洁明了地描述您要发生的事情。 -->
|
||||
|
||||
|
||||
**描述您考虑过的替代方案**
|
||||
<!-- 对您考虑过的所有替代解决方案或功能的简洁明了的描述。 -->
|
||||
|
||||
|
||||
**其他内容**
|
||||
<!-- 在此处添加有关功能请求的任何其他上下文或屏幕截图(直接把图片拖到编辑框即可添加图片)。 -->
|
||||
|
||||
42
.github/ISSUE_TEMPLATE/--bug.md
vendored
Normal file
42
.github/ISSUE_TEMPLATE/--bug.md
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: 报告Bug(请先查看常见问题及搜索issue列表中有无你要提的问题)
|
||||
about: 创建报告以帮助我们改进
|
||||
title: 例如:音乐无法播放
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**解决方案检查**
|
||||
<!-- 请确保你已从以下渠道寻找过解决方案,然后将 [ ] 替换成 [x] -->
|
||||
- [ ] 我已阅读常见问题(<https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md>)
|
||||
- [ ] 我已搜索issue列表(<https://github.com/lyswhut/lx-music-desktop/issues?utf8=✓&q=>)
|
||||
|
||||
**描述错误**
|
||||
<!-- 清楚简洁地说明错误是什么。 -->
|
||||
|
||||
|
||||
**重现**
|
||||
重现行为的步骤:
|
||||
1.转到“ ...”
|
||||
2.点击“ ....”
|
||||
3.向下滚动到“ ....”
|
||||
4.看到错误
|
||||
|
||||
|
||||
**预期行为**
|
||||
<!-- 对您期望发生的事情的简洁明了的描述。 -->
|
||||
|
||||
|
||||
**截图**
|
||||
<!-- 如果适用,请添加屏幕截图以帮助解释您的问题(直接把图片拖到编辑框即可添加图片)。 -->
|
||||
|
||||
|
||||
**环境:**
|
||||
-操作系统及版本:[例如:Windows 10 64位 18362.156]
|
||||
-软件安装包及版本:[例如:1.0.0 安装版]
|
||||
|
||||
|
||||
**其他内容**
|
||||
<!-- 在此处添加有关该问题的任何其他上下文。 -->
|
||||
|
||||
233
.github/workflows/beta-pack.yml
vendored
Normal file
233
.github/workflows/beta-pack.yml
vendored
Normal file
@@ -0,0 +1,233 @@
|
||||
name: Build Beta
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- beta
|
||||
|
||||
jobs:
|
||||
Windows:
|
||||
name: Windows
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14.x'
|
||||
|
||||
- name: Cache file
|
||||
uses: actions/cache@v2
|
||||
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: Build src code
|
||||
run: npm run build:src
|
||||
|
||||
- name: Build Package Setup x64
|
||||
run: npm run pack:win:setup:x64
|
||||
- name: Upload Artifact Setup x64
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: lx-music-desktop-x64-Setup
|
||||
path: build/* x64 Setup.exe
|
||||
|
||||
- name: Build Package Setup x86
|
||||
run: npm run pack:win:setup:x86
|
||||
- name: Upload Artifact Setup x86
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: lx-music-desktop-x86-Setup
|
||||
path: build/* x86 Setup.exe
|
||||
|
||||
- name: Build Package Setup arm64
|
||||
run: npm run pack:win:setup:arm64
|
||||
- name: Upload Artifact Setup arm64
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: lx-music-desktop-arm64-Setup
|
||||
path: build/* arm64 Setup.exe
|
||||
|
||||
- name: Build Package Setup x86_64
|
||||
run: npm run pack:win:setup:x86_64
|
||||
- name: Upload Artifact Setup x86_64
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: lx-music-desktop-x86_64-Setup
|
||||
path: build/*x86_64 Setup.exe
|
||||
|
||||
- name: Build Package 7z x64
|
||||
run: npm run pack:win:7z:x64
|
||||
- name: Upload Artifact 7z x64
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: lx-music-desktop-win_x64-green
|
||||
path: build/*win_x64 green.7z
|
||||
|
||||
- name: Build Package 7z x86
|
||||
run: npm run pack:win:7z:x86
|
||||
- name: Upload Artifact 7z x86
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: lx-music-desktop-win_x86-green
|
||||
path: build/*win_x86 green.7z
|
||||
|
||||
- name: Build Package 7z arm64
|
||||
run: npm run pack:win:7z:arm64
|
||||
- name: Upload Artifact 7z arm64
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: lx-music-desktop-win_arm64-green
|
||||
path: build/*win_arm64 green.7z
|
||||
|
||||
Mac:
|
||||
name: Mac
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14.x'
|
||||
|
||||
- name: Cache file
|
||||
uses: actions/cache@v2
|
||||
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: Build src code
|
||||
run: npm run build:src
|
||||
|
||||
- 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@v2
|
||||
with:
|
||||
name: lx-music-desktop-mac-dmg
|
||||
path: |
|
||||
build/*.dmg
|
||||
!build/*-arm64.dmg
|
||||
- name: Upload Artifact dmg
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: lx-music-desktop-mac-dmg-arm64
|
||||
path: build/*-arm64.dmg
|
||||
|
||||
Linux:
|
||||
name: Linux
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install package
|
||||
run: sudo apt-get install -y rpm libarchive-tools
|
||||
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14.x'
|
||||
|
||||
- name: Cache file
|
||||
uses: actions/cache@v2
|
||||
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: Build src code
|
||||
run: npm run build:src
|
||||
|
||||
- name: Build Package deb x64
|
||||
run: npm run pack:linux:deb:x64
|
||||
- name: Upload Artifact deb x64
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: lx-music-desktop-deb-x64
|
||||
path: build/* x64.deb
|
||||
|
||||
- name: Build Package deb x86
|
||||
run: npm run pack:linux:deb:x86
|
||||
- name: Upload Artifact deb x86
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: lx-music-desktop-deb-x86
|
||||
path: build/* x86.deb
|
||||
|
||||
- name: Build Package deb arm64
|
||||
run: npm run pack:linux:deb:arm64
|
||||
- name: Upload Artifact deb arm64
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: lx-music-desktop-deb-arm64
|
||||
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@v2
|
||||
with:
|
||||
name: lx-music-desktop-deb-armv7l
|
||||
path: build/* armv7l.deb
|
||||
|
||||
- name: Build Package x64 appImage
|
||||
run: npm run pack:linux:appImage
|
||||
- name: Upload Artifact x64 appImage
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: lx-music-desktop-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@v2
|
||||
with:
|
||||
name: lx-music-desktop-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@v2
|
||||
with:
|
||||
name: lx-music-desktop-x64-pacman
|
||||
path: build/* x64.pacman
|
||||
136
.github/workflows/release.yml
vendored
Normal file
136
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
Windows:
|
||||
name: Windows
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14.x'
|
||||
|
||||
- name: Cache file
|
||||
uses: actions/cache@v2
|
||||
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: Build src code
|
||||
run: npm run build:src
|
||||
|
||||
- name: Release package
|
||||
run: |
|
||||
npm run publish:win:setup:x64:always
|
||||
npm run publish:win:setup:x86
|
||||
npm run publish:win:setup:arm64
|
||||
npm run publish:win:setup:x86_64
|
||||
npm run publish:win:7z:x64
|
||||
npm run publish:win:7z:x86
|
||||
npm run publish:win:7z:arm64
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BT_TOKEN: ${{ secrets.BT_TOKEN }}
|
||||
|
||||
Mac:
|
||||
name: Mac
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14.x'
|
||||
|
||||
- name: Cache file
|
||||
uses: actions/cache@v2
|
||||
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: Build src code
|
||||
run: npm run build:src
|
||||
|
||||
- name: Release package
|
||||
run: |
|
||||
npm run publish:mac:dmg:always
|
||||
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 }}
|
||||
|
||||
Linux:
|
||||
name: Linux
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install package
|
||||
run: sudo apt-get install -y rpm libarchive-tools
|
||||
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14.x'
|
||||
|
||||
- name: Cache file
|
||||
uses: actions/cache@v2
|
||||
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: Build src code
|
||||
run: npm run build:src
|
||||
|
||||
- name: Release package
|
||||
run: |
|
||||
npm run publish:linux:deb:x64:always
|
||||
npm run publish:linux:deb:x86
|
||||
npm run publish:linux:deb:arm64
|
||||
npm run publish:linux:deb:armv7l
|
||||
npm run publish:linux:appImage
|
||||
npm run publish:linux:rpm
|
||||
npm run publish:linux:pacman
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BT_TOKEN: ${{ secrets.BT_TOKEN }}
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -34,6 +34,7 @@ build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
node_modules.bak*/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
@@ -68,3 +69,5 @@ dist
|
||||
publish/assets
|
||||
|
||||
publish/utils/githubToken.js
|
||||
|
||||
src/**/*-internal.js
|
||||
|
||||
7
.ncurc.js
Normal file
7
.ncurc.js
Normal file
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
upgrade: true,
|
||||
reject: [
|
||||
'vue-loader'
|
||||
// 'eslint-config-standard'
|
||||
]
|
||||
}
|
||||
19
.vscode/settings.json
vendored
Normal file
19
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"path-intellisense.mappings": {
|
||||
"@main/*": "${workspaceFolder}/src/main/*",
|
||||
"@renderer/*": "${workspaceFolder}/src/renderer/*",
|
||||
"@lyric/*": "${workspaceFolder}/src/renderer-lyric/*",
|
||||
"@static/*": "${workspaceFolder}/src/static/*",
|
||||
"@common/*": "${workspaceFolder}/src/common/*",
|
||||
},
|
||||
"i18n-ally.localesPaths": [
|
||||
"src/renderer/lang"
|
||||
],
|
||||
"i18n-ally.displayLanguage": "zh-cn",
|
||||
"i18n-ally.sourceLanguage": "zh-cn",
|
||||
"i18n-ally.namespace": true,
|
||||
"i18n-ally.translate.engines": ["google-cn", "google"],
|
||||
"i18n-ally.pathMatcher": "{locale}/{namespaces}.json",
|
||||
"i18n-ally.keystyle": "flat",
|
||||
"i18n-ally.sortKeys": true
|
||||
}
|
||||
1005
CHANGELOG.md
1005
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
376
FAQ.md
Normal file
376
FAQ.md
Normal file
@@ -0,0 +1,376 @@
|
||||
# lx-music-desktop 常见问题
|
||||
|
||||
在阅读本常见问题后,仍然无法解决你的问题,请提交issue或者加企鹅群`830125506`反馈(无事勿加,入群先看群公告),反馈时请**注明**已阅读常见问题!
|
||||
|
||||
## ~~软件为什么没有桌面歌词与自定义列表功能~~
|
||||
|
||||
洛雪音乐的最初定位不是作为播放器开发的,它主要用于**查找歌曲**,软件的播放功能仅用于试听,不建议用作为常用播放器使用。
|
||||
|
||||
## 歌曲无法试听与下载
|
||||
|
||||
### 所有歌曲都提示 `请求异常😮,可以多试几次,若还是不行就换一首吧。。。`
|
||||
|
||||
尝试更换网络,如切换到移动网络,若移动网络还是不行则尝试开关下手机的飞行模式后再试,<br>
|
||||
若使用家庭网络的话,可尝试将光猫断电5分钟左右再通电联网后播放。
|
||||
|
||||
### 提示 `getaddrinfo EAI_AGAIN ...` 或 `无法连接到服务器`
|
||||
|
||||
尝试在在浏览器打开这个地址`http://ts.tempmusic.tk`,浏览器显示404是正常的,如果不是404那就证明所在网络无法访问接口服务器。
|
||||
若网页无法打开或打开来不是404,则可能是DNS的问题,可以尝试以下办法:
|
||||
|
||||
1. 将DNS改成自动获取试试(注:改完可能需要清理下系统DNS缓存才生效)
|
||||
2. 手动把DNS改一下,不要用360的DNS,可以把DNS改成`114.114.114.114`、`8.8.8.8`(注:改完可能需要清理下系统DNS缓存才生效)
|
||||
|
||||
改完DNS后可能需要重启软件才生效
|
||||
|
||||
### 通用解决方法
|
||||
|
||||
尝试按以下顺序解决:
|
||||
|
||||
1. 尝试更新到最新版本
|
||||
2. 尝试切换其他歌曲(或直接搜索该歌曲),若全部歌曲都无法试听与下载则进行下一步
|
||||
3. 尝试到 设置-音乐来源 切换到其他接口
|
||||
4. 尝试切换网络,比如用手机开热点(所有歌曲都提示请求异常时可通过此方法解决,或等一两天后再试)
|
||||
5. 若还不行请到这个链接查看详情:<https://github.com/lyswhut/lx-music-desktop/issues/5>
|
||||
6. 若没有在第5条链接中的第一条评论中看到接口无法使用的说明,则应该是你网络无法访问接口服务器的问题,如果接口有问题我会在那里说明。
|
||||
|
||||
想要知道是不是自己网络的问题可以看看`http://ts.tempmusic.tk`能不能在浏览器打开,浏览器显示404是正常的,如果不是404那就证明所在网络无法访问接口服务器。
|
||||
若网页无法打开或打来不是404,则应该是DNS的问题,可以尝试以下办法:
|
||||
|
||||
1. 将DNS改成自动获取试试
|
||||
2. 手动把DNS改一下,不要用360的DNS,可以把DNS改成`114.114.114.114`、`8.8.8.8`
|
||||
|
||||
## 列表多选
|
||||
|
||||
从v0.18.0起,列表多选需要键盘配合,想要多选前需按下`Shift`或`Ctrl`键然后再鼠标点击想要选中的内容即可触发多选机制,其中`Shift`键用于连续选择,`Ctrl`键用于不连续选择,`Ctrl+a`用于快速全选。
|
||||
|
||||
- 例子一:想要选中1-5项,则先按下`Shift`键后,鼠标点击第一项,再点击第五项即可完成选择;
|
||||
- 例子二:想要选中1项与第3项,则先按下`Ctrl`键后,鼠标点击第一项,再点击第三项即可完成选择;
|
||||
- 例子三:想要选中当前列表的全部内容,键盘先按下`Ctrl`键不放,然后按`a`键,即可完成选择。
|
||||
|
||||
用`Shift`或`Ctrl`选择时,鼠标点击未选中的内容会将其选中,点击已选择的内容会将其取消选择,若想全部取消选择,在不按`Shift`或`Alt`键的情况下,随意点击列表里的一项内容即可全部取消选择。(P.S:`Ctrl`键对应Mac OS上的`Command`键)
|
||||
|
||||
注:选完后可用鼠标右击弹出右键菜单操作已选的内容
|
||||
|
||||
## 播放整个歌单或排行榜
|
||||
|
||||
播放在线列表内的歌曲需要将它们都添加到我的列表才能播放,你可以全选列表内的歌曲然后添加到现有列表或者新创建的列表,然后去播放该列表内的歌曲。
|
||||
|
||||
## 无法打开外部歌单
|
||||
|
||||
不支持垮源打开歌单,请**确认**你需要打开的歌单平台是否与软件标签所写的**歌单源**对应(不一样的话请通过右上角切换歌单源);<br>
|
||||
对于分享出来的歌单,若打开失败,可尝试先在浏览器中打开后,再从浏览器地址栏复制URL地址到软件打开;<br>
|
||||
或者如果你知道歌单 id 也可以直接输入歌单 id 打开。<br>
|
||||
|
||||
注:网易源的“我喜欢”歌单无法在未登录的情况下打开,所以你需要手动创建一个歌单后将“我喜欢”里的歌曲移动到该歌单打开
|
||||
|
||||
## 更新已收藏的在线歌单
|
||||
|
||||
该功能仅对直接从歌单详情页点“收藏”按钮收藏的歌单有效,可右击已收藏的列表名从弹出的菜单中选择“更新”使用该功能,
|
||||
|
||||
需要注意的是:这将会覆盖本地的目标列表,歌曲将被替换成最新的在线列表。
|
||||
|
||||
|
||||
## 同步功能的使用(实验性,首次使用前建议先备份一次列表)
|
||||
|
||||
**注意:由于同步传输时的数据是明文传输,请在受信任的网络下使用此功能!**<br>
|
||||
此功能需要配合移动端使用,PC端与移动端处在同一个局域网(路由器的网络)下时,可以多端实时同步歌曲列表,使用方法:
|
||||
|
||||
1. 在PC端的设置-数据同步开启同步功能(这时如果出现安全软件、防火墙等提示网络连接弹窗时需要点击允许)
|
||||
2. 在移动端的设置-同步-同步服务器地址输入PC端显示的同步服务器地址(如果显示可以多个,则输入与**移动端上显示的本机地址**最相似的那个),端口号与PC端的同步端口一致(**输入完毕后需要按一下键盘上的回车键使输入的内容生效**)
|
||||
3. 输入完这两项后点击“启动同步”
|
||||
4. 若连接成功,对于首次同步时,若两边的设备的列表不为空,则PC端会弹出选择列表同步方式的弹窗,同步方式的说明弹窗下面有介绍
|
||||
|
||||
对于连接同步服务失败的可能原因:
|
||||
|
||||
- 此功能需要PC端与移动端都连接在同一个路由器下的网络才能使用
|
||||
- 路由器若开启了AP隔离,则此功能无法使用
|
||||
- 检查防火墙是否拦截了PC端的服务端口
|
||||
|
||||
|
||||
## 界面异常(界面显示不完整)
|
||||
|
||||
### Windows 7 下界面异常
|
||||
|
||||
由于软件默认使用了透明窗口,根据Electron官方文档的[说明](https://electronjs.org/docs/api/frameless-window#%E5%B1%80%E9%99%90%E6%80%A7):
|
||||
> 在 windows 操作系统上, 当 DWM 被禁用时, 透明窗口将无法工作。
|
||||
|
||||
因此,当 win7 没有使用**AERO**主题时界面将会显示异常,开启AERO的方法请自行百度:`win7开启aero效果`(开启后可看到任务栏变透明)。<br>
|
||||
从`0.14.0`版本起不再强制要求开启透明效果,若你实在不想开启(若非电脑配置太低,墙裂建议开启!),可通过添加运行参数`-dt`来运行程序即可,例如:`.\lx-music-desktop.exe -dt`,添加方法可自行百度“给快捷方式加参数”,该参数的作用是用来控制程序是否使用非透明窗口运行。
|
||||
|
||||
注:启用**AERO**主题后,若软件出现黑边框,则重启软件即可恢复正常。
|
||||
|
||||
对于一些完全无法正常显示界面、开启了AERO后问题仍未解决的情况,请阅读下面的 **软件启动后,界面无法显示** 解决。
|
||||
|
||||
### Linux 下界面异常
|
||||
|
||||
根据Electron里issue的[解决方案](https://github.com/electron/electron/issues/2170#issuecomment-736223269),<br>
|
||||
若你遇到透明问题可尝试添加启动参数 `-dha` 来禁用硬件加速,例如:`.\lx-music-desktop.exe -dha`。
|
||||
|
||||
注:v1.6.0及之后的版本才支持`-dha`参数
|
||||
|
||||
## 软件启动后,界面无法显示
|
||||
|
||||
对于软件启动后,可以在任务栏看到软件,但软件界面在桌面上无任何显示,或者整个界面偶尔闪烁的情况。<br>
|
||||
原始问题看:<https://github.com/electron/electron/issues/19569#issuecomment-522231083><br>
|
||||
解决办法:下载`.NET Framework 4.7.1`或**更高**版本安装即可(建议安装最新版,若安装过程中遇到问题可尝试自行百度解决)。<br>
|
||||
微软官方下载地址:<https://dotnet.microsoft.com/download/dotnet-framework><br>
|
||||
下载`Runtime(运行时)`版即可,安装完成后可能需要重启才生效。
|
||||
|
||||
若还是不行可尝试以下操作:
|
||||
|
||||
- 更新显卡驱动
|
||||
- 添加启动参数`-dha`运行(添加的方法请自行百度“给快捷方式加参数”)
|
||||
- 尝试将绿色版的软件放在**桌面**或**我的文档**运行
|
||||
|
||||
## 安装版安装失败,提示安装程序并未成功地运行完成
|
||||
|
||||
对于部分电脑出现安装失败的问题,可以做出以下尝试:
|
||||
|
||||
- 若你之前可以安装成功,但现在安装失败,就去**控制面板-程序和功能**或用第三方卸载工具看下有没有之前的版本残留,若同时在不同路径下安装了多个版本就可能会出现该问题,这种情况卸载掉所有版本重新安装即可
|
||||
- 清理安装路径下的残留文件
|
||||
- 清理注册表(建议用清理工具清理)
|
||||
|
||||
## 软件无法联网
|
||||
|
||||
软件的排行榜、歌单、搜索列表**都**无法加载:
|
||||
|
||||
- 检查是否在设置界面开启了代理(当代理乱设置时软件将无法联网)
|
||||
- 检查软件是否被第三方软件/防火墙阻止联网
|
||||
|
||||
## 桌面歌词显示异常
|
||||
|
||||
### Windows 7 系统桌面歌词显示异常
|
||||
|
||||
Windows 7 未开启 Aero 效果时桌面歌词会有问题,详情看上面的 **Windows 7 下界面异常** 方法解决。
|
||||
|
||||
### MAC OS 系统、桌面歌词有残留阴影
|
||||
|
||||
此问题似乎是Electron的Bug,翻阅electron的issue列表发现该Bug以存在很久了,遗憾的是没有一直都没有修复,由于我没有装MAC平台的电脑,没法重现,就没再去electron提issue,更多信息看:
|
||||
|
||||
- <https://github.com/electron/electron/issues/21173>
|
||||
- <https://github.com/electron/electron/issues/14304>
|
||||
|
||||
### Linux 系统下桌面歌词窗口异常
|
||||
|
||||
`v1.2.1`以前的版本在 Ubuntu 18.10 下第一次开启桌面歌词时歌词窗口会变白,需要关闭后再开启,
|
||||
`v1.2.1`及之后的版本已修复该问题。
|
||||
|
||||
其他 Linux 系统未测试,如有异常也是意料之中,目前不打算去处理 Linux 平台的桌面歌词问题,但你可以尝试按照`Linux 下界面异常`的解决方案去解决。
|
||||
|
||||
## 歌曲下载失败,提示 `ENOENT: no such file or directory, mkdir`
|
||||
|
||||
更换下载歌曲目录即可解决(一般是设置的歌曲下载目录没有读写权限导致的)。
|
||||
|
||||
## 使用软件时导致耳机意外关机
|
||||
|
||||
据反馈,漫步者部分型号的耳机与本软件一起使用时将会导致耳机意外关机,
|
||||
详情看:<https://github.com/lyswhut/lx-music-desktop/issues/457>,
|
||||
若出现该问题可尝试添加`-dhmkh`启动参数解决,启动参数添加方法请自行百度“windows给快捷方式添加启动参数”。
|
||||
|
||||
## 软件安装包说明
|
||||
|
||||
软件发布页及网盘中有多个类型的安装文件,以下是对这些类型文件的说明:
|
||||
|
||||
文件名带 `win_` 的是在Windows系统上运行的版本,<br>
|
||||
其中安装版(Setup)可自动更新软件,<br>
|
||||
绿色版(green)为免安装版,自动更新功能不可用;
|
||||
|
||||
以 **`.dmg`** 结尾的文件为 MAC 版本;
|
||||
|
||||
以 **`.AppImage`**、**`.deb`** 结尾的为 Linux 版本。
|
||||
|
||||
带有`x64`的为64位的系统版本,带`x86`的为32位的系统版本;若两个都带有的则为集合版,安装时会自动根据系统位数选择对应的版本安装。
|
||||
|
||||
## 软件更新
|
||||
|
||||
软件启动时若发现新版本时会自动从本仓库下载安装包,下载完毕会弹窗提示更新。<br>
|
||||
若下载未完成时软件被关闭,下次启动软件会再次自动下载。<br>
|
||||
若还是**更新失败**,可能是无法访问GitHub导致的,这时需要手动更新,即下载最新安装包直接覆盖安装即可。<br>
|
||||
注意:**绿色版**的软件自动更新功能**不可用**,建议使用安装版!!<br>
|
||||
注意:**Mac版**、**Linux deb**版不支持自动更新!
|
||||
|
||||
## 更新已收藏的在线歌单
|
||||
|
||||
该功能仅对直接从歌单详情页点“收藏”按钮收藏的歌单有效,可右击已收藏的列表名从弹出的菜单中选择“同步”使用该功能,
|
||||
|
||||
需要注意的是:这将会覆盖本地的目标列表,歌曲将被替换成最新的在线列表。
|
||||
|
||||
## 缺少`xxx.dll`
|
||||
|
||||
这个是电脑缺少某些dll导致的,正常的系统是没有这个问题的,可以尝试如下几个解决办法:
|
||||
|
||||
- 以管理员权限打开`cmd`,输入`sfc /scannow`回车等待检查完成重启电脑
|
||||
- 若上面的方法**修复、重启**电脑后仍然不行,就自行百度弹出的**错误信息**看下别人是怎么解决的
|
||||
|
||||
## 杀毒软件提示有病毒或恶意行为
|
||||
|
||||
本人只能保证我写的代码不包含任何**恶意代码**、**收集用户信息**的行为,并且软件代码已开源,请自行查阅,软件安装包也是由CI拉取源代码构建,构建日志:[GitHub Actions](https://github.com/lyswhut/lx-music-desktop/actions)<br>
|
||||
尽管如此,但这不意味着软件是100%安全的,由于软件使用了第三方依赖,当这些依赖存在恶意行为时([供应链攻击](https://docs.microsoft.com/zh-cn/windows/security/threat-protection/intelligence/supply-chain-malware)),软件也将会受到牵连,所以我只能尽量选择使用较多人用、信任度较高的依赖。<br>
|
||||
当然,以上说明建立的前提是在你所用的安装包是从**本项目主页上写的链接**下载的,或者有相关能力者还可以下载源代码自己构建安装包。
|
||||
|
||||
从`v0.17.0`起,由于加入了音频输出设备切换功能,该功能调用了 [MediaDevices.enumerateDevices()](https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices/enumerateDevices),可能导致安全软件提示洛雪要访问摄像头(目前发现卡巴斯基会提示),但实际上没有用到摄像头,并且摄像头的提示灯也不会亮,你可以选择阻止访问。
|
||||
|
||||
最后,若出现杀毒软件报毒、存在恶意行为,请自行判断选择是否继续使用本软件!
|
||||
|
||||
## 自定义源脚本编写说明
|
||||
|
||||
文件请使用UTF-8编码格式编写,脚本所用编程语言为JavaScript,可以使用ES6+语法,脚本与应用的交互是使用类似事件收发的方式进行,这是一个基本的脚本例子:
|
||||
|
||||
```js
|
||||
/**
|
||||
* @name 测试音乐源
|
||||
* @description 我只是一个测试音乐源哦
|
||||
* @version 1.0.0
|
||||
* @author xxx
|
||||
* @homepage http://xxx
|
||||
*/
|
||||
|
||||
|
||||
const { EVENT_NAMES, request, on, send } = window.lx
|
||||
|
||||
const qualitys = {
|
||||
kw: {
|
||||
'128k': '128',
|
||||
'320k': '320',
|
||||
flac: 'flac',
|
||||
},
|
||||
}
|
||||
const httpRequest = (url, options) => new Promise((resolve, reject) => {
|
||||
request(url, options, (err, resp) => {
|
||||
if (err) return reject(err)
|
||||
resolve(resp.body)
|
||||
})
|
||||
})
|
||||
|
||||
const apis = {
|
||||
kw: {
|
||||
musicUrl({ songmid }, quality) {
|
||||
return httpRequest('http://xxx').then(data => {
|
||||
return data.url
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// 注册应用API请求事件
|
||||
// source 音乐源,可能的值取决于初始化时传入的sources对象的源key值
|
||||
// info 请求附加信息,内容根据action变化
|
||||
// action 请求操作类型,目前只有musicUrl,即获取音乐URL链接,
|
||||
// 当action为musicUrl时info的结构:{type, musicInfo},
|
||||
// info.type:音乐质量,可能的值有128k / 320k / flac(取决于初始化时对应源传入的qualitys值中的一个),
|
||||
// info.musicInfo:音乐信息对象,里面有音乐ID、名字等信息
|
||||
on(EVENT_NAMES.request, ({ source, action, info }) => {
|
||||
// 回调必须返回 Promise 对象
|
||||
switch (action) {
|
||||
// action 为 musicUrl 时需要在 Promise 返回歌曲 url
|
||||
case 'musicUrl':
|
||||
return apis[source].musicUrl(info.musicInfo, qualitys[source][info.type]).catch(err => {
|
||||
console.log(err)
|
||||
return Promise.reject(err)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 脚本初始化完成后需要发送inited事件告知应用
|
||||
send(EVENT_NAMES.inited, {
|
||||
status: true, // 初始化成功 or 失败
|
||||
openDevTools: false, // 是否打开开发者工具,方便用于调试脚本
|
||||
sources: { // 当前脚本支持的源
|
||||
kw: { // 支持的源对象,可用key值:kw/kg/tx/wy/mg
|
||||
name: '酷我音乐',
|
||||
type: 'music', // 目前固定为 music
|
||||
actions: ['musicUrl'], // 目前固定为 ['musicUrl']
|
||||
qualitys: ['128k', '320k', 'flac'], // 当前脚本的该源所支持获取的Url音质,有效的值有:['128k', '320k', 'flac']
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
### 自定义源信息
|
||||
|
||||
文件的开头必须包含以下注释:
|
||||
|
||||
```js
|
||||
/**
|
||||
* @name 测试脚本
|
||||
* @description 我只是一个测试脚本
|
||||
* @version 1.0.0
|
||||
* @author xxx
|
||||
* @homepage http://xxx
|
||||
*/
|
||||
|
||||
```
|
||||
|
||||
- `@name `:源的名字,建议不要过长,10个字符以内
|
||||
- `@description `:源的描述,建议不要过长,20个字符以内,可不填,不填时必须保留 @description
|
||||
- `@version`:源的版本号,可不填,不填时可以删除 @version
|
||||
- `@author `:脚本作者名字,可不填,不填时可以删除 @author
|
||||
- `@homepage `:脚本主页,可不填,不填时可以删除 @homepage
|
||||
|
||||
### `window.lx`
|
||||
|
||||
应用为脚本暴露的API对象。
|
||||
|
||||
#### `window.lx.EVENT_NAMES`
|
||||
|
||||
常量事件名称对象,发送、注册事件时传入事件名时使用,可用值:
|
||||
|
||||
| 事件名 | 描述
|
||||
| --- | ---
|
||||
| `inited` | 脚本初始化完成后发送给应用的事件名,发送该事件时需要传入以下信息:`{status, sources, openDevTools}`<br>`status`:初始化结果(`true`成功,`false`失败)<br>`openDevTools`:是否打开DevTools,此选项可用于开发脚本时的调试<br>`sources`:支持的源信息对象,<br>`sources[kw/kg/tx/wy/mg].name`:源的名字(目前非必须)<br>`sources[kw/kg/tx/wy/mg].type`:源类型,目前固定值需为`music`<br>`sources[kw/kg/tx/wy/mg].actions`:支持的actions,由于目前只支持`musicUrl`,所以固定传`['musicUrl']`即可<br>`sources[kw/kg/tx/wy/mg].qualitys`:该源支持的音质列表,有效的值为`['128k', '320k', 'flac']`,该字段用于控制应用可用的音质类型
|
||||
| `request` | 应用API请求事件名,回调入参:`handler({ source, action, info})`,回调必须返回`Promise`对象<br>`source`:音乐源,可能的值取决于初始化时传入的`sources`对象的源key值<br>`info`:请求附加信息,内容根据`action`变化<br>`action`:请求操作类型,目前只有`musicUrl`,即获取音乐URL链接,需要在 Promise 返回歌曲 url,`info`的结构:`{type, musicInfo}`,`info.type`:音乐质量,可能的值有`128k` / `320k` / `flac`(取决于初始化时对应源传入的`qualitys`值中的一个),`info.musicInfo`:音乐信息对象,里面有音乐ID、名字等信息
|
||||
|
||||
|
||||
#### `window.lx.on`
|
||||
|
||||
事件注册方法,应用主动与脚本通信时使用:
|
||||
|
||||
```js
|
||||
/**
|
||||
* @param event_name 事件名
|
||||
* @param handler 事件处理回调 -- 注意:注册的回调必须返回 Promise 对象
|
||||
*/
|
||||
window.lx.on(event_name, handler)
|
||||
```
|
||||
|
||||
**注意:** 注册的回调必须返回 `Promise` 对象。
|
||||
|
||||
#### `window.lx.send`
|
||||
|
||||
事件发送方法,脚本主动与应用通信时使用:
|
||||
|
||||
```js
|
||||
/**
|
||||
* @param event_name 事件名
|
||||
* @param datas 要传给应用的数据
|
||||
*/
|
||||
window.lx.send(event_name, datas)
|
||||
```
|
||||
|
||||
#### `window.lx.request`
|
||||
|
||||
HTTP请求方法,用于发送HTTP请求,此HTTP请求方法不受跨域规则限制:
|
||||
|
||||
```js
|
||||
/**
|
||||
* @param url 请求的URL
|
||||
* @param options 请求选项,可用选项有 method / headers / body / form / formData / timeout
|
||||
* @param callback 请求结果的回调 入参:err, resp, body
|
||||
* @return 返回一个方法,调用此方法可以终止HTTP请求
|
||||
*/
|
||||
const cancelHttp = window.lx.request(url, options, callback)
|
||||
```
|
||||
|
||||
#### `window.lx.utils`
|
||||
|
||||
应用提供给脚本的工具方法:
|
||||
|
||||
- `window.lx.utils.buffer.from`:对应Node.js的 `Buffer.from`
|
||||
- `window.lx.utils.crypto.aesEncrypt`:AES加密 `aesEncrypt(buffer, mode, key, iv)`
|
||||
- `window.lx.utils.crypto.md5`:MD5加密 `md5(str)`
|
||||
- `window.lx.utils.crypto.randomBytes`:生成随机字符串 `randomBytes(size)`
|
||||
- `window.lx.utils.crypto.rsaEncrypt`:RSA加密 `rsaEncrypt(buffer, key)`
|
||||
|
||||
目前仅提供以上工具方法,如果需要其他方法可以开issue讨论。
|
||||
80
README.md
80
README.md
@@ -2,8 +2,10 @@
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/lyswhut/lx-music-desktop/releases"><img src="https://img.shields.io/github/release/lyswhut/lx-music-desktop" alt="Release version"></a>
|
||||
<a href="https://ci.appveyor.com/project/lyswhut/lx-music-desktop"><img src="https://ci.appveyor.com/api/projects/status/flrsqd5ymp8fnte5?svg=true" alt="Build status"></a>
|
||||
<a href="https://github.com/lyswhut/lx-music-desktop/releases"><img src="https://img.shields.io/github/downloads/lyswhut/lx-music-desktop/latest/total" alt="Downloads"></a>
|
||||
<a href="https://github.com/lyswhut/lx-music-desktop/actions/workflows/release.yml"><img src="https://github.com/lyswhut/lx-music-desktop/workflows/Build/badge.svg" alt="Build status"></a>
|
||||
<a href="https://github.com/lyswhut/lx-music-desktop/actions/workflows/beta-pack.yml"><img src="https://github.com/lyswhut/lx-music-desktop/workflows/Build%20Beta/badge.svg" alt="Build status"></a>
|
||||
<a href="https://electronjs.org/releases/stable"><img src="https://img.shields.io/github/package-json/dependency-version/lyswhut/lx-music-desktop/dev/electron/master" alt="Electron version"></a>
|
||||
<!-- <a href="https://github.com/lyswhut/lx-music-desktop/releases"><img src="https://img.shields.io/github/downloads/lyswhut/lx-music-desktop/latest/total" alt="Downloads"></a> -->
|
||||
<a href="https://github.com/lyswhut/lx-music-desktop/tree/dev"><img src="https://img.shields.io/github/package-json/v/lyswhut/lx-music-desktop/dev" alt="Dev branch version"></a>
|
||||
<!-- <a href="https://github.com/lyswhut/lx-music-desktop/blob/master/LICENSE"><img src="https://img.shields.io/github/license/lyswhut/lx-music-desktop" alt="License"></a> -->
|
||||
</p>
|
||||
@@ -30,29 +32,27 @@
|
||||
|
||||
### 说明
|
||||
|
||||
一个基于 Electron + Vue 开发的 Windows 版音乐软件。
|
||||
一个基于 Electron + Vue 开发的音乐软件。
|
||||
|
||||
所用技术栈:
|
||||
|
||||
- Electron 6.x
|
||||
- Vue 2.x
|
||||
- Electron 13
|
||||
- Vue 2
|
||||
|
||||
已支持的平台:
|
||||
|
||||
- Windows 7 及以上
|
||||
- Mac OS
|
||||
- Linux
|
||||
|
||||
软件变化请查看:[更新日志](https://github.com/lyswhut/lx-music-desktop/blob/master/CHANGELOG.md)<br>
|
||||
软件下载请转到:[发布页面](https://github.com/lyswhut/lx-music-desktop/releases)<br>
|
||||
或者到网盘下载:<br>
|
||||
`https://www.lanzous.com/b906260/`<br>
|
||||
`密码:glqw`
|
||||
|
||||
#### 关于软件更新
|
||||
|
||||
软件启动时若发现新版本时会自动从本仓库下载安装包,下载完毕会弹窗提示更新。<br>
|
||||
若下载未完成时软件被关闭,下次启动软件会再次自动下载。<br>
|
||||
目前暂未添加跳过更新某个版本的功能。<br>
|
||||
**注意:**绿色版的软件更新功能不可用!
|
||||
或者到网盘下载(网盘内有MAC、windows版):`https://www.lanzoui.com/b0bf2cfa/` 密码:`glqw`(若链接无法打开请百度:蓝奏云链接打不开)<br>
|
||||
使用常见问题请转至:[常见问题](https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md)
|
||||
|
||||
### 源码使用方法
|
||||
|
||||
环境要求:Node.js 12.x
|
||||
环境要求:Node.js 14+
|
||||
|
||||
```bash
|
||||
# 开发模式
|
||||
@@ -61,8 +61,14 @@ npm run dev
|
||||
# 构建免安装版
|
||||
npm run pack:dir
|
||||
|
||||
# 构建安装包
|
||||
npm run pack
|
||||
# 构建安装包(Windows版)
|
||||
npm run pack:win
|
||||
|
||||
# 构建安装包(Mac版)
|
||||
npm run pack:mac
|
||||
|
||||
# 构建安装包(Linux版)
|
||||
npm run pack:linux
|
||||
|
||||
```
|
||||
|
||||
@@ -70,10 +76,40 @@ npm run pack
|
||||
|
||||
<p><a href="https://github.com/lyswhut/lx-music-desktop"><img width="100%" src="https://github.com/lyswhut/lx-music-desktop/blob/master/doc/images/app.png" alt="lx-music UI"></a></p>
|
||||
|
||||
### 致谢
|
||||
### 启动参数
|
||||
|
||||
感谢 [@messoer](https://github.com/messoer) 提供的部分音乐API!
|
||||
目前软件已支持的启动参数如下:
|
||||
|
||||
### 许可证
|
||||
- `-search` 启动软件时自动在搜索框搜索指定的内容,例如:`-search="突然的自我 - 伍佰"`
|
||||
- `-dha` 禁用硬件加速启动(Disable Hardware Acceleration),窗口显示有问题时可以尝试添加此参数启动(v1.6.0起新增)
|
||||
- `-dt` 以非透明模式启动(Disable Transparent),对于未开启AERO效果的win7系统可加此参数启动以确保界面正常显示(注:该参数对桌面歌词无效),原来的`-nt`参数已重命名为`-dt`(v1.6.0起重命名)
|
||||
- `-dhmkh` 禁用硬件媒体密钥处理(Disable Hardware Media Key Handling),此选项将禁用Chromium的Hardware Media Key Handling特性(v1.9.0起新增)
|
||||
- `-play` 启动时播放指定列表的音乐,参数说明:
|
||||
- `type`:播放类型,目前固定为`songList`
|
||||
- `source`:播放源,可用值为`kw/kg/tx/wy/mg/myList`,其中`kw/kg/tx/wy/mg`对应各源的在线列表,`myList`为本地列表
|
||||
- `link`:要播放的在线列表歌单链接、或ID,source为`kw/kg/tx/wy/mg`之一(在线列表)时必传,举例:`./lx-music-desktop -play="type=songList&source=kw&link=歌单URL or ID"`,注意:如果传入URL时必须对URL进行编码后再传入
|
||||
- `name`:要播放的本地列表歌单名字,source为`myList`时必传,举例:`./lx-music-desktop -play="type=songList&source=myList&name=默认列表"`
|
||||
- `index`:从列表的哪个位置开始播放,选传,若不传默认播放第一首歌曲,举例:`./lx-music-desktop -play="type=songList&source=myList&name=默认列表&index=2"`
|
||||
|
||||
[Apache License 2.0](https://github.com/lyswhut/lx-music-desktop/blob/master/LICENSE)
|
||||
|
||||
### 常见问题
|
||||
|
||||
常见问题已移至:<https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md>
|
||||
|
||||
### 项目协议
|
||||
|
||||
本项目基于 [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 面向全世界人用作对技术的学习交流,本项目不对项目内的技术可能存在违反当地法律法规的行为作保证,**禁止在违反当地法律法规的情况下使用本项目**,对于使用者在明知或不知当地法律法规不允许的情况下使用本项目所造成的任何违法违规行为由使用者承担,本项目不承担由此造成的任何直接、间接、特殊、偶然或结果性责任。
|
||||
|
||||
若你使用了本项目,将代表你接受以上协议。
|
||||
|
||||
音乐平台不易,请尊重版权,支持正版。<br>
|
||||
若对此有疑问请 mail to: lyswhut+qq.com (请将`+`替换成`@`)
|
||||
|
||||
20
appveyor.yml
20
appveyor.yml
@@ -1,20 +0,0 @@
|
||||
platform:
|
||||
- x64
|
||||
|
||||
cache:
|
||||
- node_modules
|
||||
- '%APPDATA%\npm-cache'
|
||||
- '%LOCALAPPDATA%\electron\Cache'
|
||||
|
||||
install:
|
||||
- ps: Install-Product node 12 x64
|
||||
- npm install
|
||||
|
||||
build_script:
|
||||
- npm run publish:gh
|
||||
|
||||
test: off
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
@@ -2,7 +2,8 @@ const isDev = process.env.NODE_ENV === 'development'
|
||||
|
||||
module.exports = {
|
||||
modules: {
|
||||
localIdentName: isDev ? '[folder]-[name]--[local]--[hash:base64:5]' : '[hash:base64:5]',
|
||||
localIdentName: isDev ? '[path][name]__[local]--[hash:base64:5]' : '[hash:base64:5]',
|
||||
exportLocalsConvention: 'camelCase',
|
||||
},
|
||||
localsConvention: 'camelCase',
|
||||
sourceMap: isDev,
|
||||
}
|
||||
|
||||
@@ -9,7 +9,10 @@ module.exports = {
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
common: path.join(__dirname, '../../src/common'),
|
||||
'@main': path.join(__dirname, '../../src/main'),
|
||||
'@renderer': path.join(__dirname, '../../src/renderer'),
|
||||
'@lyric': path.join(__dirname, '../../src/renderer-lyric'),
|
||||
'@common': path.join(__dirname, '../../src/common'),
|
||||
},
|
||||
extensions: ['*', '.js', '.json', '.node'],
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const path = require('path')
|
||||
const merge = require('webpack-merge')
|
||||
const { merge } = require('webpack-merge')
|
||||
const webpack = require('webpack')
|
||||
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
|
||||
|
||||
const baseConfig = require('./webpack.config.base')
|
||||
|
||||
@@ -16,7 +17,19 @@ module.exports = merge(baseConfig, {
|
||||
NODE_ENV: '"development"',
|
||||
},
|
||||
__static: `"${path.join(__dirname, '../../src/static').replace(/\\/g, '\\\\')}"`,
|
||||
__userApi: `"${path.join(__dirname, '../../src/main/modules/userApi').replace(/\\/g, '\\\\')}"`,
|
||||
}),
|
||||
new FriendlyErrorsPlugin({
|
||||
onErrors(severity, errors) { // Silent warning from electron-debug
|
||||
if (severity != 'warning') return
|
||||
|
||||
for (let i = errors.length - 1; i > -1; i--) {
|
||||
const error = errors[i]
|
||||
if (error.file == './node_modules/electron-debug/index.js') errors.splice(i, 1)
|
||||
}
|
||||
// You can listen to errors transformed and prioritized by the plugin
|
||||
// severity can be 'error' or 'warning'
|
||||
},
|
||||
}),
|
||||
new webpack.NoEmitOnErrorsPlugin(),
|
||||
],
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const path = require('path')
|
||||
const merge = require('webpack-merge')
|
||||
const { merge } = require('webpack-merge')
|
||||
const webpack = require('webpack')
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
|
||||
const baseConfig = require('./webpack.config.base')
|
||||
|
||||
@@ -20,6 +21,18 @@ module.exports = merge(baseConfig, {
|
||||
__filename: false,
|
||||
},
|
||||
plugins: [
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: path.join(__dirname, '../../src/main/modules/userApi/renderer'),
|
||||
to: path.join(__dirname, '../../dist/electron/userApi/renderer'),
|
||||
},
|
||||
{
|
||||
from: path.join(__dirname, '../../src/main/modules/userApi/rendererEvent/name.js'),
|
||||
to: path.join(__dirname, '../../dist/electron/userApi/rendererEvent/name.js'),
|
||||
},
|
||||
],
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: '"production"',
|
||||
|
||||
@@ -3,52 +3,67 @@ process.env.NODE_ENV = 'production'
|
||||
const chalk = require('chalk')
|
||||
const del = require('del')
|
||||
const webpack = require('webpack')
|
||||
const Multispinner = require('multispinner')
|
||||
const Spinnies = require('spinnies')
|
||||
|
||||
const mainConfig = require('./main/webpack.config.prod')
|
||||
const rendererConfig = require('./renderer/webpack.config.prod')
|
||||
const rendererLyricConfig = require('./renderer-lyric/webpack.config.prod')
|
||||
|
||||
const errorLog = chalk.bgRed.white(' ERROR ') + ' '
|
||||
const okayLog = chalk.bgGreen.white(' OKAY ') + ' '
|
||||
|
||||
|
||||
function build() {
|
||||
del.sync(['dist/electron', 'build'])
|
||||
|
||||
const tasks = ['main', 'renderer']
|
||||
const m = new Multispinner(tasks, {
|
||||
preText: 'building',
|
||||
postText: 'process',
|
||||
})
|
||||
del.sync(['dist/electron/**', 'build/**'])
|
||||
|
||||
const spinners = new Spinnies({ color: 'blue' })
|
||||
spinners.add('main', { text: 'main building' })
|
||||
spinners.add('renderer', { text: 'renderer building' })
|
||||
spinners.add('renderer-lyric', { text: 'renderer-lyric building' })
|
||||
let results = ''
|
||||
|
||||
m.on('success', () => {
|
||||
// m.on('success', () => {
|
||||
// process.stdout.write('\x1B[2J\x1B[0f')
|
||||
// console.log(`\n\n${results}`)
|
||||
// console.log(`${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n`)
|
||||
// process.exit()
|
||||
// })
|
||||
function handleSuccess() {
|
||||
process.stdout.write('\x1B[2J\x1B[0f')
|
||||
console.log(`\n\n${results}`)
|
||||
console.log(`${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n`)
|
||||
process.exit()
|
||||
})
|
||||
}
|
||||
|
||||
pack(mainConfig).then(result => {
|
||||
results += result + '\n\n'
|
||||
m.success('main')
|
||||
}).catch(err => {
|
||||
m.error('main')
|
||||
console.log(`\n ${errorLog}failed to build main process`)
|
||||
console.error(`\n${err}\n`)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
pack(rendererConfig).then(result => {
|
||||
results += result + '\n\n'
|
||||
m.success('renderer')
|
||||
}).catch(err => {
|
||||
m.error('renderer')
|
||||
console.log(`\n ${errorLog}failed to build renderer process`)
|
||||
console.error(`\n${err}\n`)
|
||||
process.exit(1)
|
||||
})
|
||||
Promise.all([
|
||||
pack(mainConfig).then(result => {
|
||||
results += result + '\n\n'
|
||||
spinners.succeed('main', { text: 'main build success!' })
|
||||
}).catch(err => {
|
||||
spinners.fail('main', { text: 'main build fail :(' })
|
||||
console.log(`\n ${errorLog}failed to build main process`)
|
||||
console.error(`\n${err}\n`)
|
||||
process.exit(1)
|
||||
}),
|
||||
pack(rendererConfig).then(result => {
|
||||
results += result + '\n\n'
|
||||
spinners.succeed('renderer', { text: 'renderer build success!' })
|
||||
}).catch(err => {
|
||||
spinners.fail('renderer', { text: 'renderer build fail :(' })
|
||||
console.log(`\n ${errorLog}failed to build renderer process`)
|
||||
console.error(`\n${err}\n`)
|
||||
process.exit(1)
|
||||
}),
|
||||
pack(rendererLyricConfig).then(result => {
|
||||
results += result + '\n\n'
|
||||
spinners.succeed('renderer-lyric', { text: 'renderer-lyric build success!' })
|
||||
}).catch(err => {
|
||||
spinners.fail('renderer-lyric', { text: 'renderer-lyric build fail :(' })
|
||||
console.log(`\n ${errorLog}failed to build renderer-lyric process`)
|
||||
console.error(`\n${err}\n`)
|
||||
process.exit(1)
|
||||
}),
|
||||
]).then(handleSuccess)
|
||||
}
|
||||
|
||||
function pack(config) {
|
||||
|
||||
156
build-config/renderer-lyric/webpack.config.base.js
Normal file
156
build-config/renderer-lyric/webpack.config.base.js
Normal file
@@ -0,0 +1,156 @@
|
||||
const path = require('path')
|
||||
const VueLoaderPlugin = require('vue-loader/lib/plugin')
|
||||
const HTMLPlugin = require('html-webpack-plugin')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const CleanCSSPlugin = require('less-plugin-clean-css')
|
||||
|
||||
const vueLoaderConfig = require('../vue-loader.config')
|
||||
const { mergeCSSLoader } = require('../utils')
|
||||
|
||||
const isDev = process.env.NODE_ENV === 'development'
|
||||
|
||||
module.exports = {
|
||||
target: 'electron-renderer',
|
||||
entry: {
|
||||
'renderer-lyric': path.join(__dirname, '../../src/renderer-lyric/main.js'),
|
||||
},
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
libraryTarget: 'commonjs2',
|
||||
path: path.join(__dirname, '../../dist/electron'),
|
||||
publicPath: 'auto',
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@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: ['*', '.js', '.json', '.vue', '.node'],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(vue|js)$/,
|
||||
use: {
|
||||
loader: 'eslint-loader',
|
||||
options: {
|
||||
formatter: require('eslint-formatter-friendly'),
|
||||
emitWarning: isDev,
|
||||
},
|
||||
},
|
||||
exclude: /node_modules/,
|
||||
enforce: 'pre',
|
||||
},
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader',
|
||||
options: vueLoaderConfig,
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
oneOf: mergeCSSLoader(),
|
||||
},
|
||||
{
|
||||
test: /\.less$/,
|
||||
oneOf: mergeCSSLoader({
|
||||
loader: 'less-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
lessOptions: {
|
||||
plugins: [
|
||||
new CleanCSSPlugin({ advanced: true }),
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
test: /\.styl(:?us)?$/,
|
||||
oneOf: mergeCSSLoader({
|
||||
loader: 'stylus-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
test: /\.pug$/,
|
||||
oneOf: [
|
||||
// Use pug-plain-loader handle .vue file
|
||||
{
|
||||
resourceQuery: /vue/,
|
||||
use: ['pug-plain-loader'],
|
||||
},
|
||||
// Use pug-loader handle .pug file
|
||||
{
|
||||
use: ['pug-loader'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
||||
type: 'asset',
|
||||
parser: {
|
||||
dataUrlCondition: {
|
||||
maxSize: 10000,
|
||||
},
|
||||
},
|
||||
generator: {
|
||||
filename: 'imgs/[name]-[contenthash:8][ext]',
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
|
||||
type: 'asset',
|
||||
parser: {
|
||||
dataUrlCondition: {
|
||||
maxSize: 10000,
|
||||
},
|
||||
},
|
||||
generator: {
|
||||
filename: 'media/[name]-[contenthash:8][ext]',
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
||||
type: 'asset',
|
||||
parser: {
|
||||
dataUrlCondition: {
|
||||
maxSize: 10000,
|
||||
},
|
||||
},
|
||||
generator: {
|
||||
filename: 'fonts/[name]-[contenthash:8][ext]',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
performance: {
|
||||
maxEntrypointSize: 300000,
|
||||
},
|
||||
plugins: [
|
||||
new HTMLPlugin({
|
||||
filename: 'lyric.html',
|
||||
template: path.join(__dirname, '../../src/renderer-lyric/index.pug'),
|
||||
isProd: process.env.NODE_ENV == 'production',
|
||||
browser: process.browser,
|
||||
scriptLoading: 'blocking',
|
||||
__dirname,
|
||||
}),
|
||||
new VueLoaderPlugin(),
|
||||
new MiniCssExtractPlugin({
|
||||
// Options similar to the same options in webpackOptions.output
|
||||
// both options are optional
|
||||
filename: isDev ? '[name].css' : '[name].[contenthash:8].css',
|
||||
chunkFilename: isDev ? '[id].css' : '[id].[contenthash:8].css',
|
||||
}),
|
||||
],
|
||||
}
|
||||
27
build-config/renderer-lyric/webpack.config.dev.js
Normal file
27
build-config/renderer-lyric/webpack.config.dev.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
|
||||
|
||||
const { merge } = require('webpack-merge')
|
||||
|
||||
const baseConfig = require('./webpack.config.base')
|
||||
|
||||
module.exports = merge(baseConfig, {
|
||||
mode: 'development',
|
||||
devtool: 'eval-source-map',
|
||||
plugins: [
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new FriendlyErrorsPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: '"development"',
|
||||
ELECTRON_DISABLE_SECURITY_WARNINGS: 'true',
|
||||
},
|
||||
__static: `"${path.join(__dirname, '../../src/static').replace(/\\/g, '\\\\')}"`,
|
||||
}),
|
||||
],
|
||||
performance: {
|
||||
hints: false,
|
||||
},
|
||||
})
|
||||
|
||||
51
build-config/renderer-lyric/webpack.config.prod.js
Normal file
51
build-config/renderer-lyric/webpack.config.prod.js
Normal file
@@ -0,0 +1,51 @@
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
|
||||
const TerserPlugin = require('terser-webpack-plugin')
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
const { merge } = require('webpack-merge')
|
||||
|
||||
const baseConfig = require('./webpack.config.base')
|
||||
|
||||
const { dependencies } = require('../../package.json')
|
||||
|
||||
let whiteListedModules = ['vue']
|
||||
|
||||
|
||||
module.exports = merge(baseConfig, {
|
||||
mode: 'production',
|
||||
devtool: false,
|
||||
externals: [
|
||||
...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)),
|
||||
],
|
||||
plugins: [
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: path.join(__dirname, '../../src/static'),
|
||||
to: path.join(__dirname, '../../dist/electron/static'),
|
||||
},
|
||||
],
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: '"production"',
|
||||
},
|
||||
}),
|
||||
],
|
||||
optimization: {
|
||||
minimizer: [
|
||||
new TerserPlugin(),
|
||||
new CssMinimizerPlugin(),
|
||||
],
|
||||
},
|
||||
performance: {
|
||||
hints: 'warning',
|
||||
},
|
||||
node: {
|
||||
__dirname: false,
|
||||
__filename: false,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
const path = require('path')
|
||||
const VueLoaderPlugin = require('vue-loader/lib/plugin')
|
||||
const HTMLPlugin = require('html-webpack-plugin')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const CleanCSSPlugin = require('less-plugin-clean-css')
|
||||
|
||||
const vueLoaderConfig = require('../vue-loader.config')
|
||||
const { mergeCSSLoader } = require('../utils')
|
||||
|
||||
const isDev = process.env.NODE_ENV === 'development'
|
||||
|
||||
module.exports = {
|
||||
target: 'electron-renderer',
|
||||
@@ -14,12 +18,15 @@ module.exports = {
|
||||
filename: '[name].js',
|
||||
libraryTarget: 'commonjs2',
|
||||
path: path.join(__dirname, '../../dist/electron'),
|
||||
publicPath: './',
|
||||
publicPath: 'auto',
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.join(__dirname, '../../src/renderer'),
|
||||
common: path.join(__dirname, '../../src/common'),
|
||||
'@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: ['*', '.js', '.json', '.vue', '.node'],
|
||||
},
|
||||
@@ -31,6 +38,7 @@ module.exports = {
|
||||
loader: 'eslint-loader',
|
||||
options: {
|
||||
formatter: require('eslint-formatter-friendly'),
|
||||
emitWarning: isDev,
|
||||
},
|
||||
},
|
||||
exclude: /node_modules/,
|
||||
@@ -46,6 +54,33 @@ module.exports = {
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
oneOf: mergeCSSLoader(),
|
||||
},
|
||||
{
|
||||
test: /\.less$/,
|
||||
oneOf: mergeCSSLoader({
|
||||
loader: 'less-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
lessOptions: {
|
||||
plugins: [
|
||||
new CleanCSSPlugin({ advanced: true }),
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
test: /\.styl(:?us)?$/,
|
||||
oneOf: mergeCSSLoader({
|
||||
loader: 'stylus-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
test: /\.pug$/,
|
||||
oneOf: [
|
||||
@@ -62,26 +97,38 @@ module.exports = {
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'imgs/[name]--[folder].[ext]',
|
||||
type: 'asset',
|
||||
parser: {
|
||||
dataUrlCondition: {
|
||||
maxSize: 10000,
|
||||
},
|
||||
},
|
||||
generator: {
|
||||
filename: 'imgs/[name]-[contenthash:8][ext]',
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'media/[name]--[folder].[ext]',
|
||||
type: 'asset',
|
||||
parser: {
|
||||
dataUrlCondition: {
|
||||
maxSize: 10000,
|
||||
},
|
||||
},
|
||||
generator: {
|
||||
filename: 'media/[name]-[contenthash:8][ext]',
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'fonts/[name]--[folder].[ext]',
|
||||
type: 'asset',
|
||||
parser: {
|
||||
dataUrlCondition: {
|
||||
maxSize: 10000,
|
||||
},
|
||||
},
|
||||
generator: {
|
||||
filename: 'fonts/[name]-[contenthash:8][ext]',
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -92,11 +139,18 @@ module.exports = {
|
||||
plugins: [
|
||||
new HTMLPlugin({
|
||||
filename: 'index.html',
|
||||
template: path.join(__dirname, '../../src/index.pug'),
|
||||
template: path.join(__dirname, '../../src/renderer/index.pug'),
|
||||
isProd: process.env.NODE_ENV == 'production',
|
||||
browser: process.browser,
|
||||
scriptLoading: 'blocking',
|
||||
__dirname,
|
||||
}),
|
||||
new VueLoaderPlugin(),
|
||||
new MiniCssExtractPlugin({
|
||||
// Options similar to the same options in webpackOptions.output
|
||||
// both options are optional
|
||||
filename: isDev ? '[name].css' : '[name].[contenthash:8].css',
|
||||
chunkFilename: isDev ? '[id].css' : '[id].[contenthash:8].css',
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
@@ -2,44 +2,15 @@ const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
|
||||
|
||||
const merge = require('webpack-merge')
|
||||
const { merge } = require('webpack-merge')
|
||||
|
||||
const baseConfig = require('./webpack.config.base')
|
||||
|
||||
const { mergeCSSLoaderDev } = require('../utils')
|
||||
|
||||
module.exports = merge(baseConfig, {
|
||||
mode: 'development',
|
||||
devtool: 'eval-source-map',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
oneOf: mergeCSSLoaderDev(),
|
||||
},
|
||||
{
|
||||
test: /\.less$/,
|
||||
oneOf: mergeCSSLoaderDev({
|
||||
loader: 'less-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
test: /\.styl(:?us)?$/,
|
||||
oneOf: mergeCSSLoaderDev({
|
||||
loader: 'stylus-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NoEmitOnErrorsPlugin(),
|
||||
new FriendlyErrorsPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
|
||||
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
|
||||
const TerserPlugin = require('terser-webpack-plugin')
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
const merge = require('webpack-merge')
|
||||
const { merge } = require('webpack-merge')
|
||||
|
||||
const baseConfig = require('./webpack.config.base')
|
||||
|
||||
const { mergeCSSLoaderProd } = require('../utils')
|
||||
const { dependencies } = require('../../package.json')
|
||||
|
||||
let whiteListedModules = ['vue']
|
||||
@@ -20,58 +18,25 @@ module.exports = merge(baseConfig, {
|
||||
externals: [
|
||||
...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)),
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
oneOf: mergeCSSLoaderProd(),
|
||||
},
|
||||
{
|
||||
test: /\.less$/,
|
||||
oneOf: mergeCSSLoaderProd({
|
||||
loader: 'less-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
test: /\.styl(:?us)?$/,
|
||||
oneOf: mergeCSSLoaderProd({
|
||||
loader: 'stylus-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
from: path.join(__dirname, '../../src/static'),
|
||||
to: path.join(__dirname, '../../dist/electron/static'),
|
||||
ignore: ['.*'],
|
||||
},
|
||||
]),
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: path.join(__dirname, '../../src/static'),
|
||||
to: path.join(__dirname, '../../dist/electron/static'),
|
||||
},
|
||||
],
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: '"production"',
|
||||
},
|
||||
}),
|
||||
new MiniCssExtractPlugin({
|
||||
filename: '[name].css',
|
||||
}),
|
||||
new webpack.NamedChunksPlugin(),
|
||||
],
|
||||
optimization: {
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
cache: true,
|
||||
parallel: true,
|
||||
sourceMap: false, // set to true if you want JS source maps
|
||||
}),
|
||||
new OptimizeCSSAssetsPlugin({}),
|
||||
new TerserPlugin(),
|
||||
new CssMinimizerPlugin(),
|
||||
],
|
||||
},
|
||||
performance: {
|
||||
|
||||
@@ -7,12 +7,12 @@ const path = require('path')
|
||||
const { spawn } = require('child_process')
|
||||
const webpack = require('webpack')
|
||||
const WebpackDevServer = require('webpack-dev-server')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const webpackHotMiddleware = require('webpack-hot-middleware')
|
||||
|
||||
const mainConfig = require('./main/webpack.config.dev')
|
||||
const rendererConfig = require('./renderer/webpack.config.dev')
|
||||
|
||||
const { logStats } = require('./utils')
|
||||
const rendererLyricConfig = require('./renderer-lyric/webpack.config.dev')
|
||||
|
||||
let electronProcess = null
|
||||
let manualRestart = false
|
||||
@@ -31,7 +31,7 @@ function startRenderer() {
|
||||
|
||||
compiler.hooks.compilation.tap('compilation', compilation => {
|
||||
// console.log(Object.keys(compilation.hooks))
|
||||
compilation.hooks.htmlWebpackPluginAfterEmit.tapAsync('html-webpack-plugin-after-emit', (data, cb) => {
|
||||
HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync('html-webpack-plugin-after-emit', (data, cb) => {
|
||||
hotMiddleware.publish({ action: 'reload' })
|
||||
cb()
|
||||
})
|
||||
@@ -59,13 +59,60 @@ function startRenderer() {
|
||||
resolve()
|
||||
})
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
server.listen(9080)
|
||||
})
|
||||
}
|
||||
|
||||
function startRendererLyric() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// rendererConfig.entry.renderer = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.renderer)
|
||||
// rendererConfig.mode = 'development'
|
||||
const compiler = webpack(rendererLyricConfig)
|
||||
hotMiddleware = webpackHotMiddleware(compiler, {
|
||||
log: false,
|
||||
heartbeat: 2500,
|
||||
})
|
||||
|
||||
compiler.hooks.compilation.tap('compilation', compilation => {
|
||||
// console.log(Object.keys(compilation.hooks))
|
||||
HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync('html-webpack-plugin-after-emit', (data, cb) => {
|
||||
hotMiddleware.publish({ action: 'reload' })
|
||||
cb()
|
||||
})
|
||||
})
|
||||
|
||||
compiler.hooks.done.tap('done', stats => {
|
||||
// logStats('Renderer', 'Compile done')
|
||||
// logStats('Renderer', stats)
|
||||
})
|
||||
|
||||
const server = new WebpackDevServer(
|
||||
compiler,
|
||||
{
|
||||
contentBase: path.join(__dirname, '../'),
|
||||
quiet: true,
|
||||
hot: true,
|
||||
historyApiFallback: true,
|
||||
clientLogLevel: 'warning',
|
||||
overlay: {
|
||||
errors: true,
|
||||
},
|
||||
before(app, ctx) {
|
||||
app.use(hotMiddleware)
|
||||
ctx.middleware.waitUntilValid(() => {
|
||||
resolve()
|
||||
})
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
server.listen(9081)
|
||||
})
|
||||
}
|
||||
|
||||
function startMain() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// mainConfig.entry.main = [path.join(__dirname, '../src/main/index.dev.js')].concat(mainConfig.entry.main)
|
||||
@@ -73,7 +120,6 @@ function startMain() {
|
||||
const compiler = webpack(mainConfig)
|
||||
|
||||
compiler.hooks.watchRun.tapAsync('watch-run', (compilation, done) => {
|
||||
logStats('Main', chalk.white.bold('compiling...'))
|
||||
hotMiddleware.publish({ action: 'compiling' })
|
||||
done()
|
||||
})
|
||||
@@ -133,38 +179,33 @@ function startElectron() {
|
||||
function electronLog(data, color) {
|
||||
let log = data.toString()
|
||||
if (/[0-9A-z]+/.test(log)) {
|
||||
// 抑制 user api 窗口使用 data url 加载页面时 vue扩展 的报错日志刷屏的问题
|
||||
if (color == 'red' && typeof log === 'string' && log.includes('"Extension server error: Operation failed: Permission denied", source: devtools://devtools/bundled/extensions/extensions.js')) return
|
||||
|
||||
console.log(chalk[color](log))
|
||||
}
|
||||
}
|
||||
|
||||
function greeting() {
|
||||
/* const cols = process.stdout.columns
|
||||
let text = ''
|
||||
|
||||
if (cols > 104) text = 'electron-vue'
|
||||
else if (cols > 76) text = 'electron-|vue'
|
||||
else text = false
|
||||
|
||||
if (text) {
|
||||
say(text, {
|
||||
colors: ['yellow'],
|
||||
font: 'simple3d',
|
||||
space: false,
|
||||
})
|
||||
} else console.log(chalk.yellow.bold('\n electron-vue')) */
|
||||
console.log(chalk.blue('getting ready...') + '\n')
|
||||
}
|
||||
|
||||
function init() {
|
||||
greeting()
|
||||
const Spinnies = require('spinnies')
|
||||
const spinners = new Spinnies({ color: 'blue' })
|
||||
spinners.add('main', { text: 'main compiling' })
|
||||
spinners.add('renderer', { text: 'renderer compiling' })
|
||||
spinners.add('renderer-lyric', { text: 'renderer-lyric compiling' })
|
||||
function handleSuccess(name) {
|
||||
spinners.succeed(name, { text: name + ' compile success!' })
|
||||
}
|
||||
function handleFail(name) {
|
||||
spinners.fail(name, { text: name + ' compile fail!' })
|
||||
}
|
||||
|
||||
Promise.all([startRenderer(), startMain()])
|
||||
.then(() => {
|
||||
startElectron()
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
})
|
||||
Promise.all([
|
||||
startRenderer().then(() => handleSuccess('renderer')).catch(() => handleFail('renderer')),
|
||||
startRendererLyric().then(() => handleSuccess('renderer-lyric')).catch(() => handleFail('renderer-lyric')),
|
||||
startMain().then(() => handleSuccess('main')).catch(() => handleFail('main')),
|
||||
]).then(startElectron).catch(err => {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
init()
|
||||
|
||||
@@ -2,62 +2,19 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const cssLoaderConfig = require('./css-loader.config')
|
||||
const chalk = require('chalk')
|
||||
|
||||
// merge css-loader in development
|
||||
exports.mergeCSSLoaderDev = beforeLoader => {
|
||||
// merge css-loader
|
||||
exports.mergeCSSLoader = beforeLoader => {
|
||||
const loader = [
|
||||
// 这里匹配 `<style module>`
|
||||
{
|
||||
resourceQuery: /module/,
|
||||
use: [
|
||||
'vue-style-loader',
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: Object.assign({
|
||||
sourceMap: true,
|
||||
}, cssLoaderConfig),
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
options: {
|
||||
sourceMap: true,
|
||||
esModule: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// 这里匹配普通的 `<style>` 或 `<style scoped>`
|
||||
{
|
||||
use: [
|
||||
'vue-style-loader',
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
if (beforeLoader) {
|
||||
loader[0].use.push(beforeLoader)
|
||||
loader[1].use.push(beforeLoader)
|
||||
}
|
||||
return loader
|
||||
}
|
||||
|
||||
// merge css-loader in production
|
||||
exports.mergeCSSLoaderProd = beforeLoader => {
|
||||
const loader = [
|
||||
// 这里匹配 `<style module>`
|
||||
{
|
||||
resourceQuery: /module/,
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: cssLoaderConfig,
|
||||
@@ -68,7 +25,12 @@ exports.mergeCSSLoaderProd = beforeLoader => {
|
||||
// 这里匹配普通的 `<style>` 或 `<style scoped>`
|
||||
{
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
options: {
|
||||
esModule: false,
|
||||
},
|
||||
},
|
||||
'css-loader',
|
||||
'postcss-loader',
|
||||
],
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
const HTMLPlugin = require('html-webpack-plugin')
|
||||
const VueLoaderPlugin = require('vue-loader/lib/plugin')
|
||||
|
||||
const vueLoaderConfig = require('../vue-loader.config')
|
||||
|
||||
module.exports = {
|
||||
target: 'web',
|
||||
entry: path.join(__dirname, '../../src/renderer/main.js'),
|
||||
output: {
|
||||
path: path.join(__dirname, '../../dist/web'),
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.join(__dirname, '../../src/renderer'),
|
||||
common: path.join(__dirname, '../../src/common'),
|
||||
},
|
||||
extensions: ['*', '.js', '.vue', '.json', '.css'],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(vue|js)$/,
|
||||
use: {
|
||||
loader: 'eslint-loader',
|
||||
options: {
|
||||
formatter: require('eslint-formatter-friendly'),
|
||||
},
|
||||
},
|
||||
exclude: /node_modules/,
|
||||
enforce: 'pre',
|
||||
},
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader',
|
||||
options: vueLoaderConfig,
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
{
|
||||
test: /\.pug$/,
|
||||
oneOf: [
|
||||
// Use pug-plain-loader handle .vue file
|
||||
{
|
||||
resourceQuery: /vue/,
|
||||
use: ['pug-plain-loader'],
|
||||
},
|
||||
// Use pug-loader handle .pug file
|
||||
{
|
||||
use: ['pug-loader'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
||||
use: {
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'imgs/[name].[ext]',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
||||
use: {
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'fonts/[name].[ext]',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
performance: {
|
||||
maxEntrypointSize: 300000,
|
||||
},
|
||||
plugins: [
|
||||
new VueLoaderPlugin(),
|
||||
new HTMLPlugin({
|
||||
filename: 'index.html',
|
||||
template: path.resolve(__dirname, '../../src/index.pug'),
|
||||
nodeModules: false,
|
||||
isProd: process.env.NODE_ENV == 'production',
|
||||
browser: process.browser,
|
||||
__dirname,
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.IS_WEB': 'true',
|
||||
}),
|
||||
],
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
const merge = require('webpack-merge')
|
||||
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
|
||||
|
||||
const baseConfig = require('./webpack.config.base')
|
||||
|
||||
const { mergeCSSLoaderDev } = require('../utils')
|
||||
|
||||
module.exports = merge(baseConfig, {
|
||||
mode: 'development',
|
||||
devtool: 'eval-source-map',
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
path: path.join(__dirname, '../../dist/web'),
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
oneOf: mergeCSSLoaderDev(),
|
||||
},
|
||||
{
|
||||
test: /\.less$/,
|
||||
oneOf: mergeCSSLoaderDev({
|
||||
loader: 'less-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
test: /\.styl$/,
|
||||
oneOf: mergeCSSLoaderDev({
|
||||
loader: 'stylus-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: '"development"',
|
||||
},
|
||||
}),
|
||||
new FriendlyErrorsPlugin(),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NoEmitOnErrorsPlugin(),
|
||||
],
|
||||
performance: {
|
||||
hints: false,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
const merge = require('webpack-merge')
|
||||
const TerserPlugin = require('terser-webpack-plugin')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
|
||||
const baseConfig = require('./webpack.config.base')
|
||||
|
||||
const { mergeCSSLoaderProd } = require('../utils')
|
||||
|
||||
|
||||
module.exports = merge(baseConfig, {
|
||||
mode: 'production',
|
||||
devtool: false,
|
||||
output: {
|
||||
filename: '[name].[chunkhash:8].js',
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
oneOf: mergeCSSLoaderProd(),
|
||||
},
|
||||
{
|
||||
test: /\.less$/,
|
||||
oneOf: mergeCSSLoaderProd({
|
||||
loader: 'less-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
test: /\.styl$/,
|
||||
oneOf: mergeCSSLoaderProd({
|
||||
loader: 'stylus-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
from: path.join(__dirname, '../../src/static'),
|
||||
to: path.join(__dirname, '../dist/web/static'),
|
||||
ignore: ['.*'],
|
||||
},
|
||||
]),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: '"production"',
|
||||
},
|
||||
}),
|
||||
new MiniCssExtractPlugin({
|
||||
filename: '[name].[contentHash:8].css',
|
||||
}),
|
||||
new webpack.NamedChunksPlugin(),
|
||||
],
|
||||
optimization: {
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
cache: true,
|
||||
parallel: true,
|
||||
sourceMap: false, // set to true if you want JS source maps
|
||||
}),
|
||||
new OptimizeCSSAssetsPlugin({}),
|
||||
],
|
||||
splitChunks: {
|
||||
cacheGroups: {
|
||||
// chunks: 'all',
|
||||
vendors: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
name: 'vendors',
|
||||
enforce: true,
|
||||
chunks: 'all',
|
||||
},
|
||||
styles: {
|
||||
name: 'styles',
|
||||
test: /\.(css|less)$/,
|
||||
chunks: 'all',
|
||||
enforce: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
runtimeChunk: true,
|
||||
},
|
||||
performance: {
|
||||
hints: 'warning',
|
||||
},
|
||||
})
|
||||
13
jsconfig.json
Normal file
13
jsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@main/*": ["src/main/*"],
|
||||
"@renderer/*": ["src/renderer/*"],
|
||||
"@lyric/*": ["src/renderer-lyric/*"],
|
||||
"@static/*": ["src/static/*"],
|
||||
"@common/*": ["src/common/*"],
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "build", "dist"]
|
||||
}
|
||||
@@ -1,45 +1,49 @@
|
||||
{\rtf1\adeflang1025\ansi\ansicpg936\uc2\adeff31507\deff0\stshfdbch31505\stshfloch31506\stshfhich31506\stshfbi31507\deflang1033\deflangfe2052\themelang1033\themelangfe2052\themelangcs0{\fonttbl{\f2\fbidi \fmodern\fcharset0\fprq1{\*\panose 02070309020205020404}Courier New;}
|
||||
{\rtf1\adeflang1025\ansi\ansicpg936\uc2\adeff0\deff0\stshfdbch31505\stshfloch31506\stshfhich31506\stshfbi0\deflang1033\deflangfe2052\themelang1033\themelangfe2052\themelangcs0{\fonttbl{\f0\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f2\fbidi \fmodern\fcharset0\fprq1{\*\panose 02070309020205020404}Courier New;}
|
||||
{\f13\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}\'cb\'ce\'cc\'e5{\*\falt SimSun};}{\f34\fbidi \froman\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria Math;}
|
||||
{\f44\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}@\'cb\'ce\'cc\'e5;}{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}
|
||||
{\f44\fbidi \fnil\fcharset134\fprq2{\*\panose 00000000000000000000}@\'cb\'ce\'cc\'e5;}{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}
|
||||
{\fdbmajor\f31501\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}\'b5\'c8\'cf\'df Light;}{\fhimajor\f31502\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}\'b5\'c8\'cf\'df Light;}
|
||||
{\fbimajor\f31503\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\flominor\f31504\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}
|
||||
{\fdbminor\f31505\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}\'b5\'c8\'cf\'df{\*\falt DengXian};}{\fhiminor\f31506\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}\'b5\'c8\'cf\'df{\*\falt DengXian};}
|
||||
{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f65\fbidi \fmodern\fcharset238\fprq1 Courier New CE;}{\f66\fbidi \fmodern\fcharset204\fprq1 Courier New Cyr;}
|
||||
{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f45\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\f46\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
|
||||
{\f48\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f49\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f50\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f51\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
|
||||
{\f52\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f53\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f65\fbidi \fmodern\fcharset238\fprq1 Courier New CE;}{\f66\fbidi \fmodern\fcharset204\fprq1 Courier New Cyr;}
|
||||
{\f68\fbidi \fmodern\fcharset161\fprq1 Courier New Greek;}{\f69\fbidi \fmodern\fcharset162\fprq1 Courier New Tur;}{\f70\fbidi \fmodern\fcharset177\fprq1 Courier New (Hebrew);}{\f71\fbidi \fmodern\fcharset178\fprq1 Courier New (Arabic);}
|
||||
{\f72\fbidi \fmodern\fcharset186\fprq1 Courier New Baltic;}{\f73\fbidi \fmodern\fcharset163\fprq1 Courier New (Vietnamese);}{\f177\fbidi \fnil\fcharset0\fprq2 SimSun Western{\*\falt SimSun};}{\f487\fbidi \fnil\fcharset0\fprq2 @SimSun Western;}
|
||||
{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}
|
||||
{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
|
||||
{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbmajor\f31520\fbidi \fnil\fcharset0\fprq2 DengXian Light Western;}
|
||||
{\fdbmajor\f31518\fbidi \fnil\fcharset238\fprq2 DengXian Light CE;}{\fdbmajor\f31519\fbidi \fnil\fcharset204\fprq2 DengXian Light Cyr;}{\fdbmajor\f31521\fbidi \fnil\fcharset161\fprq2 DengXian Light Greek;}
|
||||
{\fhimajor\f31530\fbidi \fnil\fcharset0\fprq2 DengXian Light Western;}{\fhimajor\f31528\fbidi \fnil\fcharset238\fprq2 DengXian Light CE;}{\fhimajor\f31529\fbidi \fnil\fcharset204\fprq2 DengXian Light Cyr;}
|
||||
{\fhimajor\f31531\fbidi \fnil\fcharset161\fprq2 DengXian Light Greek;}{\fbimajor\f31538\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
|
||||
{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
|
||||
{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}
|
||||
{\flominor\f31548\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}
|
||||
{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
|
||||
{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbminor\f31560\fbidi \fnil\fcharset0\fprq2 DengXian Western{\*\falt DengXian};}
|
||||
{\fdbminor\f31558\fbidi \fnil\fcharset238\fprq2 DengXian CE{\*\falt DengXian};}{\fdbminor\f31559\fbidi \fnil\fcharset204\fprq2 DengXian Cyr{\*\falt DengXian};}{\fdbminor\f31561\fbidi \fnil\fcharset161\fprq2 DengXian Greek{\*\falt DengXian};}
|
||||
{\fhiminor\f31570\fbidi \fnil\fcharset0\fprq2 DengXian Western{\*\falt DengXian};}{\fhiminor\f31568\fbidi \fnil\fcharset238\fprq2 DengXian CE{\*\falt DengXian};}{\fhiminor\f31569\fbidi \fnil\fcharset204\fprq2 DengXian Cyr{\*\falt DengXian};}
|
||||
{\fhiminor\f31571\fbidi \fnil\fcharset161\fprq2 DengXian Greek{\*\falt DengXian};}{\fbiminor\f31578\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbiminor\f31579\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
|
||||
{\fbiminor\f31581\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbiminor\f31582\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbiminor\f31583\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
|
||||
{\fbiminor\f31584\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbiminor\f31585\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbiminor\f31586\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}
|
||||
{\f45\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\f46\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\f48\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f49\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}
|
||||
{\f50\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f51\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\f52\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}
|
||||
{\f53\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}}{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;
|
||||
\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;\red0\green0\blue0;\red0\green0\blue0;}
|
||||
{\*\defchp \fs21\kerning2\loch\af31506\hich\af31506\dbch\af31505 }{\*\defpap \ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\stylesheet{
|
||||
\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \fs21\lang1033\langfe2052\kerning2\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp2052
|
||||
{\f72\fbidi \fmodern\fcharset186\fprq1 Courier New Baltic;}{\f73\fbidi \fmodern\fcharset163\fprq1 Courier New (Vietnamese);}{\f177\fbidi \fnil\fcharset0\fprq2 SimSun Western{\*\falt SimSun};}{\f385\fbidi \froman\fcharset238\fprq2 Cambria Math CE;}
|
||||
{\f386\fbidi \froman\fcharset204\fprq2 Cambria Math Cyr;}{\f388\fbidi \froman\fcharset161\fprq2 Cambria Math Greek;}{\f389\fbidi \froman\fcharset162\fprq2 Cambria Math Tur;}{\f392\fbidi \froman\fcharset186\fprq2 Cambria Math Baltic;}
|
||||
{\f393\fbidi \froman\fcharset163\fprq2 Cambria Math (Vietnamese);}{\f487\fbidi \fnil\fcharset0\fprq2 @SimSun Western;}{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}
|
||||
{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}
|
||||
{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}
|
||||
{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbmajor\f31520\fbidi \fnil\fcharset0\fprq2 DengXian Light Western;}{\fdbmajor\f31518\fbidi \fnil\fcharset238\fprq2 DengXian Light CE;}
|
||||
{\fdbmajor\f31519\fbidi \fnil\fcharset204\fprq2 DengXian Light Cyr;}{\fdbmajor\f31521\fbidi \fnil\fcharset161\fprq2 DengXian Light Greek;}{\fhimajor\f31530\fbidi \fnil\fcharset0\fprq2 DengXian Light Western;}
|
||||
{\fhimajor\f31528\fbidi \fnil\fcharset238\fprq2 DengXian Light CE;}{\fhimajor\f31529\fbidi \fnil\fcharset204\fprq2 DengXian Light Cyr;}{\fhimajor\f31531\fbidi \fnil\fcharset161\fprq2 DengXian Light Greek;}
|
||||
{\fbimajor\f31538\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}
|
||||
{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
|
||||
{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\flominor\f31548\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}
|
||||
{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}
|
||||
{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}
|
||||
{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbminor\f31560\fbidi \fnil\fcharset0\fprq2 DengXian Western{\*\falt DengXian};}{\fdbminor\f31558\fbidi \fnil\fcharset238\fprq2 DengXian CE{\*\falt DengXian};}
|
||||
{\fdbminor\f31559\fbidi \fnil\fcharset204\fprq2 DengXian Cyr{\*\falt DengXian};}{\fdbminor\f31561\fbidi \fnil\fcharset161\fprq2 DengXian Greek{\*\falt DengXian};}{\fhiminor\f31570\fbidi \fnil\fcharset0\fprq2 DengXian Western{\*\falt DengXian};}
|
||||
{\fhiminor\f31568\fbidi \fnil\fcharset238\fprq2 DengXian CE{\*\falt DengXian};}{\fhiminor\f31569\fbidi \fnil\fcharset204\fprq2 DengXian Cyr{\*\falt DengXian};}{\fhiminor\f31571\fbidi \fnil\fcharset161\fprq2 DengXian Greek{\*\falt DengXian};}
|
||||
{\fbiminor\f31578\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbiminor\f31579\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fbiminor\f31581\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}
|
||||
{\fbiminor\f31582\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbiminor\f31583\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fbiminor\f31584\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
|
||||
{\fbiminor\f31585\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbiminor\f31586\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}}{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;
|
||||
\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;
|
||||
\red192\green192\blue192;\red0\green0\blue0;\red0\green0\blue0;\chyperlink\ctint255\cshade255\red5\green99\blue193;\red96\green94\blue92;\red225\green223\blue221;\cfollowedhyperlink\ctint255\cshade255\red149\green79\blue114;}{\*\defchp
|
||||
\fs21\kerning2\loch\af31506\hich\af31506\dbch\af31505 }{\*\defpap \ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\stylesheet{
|
||||
\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0 \fs21\lang1033\langfe2052\kerning2\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp2052
|
||||
\snext0 \sqformat \spriority0 Normal;}{\*\cs10 \additive \ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\*
|
||||
\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv
|
||||
\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \fs21\lang1033\langfe2052\kerning2\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp2052
|
||||
\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs21\alang1025 \ltrch\fcs0 \fs21\lang1033\langfe2052\kerning2\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp2052
|
||||
\snext11 \ssemihidden \sunhideused Normal Table;}{\s15\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af2\afs22\alang1025 \ltrch\fcs0
|
||||
\fs21\lang1033\langfe2052\kerning2\loch\f31505\hich\af2\dbch\af31505\cgrid\langnp1033\langfenp2052 \sbasedon0 \snext15 \slink16 \sunhideused Plain Text;}{\*\cs16 \additive \rtlch\fcs1 \af2 \ltrch\fcs0 \loch\f31505\hich\af2 \sbasedon10 \slink15 \slocked
|
||||
\'b4\'bf\'ce\'c4\'b1\'be \'d7\'d6\'b7\'fb;}}{\*\rsidtbl \rsid927107\rsid3950508\rsid11081282\rsid12910709\rsid13643782\rsid14384001}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0
|
||||
\mnaryLim1}{\info{\author lysyw}{\operator lysyw}{\creatim\yr2019\mo8\dy17\hr10\min22}{\revtim\yr2019\mo8\dy17\hr10\min22}{\version2}{\edmins0}{\nofpages1}{\nofwords8}{\nofchars49}{\nofcharsws56}{\vern111}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.co
|
||||
m/office/word/2003/wordml}}\paperw11906\paperh16838\margl2253\margr2253\margt1440\margb1440\gutter0\ltrsect
|
||||
\'b4\'bf\'ce\'c4\'b1\'be \'d7\'d6\'b7\'fb;}{\*\cs17 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf19 \sbasedon10 \sunhideused \styrsid9533173 Hyperlink;}{\*\cs18 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \cf20\chshdng0\chcfpat0\chcbpat21
|
||||
\sbasedon10 \ssemihidden \sunhideused \styrsid9533173 Unresolved Mention;}{\*\cs19 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf22 \sbasedon10 \ssemihidden \sunhideused \styrsid9533173 FollowedHyperlink;}}{\*\pgptbl {\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}}
|
||||
{\*\rsidtbl \rsid927107\rsid1398824\rsid2109456\rsid3950508\rsid4133944\rsid4355753\rsid9533173\rsid10447395\rsid11081282\rsid12910709\rsid13643782\rsid14384001\rsid15226681}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0
|
||||
\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\author lysyw}{\operator lysyw}{\creatim\yr2019\mo8\dy17\hr10\min22}{\revtim\yr2020\mo4\dy28\hr13\min46}{\version8}{\edmins3}{\nofpages1}{\nofwords135}{\nofchars772}{\nofcharsws906}{\vern1}}
|
||||
{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}}\paperw11906\paperh16838\margl2253\margr2253\margt1440\margb1440\gutter0\ltrsect
|
||||
\deftab420\ftnbj\aenddoc\trackmoves0\trackformatting1\donotembedsysfont1\relyonvml0\donotembedlingdata0\grfdocevents0\validatexml1\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors1\formshade\horzdoc\dgmargin\dghspace180\dgvspace156
|
||||
\dghorigin2253\dgvorigin1440\dghshow0\dgvshow2\jcompress\lnongrid
|
||||
\viewkind1\viewscale100\splytwnine\ftnlytwnine\htmautsp\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct\asianbrkrule\rsidroot3950508\newtblstyruls
|
||||
\viewkind1\viewscale120\splytwnine\ftnlytwnine\htmautsp\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct\asianbrkrule\rsidroot3950508\newtblstyruls
|
||||
\nogrowautofit\usenormstyforlist\noindnmbrts\felnbrelev\nocxsptable\indrlsweleven\noafcnsttbl\afelev\utinl\hwelev\spltpgpar\notcvasp\notbrkcnstfrctbl\notvatxbx\krnprsnet\cachedcolbal \nouicompat {\upr{\*\fchars
|
||||
!%),.:\'3b>?]\'7d\'a1\'e9\'a1\'a7\'a1\'e3\'a1\'a4\'a1\'a6\'a1\'a5\'a8\'44\'a1\'ac\'a1\'af\'a1\'b1\'a1\'ad\'a1\'eb\'a1\'e4\'a1\'e5?\'a1\'e6\'a1\'c3\'a1\'a2\'a1\'a3\'a1\'a8\'a1\'b5\'a1\'b7\'a1\'b9\'a1\'bb\'a1\'bf\'a1\'b3\'a1\'bd\'a8\'95\'a6\'e1\'a6\'e3\'a6\'e7\'a6\'e5\'a6\'eb\'a9\'77\'a9\'79\'a9\'7b\'a3\'a1\'a3\'a2\'a3\'a5\'a3\'a7\'a3\'a9\'a3\'ac\'a3\'ae\'a3\'ba\'a3\'bb\'a3\'bf\'a3\'dd\'a3\'e0\'a3\'fc\'a3\'fd\'a1\'ab\'a1\'e9
|
||||
}{\*\ud\uc0{\*\fchars
|
||||
@@ -49,18 +53,67 @@ $([\'7b{\uc2\u163 \'a1\'ea\u165 \'a3\'a4\'a1\'a4\'a1\'ae\'a1\'b0\'a1\'b4\'a1\'b6
|
||||
\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\headery851\footery992\colsx425\endnhere\sectlinegrid312\sectspecifyl\sectrsid2109456\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta \dbch .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang
|
||||
{\pntxta \dbch .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta \dbch .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta \dbch )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}{\*\pnseclvl6
|
||||
\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}{\*\pnseclvl9
|
||||
\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}\pard\plain \ltrpar\s15\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid12910709 \rtlch\fcs1 \af2\afs22\alang1025 \ltrch\fcs0
|
||||
\fs21\lang1033\langfe2052\kerning2\loch\af31505\hich\af2\dbch\af31505\cgrid\langnp1033\langfenp2052 {\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'b3\'cc\'d0\'f2
|
||||
\'bd\'f6\'d3\'c3\'d3\'da\'d1\'a7\'cf\'b0\'bd\'bb\'c1\'f7\'ca\'b9\'d3\'c3\'a3\'a1}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709
|
||||
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709 \loch\af13\hich\af13\dbch\f13 \'c7\'eb\'ce\'f0\'d3\'c3\'d3\'da\'c9\'cc\'d2\'b5\'d3\'c3\'cd\'be\'a3\'a1\'a3\'a1}{\rtlch\fcs1 \af13 \ltrch\fcs0
|
||||
\loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709
|
||||
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709 \loch\af13\hich\af13\dbch\f13 \'ca\'b9\'d3\'c3\'b1\'be\'c8\'ed\'bc\'fe\'d4\'ec\'b3\'c9\'b5\'c4\'d2\'bb\'c7\'d0\'ba\'f3\'b9\'fb\'d3\'c9\'ca\'b9\'d3\'c3\'d5\'df
|
||||
\'b3\'d0\'b5\'a3\'a3\'a1}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709
|
||||
\par
|
||||
\par \hich\af13\dbch\af13\loch\f13 By: }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709 \loch\af13\hich\af13\dbch\f13 \'c2\'e4\'d1\'a9\'ce\'de\'ba\'db}{\rtlch\fcs1 \af13 \ltrch\fcs0
|
||||
\loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709
|
||||
\par
|
||||
\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}\pard\plain \ltrpar\s15\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid1398824 \rtlch\fcs1 \af2\afs22\alang1025 \ltrch\fcs0
|
||||
\fs21\lang1033\langfe2052\kerning2\loch\af31505\hich\af2\dbch\af31505\cgrid\langnp1033\langfenp2052 {\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'a3\'a8
|
||||
\'c8\'ed\'bc\'fe\'a3\'a9\'bb\'f9\'d3\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid4133944 \hich\af13\dbch\af13\loch\f13 Apache License 2.0}{\rtlch\fcs1 \af13 \ltrch\fcs0
|
||||
\loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \hich\af13\dbch\af13\loch\f13 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'d0\'ed\'bf\'c9\'d6\'a4\'b7\'a2
|
||||
\'d0\'d0\'a3\'ac\'d4\'da\'ca\'b9\'d3\'c3\'b1\'be\'c8\'ed\'bc\'fe\'c7\'b0\'a3\'ac\'c4\'e3\'a3\'a8\'ca\'b9\'d3\'c3\'d5\'df\'a3\'a9\'d0\'e8\'c7\'a9\'ca\'f0\'b1\'be\'d0\'ad\'d2\'e9\'b2\'c5\'bf\'c9\'bc\'cc\'d0\'f8\'ca\'b9\'d3\'c3\'a3\'ac\'d2\'d4\'cf\'c2
|
||||
\'d0\'ad\'d2\'e9\'ca\'c7\'b6\'d4\'d3\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \hich\af13\dbch\af13\loch\f13 Apache Li\hich\af13\dbch\af13\loch\f13 cense 2.0 }{\rtlch\fcs1 \af13 \ltrch\fcs0
|
||||
\loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'b5\'c4\'b2\'b9\'b3\'e4\'a3\'ac\'c8\'e7\'d3\'d0\'b3\'e5\'cd\'bb\'a3\'ac\'d2\'d4\'d2\'d4\'cf\'c2\'d0\'ad\'d2\'e9\'ce\'aa\'d7\'bc\'a1\'a3}{\rtlch\fcs1 \af13
|
||||
\ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824
|
||||
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15226681
|
||||
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid10447395\charrsid1398824
|
||||
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'b4\'ca\'d3\'ef\'d4\'bc\'b6\'a8\'a3\'ba\'b1\'be\'d0\'ad\'d2\'e9\'d6\'d0\'b5\'c4\'a1\'b0\'b1\'be\'c8\'ed\'bc\'fe\'a1\'b1\'d6\'b8
|
||||
\'c2\'e5\'d1\'a9\'d2\'f4\'c0\'d6\'d7\'c0\'c3\'e6\'b0\'e6\'cf\'ee\'c4\'bf\'a3\'bb\'a1\'b0\'ca\'b9\'d3\'c3\'d5\'df\'a1\'b1\'d6\'b8\'c7\'a9\'ca\'f0\'b1\'be\'d0\'ad\'d2\'e9\'b5\'c4\'ca\'b9\'d3\'c3\'d5\'df\'a3\'bb\'a1\'b0\'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6
|
||||
\'c6\'bd\'cc\'a8\'a1\'b1\'d6\'b8\'b6\'d4\'b1\'be\'c8\'ed\'bc\'fe\'c4\'da\'d6\'c3\'b5\'c4\'b0\'fc\'c0\'a8\'bf\'e1\'ce\'d2\'a1\'a2\'bf\'e1\'b9\'b7\'a1\'a2\'df\'e4\'b9\'be\'b5\'c8\'d2\'f4\'c0\'d6\'d4\'b4\'b5\'c4\'b9\'d9\'b7\'bd\'c6\'bd\'cc\'a8\'cd\'b3
|
||||
\'b3\'c6\'a3\'bb\'a1\'b0\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd\'a1\'b1\'d6\'b8\'b0\'fc\'c0\'a8\'b5\'ab\'b2\'bb\'cf\'de\'d3\'da\'cd\'bc\'cf\'f1\'a1\'a2\'d2\'f4\'c6\'b5\'a1\'a2\'c3\'fb\'d7\'d6\'b5\'c8\'d4\'da\'c4\'da\'b5\'c4\'cb\'fb\'c8\'cb\'d3\'b5\'d3\'d0
|
||||
\'cb\'f9\'ca\'f4\'b0\'e6\'c8\'a8\'b5\'c4\'ca\'fd\'be\'dd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824
|
||||
\par
|
||||
\par \hich\af13\dbch\af13\loch\f13 1}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'a1\'a2\'b1\'be\'c8\'ed\'bc\'fe\'b5\'c4\'ca\'fd\'be\'dd\'c0\'b4\'d4\'b4\'d4\'ad\'c0\'ed\'ca\'c7
|
||||
\'b4\'d3\'b8\'f7\'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'b5\'c4\'b9\'ab\'bf\'aa\'b7\'fe\'ce\'f1\'c6\'f7\'d6\'d0\'c0\'ad\'c8\'a1\'ca\'fd\'be\'dd\'a3\'ac\'be\'ad\'b9\'fd\'b6\'d4\'ca\'fd\'be\'dd\'bc\'f2\'b5\'a5\'b5\'d8\'c9\'b8\'d1\'a1\'d3\'eb
|
||||
\'ba\'cf\'b2\'a2\'ba\'f3\'bd\'f8\'d0\'d0\'d5\'b9\'ca\'be\'a3\'ac\'d2\'f2\'b4\'cb\'b1\'be\'c8\'ed\'bc\'fe\'b2\'bb\'b6\'d4\'ca\'fd\'be\'dd\'b5\'c4\'d7\'bc\'c8\'b7\'d0\'d4\'b8\'ba\'d4\'f0\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0
|
||||
\loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824
|
||||
\par \hich\af13\dbch\af13\loch\f13 2}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'a1\'a2\'ca\'b9\'d3\'c3\'b1\'be\'c8\'ed\'bc\'fe\'b5\'c4\'b9\'fd\'b3\'cc\'d6\'d0\'bf\'c9\'c4\'dc
|
||||
\'bb\'e1\'b2\'fa\'c9\'fa\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd\'a3\'ac\'b6\'d4\'d3\'da\'d5\'e2\'d0\'a9\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd\'a3\'ac\'b1\'be\'c8\'ed\'bc\'fe\'b2\'bb\'d3\'b5\'d3\'d0\'cb\'fc\loch\af13\hich\af13\dbch\f13 \'c3\'c7\'b5\'c4\'cb\'f9\'d3\'d0
|
||||
\'c8\'a8\'a3\'ac\'ce\'aa\'c1\'cb\'b1\'dc\'c3\'e2\'d4\'ec\'b3\'c9\'c7\'d6\'c8\'a8\'a3\'ac\'ca\'b9\'d3\'c3\'d5\'df\'ce\'f1\'b1\'d8\'d4\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824
|
||||
\hich\af13\dbch\af13\loch\f13 24}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'d0\'a1\'ca\'b1\'c4\'da\'c7\'e5\'b3\'fd\'ca\'b9\'d3\'c3\'b1\'be\'c8\'ed\'bc\'fe\'b5\'c4\'b9\'fd
|
||||
\'b3\'cc\'d6\'d0\'cb\'f9\'b2\'fa\'c9\'fa\'b5\'c4\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824
|
||||
\par \hich\af13\dbch\af13\loch\f13 3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'a1\'a2\'b1\'be\'c8\'ed\'bc\'fe\'c4\'da\'b5\'c4\'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8
|
||||
\'b1\'f0\'c3\'fb\'ce\'aa\'b1\'be\'c8\'ed\'bc\'fe\'c4\'da\'b6\'d4\'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'b5\'c4\'d2\'bb\'b8\'f6\'b3\'c6\'ba\'f4\'a3\'ac\'b2\'bb\'b0\'fc\'ba\'ac\'b6\'f1\'d2\'e2\'a3\'ac\'c8\'e7\'b9\'fb\'b9\'d9\'b7\'bd\'d2\'f4
|
||||
\'c0\'d6\'c6\'bd\'cc\'a8\'be\'f5\'b5\'c3\'b2\'bb\'cd\'d7\'a3\'ac\'bf\'c9\'c1\'aa\'cf\'b5\'b1\'be\'c8\'ed\'bc\'fe\'b8\'fc\'b8\'c4\'bb\'f2\'d2\'c6\'b3\'fd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824
|
||||
|
||||
\par \hich\af13\dbch\af13\loch\f13 4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'a1\'a2\'b1\'be\'c8\'ed\'bc\'fe\'c4\'da\'ca\'b9\'d3\'c3\'b5\'c4\'b2\'bf\'b7\'d6\'b0\'fc\'c0\'a8
|
||||
\'b5\'ab\'b2\'bb\'cf\'de\'d3\'da\'d7\'d6\'cc\'e5\'a1\'a2\'cd\'bc\'c6\'ac\'b5\'c8\'d7\'ca\'d4\'b4\'c0\'b4\'d4\'b4\'d3\'da\'bb\'a5\'c1\'aa\'cd\'f8\'a3\'ac\'c8\'e7\'b9\'fb\'b3\'f6\'cf\'d6\'c7\'d6\'c8\'a8\'bf\'c9\'c1\'aa\'cf\'b5\'b1\'be\'c8\'ed\'bc\'fe
|
||||
\'d2\'c6\'b3\'fd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824
|
||||
\par \hich\af13\dbch\af13\loch\f13 5}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'a1\'a2\'d3\'c9\'d3\'da\'ca\'b9\'d3\'c3\'b1\'be\'c8\'ed\'bc\'fe\'b2\'fa\'c9\'fa\'b5\'c4\'b0\'fc
|
||||
\'c0\'a8\'d3\'c9\'d3\'da\'b1\'be\'d0\'ad\'d2\'e9\'bb\'f2\'d3\'c9\'d3\'da\'ca\'b9\'d3\'c3\'bb\'f2\'ce\'de\'b7\'a8\'ca\'b9\'d3\'c3\'b1\'be\'c8\'ed\'bc\'fe\'b6\'f8\'d2\'fd\'c6\'f0\'b5\'c4\'c8\'ce\'ba\'ce\'d0\'d4\'d6\'ca\'b5\'c4\'c8\'ce\'ba\'ce\'d6\'b1
|
||||
\'bd\'d3\'a1\'a2\'bc\'e4\'bd\'d3\'a1\'a2\'cc\'d8\'ca\'e2\'a1\'a2\'c5\'bc\'c8\'bb\'bb\'f2\'bd\'e1\'b9\'fb\'d0\'d4\'cb\'f0\'ba\'a6\'a3\'a8\'b0\'fc\'c0\'a8\'b5\'ab\'b2\'bb\'cf\'de\'d3\'da\'d2\'f2\'c9\'cc\'d3\'fe\'cb\'f0\'ca\'a7\'a1\'a2\'cd\'a3\'b9\'a4
|
||||
\'a1\'a2\'bc\'c6\'cb\'e3\'bb\'fa\'b9\'ca\'d5\'cf\'bb\'f2\'b9\'ca\'d5\'cf\'d2\'fd\'c6\'f0\'b5\'c4\'cb\'f0\'ba\'a6\'c5\'e2\'b3\'a5\'a3\'ac\'bb\'f2\'c8\'ce\'ba\'ce\'bc\'b0\loch\af13\hich\af13\dbch\f13 \'cb\'f9\'d3\'d0\'c6\'e4\'cb\'fb\'c9\'cc\'d2\'b5\'cb\'f0
|
||||
\'ba\'a6\'bb\'f2\'cb\'f0\'ca\'a7\'a3\'a9\'d3\'c9\'ca\'b9\'d3\'c3\'d5\'df\'b8\'ba\'d4\'f0\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824
|
||||
\par \hich\af13\dbch\af13\loch\f13 6}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'a1\'a2\'b1\'be\'cf\'ee\'c4\'bf\'cd\'ea\'c8\'ab\'c3\'e2\'b7\'d1\'a3\'ac\'c7\'d2\'bf\'aa\'d4\'b4
|
||||
\'b7\'a2\'b2\'bc\'d3\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \hich\af13\dbch\af13\loch\f13 GitHub }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824
|
||||
\loch\af13\hich\af13\dbch\f13 \'c3\'e6\'cf\'f2\'c8\'ab\'ca\'c0\'bd\'e7\'c8\'cb\'d3\'c3\'d7\'f7\'b6\'d4\'bc\'bc\'ca\'f5\'b5\'c4\'d1\'a7\'cf\'b0\'bd\'bb\'c1\'f7\'a3\'ac\'b1\'be\'c8\'ed\'bc\'fe\'b2\'bb\'b6\'d4\'cf\'ee\'c4\'bf\'c4\'da\'b5\'c4\'bc\'bc\'ca\'f5
|
||||
\'bf\'c9\'c4\'dc\'b4\'e6\'d4\'da\'ce\'a5\'b7\'b4\'b5\'b1\'b5\'d8\'b7\'a8\'c2\'c9\'b7\'a8\'b9\'e6\'b5\'c4\'d0\'d0\'ce\'aa\'d7\'f7\'b1\'a3\'d6\'a4\'a3\'ac\'bd\'fb\'d6\'b9\'d4\'da\'ce\'a5\'b7\'b4\'b5\'b1\'b5\'d8\'b7\'a8\'c2\'c9\'b7\'a8\'b9\'e6\'b5\'c4
|
||||
\'c7\'e9\'bf\'f6\'cf\'c2\'ca\'b9\'d3\'c3\'b1\'be\'c8\'ed\'bc\'fe\'a3\'ac\'b6\'d4\'d3\'da\'ca\'b9\'d3\'c3\'d5\'df\'d4\'da\'c3\'f7\'d6\'aa\'bb\'f2\'b2\'bb\'d6\'aa\'b5\'b1\'b5\'d8\'b7\'a8\'c2\'c9\'b7\'a8\'b9\'e6\'b2\'bb\'d4\'ca\'d0\'ed\'b5\'c4\'c7\'e9
|
||||
\'bf\'f6\'cf\'c2\'ca\'b9\'d3\'c3\'b1\'be\'c8\'ed\'bc\'fe\'cb\'f9\'d4\'ec\'b3\'c9\'b5\'c4\'c8\'ce\'ba\'ce\'ce\'a5\'b7\'a8\'ce\'a5\'b9\'e6\'d0\'d0\'ce\'aa\'d3\'c9\'ca\'b9\'d3\'c3\'d5\'df\'b3\'d0\'b5\'a3\'a3\'ac\'b1\'be\'c8\'ed\'bc\'fe\'b2\'bb\'b3\'d0
|
||||
\'b5\'a3\'d3\'c9\'b4\'cb\'d4\'ec\'b3\'c9\'b5\'c4\'c8\'ce\'ba\'ce\'d6\'b1\'bd\'d3\'a1\'a2\'bc\'e4\'bd\'d3\'a1\'a2\'cc\'d8\'ca\'e2\'a1\'a2\'c5\'bc\'c8\'bb\'bb\'f2\'bd\'e1\'b9\'fb\'d0\'d4\'d4\'f0\'c8\'ce\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0
|
||||
\loch\af13\hich\af13\dbch\af13\insrsid1398824
|
||||
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15226681
|
||||
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid10447395\charrsid1398824
|
||||
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \hich\af13\dbch\af13\loch\f13 * }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13
|
||||
\'c8\'f4\'d0\'ad\'d2\'e9\'b8\'fc\'d0\'c2\'a3\'ac\'cb\'a1\'b2\'bb\'c1\'ed\'d0\'d0\'cd\'a8\'d6\'aa\'a3\'ac\'bf\'c9\'b5\'bd\'bf\'aa\'d4\'b4\'b5\'d8\'d6\'b7\'b2\'e9\'bf\'b4\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0
|
||||
\loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid15226681
|
||||
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \hich\af13\dbch\af13\loch\f13 * }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13
|
||||
\'b1\'be\'c8\'ed\'bc\'fe\'b5\'c4\'b3\'f5\'d6\'d4\'ca\'c7\'b0\'ef\'d6\'fa\'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'bc\'f2\'bb\'af\'ca\'fd\'be\'dd\'ba\'f3\'b4\'fa\'ce\'aa\'d5\'b9\'ca\'be\'a3\'ac\'b0\'ef\'d6\'fa\'ca\'b9\'d3\'c3\'d5\'df\'b8\'f9
|
||||
\'be\'dd\'b8\'e8\'c7\'fa\loch\af13\hich\af13\dbch\f13 \'c3\'fb\'a1\'a2\'d2\'d5\'ca\'f5\'bc\'d2\'b5\'c8\'b9\'d8\'bc\'fc\'d7\'d6\'bf\'ec\'cb\'d9\'b5\'d8\'b6\'a8\'ce\'bb\'cb\'f9\'d0\'e8\'c4\'da\'c8\'dd\'cb\'f9\'d4\'da\'b5\'c4\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8
|
||||
\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid15226681
|
||||
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \hich\af13\dbch\af13\loch\f13 * }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13
|
||||
\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'b2\'bb\'d2\'d7\'a3\'ac\'bd\'a8\'d2\'e9\'b5\'bd\'b6\'d4\'d3\'a6\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'d6\'a7\'b3\'d6\'d5\'fd\'b0\'e6\'d7\'ca\'d4\'b4\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0
|
||||
\loch\af13\hich\af13\dbch\af13\insrsid12910709
|
||||
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid9533173\charrsid15226681
|
||||
\par }\pard \ltrpar\s15\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid12910709 {\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709 \hich\af13\dbch\af13\loch\f13 By: }{
|
||||
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709 \loch\af13\hich\af13\dbch\f13 \'c2\'e4\'d1\'a9\'ce\'de\'ba\'db}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709
|
||||
|
||||
\par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a
|
||||
9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad
|
||||
5689811a183c61a50f98f4babebc2837878049899a52a57be670674cb23d8e90721f90a4d2fa3802cb35762680fd800ecd7551dc18eb899138e3c943d7e503b6
|
||||
@@ -114,7 +167,7 @@ e4350d363f2451eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8
|
||||
617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169
|
||||
6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363
|
||||
656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e}
|
||||
{\*\latentstyles\lsdstimax377\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 1;
|
||||
{\*\latentstyles\lsdstimax376\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 1;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 2;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 3;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 4;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 5;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 6;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 7;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 8;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 1;
|
||||
@@ -143,18 +196,8 @@ e4350d363f2451eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Cite;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Code;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Definition;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Keyboard;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Preformatted;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Sample;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Typewriter;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Variable;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation subject;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 No List;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 1;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 2;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 3;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 3;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 4;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 3;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 7;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 3;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 7;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 3;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Contemporary;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Elegant;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Professional;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Subtle 1;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Subtle 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Balloon Text;
|
||||
\lsdpriority39 \lsdlocked0 Table Grid;\lsdsemihidden1 \lsdlocked0 Placeholder Text;\lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing;\lsdpriority60 \lsdlocked0 Light Shading;\lsdpriority61 \lsdlocked0 Light List;\lsdpriority62 \lsdlocked0 Light Grid;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Balloon Text;\lsdpriority39 \lsdlocked0 Table Grid;
|
||||
\lsdsemihidden1 \lsdlocked0 Placeholder Text;\lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing;\lsdpriority60 \lsdlocked0 Light Shading;\lsdpriority61 \lsdlocked0 Light List;\lsdpriority62 \lsdlocked0 Light Grid;
|
||||
\lsdpriority63 \lsdlocked0 Medium Shading 1;\lsdpriority64 \lsdlocked0 Medium Shading 2;\lsdpriority65 \lsdlocked0 Medium List 1;\lsdpriority66 \lsdlocked0 Medium List 2;\lsdpriority67 \lsdlocked0 Medium Grid 1;\lsdpriority68 \lsdlocked0 Medium Grid 2;
|
||||
\lsdpriority69 \lsdlocked0 Medium Grid 3;\lsdpriority70 \lsdlocked0 Dark List;\lsdpriority71 \lsdlocked0 Colorful Shading;\lsdpriority72 \lsdlocked0 Colorful List;\lsdpriority73 \lsdlocked0 Colorful Grid;\lsdpriority60 \lsdlocked0 Light Shading Accent 1;
|
||||
\lsdpriority61 \lsdlocked0 Light List Accent 1;\lsdpriority62 \lsdlocked0 Light Grid Accent 1;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 1;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 1;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 1;
|
||||
@@ -202,8 +245,8 @@ e4350d363f2451eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8
|
||||
\lsdpriority49 \lsdlocked0 List Table 4 Accent 5;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 5;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 5;
|
||||
\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 List Table 2 Accent 6;\lsdpriority48 \lsdlocked0 List Table 3 Accent 6;\lsdpriority49 \lsdlocked0 List Table 4 Accent 6;
|
||||
\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Mention;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hashtag;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Unresolved Mention;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Link;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Link Error;}}{\*\datastore 0105000002000000180000004d73786d6c322e534158584d4c5265616465722e362e3000000000000000000000060000
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hashtag;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Unresolved Mention;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Link;}}{\*\datastore 01050000
|
||||
02000000180000004d73786d6c322e534158584d4c5265616465722e362e3000000000000000000000060000
|
||||
d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff090006000000000000000000000001000000010000000000000000100000feffffff00000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
@@ -212,8 +255,8 @@ fffffffffffffffffdfffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e5000000000000000000000000908a
|
||||
0599a254d501feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000
|
||||
ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e5000000000000000000000000b026
|
||||
8d59201dd601feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000
|
||||
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000
|
||||
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000
|
||||
0000000000000000000000000000000000000000000000000105000000000000}}
|
||||
18
licenses/license_en.txt
Normal file
18
licenses/license_en.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
This project (software) is issued based on the Apache License 2.0 license. Before using this software, you (users) need to sign this agreement before you can continue to use it. The following agreement is a supplement to the Apache License 2.0. In case of conflict, use the following agreement Shall prevail.
|
||||
|
||||
|
||||
Terms agreed: "this software" in this agreement refers to the Luo Xue Music desktop version of the project; "user" refers to the user who signed this agreement; "official music platform" refers to the built-in software including Kuwo, Kugou, Migu The official platforms of music sources, etc. are collectively referred to; "copyright data" refers to data including but not limited to images, audio, names, etc. that others own the copyright.
|
||||
|
||||
1. The principle of the data source of this software is to pull data from the public servers of each official music platform. After simply filtering and merging the data for display, this software is not responsible for the accuracy of the data.
|
||||
2. Copyright data may be generated during the use of this software. For this copyright data, the software does not own their ownership. In order to avoid infringement, users must clear the copyright generated during the use of this software within 24 hours data.
|
||||
3. The alias of the official music platform in this software is a name for the official music platform in the software. It does not contain maliciousness. If the official music platform feels inappropriate, you can contact the software to change or remove it.
|
||||
4. The parts used in this software include but are not limited to fonts, pictures and other resources from the Internet. If there is infringement, you can contact this software to remove it.
|
||||
5. Any direct, indirect, special, accidental or consequential damage of any nature arising from the use of this software, including this agreement or the use or inability to use this software (including but not limited to due to loss of goodwill, work stoppage, Compensation for computer failure or damage caused by the failure, or any and all other commercial damage or loss) is the responsibility of the user.
|
||||
6. This project is completely free, and the open source is published on GitHub. It is used by people all over the world to learn and exchange technology. This software does not guarantee that the technology in the project may violate local laws and regulations. It is prohibited to violate local laws and regulations. Using this software, the user shall be responsible for any violations caused by the user's use of the software without knowing or not permitted by local laws and regulations. The software shall not be responsible for any direct, indirect, special, accidental Or consequential responsibility.
|
||||
|
||||
|
||||
* If the agreement is updated without prior notice, you can check it at the open source address.
|
||||
* The original intention of this software is to help the official music platform to simplify the data generation for display, and to help users quickly locate the music platform where the desired content is based on the song title, artist and other keywords.
|
||||
* Music platform is not easy, it is recommended to support genuine resources to the corresponding music platform.
|
||||
|
||||
By: lyswhut
|
||||
18
licenses/license_zh.txt
Normal file
18
licenses/license_zh.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
本项目(软件)基于Apache License 2.0 许可证发行,在使用本软件前,你(使用者)需签署本协议才可继续使用,以下协议是对于 Apache License 2.0 的补充,如有冲突,以以下协议为准。
|
||||
|
||||
|
||||
词语约定:本协议中的“本软件”指洛雪音乐桌面版项目;“使用者”指签署本协议的使用者;“官方音乐平台”指对本软件内置的包括酷我、酷狗、咪咕等音乐源的官方平台统称;“版权数据”指包括但不限于图像、音频、名字等在内的他人拥有所属版权的数据。
|
||||
|
||||
1、本软件的数据来源原理是从各官方音乐平台的公开服务器中拉取数据,经过对数据简单地筛选与合并后进行展示,因此本软件不对数据的准确性负责。
|
||||
2、使用本软件的过程中可能会产生版权数据,对于这些版权数据,本软件不拥有它们的所有权,为了避免造成侵权,使用者务必在24小时内清除使用本软件的过程中所产生的版权数据。
|
||||
3、本软件内的官方音乐平台别名为本软件内对官方音乐平台的一个称呼,不包含恶意,如果官方音乐平台觉得不妥,可联系本软件更改或移除。
|
||||
4、本软件内使用的部分包括但不限于字体、图片等资源来源于互联网,如果出现侵权可联系本软件移除。
|
||||
5、由于使用本软件产生的包括由于本协议或由于使用或无法使用本软件而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害(包括但不限于因商誉损失、停工、计算机故障或故障引起的损害赔偿,或任何及所有其他商业损害或损失)由使用者负责。
|
||||
6、本项目完全免费,且开源发布于 GitHub 面向全世界人用作对技术的学习交流,本软件不对项目内的技术可能存在违反当地法律法规的行为作保证,禁止在违反当地法律法规的情况下使用本软件,对于使用者在明知或不知当地法律法规不允许的情况下使用本软件所造成的任何违法违规行为由使用者承担,本软件不承担由此造成的任何直接、间接、特殊、偶然或结果性责任。
|
||||
|
||||
|
||||
* 若协议更新,恕不另行通知,可到开源地址查看。
|
||||
* 本软件的初衷是帮助官方音乐平台简化数据后代为展示,帮助使用者根据歌曲名、艺术家等关键字快速地定位所需内容所在的音乐平台。
|
||||
* 音乐平台不易,建议到对应音乐平台支持正版资源。
|
||||
|
||||
By: 落雪无痕
|
||||
18536
package-lock.json
generated
18536
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
302
package.json
302
package.json
@@ -1,94 +1,140 @@
|
||||
{
|
||||
"name": "lx-music-desktop",
|
||||
"version": "0.2.3",
|
||||
"description": "一个免费的音乐下载助手",
|
||||
"version": "1.12.2",
|
||||
"description": "一个免费的音乐查找助手",
|
||||
"main": "./dist/electron/main.js",
|
||||
"productName": "lx-music-desktop",
|
||||
"scripts": {
|
||||
"pack": "node build-config/pack.js && npm run pack:win:setup",
|
||||
"pack:win": "npm run pack:win:setup && npm run pack:win:7z",
|
||||
"pack:win:setup": "cross-env TARGET=Setup ARCH=x64_x86 electron-builder -w=nsis",
|
||||
"pack:win:portable": "npm run pack:win:portable:x64_x86 && npm run pack:win:portable:x64 && npm run pack:win:portable:x86",
|
||||
"pack:win:portable:x64_x86": "cross-env TARGET=便携版 ARCH=x64_x86 electron-builder -w=portable --x64 --ia32",
|
||||
"pack:win:portable:x64": "cross-env TARGET=便携版 ARCH=x64 electron-builder -w=portable --x64",
|
||||
"pack:win:portable:x86": "cross-env TARGET=便携版 ARCH=x86 electron-builder -w=portable --ia32",
|
||||
"pack": "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: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=绿色版 ARCH=x64 electron-builder -w=7z --x64",
|
||||
"pack:win:7z:x86": "cross-env TARGET=绿色版 ARCH=x86 electron-builder -w=7z --ia32",
|
||||
"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: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:x86 && 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:x86": "cross-env ARCH=x86 electron-builder -l=deb --ia32 -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: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",
|
||||
"publish": "node publish",
|
||||
"publish:gh": "node build-config/pack.js && npm run publish:win",
|
||||
"publish:win": "npm run publish:win:setup && npm run publish:win:7z",
|
||||
"publish:win:setup": "cross-env TARGET=Setup ARCH=x64_x86 electron-builder -w=nsis --x64 --ia32 -p always",
|
||||
"publish:win:portable": "npm run publish:win:portable:x64_x86 && npm run publish:win:portable:x64 && npm run publish:win:portable:x86",
|
||||
"publish:win:portable:x64_x86": "cross-env TARGET=portable ARCH=x64_x86 electron-builder -w=portable --x64 --ia32 -p onTagOrDraft",
|
||||
"publish:win:setup:x64:always": "cross-env TARGET=Setup ARCH=x64 electron-builder -w=nsis --x64 -p always",
|
||||
"publish:win:setup:x64": "cross-env TARGET=Setup ARCH=x64 electron-builder -w=nsis --x64 -p always",
|
||||
"publish:win:setup:x86": "cross-env TARGET=Setup ARCH=x86 electron-builder -w=nsis --ia32 -p onTagOrDraft",
|
||||
"publish:win:setup:arm64": "cross-env TARGET=Setup ARCH=arm64 electron-builder -w=nsis --arm64 -p onTagOrDraft",
|
||||
"publish:win:setup:x86_64": "cross-env TARGET=Setup ARCH=x86_64 electron-builder -w=nsis --x64 --ia32 -p onTagOrDraft",
|
||||
"publish:win:portable": "npm run publish:win:portable:x86_64 && npm run publish:win:portable:x64 && npm run publish:win:portable:x86",
|
||||
"publish:win:portable:x86_64": "cross-env TARGET=portable ARCH=x86_64 electron-builder -w=portable --x64 --ia32 -p onTagOrDraft",
|
||||
"publish:win:portable:x64": "cross-env TARGET=portable ARCH=x64 electron-builder -w=portable --x64 -p onTagOrDraft",
|
||||
"publish:win:portable:x86": "cross-env TARGET=portable ARCH=x86 electron-builder -w=portable --ia32 -p onTagOrDraft",
|
||||
"publish:win:7z": "npm run publish:win:7z:x64 && npm run publish:win:7z:x86",
|
||||
"publish:win:7z:x64": "cross-env TARGET=green ARCH=x64 electron-builder -w=7z --x64 -p onTagOrDraft",
|
||||
"publish:win:7z:x86": "cross-env TARGET=green ARCH=x86 electron-builder -w=7z --ia32 -p onTagOrDraft",
|
||||
"pack:linux": "node build-config/pack.js && electron-builder -l",
|
||||
"pack:dir": "node build-config/pack.js && electron-builder --dir",
|
||||
"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:x86": "cross-env ARCH=x86 electron-builder -l=deb --ia32 -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",
|
||||
"dev": "node build-config/runner-dev.js",
|
||||
"clean:electron": "rimraf dist/electron",
|
||||
"clean:web": "rimraf dist/web",
|
||||
"clean": "rimraf dist && rimraf build",
|
||||
"build:main": "cross-env NODE_ENV=production webpack --config build-config/main/webpack.config.prod.js --progress --hide-modules",
|
||||
"build:renderer": "cross-env NODE_ENV=production webpack --config build-config/renderer/webpack.config.prod.js --progress --hide-modules",
|
||||
"build:web": "npm run clean:web && cross-env NODE_ENV=production webpack --config build-config/web/webpack.config.prod.js --progress --hide-modules",
|
||||
"build": "npm run clean:electron && npm run build:main && npm run build:renderer",
|
||||
"build:src": "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": "npm run clean:electron && npm run build:main && npm run build:renderer && npm run build:renderer-lyric",
|
||||
"lint": "eslint --ext .js,.vue -f ./node_modules/eslint-formatter-friendly src",
|
||||
"lint:fix": "eslint --ext .js,.vue -f ./node_modules/eslint-formatter-friendly --fix src"
|
||||
"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://localhost:1081 npm run pack",
|
||||
"up": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://localhost:1081 npm i"
|
||||
},
|
||||
"browserslist": [
|
||||
"Chrome >= 76"
|
||||
"Electron 13.1.7"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
"node": ">= 14"
|
||||
},
|
||||
"build": {
|
||||
"appId": "cn.toside.music.desktop",
|
||||
"directories": {
|
||||
"output": "build"
|
||||
"output": "./build"
|
||||
},
|
||||
"files": [
|
||||
"dist/electron/**/*"
|
||||
],
|
||||
"extraResources": [
|
||||
"./licenses"
|
||||
],
|
||||
"win": {
|
||||
"icon": "src/static/icons/lunch.ico",
|
||||
"icon": "./resources/icons/256x256.ico",
|
||||
"legalTrademarks": "lyswhut",
|
||||
"artifactName": "${productName} v${version} ${env.ARCH} ${env.TARGET}.${ext}"
|
||||
},
|
||||
"mac": {
|
||||
"icon": "./resources/icons/512x512.icns",
|
||||
"category": "public.app-category.music"
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
{
|
||||
"target": "AppImage",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"arch": [
|
||||
"ia32",
|
||||
"x64"
|
||||
],
|
||||
"target": "deb"
|
||||
},
|
||||
{
|
||||
"arch": [
|
||||
"x64"
|
||||
],
|
||||
"target": "snap"
|
||||
}
|
||||
],
|
||||
"maintainer": "lyswhut <lyswuhut@qq.com>"
|
||||
"maintainer": "lyswhut <lyswhut@qq.com>",
|
||||
"artifactName": "${productName} v${version} ${env.ARCH}.${ext}",
|
||||
"icon": "./resources/icons",
|
||||
"category": "Utility;AudioVideo;Audio;Player;Music;",
|
||||
"desktop": {
|
||||
"Name[zh_CN]": "洛雪音乐助手"
|
||||
}
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"language": "2052",
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"differentialPackage": true,
|
||||
"license": "./license.rtf",
|
||||
"shortcutName": "lx-music"
|
||||
"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": [
|
||||
{
|
||||
@@ -109,7 +155,7 @@
|
||||
],
|
||||
"author": {
|
||||
"name": "lyswhut",
|
||||
"email": "lyswuhut@qq.com"
|
||||
"email": "lyswhut@qq.com"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
@@ -117,79 +163,91 @@
|
||||
},
|
||||
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.5.5",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||
"@babel/polyfill": "^7.4.4",
|
||||
"@babel/preset-env": "^7.5.5",
|
||||
"autoprefixer": "^9.6.1",
|
||||
"babel-eslint": "^10.0.2",
|
||||
"babel-loader": "^8.0.6",
|
||||
"babel-minify-webpack-plugin": "^0.3.1",
|
||||
"babel-preset-minify": "^0.5.0",
|
||||
"cfonts": "^2.4.4",
|
||||
"chalk": "^2.4.2",
|
||||
"@babel/core": "^7.15.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-modules-umd": "^7.14.5",
|
||||
"@babel/plugin-transform-runtime": "^7.15.0",
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"@babel/preset-env": "^7.15.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-preset-minify": "^0.5.1",
|
||||
"browserslist": "^4.16.7",
|
||||
"cfonts": "^2.9.3",
|
||||
"chalk": "^4.1.2",
|
||||
"changelog-parser": "^2.8.0",
|
||||
"copy-webpack-plugin": "^5.0.4",
|
||||
"core-js": "^3.2.1",
|
||||
"cos-nodejs-sdk-v5": "^2.5.11",
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^3.2.0",
|
||||
"del": "^3.0.0",
|
||||
"electron": "^6.0.2",
|
||||
"electron-builder": "^21.2.0",
|
||||
"electron-debug": "^3.0.1",
|
||||
"electron-devtools-installer": "^2.2.4",
|
||||
"eslint": "^6.1.0",
|
||||
"eslint-config-standard": "^13.0.1",
|
||||
"copy-webpack-plugin": "^9.0.1",
|
||||
"core-js": "^3.16.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^6.2.0",
|
||||
"css-minimizer-webpack-plugin": "^3.0.2",
|
||||
"del": "^6.0.0",
|
||||
"electron": "^13.1.9",
|
||||
"electron-builder": "^22.11.7",
|
||||
"electron-debug": "^3.2.0",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-to-chromium": "^1.3.801",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-standard": "^16.0.3",
|
||||
"eslint-formatter-friendly": "^7.0.0",
|
||||
"eslint-loader": "^2.2.1",
|
||||
"eslint-plugin-html": "^6.0.0",
|
||||
"eslint-plugin-import": "^2.18.2",
|
||||
"eslint-plugin-node": "^9.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"file-loader": "^4.2.0",
|
||||
"eslint-loader": "^4.0.2",
|
||||
"eslint-plugin-html": "^6.1.2",
|
||||
"eslint-plugin-import": "^2.24.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"eslint-plugin-standard": "^4.1.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"friendly-errors-webpack-plugin": "^1.7.0",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"less": "^3.9.0",
|
||||
"less-loader": "^5.0.0",
|
||||
"markdown-it": "^9.1.0",
|
||||
"mini-css-extract-plugin": "^0.8.0",
|
||||
"multispinner": "^0.2.1",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"pug": "^2.0.4",
|
||||
"html-webpack-plugin": "^5.3.2",
|
||||
"less": "^4.1.1",
|
||||
"less-loader": "^10.0.1",
|
||||
"less-plugin-clean-css": "^1.5.1",
|
||||
"markdown-it": "^12.2.0",
|
||||
"mini-css-extract-plugin": "^2.2.0",
|
||||
"postcss": "^8.3.6",
|
||||
"postcss-loader": "^6.1.1",
|
||||
"postcss-pxtorem": "^6.0.0",
|
||||
"pug": "^3.0.2",
|
||||
"pug-loader": "^2.4.0",
|
||||
"pug-plain-loader": "^1.0.0",
|
||||
"raw-loader": "^3.1.0",
|
||||
"rimraf": "^3.0.0",
|
||||
"stylus": "^0.54.5",
|
||||
"stylus-loader": "^3.0.2",
|
||||
"terser-webpack-plugin": "^1.4.1",
|
||||
"url-loader": "^2.1.0",
|
||||
"vue-loader": "^15.7.1",
|
||||
"vue-style-loader": "^4.1.2",
|
||||
"vue-template-compiler": "^2.6.10",
|
||||
"webpack": "^4.39.2",
|
||||
"webpack-cli": "^3.3.6",
|
||||
"webpack-dev-server": "^3.8.0",
|
||||
"pug-plain-loader": "^1.1.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"spinnies": "^0.5.1",
|
||||
"stylus": "^0.54.8",
|
||||
"stylus-loader": "^6.1.0",
|
||||
"terser-webpack-plugin": "^5.1.4",
|
||||
"url-loader": "^4.1.1",
|
||||
"vue-loader": "^15.9.8",
|
||||
"vue-template-compiler": "^2.6.14",
|
||||
"webpack": "^5.50.0",
|
||||
"webpack-cli": "^4.7.2",
|
||||
"webpack-dev-server": "^3.11.2",
|
||||
"webpack-hot-middleware": "^2.25.0",
|
||||
"webpack-merge": "^4.2.1"
|
||||
"webpack-merge": "^5.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.19.0",
|
||||
"electron-log": "^3.0.7",
|
||||
"electron-store": "^4.0.0",
|
||||
"electron-updater": "^4.1.2",
|
||||
"js-htmlencode": "^0.3.0",
|
||||
"lrc-file-parser": "^0.1.12",
|
||||
"node-downloader-helper": "^1.0.10",
|
||||
"request": "^2.88.0",
|
||||
"vue": "^2.6.10",
|
||||
"vue-electron": "^1.0.6",
|
||||
"vue-router": "^3.1.2",
|
||||
"vuex": "^3.1.1",
|
||||
"vuex-electron": "^1.0.3",
|
||||
"crypto-js": "^4.1.1",
|
||||
"electron-log": "^4.4.1",
|
||||
"bufferutil": "^4.0.3",
|
||||
"eiows": "^3.6.1",
|
||||
"electron-store": "^8.0.0",
|
||||
"electron-updater": "^4.3.9",
|
||||
"http-terminator": "^3.0.0",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"image-size": "^1.0.0",
|
||||
"koa": "^2.13.1",
|
||||
"long": "^4.0.0",
|
||||
"lrc-file-parser": "^1.1.2",
|
||||
"needle": "^2.8.0",
|
||||
"node-id3": "^0.2.3",
|
||||
"request": "^2.88.2",
|
||||
"socket.io": "^4.1.3",
|
||||
"utf-8-validate": "^5.0.5",
|
||||
"vue": "^2.6.14",
|
||||
"vue-i18n": "^8.25.0",
|
||||
"vue-router": "^3.5.2",
|
||||
"vuex": "^3.6.2",
|
||||
"vuex-router-sync": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,28 @@
|
||||
const autoprefixer = require('autoprefixer')
|
||||
// const autoprefixer = require('autoprefixer')
|
||||
const pxtorem = require('postcss-pxtorem')
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
autoprefixer(),
|
||||
pxtorem({
|
||||
rootValue: 16,
|
||||
unitPrecision: 5,
|
||||
propList: [
|
||||
'font', 'font-size',
|
||||
'letter-spacing',
|
||||
'padding', 'margin',
|
||||
'padding-*', 'margin-*',
|
||||
'height', 'width',
|
||||
'*-height', '*-width',
|
||||
'flex', '::-webkit-scrollbar',
|
||||
'top', 'left', 'bottom', 'right',
|
||||
'border-radius',
|
||||
],
|
||||
selectorBlackList: ['html', 'ignore-to-rem'],
|
||||
replace: true,
|
||||
mediaQuery: false,
|
||||
minPixelValue: 0,
|
||||
exclude: [/node_modules/i],
|
||||
}),
|
||||
// autoprefixer(),
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
### 新增
|
||||
|
||||
- 新增任务栏程序标题改变功能(播放歌曲时任务栏标题将显示当前播放的歌曲)
|
||||
|
||||
### 修复
|
||||
|
||||
- 使用临时接口时,试听列表中的下载按钮仍然能点击的Bug
|
||||
- 修复某些情况下歌曲链接未能缓存的问题
|
||||
|
||||
### 移除
|
||||
|
||||
- 移除临时接口(因服务器被攻击,本接口已关闭)
|
||||
- 移除列表栏设置的隐藏专辑栏选项(感觉这个设置并没有什么luan用,并且还会打破布局)
|
||||
- 修复播放下载列表的歌曲时切歌的问题
|
||||
- 修复播放下载列表的歌曲时歌词无法显示的问题
|
||||
- 修复下载列表稍后播放功能无效的问题
|
||||
- 修复同步服务器启动失败时,关闭同步服务不会清空失败信息的问题
|
||||
|
||||
@@ -25,7 +25,7 @@ const updateChangeLog = async(newVerNum, newChangeLog) => {
|
||||
let changeLog = fs.readFileSync(changelogPath, 'utf-8')
|
||||
const prevVer = await getPrevVer()
|
||||
const log = `## [${newVerNum}](${pkg.repository.url.replace(/^git\+(http.+)\.git$/, '$1')}/compare/v${prevVer}...v${newVerNum}) - ${formatTime()}\n\n${newChangeLog}`
|
||||
fs.writeFileSync(changelogPath, changeLog.replace(new RegExp(`(## [?0.1.1]?)`), log + '\n$1'), 'utf-8')
|
||||
fs.writeFileSync(changelogPath, changeLog.replace(new RegExp('(## [?0.1.1]?)'), log + '\n$1'), 'utf-8')
|
||||
}
|
||||
|
||||
const renderChangeLog = md => md_renderer(md)
|
||||
@@ -44,12 +44,12 @@ module.exports = async newVerNum => {
|
||||
desc: version.desc,
|
||||
})
|
||||
version.version = newVerNum
|
||||
version.desc = newChangeLog
|
||||
version.desc = newMDChangeLog.replace(/(?:^|(\n))#{1,6} (.+)\n/g, '$1$2').trim()
|
||||
pkg.version = newVerNum
|
||||
|
||||
console.log(chalk.blue('new version: ') + chalk.green(newVerNum))
|
||||
|
||||
fs.writeFileSync(jp('../version.json'), JSON.stringify(version, null, 2) + '\n', 'utf-8')
|
||||
fs.writeFileSync(jp('../version.json'), JSON.stringify(version) + '\n', 'utf-8')
|
||||
|
||||
fs.writeFileSync(jp(pkgDir), JSON.stringify(pkg, null, 2) + '\n', 'utf-8')
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
BIN
resources/icons/512x512.icns
Normal file
BIN
resources/icons/512x512.icns
Normal file
Binary file not shown.
BIN
resources/icons/512x512.png
Normal file
BIN
resources/icons/512x512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
56
src/common/config.js
Normal file
56
src/common/config.js
Normal file
@@ -0,0 +1,56 @@
|
||||
module.exports = {
|
||||
windowSizeList: [
|
||||
{
|
||||
id: 0,
|
||||
name: 'smaller',
|
||||
width: 828,
|
||||
height: 530,
|
||||
fontSize: '14px',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'small',
|
||||
width: 920,
|
||||
height: 590,
|
||||
fontSize: '16px',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'medium',
|
||||
width: 1018,
|
||||
height: 650,
|
||||
fontSize: '16px',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'big',
|
||||
width: 1114,
|
||||
height: 708,
|
||||
fontSize: '17px',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'larger',
|
||||
width: 1202,
|
||||
height: 766,
|
||||
fontSize: '17px',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'oversized',
|
||||
width: 1382,
|
||||
height: 886,
|
||||
fontSize: '18px',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'huge',
|
||||
width: 1686,
|
||||
height: 1062,
|
||||
fontSize: '19px',
|
||||
},
|
||||
],
|
||||
navigationUrlWhiteList: [
|
||||
|
||||
],
|
||||
}
|
||||
89
src/common/defaultHotKey.js
Normal file
89
src/common/defaultHotKey.js
Normal file
@@ -0,0 +1,89 @@
|
||||
const { player: hotKeyPlayer, common: hotKeyCommon, desktop_lyric: hotKeyDesktopLyric } = require('./hotKey')
|
||||
|
||||
module.exports = {
|
||||
local: {
|
||||
enable: true,
|
||||
keys: {
|
||||
'mod+f5': {
|
||||
type: hotKeyPlayer.toggle_play.type,
|
||||
name: hotKeyPlayer.toggle_play.name,
|
||||
action: hotKeyPlayer.toggle_play.action,
|
||||
},
|
||||
'mod+arrowleft': {
|
||||
type: hotKeyPlayer.prev.type,
|
||||
name: hotKeyPlayer.prev.name,
|
||||
action: hotKeyPlayer.prev.action,
|
||||
},
|
||||
'mod+arrowright': {
|
||||
type: hotKeyPlayer.next.type,
|
||||
name: hotKeyPlayer.next.name,
|
||||
action: hotKeyPlayer.next.action,
|
||||
},
|
||||
f1: {
|
||||
type: hotKeyCommon.focusSearchInput.type,
|
||||
name: hotKeyCommon.focusSearchInput.name,
|
||||
action: hotKeyCommon.focusSearchInput.action,
|
||||
},
|
||||
},
|
||||
},
|
||||
global: {
|
||||
enable: false,
|
||||
keys: {
|
||||
MediaPlayPause: {
|
||||
type: hotKeyPlayer.toggle_play.type,
|
||||
name: null,
|
||||
action: hotKeyPlayer.toggle_play.action,
|
||||
},
|
||||
MediaPreviousTrack: {
|
||||
type: hotKeyPlayer.prev.type,
|
||||
name: null,
|
||||
action: hotKeyPlayer.prev.action,
|
||||
},
|
||||
MediaNextTrack: {
|
||||
type: hotKeyPlayer.next.type,
|
||||
name: null,
|
||||
action: hotKeyPlayer.next.action,
|
||||
},
|
||||
'mod+alt+f5': {
|
||||
type: hotKeyPlayer.toggle_play.type,
|
||||
name: hotKeyPlayer.toggle_play.name,
|
||||
action: hotKeyPlayer.toggle_play.action,
|
||||
},
|
||||
'mod+alt+arrowleft': {
|
||||
type: hotKeyPlayer.prev.type,
|
||||
name: hotKeyPlayer.prev.name,
|
||||
action: hotKeyPlayer.prev.action,
|
||||
},
|
||||
'mod+alt+arrowright': {
|
||||
type: hotKeyPlayer.next.type,
|
||||
name: hotKeyPlayer.next.name,
|
||||
action: hotKeyPlayer.next.action,
|
||||
},
|
||||
'mod+alt+arrowup': {
|
||||
type: hotKeyPlayer.volume_up.type,
|
||||
name: hotKeyPlayer.volume_up.name,
|
||||
action: hotKeyPlayer.volume_up.action,
|
||||
},
|
||||
'mod+alt+arrowdown': {
|
||||
type: hotKeyPlayer.volume_down.type,
|
||||
name: hotKeyPlayer.volume_down.name,
|
||||
action: hotKeyPlayer.volume_down.action,
|
||||
},
|
||||
'mod+alt+0': {
|
||||
type: hotKeyDesktopLyric.toggle_visible.type,
|
||||
name: hotKeyDesktopLyric.toggle_visible.name,
|
||||
action: hotKeyDesktopLyric.toggle_visible.action,
|
||||
},
|
||||
'mod+alt+-': {
|
||||
type: hotKeyDesktopLyric.toggle_lock.type,
|
||||
name: hotKeyDesktopLyric.toggle_lock.name,
|
||||
action: hotKeyDesktopLyric.toggle_lock.action,
|
||||
},
|
||||
'mod+alt+=': {
|
||||
type: hotKeyDesktopLyric.toggle_always_top.type,
|
||||
name: hotKeyDesktopLyric.toggle_always_top.name,
|
||||
action: hotKeyDesktopLyric.toggle_always_top.action,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
114
src/common/defaultSetting.js
Normal file
114
src/common/defaultSetting.js
Normal file
@@ -0,0 +1,114 @@
|
||||
const path = require('path')
|
||||
const os = require('os')
|
||||
|
||||
const defaultSetting = {
|
||||
version: '1.0.43',
|
||||
player: {
|
||||
togglePlayMethod: 'listLoop',
|
||||
highQuality: false,
|
||||
isShowTaskProgess: true,
|
||||
volume: 1,
|
||||
isMute: false,
|
||||
mediaDeviceId: 'default',
|
||||
isMediaDeviceRemovedStopPlay: false,
|
||||
isShowLyricTranslation: false,
|
||||
isPlayLxlrc: true,
|
||||
isSavePlayTime: false,
|
||||
},
|
||||
desktopLyric: {
|
||||
enable: false,
|
||||
isLock: false,
|
||||
isAlwaysOnTop: false,
|
||||
width: 380,
|
||||
height: 420,
|
||||
x: null,
|
||||
y: null,
|
||||
theme: 0,
|
||||
isLockScreen: true,
|
||||
style: {
|
||||
fontSize: 120,
|
||||
opacity: 95,
|
||||
isZoomActiveLrc: true,
|
||||
},
|
||||
},
|
||||
list: {
|
||||
isShowAlbumName: true,
|
||||
isShowSource: true,
|
||||
prevSelectListId: 'default',
|
||||
isSaveScrollLocation: true,
|
||||
addMusicLocationType: 'top',
|
||||
},
|
||||
download: {
|
||||
enable: false,
|
||||
savePath: path.join(os.homedir(), 'Desktop'),
|
||||
fileName: '歌名 - 歌手',
|
||||
maxDownloadNum: 3,
|
||||
isDownloadLrc: false,
|
||||
isEmbedPic: true,
|
||||
isEmbedLyric: false,
|
||||
isUseOtherSource: false,
|
||||
},
|
||||
leaderboard: {
|
||||
source: 'kw',
|
||||
tabId: 'kw__16',
|
||||
},
|
||||
songList: {
|
||||
source: 'kg',
|
||||
sortId: '5',
|
||||
tagInfo: {
|
||||
name: '默认',
|
||||
id: null,
|
||||
},
|
||||
},
|
||||
odc: {
|
||||
isAutoClearSearchInput: false,
|
||||
isAutoClearSearchList: false,
|
||||
},
|
||||
search: {
|
||||
searchSource: 'all',
|
||||
tempSearchSource: 'kw',
|
||||
isShowHotSearch: false,
|
||||
isShowHistorySearch: false,
|
||||
isFocusSearchBox: false,
|
||||
},
|
||||
network: {
|
||||
proxy: {
|
||||
enable: false,
|
||||
host: '',
|
||||
port: '',
|
||||
username: '',
|
||||
password: '',
|
||||
},
|
||||
},
|
||||
tray: {
|
||||
isShow: false,
|
||||
isToTray: false,
|
||||
themeId: 0,
|
||||
},
|
||||
sync: {
|
||||
enable: false,
|
||||
port: '23332',
|
||||
},
|
||||
windowSizeId: 2,
|
||||
themeId: 0,
|
||||
langId: null,
|
||||
sourceId: 'kw',
|
||||
apiSource: 'temp',
|
||||
sourceNameType: 'alias',
|
||||
isShowAnimation: true,
|
||||
randomAnimate: true,
|
||||
ignoreVersion: null,
|
||||
isAgreePact: false,
|
||||
controlBtnPosition: process.platform === 'darwin' ? 'left' : 'right',
|
||||
}
|
||||
|
||||
const overwriteSetting = {
|
||||
|
||||
}
|
||||
|
||||
// 使用新年皮肤
|
||||
if (new Date().getMonth() < 2) defaultSetting.themeId = 9
|
||||
|
||||
|
||||
exports.defaultSetting = defaultSetting
|
||||
exports.overwriteSetting = overwriteSetting
|
||||
12
src/common/error.js
Normal file
12
src/common/error.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const { log } = require('./utils')
|
||||
|
||||
process.on('uncaughtException', function(err) {
|
||||
console.error('An uncaught error occurred!')
|
||||
console.error(err)
|
||||
log.error(err)
|
||||
})
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.error('Unhandled Rejection at: Promise ', p)
|
||||
console.error(' reason: ', reason)
|
||||
log.error(reason)
|
||||
})
|
||||
83
src/common/hotKey.js
Normal file
83
src/common/hotKey.js
Normal file
@@ -0,0 +1,83 @@
|
||||
const names = require('@main/events/_name')
|
||||
const hotKey = {
|
||||
common: {
|
||||
min: {
|
||||
name: 'min',
|
||||
action: 'min',
|
||||
},
|
||||
min_toggle: {
|
||||
name: 'toggle_min',
|
||||
action: 'toggle_min',
|
||||
},
|
||||
hide_toggle: {
|
||||
name: 'toggle_hide',
|
||||
action: 'toggle_hide',
|
||||
},
|
||||
close: {
|
||||
name: 'toggle_close',
|
||||
action: 'toggle_close',
|
||||
},
|
||||
focusSearchInput: {
|
||||
name: 'focus_search_input',
|
||||
action: 'focus_search_input',
|
||||
},
|
||||
},
|
||||
player: {
|
||||
toggle_play: {
|
||||
name: 'toggle_play',
|
||||
action: 'toggle_play',
|
||||
},
|
||||
next: {
|
||||
name: 'next',
|
||||
action: 'next',
|
||||
},
|
||||
prev: {
|
||||
name: 'prev',
|
||||
action: 'prev',
|
||||
},
|
||||
volume_up: {
|
||||
name: 'volume_up',
|
||||
action: 'volume_up',
|
||||
},
|
||||
volume_down: {
|
||||
name: 'volume_down',
|
||||
action: 'volume_down',
|
||||
},
|
||||
volume_mute: {
|
||||
name: 'volume_mute',
|
||||
action: 'volume_mute',
|
||||
},
|
||||
},
|
||||
desktop_lyric: {
|
||||
toggle_visible: {
|
||||
name: 'toggle_visible',
|
||||
action: 'toggle_visible',
|
||||
},
|
||||
toggle_lock: {
|
||||
name: 'toggle_lock',
|
||||
action: 'toggle_lock',
|
||||
},
|
||||
toggle_always_top: {
|
||||
name: 'toggle_always_top',
|
||||
action: 'toggle_always_top',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const keyName = {
|
||||
player: names.mainWindow.name,
|
||||
desktop_lyric: names.winLyric.name,
|
||||
}
|
||||
|
||||
for (const type of Object.keys(hotKey)) {
|
||||
let item = hotKey[type]
|
||||
for (const key of Object.keys(item)) {
|
||||
item[key].action = `${type}_${item[key].action}`
|
||||
item[key].name = `${type}_${item[key].name}`
|
||||
item[key].type = keyName[type]
|
||||
}
|
||||
}
|
||||
|
||||
exports.common = hotKey.common
|
||||
exports.player = hotKey.player
|
||||
exports.desktop_lyric = hotKey.desktop_lyric
|
||||
@@ -1,19 +0,0 @@
|
||||
const { ipcMain, ipcRenderer } = require('electron')
|
||||
|
||||
|
||||
export const mainSend = (name, params) => {
|
||||
ipcMain.send(name, params)
|
||||
}
|
||||
|
||||
export const mainOn = (name, callback) => {
|
||||
ipcMain.on(name, callback)
|
||||
}
|
||||
|
||||
|
||||
export const rendererSend = (name, params) => {
|
||||
ipcRenderer.send(name, params)
|
||||
}
|
||||
|
||||
export const rendererOn = (name, callback) => {
|
||||
ipcRenderer.on(name, callback)
|
||||
}
|
||||
52
src/common/ipc.js
Normal file
52
src/common/ipc.js
Normal file
@@ -0,0 +1,52 @@
|
||||
const { ipcMain, ipcRenderer } = require('electron')
|
||||
const names = require('./ipcNames')
|
||||
|
||||
|
||||
exports.mainOn = (name, callback) => {
|
||||
ipcMain.on(name, callback)
|
||||
}
|
||||
exports.mainOnce = (name, callback) => {
|
||||
ipcMain.once(name, callback)
|
||||
}
|
||||
exports.mainOff = (name, callback) => {
|
||||
ipcMain.removeListener(name, callback)
|
||||
}
|
||||
exports.mainOffAll = name => {
|
||||
ipcMain.removeAllListeners(name)
|
||||
}
|
||||
|
||||
exports.mainHandle = (name, callback) => {
|
||||
ipcMain.handle(name, callback)
|
||||
}
|
||||
exports.mainHandleOnce = (name, callback) => {
|
||||
ipcMain.handleOnce(name, callback)
|
||||
}
|
||||
exports.mainHandleRemove = name => {
|
||||
ipcMain.removeListener(name)
|
||||
}
|
||||
|
||||
exports.mainSend = (window, name, params) => {
|
||||
window.webContents.send(name, params)
|
||||
}
|
||||
|
||||
exports.rendererSend = (name, params) => {
|
||||
ipcRenderer.send(name, params)
|
||||
}
|
||||
exports.rendererSendSync = (name, params) => ipcRenderer.sendSync(name, params)
|
||||
|
||||
exports.rendererInvoke = (name, params) => ipcRenderer.invoke(name, params)
|
||||
|
||||
exports.rendererOn = (name, callback) => {
|
||||
ipcRenderer.on(name, callback)
|
||||
}
|
||||
exports.rendererOnce = (name, callback) => {
|
||||
ipcRenderer.once(name, callback)
|
||||
}
|
||||
exports.rendererOff = (name, callback) => {
|
||||
ipcRenderer.removeListener(name, callback)
|
||||
}
|
||||
exports.rendererOffAll = name => {
|
||||
ipcRenderer.removeAllListeners(name)
|
||||
}
|
||||
|
||||
exports.NAMES = names
|
||||
102
src/common/ipcNames.js
Normal file
102
src/common/ipcNames.js
Normal file
@@ -0,0 +1,102 @@
|
||||
const names = {
|
||||
mainWindow: {
|
||||
focus: 'focus',
|
||||
close: 'close',
|
||||
min: 'min',
|
||||
max: 'max',
|
||||
set_app_name: 'set_app_name',
|
||||
clear_cache: 'clear_cache',
|
||||
get_cache_size: 'get_cache_size',
|
||||
get_env_params: 'get_env_params',
|
||||
|
||||
set_music_meta: 'set_music_meta',
|
||||
progress: 'progress',
|
||||
change_tray: 'change_tray',
|
||||
quit_update: 'quit_update',
|
||||
update_available: 'update_available',
|
||||
update_error: 'update_error',
|
||||
update_progress: 'update_progress',
|
||||
update_downloaded: 'update_downloaded',
|
||||
update_not_available: 'update_not_available',
|
||||
set_ignore_mouse_events: 'set_ignore_mouse_events',
|
||||
set_app_setting: 'set_app_setting',
|
||||
set_window_size: 'set_window_size',
|
||||
show_save_dialog: 'show_save_dialog',
|
||||
|
||||
handle_request: 'handle_request',
|
||||
cancel_request: 'cancel_request',
|
||||
|
||||
handle_xm_verify_open: 'handle_xm_verify_open',
|
||||
handle_xm_verify_close: 'handle_xm_verify_close',
|
||||
select_dir: 'select_dir',
|
||||
|
||||
restart_window: 'restart_window',
|
||||
|
||||
handle_kw_decode_lyric: 'handle_kw_decode_lyric',
|
||||
get_lyric_info: 'get_lyric_info',
|
||||
set_lyric_info: 'set_lyric_info',
|
||||
set_config: 'set_config',
|
||||
set_hot_key_config: 'set_hot_key_config',
|
||||
key_down: 'key_down',
|
||||
quit: 'quit',
|
||||
min_toggle: 'min_toggle',
|
||||
hide_toggle: 'hide_toggle',
|
||||
get_data_path: 'get_data_path',
|
||||
show_dialog: 'show_dialog',
|
||||
|
||||
get_setting: 'get_setting',
|
||||
get_playlist: 'get_playlist',
|
||||
save_playlist: 'save_playlist',
|
||||
get_data: 'get_data',
|
||||
save_data: 'save_data',
|
||||
get_hot_key: 'get_hot_key',
|
||||
|
||||
import_user_api: 'import_user_api',
|
||||
remove_user_api: 'remove_user_api',
|
||||
set_user_api: 'set_user_api',
|
||||
get_user_api_list: 'get_user_api_list',
|
||||
request_user_api: 'request_user_api',
|
||||
request_user_api_cancel: 'request_user_api_cancel',
|
||||
get_user_api_status: 'get_user_api_status',
|
||||
user_api_status: 'user_api_status',
|
||||
|
||||
get_lyric: 'get_lyric',
|
||||
save_lyric: 'save_lyric',
|
||||
clear_lyric: 'clear_lyric',
|
||||
get_music_url: 'get_music_url',
|
||||
save_music_url: 'save_music_url',
|
||||
clear_music_url: 'clear_music_url',
|
||||
|
||||
sync_enable: 'sync_enable',
|
||||
sync_status: 'sync_status',
|
||||
sync_get_status: 'sync_get_status',
|
||||
sync_generate_code: 'sync_generate_code',
|
||||
sync_action_list: 'sync_action_list',
|
||||
sync_list: 'sync_list',
|
||||
},
|
||||
winLyric: {
|
||||
close: 'close',
|
||||
set_lyric_info: 'set_lyric_info',
|
||||
get_lyric_info: 'get_lyric_info',
|
||||
set_lyric_config: 'set_lyric_config',
|
||||
get_lyric_config: 'get_lyric_config',
|
||||
set_win_bounds: 'set_win_bounds',
|
||||
key_down: 'key_down',
|
||||
},
|
||||
hotKey: {
|
||||
enable: 'enable',
|
||||
status: 'status',
|
||||
set_config: 'set_config',
|
||||
},
|
||||
}
|
||||
|
||||
for (const item of Object.keys(names)) {
|
||||
let name = names[item]
|
||||
for (const key of Object.keys(name)) {
|
||||
name[key] = `${item}_${name[key]}`
|
||||
}
|
||||
}
|
||||
|
||||
exports.mainWindow = names.mainWindow
|
||||
exports.winLyric = names.winLyric
|
||||
exports.hotKey = names.hotKey
|
||||
42
src/common/store.js
Normal file
42
src/common/store.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const Store = require('electron-store')
|
||||
const { dialog, app, shell } = require('electron')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const log = require('electron-log')
|
||||
|
||||
const stores = {}
|
||||
|
||||
/**
|
||||
* 获取 Store 对象
|
||||
* @param {*} name store 名
|
||||
* @param {*} isIgnoredError 是否忽略错误
|
||||
* @param {*} isShowErrorAlert 是否显示错误弹窗
|
||||
* @returns Store
|
||||
*/
|
||||
module.exports = (name, isIgnoredError = true, isShowErrorAlert = true) => {
|
||||
if (stores[name]) return stores[name]
|
||||
let store
|
||||
try {
|
||||
store = stores[name] = new Store({ name, clearInvalidConfig: false })
|
||||
} catch (error) {
|
||||
log.error(error)
|
||||
|
||||
if (!isIgnoredError) throw error
|
||||
|
||||
|
||||
const backPath = path.join(app.getPath('userData'), name + '.json.bak')
|
||||
fs.copyFileSync(path.join(app.getPath('userData'), name + '.json'), backPath)
|
||||
if (isShowErrorAlert) {
|
||||
dialog.showMessageBoxSync({
|
||||
type: 'error',
|
||||
message: name + ' data load error',
|
||||
detail: `We have helped you back up the old ${name} file to: ${backPath}\nYou can try to repair and restore it manually\n\nError detail: ${error.message}`,
|
||||
})
|
||||
shell.showItemInFolder(backPath)
|
||||
}
|
||||
|
||||
|
||||
store = new Store({ name, clearInvalidConfig: true })
|
||||
}
|
||||
return store
|
||||
}
|
||||
217
src/common/utils.js
Normal file
217
src/common/utils.js
Normal file
@@ -0,0 +1,217 @@
|
||||
const log = require('electron-log')
|
||||
const { defaultSetting, overwriteSetting } = require('./defaultSetting')
|
||||
// const apiSource = require('../renderer/utils/music/api-source-info')
|
||||
const getStore = require('./store')
|
||||
const defaultHotKey = require('./defaultHotKey')
|
||||
|
||||
exports.isLinux = process.platform == 'linux'
|
||||
exports.isWin = process.platform == 'win32'
|
||||
exports.isMac = process.platform == 'darwin'
|
||||
|
||||
|
||||
/**
|
||||
* 生成节流函数
|
||||
* @param {*} fn
|
||||
* @param {*} delay
|
||||
*/
|
||||
exports.throttle = (fn, delay = 100) => {
|
||||
let timer = null
|
||||
let _args = null
|
||||
return function(...args) {
|
||||
_args = args
|
||||
if (timer) return
|
||||
timer = setTimeout(() => {
|
||||
timer = null
|
||||
fn.apply(this, _args)
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成防抖函数
|
||||
* @param {*} fn
|
||||
* @param {*} delay
|
||||
*/
|
||||
exports.debounce = (fn, delay = 100) => {
|
||||
let timer = null
|
||||
let _args = null
|
||||
return function(...args) {
|
||||
_args = args
|
||||
if (timer) clearTimeout(timer)
|
||||
timer = setTimeout(() => {
|
||||
timer = null
|
||||
fn.apply(this, _args)
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
exports.log = log
|
||||
|
||||
// https://stackoverflow.com/a/53387532
|
||||
exports.compareVer = (currentVer, targetVer) => {
|
||||
// treat non-numerical characters as lower version
|
||||
// replacing them with a negative number based on charcode of each character
|
||||
const fix = s => `.${s.toLowerCase().charCodeAt(0) - 2147483647}.`
|
||||
|
||||
currentVer = ('' + currentVer).replace(/[^0-9.]/g, fix).split('.')
|
||||
targetVer = ('' + targetVer).replace(/[^0-9.]/g, fix).split('.')
|
||||
let c = Math.max(currentVer.length, targetVer.length)
|
||||
for (let i = 0; i < c; i++) {
|
||||
// convert to integer the most efficient way
|
||||
currentVer[i] = ~~currentVer[i]
|
||||
targetVer[i] = ~~targetVer[i]
|
||||
if (currentVer[i] > targetVer[i]) return 1
|
||||
else if (currentVer[i] < targetVer[i]) return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
exports.isObject = item => item && typeof item === 'object' && !Array.isArray(item)
|
||||
|
||||
/**
|
||||
* 对象深度合并
|
||||
* @param {} target 要合并源对象
|
||||
* @param {} source 要合并目标对象
|
||||
*/
|
||||
exports.objectDeepMerge = (target, source, mergedObj) => {
|
||||
if (!mergedObj) {
|
||||
mergedObj = new Set()
|
||||
mergedObj.add(target)
|
||||
}
|
||||
let base = {}
|
||||
Object.keys(source).forEach(item => {
|
||||
if (exports.isObject(source[item])) {
|
||||
if (mergedObj.has(source[item])) return
|
||||
if (!exports.isObject(target[item])) target[item] = {}
|
||||
mergedObj.add(source[item])
|
||||
exports.objectDeepMerge(target[item], source[item], mergedObj)
|
||||
return
|
||||
}
|
||||
base[item] = source[item]
|
||||
})
|
||||
Object.assign(target, base)
|
||||
}
|
||||
|
||||
exports.mergeSetting = (setting, version) => {
|
||||
let defaultSettingCopy = JSON.parse(JSON.stringify(defaultSetting))
|
||||
let overwriteSettingCopy = JSON.parse(JSON.stringify(overwriteSetting))
|
||||
const defaultVersion = defaultSettingCopy.version
|
||||
if (!version) {
|
||||
if (setting) {
|
||||
version = setting.version
|
||||
delete setting.version
|
||||
}
|
||||
}
|
||||
|
||||
if (!setting) {
|
||||
setting = defaultSettingCopy
|
||||
} else if (exports.compareVer(version, defaultVersion) < 0) {
|
||||
exports.objectDeepMerge(defaultSettingCopy, setting)
|
||||
exports.objectDeepMerge(defaultSettingCopy, overwriteSettingCopy)
|
||||
setting = defaultSettingCopy
|
||||
}
|
||||
|
||||
// if (!apiSource.some(api => api.id === setting.apiSource && !api.disabled)) {
|
||||
// let api = apiSource.find(api => !api.disabled)
|
||||
// if (api) setting.apiSource = api.id
|
||||
// }
|
||||
|
||||
return { setting, version: defaultVersion }
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化设置
|
||||
* @param {*} setting
|
||||
* @param {*} isShowErrorAlert
|
||||
*/
|
||||
exports.initSetting = isShowErrorAlert => {
|
||||
const electronStore_list = getStore('playList', true, isShowErrorAlert)
|
||||
const electronStore_config = getStore('config')
|
||||
const electronStore_downloadList = getStore('downloadList')
|
||||
|
||||
let setting = electronStore_config.get('setting')
|
||||
if (setting) {
|
||||
let version = electronStore_config.get('version')
|
||||
if (!version) { // 迁移配置
|
||||
version = electronStore_config.get('setting.version')
|
||||
electronStore_config.set('version', version)
|
||||
electronStore_config.delete('setting.version')
|
||||
const list = electronStore_config.get('list')
|
||||
if (list) {
|
||||
if (list.defaultList) electronStore_list.set('defaultList', list.defaultList)
|
||||
if (list.loveList) electronStore_list.set('loveList', list.loveList)
|
||||
electronStore_config.delete('list')
|
||||
}
|
||||
const downloadList = electronStore_config.get('download')
|
||||
if (downloadList) {
|
||||
if (downloadList.list) electronStore_downloadList.set('list', downloadList.list)
|
||||
electronStore_config.delete('download')
|
||||
}
|
||||
}
|
||||
|
||||
// 迁移列表滚动位置设置 ~0.18.3
|
||||
if (setting.list.scroll) {
|
||||
let scroll = setting.list.scroll
|
||||
electronStore_list.set('defaultList.location', scroll.locations.default || 0)
|
||||
electronStore_list.set('loveList.location', scroll.locations.love || 0)
|
||||
electronStore_config.delete('setting.list.scroll')
|
||||
electronStore_config.set('setting.list.isSaveScrollLocation', scroll.enable)
|
||||
delete setting.list.scroll
|
||||
}
|
||||
|
||||
if (setting.player.isShowLyricTransition != null) { // 修正拼写问题 v1.8.2 及以前
|
||||
setting.player.isShowLyricTranslation = setting.player.isShowLyricTransition
|
||||
delete setting.player.isShowLyricTransition
|
||||
}
|
||||
}
|
||||
|
||||
// 从我的列表分离下载列表 v1.7.0 后
|
||||
let downloadList = electronStore_list.get('downloadList')
|
||||
if (downloadList) {
|
||||
electronStore_downloadList.set('list', downloadList)
|
||||
electronStore_list.delete('downloadList')
|
||||
}
|
||||
|
||||
const { version: settingVersion, setting: newSetting } = exports.mergeSetting(setting, electronStore_config.get('version'))
|
||||
|
||||
// 重置 ^0.18.2 排行榜ID
|
||||
if (!newSetting.leaderboard.tabId.includes('__')) newSetting.leaderboard.tabId = 'kw__16'
|
||||
|
||||
// newSetting.controlBtnPosition = 'right'
|
||||
electronStore_config.set({ version: settingVersion, setting: newSetting })
|
||||
return { version: settingVersion, setting: newSetting }
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化快捷键设置
|
||||
*/
|
||||
exports.initHotKey = () => {
|
||||
const electronStore_hotKey = getStore('hotKey')
|
||||
|
||||
let localConfig = electronStore_hotKey.get('local')
|
||||
if (!localConfig) {
|
||||
localConfig = defaultHotKey.local
|
||||
electronStore_hotKey.set('local', localConfig)
|
||||
}
|
||||
|
||||
let globalConfig = electronStore_hotKey.get('global')
|
||||
|
||||
// 移除v1.0.1及之前设置的全局声音媒体快捷键接管
|
||||
if (globalConfig && globalConfig.keys.VolumeUp) {
|
||||
delete globalConfig.keys.VolumeUp
|
||||
delete globalConfig.keys.VolumeDown
|
||||
delete globalConfig.keys.VolumeMute
|
||||
electronStore_hotKey.set('global', globalConfig)
|
||||
}
|
||||
|
||||
if (!globalConfig) {
|
||||
globalConfig = defaultHotKey.global
|
||||
electronStore_hotKey.set('global', globalConfig)
|
||||
}
|
||||
|
||||
return {
|
||||
global: globalConfig,
|
||||
local: localConfig,
|
||||
}
|
||||
}
|
||||
17
src/main/env/cmdParams.js
vendored
Normal file
17
src/main/env/cmdParams.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
const parseEnv = () => {
|
||||
const params = {}
|
||||
const rx = /^-\w+/
|
||||
for (let param of process.argv) {
|
||||
if (!rx.test(param)) continue
|
||||
param = param.substring(1)
|
||||
let index = param.indexOf('=')
|
||||
if (index < 0) {
|
||||
params[param] = true
|
||||
} else {
|
||||
params[param.substring(0, index)] = param.substring(index + 1)
|
||||
}
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
global.envParams.cmdParams = parseEnv()
|
||||
4
src/main/env/index.js
vendored
Normal file
4
src/main/env/index.js
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
global.envParams = {}
|
||||
|
||||
require('./cmdParams')
|
||||
require('./screen')
|
||||
11
src/main/env/screen.js
vendored
Normal file
11
src/main/env/screen.js
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
const { app, screen } = require('electron')
|
||||
|
||||
const initScreenParams = () => {
|
||||
global.envParams.workAreaSize = screen.getPrimaryDisplay().workAreaSize
|
||||
}
|
||||
|
||||
|
||||
app.on('ready', () => {
|
||||
screen.on('display-metrics-changed', initScreenParams)
|
||||
initScreenParams()
|
||||
})
|
||||
34
src/main/event.js
Normal file
34
src/main/event.js
Normal file
@@ -0,0 +1,34 @@
|
||||
const { common: COMMON_EVENT_NAME, mainWindow: MAIN_WINDOW_EVENT_NAME } = require('./events/_name')
|
||||
const { mainSend, NAMES: { mainWindow: ipcMainWindowNames } } = require('./../common/ipc')
|
||||
const { getAppHotKeyConfig } = require('./utils')
|
||||
|
||||
global.lx_event.common.on(COMMON_EVENT_NAME.configStatus, name => {
|
||||
if (MAIN_WINDOW_EVENT_NAME.name === name) return
|
||||
if (global.modules.mainWindow) mainSend(global.modules.mainWindow, ipcMainWindowNames.set_config, global.appSetting)
|
||||
})
|
||||
|
||||
global.lx_event.common.on(COMMON_EVENT_NAME.initHotKey, () => {
|
||||
global.appHotKey.config = getAppHotKeyConfig()
|
||||
})
|
||||
|
||||
global.lx_event.mainWindow.on(MAIN_WINDOW_EVENT_NAME.quit, () => {
|
||||
global.isTrafficLightClose = false
|
||||
global.isQuitting = true
|
||||
global.modules.mainWindow.close()
|
||||
})
|
||||
global.lx_event.mainWindow.on(MAIN_WINDOW_EVENT_NAME.toggle_minimize, () => {
|
||||
if (global.modules.mainWindow.isMinimized()) {
|
||||
if (!global.modules.mainWindow.isVisible()) {
|
||||
global.modules.mainWindow.show()
|
||||
}
|
||||
global.modules.mainWindow.restore()
|
||||
global.modules.mainWindow.focus()
|
||||
} else {
|
||||
global.modules.mainWindow.minimize()
|
||||
}
|
||||
})
|
||||
global.lx_event.mainWindow.on(MAIN_WINDOW_EVENT_NAME.toggle_hide, () => {
|
||||
global.modules.mainWindow.isVisible()
|
||||
? global.modules.mainWindow.hide()
|
||||
: global.modules.mainWindow.show()
|
||||
})
|
||||
20
src/main/events/Common.js
Normal file
20
src/main/events/Common.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const { EventEmitter } = require('events')
|
||||
const { common: COMMON_EVENT_NAME } = require('./_name')
|
||||
|
||||
class Common extends EventEmitter {
|
||||
initSetting() {
|
||||
this.emit(COMMON_EVENT_NAME.initConfig)
|
||||
this.configStatus(null)
|
||||
}
|
||||
|
||||
configStatus(name) {
|
||||
this.emit(COMMON_EVENT_NAME.configStatus, name)
|
||||
}
|
||||
|
||||
saveMyList(data) {
|
||||
this.emit(COMMON_EVENT_NAME.saveMyList, data)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Common
|
||||
|
||||
21
src/main/events/HotKey.js
Normal file
21
src/main/events/HotKey.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const { EventEmitter } = require('events')
|
||||
const { hotKey: HOT_KEY_EVENT_NAME } = require('./_name')
|
||||
const { saveAppHotKeyConfig } = require('../utils')
|
||||
|
||||
class HotKey extends EventEmitter {
|
||||
init() {
|
||||
this.emit(HOT_KEY_EVENT_NAME.init)
|
||||
}
|
||||
|
||||
saveConfig(config, source) {
|
||||
if (config) saveAppHotKeyConfig(config)
|
||||
this.emit(HOT_KEY_EVENT_NAME.config, config, source)
|
||||
}
|
||||
|
||||
keyDown(info) {
|
||||
this.emit(HOT_KEY_EVENT_NAME.keyDown, info)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HotKey
|
||||
|
||||
34
src/main/events/MainWindow.js
Normal file
34
src/main/events/MainWindow.js
Normal file
@@ -0,0 +1,34 @@
|
||||
const { EventEmitter } = require('events')
|
||||
const { mainWindow: MAIN_WINDOW_EVENT_NAME } = require('./_name')
|
||||
|
||||
class MainWindow extends EventEmitter {
|
||||
setLyricInfo(info) {
|
||||
this.emit(MAIN_WINDOW_EVENT_NAME.setLyricInfo, info)
|
||||
}
|
||||
|
||||
quit() {
|
||||
this.emit(MAIN_WINDOW_EVENT_NAME.quit)
|
||||
}
|
||||
|
||||
toggleMinimize() {
|
||||
this.emit(MAIN_WINDOW_EVENT_NAME.toggle_minimize)
|
||||
}
|
||||
|
||||
toggleHide() {
|
||||
this.emit(MAIN_WINDOW_EVENT_NAME.toggle_hide)
|
||||
}
|
||||
|
||||
readyToShow() {
|
||||
this.emit(MAIN_WINDOW_EVENT_NAME.ready_to_show)
|
||||
}
|
||||
|
||||
show() {
|
||||
this.emit(MAIN_WINDOW_EVENT_NAME.show)
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.emit(MAIN_WINDOW_EVENT_NAME.hide)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MainWindow
|
||||
14
src/main/events/Tray.js
Normal file
14
src/main/events/Tray.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const { EventEmitter } = require('events')
|
||||
const { tray: TRAY_EVENT_NAME } = require('./_name')
|
||||
|
||||
class Tray extends EventEmitter {
|
||||
create() {
|
||||
this.emit(TRAY_EVENT_NAME.create)
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.emit(TRAY_EVENT_NAME.destroy)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Tray
|
||||
15
src/main/events/WinLyric.js
Normal file
15
src/main/events/WinLyric.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const { EventEmitter } = require('events')
|
||||
const { winLyric: WIN_LYRIC_EVENT_NAME } = require('./_name')
|
||||
// const { updateSetting } = require('../utils')
|
||||
|
||||
class WinLyric extends EventEmitter {
|
||||
create() {
|
||||
this.emit(WIN_LYRIC_EVENT_NAME.create)
|
||||
}
|
||||
|
||||
close() {
|
||||
this.emit(WIN_LYRIC_EVENT_NAME.close)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WinLyric
|
||||
36
src/main/events/_name.js
Normal file
36
src/main/events/_name.js
Normal file
@@ -0,0 +1,36 @@
|
||||
exports.common = {
|
||||
initConfig: 'initConfig',
|
||||
configStatus: 'config',
|
||||
saveMyList: 'saveMyList',
|
||||
}
|
||||
|
||||
exports.mainWindow = {
|
||||
name: 'mainWindow',
|
||||
setLyricInfo: 'setLyricInfo',
|
||||
destroy: 'destroy',
|
||||
quit: 'quit',
|
||||
toggle_minimize: 'toggle_minimize',
|
||||
toggle_hide: 'toggle_hide',
|
||||
ready_to_show: 'ready_to_show',
|
||||
show: 'show',
|
||||
hide: 'hide',
|
||||
}
|
||||
|
||||
exports.tray = {
|
||||
name: 'tray',
|
||||
create: 'create',
|
||||
destroy: 'destroy',
|
||||
}
|
||||
|
||||
exports.winLyric = {
|
||||
name: 'winLyric',
|
||||
create: 'create',
|
||||
close: 'close',
|
||||
}
|
||||
|
||||
exports.hotKey = {
|
||||
name: 'hotKey',
|
||||
init: 'init',
|
||||
config: 'config',
|
||||
keyDown: 'keyDown',
|
||||
}
|
||||
@@ -1,4 +1,19 @@
|
||||
if (!global.lx_event) global.lx_event = {}
|
||||
|
||||
require('./request')
|
||||
require('./appName')
|
||||
const Common = require('./Common')
|
||||
const MainWindow = require('./MainWindow')
|
||||
const Tray = require('./Tray')
|
||||
const WinLyric = require('./WinLyric')
|
||||
const HotKey = require('./HotKey')
|
||||
|
||||
const { Event: UserApi } = require('../modules/userApi')
|
||||
const { Event: Sync } = require('../modules/sync')
|
||||
|
||||
if (!global.lx_event.common) global.lx_event.common = new Common()
|
||||
if (!global.lx_event.mainWindow) global.lx_event.mainWindow = new MainWindow()
|
||||
if (!global.lx_event.tray) global.lx_event.tray = new Tray()
|
||||
if (!global.lx_event.winLyric) global.lx_event.winLyric = new WinLyric()
|
||||
if (!global.lx_event.hotKey) global.lx_event.hotKey = new HotKey()
|
||||
|
||||
if (!global.lx_event.userApi) global.lx_event.userApi = new UserApi()
|
||||
if (!global.lx_event.sync) global.lx_event.sync = new Sync()
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
const { mainOn } = require('../../common/icp')
|
||||
|
||||
module.exports = win => {
|
||||
mainOn('progress', (event, params) => {
|
||||
// console.log(params)
|
||||
win.setProgressBar(params.status, {
|
||||
mode: params.mode || 'normal',
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
const { mainOn } = require('../../common/icp')
|
||||
const { dialog } = require('electron')
|
||||
|
||||
module.exports = win => {
|
||||
mainOn('selectPath', (event, params) => {
|
||||
let path = dialog.showOpenDialog(win, params.options)
|
||||
if (path === undefined) return
|
||||
event.sender.send(params.eventName, path)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
const { mainOn } = require('../../common/icp')
|
||||
|
||||
module.exports = win => {
|
||||
mainOn('min', event => {
|
||||
if (win) {
|
||||
win.minimize()
|
||||
}
|
||||
})
|
||||
// mainOn('max', event => {
|
||||
// if (win) {
|
||||
// win.maximize()
|
||||
// }
|
||||
// })
|
||||
mainOn('close', event => {
|
||||
if (win) {
|
||||
win.close()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,74 +1,179 @@
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
const { app, BrowserWindow, shell } = require('electron')
|
||||
const path = require('path')
|
||||
|
||||
// 单例应用程序
|
||||
if (!app.requestSingleInstanceLock()) {
|
||||
app.quit()
|
||||
process.exit(0)
|
||||
}
|
||||
if (!global.modules) global.modules = {}
|
||||
app.on('second-instance', (event, argv, cwd) => {
|
||||
if (global.modules.mainWindow) {
|
||||
if (global.modules.mainWindow.isMinimized()) {
|
||||
global.modules.mainWindow.restore()
|
||||
}
|
||||
if (global.modules.mainWindow.isVisible()) {
|
||||
global.modules.mainWindow.focus()
|
||||
} else {
|
||||
global.modules.mainWindow.show()
|
||||
}
|
||||
} else {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
const isDev = global.isDev = process.env.NODE_ENV !== 'production'
|
||||
require('./env')
|
||||
// console.log(global.envParams.cmdParams)
|
||||
|
||||
// Is disable hardware acceleration
|
||||
if (global.envParams.cmdParams.dha) app.disableHardwareAcceleration()
|
||||
|
||||
if (global.envParams.cmdParams.dt == null && global.envParams.cmdParams.nt != null) global.envParams.cmdParams.dt = global.envParams.cmdParams.nt
|
||||
if (global.envParams.cmdParams.dhmkh) app.commandLine.appendSwitch('disable-features', 'HardwareMediaKeyHandling')
|
||||
// fix linux transparent fail. https://github.com/electron/electron/issues/25153#issuecomment-843688494
|
||||
if (process.platform == 'linux') app.commandLine.appendSwitch('use-gl', 'desktop')
|
||||
|
||||
// https://github.com/electron/electron/issues/22691
|
||||
app.commandLine.appendSwitch('wm-window-animations-disabled')
|
||||
|
||||
|
||||
const { navigationUrlWhiteList } = require('../common/config')
|
||||
const { getWindowSizeInfo, initSetting, updateSetting } = require('./utils')
|
||||
const { isMac, isLinux, initHotKey } = require('../common/utils')
|
||||
|
||||
|
||||
// https://github.com/electron/electron/issues/18397
|
||||
// 开发模式下为true时 多次引入native模块会导致渲染进程卡死
|
||||
// https://github.com/electron/electron/issues/22791
|
||||
app.allowRendererProcessReuse = !isDev
|
||||
|
||||
|
||||
app.on('web-contents-created', (event, contents) => {
|
||||
contents.on('will-navigate', (event, navigationUrl) => {
|
||||
if (isDev) return console.log('navigation to url:', navigationUrl)
|
||||
if (!navigationUrlWhiteList.some(url => url.test(navigationUrl))) return event.preventDefault()
|
||||
console.log('navigation to url:', navigationUrl)
|
||||
})
|
||||
contents.setWindowOpenHandler(({ url }) => {
|
||||
if (!/^devtools/.test(url) && /^https?:\/\//.test(url)) {
|
||||
shell.openExternal(url)
|
||||
}
|
||||
console.log(url)
|
||||
return { action: 'deny' }
|
||||
})
|
||||
contents.on('will-attach-webview', (event, webPreferences, params) => {
|
||||
// Strip away preload scripts if unused or verify their location is legitimate
|
||||
delete webPreferences.preload
|
||||
delete webPreferences.preloadURL
|
||||
|
||||
// Disable Node.js integration
|
||||
webPreferences.nodeIntegration = false
|
||||
|
||||
// Verify URL being loaded
|
||||
if (!navigationUrlWhiteList.some(url => url.test(params.src))) {
|
||||
event.preventDefault()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
require('../common/error')
|
||||
require('./events')
|
||||
const progressBar = require('./events/progressBar')
|
||||
const trafficLight = require('./events/trafficLight')
|
||||
require('./event')
|
||||
require('./rendererEvents')
|
||||
const winEvent = require('./rendererEvents/winEvent')
|
||||
const autoUpdate = require('./utils/autoUpdate')
|
||||
|
||||
const isDev = process.env.NODE_ENV !== 'production'
|
||||
|
||||
/**
|
||||
* Set `__static` path to static files in production
|
||||
* https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html
|
||||
*/
|
||||
|
||||
let mainWindow
|
||||
let winURL
|
||||
|
||||
if (isDev) {
|
||||
global.__static = path.join(__dirname, '../static')
|
||||
winURL = `http://localhost:9080`
|
||||
// eslint-disable-next-line no-undef
|
||||
global.__static = __static
|
||||
winURL = 'http://localhost:9080'
|
||||
} else {
|
||||
global.__static = path.join(__dirname, '/static')
|
||||
winURL = `file://${__dirname}/index.html`
|
||||
winURL = `file://${path.join(__dirname, 'index.html')}`
|
||||
}
|
||||
|
||||
function createWindow() {
|
||||
const windowSizeInfo = getWindowSizeInfo(global.appSetting)
|
||||
/**
|
||||
* Initial window options
|
||||
*/
|
||||
mainWindow = new BrowserWindow({
|
||||
height: 590,
|
||||
global.modules.mainWindow = new BrowserWindow({
|
||||
height: windowSizeInfo.height,
|
||||
useContentSize: true,
|
||||
width: 920,
|
||||
width: windowSizeInfo.width,
|
||||
frame: false,
|
||||
transparent: true,
|
||||
icon: path.join(global.__static, 'icons/lunch.ico'),
|
||||
transparent: !global.envParams.cmdParams.dt,
|
||||
enableRemoteModule: false,
|
||||
// icon: path.join(global.__static, isWin ? 'icons/256x256.ico' : 'icons/512x512.png'),
|
||||
resizable: false,
|
||||
maximizable: false,
|
||||
fullscreenable: false,
|
||||
show: false,
|
||||
webPreferences: {
|
||||
// contextIsolation: true,
|
||||
contextIsolation: false,
|
||||
webSecurity: !isDev,
|
||||
nodeIntegration: true,
|
||||
},
|
||||
})
|
||||
|
||||
mainWindow.loadURL(winURL)
|
||||
global.modules.mainWindow.loadURL(winURL)
|
||||
|
||||
mainWindow.on('closed', () => {
|
||||
mainWindow = null
|
||||
})
|
||||
winEvent(global.modules.mainWindow)
|
||||
// global.modules.mainWindow.webContents.openDevTools()
|
||||
|
||||
// mainWindow.webContents.openDevTools()
|
||||
|
||||
trafficLight(mainWindow)
|
||||
progressBar(mainWindow)
|
||||
if (!isDev) autoUpdate(mainWindow)
|
||||
if (!isDev) autoUpdate()
|
||||
}
|
||||
|
||||
app.once('ready', createWindow)
|
||||
global.appHotKey = {
|
||||
enable: true,
|
||||
config: {},
|
||||
state: null,
|
||||
}
|
||||
|
||||
global.lx_core = {
|
||||
setAppConfig(setting, name) {
|
||||
updateSetting(setting)
|
||||
global.lx_event.common.configStatus(name)
|
||||
},
|
||||
}
|
||||
|
||||
function init() {
|
||||
console.log('init')
|
||||
initSetting()
|
||||
global.appHotKey.config = initHotKey()
|
||||
global.lx_event.common.initSetting()
|
||||
global.lx_event.hotKey.init()
|
||||
createWindow()
|
||||
}
|
||||
|
||||
// https://github.com/electron/electron/issues/16809
|
||||
app.on('ready', isLinux ? () => setTimeout(init, 300) : init)
|
||||
|
||||
app.on('activate', () => {
|
||||
if (global.modules.mainWindow) {
|
||||
if (global.modules.mainWindow.isMinimized()) {
|
||||
global.modules.mainWindow.restore()
|
||||
} else if (global.modules.mainWindow.isVisible()) {
|
||||
global.modules.mainWindow.focus()
|
||||
} else {
|
||||
global.modules.mainWindow.show()
|
||||
}
|
||||
} else if (global.modules.mainWindow === null) {
|
||||
init()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
if (isMac) {
|
||||
global.lx_event.tray.destroy()
|
||||
} else {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
if (mainWindow === null) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
|
||||
require('./modules')
|
||||
|
||||
48
src/main/modules/appMenu.js
Normal file
48
src/main/modules/appMenu.js
Normal file
@@ -0,0 +1,48 @@
|
||||
const { app, Menu } = require('electron')
|
||||
const { isMac } = require('../../common/utils')
|
||||
|
||||
if (isMac) {
|
||||
const template = [
|
||||
{
|
||||
label: app.getName(),
|
||||
submenu: [
|
||||
{ label: '关于洛雪音乐', role: 'about' },
|
||||
{ type: 'separator' },
|
||||
{ label: '隐藏', role: 'hide' },
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: '退出',
|
||||
accelerator: 'Command+Q',
|
||||
click() {
|
||||
global.isQuitting = true
|
||||
app.quit()
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '窗口',
|
||||
role: 'window',
|
||||
submenu: [
|
||||
{ label: '最小化', role: 'minimize', accelerator: 'Command+W' },
|
||||
{ label: '关闭', role: 'close' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '编辑',
|
||||
submenu: [
|
||||
{ label: '撤销', accelerator: 'Command+Z', role: 'undo' },
|
||||
{ label: '恢复', accelerator: 'Shift+Command+Z', role: 'redo' },
|
||||
{ type: 'separator' },
|
||||
{ label: '剪切', accelerator: 'Command+X', role: 'cut' },
|
||||
{ label: '复制', accelerator: 'Command+C', role: 'copy' },
|
||||
{ label: '粘贴', accelerator: 'Command+V', role: 'paste' },
|
||||
{ label: '选择全部', accelerator: 'Command+A', role: 'selectAll' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
|
||||
} else {
|
||||
Menu.setApplicationMenu(null)
|
||||
}
|
||||
12
src/main/modules/hotKey/index.js
Normal file
12
src/main/modules/hotKey/index.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const { app } = require('electron')
|
||||
const { init, unRegisterHotkeyAll } = require('./utils')
|
||||
const { hotKey: HOT_KEY_EVENT_NAME } = require('../../events/_name')
|
||||
|
||||
global.appHotKey.state = {}
|
||||
|
||||
app.on('will-quit', unRegisterHotkeyAll)
|
||||
|
||||
global.lx_event.hotKey.on(HOT_KEY_EVENT_NAME.init, init)
|
||||
|
||||
|
||||
require('./rendererEvent')
|
||||
28
src/main/modules/hotKey/rendererEvent.js
Normal file
28
src/main/modules/hotKey/rendererEvent.js
Normal file
@@ -0,0 +1,28 @@
|
||||
const {
|
||||
mainHandle,
|
||||
NAMES: {
|
||||
hotKey: ipcHotKeyNames,
|
||||
},
|
||||
} = require('../../../common/ipc')
|
||||
const { init, registerHotkey, unRegisterHotkey, unRegisterHotkeyAll } = require('./utils')
|
||||
|
||||
mainHandle(ipcHotKeyNames.set_config, async(event, { action, data, source }) => {
|
||||
switch (action) {
|
||||
case 'config':
|
||||
global.lx_event.hotKey.saveConfig(data, source)
|
||||
return true
|
||||
case 'enable':
|
||||
data ? init(true) : unRegisterHotkeyAll()
|
||||
return true
|
||||
case 'register':
|
||||
return registerHotkey(data)
|
||||
case 'unregister':
|
||||
return unRegisterHotkey(data)
|
||||
}
|
||||
})
|
||||
|
||||
mainHandle(ipcHotKeyNames.status, async() => global.appHotKey.state)
|
||||
|
||||
mainHandle(ipcHotKeyNames.enable, async(event, flag) => {
|
||||
flag ? init() : unRegisterHotkeyAll()
|
||||
})
|
||||
67
src/main/modules/hotKey/utils.js
Normal file
67
src/main/modules/hotKey/utils.js
Normal file
@@ -0,0 +1,67 @@
|
||||
const { globalShortcut } = require('electron')
|
||||
const { log } = require('../../../common/utils')
|
||||
|
||||
const handleKeyDown = key => {
|
||||
if (!global.appHotKey.enable) return
|
||||
global.lx_event.hotKey.keyDown({ type: 'global', key })
|
||||
}
|
||||
|
||||
const transformedKeyRxp = /(^|\+)[a-z]/g
|
||||
|
||||
const transformedKey = key => {
|
||||
if (key.includes('arrow')) key = key.replace(/arrow/g, '')
|
||||
return key.replace('mod', 'CommandOrControl').replace(transformedKeyRxp, l => l.toUpperCase())
|
||||
}
|
||||
exports.registerHotkey = ({ key, info }) => {
|
||||
if (global.appHotKey.state[key] && global.appHotKey.state[key].status) return true
|
||||
let transKey = transformedKey(key)
|
||||
// console.log('Register key:', transKey)
|
||||
if (!global.appHotKey.state[key]) {
|
||||
global.appHotKey.state[key] = {
|
||||
status: false,
|
||||
info: null,
|
||||
}
|
||||
}
|
||||
global.appHotKey.state[key].info = info
|
||||
let status = global.appHotKey.state[key].status = globalShortcut.isRegistered(transKey)
|
||||
? false
|
||||
: globalShortcut.register(transKey, () => {
|
||||
handleKeyDown(key)
|
||||
})
|
||||
return status
|
||||
}
|
||||
|
||||
exports.unRegisterHotkey = key => {
|
||||
let transKey = transformedKey(key)
|
||||
// console.log('Unregister key:', transKey)
|
||||
globalShortcut.unregister(transKey)
|
||||
delete global.appHotKey.state[key]
|
||||
}
|
||||
|
||||
exports.unRegisterHotkeyAll = () => {
|
||||
global.appHotKey.state = {}
|
||||
globalShortcut.unregisterAll()
|
||||
}
|
||||
|
||||
exports.handleKeyDown = handleKeyDown
|
||||
exports.transformedKey = transformedKey
|
||||
|
||||
const handleRegisterHotkey = data => {
|
||||
let ret = exports.registerHotkey(data)
|
||||
if (!ret) log.info('Register hot key failed:', data.key)
|
||||
}
|
||||
|
||||
|
||||
exports.init = (isForce = false) => {
|
||||
exports.unRegisterHotkeyAll()
|
||||
if (!isForce && !global.appHotKey.config.global.enable) return
|
||||
global.appHotKey.state = {}
|
||||
// console.log(global.appHotKey.config.global.keys)
|
||||
for (const key of Object.keys(global.appHotKey.config.global.keys)) {
|
||||
try {
|
||||
handleRegisterHotkey({ key, info: global.appHotKey.config.global.keys[key] })
|
||||
} catch (err) {
|
||||
log.info(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
5
src/main/modules/index.js
Normal file
5
src/main/modules/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
require('./appMenu')
|
||||
require('./winLyric')
|
||||
require('./tray')
|
||||
require('./hotKey')
|
||||
require('./userApi')
|
||||
23
src/main/modules/sync/event/event.js
Normal file
23
src/main/modules/sync/event/event.js
Normal file
@@ -0,0 +1,23 @@
|
||||
const { EventEmitter } = require('events')
|
||||
const SYNC_EVENT_NAME = require('./name')
|
||||
|
||||
class Sync extends EventEmitter {
|
||||
status(status) {
|
||||
this.emit(SYNC_EVENT_NAME.status, status)
|
||||
}
|
||||
|
||||
sync_list(data) {
|
||||
this.emit(SYNC_EVENT_NAME.sync_list, data)
|
||||
}
|
||||
|
||||
sync_handle_list(data) {
|
||||
this.emit(SYNC_EVENT_NAME.sync_handle_list, data)
|
||||
}
|
||||
|
||||
action_list(data) {
|
||||
this.emit(SYNC_EVENT_NAME.sync_action_list, data)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Sync
|
||||
|
||||
6
src/main/modules/sync/event/name.js
Normal file
6
src/main/modules/sync/event/name.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
sync_action_list: 'sync_action_list',
|
||||
sync_list: 'sync_list',
|
||||
sync_handle_list: 'sync_handle_list',
|
||||
status: 'status',
|
||||
}
|
||||
15
src/main/modules/sync/index.js
Normal file
15
src/main/modules/sync/index.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const Event = require('./event/event')
|
||||
const eventNames = require('./event/name')
|
||||
const modules = require('./modules')
|
||||
const { startServer, stopServer, getStatus, generateCode } = require('./server/server')
|
||||
|
||||
|
||||
module.exports = {
|
||||
startServer,
|
||||
stopServer,
|
||||
getStatus,
|
||||
generateCode,
|
||||
Event,
|
||||
eventNames,
|
||||
modules,
|
||||
}
|
||||
1
src/main/modules/sync/modules/index.js
Normal file
1
src/main/modules/sync/modules/index.js
Normal file
@@ -0,0 +1 @@
|
||||
exports.list = require('./list')
|
||||
41
src/main/modules/sync/modules/list.js
Normal file
41
src/main/modules/sync/modules/list.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const { encryptMsg, decryptMsg } = require('../server/utils')
|
||||
let io
|
||||
|
||||
const handleListAction = ({ action, data }) => {
|
||||
// console.log(action, data)
|
||||
global.lx_event.sync.action_list({ action, data })
|
||||
}
|
||||
|
||||
// const addMusic = (orderId, callback) => {
|
||||
// // ...
|
||||
// }
|
||||
|
||||
const broadcast = async(action, data, excludeIds = []) => {
|
||||
if (!io) return
|
||||
const sockets = await io.fetchSockets()
|
||||
for (const socket of sockets) {
|
||||
if (excludeIds.includes(socket.data.keyInfo.clientId)) continue
|
||||
socket.emit(action, encryptMsg(socket.data.keyInfo, data))
|
||||
}
|
||||
}
|
||||
|
||||
exports.sendListAction = (action, data) => {
|
||||
// io.sockets
|
||||
return broadcast('list:action', JSON.stringify({ action, data }))
|
||||
}
|
||||
|
||||
exports.registerListHandler = (_io, socket) => {
|
||||
io = _io
|
||||
socket.on('list:action', msg => {
|
||||
// console.log(msg)
|
||||
msg = decryptMsg(socket.data.keyInfo, msg)
|
||||
if (!msg) return
|
||||
handleListAction(JSON.parse(msg))
|
||||
broadcast('list:action', msg, [socket.data.keyInfo.clientId])
|
||||
// socket.broadcast.emit('list:action', { action: 'list_remove', data: { id: 'default', index: 0 } })
|
||||
})
|
||||
// socket.on('list:add', addMusic)
|
||||
}
|
||||
exports.unregisterListHandler = () => {
|
||||
io = null
|
||||
}
|
||||
69
src/main/modules/sync/server/auth.js
Normal file
69
src/main/modules/sync/server/auth.js
Normal file
@@ -0,0 +1,69 @@
|
||||
const { aesEncrypt, aesDecrypt, createClientKeyInfo, getClientKeyInfo, setClientKeyInfo } = require('./utils')
|
||||
|
||||
const authMsg = 'lx-music auth::'
|
||||
const helloMsg = 'Hello~::^-^::'
|
||||
|
||||
exports.authCode = async(req, res, authCode) => {
|
||||
let code = 401
|
||||
let msg = 'Forbidden'
|
||||
// console.log(req.headers)
|
||||
if (req.headers.m) {
|
||||
label:
|
||||
if (req.headers.i) {
|
||||
const keyInfo = getClientKeyInfo(req.headers.i)
|
||||
if (!keyInfo) break label
|
||||
let text
|
||||
try {
|
||||
text = aesDecrypt(req.headers.m, keyInfo.key, keyInfo.iv)
|
||||
} catch (err) {
|
||||
break label
|
||||
}
|
||||
console.log(text)
|
||||
if (text.startsWith(authMsg)) {
|
||||
code = 200
|
||||
const deviceName = text.replace(authMsg, '') || 'Unknown'
|
||||
if (deviceName != keyInfo.deviceName) {
|
||||
keyInfo.deviceName = deviceName
|
||||
setClientKeyInfo(keyInfo)
|
||||
}
|
||||
msg = aesEncrypt(helloMsg, keyInfo.key, keyInfo.iv)
|
||||
}
|
||||
} else {
|
||||
let key = ''.padStart(16, Buffer.from(authCode).toString('hex'))
|
||||
const iv = Buffer.from(key.split('').reverse().join('')).toString('base64')
|
||||
key = Buffer.from(key).toString('base64')
|
||||
// console.log(authCode, key, iv)
|
||||
let text
|
||||
try {
|
||||
text = aesDecrypt(req.headers.m, key, iv)
|
||||
} catch (err) {
|
||||
break label
|
||||
}
|
||||
console.log(text)
|
||||
if (text.startsWith(authMsg)) {
|
||||
code = 200
|
||||
const deviceName = text.replace(authMsg, '') || 'Unknown'
|
||||
msg = aesEncrypt(JSON.stringify(createClientKeyInfo(deviceName)), key, iv)
|
||||
}
|
||||
}
|
||||
}
|
||||
res.writeHead(code)
|
||||
res.end(msg)
|
||||
}
|
||||
|
||||
exports.authConnect = async req => {
|
||||
const { i, t } = req._query
|
||||
label:
|
||||
if (i && t) {
|
||||
const keyInfo = getClientKeyInfo(i)
|
||||
if (!keyInfo) break label
|
||||
let text
|
||||
try {
|
||||
text = aesDecrypt(t, keyInfo.key, keyInfo.iv)
|
||||
} catch (err) {
|
||||
break label
|
||||
}
|
||||
if (text == 'lx-music connect') return
|
||||
}
|
||||
throw new Error('failed')
|
||||
}
|
||||
7
src/main/modules/sync/server/index.js
Normal file
7
src/main/modules/sync/server/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const { startServer, stopServer, getStatus } = require('./server')
|
||||
|
||||
module.exports = {
|
||||
startServer,
|
||||
stopServer,
|
||||
getStatus,
|
||||
}
|
||||
172
src/main/modules/sync/server/server.js
Normal file
172
src/main/modules/sync/server/server.js
Normal file
@@ -0,0 +1,172 @@
|
||||
const http = require('http')
|
||||
const sio = require('socket.io')
|
||||
const { createHttpTerminator } = require('http-terminator')
|
||||
const modules = require('../modules')
|
||||
const { authCode, authConnect } = require('./auth')
|
||||
const { getAddress, getServerId, generateCode, getClientKeyInfo } = require('./utils')
|
||||
const syncList = require('./syncList')
|
||||
|
||||
|
||||
let status = {
|
||||
status: false,
|
||||
message: '',
|
||||
address: [],
|
||||
code: '',
|
||||
devices: [],
|
||||
}
|
||||
|
||||
const handleConnection = (io, socket) => {
|
||||
console.log('connection')
|
||||
// console.log(socket.handshake.query)
|
||||
for (const module of Object.values(modules)) {
|
||||
module.registerListHandler(io, socket)
|
||||
}
|
||||
}
|
||||
|
||||
const authConnection = (req, callback) => {
|
||||
// console.log(req.headers)
|
||||
// // console.log(req.auth)
|
||||
// console.log(req._query.authCode)
|
||||
authConnect(req).then(() => {
|
||||
callback(null, true)
|
||||
}).catch(err => {
|
||||
callback(err, false)
|
||||
})
|
||||
}
|
||||
|
||||
let httpTerminator = null
|
||||
let io = null
|
||||
|
||||
const handleStartServer = (port = 9527) => new Promise((resolve, reject) => {
|
||||
const httpServer = http.createServer((req, res) => {
|
||||
// console.log(req.url)
|
||||
let code
|
||||
let msg
|
||||
switch (req.url) {
|
||||
case '/hello':
|
||||
code = 200
|
||||
msg = 'Hello~::^-^::'
|
||||
break
|
||||
case '/id':
|
||||
code = 200
|
||||
msg = 'OjppZDo6' + getServerId()
|
||||
break
|
||||
case '/ah':
|
||||
authCode(req, res, status.code)
|
||||
break
|
||||
default:
|
||||
code = 401
|
||||
msg = 'Forbidden'
|
||||
break
|
||||
}
|
||||
if (!code) return
|
||||
res.writeHead(code)
|
||||
res.end(msg)
|
||||
})
|
||||
httpTerminator = createHttpTerminator({
|
||||
server: httpServer,
|
||||
})
|
||||
io = sio(httpServer, {
|
||||
path: '/sync',
|
||||
serveClient: false,
|
||||
connectTimeout: 10000,
|
||||
pingTimeout: 30000,
|
||||
maxHttpBufferSize: 3e6,
|
||||
allowRequest: authConnection,
|
||||
transports: ['websocket'],
|
||||
})
|
||||
|
||||
io.on('connection', async socket => {
|
||||
socket.on('disconnect', reason => {
|
||||
console.log('disconnect', reason)
|
||||
status.devices.splice(status.devices.findIndex(k => k.clientId == keyInfo.clientId), 1)
|
||||
global.lx_event.sync.status(status)
|
||||
})
|
||||
const keyInfo = getClientKeyInfo(socket.handshake.query.i)
|
||||
// socket.lx_keyInfo = keyInfo
|
||||
socket.data.keyInfo = keyInfo
|
||||
try {
|
||||
await syncList(io, socket)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return
|
||||
}
|
||||
status.devices.push(keyInfo)
|
||||
handleConnection(io, socket, keyInfo)
|
||||
global.lx_event.sync.status(status)
|
||||
})
|
||||
|
||||
httpServer.on('error', error => {
|
||||
console.log(error)
|
||||
reject(error)
|
||||
})
|
||||
|
||||
httpServer.on('listening', () => {
|
||||
const addr = httpServer.address()
|
||||
const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port
|
||||
console.info(`Listening on ${bind}`)
|
||||
resolve()
|
||||
})
|
||||
|
||||
httpServer.listen(port)
|
||||
})
|
||||
|
||||
const handleStopServer = async() => {
|
||||
if (!httpTerminator) return
|
||||
await io.close()
|
||||
await httpTerminator.terminate().catch(() => {})
|
||||
io = null
|
||||
httpTerminator = null
|
||||
}
|
||||
|
||||
exports.stopServer = async() => {
|
||||
if (!status.status) {
|
||||
status.status = false
|
||||
status.message = ''
|
||||
status.address = []
|
||||
status.code = ''
|
||||
global.lx_event.sync.status(status)
|
||||
return
|
||||
}
|
||||
console.log('stoping sync server...')
|
||||
return handleStopServer().then(() => {
|
||||
console.log('sync server stoped')
|
||||
status.status = false
|
||||
status.message = ''
|
||||
status.address = []
|
||||
status.code = ''
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
status.message = err.message
|
||||
}).finally(() => {
|
||||
global.lx_event.sync.status(status)
|
||||
})
|
||||
}
|
||||
exports.startServer = async port => {
|
||||
if (status.status) await handleStopServer()
|
||||
|
||||
console.log('starting sync server...')
|
||||
return handleStartServer(port).then(() => {
|
||||
console.log('sync server started')
|
||||
status.status = true
|
||||
status.message = ''
|
||||
status.address = getAddress()
|
||||
status.code = generateCode()
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
status.status = false
|
||||
status.message = err.message
|
||||
status.address = []
|
||||
status.code = ''
|
||||
}).finally(() => {
|
||||
global.lx_event.sync.status(status)
|
||||
})
|
||||
}
|
||||
|
||||
exports.getStatus = () => status
|
||||
|
||||
exports.generateCode = async() => {
|
||||
status.code = generateCode()
|
||||
global.lx_event.sync.status(status)
|
||||
return status.code
|
||||
}
|
||||
394
src/main/modules/sync/server/syncList.js
Normal file
394
src/main/modules/sync/server/syncList.js
Normal file
@@ -0,0 +1,394 @@
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const fsPromises = fs.promises
|
||||
const { app } = require('electron')
|
||||
const { encryptMsg, decryptMsg } = require('./utils')
|
||||
const SYNC_EVENT_NAMES = require('../event/name')
|
||||
const { common: COMMON_EVENT_NAME } = require('@main/events/_name')
|
||||
const { throttle } = require('@common/utils')
|
||||
|
||||
let io
|
||||
let syncingId = null
|
||||
const wait = (time = 1000) => new Promise((resolve, reject) => setTimeout(resolve, time))
|
||||
|
||||
const getRemoteListData = socket => new Promise((resolve, reject) => {
|
||||
console.log('getRemoteListData')
|
||||
const handleError = reason => {
|
||||
reject(new Error(reason))
|
||||
}
|
||||
const handleSuccess = enData => {
|
||||
socket.removeListener('disconnect', handleError)
|
||||
socket.removeListener('list:sync', handleSuccess)
|
||||
console.log('getRemoteListData', 'handleSuccess')
|
||||
const data = JSON.parse(decryptMsg(socket.data.keyInfo, enData))
|
||||
if (!data) return reject(new Error('Get remote list data failed'))
|
||||
if (data.action != 'getData') return
|
||||
resolve(data.data)
|
||||
}
|
||||
|
||||
socket.on('disconnect', handleError)
|
||||
socket.on('list:sync', handleSuccess)
|
||||
socket.emit('list:sync', encryptMsg(socket.data.keyInfo, JSON.stringify({ action: 'getData', data: 'all' })))
|
||||
})
|
||||
|
||||
const getLocalListData = () => new Promise((resolve, reject) => {
|
||||
const handleSuccess = ({ action, data }) => {
|
||||
if (action !== 'getData') return
|
||||
global.lx_event.sync.off(SYNC_EVENT_NAMES.sync_handle_list, handleSuccess)
|
||||
resolve(data)
|
||||
}
|
||||
global.lx_event.sync.on(SYNC_EVENT_NAMES.sync_handle_list, handleSuccess)
|
||||
global.lx_event.sync.sync_list({
|
||||
action: 'getData',
|
||||
})
|
||||
})
|
||||
const getSyncMode = keyInfo => new Promise((resolve, reject) => {
|
||||
const handleSuccess = ({ action, data }) => {
|
||||
if (action !== 'selectMode') return
|
||||
global.lx_event.sync.off(SYNC_EVENT_NAMES.sync_handle_list, handleSuccess)
|
||||
resolve(data)
|
||||
}
|
||||
global.lx_event.sync.on(SYNC_EVENT_NAMES.sync_handle_list, handleSuccess)
|
||||
global.lx_event.sync.sync_list({
|
||||
action: 'selectMode',
|
||||
data: keyInfo,
|
||||
})
|
||||
})
|
||||
|
||||
const finishedSync = socket => {
|
||||
return socket.emit('list:sync', encryptMsg(socket.data.keyInfo, JSON.stringify({
|
||||
action: 'finished',
|
||||
})))
|
||||
}
|
||||
|
||||
const setLocalList = listData => {
|
||||
global.lx_event.sync.sync_list({
|
||||
action: 'setData',
|
||||
data: listData,
|
||||
})
|
||||
}
|
||||
const setRemotelList = async(socket, listData) => {
|
||||
if (!io) return
|
||||
const sockets = await io.fetchSockets()
|
||||
for (const socket of sockets) {
|
||||
// if (excludeIds.includes(socket.data.keyInfo.clientId)) continue
|
||||
socket.emit('list:sync', encryptMsg(socket.data.keyInfo, JSON.stringify({ action: 'setData', data: listData })))
|
||||
}
|
||||
}
|
||||
|
||||
let writeFilePromises = {}
|
||||
const updateSnapshot = (path, data) => {
|
||||
console.log('updateSnapshot', path)
|
||||
let writeFilePromise = writeFilePromises[path] || Promise.resolve()
|
||||
return writeFilePromise.then(() => {
|
||||
writeFilePromise = writeFilePromises[path] = fsPromises.writeFile(path, data)
|
||||
return writeFilePromise
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const createListDataObj = listData => {
|
||||
const listDataObj = {}
|
||||
for (const list of listData.userList) listDataObj[list.id] = list
|
||||
return listDataObj
|
||||
}
|
||||
|
||||
const handleMergeList = (sourceList, targetList, addMusicLocationType) => {
|
||||
let newList
|
||||
switch (addMusicLocationType) {
|
||||
case 'top':
|
||||
newList = [...targetList.list, ...sourceList.list]
|
||||
break
|
||||
case 'bottom':
|
||||
default:
|
||||
newList = [...sourceList.list, ...targetList.list]
|
||||
break
|
||||
}
|
||||
const map = {}
|
||||
const ids = []
|
||||
switch (addMusicLocationType) {
|
||||
case 'top':
|
||||
newList = [...targetList.list, ...sourceList.list]
|
||||
for (let i = newList.length - 1; i > -1; i--) {
|
||||
const item = newList[i]
|
||||
if (map[item.songmid]) continue
|
||||
ids.unshift(item.songmid)
|
||||
map[item.songmid] = item
|
||||
}
|
||||
break
|
||||
case 'bottom':
|
||||
default:
|
||||
newList = [...sourceList.list, ...targetList.list]
|
||||
for (const item of newList) {
|
||||
if (map[item.songmid]) continue
|
||||
ids.push(item.songmid)
|
||||
map[item.songmid] = item
|
||||
}
|
||||
break
|
||||
}
|
||||
return {
|
||||
...sourceList,
|
||||
list: ids.map(id => map[id]),
|
||||
}
|
||||
}
|
||||
const mergeList = (sourceListData, targetListData) => {
|
||||
const addMusicLocationType = global.appSetting.list.addMusicLocationType
|
||||
const newListData = {}
|
||||
newListData.defaultList = handleMergeList(sourceListData.defaultList, targetListData.defaultList, addMusicLocationType)
|
||||
newListData.loveList = handleMergeList(sourceListData.loveList, targetListData.loveList, addMusicLocationType)
|
||||
|
||||
const listDataObj = createListDataObj(sourceListData)
|
||||
newListData.userList = [...sourceListData.userList]
|
||||
|
||||
for (const list of targetListData.userList) {
|
||||
const targetList = listDataObj[list.id]
|
||||
if (targetList) {
|
||||
targetList.list = handleMergeList(targetList, list, addMusicLocationType).list
|
||||
} else {
|
||||
newListData.userList.push(list)
|
||||
}
|
||||
}
|
||||
|
||||
return newListData
|
||||
}
|
||||
const overwriteList = (sourceListData, targetListData) => {
|
||||
const newListData = {}
|
||||
newListData.defaultList = sourceListData.defaultList
|
||||
newListData.loveList = sourceListData.loveList
|
||||
|
||||
const listDataObj = createListDataObj(sourceListData)
|
||||
newListData.userList = [...sourceListData.userList]
|
||||
|
||||
for (const list of targetListData.userList) {
|
||||
const targetList = listDataObj[list.id]
|
||||
if (targetList) continue
|
||||
newListData.userList.push(list)
|
||||
}
|
||||
|
||||
return newListData
|
||||
}
|
||||
|
||||
const handleMergeListData = async socket => {
|
||||
let isSelectingMode = false
|
||||
const handleDisconnect = () => {
|
||||
if (!isSelectingMode) return
|
||||
global.lx_event.sync.sync_list({
|
||||
action: 'closeSelectMode',
|
||||
})
|
||||
}
|
||||
socket.on('disconnect', handleDisconnect)
|
||||
isSelectingMode = true
|
||||
const mode = await getSyncMode(socket.data.keyInfo)
|
||||
isSelectingMode = false
|
||||
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
|
||||
console.log('handleMergeListData', 'remoteListData, localListData')
|
||||
let listData
|
||||
switch (mode) {
|
||||
case 'merge_local_remote':
|
||||
listData = mergeList(localListData, remoteListData)
|
||||
break
|
||||
case 'merge_remote_local':
|
||||
listData = mergeList(remoteListData, localListData)
|
||||
break
|
||||
case 'overwrite_local_remote':
|
||||
listData = overwriteList(localListData, remoteListData)
|
||||
break
|
||||
case 'overwrite_remote_local':
|
||||
listData = overwriteList(remoteListData, localListData)
|
||||
break
|
||||
case 'overwrite_local_remote_full':
|
||||
listData = localListData
|
||||
break
|
||||
case 'overwrite_remote_local_full':
|
||||
listData = remoteListData
|
||||
break
|
||||
case 'none': return
|
||||
case 'cancel':
|
||||
socket.disconnect(true)
|
||||
throw new Error('cancel')
|
||||
}
|
||||
return listData
|
||||
}
|
||||
|
||||
const handleSyncList = async socket => {
|
||||
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
|
||||
console.log('handleSyncList', 'remoteListData, localListData')
|
||||
const listData = {}
|
||||
if (localListData.defaultList.list.length || localListData.loveList.list.length || localListData.userList.length) {
|
||||
if (remoteListData.defaultList.list.length || remoteListData.loveList.list.length || remoteListData.userList.length) {
|
||||
const mergedList = await handleMergeListData(socket)
|
||||
console.log('handleMergeListData', 'mergedList')
|
||||
console.log(mergedList)
|
||||
if (!mergedList) return
|
||||
listData.defaultList = mergedList.defaultList
|
||||
listData.loveList = mergedList.loveList
|
||||
listData.userList = mergedList.userList
|
||||
setLocalList(mergedList)
|
||||
setRemotelList(socket, mergedList)
|
||||
} else {
|
||||
setRemotelList(socket, localListData)
|
||||
listData.defaultList = localListData.defaultList
|
||||
listData.loveList = localListData.loveList
|
||||
listData.userList = localListData.userList
|
||||
}
|
||||
} else {
|
||||
if (remoteListData.defaultList.list.length || remoteListData.loveList.list.length || remoteListData.userList.length) {
|
||||
setLocalList(remoteListData)
|
||||
listData.defaultList = remoteListData.defaultList
|
||||
listData.loveList = remoteListData.loveList
|
||||
listData.userList = remoteListData.userList
|
||||
} else {
|
||||
listData.defaultList = localListData.defaultList
|
||||
listData.loveList = localListData.loveList
|
||||
listData.userList = localListData.userList
|
||||
}
|
||||
}
|
||||
return updateSnapshot(socket.data.snapshotFilePath, JSON.stringify({
|
||||
defaultList: listData.defaultList,
|
||||
loveList: listData.loveList,
|
||||
userList: listData.userList,
|
||||
})).then(() => {
|
||||
socket.data.isCreatedSnapshot = true
|
||||
return listData
|
||||
})
|
||||
}
|
||||
|
||||
const mergeListDataFromSnapshot = (sourceList, targetList, snapshotList, addMusicLocationType) => {
|
||||
const removedListIds = new Set()
|
||||
const sourceListItemIds = new Set()
|
||||
const targetListItemIds = new Set()
|
||||
for (const m of sourceList.list) sourceListItemIds.add(m.songmid)
|
||||
for (const m of targetList.list) targetListItemIds.add(m.songmid)
|
||||
for (const m of snapshotList.list) {
|
||||
if (!sourceListItemIds.has(m.songmid)) removedListIds.add(m.songmid)
|
||||
}
|
||||
for (const m of snapshotList.list) {
|
||||
if (!targetListItemIds.has(m.songmid)) removedListIds.add(m.songmid)
|
||||
}
|
||||
|
||||
let newList
|
||||
const map = {}
|
||||
const ids = []
|
||||
switch (addMusicLocationType) {
|
||||
case 'top':
|
||||
newList = [...targetList.list, ...sourceList.list]
|
||||
for (let i = newList.length - 1; i > -1; i--) {
|
||||
const item = newList[i]
|
||||
if (map[item.songmid] || removedListIds.has(item.songmid)) continue
|
||||
ids.unshift(item.songmid)
|
||||
map[item.songmid] = item
|
||||
}
|
||||
break
|
||||
case 'bottom':
|
||||
default:
|
||||
newList = [...sourceList.list, ...targetList.list]
|
||||
for (const item of newList) {
|
||||
if (map[item.songmid] || removedListIds.has(item.songmid)) continue
|
||||
ids.push(item.songmid)
|
||||
map[item.songmid] = item
|
||||
}
|
||||
break
|
||||
}
|
||||
return {
|
||||
...sourceList,
|
||||
list: ids.map(id => map[id]),
|
||||
}
|
||||
}
|
||||
const handleMergeListDataFromSnapshot = async(socket, snapshot) => {
|
||||
const addMusicLocationType = global.appSetting.list.addMusicLocationType
|
||||
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
|
||||
console.log('handleMergeListDataFromSnapshot', 'remoteListData, localListData')
|
||||
const newListData = {}
|
||||
newListData.defaultList = mergeListDataFromSnapshot(localListData.defaultList, remoteListData.defaultList, snapshot.defaultList, addMusicLocationType)
|
||||
newListData.loveList = mergeListDataFromSnapshot(localListData.loveList, remoteListData.loveList, snapshot.loveList, addMusicLocationType)
|
||||
const localUserListData = createListDataObj(localListData)
|
||||
const remoteUserListData = createListDataObj(remoteListData)
|
||||
const snapshotUserListData = createListDataObj(snapshot)
|
||||
const removedListIds = new Set()
|
||||
const localUserListIds = new Set()
|
||||
const remoteUserListIds = new Set()
|
||||
for (const l of localListData.userList) localUserListIds.add(l.id)
|
||||
for (const l of remoteListData.userList) remoteUserListIds.add(l.id)
|
||||
|
||||
for (const l of snapshot.userList) {
|
||||
if (!localUserListIds.has(l.id)) removedListIds.add(l.id)
|
||||
}
|
||||
for (const l of snapshot.userList) {
|
||||
if (!remoteUserListIds.has(l.id)) removedListIds.add(l.id)
|
||||
}
|
||||
|
||||
let newUserList = []
|
||||
for (const list of localListData.userList) {
|
||||
if (removedListIds.has(list.id)) continue
|
||||
const remoteList = remoteUserListData[list.id]
|
||||
let newList
|
||||
if (remoteList) {
|
||||
newList = mergeListDataFromSnapshot(list, remoteList, snapshotUserListData[list.id], addMusicLocationType)
|
||||
} else {
|
||||
newList = { ...list }
|
||||
}
|
||||
newUserList.push(newList)
|
||||
}
|
||||
for (const list of remoteListData.userList) {
|
||||
if (removedListIds.has(list.id) || localUserListData[list.id]) continue
|
||||
newUserList.push({ ...list })
|
||||
}
|
||||
newListData.userList = newUserList
|
||||
setLocalList(newListData)
|
||||
setRemotelList(socket, newListData)
|
||||
return updateSnapshot(socket.data.snapshotFilePath, JSON.stringify({
|
||||
defaultList: newListData.defaultList,
|
||||
loveList: newListData.loveList,
|
||||
userList: newListData.userList,
|
||||
})).then(() => {
|
||||
socket.data.isCreatedSnapshot = true
|
||||
return newListData
|
||||
})
|
||||
}
|
||||
|
||||
const registerUpdateSnapshotTask = (socket, snapshot) => {
|
||||
if (!socket.data.isCreatedSnapshot) return
|
||||
const handleUpdateSnapshot = throttle(({ defaultList, loveList, userList }) => {
|
||||
if (defaultList != null) snapshot.defaultList = defaultList
|
||||
if (loveList != null) snapshot.loveList = loveList
|
||||
if (userList != null) snapshot.userList = userList
|
||||
updateSnapshot(socket.data.snapshotFilePath, JSON.stringify(snapshot))
|
||||
}, 10000)
|
||||
global.lx_event.common.on(COMMON_EVENT_NAME.saveMyList, handleUpdateSnapshot)
|
||||
socket.on('disconnect', () => {
|
||||
global.lx_event.common.off(COMMON_EVENT_NAME.saveMyList, handleUpdateSnapshot)
|
||||
})
|
||||
}
|
||||
|
||||
const syncList = async socket => {
|
||||
socket.data.snapshotFilePath = path.join(app.getPath('userData'), `snapshot-${Buffer.from(socket.data.keyInfo.clientId).toString('hex').substring(0, 10)}.json`)
|
||||
let fileData
|
||||
let isSyncRequired = false
|
||||
try {
|
||||
fileData = await fsPromises.readFile(socket.data.snapshotFilePath)
|
||||
fileData = JSON.parse(fileData)
|
||||
} catch (err) {
|
||||
if (err.code !== 'ENOENT') throw err
|
||||
isSyncRequired = true
|
||||
}
|
||||
console.log('isSyncRequired', isSyncRequired)
|
||||
if (isSyncRequired) return handleSyncList(socket)
|
||||
return handleMergeListDataFromSnapshot(socket, fileData)
|
||||
}
|
||||
|
||||
const checkSyncQueue = async() => {
|
||||
if (!syncingId) return
|
||||
await wait()
|
||||
return checkSyncQueue()
|
||||
}
|
||||
module.exports = async(_io, socket) => {
|
||||
io = _io
|
||||
await checkSyncQueue()
|
||||
syncingId = socket.data.keyInfo.clientId
|
||||
return syncList(socket).then(newListData => {
|
||||
registerUpdateSnapshotTask(socket, { ...newListData })
|
||||
return finishedSync(socket)
|
||||
}).finally(() => {
|
||||
syncingId = null
|
||||
})
|
||||
}
|
||||
93
src/main/modules/sync/server/utils.js
Normal file
93
src/main/modules/sync/server/utils.js
Normal file
@@ -0,0 +1,93 @@
|
||||
const { networkInterfaces } = require('os')
|
||||
const { randomBytes, createCipheriv, createDecipheriv } = require('crypto')
|
||||
const getStore = require('@common/store')
|
||||
const STORE_NAME = 'sync'
|
||||
|
||||
exports.getAddress = () => {
|
||||
const nets = networkInterfaces()
|
||||
const results = []
|
||||
// console.log(nets)
|
||||
|
||||
for (const name of Object.keys(nets)) {
|
||||
for (const net of nets[name]) {
|
||||
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
|
||||
if (net.family === 'IPv4' && !net.internal) {
|
||||
results.push(net.address)
|
||||
}
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
let serverId
|
||||
exports.getServerId = () => {
|
||||
if (serverId) return serverId
|
||||
const store = getStore(STORE_NAME)
|
||||
serverId = store.get('serverId')
|
||||
if (!serverId) {
|
||||
serverId = randomBytes(4 * 4).toString('base64')
|
||||
store.set('serverId', serverId)
|
||||
}
|
||||
return serverId
|
||||
}
|
||||
|
||||
let keyInfos
|
||||
exports.createClientKeyInfo = deviceName => {
|
||||
const keyInfo = {
|
||||
clientId: randomBytes(4 * 4).toString('base64'),
|
||||
key: randomBytes(16).toString('base64'),
|
||||
iv: randomBytes(16).toString('base64'),
|
||||
deviceName,
|
||||
}
|
||||
const store = getStore(STORE_NAME)
|
||||
if (!keyInfos) keyInfos = store.get('keys') || {}
|
||||
if (Object.keys(keyInfos).length > 101) throw new Error('max keys')
|
||||
|
||||
keyInfos[keyInfo.clientId] = keyInfo
|
||||
store.set('keys', keyInfos)
|
||||
return keyInfo
|
||||
}
|
||||
exports.setClientKeyInfo = keyInfo => {
|
||||
keyInfos[keyInfo.clientId] = keyInfo
|
||||
const store = getStore(STORE_NAME)
|
||||
store.set('keys', keyInfos)
|
||||
}
|
||||
exports.getClientKeyInfo = clientId => {
|
||||
if (!keyInfos) {
|
||||
const store = getStore(STORE_NAME)
|
||||
keyInfos = store.get('keys') || {}
|
||||
}
|
||||
return keyInfos[clientId] || null
|
||||
}
|
||||
|
||||
exports.generateCode = () => {
|
||||
return Math.random().toString().substring(2, 8)
|
||||
}
|
||||
|
||||
exports.aesEncrypt = (buffer, key, iv) => {
|
||||
const cipher = createCipheriv('aes-128-cbc', Buffer.from(key, 'base64'), Buffer.from(iv, 'base64'))
|
||||
return Buffer.concat([cipher.update(buffer), cipher.final()]).toString('base64')
|
||||
}
|
||||
|
||||
exports.aesDecrypt = (text, key, iv) => {
|
||||
const decipher = createDecipheriv('aes-128-cbc', Buffer.from(key, 'base64'), Buffer.from(iv, 'base64'))
|
||||
return Buffer.concat([decipher.update(Buffer.from(text, 'base64')), decipher.final()]).toString()
|
||||
}
|
||||
|
||||
exports.encryptMsg = (keyInfo, msg) => {
|
||||
return msg
|
||||
// if (!keyInfo) return ''
|
||||
// return exports.aesEncrypt(msg, keyInfo.key, keyInfo.iv)
|
||||
}
|
||||
|
||||
exports.decryptMsg = (keyInfo, enMsg) => {
|
||||
return enMsg
|
||||
// if (!keyInfo) return ''
|
||||
// let msg = ''
|
||||
// try {
|
||||
// msg = exports.aesDecrypt(enMsg, keyInfo.key, keyInfo.iv)
|
||||
// } catch (err) {
|
||||
// console.log(err)
|
||||
// }
|
||||
// return msg
|
||||
}
|
||||
146
src/main/modules/tray.js
Normal file
146
src/main/modules/tray.js
Normal file
@@ -0,0 +1,146 @@
|
||||
const { app, Tray, Menu } = require('electron')
|
||||
const { isWin } = require('../../common/utils')
|
||||
const { tray: TRAY_EVENT_NAME, common: COMMON_EVENT_NAME, mainWindow: MAIN_WINDOW_NAME } = require('../events/_name')
|
||||
const path = require('path')
|
||||
let isEnableTray = null
|
||||
let themeId = null
|
||||
const themeList = [
|
||||
{
|
||||
id: 0,
|
||||
fileName: 'tray0Template',
|
||||
isNative: true,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
fileName: 'tray1Template',
|
||||
isNative: false,
|
||||
},
|
||||
]
|
||||
global.lx_event.common.on(COMMON_EVENT_NAME.configStatus, sourceName => {
|
||||
if (sourceName === TRAY_EVENT_NAME.name) return
|
||||
if (themeId !== global.appSetting.tray.themeId) {
|
||||
themeId = global.appSetting.tray.themeId
|
||||
setTrayImage(themeId)
|
||||
}
|
||||
if (isEnableTray !== global.appSetting.tray.isShow) {
|
||||
isEnableTray = global.appSetting.tray.isShow
|
||||
global.appSetting.tray.isShow ? createTray() : destroyTray()
|
||||
}
|
||||
createMenu(global.modules.tray)
|
||||
})
|
||||
|
||||
global.lx_event.mainWindow.on(MAIN_WINDOW_NAME.ready_to_show, () => {
|
||||
createMenu(global.modules.tray)
|
||||
})
|
||||
global.lx_event.mainWindow.on(MAIN_WINDOW_NAME.show, () => {
|
||||
createMenu(global.modules.tray)
|
||||
})
|
||||
global.lx_event.mainWindow.on(MAIN_WINDOW_NAME.hide, () => {
|
||||
createMenu(global.modules.tray)
|
||||
})
|
||||
|
||||
const createTray = () => {
|
||||
if ((global.modules.tray && !global.modules.tray.isDestroyed()) || !global.appSetting.tray || !global.appSetting.tray.isShow) return
|
||||
|
||||
themeId = global.appSetting.tray.themeId
|
||||
let themeName = (themeList.find(item => item.id === themeId) || themeList[0]).fileName
|
||||
const iconPath = path.join(global.__static, 'images/tray', isWin ? themeName + '@2x.ico' : themeName + '.png')
|
||||
|
||||
// 托盘
|
||||
global.modules.tray = new Tray(iconPath)
|
||||
|
||||
global.modules.tray.setToolTip('洛雪音乐助手')
|
||||
createMenu(global.modules.tray)
|
||||
global.modules.tray.setIgnoreDoubleClickEvents(true)
|
||||
global.modules.tray.on('click', () => {
|
||||
const mainWindow = global.modules.mainWindow
|
||||
if (!mainWindow) return
|
||||
mainWindow.isVisible()
|
||||
? mainWindow.focus()
|
||||
: mainWindow.show()
|
||||
})
|
||||
}
|
||||
|
||||
const destroyTray = () => {
|
||||
if (!global.modules.tray) return
|
||||
global.modules.tray.destroy()
|
||||
global.modules.tray = null
|
||||
}
|
||||
|
||||
const createMenu = tray => {
|
||||
if (!global.modules.tray) return
|
||||
let menu = []
|
||||
global.modules.mainWindow && menu.push(global.modules.mainWindow.isVisible()
|
||||
? {
|
||||
label: '隐藏主界面',
|
||||
click() {
|
||||
global.modules.mainWindow.hide()
|
||||
},
|
||||
}
|
||||
: {
|
||||
label: '显示主界面',
|
||||
click() {
|
||||
if (!global.modules.mainWindow) return
|
||||
if (!global.modules.mainWindow.isVisible()) {
|
||||
global.modules.mainWindow.show()
|
||||
}
|
||||
global.modules.mainWindow.restore()
|
||||
global.modules.mainWindow.focus()
|
||||
},
|
||||
})
|
||||
menu.push(global.appSetting.desktopLyric.enable
|
||||
? {
|
||||
label: '关闭桌面歌词',
|
||||
click() {
|
||||
global.lx_core.setAppConfig({ desktopLyric: { enable: false } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
}
|
||||
: {
|
||||
label: '开启桌面歌词',
|
||||
click() {
|
||||
global.lx_core.setAppConfig({ desktopLyric: { enable: true } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
})
|
||||
menu.push(global.appSetting.desktopLyric.isLock
|
||||
? {
|
||||
label: '解锁桌面歌词',
|
||||
click() {
|
||||
global.lx_core.setAppConfig({ desktopLyric: { isLock: false } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
}
|
||||
: {
|
||||
label: '锁定桌面歌词',
|
||||
click() {
|
||||
global.lx_core.setAppConfig({ desktopLyric: { isLock: true } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
})
|
||||
menu.push(global.appSetting.desktopLyric.isAlwaysOnTop
|
||||
? {
|
||||
label: '取消置顶',
|
||||
click() {
|
||||
global.lx_core.setAppConfig({ desktopLyric: { isAlwaysOnTop: false } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
}
|
||||
: {
|
||||
label: '置顶歌词',
|
||||
click() {
|
||||
global.lx_core.setAppConfig({ desktopLyric: { isAlwaysOnTop: true } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
})
|
||||
menu.push({
|
||||
label: '退出',
|
||||
click() {
|
||||
global.isQuitting = true
|
||||
app.quit()
|
||||
},
|
||||
})
|
||||
const contextMenu = Menu.buildFromTemplate(menu)
|
||||
tray.setContextMenu(contextMenu)
|
||||
}
|
||||
|
||||
const setTrayImage = themeId => {
|
||||
if (!global.modules.tray) return
|
||||
let themeName = (themeList.find(item => item.id === themeId) || themeList[0]).fileName
|
||||
const iconPath = path.join(global.__static, 'images/tray', isWin ? themeName + '@2x.ico' : themeName + '.png')
|
||||
global.modules.tray.setImage(iconPath)
|
||||
}
|
||||
2
src/main/modules/userApi/config/index.js
Normal file
2
src/main/modules/userApi/config/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
exports.userApis = []
|
||||
11
src/main/modules/userApi/event/event.js
Normal file
11
src/main/modules/userApi/event/event.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const { EventEmitter } = require('events')
|
||||
const USER_API_EVENT_NAME = require('./name')
|
||||
|
||||
class UserApi extends EventEmitter {
|
||||
status(info) {
|
||||
this.emit(USER_API_EVENT_NAME.status, info)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserApi
|
||||
|
||||
3
src/main/modules/userApi/event/name.js
Normal file
3
src/main/modules/userApi/event/name.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
status: 'status',
|
||||
}
|
||||
44
src/main/modules/userApi/index.js
Normal file
44
src/main/modules/userApi/index.js
Normal file
@@ -0,0 +1,44 @@
|
||||
const Event = require('./event/event')
|
||||
const eventNames = require('./event/name')
|
||||
const { closeWindow } = require('./main')
|
||||
const { getUserApis, importApi, removeApi } = require('./utils')
|
||||
const { request, cancelRequest, getStatus, loadApi } = require('./rendererEvent/rendererEvent')
|
||||
|
||||
// const { getApiList, importApi, removeApi, setApi, getStatus, request, eventNames }
|
||||
let userApiId
|
||||
|
||||
exports.Event = Event
|
||||
exports.eventNames = eventNames
|
||||
|
||||
exports.getApiList = getUserApis
|
||||
exports.importApi = script => {
|
||||
return {
|
||||
apiInfo: importApi(script),
|
||||
apiList: getUserApis(),
|
||||
}
|
||||
}
|
||||
exports.request = request
|
||||
exports.cancelRequest = cancelRequest
|
||||
exports.getStatus = getStatus
|
||||
|
||||
exports.removeApi = async ids => {
|
||||
if (userApiId && ids.includes(userApiId)) {
|
||||
userApiId = null
|
||||
await closeWindow()
|
||||
}
|
||||
removeApi(ids)
|
||||
return getUserApis()
|
||||
}
|
||||
|
||||
exports.setApi = async id => {
|
||||
if (userApiId) {
|
||||
userApiId = null
|
||||
await closeWindow()
|
||||
}
|
||||
const apiList = getUserApis()
|
||||
if (!apiList.some(a => a.id === id)) return
|
||||
userApiId = id
|
||||
await loadApi(id)
|
||||
}
|
||||
|
||||
|
||||
89
src/main/modules/userApi/main.js
Normal file
89
src/main/modules/userApi/main.js
Normal file
@@ -0,0 +1,89 @@
|
||||
const { BrowserWindow } = require('electron')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const dir = global.isDev ? __userApi : path.join(__dirname, 'userApi')
|
||||
|
||||
const wait = time => new Promise(resolve => setTimeout(() => resolve(), time))
|
||||
|
||||
let html = ''
|
||||
|
||||
fs.readFile(path.join(dir, 'renderer/user-api.html'), 'utf8', (err, data) => {
|
||||
if (err) throw new Error('api html read failed, info: ' + err.message)
|
||||
html = data
|
||||
})
|
||||
|
||||
const denyEvents = [
|
||||
'new-window',
|
||||
'will-navigate',
|
||||
'will-redirect',
|
||||
'will-attach-webview',
|
||||
'will-prevent-unload',
|
||||
'media-started-playing',
|
||||
]
|
||||
|
||||
const winEvent = win => {
|
||||
win.on('closed', () => {
|
||||
win = global.modules.userApiWindow = null
|
||||
})
|
||||
}
|
||||
|
||||
exports.createWindow = async userApi => {
|
||||
if (global.modules.userApiWindow) return
|
||||
while (true) {
|
||||
if (html) break
|
||||
await wait(100)
|
||||
}
|
||||
/**
|
||||
* Initial window options
|
||||
*/
|
||||
global.modules.userApiWindow = new BrowserWindow({
|
||||
enableRemoteModule: false,
|
||||
resizable: false,
|
||||
minimizable: false,
|
||||
maximizable: false,
|
||||
fullscreenable: false,
|
||||
show: false,
|
||||
webPreferences: {
|
||||
contextIsolation: true,
|
||||
worldSafeExecuteJavaScript: true,
|
||||
nodeIntegration: false,
|
||||
preload: path.join(dir, 'renderer/preload.js'),
|
||||
},
|
||||
})
|
||||
|
||||
for (const eventName of denyEvents) {
|
||||
global.modules.userApiWindow.webContents.on(eventName, event => {
|
||||
event.preventDefault()
|
||||
})
|
||||
}
|
||||
global.modules.userApiWindow.webContents.session.setPermissionRequestHandler((webContents, permission, callback) => {
|
||||
// eslint-disable-next-line node/no-callback-literal
|
||||
callback(false)
|
||||
})
|
||||
|
||||
winEvent(global.modules.userApiWindow)
|
||||
|
||||
// console.log(html.replace('</body>', `<script>${userApi.script}</script></body>`))
|
||||
const randomNum = Math.random().toString().substring(2, 10)
|
||||
global.modules.userApiWindow.loadURL(
|
||||
'data:text/html;charset=UTF-8,' + encodeURIComponent(html
|
||||
.replace('<meta http-equiv="Content-Security-Policy" content="default-src \'none\'">',
|
||||
`<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'nonce-${randomNum}';">`)
|
||||
.replace('</body>', `<script nonce="${randomNum}">${userApi.script}</script></body>`)))
|
||||
|
||||
// global.modules.userApiWindow.loadFile(path.join(dir, 'renderer/user-api.html'))
|
||||
// global.modules.userApiWindow.webContents.openDevTools()
|
||||
}
|
||||
|
||||
exports.closeWindow = async() => {
|
||||
if (!global.modules.userApiWindow) return
|
||||
await Promise.all([
|
||||
global.modules.userApiWindow.webContents.session.clearAuthCache(),
|
||||
global.modules.userApiWindow.webContents.session.clearStorageData(),
|
||||
global.modules.userApiWindow.webContents.session.clearCache(),
|
||||
])
|
||||
global.modules.userApiWindow.destroy()
|
||||
global.modules.userApiWindow = null
|
||||
}
|
||||
227
src/main/modules/userApi/renderer/preload.js
Normal file
227
src/main/modules/userApi/renderer/preload.js
Normal file
@@ -0,0 +1,227 @@
|
||||
const { contextBridge, ipcRenderer } = require('electron')
|
||||
const needle = require('needle')
|
||||
const { createCipheriv, publicEncrypt, constants, randomBytes, createHash } = require('crypto')
|
||||
const USER_API_RENDERER_EVENT_NAME = require('../rendererEvent/name')
|
||||
|
||||
const sendMessage = (action, status, data, message) => {
|
||||
ipcRenderer.send(action, { status, data, message })
|
||||
}
|
||||
|
||||
let isInitedApi = false
|
||||
const EVENT_NAMES = {
|
||||
request: 'request',
|
||||
inited: 'inited',
|
||||
}
|
||||
const eventNames = Object.values(EVENT_NAMES)
|
||||
const events = {
|
||||
request: null,
|
||||
}
|
||||
const allSources = ['kw', 'kg', 'tx', 'wy', 'mg']
|
||||
const supportQualitys = {
|
||||
kw: ['128k', '320k', 'flac'],
|
||||
kg: ['128k', '320k', 'flac'],
|
||||
tx: ['128k', '320k', 'flac'],
|
||||
wy: ['128k', '320k', 'flac'],
|
||||
mg: ['128k', '320k', 'flac'],
|
||||
}
|
||||
const supportActions = {
|
||||
kw: ['musicUrl'],
|
||||
kg: ['musicUrl'],
|
||||
tx: ['musicUrl'],
|
||||
wy: ['musicUrl'],
|
||||
mg: ['musicUrl'],
|
||||
xm: ['musicUrl'],
|
||||
}
|
||||
|
||||
const handleRequest = (context, { requestKey, data }) => {
|
||||
// console.log(data)
|
||||
if (!events.request) return sendMessage(USER_API_RENDERER_EVENT_NAME.response, false, { requestKey }, 'Request event is not defined')
|
||||
try {
|
||||
events.request.call(context, { source: data.source, action: data.action, info: data.info }).then(response => {
|
||||
let sendData = {
|
||||
requestKey,
|
||||
}
|
||||
switch (data.action) {
|
||||
case 'musicUrl':
|
||||
sendData.result = {
|
||||
source: data.source,
|
||||
action: data.action,
|
||||
data: {
|
||||
type: data.info.type,
|
||||
url: response,
|
||||
},
|
||||
}
|
||||
break
|
||||
}
|
||||
sendMessage(USER_API_RENDERER_EVENT_NAME.response, true, sendData)
|
||||
}).catch(err => {
|
||||
sendMessage(USER_API_RENDERER_EVENT_NAME.response, false, { requestKey }, err.message)
|
||||
})
|
||||
} catch (err) {
|
||||
sendMessage(USER_API_RENDERER_EVENT_NAME.response, false, { requestKey }, err.message)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} context
|
||||
* @param {*} info {
|
||||
* status: true,
|
||||
* message: 'xxx',
|
||||
* sources: {
|
||||
* kw: ['128k', '320k', 'flac'],
|
||||
* kg: ['128k', '320k', 'flac'],
|
||||
* tx: ['128k', '320k', 'flac'],
|
||||
* wy: ['128k', '320k', 'flac'],
|
||||
* mg: ['128k', '320k', 'flac'],
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
const handleInit = (context, info) => {
|
||||
if (!info) {
|
||||
sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, 'Init failed')
|
||||
// sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, typeof info.message === 'string' ? info.message.substring(0, 100) : '')
|
||||
return
|
||||
}
|
||||
if (info.openDevTools === true) {
|
||||
sendMessage(USER_API_RENDERER_EVENT_NAME.openDevTools)
|
||||
}
|
||||
if (!info.status) {
|
||||
sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, 'Init failed')
|
||||
// sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, typeof info.message === 'string' ? info.message.substring(0, 100) : '')
|
||||
return
|
||||
}
|
||||
const sourceInfo = {
|
||||
sources: {},
|
||||
}
|
||||
try {
|
||||
for (const source of allSources) {
|
||||
const userSource = info.sources[source]
|
||||
if (!userSource || userSource.type !== 'music') continue
|
||||
const qualitys = supportQualitys[source]
|
||||
const actions = supportActions[source]
|
||||
sourceInfo.sources[source] = {
|
||||
type: 'music',
|
||||
actions: actions.filter(a => userSource.actions.includes(a)),
|
||||
qualitys: qualitys.filter(q => userSource.qualitys.includes(q)),
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, error.message)
|
||||
return
|
||||
}
|
||||
sendMessage(USER_API_RENDERER_EVENT_NAME.init, true, sourceInfo)
|
||||
|
||||
ipcRenderer.on(USER_API_RENDERER_EVENT_NAME.request, (event, data) => {
|
||||
handleRequest(context, data)
|
||||
})
|
||||
}
|
||||
|
||||
contextBridge.exposeInMainWorld('lx', {
|
||||
EVENT_NAMES,
|
||||
request(url, { method = 'get', timeout, headers, body, form, formData }, callback) {
|
||||
let options = { headers }
|
||||
let data
|
||||
if (body) {
|
||||
data = body
|
||||
} else if (form) {
|
||||
data = form
|
||||
// data.content_type = 'application/x-www-form-urlencoded'
|
||||
options.json = false
|
||||
} else if (formData) {
|
||||
data = formData
|
||||
// data.content_type = 'multipart/form-data'
|
||||
options.json = false
|
||||
}
|
||||
options.response_timeout = timeout
|
||||
|
||||
let request = needle.request(method, url, data, options, (err, resp, body) => {
|
||||
if (!err) {
|
||||
body = resp.body = resp.raw.toString()
|
||||
try {
|
||||
resp.body = JSON.parse(resp.body)
|
||||
} catch (_) {}
|
||||
body = resp.body
|
||||
}
|
||||
callback(err, {
|
||||
statusCode: resp.statusCode,
|
||||
statusMessage: resp.statusMessage,
|
||||
headers: resp.headers,
|
||||
bytes: resp.bytes,
|
||||
raw: resp.raw,
|
||||
body: body,
|
||||
}, body)
|
||||
}).request
|
||||
|
||||
return () => {
|
||||
if (!request.aborted) request.abort()
|
||||
request = null
|
||||
}
|
||||
},
|
||||
send(eventName, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!eventNames.includes(eventName)) return reject(new Error('The event is not supported: ' + eventName))
|
||||
switch (eventName) {
|
||||
case EVENT_NAMES.inited:
|
||||
if (isInitedApi) return
|
||||
isInitedApi = true
|
||||
handleInit(this, data)
|
||||
break
|
||||
default:
|
||||
resolve(new Error('Unknown event name: ' + eventName))
|
||||
}
|
||||
})
|
||||
},
|
||||
on(eventName, handler) {
|
||||
if (!eventNames.includes(eventName)) return Promise.reject(new Error('The event is not supported: ' + eventName))
|
||||
switch (eventName) {
|
||||
case EVENT_NAMES.request:
|
||||
events.request = handler
|
||||
break
|
||||
}
|
||||
},
|
||||
utils: {
|
||||
crypto: {
|
||||
aesEncrypt(buffer, mode, key, iv) {
|
||||
const cipher = createCipheriv('aes-128-' + mode, key, iv)
|
||||
return Buffer.concat([cipher.update(buffer), cipher.final()])
|
||||
},
|
||||
rsaEncrypt(buffer, key) {
|
||||
buffer = Buffer.concat([Buffer.alloc(128 - buffer.length), buffer])
|
||||
return publicEncrypt({ key: key, padding: constants.RSA_NO_PADDING }, buffer)
|
||||
},
|
||||
randomBytes(size) {
|
||||
return randomBytes(size)
|
||||
},
|
||||
md5(str) {
|
||||
return createHash('md5').update(str).digest('hex')
|
||||
},
|
||||
},
|
||||
buffer: {
|
||||
from(...args) {
|
||||
return Buffer.from(...args)
|
||||
},
|
||||
},
|
||||
},
|
||||
// removeEvent(eventName, handler) {
|
||||
// if (!eventNames.includes(eventName)) return Promise.reject(new Error('The event is not supported: ' + eventName))
|
||||
// let handlers
|
||||
// switch (eventName) {
|
||||
// case EVENT_NAMES.request:
|
||||
// handlers = events.request
|
||||
// break
|
||||
// }
|
||||
// for (let index = 0; index < handlers.length; index++) {
|
||||
// if (handlers[index] === handler) {
|
||||
// handlers.splice(index, 1)
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// removeAllEvents() {
|
||||
// for (const handlers of Object.values(events)) {
|
||||
// handlers.splice(0, handlers.length)
|
||||
// }
|
||||
// },
|
||||
})
|
||||
12
src/main/modules/userApi/renderer/user-api.html
Normal file
12
src/main/modules/userApi/renderer/user-api.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>User api</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
12
src/main/modules/userApi/rendererEvent/name.js
Normal file
12
src/main/modules/userApi/rendererEvent/name.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const names = {
|
||||
init: '',
|
||||
request: '',
|
||||
response: '',
|
||||
openDevTools: '',
|
||||
}
|
||||
|
||||
|
||||
for (const key of Object.keys(names)) {
|
||||
names[key] = `userApi_${key}`
|
||||
}
|
||||
module.exports = names
|
||||
82
src/main/modules/userApi/rendererEvent/rendererEvent.js
Normal file
82
src/main/modules/userApi/rendererEvent/rendererEvent.js
Normal file
@@ -0,0 +1,82 @@
|
||||
const { mainOn, mainSend } = require('@common/ipc')
|
||||
|
||||
const USER_API_RENDERER_EVENT_NAME = require('../rendererEvent/name')
|
||||
const { createWindow } = require('../main')
|
||||
const { getUserApis } = require('../utils')
|
||||
|
||||
let userApi
|
||||
let status = { status: true }
|
||||
const requestQueue = new Map()
|
||||
const timeouts = {}
|
||||
|
||||
const handleInit = (event, { status, message, data: apiInfo }) => {
|
||||
// console.log('inited')
|
||||
if (!status) {
|
||||
console.log('init failed:', message)
|
||||
global.lx_event.userApi.status(status = { status: false, apiInfo: userApi, message })
|
||||
return
|
||||
}
|
||||
global.lx_event.userApi.status(status = { status: true, apiInfo: { ...userApi, sources: apiInfo.sources } })
|
||||
}
|
||||
const handleResponse = (event, { status, data: { requestKey, result }, message }) => {
|
||||
const request = requestQueue.get(requestKey)
|
||||
if (!request) return
|
||||
requestQueue.delete(requestKey)
|
||||
clearTimeout(timeouts[requestKey])
|
||||
delete timeouts[requestKey]
|
||||
if (status) {
|
||||
request[0](result)
|
||||
} else {
|
||||
request[1](new Error(message))
|
||||
}
|
||||
}
|
||||
const handleOpenDevTools = () => {
|
||||
if (global.modules.userApiWindow) {
|
||||
global.modules.userApiWindow.webContents.openDevTools()
|
||||
}
|
||||
}
|
||||
mainOn(USER_API_RENDERER_EVENT_NAME.init, handleInit)
|
||||
mainOn(USER_API_RENDERER_EVENT_NAME.response, handleResponse)
|
||||
mainOn(USER_API_RENDERER_EVENT_NAME.openDevTools, handleOpenDevTools)
|
||||
|
||||
exports.loadApi = async apiId => {
|
||||
if (!apiId) return global.lx_event.userApi.status(status = { status: false, message: 'api id is null' })
|
||||
userApi = getUserApis().find(api => api.id == apiId)
|
||||
console.log('load api', userApi.name)
|
||||
await createWindow(userApi)
|
||||
// if (!userApi) return global.lx_event.userApi.status(status = { status: false, message: 'api script is not found' })
|
||||
// if (!global.modules.userApiWindow) {
|
||||
// global.lx_event.userApi.status(status = { status: false, message: 'user api runtime is not defined' })
|
||||
// throw new Error('user api window is not defined')
|
||||
// }
|
||||
|
||||
// // const path = require('path')
|
||||
// // // eslint-disable-next-line no-undef
|
||||
// // userApi.script = require('fs').readFileSync(path.join(global.isDev ? __userApi : __dirname, 'renderer/test-api.js')).toString()
|
||||
// console.log('load api', userApi.name)
|
||||
// mainSend(global.modules.userApiWindow, USER_API_RENDERER_EVENT_NAME.init, { userApi })
|
||||
}
|
||||
|
||||
exports.cancelRequest = requestKey => {
|
||||
if (!requestQueue.has(requestKey)) return
|
||||
const request = requestQueue.get(requestKey)
|
||||
request[1](new Error('Cancel request'))
|
||||
requestQueue.delete(requestKey)
|
||||
clearTimeout(timeouts[requestKey])
|
||||
delete timeouts[requestKey]
|
||||
}
|
||||
|
||||
exports.request = ({ requestKey, data }) => new Promise((resolve, reject) => {
|
||||
if (!userApi) return reject(new Error('user api is not load'))
|
||||
|
||||
// const requestKey = `request__${Math.random().toString().substring(2)}`
|
||||
|
||||
timeouts[requestKey] = setTimeout(() => {
|
||||
exports.cancelRequest(requestKey)
|
||||
}, 20000)
|
||||
|
||||
requestQueue.set(requestKey, [resolve, reject, data])
|
||||
mainSend(global.modules.userApiWindow, USER_API_RENDERER_EVENT_NAME.request, { requestKey, data })
|
||||
})
|
||||
|
||||
exports.getStatus = () => status
|
||||
44
src/main/modules/userApi/utils/index.js
Normal file
44
src/main/modules/userApi/utils/index.js
Normal file
@@ -0,0 +1,44 @@
|
||||
const { userApis: defaultUserApis } = require('../config')
|
||||
const getStore = require('@common/store')
|
||||
|
||||
let userApis
|
||||
|
||||
exports.getUserApis = () => {
|
||||
const electronStore_userApi = getStore('userApi')
|
||||
if (userApis) return userApis
|
||||
userApis = electronStore_userApi.get('userApis')
|
||||
if (!userApis) {
|
||||
userApis = defaultUserApis
|
||||
electronStore_userApi.set('userApis', userApis)
|
||||
}
|
||||
return userApis
|
||||
}
|
||||
|
||||
exports.importApi = script => {
|
||||
let scriptInfo = script.split(/\r?\n/)
|
||||
let name = scriptInfo[1] || ''
|
||||
let description = scriptInfo[2] || ''
|
||||
name = name.startsWith(' * @name ') ? name.replace(' * @name ', '').trim() : `user_api_${new Date().toLocaleString()}`
|
||||
if (name.length > 10) name = name.substring(0, 10) + '...'
|
||||
description = description.startsWith(' * @description ') ? description.replace(' * @description ', '').trim() : ''
|
||||
if (description.length > 20) description = description.substring(0, 20) + '...'
|
||||
const apiInfo = {
|
||||
id: `user_api_${Math.random().toString().substring(2, 5)}_${Date.now()}`,
|
||||
name,
|
||||
description,
|
||||
script,
|
||||
}
|
||||
userApis.push(apiInfo)
|
||||
getStore('userApi').set('userApis', userApis)
|
||||
return apiInfo
|
||||
}
|
||||
|
||||
exports.removeApi = ids => {
|
||||
for (let index = userApis.length - 1; index > -1; index--) {
|
||||
if (ids.includes(userApis[index].id)) {
|
||||
userApis.splice(index, 1)
|
||||
ids.splice(index, 1)
|
||||
}
|
||||
}
|
||||
getStore('userApi').set('userApis', userApis)
|
||||
}
|
||||
81
src/main/modules/winLyric/event.js
Normal file
81
src/main/modules/winLyric/event.js
Normal file
@@ -0,0 +1,81 @@
|
||||
const { common: COMMON_EVENT_NAME, winLyric: WIN_LYRIC_EVENT_NAME, hotKey: HOT_KEY_EVENT_NAME, mainWindow: MAIN_WINDOW_EVENT_NAME } = require('../../events/_name')
|
||||
const { mainSend, NAMES: { winLyric: ipcWinLyricNames } } = require('../../../common/ipc')
|
||||
const { desktop_lyric } = require('../../../common/hotKey')
|
||||
const { getLyricWindowBounds } = require('./utils')
|
||||
|
||||
let isLock = null
|
||||
let isEnable = null
|
||||
let isAlwaysOnTop = null
|
||||
let isLockScreen = null
|
||||
const setLrcConfig = () => {
|
||||
let desktopLyric = global.appSetting.desktopLyric
|
||||
if (global.modules.lyricWindow) {
|
||||
mainSend(global.modules.lyricWindow, ipcWinLyricNames.set_lyric_config, {
|
||||
config: desktopLyric,
|
||||
languageId: global.appSetting.langId,
|
||||
isShowLyricTranslation: global.appSetting.player.isShowLyricTranslation,
|
||||
isPlayLxlrc: global.appSetting.player.isPlayLxlrc,
|
||||
})
|
||||
if (isLock != desktopLyric.isLock) {
|
||||
isLock = desktopLyric.isLock
|
||||
if (desktopLyric.isLock) {
|
||||
global.modules.lyricWindow.setIgnoreMouseEvents(true, { forward: false })
|
||||
} else {
|
||||
global.modules.lyricWindow.setIgnoreMouseEvents(false)
|
||||
}
|
||||
}
|
||||
if (isAlwaysOnTop != desktopLyric.isAlwaysOnTop) {
|
||||
isAlwaysOnTop = desktopLyric.isAlwaysOnTop
|
||||
global.modules.lyricWindow.setAlwaysOnTop(desktopLyric.isAlwaysOnTop, 'screen-saver')
|
||||
}
|
||||
if (isLockScreen != desktopLyric.isLockScreen) {
|
||||
isLockScreen = desktopLyric.isLockScreen
|
||||
if (desktopLyric.isLockScreen) {
|
||||
global.modules.lyricWindow.setBounds(getLyricWindowBounds(global.modules.lyricWindow.getBounds(), {
|
||||
x: null,
|
||||
y: null,
|
||||
w: desktopLyric.width,
|
||||
h: desktopLyric.height,
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isEnable != desktopLyric.enable) {
|
||||
isEnable = desktopLyric.enable
|
||||
if (desktopLyric.enable) {
|
||||
global.lx_event.winLyric.create()
|
||||
} else {
|
||||
global.lx_event.winLyric.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
global.lx_event.common.on(COMMON_EVENT_NAME.configStatus, name => {
|
||||
if (WIN_LYRIC_EVENT_NAME.name === name) return
|
||||
setLrcConfig()
|
||||
})
|
||||
|
||||
global.lx_event.mainWindow.on(MAIN_WINDOW_EVENT_NAME.setLyricInfo, info => {
|
||||
if (!global.modules.lyricWindow) return
|
||||
mainSend(global.modules.lyricWindow, ipcWinLyricNames.set_lyric_info, info)
|
||||
})
|
||||
|
||||
global.lx_event.hotKey.on(HOT_KEY_EVENT_NAME.keyDown, ({ type, key }) => {
|
||||
let info = global.appHotKey.config.global.keys[key]
|
||||
if (!info || info.type != WIN_LYRIC_EVENT_NAME.name) return
|
||||
let desktopLyricSetting = JSON.parse(JSON.stringify(global.appSetting.desktopLyric))
|
||||
let settingKey
|
||||
switch (info.action) {
|
||||
case desktop_lyric.toggle_visible.action:
|
||||
settingKey = 'enable'
|
||||
break
|
||||
case desktop_lyric.toggle_lock.action:
|
||||
settingKey = 'isLock'
|
||||
break
|
||||
case desktop_lyric.toggle_always_top.action:
|
||||
settingKey = 'isAlwaysOnTop'
|
||||
break
|
||||
}
|
||||
desktopLyricSetting[settingKey] = !desktopLyricSetting[settingKey]
|
||||
|
||||
global.lx_core.setAppConfig({ desktopLyric: desktopLyricSetting }, null)
|
||||
})
|
||||
127
src/main/modules/winLyric/index.js
Normal file
127
src/main/modules/winLyric/index.js
Normal file
@@ -0,0 +1,127 @@
|
||||
const path = require('path')
|
||||
const { BrowserWindow } = require('electron')
|
||||
const { winLyric: WIN_LYRIC_EVENT_NAME } = require('../../events/_name')
|
||||
const { debounce } = require('../../../common/utils')
|
||||
const { getLyricWindowBounds } = require('./utils')
|
||||
|
||||
require('./event')
|
||||
require('./rendererEvent')
|
||||
|
||||
global.lx_event.winLyric.on(WIN_LYRIC_EVENT_NAME.create, () => {
|
||||
createWindow()
|
||||
})
|
||||
global.lx_event.winLyric.on(WIN_LYRIC_EVENT_NAME.close, () => {
|
||||
closeWindow()
|
||||
})
|
||||
|
||||
let winURL = global.isDev ? 'http://localhost:9081/lyric.html' : `file://${path.join(__dirname, 'lyric.html')}`
|
||||
|
||||
const setLyricsConfig = debounce(config => {
|
||||
// if (x != null) bounds.x = x
|
||||
// if (y != null) bounds.y = y
|
||||
// if (width != null) bounds.width = width
|
||||
// if (height != null) bounds.height = height
|
||||
global.lx_core.setAppConfig({ desktopLyric: config }, WIN_LYRIC_EVENT_NAME.name)
|
||||
}, 500)
|
||||
|
||||
const winEvent = lyricWindow => {
|
||||
// let bounds
|
||||
// lyricWindow.on('close', event => {
|
||||
// if (global.isQuitting || !global.appSetting.tray.isToTray || (!isWin && !global.isTrafficLightClose)) {
|
||||
// lyricWindow.setProgressBar(-1)
|
||||
// return
|
||||
// }
|
||||
|
||||
// if (global.isTrafficLightClose) global.isTrafficLightClose = false
|
||||
// event.preventDefault()
|
||||
// lyricWindow.hide()
|
||||
// })
|
||||
|
||||
lyricWindow.on('closed', () => {
|
||||
lyricWindow = global.modules.lyricWindow = null
|
||||
})
|
||||
|
||||
|
||||
lyricWindow.on('move', event => {
|
||||
// bounds = lyricWindow.getBounds()
|
||||
// console.log(bounds)
|
||||
setLyricsConfig(lyricWindow.getBounds())
|
||||
})
|
||||
|
||||
lyricWindow.on('resize', event => {
|
||||
// bounds = lyricWindow.getBounds()
|
||||
// console.log(bounds)
|
||||
setLyricsConfig(lyricWindow.getBounds())
|
||||
})
|
||||
|
||||
// lyricWindow.on('restore', () => {
|
||||
// lyricWindow.webContents.send('restore')
|
||||
// })
|
||||
// lyricWindow.on('focus', () => {
|
||||
// lyricWindow.webContents.send('focus')
|
||||
// })
|
||||
|
||||
lyricWindow.once('ready-to-show', () => {
|
||||
lyricWindow.show()
|
||||
if (global.appSetting.desktopLyric.isLock) {
|
||||
global.modules.lyricWindow.setIgnoreMouseEvents(true, { forward: false })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const createWindow = () => {
|
||||
if (global.modules.lyricWindow) return
|
||||
if (!global.appSetting.desktopLyric.enable) return
|
||||
// const windowSizeInfo = getWindowSizeInfo(global.appSetting)
|
||||
let { x, y, width, height, isAlwaysOnTop } = global.appSetting.desktopLyric
|
||||
let { width: screenWidth, height: screenHeight } = global.envParams.workAreaSize
|
||||
if (x == null) {
|
||||
x = screenWidth - width
|
||||
y = screenHeight - height
|
||||
}
|
||||
if (global.appSetting.desktopLyric.isLockScreen) {
|
||||
let bounds = getLyricWindowBounds({ x, y, width, height }, { x: null, y: null, w: width, h: height })
|
||||
x = bounds.x
|
||||
y = bounds.y
|
||||
width = bounds.width
|
||||
height = bounds.height
|
||||
}
|
||||
/**
|
||||
* Initial window options
|
||||
*/
|
||||
global.modules.lyricWindow = new BrowserWindow({
|
||||
height,
|
||||
width,
|
||||
x,
|
||||
y,
|
||||
minWidth: 380,
|
||||
minHeight: 80,
|
||||
useContentSize: true,
|
||||
frame: false,
|
||||
transparent: true,
|
||||
enableRemoteModule: false,
|
||||
// icon: path.join(global.__static, isWin ? 'icons/256x256.ico' : 'icons/512x512.png'),
|
||||
resizable: false,
|
||||
minimizable: false,
|
||||
maximizable: false,
|
||||
fullscreenable: false,
|
||||
show: false,
|
||||
alwaysOnTop: isAlwaysOnTop,
|
||||
skipTaskbar: true,
|
||||
webPreferences: {
|
||||
contextIsolation: false,
|
||||
webSecurity: !global.isDev,
|
||||
nodeIntegration: true,
|
||||
},
|
||||
})
|
||||
|
||||
global.modules.lyricWindow.loadURL(winURL)
|
||||
|
||||
winEvent(global.modules.lyricWindow)
|
||||
// mainWindow.webContents.openDevTools()
|
||||
}
|
||||
|
||||
const closeWindow = () => {
|
||||
if (!global.modules.lyricWindow) return
|
||||
global.modules.lyricWindow.close()
|
||||
}
|
||||
37
src/main/modules/winLyric/rendererEvent.js
Normal file
37
src/main/modules/winLyric/rendererEvent.js
Normal file
@@ -0,0 +1,37 @@
|
||||
const {
|
||||
mainOn,
|
||||
mainHandle,
|
||||
mainSend,
|
||||
NAMES: {
|
||||
mainWindow: ipcMainWindowNames,
|
||||
winLyric: ipcWinLyricNames,
|
||||
},
|
||||
} = require('../../../common/ipc')
|
||||
const { winLyric: WIN_LYRIC_EVENT_NAME } = require('../../events/_name')
|
||||
const { getLyricWindowBounds } = require('./utils')
|
||||
|
||||
mainOn(ipcWinLyricNames.get_lyric_info, (event, action) => {
|
||||
if (!global.modules.mainWindow) return
|
||||
mainSend(global.modules.mainWindow, ipcMainWindowNames.get_lyric_info, {
|
||||
name: ipcWinLyricNames.set_lyric_info,
|
||||
modal: 'lyricWindow',
|
||||
action,
|
||||
})
|
||||
})
|
||||
|
||||
mainOn(ipcWinLyricNames.set_lyric_config, (event, config) => {
|
||||
global.lx_core.setAppConfig({ desktopLyric: config }, WIN_LYRIC_EVENT_NAME.name)
|
||||
})
|
||||
|
||||
mainHandle(ipcWinLyricNames.get_lyric_config, async() => {
|
||||
return {
|
||||
config: global.appSetting.desktopLyric,
|
||||
languageId: global.appSetting.langId,
|
||||
isShowLyricTranslation: global.appSetting.player.isShowLyricTranslation,
|
||||
isPlayLxlrc: global.appSetting.player.isPlayLxlrc,
|
||||
}
|
||||
})
|
||||
|
||||
mainOn(ipcWinLyricNames.set_win_bounds, (event, options) => {
|
||||
global.modules.lyricWindow.setBounds(getLyricWindowBounds(global.modules.lyricWindow.getBounds(), options))
|
||||
})
|
||||
68
src/main/modules/winLyric/utils.js
Normal file
68
src/main/modules/winLyric/utils.js
Normal file
@@ -0,0 +1,68 @@
|
||||
// 设置窗口位置、大小
|
||||
let winX
|
||||
let winY
|
||||
let wasW
|
||||
let wasH
|
||||
let offset = 8
|
||||
let minWidth = 380
|
||||
let minHeight = 80
|
||||
exports.getLyricWindowBounds = (bounds, { x = 0, y = 0, w = 0, h = 0 }) => {
|
||||
if (w < minWidth) w = minWidth
|
||||
if (h < minHeight) h = minHeight
|
||||
|
||||
if (global.appSetting.desktopLyric.isLockScreen) {
|
||||
wasW = global.envParams.workAreaSize.width + offset
|
||||
wasH = global.envParams.workAreaSize.height + offset
|
||||
if (w > wasW + offset) w = wasW + offset
|
||||
if (h > wasH + offset) h = wasH + offset
|
||||
if (x == null) {
|
||||
if (bounds.x > wasW - w) {
|
||||
x = wasW - w - bounds.x
|
||||
} else if (bounds.x < -offset) {
|
||||
x = bounds.x + offset
|
||||
} else {
|
||||
x = 0
|
||||
}
|
||||
if (bounds.y > wasH - h) {
|
||||
y = wasH - h - bounds.y
|
||||
} else if (bounds.y < -offset) {
|
||||
y = bounds.y + offset
|
||||
} else {
|
||||
y = 0
|
||||
}
|
||||
}
|
||||
winX = bounds.x + x
|
||||
winY = bounds.y + y
|
||||
|
||||
if (x != 0) {
|
||||
if (winX < -offset) {
|
||||
winX = -offset
|
||||
} else if (winX + w > wasW) {
|
||||
winX = wasW - w
|
||||
}
|
||||
}
|
||||
if (y != 0) {
|
||||
if (winY < -offset) {
|
||||
winY = -offset
|
||||
} else if (winY + h > wasH) {
|
||||
winY = wasH - h
|
||||
}
|
||||
}
|
||||
|
||||
x = winX
|
||||
y = winY
|
||||
|
||||
if (x + w > wasW) w = wasW - x
|
||||
if (y + h > wasH) h = wasH - y
|
||||
} else {
|
||||
y += bounds.y
|
||||
x += bounds.x
|
||||
}
|
||||
|
||||
bounds.width = w
|
||||
bounds.height = h
|
||||
bounds.x = x
|
||||
bounds.y = y
|
||||
// console.log('util bounds', bounds)
|
||||
return bounds
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
const { mainOn } = require('../../common/icp')
|
||||
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
|
||||
const { app } = require('electron')
|
||||
const { name: defaultName } = require('../../../package.json')
|
||||
|
||||
|
||||
mainOn('appName', (event, params) => {
|
||||
mainOn(ipcMainWindowNames.set_app_name, (event, params) => {
|
||||
if (params == null) {
|
||||
app.setName(defaultName)
|
||||
} else {
|
||||
11
src/main/rendererEvents/appSetting.js
Normal file
11
src/main/rendererEvents/appSetting.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const { mainWindow: MAIN_WINDOW_EVENT_NAME } = require('../events/_name')
|
||||
const { NAMES: { mainWindow: ipcMainWindowNames }, mainHandle } = require('../../common/ipc')
|
||||
|
||||
|
||||
mainHandle(ipcMainWindowNames.set_app_setting, (event, config) => {
|
||||
if (!config) return
|
||||
global.lx_core.setAppConfig(config, MAIN_WINDOW_EVENT_NAME.name)
|
||||
return global.appSetting
|
||||
})
|
||||
|
||||
mainHandle(ipcMainWindowNames.get_setting, async() => ({ setting: global.appSetting, version: global.appSettingVersion }))
|
||||
11
src/main/rendererEvents/cache.js
Normal file
11
src/main/rendererEvents/cache.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
|
||||
|
||||
mainHandle(ipcMainWindowNames.clear_cache, async(event, options) => {
|
||||
if (!global.modules.mainWindow) throw new Error('mainWindow is undefined')
|
||||
return global.modules.mainWindow.webContents.session.clearCache()
|
||||
})
|
||||
|
||||
mainHandle(ipcMainWindowNames.get_cache_size, async(event, options) => {
|
||||
if (!global.modules.mainWindow) throw new Error('mainWindow is undefined')
|
||||
return global.modules.mainWindow.webContents.session.getCacheSize()
|
||||
})
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user