Compare commits
766 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d93d88bf0 | ||
|
|
8a49e32d2a | ||
|
|
8bbc4a3c4a | ||
|
|
3a9dbbb577 | ||
|
|
5f11fa65b4 | ||
|
|
6f4129d317 | ||
|
|
e2a1d7b99d | ||
|
|
18b6f73008 | ||
|
|
c16de232ad | ||
|
|
6ff1d683a5 | ||
|
|
a7c2857808 | ||
|
|
4cea16c326 | ||
|
|
a8228159b7 | ||
|
|
4594f2fd1f | ||
|
|
9c7661d1ae | ||
|
|
dd2416b5ae | ||
|
|
54ddb7473b | ||
|
|
cc7ad7c71f | ||
|
|
bfc07b2a97 | ||
|
|
2aad2c6b8a | ||
|
|
959df65e0a | ||
|
|
76d32c1cb4 | ||
|
|
da59164664 | ||
|
|
01c3a7d687 | ||
|
|
9a57118300 | ||
|
|
8ecd6e6f54 | ||
|
|
b9ac754c07 | ||
|
|
3bd48e547d | ||
|
|
92c77332a0 | ||
|
|
5c7652afe8 | ||
|
|
c2dd35790f | ||
|
|
5dae561b70 | ||
|
|
b3600bef34 | ||
|
|
c6087f577d | ||
|
|
c695c00e76 | ||
|
|
0d10d1f95b | ||
|
|
f80d03ed15 | ||
|
|
f2b023193c | ||
|
|
e467b92bb9 | ||
|
|
ae467cd515 | ||
|
|
9e51a55d45 | ||
|
|
67a3ab164a | ||
|
|
77ea6d3748 | ||
|
|
5e9b3a16fa | ||
|
|
bdeab55d3c | ||
|
|
bb10474cbb | ||
|
|
d7a8e3dc51 | ||
|
|
2d5e336f0f | ||
|
|
2aa4ff8422 | ||
|
|
8f7523dd95 | ||
|
|
f5cdd99fe6 | ||
|
|
2459b4c64c | ||
|
|
54ea4c03d6 | ||
|
|
59061a0672 | ||
|
|
03b73e89b3 | ||
|
|
3bc6fc97a2 | ||
|
|
82176a831d | ||
|
|
9920e5ef99 | ||
|
|
f3852b20ca | ||
|
|
b20886f08a | ||
|
|
ec2b802912 | ||
|
|
3b5832eceb | ||
|
|
589e373bd8 | ||
|
|
a6fd41900f | ||
|
|
7daa626d55 | ||
|
|
72da89235a | ||
|
|
8a14fe7985 | ||
|
|
1e1c0515d1 | ||
|
|
95a398da23 | ||
|
|
ff8a8d860f | ||
|
|
928cf264b0 | ||
|
|
7139682bc7 | ||
|
|
b5ad039980 | ||
|
|
ab37817d26 | ||
|
|
e79f8b357c | ||
|
|
bdb9f9b949 | ||
|
|
07708c7a56 | ||
|
|
05e9c07aa1 | ||
|
|
3486cfaab7 | ||
|
|
313eba409c | ||
|
|
88cae5d8e9 | ||
|
|
52c4a77934 | ||
|
|
e526c244f3 | ||
|
|
54b5517222 | ||
|
|
70a691bc68 | ||
|
|
55d22e424d | ||
|
|
69ea40abc0 | ||
|
|
953fc94cb0 | ||
|
|
4b22518532 | ||
|
|
b60c438694 | ||
|
|
d1d9d65a06 | ||
|
|
b1ee4d10d7 | ||
|
|
908ef635fa | ||
|
|
6b2cf5df2f | ||
|
|
1105a9f31c | ||
|
|
689724db6c | ||
|
|
3530a95738 | ||
|
|
c9d8bbe874 | ||
|
|
8d87990793 | ||
|
|
5c4590d507 | ||
|
|
11d3754c08 | ||
|
|
209bf9e0aa | ||
|
|
9eaf072e8f | ||
|
|
065bbef781 | ||
|
|
beb5589a4a | ||
|
|
d88d5e7818 | ||
|
|
12336148d9 | ||
|
|
9bec54c454 | ||
|
|
303d712860 | ||
|
|
9e6bfaada4 | ||
|
|
3f8c47100d | ||
|
|
716120ea61 | ||
|
|
2003c5a0c9 | ||
|
|
07898cdfd6 | ||
|
|
72418194e9 | ||
|
|
358b93fb70 | ||
|
|
3e9a111643 | ||
|
|
8841951fe3 | ||
|
|
e45cee6596 | ||
|
|
66b965d9f7 | ||
|
|
f4285ae106 | ||
|
|
657e65bc6a | ||
|
|
41fc8d2e2e | ||
|
|
25ca781d76 | ||
|
|
41f4130e5d | ||
|
|
8e19f3fc0b | ||
|
|
c4b8ac8b0b | ||
|
|
0ac2df1297 | ||
|
|
251450fd50 | ||
|
|
85c5595f43 | ||
|
|
ca3188c930 | ||
|
|
2f47f82b0f | ||
|
|
0e09598b10 | ||
|
|
a705420528 | ||
|
|
4089887514 | ||
|
|
a3d02e5f39 | ||
|
|
6e09363832 | ||
|
|
1c58a64847 | ||
|
|
2a716e4d5a | ||
|
|
a2c613d380 | ||
|
|
aa4b09436c | ||
|
|
9521bba4ba | ||
|
|
55d10021f8 | ||
|
|
7fe35748d0 | ||
|
|
ff9efbd4ea | ||
|
|
23b8c325f4 | ||
|
|
ecfccb4c40 | ||
|
|
2472574cde | ||
|
|
bf81c8facc | ||
|
|
9bf0d69ab6 | ||
|
|
cf9d98d434 | ||
|
|
da8e67d4e5 | ||
|
|
d695cdef08 | ||
|
|
d7a7701c09 | ||
|
|
a31180260b | ||
|
|
f33947287c | ||
|
|
c054ceeab5 | ||
|
|
c8dce1bcb6 | ||
|
|
6195c10646 | ||
|
|
535cbbbd16 | ||
|
|
fa99f35bc0 | ||
|
|
5800f0e354 | ||
|
|
aa5e621184 | ||
|
|
283bdd6246 | ||
|
|
2884e67109 | ||
|
|
4542f9b2e3 | ||
|
|
9d08f91079 | ||
|
|
0704a4ad12 | ||
|
|
bb8509a21c | ||
|
|
f2e3d4a53d | ||
|
|
b8a221e67e | ||
|
|
f4147cce37 | ||
|
|
01f9fd502f | ||
|
|
5d20adc81e | ||
|
|
6a78f659e3 | ||
|
|
211f584bf8 | ||
|
|
fcfba8b12c | ||
|
|
85e4c7555a | ||
|
|
19dccffdde | ||
|
|
359514a83c | ||
|
|
90ea0dc857 | ||
|
|
ae18db45a4 | ||
|
|
4783c9be8d | ||
|
|
ff41fa9829 | ||
|
|
aa56dafd5f | ||
|
|
f2f2008d5d | ||
|
|
15d2589db8 | ||
|
|
47a8da49ca | ||
|
|
2250cac7f9 | ||
|
|
94e3063307 | ||
|
|
88691b3f18 | ||
|
|
3851880007 | ||
|
|
e5d2e089f4 | ||
|
|
bfe7f24a4b | ||
|
|
0d1e2c3349 | ||
|
|
bc6272a0ed | ||
|
|
56a752dc26 | ||
|
|
0fe443c92a | ||
|
|
74afdb2a46 | ||
|
|
ebd6ff36ed | ||
|
|
a5ec36bf89 | ||
|
|
c65802c1c3 | ||
|
|
784515e411 | ||
|
|
e859d39b2b | ||
|
|
62c4bfb9eb | ||
|
|
8ab1bdf30a | ||
|
|
c758066e25 | ||
|
|
06b7f69b3c | ||
|
|
c3c43a2645 | ||
|
|
343395a5de | ||
|
|
3c9bb74b61 | ||
|
|
c9cf30692f | ||
|
|
c979339c4e | ||
|
|
9393906eb9 | ||
|
|
1b4c910264 | ||
|
|
4db5d26b1e | ||
|
|
c09a4131db | ||
|
|
ce6f3f23c6 | ||
|
|
7b898c2d91 | ||
|
|
3da797a0ad | ||
|
|
517899cd03 | ||
|
|
1d6a14685e | ||
|
|
737d00f550 | ||
|
|
68798a9627 | ||
|
|
5b1dfee05c | ||
|
|
e75863e99d | ||
|
|
1c542a9b17 | ||
|
|
729ac5a0d7 | ||
|
|
ff8195df46 | ||
|
|
692ab6dacb | ||
|
|
128039d678 | ||
|
|
bb5de056d4 | ||
|
|
d927ac90e7 | ||
|
|
f61099ecad | ||
|
|
f96b663bbe | ||
|
|
f65213f8aa | ||
|
|
2e5adeb50c | ||
|
|
6de84d3b9b | ||
|
|
65ae08add6 | ||
|
|
3c970c24c1 | ||
|
|
93f3507dc8 | ||
|
|
e92e291409 | ||
|
|
4b4de8937e | ||
|
|
27a036bc00 | ||
|
|
1eb2540c67 | ||
|
|
3a5bd6029f | ||
|
|
249ff7f2d2 | ||
|
|
9afa965d5d | ||
|
|
28b7b5581d | ||
|
|
26aab15e50 | ||
|
|
92351049f1 | ||
|
|
47d3c50863 | ||
|
|
36ac3532e6 | ||
|
|
dee16f6943 | ||
|
|
3541b568c9 | ||
|
|
4dfa4d9525 | ||
|
|
5938f4e591 | ||
|
|
9ac26be65d | ||
|
|
5099ba9c96 | ||
|
|
bca5b13516 | ||
|
|
6ceb984393 | ||
|
|
17b29f376a | ||
|
|
36bef673d3 | ||
|
|
c2fa4e5220 | ||
|
|
ddf06d20c2 | ||
|
|
e51f640371 | ||
|
|
e302f53d5c | ||
|
|
b2058b9581 | ||
|
|
35501d328b | ||
|
|
cb746c64db | ||
|
|
e2f6d117bc | ||
|
|
9fd9449eca | ||
|
|
1f97af6ff1 | ||
|
|
f1a7862a9c | ||
|
|
8aeac3ed2e | ||
|
|
469832a598 | ||
|
|
e0e65e4374 | ||
|
|
66f78d6979 | ||
|
|
efc58f7dd1 | ||
|
|
bc301c27c2 | ||
|
|
82ea1935de | ||
|
|
226fb05337 | ||
|
|
be219f03d5 | ||
|
|
f10c1e923e | ||
|
|
66121e5ff3 | ||
|
|
478b71172d | ||
|
|
e7370f12ae | ||
|
|
b844a577c7 | ||
|
|
9bf33fdb25 | ||
|
|
8b988b227b | ||
|
|
8b2be019c6 | ||
|
|
4d7d92759f | ||
|
|
798b840472 | ||
|
|
48db75a995 | ||
|
|
7072541ba7 | ||
|
|
7119c915ec | ||
|
|
5e3035cc15 | ||
|
|
7cf2e4c4ed | ||
|
|
674a3a0a3f | ||
|
|
7486a99494 | ||
|
|
783d96bd7d | ||
|
|
64eea42092 | ||
|
|
8453ad0fb4 | ||
|
|
0acfeff006 | ||
|
|
a338b7ef39 | ||
|
|
e821660124 | ||
|
|
bad6045164 | ||
|
|
6779fd7ae5 | ||
|
|
04858cc42c | ||
|
|
916975b0a9 | ||
|
|
644db88e58 | ||
|
|
b025af45fa | ||
|
|
0f11731c01 | ||
|
|
ea1d21a3ef | ||
|
|
545ba6521b | ||
|
|
1fd3e6c9fa | ||
|
|
2913c0c7de | ||
|
|
a4c851899f | ||
|
|
df1c58e79b | ||
|
|
67edeab09b | ||
|
|
fe07ac025a | ||
|
|
fc72d99ab3 | ||
|
|
377f665361 | ||
|
|
4e458ec4cb | ||
|
|
6f4500f31a | ||
|
|
a363c5525b | ||
|
|
65027fceba | ||
|
|
2b0c75a649 | ||
|
|
bcc79541c1 | ||
|
|
081d6dc91e | ||
|
|
09a5111e36 | ||
|
|
253746843c | ||
|
|
5d2fac9247 | ||
|
|
3c2a1093a0 | ||
|
|
88b3d8ee4a | ||
|
|
d35a6f504f | ||
|
|
4965dad6a2 | ||
|
|
3ad4584159 | ||
|
|
5e291255bb | ||
|
|
205c99372e | ||
|
|
a05f3a969b | ||
|
|
3acc0931d8 | ||
|
|
05f6a953ea | ||
|
|
6253368f05 | ||
|
|
9483f2f178 | ||
|
|
8df6e7ef90 | ||
|
|
b09dba0a95 | ||
|
|
cfee4a0b24 | ||
|
|
34febf4949 | ||
|
|
6aed9dfb9f | ||
|
|
c7360993c9 | ||
|
|
34a072bada | ||
|
|
dda0d54f20 | ||
|
|
d710c31d48 | ||
|
|
02c74f7d96 | ||
|
|
5bdccba29e | ||
|
|
cdc819fbe8 | ||
|
|
4629d4cd3e | ||
|
|
ba3a70c524 | ||
|
|
52b985dd8e | ||
|
|
b51c8e1552 | ||
|
|
86f9c03707 | ||
|
|
e61c84ee08 | ||
|
|
2f39808275 | ||
|
|
f5aee4cbd0 | ||
|
|
9fd9bb4eef | ||
|
|
685cae8993 | ||
|
|
ec14aadec6 | ||
|
|
b934770e1e | ||
|
|
2f76433f34 | ||
|
|
535c63be2b | ||
|
|
53d4f08ee7 | ||
|
|
81b72fe72b | ||
|
|
e496794052 | ||
|
|
442b4fb30f | ||
|
|
9528b10d01 | ||
|
|
a9f9c887b5 | ||
|
|
6105a331aa | ||
|
|
0f0be95c12 | ||
|
|
d6ca9e4539 | ||
|
|
d3b5171237 | ||
|
|
cf501ac19e | ||
|
|
4fd4746ddf | ||
|
|
61c1476823 | ||
|
|
e28b8bb6e8 | ||
|
|
5eb9107adf | ||
|
|
83ddf74890 | ||
|
|
6aa7f2a0ee | ||
|
|
d801d55c3c | ||
|
|
491e644c3c | ||
|
|
5844232a00 | ||
|
|
abbf067625 | ||
|
|
806c882965 | ||
|
|
3fe5a85ffe | ||
|
|
95dae62bb3 | ||
|
|
f7edd54ea7 | ||
|
|
78ae89199e | ||
|
|
fc12a235f2 | ||
|
|
48fc96226c | ||
|
|
3534dd27dc | ||
|
|
a7e3c1a0c4 | ||
|
|
43c5051158 | ||
|
|
2f56a7090a | ||
|
|
13ffcfbbcd | ||
|
|
1aeb8e29e2 | ||
|
|
a3b844d2e3 | ||
|
|
5aa027f1a9 | ||
|
|
c12be6a1c8 | ||
|
|
c891ed81e1 | ||
|
|
c48921990d | ||
|
|
54bfd945e5 | ||
|
|
1d8c4db5eb | ||
|
|
055ab98d4c | ||
|
|
777551cb43 | ||
|
|
68827f7697 | ||
|
|
8e5b144f00 | ||
|
|
748a420b64 | ||
|
|
80fc43a075 | ||
|
|
0588f2846b | ||
|
|
8e4e7d9efa | ||
|
|
6df01edc7a | ||
|
|
996396c943 | ||
|
|
e3dddb9288 | ||
|
|
47ce71c959 | ||
|
|
8921aeb3f2 | ||
|
|
35be9739d4 | ||
|
|
f5ce46431b | ||
|
|
81ed70d291 | ||
|
|
4b0fdceb76 | ||
|
|
58e537732c | ||
|
|
02ad79b12a | ||
|
|
b179887efc | ||
|
|
0580b059fc | ||
|
|
bebd0e7258 | ||
|
|
6e96b2163e | ||
|
|
ca15ac987c | ||
|
|
6d3414caa9 | ||
|
|
85b80312e0 | ||
|
|
30d5f53c1d | ||
|
|
bad2e98757 | ||
|
|
45da396cb2 | ||
|
|
d195176a46 | ||
|
|
53078f79a6 | ||
|
|
dbb7f0c683 | ||
|
|
cb7703ecbb | ||
|
|
e63d7174bb | ||
|
|
31ab7a0493 | ||
|
|
c503f09529 | ||
|
|
f5bb9189af | ||
|
|
9ed8ba80c4 | ||
|
|
f9cce82a93 | ||
|
|
79d418d1a1 | ||
|
|
15d7f73aca | ||
|
|
4561870ece | ||
|
|
dbc8c9d07d | ||
|
|
d81a2029ec | ||
|
|
d04a5a2f92 | ||
|
|
5146f083ff | ||
|
|
57122c6eea | ||
|
|
ca748906c0 | ||
|
|
4d31bde98a | ||
|
|
3f98c332bd | ||
|
|
fb3eced1fb | ||
|
|
9641ea0b3a | ||
|
|
1df05a5529 | ||
|
|
a393bb02dd | ||
|
|
20518cbe2b | ||
|
|
9447034deb | ||
|
|
c7f0fcbe9f | ||
|
|
fce8719432 | ||
|
|
ce383769da | ||
|
|
342af62aea | ||
|
|
b3ab05cc86 | ||
|
|
36966e220c | ||
|
|
8ae3347c05 | ||
|
|
4f51a8ce31 | ||
|
|
1371bbe6fb | ||
|
|
94f4c9e334 | ||
|
|
c5c89f9d9e | ||
|
|
6a79f8ec0d | ||
|
|
aa940ef9f7 | ||
|
|
7a20f06cf4 | ||
|
|
b9596f2a6e | ||
|
|
bb2dfc269c | ||
|
|
684af4e7db | ||
|
|
1c946ac661 | ||
|
|
f8d06d38ab | ||
|
|
2a679c914b | ||
|
|
3f8d5ed570 | ||
|
|
028c2f9f03 | ||
|
|
b783c0c227 | ||
|
|
30787a372e | ||
|
|
b99169cc3b | ||
|
|
78cf146a92 | ||
|
|
b867167b78 | ||
|
|
d8dfbf6995 | ||
|
|
c2d6737796 | ||
|
|
dcfe055461 | ||
|
|
c86b1c259c | ||
|
|
400c16ba2d | ||
|
|
3ff0dbb7ef | ||
|
|
469a88f368 | ||
|
|
217ff27e72 | ||
|
|
e337f7121b | ||
|
|
665ac5ee99 | ||
|
|
f06c493458 | ||
|
|
f383e47344 | ||
|
|
e15518cd51 | ||
|
|
27ce62dd4f | ||
|
|
26202d8e56 | ||
|
|
d93b5a3374 | ||
|
|
e3acb7ce4e | ||
|
|
5b7c965c48 | ||
|
|
5f1e670dd7 | ||
|
|
ab1562ea33 | ||
|
|
2359a1444b | ||
|
|
9075b6629f | ||
|
|
583e8c43d6 | ||
|
|
26369dd14b | ||
|
|
9d40e7c004 | ||
|
|
267535844e | ||
|
|
0f5d2189e0 | ||
|
|
f76f4d9d29 | ||
|
|
2ba5e40012 | ||
|
|
1fe6ae4556 | ||
|
|
3d8a1894f6 | ||
|
|
70cfce833c | ||
|
|
96c3034a07 | ||
|
|
5aad81c696 | ||
|
|
b0e2f44f01 | ||
|
|
442e633800 | ||
|
|
5f1e788a25 | ||
|
|
3808e7e78f | ||
|
|
650edc1b1e | ||
|
|
cb5e1bd795 | ||
|
|
3282557769 | ||
|
|
dff18b6b9d | ||
|
|
2a64ead1c4 | ||
|
|
eb0a61d35b | ||
|
|
ddca70980e | ||
|
|
5aa3502ef5 | ||
|
|
3b02703805 | ||
|
|
f39d13c8f8 | ||
|
|
16a203227a | ||
|
|
b350f133b0 | ||
|
|
ec3a0e08c3 | ||
|
|
a96eea5e44 | ||
|
|
0dc7815e35 | ||
|
|
7f6e1c25e9 | ||
|
|
9291c35b00 | ||
|
|
90b67fc05c | ||
|
|
09afd2c6f8 | ||
|
|
1def571191 | ||
|
|
ef52f2465e | ||
|
|
845e5de43f | ||
|
|
b7433264eb | ||
|
|
60e606a413 | ||
|
|
9afafec9d6 | ||
|
|
c9cb84a28d | ||
|
|
8949383ade | ||
|
|
f732ea4dab | ||
|
|
162842415f | ||
|
|
07d200a752 | ||
|
|
822e1ea6c9 | ||
|
|
f79ae1541d | ||
|
|
2dfa93383f | ||
|
|
1ab5db8507 | ||
|
|
ccd951b5d3 | ||
|
|
6259690d12 | ||
|
|
8059d9cd73 | ||
|
|
c6635e554a | ||
|
|
d7ca662e70 | ||
|
|
b6f885b250 | ||
|
|
63141c4ac9 | ||
|
|
94e9f01e62 | ||
|
|
cffa74b1c2 | ||
|
|
1fb917941e | ||
|
|
08c6a4e2fb | ||
|
|
976be6d96f | ||
|
|
fe95b45820 | ||
|
|
47dffeee76 | ||
|
|
be7b06f5c8 | ||
|
|
3a371f8d75 | ||
|
|
fa97058895 | ||
|
|
df9289367b | ||
|
|
3a824b1d5e | ||
|
|
6f72da87b1 | ||
|
|
1b1c5ad6da | ||
|
|
f4777f787c | ||
|
|
6bcf45fba7 | ||
|
|
1e8a05e313 | ||
|
|
1cf0f2dd0b | ||
|
|
25a0439928 | ||
|
|
df20e97d60 | ||
|
|
fd764a28a4 | ||
|
|
81bd8fbb74 | ||
|
|
47ee572640 | ||
|
|
a0b9553158 | ||
|
|
7659a6cadf | ||
|
|
a65bae8243 | ||
|
|
421abac30a | ||
|
|
0b11e97427 | ||
|
|
fbe1ff73f1 | ||
|
|
68e831468b | ||
|
|
e382f0dba0 | ||
|
|
f0f92952d6 | ||
|
|
58f016ca00 | ||
|
|
faff0edb4a | ||
|
|
5e9a9e5b55 | ||
|
|
e182dde718 | ||
|
|
7dfc2e8736 | ||
|
|
b27a547fb8 | ||
|
|
379c752cf8 | ||
|
|
35abdc63d4 | ||
|
|
a386915b50 | ||
|
|
ab16bc849d | ||
|
|
90007127ba | ||
|
|
a1935cbdac | ||
|
|
7de91bd923 | ||
|
|
cdbcae21ac | ||
|
|
ea62033cf3 | ||
|
|
a48423944a | ||
|
|
b7fb3cd208 | ||
|
|
34825b5063 | ||
|
|
7737012103 | ||
|
|
3912a375c5 | ||
|
|
35ca6db1ee | ||
|
|
0d20869337 | ||
|
|
89d161268e | ||
|
|
5ae39ef8d3 | ||
|
|
e57696d4ff | ||
|
|
15c9a5468b | ||
|
|
30318b7c61 | ||
|
|
8b0fc9bf5f | ||
|
|
5fdba7720c | ||
|
|
ca12dc604d | ||
|
|
09f4d8ac0c | ||
|
|
254b82cc60 | ||
|
|
d4240006f1 | ||
|
|
68518e609d | ||
|
|
001eba6729 | ||
|
|
ff47a224c7 | ||
|
|
8810e25ea9 | ||
|
|
eb67d67a60 | ||
|
|
e12f95ff74 | ||
|
|
2fe213d060 | ||
|
|
b726ee42f7 | ||
|
|
6b71b4709e | ||
|
|
0701b78720 | ||
|
|
82d1ac1e83 | ||
|
|
8acf98631f | ||
|
|
0a31d19561 | ||
|
|
91279922f1 | ||
|
|
96804ae2e2 | ||
|
|
3ae8a89961 | ||
|
|
f8f13fd32c | ||
|
|
ae61d2c629 | ||
|
|
b1918e8b15 | ||
|
|
ce7bca5b36 | ||
|
|
51be62bc29 | ||
|
|
c1ff3971de | ||
|
|
57db286405 | ||
|
|
592b670e96 | ||
|
|
97fdcaac51 | ||
|
|
271d9ac13e | ||
|
|
b6cea110a7 | ||
|
|
4b5dae4779 | ||
|
|
a250317a28 | ||
|
|
431605e793 | ||
|
|
a4d3c06119 | ||
|
|
246412cb2c | ||
|
|
2fb013fb41 | ||
|
|
6f9d684b2d | ||
|
|
af6a2ec581 | ||
|
|
488b28921f | ||
|
|
4c23fc1011 | ||
|
|
db1c903f1b | ||
|
|
3fdf7411fc | ||
|
|
1da275ff3a | ||
|
|
bc5167f38d | ||
|
|
609b6f3123 | ||
|
|
fe35081e03 | ||
|
|
72df1a35a0 | ||
|
|
90ffcf5071 | ||
|
|
35f60cc536 | ||
|
|
31bb40c50b | ||
|
|
bc7b35f816 | ||
|
|
0c76612a4c | ||
|
|
ab86674aa3 | ||
|
|
8cd5a01dd5 | ||
|
|
4ef5bafcca | ||
|
|
2c783e0657 | ||
|
|
0c7ead5b21 | ||
|
|
57a8b7842e | ||
|
|
4b5a7b6a74 | ||
|
|
c09c31767d | ||
|
|
ecff04618b | ||
|
|
c377469dad | ||
|
|
ecb41382cb | ||
|
|
3150b12b99 | ||
|
|
364260b070 | ||
|
|
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 | ||
|
|
50ddc8caa3 |
17
.babelrc
@@ -1,20 +1,21 @@
|
||||
{
|
||||
"presets": [
|
||||
"@babel/preset-typescript",
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"corejs": "3",
|
||||
"useBuiltIns": "usage"
|
||||
}
|
||||
],
|
||||
[
|
||||
"minify",
|
||||
{
|
||||
"builtIns": false,
|
||||
"evaluate": false,
|
||||
"mangle": false
|
||||
}
|
||||
]
|
||||
// [
|
||||
// "minify",
|
||||
// {
|
||||
// "builtIns": false,
|
||||
// "evaluate": false,
|
||||
// "mangle": false
|
||||
// }
|
||||
// ]
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-syntax-dynamic-import",
|
||||
|
||||
105
.eslintrc
@@ -1,9 +1,15 @@
|
||||
{
|
||||
"extends": "standard",
|
||||
"root": true,
|
||||
"extends": [
|
||||
"standard"
|
||||
],
|
||||
"plugins": [
|
||||
"html"
|
||||
],
|
||||
"parser": "babel-eslint",
|
||||
"parser": "@babel/eslint-parser",
|
||||
"parserOptions": {
|
||||
// "requireConfigFile": false
|
||||
},
|
||||
"rules": {
|
||||
"no-new": "off",
|
||||
"camelcase": "off",
|
||||
@@ -16,9 +22,96 @@
|
||||
"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"]
|
||||
}
|
||||
"ignorePatterns": ["vendors", "*.min.js", "dist"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": [ "*.vue" ],
|
||||
"rules": {
|
||||
"no-new": "off",
|
||||
"camelcase": "off",
|
||||
"no-return-assign": "off",
|
||||
"space-before-function-paren": ["error", "never"],
|
||||
"no-var": "error",
|
||||
"no-fallthrough": "off",
|
||||
"prefer-promise-reject-errors": "off",
|
||||
"eqeqeq": "off",
|
||||
"no-multiple-empty-lines": [1, {"max": 2}],
|
||||
"comma-dangle": [2, "always-multiline"],
|
||||
"standard/no-callback-literal": "off",
|
||||
"prefer-const": "off",
|
||||
"no-labels": "off",
|
||||
"node/no-callback-literal": "off",
|
||||
"vue/multi-word-component-names": "off",
|
||||
"@typescript-eslint/strict-boolean-expressions": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/space-before-function-paren": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/naming-convention": "off",
|
||||
"vue/max-attributes-per-line": "off",
|
||||
"vue/singleline-html-element-content-newline": "off",
|
||||
"vue/use-v-on-exact": "off",
|
||||
"@typescript-eslint/restrict-template-expressions": "off",
|
||||
// "no-undef": "off"
|
||||
},
|
||||
"parser": "vue-eslint-parser",
|
||||
"extends": [
|
||||
"plugin:vue/base",
|
||||
// "plugin:vue/strongly-recommended"
|
||||
"plugin:vue/vue3-recommended",
|
||||
"standard-with-typescript",
|
||||
],
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"parser": {
|
||||
// Script parser for `<script>`
|
||||
"js": "@typescript-eslint/parser",
|
||||
|
||||
// Script parser for `<script lang="ts">`
|
||||
"ts": "@typescript-eslint/parser"
|
||||
},
|
||||
"extraFileExtensions": [".vue"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [ "*.ts" ],
|
||||
"rules": {
|
||||
"no-new": "off",
|
||||
"camelcase": "off",
|
||||
"no-return-assign": "off",
|
||||
"space-before-function-paren": ["error", "never"],
|
||||
"no-var": "error",
|
||||
"no-fallthrough": "off",
|
||||
"prefer-promise-reject-errors": "off",
|
||||
"eqeqeq": "off",
|
||||
"no-multiple-empty-lines": [1, {"max": 2}],
|
||||
"comma-dangle": [2, "always-multiline"],
|
||||
"standard/no-callback-literal": "off",
|
||||
"prefer-const": "off",
|
||||
"no-labels": "off",
|
||||
"node/no-callback-literal": "off",
|
||||
"@typescript-eslint/strict-boolean-expressions": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/space-before-function-paren": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/restrict-template-expressions": [1, {
|
||||
"allowBoolean": true
|
||||
}],
|
||||
"@typescript-eslint/naming-convention": "off",
|
||||
"@typescript-eslint/return-await": "off",
|
||||
"multiline-ternary": "off",
|
||||
"@typescript-eslint/comma-dangle": "off",
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"extends": [
|
||||
"standard-with-typescript"
|
||||
],
|
||||
"parserOptions": {
|
||||
"project": "./src/**/tsconfig.json"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
25
.github/ISSUE_TEMPLATE/----.md
vendored
@@ -1,25 +0,0 @@
|
||||
---
|
||||
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=>)
|
||||
|
||||
**描述您想要的解决方案**
|
||||
<!-- 简洁明了地描述您要发生的事情。 -->
|
||||
|
||||
|
||||
**描述您考虑过的替代方案**
|
||||
<!-- 对您考虑过的所有替代解决方案或功能的简洁明了的描述。 -->
|
||||
|
||||
|
||||
**其他内容**
|
||||
<!-- 在此处添加有关功能请求的任何其他上下文或屏幕截图(直接把图片拖到编辑框即可添加图片)。 -->
|
||||
|
||||
40
.github/ISSUE_TEMPLATE/----.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
name: ✨功能请求
|
||||
description: 为这个项目提出一个想法,请先查看常见问题及搜索issue列表中有无你要提的问题
|
||||
title: "[Feature]: "
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: check-answer
|
||||
attributes:
|
||||
label: 解决方案检查
|
||||
description: 请确保你已完成以下所有操作
|
||||
options:
|
||||
- label: 我已阅读常见问题(<https://lyswhut.github.io/lx-music-doc/desktop/faq>),但没有找到解决方案
|
||||
required: true
|
||||
- label: 我已搜索issue列表(<https://github.com/lyswhut/lx-music-desktop/issues?utf8=✓&q=>),但没有发现类似的问题
|
||||
required: true
|
||||
- type: textarea
|
||||
id: problem-description
|
||||
attributes:
|
||||
label: 问题描述
|
||||
description: 请添加清晰简洁的描述,说明你希望通过此功能请求解决的问题
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: proposed-solution
|
||||
attributes:
|
||||
label: 描述你想要的解决方案
|
||||
description: 简洁明了地描述你要发生的事情
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: alternatives-considered
|
||||
attributes:
|
||||
label: 描述你考虑过的替代方案
|
||||
description: 对你考虑过的所有替代解决方案或功能的简洁明了的描述
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: additional-information
|
||||
attributes:
|
||||
label: 附加信息
|
||||
description: 如果你的问题需要进一步解释,或者想要表达其他内容,请在此处添加更多信息。(直接把图片、视频拖到编辑框即可添加图片或视频)
|
||||
42
.github/ISSUE_TEMPLATE/--bug.md
vendored
@@ -1,42 +0,0 @@
|
||||
---
|
||||
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 安装版]
|
||||
|
||||
|
||||
**其他内容**
|
||||
<!-- 在此处添加有关该问题的任何其他上下文。 -->
|
||||
|
||||
55
.github/ISSUE_TEMPLATE/--bug.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
name: 🐞报告Bug
|
||||
description: 报告bug,请先查看常见问题及搜索issue列表中有无你要提的问题
|
||||
title: "[Bug]: "
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: check-answer
|
||||
attributes:
|
||||
label: 解决方案检查
|
||||
description: 请确保你已完成以下所有操作
|
||||
options:
|
||||
- label: 我已阅读常见问题(<https://lyswhut.github.io/lx-music-doc/desktop/faq>),并没有找到解决方案
|
||||
required: true
|
||||
- label: 我已搜索issue列表(<https://github.com/lyswhut/lx-music-desktop/issues?utf8=✓&q=>),并没有发现类似的问题
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected-behavior
|
||||
attributes:
|
||||
label: 预期行为
|
||||
description: 对期望发生的事情的清晰简明描述
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: actual-behavior
|
||||
attributes:
|
||||
label: 实际行为
|
||||
description: 对实际发生的事情的清晰简明描述
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Lx Music 版本
|
||||
description: 你使用什么版本的LX Music?
|
||||
placeholder: 1.15.0
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: last-known-working-version
|
||||
attributes:
|
||||
label: 最后正常的版本
|
||||
description: 如果有,请在此处填写最后正常的版本是多少?
|
||||
placeholder: 1.15.0
|
||||
- type: input
|
||||
id: operating-system-version
|
||||
attributes:
|
||||
label: 操作系统版本
|
||||
description: 您使用的是什么操作系统版本?在 Windows 上,单击开始按钮 > 设置 > 系统 > 关于;在 macOS 上,单击 Apple 菜单 > 关于本机;在 Linux 上,使用 lsb_release 或 uname -a
|
||||
placeholder: "例如 Windows 10 版本 1909、macOS Catalina 10.15.7 或 Ubuntu 20.04"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: additional-information
|
||||
attributes:
|
||||
label: 附加信息
|
||||
description: 如果你的问题需要进一步解释,或者你所遇到的问题不容易重现,请在此处添加更多信息。(直接把图片、视频拖到编辑框即可添加图片或视频)
|
||||
126
.github/workflows/beta-pack.yml
vendored
@@ -11,15 +11,15 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '14.x'
|
||||
node-version: '16'
|
||||
|
||||
- name: Cache file
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
@@ -31,7 +31,8 @@ jobs:
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
run: |
|
||||
npm install
|
||||
|
||||
- name: Build src code
|
||||
run: npm run build:src
|
||||
@@ -39,65 +40,78 @@ jobs:
|
||||
- name: Build Package Setup x64
|
||||
run: npm run pack:win:setup:x64
|
||||
- name: Upload Artifact Setup x64
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
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 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
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: lx-music-desktop-win_x64-green
|
||||
path: build/*win_x64 green.7z
|
||||
|
||||
- name: Build Package Setup x86
|
||||
run: npm run pack:win:setup:x86
|
||||
- name: Upload Artifact Setup x86
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: lx-music-desktop-x86-Setup
|
||||
path: build/* x86 Setup.exe
|
||||
|
||||
- name: Build Package 7z x86
|
||||
run: npm run pack:win:7z:x86
|
||||
- name: Upload Artifact 7z x86
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: lx-music-desktop-win_x86-green
|
||||
path: build/*win_x86 green.7z
|
||||
|
||||
- name: Build Package Setup arm64
|
||||
run: npm run pack:win:setup:arm64
|
||||
- name: Upload Artifact Setup arm64
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: lx-music-desktop-arm64-Setup
|
||||
path: build/* arm64 Setup.exe
|
||||
|
||||
- name: Build Package 7z arm64
|
||||
run: npm run pack:win:7z:arm64
|
||||
- name: Upload Artifact 7z arm64
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: lx-music-desktop-win_arm64-green
|
||||
path: build/*win_arm64 green.7z
|
||||
|
||||
- name: Build Package Setup x86_64
|
||||
run: npm run pack:win:setup:x86_64
|
||||
- name: Upload Artifact Setup x86_64
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: lx-music-desktop-x86_64-Setup
|
||||
path: build/*x86_64 Setup.exe
|
||||
|
||||
- name: Generate file MD5
|
||||
run: |
|
||||
cd build
|
||||
Get-FileHash *.exe,*.7z -Algorithm MD5 | Format-List
|
||||
|
||||
Mac:
|
||||
name: Mac
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '14.x'
|
||||
node-version: '16'
|
||||
|
||||
- name: Cache file
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
@@ -109,49 +123,55 @@ jobs:
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
run: |
|
||||
npm install
|
||||
|
||||
- name: Build src code
|
||||
run: npm run build:src
|
||||
|
||||
- name: Build Package dmg
|
||||
run: |
|
||||
npm run publish:mac:dmg
|
||||
npm run publish:mac:dmg:arm64
|
||||
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
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: lx-music-desktop-mac-dmg
|
||||
path: |
|
||||
build/*.dmg
|
||||
!build/*-arm64.dmg
|
||||
- name: Upload Artifact dmg
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: lx-music-desktop-mac-dmg-arm64
|
||||
path: build/*-arm64.dmg
|
||||
|
||||
- name: Generate file MD5
|
||||
run: |
|
||||
cd build
|
||||
md5 *.dmg
|
||||
|
||||
Linux:
|
||||
name: Linux
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install package
|
||||
run: sudo apt-get install -y rpm libarchive-tools
|
||||
run: sudo apt-get update && sudo apt-get install -y rpm libarchive-tools
|
||||
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '14.x'
|
||||
node-version: '16'
|
||||
|
||||
- name: Cache file
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
@@ -163,7 +183,8 @@ jobs:
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
run: |
|
||||
npm install
|
||||
|
||||
- name: Build src code
|
||||
run: npm run build:src
|
||||
@@ -171,23 +192,15 @@ jobs:
|
||||
- name: Build Package deb x64
|
||||
run: npm run pack:linux:deb:x64
|
||||
- name: Upload Artifact deb x64
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
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
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: lx-music-desktop-deb-arm64
|
||||
path: build/* arm64.deb
|
||||
@@ -195,7 +208,7 @@ jobs:
|
||||
- name: Build Package deb armv7l
|
||||
run: npm run pack:linux:deb:armv7l
|
||||
- name: Upload Artifact deb armv7l
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: lx-music-desktop-deb-armv7l
|
||||
path: build/* armv7l.deb
|
||||
@@ -203,7 +216,7 @@ jobs:
|
||||
- name: Build Package x64 appImage
|
||||
run: npm run pack:linux:appImage
|
||||
- name: Upload Artifact x64 appImage
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: lx-music-desktop-x64-appImage
|
||||
path: build/* x64.AppImage
|
||||
@@ -211,7 +224,7 @@ jobs:
|
||||
- name: Build Package x64 rpm
|
||||
run: npm run pack:linux:rpm
|
||||
- name: Upload Artifact x64 rpm
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: lx-music-desktop-x64-rpm
|
||||
path: build/* x64.rpm
|
||||
@@ -219,7 +232,12 @@ jobs:
|
||||
- name: Build Package x64 pacman
|
||||
run: npm run pack:linux:pacman
|
||||
- name: Upload Artifact x64 pacman
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: lx-music-desktop-x64-pacman
|
||||
path: build/* x64.pacman
|
||||
|
||||
- name: Generate file MD5
|
||||
run: |
|
||||
cd build
|
||||
md5sum *.deb *.rpm *.pacman *.AppImage
|
||||
|
||||
56
.github/workflows/release.yml
vendored
@@ -11,15 +11,15 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '14.x'
|
||||
node-version: '16'
|
||||
|
||||
- name: Cache file
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
@@ -31,7 +31,8 @@ jobs:
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
run: |
|
||||
npm install
|
||||
|
||||
- name: Build src code
|
||||
run: npm run build:src
|
||||
@@ -39,29 +40,35 @@ jobs:
|
||||
- name: Release package
|
||||
run: |
|
||||
npm run publish:win:setup:x64:always
|
||||
npm run publish:win:setup:x86
|
||||
npm run publish:win:setup:x86_64
|
||||
npm run publish:win:7z:x64
|
||||
npm run publish:win:setup:x86
|
||||
npm run publish:win:7z:x86
|
||||
npm run publish:win:setup:arm64
|
||||
npm run publish:win:7z:arm64
|
||||
npm run publish:win:setup:x86_64
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BT_TOKEN: ${{ secrets.BT_TOKEN }}
|
||||
|
||||
- name: Generate file MD5
|
||||
run: |
|
||||
cd build
|
||||
Get-FileHash *.exe,*.7z -Algorithm MD5 | Format-List
|
||||
|
||||
Mac:
|
||||
name: Mac
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '14.x'
|
||||
node-version: '16'
|
||||
|
||||
- name: Cache file
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
@@ -73,7 +80,8 @@ jobs:
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
run: |
|
||||
npm install
|
||||
|
||||
- name: Build src code
|
||||
run: npm run build:src
|
||||
@@ -88,23 +96,28 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BT_TOKEN: ${{ secrets.BT_TOKEN }}
|
||||
|
||||
- name: Generate file MD5
|
||||
run: |
|
||||
cd build
|
||||
md5 *.dmg
|
||||
|
||||
Linux:
|
||||
name: Linux
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install package
|
||||
run: sudo apt-get install -y rpm libarchive-tools
|
||||
run: sudo apt-get update && sudo apt-get install -y rpm libarchive-tools
|
||||
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '14.x'
|
||||
node-version: '16'
|
||||
|
||||
- name: Cache file
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
@@ -116,7 +129,8 @@ jobs:
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
run: |
|
||||
npm install
|
||||
|
||||
- name: Build src code
|
||||
run: npm run build:src
|
||||
@@ -124,7 +138,6 @@ jobs:
|
||||
- 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
|
||||
@@ -133,3 +146,8 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BT_TOKEN: ${{ secrets.BT_TOKEN }}
|
||||
|
||||
- name: Generate file MD5
|
||||
run: |
|
||||
cd build
|
||||
md5sum *.deb *.rpm *.pacman *.AppImage
|
||||
|
||||
19
.ncurc.js
Normal file
@@ -0,0 +1,19 @@
|
||||
module.exports = {
|
||||
upgrade: true,
|
||||
reject: [
|
||||
'electron',
|
||||
'chalk',
|
||||
'del',
|
||||
],
|
||||
// target: 'newest',
|
||||
// filter: [
|
||||
// /^vue/,
|
||||
// 'electron-builder',
|
||||
// 'electron-updater',
|
||||
// ],
|
||||
|
||||
// target: 'minor',
|
||||
// filter: [
|
||||
// 'electron',
|
||||
// ],
|
||||
}
|
||||
29
.vscode/i18n-ally-custom-framework.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# .vscode/i18n-ally-custom-framework.yml
|
||||
|
||||
# An array of strings which contain Language Ids defined by VS Code
|
||||
# You can check avaliable language ids here: https://code.visualstudio.com/docs/languages/overview#_language-id
|
||||
languageIds:
|
||||
- javascript
|
||||
- typescript
|
||||
- vue
|
||||
|
||||
# An array of RegExes to find the key usage. **The key should be captured in the first match group**.
|
||||
# You should unescape RegEx strings in order to fit in the YAML file
|
||||
# To help with this, you can use https://www.freeformatter.com/json-escape.html
|
||||
usageMatchRegex:
|
||||
# The following example shows how to detect `t("your.i18n.keys")`
|
||||
# the `{key}` will be placed by a proper keypath matching regex,
|
||||
# you can ignore it and use your own matching rules as well
|
||||
- "[^\\w\\d]t\\(['\"`]({key})['\"`]"
|
||||
|
||||
|
||||
# An array of strings containing refactor templates.
|
||||
# The "$1" will be replaced by the keypath specified.
|
||||
# Optional: uncomment the following two lines to use
|
||||
|
||||
# refactorTemplates:
|
||||
# - i18n.get("$1")
|
||||
|
||||
|
||||
# If set to true, only enables this custom framework (will disable all built-in frameworks)
|
||||
monopoly: true
|
||||
37
.vscode/javascript.code-snippets
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
// Place your lx-music-desktop-new 工作区 snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
|
||||
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
|
||||
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
|
||||
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
|
||||
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
|
||||
// Placeholders with the same ids are connected.
|
||||
// Example:
|
||||
// "Print to console": {
|
||||
// "scope": "javascript,typescript",
|
||||
// "prefix": "log",
|
||||
// "body": [
|
||||
// "console.log('$1');",
|
||||
// "$2"
|
||||
// ],
|
||||
// "description": "Log output to console"
|
||||
// }
|
||||
"use i18n": {
|
||||
"prefix": "ui18n",
|
||||
"body": [
|
||||
"import { useI18n } from '@renderer/plugins/i18n'",
|
||||
"${1:const t = useI18n()}"
|
||||
]
|
||||
},
|
||||
"list action": {
|
||||
"prefix": "listacion",
|
||||
"body": [
|
||||
"import { $1 } from '@renderer/store/list/action'",
|
||||
]
|
||||
},
|
||||
"import vue tools": {
|
||||
"prefix": "imvt",
|
||||
"body": [
|
||||
"import { $1 } from '@common/utils/vueTools'",
|
||||
]
|
||||
}
|
||||
}
|
||||
21
.vscode/settings.json
vendored
@@ -1,9 +1,16 @@
|
||||
{
|
||||
"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/lang"
|
||||
],
|
||||
// "i18n-ally.fullReloadOnChanged": true,
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"i18n-ally.displayLanguage": "zh-cn",
|
||||
"i18n-ally.sourceLanguage": "zh-cn",
|
||||
"i18n-ally.translate.engines": [
|
||||
"google-cn",
|
||||
"google"
|
||||
],
|
||||
"i18n-ally.sortKeys": true,
|
||||
"javascript.preferences.importModuleSpecifier": "non-relative",
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
|
||||
31
.vscode/typescript.code-snippets
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
// Place your lx-music-desktop-new 工作区 snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
|
||||
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
|
||||
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
|
||||
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
|
||||
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
|
||||
// Placeholders with the same ids are connected.
|
||||
// Example:
|
||||
// "Print to console": {
|
||||
// "scope": "javascript,typescript",
|
||||
// "prefix": "log",
|
||||
// "body": [
|
||||
// "console.log('$1');",
|
||||
// "$2"
|
||||
// ],
|
||||
// "description": "Log output to console"
|
||||
// }
|
||||
"use i18n": {
|
||||
"prefix": "ui18n",
|
||||
"body": [
|
||||
"import { useI18n } from '@renderer/plugins/i18n'",
|
||||
"${1:const t = useI18n()}"
|
||||
]
|
||||
},
|
||||
"import vue tools": {
|
||||
"prefix": "imvt",
|
||||
"body": [
|
||||
"import { $1 } from '@common/utils/vueTools'",
|
||||
]
|
||||
}
|
||||
}
|
||||
521
CHANGELOG.md
@@ -6,6 +6,527 @@ Project versioning adheres to [Semantic Versioning](http://semver.org/).
|
||||
Commit convention is based on [Conventional Commits](http://conventionalcommits.org).
|
||||
Change log format is based on [Keep a Changelog](http://keepachangelog.com/).
|
||||
|
||||
## [2.0.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.22.3...v2.0.0) - 2023-01-01
|
||||
|
||||
|
||||
### 不兼容性变更说明
|
||||
|
||||
- 数据迁移,升级此版本时,会使用旧版本的我的列表、下载设置、快捷键设置、自定义源等数据会自动迁移到新的数据格式版本,旧的数据仍然会保留,但下载列表的数据不做迁移
|
||||
- 备份文件,v2.0.0及以后版本导出的列表、配置不支持导入v2.0.0之前版本,但v2.0.0之前版本导出的列表、配置支持导入v2.0.0以及以后版本(移动端需v0.15.0起才支持导入PC端v2生成的备份数据)
|
||||
- 同步功能,该功能不支持与移动端v1.0.0之前版本的使用,需等待后面的新版移动端,目前移动端v1的开发工作已在进行中
|
||||
|
||||
### 新增
|
||||
|
||||
- 新增自定义主题功能
|
||||
- 新增歌单搜索功能
|
||||
- 新增将本地歌曲添加到我的列表的支持,此功能可以在列表的右击菜单中使用(本地歌曲的歌词优先尝试读取相同路径下的同名歌词文件,若文件不存在则尝试读取歌曲文件内的歌词,若还是找不到歌词则尝试利用换源功能获取在线歌词,歌曲封面则是尝试读取歌曲文件内的封面,若不存在则利用换源功能获取在线封面)
|
||||
- 启动软件时自动回到上次的界面,例如上次退出软件时在我的列表,下次启动软件时会自动进入我的列表
|
||||
- 新增启动软件时自动播放音乐设置,默认关闭,可去设置-播放设置开启
|
||||
- 新增“蛋雅深藍”、“近墨者黑”皮肤
|
||||
- 新增下载歌词时是否同时下载歌词翻译、罗马音设置,默认关闭,可以去设置-下载设置开启(#344)
|
||||
- 新增下载时,若目录存在同名的文件时是否跳过下载此任务的设置(默认跳过,可以去设置-下载设置更改)
|
||||
- 新增界面字体大小设置
|
||||
- 桌面歌词新增竖排歌词显示功能(#971)
|
||||
- 桌面歌词新增歌词对齐方式、是否不允许歌词换行、歌词颜色、滚动对齐方式、歌词间距设置
|
||||
- 桌面歌词新增歌曲频谱显示(得益于主窗口与桌面歌词进程通信的改进,可以将此功能以CPU使用率“相对较低”的方式带到桌面歌词中)
|
||||
- 桌面歌词新增在任务栏显示歌词进程设置(此设置用于在录屏软件无法捕获歌词窗口时的变通解决方法)(#1063)
|
||||
- 添加kg源罗马音歌词的支持(感谢@helloplhm-qwq)
|
||||
- 支持打开波点音乐歌单(需在酷我源打开)
|
||||
- 新增设置-基本设置-播放栏进度条样式设置(此版本默认使用迷你进度条样式,对于某些不喜欢该样式的人可以将其换成其他样式)
|
||||
- 添加kg源评论图片展示(感谢@helloplhm-qwq)
|
||||
|
||||
### 优化(界面/交互/功能)
|
||||
|
||||
- 调整软件界面及配色,使其更加清爽
|
||||
- 处于单曲循环、顺序播放、禁用切歌模式时,手动切歌将会按列表循环模式的逻辑处理切歌(#864)
|
||||
- 歌单右键菜单的“重复歌曲”扫描功能现在会将歌曲名字内的括号内容移除再对比,这可以有效找出歌曲的变体,例如:`突然的自我`、`突然的自我(Live)`、`突然的自我(女生版)`、`突然的自我(DJ版)`等都会被找出来(#987)
|
||||
- 允许更小的桌面歌词窗口高度,可以取消“不允许拖动到主屏幕之外”设置后,再启用“不允许歌词换行”、“置顶歌词”与“自动刷新置顶”等设置,把它拖动到任务栏上,当做任务栏歌词使用(具体可以按你想要的显示方式使用这些设置组合去调)
|
||||
|
||||
### 优化(程序)
|
||||
|
||||
- 优化程序启动性能,优化与程序交互的流畅度
|
||||
- 重构整个程序,重新梳理了程序逻辑,使其更容易扩展及维护,将大部分代码从JavaScript迁移到TypeScript
|
||||
- 重写配置管理、列表管理功能,列表、歌词数据从json文件迁移到sqlite3存储,这应该能解决因为意外的字符编码导致的数据文件损坏问题
|
||||
|
||||
### 变更
|
||||
|
||||
- 列表右侧的操作按钮栏默认不再显示,歌曲的操作可以使用右键菜单代替,若想恢复它们的显示,可以去设置-列表设置-启用操作按钮栏开启
|
||||
- 窗口大小设置时不再自动调整字体大小,想要调整字体大小可以使用新增的字体大小设置调整
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复Linux、macOS下若程序路径存在百分号时会导致软件无法启动的问题(#963)
|
||||
- 支持单行多时间标签歌词解析,修复某些歌词会出现时间标签的问题
|
||||
|
||||
### 移除
|
||||
|
||||
- 移除“信口雌黄”皮肤(由于该皮肤的配色有点刺眼),若你正在使用该皮肤,可以使用自定义主题功能恢复它
|
||||
- 移除Linux deb x86包构建,Electron/Chromium已不再支持 32-bit Linux(electron/electron#34787)
|
||||
- 移除桌面歌词主题设置,改用桌面歌词字体颜色设置功能代替
|
||||
|
||||
### 其他
|
||||
|
||||
- 更新Electron到v19.1.9
|
||||
|
||||
## [1.22.3](https://github.com/lyswhut/lx-music-desktop/compare/v1.22.2...v1.22.3) - 2022-09-03
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复因音源的域名到期导致的音源失效的问题
|
||||
|
||||
## [1.22.2](https://github.com/lyswhut/lx-music-desktop/compare/v1.22.1...v1.22.2) - 2022-08-18
|
||||
|
||||
### 优化
|
||||
|
||||
- 为tx、kw源添加 Flac 24bit 音质显示,注:由于之前没有记录此音质,所以之前收藏的歌曲信息中不包含它
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复无法批量排序歌曲的问题
|
||||
- 修复某些缺失的繁体中文翻译
|
||||
- 修复企鹅音乐搜索失效的问题
|
||||
|
||||
### 其他
|
||||
|
||||
- 降级electron到v15.5.7
|
||||
|
||||
## [1.22.1](https://github.com/lyswhut/lx-music-desktop/compare/v1.22.0...v1.22.1) - 2022-07-09
|
||||
|
||||
### 优化
|
||||
|
||||
- 歌单列表添加歌单内歌曲数量显示,注:目前只有kw、mg、wy、tx(部分)源支持显示
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复处于不支持的源时,歌单、排行榜的右键下载菜单没有禁用的问题
|
||||
- 修复若桌面歌词窗口与主窗口重叠时,鼠标划过重叠区域鼠标会闪烁的问题,注:此修复只对未启用“鼠标移入歌词区域时降低歌词透明度”时有效
|
||||
- 修复tx源搜索失效的问题
|
||||
|
||||
### 其他
|
||||
|
||||
- 升级Electron到 v17.4.10
|
||||
|
||||
## [1.22.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.21.0...v1.22.0) - 2022-06-19
|
||||
|
||||
### 新增
|
||||
|
||||
- 新增设置-以全屏模式启动设置
|
||||
- 新增设置-桌面歌词设置-鼠标移入歌词区域时降低歌词透明度(#883),默认关闭,此设置不支持linux,注:此功能存在兼容性问题,若鼠标移出后无法恢复到正常透明度,可尝试再移入移出即可恢复
|
||||
|
||||
### 优化
|
||||
|
||||
- 添加歌曲到“我的列表”时,若按住`ctrl`键(Mac对应`Command`),则不会自动关闭添加窗口,这对想要将同一首(一批)歌曲添加到多个列表时会很有用
|
||||
- 支持mg源逐字歌词的播放,感谢 @mozbugbox 提供的帮助
|
||||
- 添加歌曲列表更新操作的二次确认
|
||||
- 添加导入文件错误时的指引提示
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复若配置了`http_proxy`环境变量时,会意外使用此代理配置的问题
|
||||
- 修复多选后切换列表后不会清空多选内容的问题
|
||||
- 修复设置快捷键时的处理逻辑问题
|
||||
- 修复在新建歌单输入框、歌单内歌曲搜索输入框会意外触发设置的全局快捷键的问题(#879)
|
||||
|
||||
### 文档
|
||||
|
||||
桌面版文档已迁移到:<https://lyswhut.github.io/lx-music-doc/desktop>
|
||||
|
||||
### 其他
|
||||
|
||||
- 更新 Electron 到 v17.4.7
|
||||
|
||||
## [1.21.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.20.0...v1.21.0) - 2022-05-22
|
||||
|
||||
### 新增
|
||||
|
||||
- 新增设置-播放设置-显示歌词罗马音,默认关闭,注:目前只有网易源能获取到罗马音歌词(得益于 Binaryify/NeteaseCloudMusicApi/pull/1523),如果你知道其他源的歌词罗马音获取方式,欢迎PR或开issue交流!
|
||||
|
||||
### 优化
|
||||
|
||||
- 同时删除一首歌以上时将需要二次确认删除
|
||||
- 禁用透明窗口时右侧不再偏移5px距离(在win7、Ubuntu等系统上测试发现不偏移也不影响滚动条的拖动了)
|
||||
- 删除未下载完成的任务时,只同时尝试删除已有下载进度的本地文件
|
||||
- 在全屏状态下使用`Esc`键可以退出全屏(#827)
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复某些情况下歌曲播放出错时不会自动切歌的问题
|
||||
- 修复关闭“显示切换动画”设置后,在应用启动时该设置没有被应用的问题
|
||||
- 修复原始歌词存在偏移时,歌词偏移设置的重置未按预期工作的问题
|
||||
- 修复长度大于一行的歌词在使用歌词调整播放进度时的时间不准问题
|
||||
- 修复潜在歌单更新失败的问题
|
||||
|
||||
### 文档
|
||||
|
||||
- 将歌曲添加“稍后播放”后,它们会被放在一个优先级最高的特殊队列中,点击“下一曲”时会消耗该队列中的歌曲,并且无法通过“上一曲”功能播放该队列的上一首歌曲
|
||||
- 在切歌时若不是通过“上一曲”、“下一曲”功能切歌(例如直接点击“排行榜列表”、“我的列表”中的歌曲切歌),“稍后播放”队列将会被清空
|
||||
|
||||
## [1.20.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.19.0...v1.20.0) - 2022-04-17
|
||||
|
||||
特别说明:Scheme URL其实是支持Linux系统的,但好像需要deb之类的安装包创建出`.desktop`文件才行。
|
||||
|
||||
### 新增
|
||||
|
||||
- 新增播放详情页歌词右键菜单,原来设置-播放详情页设置的字体重置已迁移到此菜单内
|
||||
- 新增歌词偏移设置,可以在播放详情页歌词右键菜单中使用
|
||||
- 新增设置-播放设置-播放错误时自动切换歌曲设置,默认开启(原来的行为),若你不想在遇到音频加载失败、url获取失败等错误时自动切歌可以关闭此设置
|
||||
- 新增设置-桌面歌词设置-自动刷新歌词置顶(当歌词置顶后仍被某些程序遮挡时可尝试启用此设置)
|
||||
- 新增列表更新管理,可以在鼠标移入“我的列表”标题时出现的按钮中进入,这可以用来设置启动软件时需要自动从原平台更新的列表
|
||||
|
||||
### 优化
|
||||
|
||||
- 优化播放详情页背景显示,现在有背景图片的主题可以在播放详情页显示它的图片了
|
||||
- 播放详情页在全屏状态下仍会显示退出播放详情页按钮,同时在其旁边添加退出全屏按钮
|
||||
- 播放详情页在全屏状态下鼠标在空白处静止不动3秒后自动将其隐藏
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复Linux无法全屏的问题
|
||||
- 修复播放下载列表的歌曲时,使用Windows任务栏缩略图工具栏控制按钮的收藏按钮收藏歌曲时的异常问题
|
||||
- 修复启用搜索历史但不启用热门搜索时,搜索历史不显示的问题
|
||||
- 修复窗口尺寸设置对应的字体大小在启动后不生效的问题
|
||||
- 修复wy源搜索某些歌曲时第一页之后的歌曲无法加载的问题
|
||||
- 修复使用Scheme URL搜索歌曲时,不会自动关闭播放详情页(若处于打开状态)的问题
|
||||
- 修复换源失败时的处理问题
|
||||
- 修复启用代理时,https请求可能被挂起或被转为http的问题
|
||||
- 修复正在下载的歌曲暂停任务后,再开始会导致程序卡死的问题
|
||||
|
||||
### 变更
|
||||
|
||||
- 播放详情页的任意地方右键双击隐藏详情页的行为,“任意区域”改为在“非歌词区域”
|
||||
|
||||
### 移除
|
||||
|
||||
- 移除设置-播放详情页设置-歌词字体重置,此设置项已迁移到播放详情页的歌词菜单中
|
||||
- 移除播放详情页使用+-快捷键调整字体大小的功能,改用歌词右键菜单的字体大小调整功能
|
||||
|
||||
## [1.19.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.18.0...v1.19.0) - 2022-03-20
|
||||
|
||||
### 新增
|
||||
|
||||
- 新增对播放详情页歌词大小、是否缩放、对齐方式的设置,可以去设置-播放详情页设置查看
|
||||
- 新增播放详情页通过歌词调整播放进度,默认关闭,需要到设置-播放详情页设置开启,开启后在播放详情页拖动歌词时将会出现跳转当前行歌词播放的按钮
|
||||
- 新增全屏状态,按F11可以进入、退出全屏状态,由于全屏时会隐藏控制栏按钮,所以需要使用鼠标右键双击(详情页的任意地方都可以)来关闭播放详情页
|
||||
- 新增动态主题“道法自然”,你可以预先设置一个亮色主题及暗色主题,此后将根据系统的亮、暗主题色自动切换为你预先设置的相应主题。注:鼠标 右击 此主题项即可打开亮、暗色主题设置窗口。
|
||||
- 新增对kw源卡拉OK歌词的支持
|
||||
|
||||
### 优化
|
||||
|
||||
- 优化Windows任务栏缩略图工具栏控制按钮在浅色任务栏下的显示效果
|
||||
- 添加音频可视化与音频输出设备冲突的提示
|
||||
- 优化歌词的播放偏移
|
||||
- 优化托盘菜单操作(#686)
|
||||
- 优化播放下载列表时的切歌性能
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复“当前的声音输出设备被改变时暂停播放歌曲”设置无效的问题
|
||||
- 修复桌面歌词没有处理停止播放状态的问题
|
||||
- 修复AppImage包无法运行的问题
|
||||
- 修复Windows任务栏缩略图工具栏控制按钮的歌曲收藏按钮状态更新问题
|
||||
- 修复使用链接导入的歌单无法在我的列表打开原歌单详情页的问题
|
||||
- 修复播放下载列表的歌曲时增删下载任务导致正在的歌曲序号改变时,不会更新到新增序号的问题
|
||||
|
||||
### 文档
|
||||
|
||||
添加LX中定义的快捷操作汇总说明到常见问题中,这是目前可用的鼠标、键盘快捷操作,它们都可以在更新日志中找到
|
||||
|
||||
- 鼠标右击播放栏的歌曲图片封面可以定位当前播放的歌曲
|
||||
- 鼠标右击播放栏进度条上的LRC按钮可以锁定/解锁桌面歌词
|
||||
- 歌曲搜索框、歌单链接输入框内鼠标右击可以将当前剪贴板上的文字粘贴到输入框内
|
||||
- 鼠标右击搜索界面中的单条搜索历史可以将其移除
|
||||
- 歌曲列表内的文字在选中后,鼠标右击可以复制已选中的文字,此功能只对搜索、歌单、排行榜、我的列表中的列表有效
|
||||
- 鼠标在播放详情页内右键双击可以关闭播放详情页
|
||||
- 鼠标左击播放栏上的歌曲名字可以将它复制
|
||||
- 鼠标右击“道法自然(英文Auto)”主题可以打开亮、暗主题设置窗口
|
||||
- 歌曲搜索框的候选内容可以用键盘上下方向键选择,按回车键搜索已选内容
|
||||
- 在歌单详情页按退格键可以返回歌单列表
|
||||
- 歌曲列表中可以使用Ctrl、Shift键进行多选,这类似Windows下的文件选择,详情看常见问题列表多选部分
|
||||
- 在我的列表内可以使用Ctrl+f键打开搜索框进行列表内歌曲搜索,搜索框按Esc键可以关闭搜索框,搜索框内按上下方向键可以选择歌曲,按回车键跳转到已选歌曲,按Ctrl+回车可以跳转并播放已选歌曲
|
||||
- 在我的列表按住Ctrl键可以进入列表拖动模式,此时可以用鼠标拖动列表调整列表的位置
|
||||
- 编辑列表名时按Esc键可以取消编辑
|
||||
- 按F11可以进入、退出全屏状态
|
||||
|
||||
## [1.18.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.17.1...v1.18.0) - 2022-02-26
|
||||
|
||||
### 新增
|
||||
|
||||
- 新增“双击列表里的歌曲时自动切换到当前列表播放”设置,此功能仅对歌单、排行榜有效,默认关闭
|
||||
- 新增打开收藏的在线列表的对应平台详情页功能,可以在我的列表-列表右键菜单中使用
|
||||
- 新增定时暂停播放功能,由于此功能大多数人可能不常用,所以将其放在设置-基本设置中
|
||||
- 新增任务栏缩略图工具栏控制按钮(此功能仅在Windows平台可用),按钮分别为收藏/取消收藏(将歌曲添加到“我的收藏”列表)、上一曲、播放/暂停、下一曲
|
||||
- 新增设置-基本设置-软件字体设置,此设置可用于设置主界面的字体(已知的问题:Windows 7 下可能会出现字体列表为空的情况,这是当前系统的 Powershell 版本小于5.1导致的,请自行尝试看常见解决)
|
||||
- 新增Scheme URL对音乐搜索的调用支持,详情看常见问题-Scheme URL支持
|
||||
- 新增Scheme URL以url传参的方式调用,详情看常见问题-Scheme URL支持
|
||||
- 自定义源新增更新弹窗方法,同时自定义源管理新增是否允许源显示更新弹窗设置(出于防止滥用考虑),当源作者想要通知用户源已更新时,可以调用此方法弹窗告诉用户,调用说明看常见问题-自定义源部分
|
||||
|
||||
### 优化
|
||||
|
||||
- 过滤tx源某些不支持播放的歌曲,解决播放此类内容会导致意外的问题
|
||||
- 把歌曲的热门评论与最新评论拆分成两个列表显示
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复排行榜名字右击菜单的播放功能在播放非激活的列表时的列表获取问题
|
||||
- 修复修改列表名时无法使用`Ctrl`键的问题
|
||||
- 修复wy源某些歌曲获取歌词翻译的问题处理
|
||||
- 修复下载功能的歌词换源时会进入死循环的问题
|
||||
- 修复某些歌曲无法下载的问题
|
||||
- 修复windows平台下软件目录存在`portable`文件夹时,仍会创建`C:\Users\<user>\AppData\Roaming\lx-music-desktop\Dictionaries\en-US-9-0.bdic`文件的问题,现在不会再创建文件,但仍会创建空目录(Electron的问题,目前暂无解决方法)
|
||||
- 修复播放器的停止逻辑问题
|
||||
|
||||
### 其他
|
||||
|
||||
- 更新electron到v13.6.9
|
||||
|
||||
## [1.17.1](https://github.com/lyswhut/lx-music-desktop/compare/v1.17.0...v1.17.1) - 2022-01-28
|
||||
|
||||
### 优化
|
||||
|
||||
- 优化kw源英文与翻译歌词的匹配
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复快捷键与默认按键行为冲突的问题,现在若将某些有默认行为的按键(如在列表中上、下箭头、Home、End等键可以使列表滚动)设置为快捷键时,将禁用其默认行为
|
||||
- 修复列表的聚焦问题,现在在列表中使用上、下箭头、空格等键滚动列表时不会导致滚动到一定距离后丢失焦点的问题
|
||||
|
||||
### 其他
|
||||
|
||||
- 更新electron到v13.6.8
|
||||
|
||||
## [1.17.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.16.0...v1.17.0) - 2022-01-22
|
||||
|
||||
### 新增
|
||||
|
||||
- 新增“便携”功能,在Windows平台下,若程序目录下存在 portable 目录,则自动使用此目录作为数据存储目录
|
||||
- 新增 Scheme URL 支持,同时发布lx-music-script项目配合使用(一个油猴脚本,可以在浏览器中的官方平台网页直接调用LX Music),Scheme URL的调用说明看Readme.md文档的Scheme URL支持部分
|
||||
- 新增启动参数`-proxy-server`与`-proxy-bypass-list`,详细介绍看Readme.md文档的启动参数部分
|
||||
- 新增桌面歌词是否延迟滚动设置,默认开启,若你不想要桌面歌词延迟滚动可以去设置-桌面歌词设置关掉
|
||||
|
||||
### 优化
|
||||
|
||||
- 为可视化音频的频谱整体添加频谱均值加成,使频谱显示更有节奏感
|
||||
- 优化程序初始化逻辑,修复无网络的情况下的初始化问题
|
||||
- 我的列表-列表名的右击菜单更新已收藏的在线列表时,将始终重新加载,不再使用缓存,解决在原平台更新歌单后,在LX点击更新可能看到的还是在原平台更新前的歌单的问题
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复代理不生效的问题
|
||||
- 修复`openDevTools`选项无效的问题
|
||||
- 修复播放状态的提示问题
|
||||
- 修复tx源无搜索结果的问题
|
||||
|
||||
### 其他
|
||||
|
||||
- 更新 Electron 到 v13.6.7
|
||||
|
||||
## [1.16.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.15.3...v1.16.0) - 2022-01-01
|
||||
|
||||
这算是一个大版本,对主窗口部分的代码逻辑做了较大改动,但由于界面的改动不大,所以没有更新大版本号。
|
||||
虽然经过一个月的测试与问题修复,但可能仍然存在未发现的问题,若你发现某些界面异常、某些行为与旧版本存在差异等问题,欢迎反馈!
|
||||
另外祝大家元旦快乐~!
|
||||
|
||||
### 新增
|
||||
|
||||
- 播放详情页新增音量控制条
|
||||
- 播放详情页新增桌面歌词切换按钮
|
||||
- 新增将我的列表保存为TXT、CSV格式,可以去设置-备份与恢复中使用(注意:此类格式的备份目前不支持恢复到LX Music中)
|
||||
- 新增根据歌曲名、歌手名等字段对列表自动排序的功能,可以在我的列表右击列表名弹出的菜单中使用
|
||||
- 新增将播放与下载的歌词转换为繁体中文选项,默认关闭,可在设置-播放设置中开启
|
||||
- 现在已允许进入临时播放列表,即:使用歌单详情页、排行榜名称右键菜单的“播放”按钮播放歌曲时,可右击播放封面进入此临时列表
|
||||
- 播放详情页新增音频可视化功能(实验性)
|
||||
- 我的列表新增拖动调整位置功能,按住Ctrl键(Mac上对应Command键)的时候将进入“拖动模式”,此时可以拖动列表的位置来调整顺序
|
||||
|
||||
### 优化
|
||||
|
||||
- 优化列表性能,软件整体性能
|
||||
- 调整Mac平台下的图标大小
|
||||
- 同步功能添加对列表顺序调整的控制,确保手动调整位置后的列表与不同的电脑同步时,列表位置不会被还原
|
||||
- 优化歌单详情、排行榜名右键的播放按钮的播放机制,现在不用等待整个列表(多页时)加载完成才能播放了
|
||||
- 为播放详情页、桌面歌词添加延迟滚动,播放详情页略微减小已激活歌词的缩放大小及桌面歌词翻译大小
|
||||
- 修改右边控制按钮为windows风格
|
||||
- 更新了新年皮肤的背景与配色,欢迎体验~
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复kw源某些歌曲的歌词提取异常的问题
|
||||
|
||||
### 变更
|
||||
|
||||
- 现在使用繁体中文语言时将不再自动转换歌词,转换行为将由上面新增的转换开关控制
|
||||
|
||||
### 移除
|
||||
|
||||
- 移除我的列表右键菜单的“上移、下移列表”功能,调整改用新增的拖动功能去调整位置
|
||||
|
||||
### 其他
|
||||
|
||||
- 升级vue到 3.x
|
||||
|
||||
## [1.15.3](https://github.com/lyswhut/lx-music-desktop/compare/v1.15.2...v1.15.3) - 2021-11-21
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复设置-控制按钮位置选项与下载歌词编码格式选项命名冲突导致选项显示异常的问题
|
||||
- 修复播放下载列表时存在失效的歌曲会导致切歌不准确的问题
|
||||
- 修复潜在的音乐加载超时不会切歌的问题
|
||||
- 修复因kw源歌词接口停用导致该源歌词获取失败的问题
|
||||
|
||||
## [1.15.2](https://github.com/lyswhut/lx-music-desktop/compare/v1.15.3...v1.15.2) - 2021-11-09
|
||||
|
||||
### 其他
|
||||
|
||||
- 降级electron到v13.4.0(这修复了windows 7下播放歌曲时软件会崩溃的问题)
|
||||
|
||||
## [1.15.1](https://github.com/lyswhut/lx-music-desktop/compare/v1.15.0...v1.15.1) - 2021-11-09
|
||||
|
||||
### 优化
|
||||
|
||||
- 优化我的列表、下载列表等列表的滚动流畅度
|
||||
- 优化下载功能的批量添加、删除、暂停任务时的流畅度,现在进行这些操作应该不会再觉得卡顿了
|
||||
- 支持启动软件时恢复播放下载列表里的歌曲
|
||||
- 添加媒体播放进度条的信息设置
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复某些情况下获取URL失败时会意外切歌的问题
|
||||
- 修复了某些情况下会列表同步失败,导致连接断开无限重连或一直卡在 `syncing...` 的问题
|
||||
- 修复列表数据过大导致同步失败的问题
|
||||
|
||||
### 其他
|
||||
|
||||
- 更新electron到v15.3.1(这修复了媒体控制失效的问题)
|
||||
|
||||
## [1.15.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.14.1...v1.15.0) - 2021-10-29
|
||||
|
||||
### 新增
|
||||
|
||||
- 添加黑色托盘图标
|
||||
- 自定义源新增`version`字段,新增`utils.buffer.bufToString`方法
|
||||
|
||||
### 优化
|
||||
|
||||
- 大幅优化我的列表、下载、歌单、排行榜列表性能,现在即使同一列表内的歌曲很多时也不会卡顿了
|
||||
- 优化列表同步代码逻辑
|
||||
- 优化开关评论时的动画性能
|
||||
- 优化进入、离开播放详情页的性能
|
||||
- 兼容桌面歌词以触摸的方式移动、调整大小
|
||||
- 调整图标尺寸
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复kg源的歌单链接无法打开的问题
|
||||
- 修复同一首歌的URL、歌词等同时需要换源时的处理问题
|
||||
|
||||
### 其他
|
||||
|
||||
- 更新 Electron 到 v15.3.0
|
||||
|
||||
## [1.14.1](https://github.com/lyswhut/lx-music-desktop/compare/v1.14.0...v1.14.1) - 2021-10-04
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复我的列表搜索无法搜索小括号、中括号等字符的问题
|
||||
- 修复v1.14.0出现的备份与恢复功能备份的数据无法恢复的问题,同时兼容使用v1.14.0导出的存在问题的数据
|
||||
|
||||
## [1.14.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.13.0...v1.14.0) - 2021-10-02
|
||||
|
||||
### 新增
|
||||
|
||||
- 新增歌词简体中文转繁体中文,当软件语言被设置为繁体中文后,播放歌曲的歌词也将自动转成繁体中文显示
|
||||
- 新增单个列表导入/导出功能,可以方便分享歌曲列表,可在右击“我的列表”里的列表名后弹出的菜单中使用
|
||||
- 新增删除列表前的确认弹窗,防止误删列表
|
||||
- 新增歌词文本选择复制功能,可在详情页进度条上方的歌词文本选择按钮进入歌词文本选择模式,选择完成后可鼠标右击或者使用系统快捷键复制
|
||||
- 新增重复歌曲列表,可以方便移除我的列表中的重复歌曲,此列表会列出目标列表里歌曲名相同的歌曲,可在右击“我的列表”里的列表名后弹出的菜单中使用
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复mg排行榜无法加载的问题
|
||||
- 修复点击播放详情页的进度条跳进度时会出现偏移的问题
|
||||
- 修复在有提示信息的地方长按鼠标按键时提示信息会闪烁的问题
|
||||
- 修复下载歌曲时的歌词下载不尝试获取缓存歌词的问题
|
||||
- 修复GNOME等桌面下每次打开应用时需重新设置歌词窗口置顶的问题
|
||||
|
||||
## [1.13.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.12.2...v1.13.0) - 2021-09-05
|
||||
|
||||
如果你喜欢并经常使用洛雪音乐,并想要第一时间尝鲜洛雪的新功能,可以加入测试企鹅群768786588,
|
||||
注意:测试版的功可能会不稳定,打算潜水的勿加。
|
||||
|
||||
### 新增
|
||||
|
||||
- 歌曲搜索框新增清理按钮,点击此按钮可以清理搜索框并返回初始搜索界面
|
||||
- 新增“下载的歌词文件编码格式”设置,默认下载的歌词编码仍是`UTF-8`,对于某些在设备(如车机)上出现歌词中文乱码的用户可以尝试选择以`GBK`编码格式保存歌词文件
|
||||
- 新增设置-桌面歌词-歌词字体设置,此设置可用于设置桌面歌词的字体(已知的问题:Windows 7 下可能会出现字体列表为空的情况,这是当前系统的 Powershell 版本小于5.1导致的,请自行**尝试**看常见解决)
|
||||
|
||||
### 优化
|
||||
|
||||
- 支持网易源“我喜欢”歌单以注入token的方式打开。由于网易源的“我喜欢”歌单需要登录才能打开(若你看不懂后半句就去阅读 常见问题-无法打开外部歌单),现若想要打开此类歌单,需要在歌单链接后面拼上 `###` 再加上有效的token,拼接格式:`[id|url]###token`,例子(最后面的xxxxxx替换成你的token):`https://music.163.com/#/playlist?id=123456&userid=123456###xxxxxx`
|
||||
- 软件内快捷键的最小化触发时,如果已启用托盘,则隐藏程序,否则最小化程序
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复某些情况下同步功能会导致切歌混乱的问题
|
||||
- 修复从电脑浏览器复制的企鹅歌单链接无法打开的问题
|
||||
|
||||
## [1.12.2](https://github.com/lyswhut/lx-music-desktop/compare/v1.12.1...v1.12.2) - 2021-08-11
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复播放下载列表的歌曲时切歌的问题
|
||||
- 修复播放下载列表的歌曲时歌词无法显示的问题
|
||||
- 修复下载列表稍后播放功能无效的问题
|
||||
- 修复同步服务器启动失败时,关闭同步服务不会清空失败信息的问题
|
||||
|
||||
## [1.12.1](https://github.com/lyswhut/lx-music-desktop/compare/v1.12.0...v1.12.1) - 2021-08-08
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复随机播放下无法切歌的问题
|
||||
|
||||
## [1.12.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.11.0...v1.12.0) - 2021-08-08
|
||||
|
||||
### 新增
|
||||
|
||||
- 新增局域网同步功能(实验性,首次使用前建议先备份一次列表),此功能需要配合PC端使用,移动端与PC端处在同一个局域网(路由器的网络)下时,可以多端实时同步歌曲列表,使用问题请看"常见问题"。
|
||||
|
||||
### 优化
|
||||
|
||||
- 添加播放器对系统媒体控制与显示的兼容处理,现在在windows下的锁屏界面可以正确显示当前播放的音乐信息及切换歌曲了
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复导入kg歌单最多只能加载100、500首歌曲的问题。注:现在可以加载1000+首歌曲的歌单,但出于未知原因会导致部分歌曲无法加载(可能是无版权导致的),目前酷狗码仍然最多只能加载500首歌
|
||||
- 修复某些情况下所显示的歌词、封面图片与当前正在播放的歌曲不一致的问题
|
||||
|
||||
## [1.11.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.10.2...v1.11.0) - 2021-07-18
|
||||
|
||||
### 新增
|
||||
|
||||
- 添加 win arm64 架构的安装包构建
|
||||
- 新增“添加歌曲到列表时的位置”设置,可选项为列表的“顶部”与“底部”
|
||||
|
||||
### 优化
|
||||
|
||||
- 优化网络请求,尝试去解决无法连接服务器的问题
|
||||
- 优化mg源打开歌单的链接兼容
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复mg源搜索失效的问题
|
||||
|
||||
### 移除
|
||||
|
||||
- 因wy源的歌单列表已没有“最新”排序的选项,所以现跟随移除wy源歌单列表按“最新”排序的按钮
|
||||
|
||||
### 变更
|
||||
|
||||
- 添加歌曲到列表时从原来的底部改为顶部,若你想要将你的列表歌曲顺序反转以适应这一变更,可先按住`shift`键的情况下点击列表的最后一首歌,然后再点击列表的第一首歌,完成倒序选中,最后随便右击列表的任意一首歌,在弹出的菜单中选择调整顺序,在弹出框输入1后确定即可反转列表。
|
||||
若你想要恢复原来的行为则可以去更改“添加歌曲到列表时的位置”设置项。
|
||||
|
||||
### 其他
|
||||
|
||||
- 更新electron到v13.1.7
|
||||
|
||||
## [1.10.2](https://github.com/lyswhut/lx-music-desktop/compare/v1.10.1...v1.10.2) - 2021-05-25
|
||||
|
||||
### 修复
|
||||
|
||||
269
FAQ.md
@@ -1,11 +1,45 @@
|
||||
# lx-music-desktop 常见问题
|
||||
|
||||
本文档已迁移到:<https://lyswhut.github.io/lx-music-doc/desktop/faq>
|
||||
|
||||
在阅读本常见问题后,仍然无法解决你的问题,请提交issue或者加企鹅群`830125506`反馈(无事勿加,入群先看群公告),反馈时请**注明**已阅读常见问题!
|
||||
|
||||
## ~~软件为什么没有桌面歌词与自定义列表功能~~
|
||||
|
||||
洛雪音乐的最初定位不是作为播放器开发的,它主要用于**查找歌曲**,软件的播放功能仅用于试听,不建议用作为常用播放器使用。
|
||||
|
||||
## 音乐播放列表机制
|
||||
|
||||
1. 默认情况下,播放搜索列表、歌单列表、排行榜列表的歌曲时会自动将该歌曲添加到“我的列表”的试听列表后再播放,这与手动将歌曲添加到试听列表,再去试听列表找到这首歌点播放是等价的
|
||||
2. 如果你想要播放多首歌曲,需要使用多选功能(若不知道如何多选请看常见问题)多选后,将这些歌曲添加到“我的列表”播放,或使用稍后播放功能播放
|
||||
3. 第2条适用于搜索列表、歌单列表、排行榜列表、我的列表中的歌曲
|
||||
4. 对于歌单详情列表,除了可以使用第2条的方式播放外,你可以点击详情页上面的播放按钮临时播放当前歌单,或点击收藏将当前歌单收藏到“我的列表”后再去播放
|
||||
5. 对于排行榜详情列表,除了可以使用第2条的方式播放外,你可以在右击排行榜名字后弹出的菜单中,播放或收藏整个排行榜,这与第四条的歌单中的播放、与收藏按钮功能一致
|
||||
6. v1.18.0及之后新增了“双击列表里的歌曲时自动切换到当前列表播放”设置,默认关闭,此功能仅对歌单、排行榜有效
|
||||
7. 将歌曲添加“稍后播放”后,它们会被放在一个优先级最高的特殊队列中,点击“下一曲”时会消耗该队列中的歌曲,并且无法通过“上一曲”功能播放该队列的上一首歌曲
|
||||
8. 在切歌时若不是通过“上一曲”、“下一曲”功能切歌(例如直接点击“排行榜列表”、“我的列表”中的歌曲切歌),“稍后播放”队列将会被清空
|
||||
|
||||
## 可用的鼠标、键盘快捷操作
|
||||
|
||||
- 鼠标右击播放栏的歌曲图片封面可以定位当前播放的歌曲
|
||||
- 鼠标右击播放栏进度条上的`LRC`按钮可以锁定/解锁桌面歌词
|
||||
- 歌曲搜索框、歌单链接输入框内鼠标右击可以将当前剪贴板上的文字粘贴到输入框内
|
||||
- 鼠标右击搜索界面中的单条搜索历史可以将其移除
|
||||
- 歌曲列表内的文字在选中后,鼠标右击可以复制已选中的文字,此功能只对搜索、歌单、排行榜、我的列表中的列表有效
|
||||
- 鼠标在播放详情页内右键双击可以关闭播放详情页
|
||||
- 鼠标左击播放栏上的歌曲名字可以将它复制
|
||||
- 鼠标右击设置-主题设置的“道法自然(英文Auto)”主题可以打开亮、暗主题设置窗口
|
||||
- 歌曲搜索框的候选内容可以用键盘上下方向键选择,按回车键搜索已选内容
|
||||
- 在歌单详情页按退格键可以返回歌单列表
|
||||
- 歌曲列表中可以使用`Ctrl`、`Shift`键进行多选,这类似Windows下的文件选择,详情看常见问题列表多选部分
|
||||
- 在我的列表内可以使用`Ctrl + f`键打开搜索框进行列表内歌曲搜索,搜索框按`Esc`键可以关闭搜索框,搜索框内按上下方向键可以选择歌曲,按`回车`键跳转到已选歌曲,按`Ctrl + 回车`可以跳转并播放已选歌曲
|
||||
- 在我的列表按住`Ctrl`键可以进入列表拖动模式,此时可以用鼠标拖动列表调整列表的位置
|
||||
- 编辑列表名时按`Esc`键可以取消编辑
|
||||
- 按`F11`可以进入、退出全屏状态(v1.19.0新增)
|
||||
- 在歌曲添加弹窗中,若按住`Ctrl`键后再点击列表名,将不会自动关闭添加窗口,这对想要将同一首(一批)歌曲添加到多个列表时会很有用(v1.22.0新增)
|
||||
|
||||
注:在macOS上`Ctrl`键对应`Command`键
|
||||
|
||||
## 歌曲无法试听与下载
|
||||
|
||||
### 所有歌曲都提示 `请求异常😮,可以多试几次,若还是不行就换一首吧。。。`
|
||||
@@ -13,13 +47,13 @@
|
||||
尝试更换网络,如切换到移动网络,若移动网络还是不行则尝试开关下手机的飞行模式后再试,<br>
|
||||
若使用家庭网络的话,可尝试将光猫断电5分钟左右再通电联网后播放。
|
||||
|
||||
### 提示`getaddrinfo EAI_AGAIN ...`
|
||||
### 提示 `getaddrinfo EAI_AGAIN ...` 或 `无法连接到服务器`
|
||||
|
||||
尝试在在浏览器打开这个地址`http://ts.tempmusic.tk`,浏览器显示404是正常的,如果不是404那就证明所在网络无法访问接口服务器。
|
||||
尝试在在浏览器打开这个地址`http://ts.tempmusics.tk`,浏览器显示404是正常的,如果不是404那就证明所在网络无法访问接口服务器。
|
||||
若网页无法打开或打开来不是404,则可能是DNS的问题,可以尝试以下办法:
|
||||
|
||||
1. 将DNS改成自动获取试试(注:改完可能需要清理下系统DNS缓存才生效)
|
||||
2. 手动把DNS改一下,不要用360的DNS,可以把DNS改成`114.114.114.114`、`8.8.8.8`(注:改完可能需要清理下系统DNS缓存才生效)
|
||||
2. 手动把DNS改一下,不要用360的DNS,可以把DNS改成`223.6.6.6`、`8.8.8.8`(注:改完可能需要清理下系统DNS缓存才生效)
|
||||
|
||||
改完DNS后可能需要重启软件才生效
|
||||
|
||||
@@ -34,11 +68,21 @@
|
||||
5. 若还不行请到这个链接查看详情:<https://github.com/lyswhut/lx-music-desktop/issues/5>
|
||||
6. 若没有在第5条链接中的第一条评论中看到接口无法使用的说明,则应该是你网络无法访问接口服务器的问题,如果接口有问题我会在那里说明。
|
||||
|
||||
想要知道是不是自己网络的问题可以看看`http://ts.tempmusic.tk`能不能在浏览器打开,浏览器显示404是正常的,如果不是404那就证明所在网络无法访问接口服务器。
|
||||
想要知道是不是自己网络的问题可以看看`http://ts.tempmusics.tk`能不能在浏览器打开,浏览器显示404是正常的,如果不是404那就证明所在网络无法访问接口服务器。
|
||||
若网页无法打开或打来不是404,则应该是DNS的问题,可以尝试以下办法:
|
||||
|
||||
1. 将DNS改成自动获取试试
|
||||
2. 手动把DNS改一下,不要用360的DNS,可以把DNS改成`114.114.114.114`、`8.8.8.8`
|
||||
2. 手动把DNS改一下,不要用360的DNS,可以把DNS改成`223.6.6.6`、`8.8.8.8`
|
||||
|
||||
### Windows版所有歌曲都提示 `音频加载错误,5秒后切换下一首`
|
||||
|
||||
尝试关闭 Internet选项 的代理设置。
|
||||
|
||||
如果你不知道怎么做,可以尝试按以下步骤去做:
|
||||
|
||||
按<kbd>windows</kbd>+<kbd>r</kbd>键打开“运行”窗口,输入`inetcpl.cpl`后回车,在打开的 Internet选项 对话框中,切换到 连接 -> 局域网设置,在弹出的新窗口中把代理服务器下的勾去掉,如果自动配置下的勾也有被勾选,那么建议也去掉,最后按确定关闭所有弹窗。
|
||||
|
||||
> 来源:<https://github.com/lyswhut/lx-music-desktop/issues/873#issuecomment-1146945724>
|
||||
|
||||
## 列表多选
|
||||
|
||||
@@ -56,25 +100,112 @@
|
||||
|
||||
播放在线列表内的歌曲需要将它们都添加到我的列表才能播放,你可以全选列表内的歌曲然后添加到现有列表或者新创建的列表,然后去播放该列表内的歌曲。
|
||||
|
||||
从v1.10.0起,你可以右击排行榜名字的弹出菜单中直接播放或收藏整个排行榜的歌曲。
|
||||
|
||||
## 无法打开外部歌单
|
||||
|
||||
不支持垮源打开歌单,请**确认**你需要打开的歌单平台是否与软件标签所写的**歌单源**对应(不一样的话请通过右上角切换歌单源);<br>
|
||||
不支持跨源打开歌单,请**确认**你需要打开的歌单平台是否与软件标签所写的**歌单源**对应(不一样的话请通过右上角切换歌单源);<br>
|
||||
对于分享出来的歌单,若打开失败,可尝试先在浏览器中打开后,再从浏览器地址栏复制URL地址到软件打开;<br>
|
||||
或者如果你知道歌单 id 也可以直接输入歌单 id 打开。<br>
|
||||
|
||||
注:网易源的“我喜欢”歌单无法在未登录的情况下打开,所以你需要手动创建一个歌单后将“我喜欢”里的歌曲移动到该歌单打开
|
||||
|
||||
### 打开网易源“我喜欢”歌单
|
||||
|
||||
由于网易源的“我喜欢”歌单需要登录才能打开,从v1.13.0起提供了可以以注入token的方式打开网易源“我喜欢”歌单的功能,现若想要打开此类歌单,需要在歌单链接或id后面拼上 `###` 再加上有效的token,拼接格式:`[id|url]###token`,例子(最后面的xxxxxx替换成你的token):
|
||||
- `https://music.163.com/#/playlist?id=11332&userid=123456###xxxxxx`
|
||||
- `11332###xxxxxx`
|
||||
|
||||
即:将 `歌单链接或者歌单ID`、`###`、`token` 这三者拼到一起。
|
||||
|
||||
#### `token`的获取方法
|
||||
|
||||
**注:`token`是你账号的临时身份令牌,不要随便泄露给他人**<br>
|
||||
在浏览器打开登录网易云音乐并**登录**后,按`F12`,此时将会打开开发者窗口,然后按你使用的浏览器操作:
|
||||
|
||||
##### Chrome、360、QQ等浏览器
|
||||
|
||||
这些浏览器打开此窗口时界面可能是中文也可能是英文,英文的话按括号里的来
|
||||
|
||||
1. 点击窗口顶部`应用程序(application)`(若找不到此选项,则可能是被折叠起来了,看看顶部菜单的`>>`)
|
||||
2. 展开左侧 `Cookies`
|
||||
3. 点击 `https://music.163.com`
|
||||
4. 在右侧窗口找到 `名称(Name)` 为 `MUSIC_U` 的这行,这行的第二列(`值(Value)`)内的那串内容就是`token`,双击它进入编辑状态,然后按`ctrl + c`键就可以将它复制
|
||||
|
||||
##### 火狐浏览器
|
||||
|
||||
1. 点击窗口顶部`存储`
|
||||
2. 展开左侧 `Cookie`
|
||||
3. 点击 `https://music.163.com`
|
||||
4. 在右侧窗口找到 `名称` 为 `MUSIC_U` 的这行,这行的最后一列(`值`)内的那串内容就是`token`,双击它进入编辑状态,然后按`ctrl + c`键就可以将它复制
|
||||
|
||||
## 更新已收藏的在线歌单
|
||||
|
||||
该功能仅对直接从歌单详情页点“收藏”按钮收藏的歌单有效,可右击已收藏的列表名从弹出的菜单中选择“更新”使用该功能,
|
||||
|
||||
需要注意的是:这将会覆盖本地的目标列表,歌曲将被替换成最新的在线列表。
|
||||
|
||||
## 调整我的列表的列表顺序
|
||||
|
||||
按住Ctrl键(Mac上对应Command键)的时候将进入“拖动模式”,此时可以拖动列表的位置来调整顺序。
|
||||
|
||||
## 同步功能的使用(实验性,首次使用前建议先备份一次列表)
|
||||
|
||||
**注意:由于同步传输时的数据是明文传输,请在受信任的网络下使用此功能!**<br>
|
||||
此功能需要配合移动端使用,PC端与移动端处在同一个局域网(路由器的网络)下时,可以多端实时同步歌曲列表,使用方法:
|
||||
|
||||
1. 在PC端的设置-数据同步开启同步功能(这时如果出现安全软件、防火墙等提示网络连接弹窗时需要点击允许)
|
||||
2. 在移动端的设置-同步-同步服务器地址输入PC端显示的同步服务器地址(如果显示可以多个,则输入与**移动端上显示的本机地址**最相似的那个),端口号与PC端的同步端口一致(**输入完毕后需要按一下键盘上的回车键使输入的内容生效**)
|
||||
3. 输入完这两项后点击“启动同步”
|
||||
4. 若连接成功,对于首次同步时,若两边的设备的列表不为空,则PC端会弹出选择列表同步方式的弹窗,同步方式的说明弹窗下面有介绍
|
||||
|
||||
#### 关于同步弹窗的说明
|
||||
|
||||
对于首次同步时,若两边的设备的列表不为空,则PC端会弹出选择列表同步方式的弹窗,此弹窗内的同步方式仅针对**首次同步**,<br>
|
||||
第一次同步成功后,以后再同步时将会自动根据两边设备的列表内容合并同步,不信你可以在同步完成后断开两边的连接,然后在两边增删一些歌曲或列表后再同步试试看~😉
|
||||
|
||||
#### 连接同步服务失败的可能原因
|
||||
|
||||
- 此功能需要PC端与移动端都连接在同一个路由器下的网络才能使用
|
||||
- 检查防火墙是否拦截了PC端的服务端口
|
||||
- 路由器若开启了AP隔离,则此功能无法使用
|
||||
|
||||
#### 连接同步服务失败的检查
|
||||
|
||||
1. 确保PC端的同步服务已启用成功(若连接码、同步服务地址没有内容,则证明服务启动失败,此时看启用同步功能复选框后面的错误信息自行解决,另外若你不知道端口号是什么意思就不要乱改,或不要改得太大与太小)
|
||||
2. 在手机浏览器地址栏输入`http://x.x.x.x:23332/hello` **(注:将`x.x.x.x`换成PC端显示的同步服务地址,`23332`为PC端的端口号)** 后回车,若此地址可以打开并显示 `Hello~::^-^::`则证明移动端与PC端网络已互通,
|
||||
3. 若移动端无法打开第2步的地址,则在PC端的浏览器地址栏输入并打开该地址,若可以打开,则要么是被LX Music PC端被电脑防火墙拦截,要么PC端与移动端不在同一个网络下,或者路由器开启了AP隔离(一般在公共网络下会出现这种情况)
|
||||
|
||||
## 界面异常(界面显示不完整)
|
||||
|
||||
### Windows 10、11界面异常、界面无法显示
|
||||
|
||||
尝试添加运行参数 `--disable-gpu-sandbox` 启动,例如:`.\lx-music-desktop.exe --disable-gpu-sandbox`,添加方法可自行百度“给快捷方式加参数”。
|
||||
|
||||
若以上方法无效,则尝试将 `--disable-gpu-sandbox` 逐个换成以下参数启动,直到恢复正常为止:
|
||||
|
||||
- `--no-sandbox`
|
||||
- `-dha`
|
||||
- `--disable-gpu`
|
||||
|
||||
:::caution
|
||||
这些参数会禁用程序的某些安全特性或降低程序性能,没有遇到问题不要使用它们!
|
||||
:::
|
||||
|
||||
对于界面无法显示,任务栏里也没看到图标,但是任务管理器里面看到进程的问题,还可尝试更换软件安装目录(对于安装版需要先卸载再换目录安装,绿色版直接剪切移动即可,只要目录换了就行),<br />
|
||||
此方法的相关讨论看:<https://github.com/lyswhut/lx-music-desktop/issues/943#issuecomment-1217832186>
|
||||
|
||||
### Windows 7 下界面异常
|
||||
|
||||
由于软件默认使用了透明窗口,根据Electron官方文档的[说明](https://electronjs.org/docs/api/frameless-window#%E5%B1%80%E9%99%90%E6%80%A7):
|
||||
由于软件默认使用了透明窗口,根据Electron官方文档的[说明](https://www.electronjs.org/docs/latest/tutorial/window-customization#limitations):
|
||||
> 在 windows 操作系统上, 当 DWM 被禁用时, 透明窗口将无法工作。
|
||||
|
||||
因此,当 win7 没有使用**AERO**主题时界面将会显示异常,开启AERO的方法请自行百度:`win7开启aero效果`(开启后可看到任务栏变透明)。<br>
|
||||
因此,当 win7 没有使用**Aero**主题时界面将会显示异常,开启AERO的方法请自行百度:`win7开启Aero效果`(开启后可看到任务栏变透明)。<br>
|
||||
从`0.14.0`版本起不再强制要求开启透明效果,若你实在不想开启(若非电脑配置太低,墙裂建议开启!),可通过添加运行参数`-dt`来运行程序即可,例如:`.\lx-music-desktop.exe -dt`,添加方法可自行百度“给快捷方式加参数”,该参数的作用是用来控制程序是否使用非透明窗口运行。
|
||||
|
||||
注:启用**AERO**主题后,若软件出现黑边框,则重启软件即可恢复正常。
|
||||
注:启用**Aero**主题后,若软件出现黑边框,则重启软件即可恢复正常。
|
||||
|
||||
对于一些完全无法正常显示界面、开启了AERO后问题仍未解决的情况,请阅读下面的 **软件启动后,界面无法显示** 解决。
|
||||
对于一些完全无法正常显示界面、开启了AERO后问题仍未解决的情况,请阅读下面的 **Window 7 下软件启动后,界面无法显示** 解决。
|
||||
|
||||
### Linux 下界面异常
|
||||
|
||||
@@ -83,19 +214,25 @@
|
||||
|
||||
注:v1.6.0及之后的版本才支持`-dha`参数
|
||||
|
||||
## 软件启动后,界面无法显示
|
||||
## Windows 7 下软件启动后,界面无法显示
|
||||
|
||||
对于软件启动后,可以在任务栏看到软件,但软件界面在桌面上无任何显示,或者整个界面偶尔闪烁的情况。<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(运行时)`版即可,安装完成后可能需要重启才生效。
|
||||
下载`Runtime(运行时)`版即可,安装完成后可能需要重启才生效,**若出现闪烁的情况**,可阅读下面的**Windows 7 下整个界面闪烁**解决。
|
||||
|
||||
若还是不行可尝试以下操作:
|
||||
## Windows 7 下整个界面闪烁(消失又出现)
|
||||
|
||||
- 更新显卡驱动
|
||||
- 添加启动参数`-dha`运行(添加的方法请自行百度“给快捷方式加参数”)
|
||||
- 尝试将绿色版的软件放在**桌面**或**我的文档**运行
|
||||
可尝试在关掉软件后,在桌面空白处鼠标右击,在弹出的菜单中选择**个性化**,在弹出的窗口中**切换到系统内置的Aero主题**,然后再启动软件看是否解决。
|
||||
|
||||
## Windows 7 下桌面歌词字体列表为空
|
||||
|
||||
Windows 7 系统系统需要安装 Powershell 5.1及以上版本才可正常获取系统字体列表。
|
||||
|
||||
想要查看当前 Powershell 版本可以在 Powershell 窗口输入命令:`Get-Host`
|
||||
|
||||
最新 Powershell 安装包可以去官方 [Github releases](https://github.com/PowerShell/PowerShell/releases) 页下载,安装过程中若出现错误,请自行按照提示或者百度/Google解决。
|
||||
|
||||
## 安装版安装失败,提示安装程序并未成功地运行完成
|
||||
|
||||
@@ -152,9 +289,9 @@ Windows 7 未开启 Aero 效果时桌面歌词会有问题,详情看上面的
|
||||
|
||||
以 **`.dmg`** 结尾的文件为 MAC 版本;
|
||||
|
||||
以 **`.AppImage`**、**`.deb`** 结尾的为 Linux 版本。
|
||||
以 **`.AppImage`**、**`.deb`**、**`.rpm`**、**`.pacman`** 结尾的为 Linux 版本。
|
||||
|
||||
带有`x64`的为64位的系统版本,带`x86`的为32位的系统版本;若两个都带有的则为集合版,安装时会自动根据系统位数选择对应的版本安装。
|
||||
带有`x64`的为64位的系统版本,带`x86`的为32位的系统版本;若两个都带有的则为集合版,安装时会自动根据系统位数选择对应的版本安装;带有`arm`的为arm架构系统的版本。
|
||||
|
||||
## 软件更新
|
||||
|
||||
@@ -162,13 +299,13 @@ Windows 7 未开启 Aero 效果时桌面歌词会有问题,详情看上面的
|
||||
若下载未完成时软件被关闭,下次启动软件会再次自动下载。<br>
|
||||
若还是**更新失败**,可能是无法访问GitHub导致的,这时需要手动更新,即下载最新安装包直接覆盖安装即可。<br>
|
||||
注意:**绿色版**的软件自动更新功能**不可用**,建议使用安装版!!<br>
|
||||
注意:**Mac版**、**Linux deb**版不支持自动更新!
|
||||
注意:**Mac版**、**Linux**版不支持自动更新!
|
||||
|
||||
## 更新已收藏的在线歌单
|
||||
### Windows 安装版在升级后,卸载了旧版本,但没有安装新版本
|
||||
|
||||
该功能仅对直接从歌单详情页点“收藏”按钮收藏的歌单有效,可右击已收藏的列表名从弹出的菜单中选择“同步”使用该功能,
|
||||
出现这个问题的原因一般是你当初在安装本软件的时候是以管理员身份安装的,运行软件的时候没有以管理员身份运行,所以卸载后无法再装上。
|
||||
|
||||
需要注意的是:这将会覆盖本地的目标列表,歌曲将被替换成最新的在线列表。
|
||||
安装本软件时建议选择 `为当前用户安装`,并安装在当前用户目录或者安装在不需要管理员权限的目录(即其他分区下),不要选`为所有用户安装`。
|
||||
|
||||
## 缺少`xxx.dll`
|
||||
|
||||
@@ -177,6 +314,26 @@ Windows 7 未开启 Aero 效果时桌面歌词会有问题,详情看上面的
|
||||
- 以管理员权限打开`cmd`,输入`sfc /scannow`回车等待检查完成重启电脑
|
||||
- 若上面的方法**修复、重启**电脑后仍然不行,就自行百度弹出的**错误信息**看下别人是怎么解决的
|
||||
|
||||
## MAC OS无法启动软件,提示 lx-music-desktop 已损坏
|
||||
|
||||
这是因为软件没有签名,被系统阻止运行,<br>
|
||||
在终端里输入 `sudo xattr -rd com.apple.quarantine /Applications/lx-music-desktop.app`,然后输入你的电脑密码即可
|
||||
|
||||
还可以参考:
|
||||
|
||||
- <http://www.pc6.com/edu/168719.html>
|
||||
- <https://blog.csdn.net/for641/article/details/104811538>
|
||||
|
||||
## 数据存储路径
|
||||
|
||||
默认情况下,软件的数据存储在:
|
||||
|
||||
- Windows:`%APPDATA%/lx-music-desktop`
|
||||
- Linux:`$XDG_CONFIG_HOME/lx-music-desktop` 或 `~/.config/lx-music-desktop`
|
||||
- macOS:`~/Library/Application/lx-music-desktop`
|
||||
|
||||
在Windows平台下,若程序目录下存在`portable`目录,则自动使用此目录作为数据存储目录(v1.17.0新增)。
|
||||
|
||||
## 杀毒软件提示有病毒或恶意行为
|
||||
|
||||
本人只能保证我写的代码不包含任何**恶意代码**、**收集用户信息**的行为,并且软件代码已开源,请自行查阅,软件安装包也是由CI拉取源代码构建,构建日志:[GitHub Actions](https://github.com/lyswhut/lx-music-desktop/actions)<br>
|
||||
@@ -187,6 +344,58 @@ Windows 7 未开启 Aero 效果时桌面歌词会有问题,详情看上面的
|
||||
|
||||
最后,若出现杀毒软件报毒、存在恶意行为,请自行判断选择是否继续使用本软件!
|
||||
|
||||
## 启动参数
|
||||
|
||||
目前软件已支持的启动参数如下:
|
||||
|
||||
- `-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起新增)
|
||||
- `-proxy-server` 设置代理服务器,代理应用的所有流量,例:`-proxy-server="127.0.0.1:1081"`(不支持设置账号密码,v1.17.0起新增)。注:应用内“设置-网络-代理设置”仅代理接口请求的流量,优先级更高
|
||||
- `-proxy-bypass-list` 以分号分隔的主机列表绕过代理服务器,例:`-proxy-bypass-list="<local>;*.google.com;*foo.com;1.2.3.4:5678"`(与`-proxy-server`一起使用才有效,v1.17.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"`
|
||||
|
||||
## Scheme URL支持
|
||||
|
||||
从v1.17.0起支持 Scheme URL,可以使用此功能从浏览器等场景下调用LX Music,我们开发了一个[油猴脚本](https://github.com/lyswhut/lx-music-script#readme)配套使用<br>
|
||||
脚本安装地址:<https://greasyfork.org/zh-CN/scripts/438148><br>
|
||||
以下是目前可用的Scheme URL调用方式:
|
||||
|
||||
- URL统一以`lxmusic://`开头
|
||||
- 若无特别说明,源的可用值为:`kw/kg/tx/wy/mg`
|
||||
- 若无特别说明,音质的可用值为:`128k/320k/flac/flac24bit`
|
||||
|
||||
目前支持两种传参方式:
|
||||
|
||||
- 通过`data`传参,以经过URL编码的JSON数据传参,例:`lxmusic://music/play?data=xxxx`,其中`xxxx`为经过URL编码后的JSON数据,支持复杂的参数调用
|
||||
- 通过`URL`传参,适用于简单传参的调用,不需要转成JSON格式,例:`lxmusic://music/search/xxxx`,但仍然需要对数据进行URL编码,只适应于简单参数调用(v1.18.0新增)
|
||||
|
||||
### `data`方式传参
|
||||
|
||||
以经过URL编码的JSON数据传参,例:`lxmusic://music/play?data=xxxx`,其中`xxxx`为经过URL编码后的JSON数据,JSON数据内容取决于下表的参数部分
|
||||
|
||||
| 描述 | URL | 参数
|
||||
| --- | --- | ---
|
||||
| 打开歌单 | `songlist/open` | `source<String>`(源,必须)<br>`id<String/Number>`(歌单ID,可选)<br>`url<String>`(歌单URL,可选)其中ID与URL必需传一个
|
||||
| 播放歌单 | `songlist/play` | `source<String>`(源,必须)<br>`id<String/Number>`(歌单ID,可选)<br>`url<String>`(歌单URL,可选)其中`id`与`url`必需传一个<br>`index<Number>`(播放第几首歌,可选,从0开始)
|
||||
| 搜索歌曲 | `music/search` | `keywords<String/Number>`(要搜索的内容,必须)<br>`source<String>`(源,可选)
|
||||
| 播放歌曲 | `music/play` | `name<String>`(歌曲名,必传)<br>`singer<String>`(艺术家名,必传)<br>`source<String>`(源,必传)<br>`songmid<String/Number>`(歌曲ID,必传)<br>`img<String>`(歌曲图片链接,选传)<br>`albumId<String/Number>`(歌曲专辑ID,选传)<br>`interval<String>`(格式化后的歌曲时长,选传,例:`03:55`)<br>`albumName<String>`(歌曲专辑名称,选传)<br>`types<Object>`(歌曲可用音质数组,必传,<br>数组格式:`[{"type": "<音质>", size: "<格式化后的文件大小,选传>", hash: "<kg源必传>"}]`,<br>例:`[{"type": "128k", size: "3.56M"}, {"type": "320k", size: null}]`)<br><br>以下为平台特定参数:<br>`hash<String>`(歌曲hash,kg源必传)<br>`strMediaMid<String>`(歌曲strMediaMid,tx源必传)<br>`albumMid<String>`(歌曲albumMid,tx源专用,选传)<br>`copyrightId<String>`(歌曲copyrightId,mg源必传)<br>`lrcUrl<String>`(歌曲lrcUrl,mg源专用,选传)
|
||||
|
||||
### `URL`方式传参
|
||||
|
||||
由于URL传参只适用于简单传参场景,所以目前只支持以下功能的调用:
|
||||
|
||||
| 描述 | URL | 参数
|
||||
| --- | --- | ---
|
||||
| 搜索歌曲 | `music/search/{source}/{keywords}` | `source`(源,可选)<br>`keywords`(要搜索的内容,必须)<br>例:`music/search/kw/xxx`、`music/search/xxx`
|
||||
| 打开歌单 | `songlist/open/{source}/{id/url}` | `source`(源,必须)<br>`id/url`(歌单ID或歌单URL,必须)<br>例:`songlist/open/kw/123456`
|
||||
|
||||
## 自定义源脚本编写说明
|
||||
|
||||
文件请使用UTF-8编码格式编写,脚本所用编程语言为JavaScript,可以使用ES6+语法,脚本与应用的交互是使用类似事件收发的方式进行,这是一个基本的脚本例子:
|
||||
@@ -255,7 +464,7 @@ send(EVENT_NAMES.inited, {
|
||||
name: '酷我音乐',
|
||||
type: 'music', // 目前固定为 music
|
||||
actions: ['musicUrl'], // 目前固定为 ['musicUrl']
|
||||
qualitys: ['128k', '320k', 'flac'], // 当前脚本的该源所支持获取的Url音质,有效的值有:['128k', '320k', 'flac']
|
||||
qualitys: ['128k', '320k', 'flac', 'flac24bit'], // 当前脚本的该源所支持获取的Url音质,有效的值有:['128k', '320k', 'flac', 'flac24bit']
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -277,8 +486,8 @@ send(EVENT_NAMES.inited, {
|
||||
|
||||
```
|
||||
|
||||
- `@name `:源的名字,建议不要过长,10个字符以内
|
||||
- `@description `:源的描述,建议不要过长,20个字符以内,可不填,不填时必须保留 @description
|
||||
- `@name `:源的名字,建议不要过长,24个字符以内
|
||||
- `@description `:源的描述,建议不要过长,36个字符以内,可不填,不填时必须保留 @description
|
||||
- `@version`:源的版本号,可不填,不填时可以删除 @version
|
||||
- `@author `:脚本作者名字,可不填,不填时可以删除 @author
|
||||
- `@homepage `:脚本主页,可不填,不填时可以删除 @homepage
|
||||
@@ -287,14 +496,19 @@ send(EVENT_NAMES.inited, {
|
||||
|
||||
应用为脚本暴露的API对象。
|
||||
|
||||
#### `window.lx.version`
|
||||
|
||||
自定义源API版本,API变更时此版本号将会更改(新增于v1.14.0之后)
|
||||
|
||||
#### `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、名字等信息
|
||||
| `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', 'flac24bit']`,该字段用于控制应用可用的音质类型
|
||||
| `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` / `flac24bit`(取决于初始化时对应源传入的`qualitys`值中的一个),`info.musicInfo`:音乐信息对象,里面有音乐ID、名字等信息
|
||||
| `updateAlert` | 显示源更新弹窗,发送该事件时的参数:`{log, updateUrl}`<br>`log`:更新日志,必传,字符串类型,内容可以使用`\n`换行,最大长度1024,超过此长度后将被截取超出的部分<br>`updateUrl`:更新地址,用于引导用户去该地址更新源,选传,需为http协议的url地址,最大长度1024<br>此事件每次运行脚本只能调用一次(源版本v1.2.0新增)<br>例子:`lx.send(lx.EVENT_NAMES.updateAlert, { log: 'hello world', updateUrl: 'https://xxx.com' })`
|
||||
|
||||
|
||||
#### `window.lx.on`
|
||||
@@ -342,6 +556,7 @@ const cancelHttp = window.lx.request(url, options, callback)
|
||||
应用提供给脚本的工具方法:
|
||||
|
||||
- `window.lx.utils.buffer.from`:对应Node.js的 `Buffer.from`
|
||||
- `window.lx.utils.buffer.bufToString`:Buffer转字符串 `bufToString(buffer, format)`,`format`对应Node.js `Buffer.toString`的参数(v1.14.0之后新增)
|
||||
- `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)`
|
||||
|
||||
57
README.md
@@ -36,8 +36,8 @@
|
||||
|
||||
所用技术栈:
|
||||
|
||||
- Electron 12
|
||||
- Vue 2
|
||||
- Electron 15+
|
||||
- Vue 3
|
||||
|
||||
已支持的平台:
|
||||
|
||||
@@ -48,11 +48,42 @@
|
||||
软件变化请查看:[更新日志](https://github.com/lyswhut/lx-music-desktop/blob/master/CHANGELOG.md)<br>
|
||||
软件下载请转到:[发布页面](https://github.com/lyswhut/lx-music-desktop/releases)<br>
|
||||
或者到网盘下载(网盘内有MAC、windows版):`https://www.lanzoui.com/b0bf2cfa/` 密码:`glqw`(若链接无法打开请百度:蓝奏云链接打不开)<br>
|
||||
使用常见问题请转至:[常见问题](https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md)
|
||||
使用常见问题请转至:[常见问题](https://lyswhut.github.io/lx-music-doc/desktop/faq)
|
||||
|
||||
#### Scheme URL支持
|
||||
|
||||
从v1.17.0起支持 Scheme URL,可以使用此功能从浏览器等场景下调用LX Music,我们开发了一个[油猴脚本](https://github.com/lyswhut/lx-music-script#readme)配套使用,<br>
|
||||
脚本安装地址:<https://greasyfork.org/zh-CN/scripts/438148><br>
|
||||
|
||||
若你想自己调用LX Music,可以看[Scheme URL支持](https://lyswhut.github.io/lx-music-doc/desktop/scheme-url)
|
||||
|
||||
#### 启动参数
|
||||
|
||||
目前软件已支持的启动参数如下:
|
||||
|
||||
- `-proxy-server` 设置代理服务器,代理应用的所有流量
|
||||
- `-proxy-bypass-list` 以分号分隔的主机列表绕过代理服务器
|
||||
- `-play` 启动时播放指定列表的音乐
|
||||
- `-search` 启动软件时自动在搜索框搜索指定的内容
|
||||
- `-dha` 禁用硬件加速启动(Disable Hardware Acceleration)
|
||||
- `-dt` 以非透明模式启动(Disable Transparent)
|
||||
- `-dhmkh` 禁用硬件媒体密钥处理(Disable Hardware Media Key Handling)
|
||||
|
||||
启动参数的详细说明请看[启动参数说明](https://lyswhut.github.io/lx-music-doc/desktop/run-params)
|
||||
|
||||
#### 数据存储路径
|
||||
|
||||
默认情况下,软件的数据存储在:
|
||||
|
||||
- Windows:`%APPDATA%/lx-music-desktop`
|
||||
- Linux:`$XDG_CONFIG_HOME/lx-music-desktop` 或 `~/.config/lx-music-desktop`
|
||||
- macOS:`~/Library/Application Support/lx-music-desktop`
|
||||
|
||||
在Windows平台下,若程序目录下存在`portable`目录,则自动使用此目录作为数据存储目录(v1.17.0新增)。
|
||||
|
||||
### 源码使用方法
|
||||
|
||||
环境要求:Node.js 14+
|
||||
环境要求:Node.js 16+
|
||||
|
||||
```bash
|
||||
# 开发模式
|
||||
@@ -76,25 +107,9 @@ npm run pack:linux
|
||||
|
||||
<p><a href="https://github.com/lyswhut/lx-music-desktop"><img width="100%" src="https://github.com/lyswhut/lx-music-desktop/blob/master/doc/images/app.png" alt="lx-music UI"></a></p>
|
||||
|
||||
### 启动参数
|
||||
|
||||
目前软件已支持的启动参数如下:
|
||||
|
||||
- `-search` 启动软件时自动在搜索框搜索指定的内容,例如:`-search="突然的自我 - 伍佰"`
|
||||
- `-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"`
|
||||
|
||||
|
||||
### 常见问题
|
||||
|
||||
常见问题已移至:<https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md>
|
||||
常见问题已移至:<https://lyswhut.github.io/lx-music-doc/desktop/faq>
|
||||
|
||||
### 项目协议
|
||||
|
||||
|
||||
25
build-config/build-after-pack.js
Normal file
@@ -0,0 +1,25 @@
|
||||
const fs = require('fs').promises
|
||||
|
||||
// https://github.com/electron-userland/electron-builder/issues/4630
|
||||
// https://github.com/electron-userland/electron-builder/issues/4630#issuecomment-782020139
|
||||
|
||||
module.exports = async(context) => {
|
||||
const { electronPlatformName, appOutDir } = context
|
||||
if (electronPlatformName !== 'darwin') return
|
||||
const {
|
||||
productFilename,
|
||||
info: {
|
||||
_metadata: { macLanguagesInfoPlistStrings },
|
||||
},
|
||||
} = context.packager.appInfo
|
||||
|
||||
const resPath = `${appOutDir}/${productFilename}.app/Contents/Resources`
|
||||
|
||||
// 创建APP语言包文件
|
||||
return await Promise.all(
|
||||
Object.entries(macLanguagesInfoPlistStrings).map(([lang, config]) => {
|
||||
let infos = Object.entries(config).map(([k, v]) => `"${k}" = "${v}";`).join('\n')
|
||||
return fs.writeFile(`${resPath}/${lang}.lproj/InfoPlist.strings`, infos)
|
||||
}),
|
||||
)
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
sourceMap: isDev,
|
||||
}
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
const path = require('path')
|
||||
const ESLintPlugin = require('eslint-webpack-plugin')
|
||||
|
||||
module.exports = {
|
||||
target: 'electron-main',
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
libraryTarget: 'commonjs2',
|
||||
path: path.join(__dirname, '../../dist/electron'),
|
||||
library: {
|
||||
type: 'commonjs2',
|
||||
},
|
||||
path: path.join(__dirname, '../../dist'),
|
||||
},
|
||||
externals: [
|
||||
'font-list',
|
||||
'better-sqlite3',
|
||||
'bufferutil',
|
||||
'utf-8-validate',
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@main': path.join(__dirname, '../../src/main'),
|
||||
@@ -14,29 +23,22 @@ module.exports = {
|
||||
'@lyric': path.join(__dirname, '../../src/renderer-lyric'),
|
||||
'@common': path.join(__dirname, '../../src/common'),
|
||||
},
|
||||
extensions: ['*', '.js', '.json', '.node'],
|
||||
extensions: ['.tsx', '.ts', '.js', '.mjs', '.json', '.node'],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: {
|
||||
loader: 'eslint-loader',
|
||||
options: {
|
||||
formatter: require('eslint-formatter-friendly'),
|
||||
},
|
||||
},
|
||||
exclude: /node_modules/,
|
||||
enforce: 'pre',
|
||||
test: /\.node$/,
|
||||
use: 'node-loader',
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
// {
|
||||
// test: /\.js$/,
|
||||
// loader: 'babel-loader',
|
||||
// exclude: /node_modules/,
|
||||
// },
|
||||
],
|
||||
},
|
||||
performance: {
|
||||
maxEntrypointSize: 300000,
|
||||
},
|
||||
plugins: [
|
||||
new ESLintPlugin(),
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
const path = require('path')
|
||||
const { merge } = require('webpack-merge')
|
||||
const webpack = require('webpack')
|
||||
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
|
||||
|
||||
const baseConfig = require('./webpack.config.base')
|
||||
|
||||
@@ -9,27 +8,21 @@ const baseConfig = require('./webpack.config.base')
|
||||
module.exports = merge(baseConfig, {
|
||||
mode: 'development',
|
||||
entry: {
|
||||
main: path.join(__dirname, '../../src/main/index.dev.js'),
|
||||
main: path.join(__dirname, '../../src/main/index-dev.ts'),
|
||||
// 'dbService.worker': path.join(__dirname, '../../src/main/worker/dbService/index.ts'),
|
||||
},
|
||||
devtool: 'eval-source-map',
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
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'
|
||||
},
|
||||
webpackStaticPath: `"${path.join(__dirname, '../../src/static').replace(/\\/g, '\\\\')}"`,
|
||||
webpackUserApiPath: `"${path.join(__dirname, '../../src/main/modules/userApi').replace(/\\/g, '\\\\')}"`,
|
||||
}),
|
||||
],
|
||||
performance: {
|
||||
maxEntrypointSize: 1024 * 1024 * 50,
|
||||
maxAssetSize: 1024 * 1024 * 30,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -5,17 +5,17 @@ const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
|
||||
const baseConfig = require('./webpack.config.base')
|
||||
|
||||
const { dependencies } = require('../../package.json')
|
||||
// const { dependencies } = require('../../package.json')
|
||||
|
||||
const buildConfig = require('../webpack-build-config')
|
||||
|
||||
|
||||
module.exports = merge(baseConfig, {
|
||||
mode: 'production',
|
||||
entry: {
|
||||
main: path.join(__dirname, '../../src/main/index.js'),
|
||||
main: path.join(__dirname, '../../src/main/index.ts'),
|
||||
// 'dbService.worker': path.join(__dirname, '../../src/main/worker/dbService/index.ts'),
|
||||
},
|
||||
externals: [
|
||||
...Object.keys(dependencies || {}),
|
||||
],
|
||||
node: {
|
||||
__dirname: false,
|
||||
__filename: false,
|
||||
@@ -24,12 +24,12 @@ module.exports = merge(baseConfig, {
|
||||
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/renderer/user-api.html'),
|
||||
to: path.join(__dirname, '../../dist/userApi/renderer/user-api.html'),
|
||||
},
|
||||
{
|
||||
from: path.join(__dirname, '../../src/main/modules/userApi/rendererEvent/name.js'),
|
||||
to: path.join(__dirname, '../../dist/electron/userApi/rendererEvent/name.js'),
|
||||
from: path.join(__dirname, '../../src/common/theme/images/*').replace(/\\/g, '/'),
|
||||
to: path.join(__dirname, '../../dist/theme_images/[name][ext]'),
|
||||
},
|
||||
],
|
||||
}),
|
||||
@@ -39,4 +39,11 @@ module.exports = merge(baseConfig, {
|
||||
},
|
||||
}),
|
||||
],
|
||||
performance: {
|
||||
maxEntrypointSize: 1024 * 1024 * 10,
|
||||
maxAssetSize: 1024 * 1024 * 20,
|
||||
},
|
||||
optimization: {
|
||||
minimize: buildConfig.minimize,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -5,21 +5,25 @@ const del = require('del')
|
||||
const webpack = require('webpack')
|
||||
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 mainConfig = './main/webpack.config.prod'
|
||||
const rendererConfig = './renderer/webpack.config.prod'
|
||||
const rendererLyricConfig = './renderer-lyric/webpack.config.prod'
|
||||
const rendererScriptConfig = './renderer-scripts/webpack.config.prod'
|
||||
|
||||
const errorLog = chalk.bgRed.white(' ERROR ') + ' '
|
||||
const okayLog = chalk.bgGreen.white(' OKAY ') + ' '
|
||||
|
||||
const { Worker, isMainThread, parentPort } = require('worker_threads')
|
||||
|
||||
|
||||
function build() {
|
||||
del.sync(['dist/electron/**', 'build/**'])
|
||||
del.sync(['dist/**', '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' })
|
||||
spinners.add('renderer-scripts', { text: 'renderer-scripts building' })
|
||||
let results = ''
|
||||
|
||||
// m.on('success', () => {
|
||||
@@ -63,11 +67,35 @@ function build() {
|
||||
console.error(`\n${err}\n`)
|
||||
process.exit(1)
|
||||
}),
|
||||
pack(rendererScriptConfig).then(result => {
|
||||
results += result + '\n\n'
|
||||
spinners.succeed('renderer-scripts', { text: 'renderer-scripts build success!' })
|
||||
}).catch(err => {
|
||||
spinners.fail('renderer-scripts', { text: 'renderer-scripts build fail :(' })
|
||||
console.log(`\n ${errorLog}failed to build renderer-scripts process`)
|
||||
console.error(`\n${err}\n`)
|
||||
process.exit(1)
|
||||
}),
|
||||
]).then(handleSuccess)
|
||||
}
|
||||
|
||||
function pack(config) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const worker = new Worker(__filename)
|
||||
const subChannel = new MessageChannel()
|
||||
worker.postMessage({ port: subChannel.port1, config }, [subChannel.port1])
|
||||
subChannel.port2.on('message', ({ status, message }) => {
|
||||
switch (status) {
|
||||
case 'success': return resolve(message)
|
||||
case 'error': return reject(message)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function runPack(config) {
|
||||
return new Promise((resolve, reject) => {
|
||||
config = require(config)
|
||||
config.mode = 'production'
|
||||
webpack(config, (err, stats) => {
|
||||
if (err) reject(err.stack || err)
|
||||
@@ -95,5 +123,22 @@ function pack(config) {
|
||||
})
|
||||
}
|
||||
|
||||
build()
|
||||
|
||||
if (isMainThread) build()
|
||||
else {
|
||||
parentPort.once('message', ({ port, config }) => {
|
||||
// assert(port instanceof MessagePort)
|
||||
runPack(config).then((result) => {
|
||||
port.postMessage({
|
||||
status: 'success',
|
||||
message: result,
|
||||
})
|
||||
}).catch((err) => {
|
||||
port.postMessage({
|
||||
status: 'error',
|
||||
message: err,
|
||||
})
|
||||
}).finally(() => {
|
||||
port.close()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
const path = require('path')
|
||||
const VueLoaderPlugin = require('vue-loader/lib/plugin')
|
||||
const { VueLoaderPlugin } = require('vue-loader')
|
||||
const HTMLPlugin = require('html-webpack-plugin')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const CleanCSSPlugin = require('less-plugin-clean-css')
|
||||
const ESLintPlugin = require('eslint-webpack-plugin')
|
||||
|
||||
const vueLoaderConfig = require('../vue-loader.config')
|
||||
const { mergeCSSLoader } = require('../utils')
|
||||
@@ -12,37 +12,47 @@ const isDev = process.env.NODE_ENV === 'development'
|
||||
module.exports = {
|
||||
target: 'electron-renderer',
|
||||
entry: {
|
||||
'renderer-lyric': path.join(__dirname, '../../src/renderer-lyric/main.js'),
|
||||
'renderer-lyric': path.join(__dirname, '../../src/renderer-lyric/main.ts'),
|
||||
},
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
libraryTarget: 'commonjs2',
|
||||
path: path.join(__dirname, '../../dist/electron'),
|
||||
publicPath: './',
|
||||
library: {
|
||||
type: 'commonjs2',
|
||||
},
|
||||
path: path.join(__dirname, '../../dist'),
|
||||
publicPath: 'auto',
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.join(__dirname, '../../src'),
|
||||
'@main': path.join(__dirname, '../../src/main'),
|
||||
'@renderer': path.join(__dirname, '../../src/renderer'),
|
||||
'@lyric': path.join(__dirname, '../../src/renderer-lyric'),
|
||||
'@static': path.join(__dirname, '../../src/static'),
|
||||
'@common': path.join(__dirname, '../../src/common'),
|
||||
},
|
||||
extensions: ['*', '.js', '.json', '.vue', '.node'],
|
||||
extensions: ['.tsx', '.ts', '.js', '.json', '.vue', '.node'],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(vue|js)$/,
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'eslint-loader',
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
formatter: require('eslint-formatter-friendly'),
|
||||
emitWarning: isDev,
|
||||
appendTsSuffixTo: [/\.vue$/],
|
||||
},
|
||||
},
|
||||
exclude: /node_modules/,
|
||||
enforce: 'pre',
|
||||
},
|
||||
{
|
||||
test: /\.node$/,
|
||||
use: 'node-loader',
|
||||
},
|
||||
{
|
||||
test: /\.vue$/,
|
||||
@@ -50,9 +60,8 @@ module.exports = {
|
||||
options: vueLoaderConfig,
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/,
|
||||
test: /\.pug$/,
|
||||
loader: 'pug-plain-loader',
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
@@ -64,73 +73,68 @@ module.exports = {
|
||||
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)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'imgs/[name]--[folder].[ext]',
|
||||
exclude: path.join(__dirname, '../../src/renderer/assets/svgs'),
|
||||
type: 'asset',
|
||||
parser: {
|
||||
dataUrlCondition: {
|
||||
maxSize: 10000,
|
||||
},
|
||||
},
|
||||
generator: {
|
||||
filename: 'imgs/[name]-[contenthash:8][ext]',
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
include: path.join(__dirname, '../../src/renderer/assets/svgs'),
|
||||
use: [
|
||||
{
|
||||
loader: 'svg-sprite-loader',
|
||||
options: {
|
||||
symbolId: 'icon-[name]',
|
||||
},
|
||||
},
|
||||
'svg-transform-loader',
|
||||
'svgo-loader',
|
||||
],
|
||||
},
|
||||
{
|
||||
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]',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
performance: {
|
||||
maxEntrypointSize: 300000,
|
||||
},
|
||||
plugins: [
|
||||
new HTMLPlugin({
|
||||
filename: 'lyric.html',
|
||||
template: path.join(__dirname, '../../src/renderer-lyric/index.pug'),
|
||||
template: path.join(__dirname, '../../src/renderer-lyric/index.html'),
|
||||
isProd: process.env.NODE_ENV == 'production',
|
||||
browser: process.browser,
|
||||
scriptLoading: 'blocking',
|
||||
__dirname,
|
||||
}),
|
||||
new VueLoaderPlugin(),
|
||||
@@ -140,5 +144,9 @@ module.exports = {
|
||||
filename: isDev ? '[name].css' : '[name].[contenthash:8].css',
|
||||
chunkFilename: isDev ? '[id].css' : '[id].[contenthash:8].css',
|
||||
}),
|
||||
new ESLintPlugin({
|
||||
extensions: ['js', 'vue'],
|
||||
formatter: require('eslint-formatter-friendly'),
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
|
||||
|
||||
const { merge } = require('webpack-merge')
|
||||
|
||||
@@ -10,14 +9,14 @@ 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, '\\\\')}"`,
|
||||
__VUE_OPTIONS_API__: 'true',
|
||||
__VUE_PROD_DEVTOOLS__: 'false',
|
||||
staticPath: `"${path.join(__dirname, '../../src/static').replace(/\\/g, '\\\\')}"`,
|
||||
}),
|
||||
],
|
||||
performance: {
|
||||
|
||||
@@ -1,45 +1,43 @@
|
||||
const path = require('path')
|
||||
// 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 buildConfig = require('../webpack-build-config')
|
||||
|
||||
const { dependencies } = require('../../package.json')
|
||||
// const { dependencies } = require('../../package.json')
|
||||
|
||||
let whiteListedModules = ['vue']
|
||||
// let whiteListedModules = ['vue']
|
||||
// let whiteListedModules = ['vue', 'vue-router', 'vuex', 'vue-i18n']
|
||||
|
||||
|
||||
module.exports = merge(baseConfig, {
|
||||
mode: 'production',
|
||||
devtool: false,
|
||||
externals: [
|
||||
...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)),
|
||||
// ...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"',
|
||||
},
|
||||
__VUE_OPTIONS_API__: 'true',
|
||||
__VUE_PROD_DEVTOOLS__: 'false',
|
||||
}),
|
||||
],
|
||||
optimization: {
|
||||
minimize: buildConfig.minimize,
|
||||
minimizer: [
|
||||
new TerserPlugin(),
|
||||
new CssMinimizerPlugin(),
|
||||
],
|
||||
},
|
||||
performance: {
|
||||
maxEntrypointSize: 1024 * 1024 * 10,
|
||||
maxAssetSize: 1024 * 1024 * 20,
|
||||
hints: 'warning',
|
||||
},
|
||||
node: {
|
||||
|
||||
57
build-config/renderer-scripts/webpack.config.base.js
Normal file
@@ -0,0 +1,57 @@
|
||||
const path = require('path')
|
||||
const ESLintPlugin = require('eslint-webpack-plugin')
|
||||
|
||||
module.exports = {
|
||||
target: 'electron-renderer',
|
||||
entry: {
|
||||
'user-api-preload': path.join(__dirname, '../../src/main/modules/userApi/renderer/preload.js'),
|
||||
},
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
library: {
|
||||
type: 'commonjs2',
|
||||
},
|
||||
path: path.join(__dirname, '../../dist'),
|
||||
publicPath: 'auto',
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.join(__dirname, '../../src'),
|
||||
'@main': path.join(__dirname, '../../src/main'),
|
||||
'@renderer': path.join(__dirname, '../../src/renderer'),
|
||||
'@lyric': path.join(__dirname, '../../src/renderer-lyric'),
|
||||
'@static': path.join(__dirname, '../../src/static'),
|
||||
'@common': path.join(__dirname, '../../src/common'),
|
||||
},
|
||||
extensions: ['.tsx', '.ts', '.js', '.json', '.vue', '.node'],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
appendTsSuffixTo: [/\.vue$/],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.node$/,
|
||||
use: 'node-loader',
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new ESLintPlugin({
|
||||
extensions: ['js'],
|
||||
formatter: require('eslint-formatter-friendly'),
|
||||
}),
|
||||
],
|
||||
}
|
||||
24
build-config/renderer-scripts/webpack.config.dev.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
|
||||
const { merge } = require('webpack-merge')
|
||||
|
||||
const baseConfig = require('./webpack.config.base')
|
||||
|
||||
module.exports = merge(baseConfig, {
|
||||
mode: 'development',
|
||||
devtool: 'eval-source-map',
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: '"development"',
|
||||
ELECTRON_DISABLE_SECURITY_WARNINGS: 'true',
|
||||
},
|
||||
staticPath: `"${path.join(__dirname, '../../src/static').replace(/\\/g, '\\\\')}"`,
|
||||
}),
|
||||
],
|
||||
performance: {
|
||||
hints: false,
|
||||
},
|
||||
})
|
||||
|
||||
45
build-config/renderer-scripts/webpack.config.prod.js
Normal file
@@ -0,0 +1,45 @@
|
||||
// const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
const TerserPlugin = require('terser-webpack-plugin')
|
||||
const { merge } = require('webpack-merge')
|
||||
|
||||
const baseConfig = require('./webpack.config.base')
|
||||
const buildConfig = require('../webpack-build-config')
|
||||
|
||||
// const { dependencies } = require('../../package.json')
|
||||
|
||||
// let whiteListedModules = ['vue']
|
||||
// let whiteListedModules = ['vue', 'vue-router', 'vuex', 'vue-i18n']
|
||||
|
||||
|
||||
module.exports = merge(baseConfig, {
|
||||
mode: 'production',
|
||||
devtool: false,
|
||||
externals: [
|
||||
// ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)),
|
||||
],
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: '"production"',
|
||||
},
|
||||
}),
|
||||
],
|
||||
optimization: {
|
||||
minimize: buildConfig.minimize,
|
||||
minimizer: [
|
||||
new TerserPlugin(),
|
||||
],
|
||||
},
|
||||
performance: {
|
||||
maxEntrypointSize: 1024 * 1024 * 10,
|
||||
maxAssetSize: 1024 * 1024 * 20,
|
||||
hints: 'warning',
|
||||
},
|
||||
node: {
|
||||
__dirname: false,
|
||||
__filename: false,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
const path = require('path')
|
||||
const VueLoaderPlugin = require('vue-loader/lib/plugin')
|
||||
const { VueLoaderPlugin } = require('vue-loader')
|
||||
const HTMLPlugin = require('html-webpack-plugin')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const CleanCSSPlugin = require('less-plugin-clean-css')
|
||||
const ESLintPlugin = require('eslint-webpack-plugin')
|
||||
|
||||
const vueLoaderConfig = require('../vue-loader.config')
|
||||
const { mergeCSSLoader } = require('../utils')
|
||||
@@ -12,37 +12,47 @@ const isDev = process.env.NODE_ENV === 'development'
|
||||
module.exports = {
|
||||
target: 'electron-renderer',
|
||||
entry: {
|
||||
renderer: path.join(__dirname, '../../src/renderer/main.js'),
|
||||
renderer: path.join(__dirname, '../../src/renderer/main.ts'),
|
||||
},
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
libraryTarget: 'commonjs2',
|
||||
path: path.join(__dirname, '../../dist/electron'),
|
||||
publicPath: './',
|
||||
library: {
|
||||
type: 'commonjs2',
|
||||
},
|
||||
path: path.join(__dirname, '../../dist'),
|
||||
publicPath: 'auto',
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.join(__dirname, '../../src'),
|
||||
'@main': path.join(__dirname, '../../src/main'),
|
||||
'@renderer': path.join(__dirname, '../../src/renderer'),
|
||||
'@lyric': path.join(__dirname, '../../src/renderer-lyric'),
|
||||
'@static': path.join(__dirname, '../../src/static'),
|
||||
'@common': path.join(__dirname, '../../src/common'),
|
||||
},
|
||||
extensions: ['*', '.js', '.json', '.vue', '.node'],
|
||||
extensions: ['.tsx', '.ts', '.js', '.json', '.vue', '.node'],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(vue|js)$/,
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'eslint-loader',
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
formatter: require('eslint-formatter-friendly'),
|
||||
emitWarning: isDev,
|
||||
appendTsSuffixTo: [/\.vue$/],
|
||||
},
|
||||
},
|
||||
exclude: /node_modules/,
|
||||
enforce: 'pre',
|
||||
},
|
||||
{
|
||||
test: /\.node$/,
|
||||
use: 'node-loader',
|
||||
},
|
||||
{
|
||||
test: /\.vue$/,
|
||||
@@ -50,9 +60,8 @@ module.exports = {
|
||||
options: vueLoaderConfig,
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/,
|
||||
test: /\.pug$/,
|
||||
loader: 'pug-plain-loader',
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
@@ -64,73 +73,68 @@ module.exports = {
|
||||
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)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'imgs/[name]--[folder].[ext]',
|
||||
exclude: path.join(__dirname, '../../src/renderer/assets/svgs'),
|
||||
type: 'asset',
|
||||
parser: {
|
||||
dataUrlCondition: {
|
||||
maxSize: 10000,
|
||||
},
|
||||
},
|
||||
generator: {
|
||||
filename: 'imgs/[name]-[contenthash:8][ext]',
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
include: path.join(__dirname, '../../src/renderer/assets/svgs'),
|
||||
use: [
|
||||
{
|
||||
loader: 'svg-sprite-loader',
|
||||
options: {
|
||||
symbolId: 'icon-[name]',
|
||||
},
|
||||
},
|
||||
'svg-transform-loader',
|
||||
'svgo-loader',
|
||||
],
|
||||
},
|
||||
{
|
||||
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]',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
performance: {
|
||||
maxEntrypointSize: 300000,
|
||||
},
|
||||
plugins: [
|
||||
new HTMLPlugin({
|
||||
filename: 'index.html',
|
||||
template: path.join(__dirname, '../../src/renderer/index.pug'),
|
||||
template: path.join(__dirname, '../../src/renderer/index.html'),
|
||||
isProd: process.env.NODE_ENV == 'production',
|
||||
browser: process.browser,
|
||||
scriptLoading: 'blocking',
|
||||
__dirname,
|
||||
}),
|
||||
new VueLoaderPlugin(),
|
||||
@@ -140,5 +144,9 @@ module.exports = {
|
||||
filename: isDev ? '[name].css' : '[name].[contenthash:8].css',
|
||||
chunkFilename: isDev ? '[id].css' : '[id].[contenthash:8].css',
|
||||
}),
|
||||
new ESLintPlugin({
|
||||
extensions: ['js', 'vue'],
|
||||
formatter: require('eslint-formatter-friendly'),
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
|
||||
|
||||
const { merge } = require('webpack-merge')
|
||||
|
||||
@@ -10,14 +9,15 @@ 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, '\\\\')}"`,
|
||||
// ENVIRONMENT: 'process.env',
|
||||
__VUE_OPTIONS_API__: 'true',
|
||||
__VUE_PROD_DEVTOOLS__: 'false',
|
||||
staticPath: `"${path.join(__dirname, '../../src/static').replace(/\\/g, '\\\\')}"`,
|
||||
}),
|
||||
],
|
||||
performance: {
|
||||
|
||||
@@ -6,24 +6,25 @@ const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
const { merge } = require('webpack-merge')
|
||||
|
||||
const baseConfig = require('./webpack.config.base')
|
||||
const buildConfig = require('../webpack-build-config')
|
||||
|
||||
const { dependencies } = require('../../package.json')
|
||||
// const { dependencies } = require('../../package.json')
|
||||
|
||||
let whiteListedModules = ['vue']
|
||||
// let whiteListedModules = ['vue', 'vue-router', 'vuex', 'vue-i18n']
|
||||
|
||||
|
||||
module.exports = merge(baseConfig, {
|
||||
mode: 'production',
|
||||
devtool: false,
|
||||
externals: [
|
||||
...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)),
|
||||
// ...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'),
|
||||
to: path.join(__dirname, '../../dist/static'),
|
||||
},
|
||||
],
|
||||
}),
|
||||
@@ -31,15 +32,25 @@ module.exports = merge(baseConfig, {
|
||||
'process.env': {
|
||||
NODE_ENV: '"production"',
|
||||
},
|
||||
// ENVIRONMENT: 'process.env',
|
||||
__VUE_OPTIONS_API__: 'true',
|
||||
__VUE_PROD_DEVTOOLS__: 'false',
|
||||
}),
|
||||
],
|
||||
optimization: {
|
||||
minimize: buildConfig.minimize,
|
||||
minimizer: [
|
||||
new TerserPlugin(),
|
||||
new CssMinimizerPlugin(),
|
||||
],
|
||||
splitChunks: {
|
||||
chunks: 'initial',
|
||||
minChunks: 2,
|
||||
},
|
||||
},
|
||||
performance: {
|
||||
maxEntrypointSize: 1024 * 1024 * 10,
|
||||
maxAssetSize: 1024 * 1024 * 20,
|
||||
hints: 'warning',
|
||||
},
|
||||
node: {
|
||||
|
||||
@@ -13,10 +13,12 @@ const webpackHotMiddleware = require('webpack-hot-middleware')
|
||||
const mainConfig = require('./main/webpack.config.dev')
|
||||
const rendererConfig = require('./renderer/webpack.config.dev')
|
||||
const rendererLyricConfig = require('./renderer-lyric/webpack.config.dev')
|
||||
const rendererScriptConfig = require('./renderer-scripts/webpack.config.dev')
|
||||
|
||||
let electronProcess = null
|
||||
let manualRestart = false
|
||||
let hotMiddleware
|
||||
let hotMiddlewareRenderer
|
||||
let hotMiddlewareRendererLyric
|
||||
|
||||
|
||||
function startRenderer() {
|
||||
@@ -24,7 +26,7 @@ function startRenderer() {
|
||||
// rendererConfig.entry.renderer = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.renderer)
|
||||
// rendererConfig.mode = 'development'
|
||||
const compiler = webpack(rendererConfig)
|
||||
hotMiddleware = webpackHotMiddleware(compiler, {
|
||||
hotMiddlewareRenderer = webpackHotMiddleware(compiler, {
|
||||
log: false,
|
||||
heartbeat: 2500,
|
||||
})
|
||||
@@ -32,37 +34,37 @@ function startRenderer() {
|
||||
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' })
|
||||
hotMiddlewareRenderer.publish({ action: 'reload' })
|
||||
cb()
|
||||
})
|
||||
})
|
||||
|
||||
compiler.hooks.done.tap('done', stats => {
|
||||
// logStats('Renderer', 'Compile done')
|
||||
// logStats('Renderer', stats)
|
||||
})
|
||||
// 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()
|
||||
})
|
||||
},
|
||||
const server = new WebpackDevServer({
|
||||
port: 9080,
|
||||
hot: true,
|
||||
historyApiFallback: true,
|
||||
static: {
|
||||
directory: path.join(__dirname, '../src/common/theme/images'),
|
||||
publicPath: '/theme_images',
|
||||
},
|
||||
)
|
||||
client: {
|
||||
logging: 'warn',
|
||||
overlay: true,
|
||||
},
|
||||
setupMiddlewares(middlewares, devServer) {
|
||||
devServer.app.use(hotMiddlewareRenderer)
|
||||
devServer.middleware.waitUntilValid(resolve)
|
||||
|
||||
server.listen(9080)
|
||||
return middlewares
|
||||
},
|
||||
}, compiler)
|
||||
|
||||
server.start()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -71,7 +73,7 @@ function startRendererLyric() {
|
||||
// rendererConfig.entry.renderer = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.renderer)
|
||||
// rendererConfig.mode = 'development'
|
||||
const compiler = webpack(rendererLyricConfig)
|
||||
hotMiddleware = webpackHotMiddleware(compiler, {
|
||||
hotMiddlewareRendererLyric = webpackHotMiddleware(compiler, {
|
||||
log: false,
|
||||
heartbeat: 2500,
|
||||
})
|
||||
@@ -79,37 +81,51 @@ function startRendererLyric() {
|
||||
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' })
|
||||
hotMiddlewareRendererLyric.publish({ action: 'reload' })
|
||||
cb()
|
||||
})
|
||||
})
|
||||
|
||||
compiler.hooks.done.tap('done', stats => {
|
||||
// logStats('Renderer', 'Compile done')
|
||||
// logStats('Renderer', stats)
|
||||
})
|
||||
// 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()
|
||||
})
|
||||
},
|
||||
const server = new WebpackDevServer({
|
||||
port: 9081,
|
||||
hot: true,
|
||||
historyApiFallback: true,
|
||||
// static: {
|
||||
// directory: path.join(__dirname, '../'),
|
||||
// },
|
||||
client: {
|
||||
logging: 'warn',
|
||||
overlay: true,
|
||||
},
|
||||
)
|
||||
setupMiddlewares(middlewares, devServer) {
|
||||
devServer.app.use(hotMiddlewareRenderer)
|
||||
devServer.middleware.waitUntilValid(resolve)
|
||||
return middlewares
|
||||
},
|
||||
}, compiler)
|
||||
|
||||
server.listen(9081)
|
||||
server.start()
|
||||
})
|
||||
}
|
||||
|
||||
function startRendererScripts() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// mainConfig.entry.main = [path.join(__dirname, '../src/main/index.dev.js')].concat(mainConfig.entry.main)
|
||||
// mainConfig.mode = 'development'
|
||||
const compiler = webpack(rendererScriptConfig)
|
||||
|
||||
compiler.watch({}, (err, stats) => {
|
||||
if (err) {
|
||||
console.log(err)
|
||||
return
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -120,7 +136,8 @@ function startMain() {
|
||||
const compiler = webpack(mainConfig)
|
||||
|
||||
compiler.hooks.watchRun.tapAsync('watch-run', (compilation, done) => {
|
||||
hotMiddleware.publish({ action: 'compiling' })
|
||||
hotMiddlewareRenderer.publish({ action: 'compiling' })
|
||||
hotMiddlewareRendererLyric.publish({ action: 'compiling' })
|
||||
done()
|
||||
})
|
||||
|
||||
@@ -152,7 +169,7 @@ function startElectron() {
|
||||
let args = [
|
||||
'--inspect=5858',
|
||||
// 'NODE_ENV=development',
|
||||
path.join(__dirname, '../dist/electron/main.js'),
|
||||
path.join(__dirname, '../dist/main.js'),
|
||||
]
|
||||
|
||||
// detect yarn or npm and process commandline args accordingly
|
||||
@@ -176,11 +193,19 @@ function startElectron() {
|
||||
})
|
||||
}
|
||||
|
||||
const logs = [
|
||||
'Manifest version 2 is deprecated, and support will be removed in 2023',
|
||||
'"Extension server error: Operation failed: Permission denied", source: devtools://devtools/bundled',
|
||||
|
||||
// https://github.com/electron/electron/issues/32133
|
||||
'"Electron sandbox_bundle.js script failed to run"',
|
||||
'"TypeError: object null is not iterable (cannot read property Symbol(Symbol.iterator))",',
|
||||
]
|
||||
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
|
||||
// 抑制某些无关的报错日志
|
||||
if (color == 'red' && typeof log === 'string' && logs.some(l => log.includes(l))) return
|
||||
|
||||
console.log(chalk[color](log))
|
||||
}
|
||||
@@ -192,6 +217,7 @@ function init() {
|
||||
spinners.add('main', { text: 'main compiling' })
|
||||
spinners.add('renderer', { text: 'renderer compiling' })
|
||||
spinners.add('renderer-lyric', { text: 'renderer-lyric compiling' })
|
||||
spinners.add('renderer-scripts', { text: 'renderer-scripts compiling' })
|
||||
function handleSuccess(name) {
|
||||
spinners.succeed(name, { text: name + ' compile success!' })
|
||||
}
|
||||
@@ -200,8 +226,18 @@ function init() {
|
||||
}
|
||||
|
||||
Promise.all([
|
||||
startRenderer().then(() => handleSuccess('renderer')).catch(() => handleFail('renderer')),
|
||||
startRendererLyric().then(() => handleSuccess('renderer-lyric')).catch(() => handleFail('renderer-lyric')),
|
||||
startRenderer().then(() => handleSuccess('renderer')).catch((err) => {
|
||||
console.error(err.message)
|
||||
return handleFail('renderer')
|
||||
}),
|
||||
startRendererLyric().then(() => handleSuccess('renderer-lyric')).catch((err) => {
|
||||
console.error(err.message)
|
||||
return handleFail('renderer-lyric')
|
||||
}),
|
||||
startRendererScripts().then(() => handleSuccess('renderer-scripts')).catch((err) => {
|
||||
console.error(err.message)
|
||||
return handleFail('renderer-scripts')
|
||||
}),
|
||||
startMain().then(() => handleSuccess('main')).catch(() => handleFail('main')),
|
||||
]).then(startElectron).catch(err => {
|
||||
console.error(err)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
module.exports = {
|
||||
status: 'status',
|
||||
minimize: false,
|
||||
}
|
||||
36586
package-lock.json
generated
244
package.json
@@ -1,15 +1,16 @@
|
||||
{
|
||||
"name": "lx-music-desktop",
|
||||
"version": "1.10.2",
|
||||
"version": "2.0.0",
|
||||
"description": "一个免费的音乐查找助手",
|
||||
"main": "./dist/electron/main.js",
|
||||
"main": "./dist/main.js",
|
||||
"productName": "lx-music-desktop",
|
||||
"scripts": {
|
||||
"pack": "node build-config/pack.js && npm run pack:win:setup:x64",
|
||||
"pack:win": "node build-config/pack.js && npm run pack:win:setup:x86_64 && npm run pack:win:setup:x64 && npm run pack:win:7z",
|
||||
"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",
|
||||
@@ -20,9 +21,8 @@
|
||||
"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": "npm run pack:linux:deb:x64 && npm run pack:linux:deb:arm64 && npm run pack:linux:deb:armv7l",
|
||||
"pack:linux:deb:x64": "cross-env ARCH=x64 electron-builder -l=deb --x64 -p never",
|
||||
"pack:linux:deb: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",
|
||||
@@ -35,6 +35,7 @@
|
||||
"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",
|
||||
@@ -48,49 +49,71 @@
|
||||
"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",
|
||||
"dev": "cross-env NODE_OPTIONS=--max-http-header-size=200000 node build-config/runner-dev.js",
|
||||
"clean:electron": "rimraf dist",
|
||||
"clean": "rimraf dist && rimraf build",
|
||||
"build:theme": "node src/common/theme/createThemes.js",
|
||||
"build:src": "node build-config/pack.js",
|
||||
"build: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",
|
||||
"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"
|
||||
"build:renderer-scripts": "cross-env NODE_ENV=production webpack --config build-config/renderer-scripts/webpack.config.prod.js --progress",
|
||||
"build": "npm run clean:electron && npm run build:main && npm run build:renderer && npm run build:renderer-lyric && npm run build:renderer-scripts",
|
||||
"lint": "eslint --ext .js,.vue -f node_modules/eslint-formatter-friendly src",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"lint:fix": "eslint --ext .js,.vue -f node_modules/eslint-formatter-friendly --fix src",
|
||||
"dp": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://127.0.0.1:1081 npm run pack",
|
||||
"up": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://127.0.0.1:1081 npm i"
|
||||
},
|
||||
"browserslist": [
|
||||
"Electron 12.0.9"
|
||||
"Electron 19.1.0"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
"node": ">= 16",
|
||||
"npm": ">=8.5.2"
|
||||
},
|
||||
"build": {
|
||||
"appId": "cn.toside.music.desktop",
|
||||
"afterPack": "./build-config/build-after-pack.js",
|
||||
"protocols": {
|
||||
"name": "lx-music-protocol",
|
||||
"schemes": [
|
||||
"lxmusic"
|
||||
]
|
||||
},
|
||||
"directories": {
|
||||
"buildResources": "./resources",
|
||||
"output": "./build"
|
||||
},
|
||||
"files": [
|
||||
"dist/electron/**/*"
|
||||
"!node_modules/**/*",
|
||||
"node_modules/font-list",
|
||||
"node_modules/better-sqlite3/lib",
|
||||
"node_modules/better-sqlite3/package.json",
|
||||
"node_modules/better-sqlite3/build/Release/better_sqlite3.node",
|
||||
"node_modules/node-gyp-build",
|
||||
"node_modules/bufferutil",
|
||||
"node_modules/utf-8-validate",
|
||||
"dist/**/*"
|
||||
],
|
||||
"asar": {
|
||||
"smartUnpack": false
|
||||
},
|
||||
"extraResources": [
|
||||
"./licenses"
|
||||
],
|
||||
"win": {
|
||||
"icon": "./resources/icons/256x256.ico",
|
||||
"icon": "./resources/icons/icon.ico",
|
||||
"legalTrademarks": "lyswhut",
|
||||
"artifactName": "${productName} v${version} ${env.ARCH} ${env.TARGET}.${ext}"
|
||||
},
|
||||
"mac": {
|
||||
"icon": "./resources/icons/512x512.icns",
|
||||
"icon": "./resources/icons/icon.icns",
|
||||
"category": "public.app-category.music"
|
||||
},
|
||||
"linux": {
|
||||
@@ -99,7 +122,12 @@
|
||||
"icon": "./resources/icons",
|
||||
"category": "Utility;AudioVideo;Audio;Player;Music;",
|
||||
"desktop": {
|
||||
"Name[zh_CN]": "洛雪音乐助手"
|
||||
"Name": "LX Music",
|
||||
"Name[zh_CN]": "LX Music",
|
||||
"Name[zh_TW]": "LX Music",
|
||||
"Encoding": "UTF-8",
|
||||
"MimeType": "x-scheme-handler/lxmusic",
|
||||
"StartupNotify": "false"
|
||||
}
|
||||
},
|
||||
"nsis": {
|
||||
@@ -142,6 +170,20 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"macLanguagesInfoPlistStrings": {
|
||||
"en": {
|
||||
"CFBundleDisplayName": "LX Music",
|
||||
"CFBundleName": "LX Music"
|
||||
},
|
||||
"zh_CN": {
|
||||
"CFBundleDisplayName": "LX Music",
|
||||
"CFBundleName": "LX Music"
|
||||
},
|
||||
"zh_TW": {
|
||||
"CFBundleDisplayName": "LX Music",
|
||||
"CFBundleName": "LX Music"
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/lyswhut/lx-music-desktop.git"
|
||||
@@ -149,7 +191,7 @@
|
||||
"keywords": [
|
||||
"music-player",
|
||||
"electron-app",
|
||||
"vuejs2"
|
||||
"vuejs3"
|
||||
],
|
||||
"author": {
|
||||
"name": "lyswhut",
|
||||
@@ -161,86 +203,108 @@
|
||||
},
|
||||
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.3",
|
||||
"@babel/plugin-proposal-class-properties": "^7.13.0",
|
||||
"@babel/core": "^7.20.7",
|
||||
"@babel/eslint-parser": "^7.19.1",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-modules-umd": "^7.14.0",
|
||||
"@babel/plugin-transform-runtime": "^7.14.3",
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"@babel/preset-env": "^7.14.2",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-minify-webpack-plugin": "^0.3.1",
|
||||
"babel-preset-minify": "^0.5.1",
|
||||
"browserslist": "^4.16.6",
|
||||
"cfonts": "^2.9.2",
|
||||
"chalk": "^4.1.1",
|
||||
"changelog-parser": "^2.8.0",
|
||||
"copy-webpack-plugin": "^9.0.0",
|
||||
"core-js": "^3.12.1",
|
||||
"@babel/plugin-transform-modules-umd": "^7.18.6",
|
||||
"@babel/plugin-transform-runtime": "^7.19.6",
|
||||
"@babel/preset-env": "^7.20.2",
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@types/better-sqlite3": "^7.6.3",
|
||||
"@types/needle": "^3.2.0",
|
||||
"@types/tunnel": "^0.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.47.1",
|
||||
"@typescript-eslint/parser": "^5.47.1",
|
||||
"@volar/vue-language-plugin-pug": "^1.0.19",
|
||||
"babel-loader": "^9.1.0",
|
||||
"browserslist": "^4.21.4",
|
||||
"chalk": "^4.1.2",
|
||||
"changelog-parser": "^3.0.1",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"core-js": "^3.27.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^5.2.6",
|
||||
"css-minimizer-webpack-plugin": "^3.0.0",
|
||||
"del": "^6.0.0",
|
||||
"electron": "^12.0.9",
|
||||
"electron-builder": "^22.11.4",
|
||||
"css-loader": "^6.7.3",
|
||||
"css-minimizer-webpack-plugin": "^4.2.2",
|
||||
"del": "^6.1.1",
|
||||
"electron": "^19.1.9",
|
||||
"electron-builder": "^24.0.0-alpha.8",
|
||||
"electron-debug": "^3.2.0",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"eslint": "^7.27.0",
|
||||
"eslint-config-standard": "^14.1.1",
|
||||
"eslint-formatter-friendly": "^7.0.0",
|
||||
"eslint-loader": "^4.0.2",
|
||||
"eslint-plugin-html": "^6.1.2",
|
||||
"eslint-plugin-import": "^2.23.3",
|
||||
"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": "^5.3.1",
|
||||
"less": "^4.1.1",
|
||||
"less-loader": "^9.0.0",
|
||||
"less-plugin-clean-css": "^1.5.1",
|
||||
"markdown-it": "^12.0.6",
|
||||
"mini-css-extract-plugin": "^1.6.0",
|
||||
"postcss": "^8.3.0",
|
||||
"postcss-loader": "^5.3.0",
|
||||
"electron-to-chromium": "^1.4.284",
|
||||
"electron-updater": "^6.0.0-alpha.6",
|
||||
"eslint": "^8.31.0",
|
||||
"eslint-config-standard": "^17.0.0",
|
||||
"eslint-config-standard-with-typescript": "^24.0.0",
|
||||
"eslint-formatter-friendly": "github:lyswhut/eslint-friendly-formatter#2170d1320e2fad13615a9dcf229669f0bb473a53",
|
||||
"eslint-plugin-html": "^7.1.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.6.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-vue": "^9.8.0",
|
||||
"eslint-webpack-plugin": "^3.2.0",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"less": "^4.1.3",
|
||||
"less-loader": "^11.1.0",
|
||||
"mini-css-extract-plugin": "^2.7.2",
|
||||
"node-loader": "^2.0.0",
|
||||
"postcss": "^8.4.20",
|
||||
"postcss-loader": "^7.0.2",
|
||||
"postcss-pxtorem": "^6.0.0",
|
||||
"pug": "^3.0.2",
|
||||
"pug-loader": "^2.4.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.0.0",
|
||||
"terser-webpack-plugin": "^5.1.2",
|
||||
"url-loader": "^4.1.1",
|
||||
"vue-loader": "^15.9.7",
|
||||
"vue-template-compiler": "^2.6.12",
|
||||
"webpack": "^5.37.1",
|
||||
"webpack-cli": "^4.7.0",
|
||||
"webpack-dev-server": "^3.11.2",
|
||||
"webpack-hot-middleware": "^2.25.0",
|
||||
"webpack-merge": "^5.7.3"
|
||||
"spinnies": "github:lyswhut/spinnies#233305c58694aa3b053e3ab9af9049993f918b9d",
|
||||
"svg-sprite-loader": "^6.0.11",
|
||||
"svg-transform-loader": "^2.0.13",
|
||||
"svgo-loader": "^3.0.3",
|
||||
"terser": "^5.16.1",
|
||||
"terser-webpack-plugin": "^5.3.6",
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^4.9.4",
|
||||
"vue-eslint-parser": "^9.1.0",
|
||||
"vue-loader": "^17.0.1",
|
||||
"vue-template-compiler": "^2.7.14",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-cli": "^5.0.1",
|
||||
"webpack-dev-server": "^4.11.1",
|
||||
"webpack-hot-middleware": "github:lyswhut/webpack-hot-middleware#329c4375134b89d39da23a56a94db651247c74a1",
|
||||
"webpack-merge": "^5.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"crypto-js": "^4.0.0",
|
||||
"dnscache": "^1.0.2",
|
||||
"electron-log": "^4.3.5",
|
||||
"electron-store": "^8.0.0",
|
||||
"electron-updater": "^4.3.9",
|
||||
"@simonwep/pickr": "^1.8.2",
|
||||
"better-sqlite3": "^8.0.1",
|
||||
"bufferutil": "^4.0.7",
|
||||
"comlink": "^4.3.1",
|
||||
"crypto-js": "^4.1.1",
|
||||
"electron-log": "^4.4.8",
|
||||
"electron-store": "^8.1.0",
|
||||
"font-list": "^1.4.5",
|
||||
"http-terminator": "^3.2.0",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"image-size": "^1.0.0",
|
||||
"js-htmlencode": "^0.3.0",
|
||||
"lrc-file-parser": "^1.0.7",
|
||||
"needle": "^2.6.0",
|
||||
"node-id3": "^0.2.3",
|
||||
"request": "^2.88.2",
|
||||
"vue": "^2.6.12",
|
||||
"vue-i18n": "^8.24.4",
|
||||
"vue-router": "^3.5.1",
|
||||
"vuex": "^3.6.2",
|
||||
"vuex-router-sync": "^5.0.0"
|
||||
"image-size": "^1.0.2",
|
||||
"jschardet": "^3.0.0",
|
||||
"koa": "^2.14.1",
|
||||
"long": "^5.2.1",
|
||||
"music-metadata": "^8.1.0",
|
||||
"needle": "github:lyswhut/needle#93299ac841b7e9a9f82ca7279b88aaaeda404060",
|
||||
"node-id3": "^0.2.5",
|
||||
"socket.io": "^4.5.4",
|
||||
"sortablejs": "^1.15.0",
|
||||
"tunnel": "^0.0.6",
|
||||
"utf-8-validate": "^5.0.10",
|
||||
"vue": "^3.2.45",
|
||||
"vue-router": "^4.1.6"
|
||||
},
|
||||
"overrides": {
|
||||
"got": "^11",
|
||||
"json5": "latest",
|
||||
"minimatch": "latest",
|
||||
"svg-sprite-loader": {
|
||||
"postcss": "latest"
|
||||
},
|
||||
"svg-baker": {
|
||||
"postcss": "latest"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ module.exports = {
|
||||
'*-height', '*-width',
|
||||
'flex', '::-webkit-scrollbar',
|
||||
'top', 'left', 'bottom', 'right',
|
||||
'border-radius',
|
||||
'border-radius', 'gap',
|
||||
],
|
||||
selectorBlackList: ['html', 'ignore-to-rem'],
|
||||
replace: true,
|
||||
|
||||
@@ -1,3 +1,59 @@
|
||||
|
||||
### 不兼容性变更说明
|
||||
|
||||
- 数据迁移,升级此版本时,会使用旧版本的我的列表、下载设置、快捷键设置、自定义源等数据会自动迁移到新的数据格式版本,旧的数据仍然会保留,但下载列表的数据不做迁移
|
||||
- 备份文件,v2.0.0及以后版本导出的列表、配置不支持导入v2.0.0之前版本,但v2.0.0之前版本导出的列表、配置支持导入v2.0.0以及以后版本(移动端需v0.15.0起才支持导入PC端v2生成的备份数据)
|
||||
- 同步功能,该功能不支持与移动端v1.0.0之前版本的使用,需等待后面的新版移动端,目前移动端v1的开发工作已在进行中
|
||||
|
||||
### 新增
|
||||
|
||||
- 新增自定义主题功能
|
||||
- 新增歌单搜索功能
|
||||
- 新增将本地歌曲添加到我的列表的支持,此功能可以在列表的右击菜单中使用(本地歌曲的歌词优先尝试读取相同路径下的同名歌词文件,若文件不存在则尝试读取歌曲文件内的歌词,若还是找不到歌词则尝试利用换源功能获取在线歌词,歌曲封面则是尝试读取歌曲文件内的封面,若不存在则利用换源功能获取在线封面)
|
||||
- 启动软件时自动回到上次的界面,例如上次退出软件时在我的列表,下次启动软件时会自动进入我的列表
|
||||
- 新增启动软件时自动播放音乐设置,默认关闭,可去设置-播放设置开启
|
||||
- 新增“蛋雅深藍”、“近墨者黑”皮肤
|
||||
- 新增下载歌词时是否同时下载歌词翻译、罗马音设置,默认关闭,可以去设置-下载设置开启(#344)
|
||||
- 新增下载时,若目录存在同名的文件时是否跳过下载此任务的设置(默认跳过,可以去设置-下载设置更改)
|
||||
- 新增界面字体大小设置
|
||||
- 桌面歌词新增竖排歌词显示功能(#971)
|
||||
- 桌面歌词新增歌词对齐方式、是否不允许歌词换行、歌词颜色、滚动对齐方式、歌词间距设置
|
||||
- 桌面歌词新增歌曲频谱显示(得益于主窗口与桌面歌词进程通信的改进,可以将此功能以CPU使用率“相对较低”的方式带到桌面歌词中)
|
||||
- 桌面歌词新增在任务栏显示歌词进程设置(此设置用于在录屏软件无法捕获歌词窗口时的变通解决方法)(#1063)
|
||||
- 添加kg源罗马音歌词的支持(感谢@helloplhm-qwq)
|
||||
- 支持打开波点音乐歌单(需在酷我源打开)
|
||||
- 新增设置-基本设置-播放栏进度条样式设置(此版本默认使用迷你进度条样式,对于某些不喜欢该样式的人可以将其换成其他样式)
|
||||
- 添加kg源评论图片展示(感谢@helloplhm-qwq)
|
||||
|
||||
### 优化(界面/交互/功能)
|
||||
|
||||
- 调整软件界面及配色,使其更加清爽
|
||||
- 处于单曲循环、顺序播放、禁用切歌模式时,手动切歌将会按列表循环模式的逻辑处理切歌(#864)
|
||||
- 歌单右键菜单的“重复歌曲”扫描功能现在会将歌曲名字内的括号内容移除再对比,这可以有效找出歌曲的变体,例如:`突然的自我`、`突然的自我(Live)`、`突然的自我(女生版)`、`突然的自我(DJ版)`等都会被找出来(#987)
|
||||
- 允许更小的桌面歌词窗口高度,可以取消“不允许拖动到主屏幕之外”设置后,再启用“不允许歌词换行”、“置顶歌词”与“自动刷新置顶”等设置,把它拖动到任务栏上,当做任务栏歌词使用(具体可以按你想要的显示方式使用这些设置组合去调)
|
||||
|
||||
### 优化(程序)
|
||||
|
||||
- 优化程序启动性能,优化与程序交互的流畅度
|
||||
- 重构整个程序,重新梳理了程序逻辑,使其更容易扩展及维护,将大部分代码从JavaScript迁移到TypeScript
|
||||
- 重写配置管理、列表管理功能,列表、歌词数据从json文件迁移到sqlite3存储,这应该能解决因为意外的字符编码导致的数据文件损坏问题
|
||||
|
||||
### 变更
|
||||
|
||||
- 列表右侧的操作按钮栏默认不再显示,歌曲的操作可以使用右键菜单代替,若想恢复它们的显示,可以去设置-列表设置-启用操作按钮栏开启
|
||||
- 窗口大小设置时不再自动调整字体大小,想要调整字体大小可以使用新增的字体大小设置调整
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复企鹅音乐搜索歌曲没有结果的问题
|
||||
- 修复Linux、macOS下若程序路径存在百分号时会导致软件无法启动的问题(#963)
|
||||
- 支持单行多时间标签歌词解析,修复某些歌词会出现时间标签的问题
|
||||
|
||||
### 移除
|
||||
|
||||
- 移除“信口雌黄”皮肤(由于该皮肤的配色有点刺眼),若你正在使用该皮肤,可以使用自定义主题功能恢复它
|
||||
- 移除Linux deb x86包构建,Electron/Chromium已不再支持 32-bit Linux(electron/electron#34787)
|
||||
- 移除桌面歌词主题设置,改用桌面歌词字体颜色设置功能代替
|
||||
|
||||
### 其他
|
||||
|
||||
- 更新Electron到v19.1.9
|
||||
|
||||
@@ -9,12 +9,12 @@ const version_bak = JSON.stringify(version, null, 2)
|
||||
const parseChangelog = require('changelog-parser')
|
||||
const changelogPath = jp('../../CHANGELOG.md')
|
||||
|
||||
const md_renderer = markdownStr => new (require('markdown-it'))({
|
||||
html: true,
|
||||
linkify: true,
|
||||
typographer: true,
|
||||
breaks: true,
|
||||
}).render(markdownStr)
|
||||
// const md_renderer = markdownStr => new (require('markdown-it'))({
|
||||
// html: true,
|
||||
// linkify: true,
|
||||
// typographer: true,
|
||||
// breaks: true,
|
||||
// }).render(markdownStr)
|
||||
|
||||
const getPrevVer = () => parseChangelog(changelogPath).then(res => {
|
||||
if (!res.versions.length) throw new Error('CHANGELOG 无法解析到版本号')
|
||||
@@ -28,7 +28,7 @@ const updateChangeLog = async(newVerNum, newChangeLog) => {
|
||||
fs.writeFileSync(changelogPath, changeLog.replace(new RegExp('(## [?0.1.1]?)'), log + '\n$1'), 'utf-8')
|
||||
}
|
||||
|
||||
const renderChangeLog = md => md_renderer(md)
|
||||
// const renderChangeLog = md => md_renderer(md)
|
||||
|
||||
|
||||
module.exports = async newVerNum => {
|
||||
@@ -38,7 +38,7 @@ module.exports = async newVerNum => {
|
||||
newVerNum = verArr.join('.')
|
||||
}
|
||||
const newMDChangeLog = fs.readFileSync(jp('../changeLog.md'), 'utf-8')
|
||||
const newChangeLog = renderChangeLog(newMDChangeLog)
|
||||
// const newChangeLog = renderChangeLog(newMDChangeLog)
|
||||
version.history.unshift({
|
||||
version: version.version,
|
||||
desc: version.desc,
|
||||
@@ -58,7 +58,7 @@ module.exports = async newVerNum => {
|
||||
return {
|
||||
pkg_bak,
|
||||
version_bak,
|
||||
changeLog: newChangeLog,
|
||||
// changeLog: newChangeLog,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BIN
resources/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
resources/icons/16x16.png
Normal file
|
After Width: | Height: | Size: 488 B |
|
Before Width: | Height: | Size: 7.4 KiB |
BIN
resources/icons/256x256.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
resources/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 901 B |
BIN
resources/icons/48x48.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
BIN
resources/icons/64x64.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
resources/icons/icon.icns
Normal file
BIN
resources/icons/icon.ico
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
resources/icons/icon.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
@@ -1,56 +0,0 @@
|
||||
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: [
|
||||
|
||||
],
|
||||
}
|
||||
86
src/common/config.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
export interface WindowSize {
|
||||
id: number
|
||||
name: string
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
export const windowSizeList: WindowSize[] = [
|
||||
{
|
||||
id: 0,
|
||||
name: 'smaller',
|
||||
width: 828,
|
||||
height: 540,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'small',
|
||||
width: 920,
|
||||
height: 600,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'medium',
|
||||
width: 1020,
|
||||
height: 660,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'big',
|
||||
width: 1114,
|
||||
height: 718,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'larger',
|
||||
width: 1202,
|
||||
height: 776,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'oversized',
|
||||
width: 1385,
|
||||
height: 896,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'huge',
|
||||
width: 1700,
|
||||
height: 1070,
|
||||
},
|
||||
]
|
||||
|
||||
export const navigationUrlWhiteList: RegExp[] = []
|
||||
|
||||
// 基础黑白色
|
||||
// export const commonColorNames = [
|
||||
// '--color-000', '--color-050', '--color-100', '--color-200', '--color-300', '--color-400',
|
||||
// '--color-500', '--color-600', '--color-700', '--color-800', '--color-900',
|
||||
// ] as const
|
||||
// export const commonLightColorValues = [
|
||||
// 'rgb(255, 255, 255)',
|
||||
// 'rgb(217,217,217)',
|
||||
// 'rgb(184,184,184)',
|
||||
// 'rgb(156,156,156)',
|
||||
// 'rgb(133,133,133)',
|
||||
// 'rgb(113,113,113)',
|
||||
// 'rgb(96,96,96)',
|
||||
// 'rgb(82,82,82)',
|
||||
// 'rgb(70,70,70)',
|
||||
// 'rgb(60,60,60)',
|
||||
// 'rgb(51,51,51)',
|
||||
// ] as const
|
||||
// export const commonDarkColorValues = [
|
||||
// 'rgb(11, 11, 11)',
|
||||
// 'rgb(60,60,60)',
|
||||
// 'rgb(99,99,99)',
|
||||
// 'rgb(130,130,130)',
|
||||
// 'rgb(155,155,155)',
|
||||
// 'rgb(175,175,175)',
|
||||
// 'rgb(191,191,191)',
|
||||
// 'rgb(204,204,204)',
|
||||
// 'rgb(214,214,214)',
|
||||
// 'rgb(222,222,222)',
|
||||
// 'rgb(229,229,229)',
|
||||
// ] as const
|
||||
|
||||
74
src/common/constants.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
export const URL_SCHEME_RXP = /^lxmusic:\/\//
|
||||
|
||||
|
||||
export const STORE_NAMES = {
|
||||
APP_SETTINGS: 'config_v2',
|
||||
DATA: 'data',
|
||||
SYNC: 'sync',
|
||||
HOTKEY: 'hot_key',
|
||||
USER_API: 'user_api',
|
||||
LRC_RAW: 'lyrics',
|
||||
LRC_EDITED: 'lyrics_edited',
|
||||
THEME: 'theme',
|
||||
} as const
|
||||
|
||||
export const APP_EVENT_NAMES = {
|
||||
winMainName: 'win_main',
|
||||
winLyricName: 'win_lyric',
|
||||
trayName: 'tray',
|
||||
} as const
|
||||
|
||||
export const LIST_IDS = {
|
||||
DEFAULT: 'default',
|
||||
LOVE: 'love',
|
||||
TEMP: 'temp',
|
||||
DOWNLOAD: 'download',
|
||||
} as const
|
||||
|
||||
export const DATA_KEYS = {
|
||||
viewPrevState: 'viewPrevState',
|
||||
playInfo: 'playInfo',
|
||||
searchHistoryList: 'searchHistoryList',
|
||||
listScrollPosition: 'listScrollPosition',
|
||||
listPrevSelectId: 'listPrevSelectId',
|
||||
listUpdateInfo: 'listUpdateInfo',
|
||||
ignoreVersion: 'ignoreVersion',
|
||||
|
||||
leaderboardSetting: 'leaderboardSetting',
|
||||
songListSetting: 'songListSetting',
|
||||
searchSetting: 'searchSetting',
|
||||
} as const
|
||||
|
||||
export const DEFAULT_SETTING = {
|
||||
leaderboard: {
|
||||
source: 'kw',
|
||||
boardId: 'kw__16',
|
||||
},
|
||||
|
||||
songList: {
|
||||
source: 'kg',
|
||||
sortId: '5',
|
||||
tagId: '',
|
||||
},
|
||||
|
||||
search: {
|
||||
temp_source: 'kw',
|
||||
source: 'all',
|
||||
type: 'music',
|
||||
},
|
||||
|
||||
viewPrevState: {
|
||||
url: '/search',
|
||||
query: {},
|
||||
},
|
||||
}
|
||||
|
||||
export const DOWNLOAD_STATUS = {
|
||||
RUN: 'run',
|
||||
WAITING: 'waiting',
|
||||
PAUSE: 'pause',
|
||||
ERROR: 'error',
|
||||
COMPLETED: 'completed',
|
||||
} as const
|
||||
|
||||
export const QUALITYS = ['flac24bit', 'flac', 'wav', 'ape', '320k', '192k', '128k'] as const
|
||||
@@ -1,89 +0,0 @@
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
93
src/common/defaultHotKey.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { HOTKEY_PLAYER, HOTKEY_COMMON, HOTKEY_DESKTOP_LYRIC } from './hotKey'
|
||||
|
||||
const local: LX.HotKeyConfig = {
|
||||
enable: true,
|
||||
keys: {
|
||||
'mod+f5': {
|
||||
type: HOTKEY_PLAYER.toggle_play.type,
|
||||
name: HOTKEY_PLAYER.toggle_play.name,
|
||||
action: HOTKEY_PLAYER.toggle_play.action,
|
||||
},
|
||||
'mod+arrowleft': {
|
||||
type: HOTKEY_PLAYER.prev.type,
|
||||
name: HOTKEY_PLAYER.prev.name,
|
||||
action: HOTKEY_PLAYER.prev.action,
|
||||
},
|
||||
'mod+arrowright': {
|
||||
type: HOTKEY_PLAYER.next.type,
|
||||
name: HOTKEY_PLAYER.next.name,
|
||||
action: HOTKEY_PLAYER.next.action,
|
||||
},
|
||||
f1: {
|
||||
type: HOTKEY_COMMON.focusSearchInput.type,
|
||||
name: HOTKEY_COMMON.focusSearchInput.name,
|
||||
action: HOTKEY_COMMON.focusSearchInput.action,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const global: LX.HotKeyConfig = {
|
||||
enable: false,
|
||||
keys: {
|
||||
MediaPlayPause: {
|
||||
type: HOTKEY_PLAYER.toggle_play.type,
|
||||
name: '',
|
||||
action: HOTKEY_PLAYER.toggle_play.action,
|
||||
},
|
||||
MediaPreviousTrack: {
|
||||
type: HOTKEY_PLAYER.prev.type,
|
||||
name: '',
|
||||
action: HOTKEY_PLAYER.prev.action,
|
||||
},
|
||||
MediaNextTrack: {
|
||||
type: HOTKEY_PLAYER.next.type,
|
||||
name: '',
|
||||
action: HOTKEY_PLAYER.next.action,
|
||||
},
|
||||
'mod+alt+f5': {
|
||||
type: HOTKEY_PLAYER.toggle_play.type,
|
||||
name: HOTKEY_PLAYER.toggle_play.name,
|
||||
action: HOTKEY_PLAYER.toggle_play.action,
|
||||
},
|
||||
'mod+alt+arrowleft': {
|
||||
type: HOTKEY_PLAYER.prev.type,
|
||||
name: HOTKEY_PLAYER.prev.name,
|
||||
action: HOTKEY_PLAYER.prev.action,
|
||||
},
|
||||
'mod+alt+arrowright': {
|
||||
type: HOTKEY_PLAYER.next.type,
|
||||
name: HOTKEY_PLAYER.next.name,
|
||||
action: HOTKEY_PLAYER.next.action,
|
||||
},
|
||||
'mod+alt+arrowup': {
|
||||
type: HOTKEY_PLAYER.volume_up.type,
|
||||
name: HOTKEY_PLAYER.volume_up.name,
|
||||
action: HOTKEY_PLAYER.volume_up.action,
|
||||
},
|
||||
'mod+alt+arrowdown': {
|
||||
type: HOTKEY_PLAYER.volume_down.type,
|
||||
name: HOTKEY_PLAYER.volume_down.name,
|
||||
action: HOTKEY_PLAYER.volume_down.action,
|
||||
},
|
||||
'mod+alt+0': {
|
||||
type: HOTKEY_DESKTOP_LYRIC.toggle_visible.type,
|
||||
name: HOTKEY_DESKTOP_LYRIC.toggle_visible.name,
|
||||
action: HOTKEY_DESKTOP_LYRIC.toggle_visible.action,
|
||||
},
|
||||
'mod+alt+-': {
|
||||
type: HOTKEY_DESKTOP_LYRIC.toggle_lock.type,
|
||||
name: HOTKEY_DESKTOP_LYRIC.toggle_lock.name,
|
||||
action: HOTKEY_DESKTOP_LYRIC.toggle_lock.action,
|
||||
},
|
||||
'mod+alt+=': {
|
||||
type: HOTKEY_DESKTOP_LYRIC.toggle_always_top.type,
|
||||
name: HOTKEY_DESKTOP_LYRIC.toggle_always_top.name,
|
||||
action: HOTKEY_DESKTOP_LYRIC.toggle_always_top.action,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
local,
|
||||
global,
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
const path = require('path')
|
||||
const os = require('os')
|
||||
|
||||
const defaultSetting = {
|
||||
version: '1.0.41',
|
||||
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,
|
||||
},
|
||||
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,
|
||||
},
|
||||
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
|
||||
125
src/common/defaultSetting.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { join } from 'path'
|
||||
import { homedir } from 'os'
|
||||
|
||||
const defaultSetting: LX.AppSetting = {
|
||||
version: '2.0.0',
|
||||
|
||||
'common.windowSizeId': 3,
|
||||
'common.fontSize': 16,
|
||||
'common.startInFullscreen': false,
|
||||
'common.langId': null,
|
||||
'common.apiSource': 'temp',
|
||||
'common.sourceNameType': 'alias',
|
||||
'common.font': '',
|
||||
'common.isShowAnimation': true,
|
||||
'common.randomAnimate': true,
|
||||
'common.isAgreePact': false,
|
||||
'common.controlBtnPosition': process.platform === 'darwin' ? 'left' : 'right',
|
||||
'common.playBarProgressStyle': 'mini',
|
||||
|
||||
'player.startupAutoPlay': false,
|
||||
'player.togglePlayMethod': 'listLoop',
|
||||
'player.highQuality': false,
|
||||
'player.isShowTaskProgess': true,
|
||||
'player.volume': 1,
|
||||
'player.isMute': false,
|
||||
'player.mediaDeviceId': 'default',
|
||||
'player.isMediaDeviceRemovedStopPlay': false,
|
||||
'player.isShowLyricTranslation': false,
|
||||
'player.isShowLyricRoma': false,
|
||||
'player.isS2t': false,
|
||||
'player.isPlayLxlrc': false,
|
||||
'player.isSavePlayTime': false,
|
||||
'player.audioVisualization': false,
|
||||
'player.waitPlayEndStop': true,
|
||||
'player.waitPlayEndStopTime': '',
|
||||
'player.autoSkipOnError': true,
|
||||
|
||||
'playDetail.isZoomActiveLrc': false,
|
||||
'playDetail.isShowLyricProgressSetting': false,
|
||||
'playDetail.style.fontSize': 100,
|
||||
'playDetail.style.align': 'center',
|
||||
|
||||
'desktopLyric.enable': false,
|
||||
'desktopLyric.isLock': false,
|
||||
'desktopLyric.isAlwaysOnTop': false,
|
||||
'desktopLyric.isAlwaysOnTopLoop': false,
|
||||
'desktopLyric.isShowTaskbar': false,
|
||||
'desktopLyric.audioVisualization': false,
|
||||
'desktopLyric.width': 450,
|
||||
'desktopLyric.height': 300,
|
||||
'desktopLyric.x': null,
|
||||
'desktopLyric.y': null,
|
||||
'desktopLyric.isLockScreen': true,
|
||||
'desktopLyric.isDelayScroll': true,
|
||||
'desktopLyric.scrollAlign': 'center',
|
||||
'desktopLyric.isHoverHide': false,
|
||||
'desktopLyric.direction': 'horizontal',
|
||||
'desktopLyric.style.align': 'center',
|
||||
'desktopLyric.style.font': '',
|
||||
'desktopLyric.style.fontSize': 20,
|
||||
'desktopLyric.style.lineGap': 15,
|
||||
'desktopLyric.style.lyricUnplayColor': 'rgba(255, 255, 255, 1)',
|
||||
'desktopLyric.style.lyricPlayedColor': 'rgba(7, 197, 86, 1)',
|
||||
'desktopLyric.style.lyricShadowColor': 'rgba(0, 0, 0, 0.15)',
|
||||
// 'desktopLyric.style.fontWeight': false,
|
||||
'desktopLyric.style.opacity': 95,
|
||||
'desktopLyric.style.ellipsis': false,
|
||||
'desktopLyric.style.isZoomActiveLrc': true,
|
||||
|
||||
'list.isClickPlayList': false,
|
||||
'list.isShowSource': true,
|
||||
'list.isSaveScrollLocation': true,
|
||||
'list.addMusicLocationType': 'top',
|
||||
'list.actionButtonsVisible': false,
|
||||
|
||||
'download.enable': false,
|
||||
'download.savePath': join(homedir(), 'Desktop'),
|
||||
'download.fileName': '歌名 - 歌手',
|
||||
'download.maxDownloadNum': 3,
|
||||
'download.skipExistFile': true,
|
||||
'download.isDownloadLrc': false,
|
||||
'download.isDownloadTLrc': false,
|
||||
'download.isDownloadRLrc': false,
|
||||
'download.lrcFormat': 'utf8',
|
||||
'download.isEmbedPic': true,
|
||||
'download.isEmbedLyric': false,
|
||||
'download.isUseOtherSource': false,
|
||||
|
||||
'search.isShowHotSearch': false,
|
||||
'search.isShowHistorySearch': false,
|
||||
'search.isFocusSearchBox': false,
|
||||
|
||||
'network.proxy.enable': false,
|
||||
'network.proxy.host': '',
|
||||
'network.proxy.port': '',
|
||||
'network.proxy.username': '',
|
||||
'network.proxy.password': '',
|
||||
|
||||
'tray.enable': false,
|
||||
// 'tray.isToTray': false,
|
||||
'tray.themeId': 0,
|
||||
|
||||
'sync.enable': false,
|
||||
'sync.port': '23332',
|
||||
|
||||
'theme.id': 'blue_plus',
|
||||
// 'theme.id': 'green',
|
||||
'theme.lightId': 'green',
|
||||
'theme.darkId': 'black',
|
||||
|
||||
'odc.isAutoClearSearchInput': false,
|
||||
'odc.isAutoClearSearchList': false,
|
||||
|
||||
}
|
||||
|
||||
|
||||
// 使用新年皮肤
|
||||
if (new Date().getMonth() < 2) {
|
||||
defaultSetting['theme.id'] = 'happy_new_year'
|
||||
defaultSetting['desktopLyric.style.lyricPlayedColor'] = 'rgba(255, 18, 34, 1)'
|
||||
}
|
||||
|
||||
|
||||
export default defaultSetting
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
const { log } = require('./utils')
|
||||
import { log } from './utils'
|
||||
|
||||
process.on('uncaughtException', function(err) {
|
||||
const ignoreErrorMessage = [
|
||||
'Possible side-effect in debug-evaluate',
|
||||
'Unexpected end of input',
|
||||
]
|
||||
|
||||
process.on('uncaughtException', err => {
|
||||
if (ignoreErrorMessage.includes(err.message)) return
|
||||
console.error('An uncaught error occurred!')
|
||||
console.error(err)
|
||||
log.error(err)
|
||||
@@ -1,83 +1,101 @@
|
||||
const names = require('@main/events/_name')
|
||||
import { APP_EVENT_NAMES } from './constants'
|
||||
|
||||
|
||||
const keyName = {
|
||||
common: APP_EVENT_NAMES.winMainName,
|
||||
player: APP_EVENT_NAMES.winMainName,
|
||||
desktop_lyric: APP_EVENT_NAMES.winLyricName,
|
||||
}
|
||||
|
||||
const hotKey = {
|
||||
common: {
|
||||
min: {
|
||||
name: 'min',
|
||||
action: 'min',
|
||||
type: '',
|
||||
},
|
||||
min_toggle: {
|
||||
name: 'toggle_min',
|
||||
action: 'toggle_min',
|
||||
type: '',
|
||||
},
|
||||
hide_toggle: {
|
||||
name: 'toggle_hide',
|
||||
action: 'toggle_hide',
|
||||
type: '',
|
||||
},
|
||||
close: {
|
||||
name: 'toggle_close',
|
||||
action: 'toggle_close',
|
||||
type: '',
|
||||
},
|
||||
focusSearchInput: {
|
||||
name: 'focus_search_input',
|
||||
action: 'focus_search_input',
|
||||
type: '',
|
||||
},
|
||||
},
|
||||
player: {
|
||||
toggle_play: {
|
||||
name: 'toggle_play',
|
||||
action: 'toggle_play',
|
||||
type: '',
|
||||
},
|
||||
next: {
|
||||
name: 'next',
|
||||
action: 'next',
|
||||
type: '',
|
||||
},
|
||||
prev: {
|
||||
name: 'prev',
|
||||
action: 'prev',
|
||||
type: '',
|
||||
},
|
||||
volume_up: {
|
||||
name: 'volume_up',
|
||||
action: 'volume_up',
|
||||
type: '',
|
||||
},
|
||||
volume_down: {
|
||||
name: 'volume_down',
|
||||
action: 'volume_down',
|
||||
type: '',
|
||||
},
|
||||
volume_mute: {
|
||||
name: 'volume_mute',
|
||||
action: 'volume_mute',
|
||||
type: '',
|
||||
},
|
||||
},
|
||||
desktop_lyric: {
|
||||
toggle_visible: {
|
||||
name: 'toggle_visible',
|
||||
action: 'toggle_visible',
|
||||
type: '',
|
||||
},
|
||||
toggle_lock: {
|
||||
name: 'toggle_lock',
|
||||
action: 'toggle_lock',
|
||||
type: '',
|
||||
},
|
||||
toggle_always_top: {
|
||||
name: 'toggle_always_top',
|
||||
action: 'toggle_always_top',
|
||||
type: '',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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]
|
||||
for (const type of Object.keys(hotKey) as Array<keyof typeof hotKey>) {
|
||||
let keys = hotKey[type]
|
||||
for (const key of Object.keys(keys) as Array<keyof typeof keys>) {
|
||||
const keyInfo: LX.HotKey = keys[key]
|
||||
keyInfo.action = `${type}_${keyInfo.action}`
|
||||
keyInfo.name = `${type}_${keyInfo.name}`
|
||||
keyInfo.type = keyName[type] as keyof typeof hotKey
|
||||
}
|
||||
}
|
||||
|
||||
exports.common = hotKey.common
|
||||
exports.player = hotKey.player
|
||||
exports.desktop_lyric = hotKey.desktop_lyric
|
||||
export const HOTKEY_COMMON = hotKey.common
|
||||
export const HOTKEY_PLAYER = hotKey.player
|
||||
export const HOTKEY_DESKTOP_LYRIC = hotKey.desktop_lyric
|
||||
@@ -1,52 +0,0 @@
|
||||
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
|
||||
@@ -1,95 +0,0 @@
|
||||
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',
|
||||
},
|
||||
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
|
||||
177
src/common/ipcNames.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
const modules = {
|
||||
common: {
|
||||
get_env_params: 'get_env_params',
|
||||
deeplink: 'deeplink',
|
||||
clear_env_params_deeplink: 'clear_env_params_deeplink',
|
||||
system_theme_change: 'system_theme_change',
|
||||
theme_change: 'theme_change',
|
||||
get_system_fonts: 'get_system_fonts',
|
||||
get_app_setting: 'get_app_setting',
|
||||
set_app_setting: 'set_app_setting',
|
||||
},
|
||||
player: {
|
||||
invoke_play_music: 'play_music',
|
||||
invoke_play_next: 'play_next',
|
||||
invoke_play_prev: 'play_prev',
|
||||
invoke_toggle_play: 'toggle_play',
|
||||
player_play: 'player_play',
|
||||
player_pause: 'player_pause',
|
||||
player_stop: 'player_stop',
|
||||
player_error: 'player_error',
|
||||
|
||||
list_data_overwire: 'list_data_overwire',
|
||||
list_get: 'list_get',
|
||||
list_add: 'list_add',
|
||||
list_remove: 'list_remove',
|
||||
list_update: 'list_update',
|
||||
list_update_position: 'list_update_position',
|
||||
list_music_get: 'list_music_get',
|
||||
list_music_add: 'list_music_add',
|
||||
list_music_move: 'list_music_move',
|
||||
list_music_remove: 'list_music_remove',
|
||||
list_music_update: 'list_music_update',
|
||||
list_music_update_position: 'list_music_update_position',
|
||||
list_music_overwrite: 'list_music_overwrite',
|
||||
list_music_clear: 'list_music_clear',
|
||||
list_music_check_exist: 'list_music_check_exist',
|
||||
list_music_get_list_ids: 'list_music_get_list_ids',
|
||||
},
|
||||
winMain: {
|
||||
focus: 'focus',
|
||||
close: 'close',
|
||||
min: 'min',
|
||||
max: 'max',
|
||||
fullscreen: 'fullscreen',
|
||||
set_app_name: 'set_app_name',
|
||||
clear_cache: 'clear_cache',
|
||||
get_cache_size: 'get_cache_size',
|
||||
inited: 'inited',
|
||||
show_save_dialog: 'show_save_dialog',
|
||||
show_select_dialog: 'show_select_dialog',
|
||||
show_dialog: 'show_dialog',
|
||||
open_dev_tools: 'open_dev_tools',
|
||||
|
||||
progress: 'progress',
|
||||
change_tray: 'change_tray',
|
||||
quit_update: 'quit_update',
|
||||
update_check: 'update_check',
|
||||
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_window_size: 'set_window_size',
|
||||
|
||||
handle_request: 'handle_request',
|
||||
cancel_request: 'cancel_request',
|
||||
|
||||
|
||||
restart_window: 'restart_window',
|
||||
|
||||
// lang_s2t: 'lang_s2t',
|
||||
|
||||
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',
|
||||
on_config_change: 'on_config_change',
|
||||
key_down: 'key_down',
|
||||
quit: 'quit',
|
||||
min_toggle: 'min_toggle',
|
||||
hide_toggle: 'hide_toggle',
|
||||
|
||||
get_other_source: 'get_other_source',
|
||||
save_other_source: 'save_other_source',
|
||||
clear_other_source: 'clear_other_source',
|
||||
get_other_source_count: 'get_other_source_count',
|
||||
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',
|
||||
user_api_show_update_alert: 'user_api_show_update_alert',
|
||||
user_api_set_allow_update_alert: 'user_api_set_allow_update_alert',
|
||||
|
||||
get_palyer_lyric: 'get_lyric',
|
||||
// save_lyric: 'save_lyric',
|
||||
// clear_lyric: 'clear_lyric',
|
||||
get_lyric_raw: 'get_lyric_raw',
|
||||
save_lyric_raw: 'save_lyric_raw',
|
||||
clear_lyric_raw: 'clear_lyric_raw',
|
||||
get_lyric_raw_count: 'get_lyric_raw_count',
|
||||
get_lyric_edited: 'get_lyric_edited',
|
||||
save_lyric_edited: 'save_lyric_edited',
|
||||
remove_lyric_edited: 'remove_lyric_edited',
|
||||
clear_lyric_edited: 'clear_lyric_edited',
|
||||
get_lyric_edited_count: 'get_lyric_edited_count',
|
||||
get_music_url: 'get_music_url',
|
||||
save_music_url: 'save_music_url',
|
||||
clear_music_url: 'clear_music_url',
|
||||
get_music_url_count: 'get_music_url_count',
|
||||
|
||||
sync_action: 'sync_action',
|
||||
|
||||
process_new_desktop_lyric_client: 'process_new_desktop_lyric_client',
|
||||
|
||||
player_action_set_buttons: 'player_action_set_buttons',
|
||||
// player_action_set_thumbnail_clip: 'player_action_set_thumbnail_clip',
|
||||
player_action_on_button_click: 'player_action_on_button_click',
|
||||
|
||||
get_themes: 'get_themes',
|
||||
save_theme: 'save_theme',
|
||||
remove_theme: 'remove_theme',
|
||||
|
||||
download_list_get: 'download_list_get',
|
||||
download_list_add: 'download_list_add',
|
||||
download_list_update: 'download_list_update',
|
||||
download_list_remove: 'download_list_remove',
|
||||
download_list_clear: 'download_list_clear',
|
||||
},
|
||||
winLyric: {
|
||||
close: 'close',
|
||||
set_config: 'set_config',
|
||||
get_config: 'get_config',
|
||||
on_config_change: 'on_config_change',
|
||||
main_window_inited: 'main_window_inited',
|
||||
set_win_bounds: 'set_win_bounds',
|
||||
key_down: 'key_down',
|
||||
request_main_window_channel: 'request_main_window_channel',
|
||||
provide_main_window_channel: 'provide_main_window_channel',
|
||||
},
|
||||
hotKey: {
|
||||
enable: 'enable',
|
||||
status: 'status',
|
||||
set_config: 'set_config',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
for (const moduleName of Object.keys(modules) as Array<keyof typeof modules>) {
|
||||
let eventNames = modules[moduleName]
|
||||
for (const eventName of Object.keys(eventNames) as Array<keyof typeof eventNames>) {
|
||||
eventNames[eventName] = `${moduleName}_${eventName as string}` as never
|
||||
}
|
||||
}
|
||||
|
||||
// for (const moduleName of Object.keys(modules) as Array<keyof typeof modules>) {
|
||||
// let eventNames = modules[moduleName]
|
||||
// for (const eventName of Object.keys(eventNames)) {
|
||||
// eventNames[eventName] = `${moduleName}_${eventName}`
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
export const CMMON_EVENT_NAME = modules.common
|
||||
export const PLAYER_EVENT_NAME = modules.player
|
||||
export const WIN_MAIN_RENDERER_EVENT_NAME = modules.winMain
|
||||
export const WIN_LYRIC_RENDERER_EVENT_NAME = modules.winLyric
|
||||
export const HOTKEY_RENDERER_EVENT_NAME = modules.hotKey
|
||||
54
src/common/mainIpc.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { ipcMain } from 'electron'
|
||||
|
||||
export function mainOn(name: string, listener: LX.IpcMainEventListener): void
|
||||
export function mainOn<T>(name: string, listener: LX.IpcMainEventListenerParams<T>): void
|
||||
export function mainOn<T>(name: string, listener: LX.IpcMainEventListenerParams<T>): void {
|
||||
ipcMain.on(name, (event, params) => {
|
||||
listener({ event, params })
|
||||
})
|
||||
}
|
||||
|
||||
export function mainOnce(name: string, listener: LX.IpcMainEventListener): void
|
||||
export function mainOnce<T>(name: string, listener: LX.IpcMainEventListenerParams<T>): void
|
||||
export function mainOnce<T>(name: string, listener: LX.IpcMainEventListenerParams<T>): void {
|
||||
ipcMain.once(name, (event, params) => {
|
||||
listener({ event, params })
|
||||
})
|
||||
}
|
||||
|
||||
export const mainOff = (name: string, listener: (...args: any[]) => void) => {
|
||||
ipcMain.removeListener(name, listener)
|
||||
}
|
||||
|
||||
export const mainOffAll = (name: string) => {
|
||||
ipcMain.removeAllListeners(name)
|
||||
}
|
||||
|
||||
export function mainHandle(name: string, listener: LX.IpcMainInvokeEventListener): void
|
||||
export function mainHandle<T>(name: string, listener: LX.IpcMainInvokeEventListenerParams<T>): void
|
||||
export function mainHandle<V>(name: string, listener: LX.IpcMainInvokeEventListenerValue<V>): void
|
||||
export function mainHandle<T, V>(name: string, listener: LX.IpcMainInvokeEventListenerParamsValue<T, V>): void
|
||||
export function mainHandle<T, V>(name: string, listener: LX.IpcMainInvokeEventListenerParamsValue<T, V>): void {
|
||||
ipcMain.handle(name, async(event, params) => {
|
||||
return await listener({ event, params })
|
||||
})
|
||||
}
|
||||
|
||||
export function mainHandleOnce(name: string, listener: LX.IpcMainInvokeEventListener): void
|
||||
export function mainHandleOnce<T>(name: string, listener: LX.IpcMainInvokeEventListenerParams<T>): void
|
||||
export function mainHandleOnce<V>(name: string, listener: LX.IpcMainInvokeEventListenerValue<V>): void
|
||||
export function mainHandleOnce<T, V>(name: string, listener: LX.IpcMainInvokeEventListenerParamsValue<T, V>): void
|
||||
export function mainHandleOnce<T, V>(name: string, listener: LX.IpcMainInvokeEventListenerParamsValue<T, V>): void {
|
||||
ipcMain.handleOnce(name, async(event, params) => {
|
||||
return await listener({ event, params })
|
||||
})
|
||||
}
|
||||
export const mainHandleRemove = (name: string) => {
|
||||
ipcMain.removeHandler(name)
|
||||
}
|
||||
|
||||
export function mainSend(window: Electron.BrowserWindow, name: string): void
|
||||
export function mainSend<T>(window: Electron.BrowserWindow, name: string, params: T): void
|
||||
export function mainSend<T>(window: Electron.BrowserWindow, name: string, params?: T): void {
|
||||
window.webContents.send(name, params)
|
||||
}
|
||||
45
src/common/rendererIpc.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
|
||||
export function rendererSend(name: string): void
|
||||
export function rendererSend<T>(name: string, params: T): void
|
||||
export function rendererSend<T>(name: string, params?: T): void {
|
||||
ipcRenderer.send(name, params)
|
||||
}
|
||||
|
||||
export function rendererSendSync(name: string): void
|
||||
export function rendererSendSync<T>(name: string, params: T): void
|
||||
export function rendererSendSync<T>(name: string, params?: T): void {
|
||||
ipcRenderer.sendSync(name, params)
|
||||
}
|
||||
|
||||
export async function rendererInvoke(name: string): Promise<void>
|
||||
export async function rendererInvoke<V>(name: string): Promise<V>
|
||||
export async function rendererInvoke<T>(name: string, params: T): Promise<void>
|
||||
export async function rendererInvoke<T, V>(name: string, params: T): Promise<V>
|
||||
export async function rendererInvoke <T, V>(name: string, params?: T): Promise<V> {
|
||||
return await ipcRenderer.invoke(name, params)
|
||||
}
|
||||
|
||||
export function rendererOn(name: string, listener: LX.IpcRendererEventListener): void
|
||||
export function rendererOn<T>(name: string, listener: LX.IpcRendererEventListenerParams<T>): void
|
||||
export function rendererOn<T>(name: string, listener: LX.IpcRendererEventListenerParams<T>): void {
|
||||
ipcRenderer.on(name, (event, params) => {
|
||||
listener({ event, params })
|
||||
})
|
||||
}
|
||||
|
||||
export function rendererOnce(name: string, listener: LX.IpcRendererEventListener): void
|
||||
export function rendererOnce<T>(name: string, listener: LX.IpcRendererEventListenerParams<T>): void
|
||||
export function rendererOnce<T>(name: string, listener: LX.IpcRendererEventListenerParams<T>): void {
|
||||
ipcRenderer.once(name, (event, params) => {
|
||||
listener({ event, params })
|
||||
})
|
||||
}
|
||||
|
||||
export const rendererOff = (name: string, listener: (...args: any[]) => any) => {
|
||||
ipcRenderer.removeListener(name, listener)
|
||||
}
|
||||
|
||||
export const rendererOffAll = (name: string) => {
|
||||
ipcRenderer.removeAllListeners(name)
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
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
|
||||
}
|
||||
73
src/common/theme/colorUtils.js
Normal file
@@ -0,0 +1,73 @@
|
||||
/* eslint-disable */
|
||||
// https://github.com/PimpTrizkit/PJs/wiki/12.-Shade,-Blend-and-Convert-a-Web-Color-(pSBC.js)#micro-functions-version-4
|
||||
|
||||
/**
|
||||
* Blend color (Lighten or Darken)
|
||||
* @param {number} p 混合百分比 范围 0.0 - 1.0
|
||||
* @param {string} c0 rgb(a) color1
|
||||
* @param {string} c1 rgb(a) color2
|
||||
* @returns color
|
||||
*/
|
||||
exports.RGB_Linear_Blend=(p,c0,c1)=>{
|
||||
var i=parseInt,r=Math.round,P=1-p,[a,b,c,d]=c0.split(","),[e,f,g,h]=c1.split(","),x=d||h,j=x?","+(!d?h:!h?d:r((parseFloat(d)*P+parseFloat(h)*p)*1000)/1000+")"):")";
|
||||
return"rgb"+(x?"a(":"(")+r(i(a[3]=="a"?a.slice(5):a.slice(4))*P+i(e[3]=="a"?e.slice(5):e.slice(4))*p)+","+r(i(b)*P+i(f)*p)+","+r(i(c)*P+i(g)*p)+j;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blend color (Lighten or Darken)
|
||||
* @param {number} p 混合百分比 范围 0.0 - 1.0
|
||||
* @param {string} c0 rgb(a) color1
|
||||
* @param {string} c1 rgb(a) color2
|
||||
* @returns color
|
||||
*/
|
||||
exports.RGB_Log_Blend=(p,c0,c1)=>{
|
||||
var i=parseInt,r=Math.round,P=1-p,[a,b,c,d]=c0.split(","),[e,f,g,h]=c1.split(","),x=d||h,j=x?","+(!d?h:!h?d:r((parseFloat(d)*P+parseFloat(h)*p)*1000)/1000+")"):")";
|
||||
return"rgb"+(x?"a(":"(")+r((P*i(a[3]=="a"?a.slice(5):a.slice(4))**2+p*i(e[3]=="a"?e.slice(5):e.slice(4))**2)**0.5)+","+r((P*i(b)**2+p*i(f)**2)**0.5)+","+r((P*i(c)**2+p*i(g)**2)**0.5)+j;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shade color (Lighten or Darken)
|
||||
* @param {number} p Shade 百分比范围为 -1.0 - 1.0 负为黑色,正为白色
|
||||
* @param {string} c0 rgb(a) color
|
||||
* @returns color
|
||||
*/
|
||||
exports.RGB_Linear_Shade=(p,c0)=>{
|
||||
var i=parseInt,r=Math.round,[a,b,c,d]=c0.split(","),n=p<0,t=n?0:255*p,P=n?1+p:1-p;
|
||||
return"rgb"+(d?"a(":"(")+r(i(a[3]=="a"?a.slice(5):a.slice(4))*P+t)+","+r(i(b)*P+t)+","+r(i(c)*P+t)+(d?","+d:")");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shade color (Lighten or Darken)
|
||||
* @param {number} p Shade 百分比范围为 -1.0 - 1.0 负为黑色,正为白色
|
||||
* @param {string} c0 rgb(a) color
|
||||
* @returns color
|
||||
*/
|
||||
exports.RGB_Log_Shade=(p,c0)=>{
|
||||
var i=parseInt,r=Math.round,[a,b,c,d]=c0.split(","),n=p<0,t=n?0:p*255**2,P=n?1+p:1-p;
|
||||
return"rgb"+(d?"a(":"(")+r((P*i(a[3]=="a"?a.slice(5):a.slice(4))**2+t)**0.5)+","+r((P*i(b)**2+t)**0.5)+","+r((P*i(c)**2+t)**0.5)+(d?","+d:")");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 修改透明度
|
||||
* @param {number} p 透明度 -1.0 - 1.0
|
||||
* @param {string} color
|
||||
* @returns color
|
||||
*/
|
||||
exports.RGB_Alpha_Shade = (p, color) => {
|
||||
var i = parseInt
|
||||
var n = p < 0
|
||||
var [r, g, b, a] = color.split(",")
|
||||
r = r[3] == 'a' ? r.slice(5) : r.slice(4)
|
||||
if (a) {
|
||||
a = parseFloat(a)
|
||||
a = a - (n ? (1 - a) * p : a * p)
|
||||
a = n ? Math.max(0, a) : Math.min(1, a)
|
||||
} else {
|
||||
a = 1 - p
|
||||
a = Math.min(1, a)
|
||||
}
|
||||
return `rgba(${i(r)}, ${i(g)}, ${i(b)}, ${a.toFixed(2)})`
|
||||
}
|
||||
369
src/common/theme/createThemes.js
Normal file
@@ -0,0 +1,369 @@
|
||||
//! 更新默认主题配置后,需要执行 npm run build:theme 重新构建index.json
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { createThemeColors } = require('./utils')
|
||||
|
||||
const defaultThemes = [
|
||||
{
|
||||
id: 'green',
|
||||
name: '绿意盎然',
|
||||
isDark: false,
|
||||
config: {
|
||||
primary: 'rgb(77, 175, 124)',
|
||||
font: 'rgb(33, 33, 33)',
|
||||
'--color-app-background': 'var(--color-primary-light-600-alpha-700)',
|
||||
'--color-main-background': 'rgba(255, 255, 255, 1)',
|
||||
'--color-nav-font': 'var(--color-primary)',
|
||||
'--background-image': 'none',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
'--color-btn-hide': '#3bc2b2',
|
||||
'--color-btn-min': '#85c43b',
|
||||
'--color-btn-close': '#fab4a0',
|
||||
|
||||
'--color-badge-primary': 'var(--color-primary)',
|
||||
'--color-badge-secondary': '#4baed5',
|
||||
'--color-badge-tertiary': '#e7aa36',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'blue',
|
||||
name: '蓝田生玉',
|
||||
isDark: false,
|
||||
config: {
|
||||
primary: 'rgb(52, 152, 219)',
|
||||
font: 'rgb(33, 33, 33)',
|
||||
'--color-app-background': 'var(--color-primary-light-600-alpha-700)',
|
||||
'--color-main-background': 'rgba(255, 255, 255, 1)',
|
||||
'--color-nav-font': 'var(--color-primary)',
|
||||
'--background-image': 'none',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
'--color-btn-hide': '#3bc2b2',
|
||||
'--color-btn-min': '#85c43b',
|
||||
'--color-btn-close': '#fab4a0',
|
||||
|
||||
'--color-badge-primary': 'var(--color-primary)',
|
||||
'--color-badge-secondary': '#5cbf9b',
|
||||
'--color-badge-tertiary': '#5cbf9b',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'blue_plus',
|
||||
name: '蛋雅深蓝',
|
||||
isDark: false,
|
||||
config: {
|
||||
primary: 'rgb(77, 131, 175)',
|
||||
font: 'rgb(33, 33, 33)',
|
||||
'--color-app-background': 'var(--color-primary-light-600-alpha-600)',
|
||||
'--color-main-background': 'rgba(255, 255, 255, 1)',
|
||||
'--color-nav-font': 'var(--color-primary)',
|
||||
'--background-image': 'none',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
'--color-btn-hide': '#3bc2b2',
|
||||
'--color-btn-min': '#85c43b',
|
||||
'--color-btn-close': '#fab4a0',
|
||||
|
||||
'--color-badge-primary': 'var(--color-primary)',
|
||||
'--color-badge-secondary': 'rgba(66.6, 150.7, 171, 1)',
|
||||
'--color-badge-tertiary': 'rgba(54, 196, 231, 1)',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'orange',
|
||||
name: '橙黄橘绿',
|
||||
isDark: false,
|
||||
config: {
|
||||
primary: 'rgb(245, 171, 53)',
|
||||
font: 'rgb(33, 33, 33)',
|
||||
'--color-app-background': 'var(--color-primary-light-600-alpha-700)',
|
||||
'--color-main-background': 'rgba(255, 255, 255, 1)',
|
||||
'--color-nav-font': 'var(--color-primary)',
|
||||
'--background-image': 'none',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
'--color-btn-hide': '#3bc2b2',
|
||||
'--color-btn-min': '#85c43b',
|
||||
'--color-btn-close': '#fab4a0',
|
||||
|
||||
'--color-badge-primary': 'var(--color-primary)',
|
||||
'--color-badge-secondary': '#9ed458',
|
||||
'--color-badge-tertiary': '#9ed458',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'red',
|
||||
name: '热情似火',
|
||||
isDark: false,
|
||||
config: {
|
||||
primary: 'rgb(214, 69, 65)',
|
||||
font: 'rgb(33, 33, 33)',
|
||||
'--color-app-background': 'var(--color-primary-light-600-alpha-700)',
|
||||
'--color-main-background': 'rgba(255, 255, 255, 1)',
|
||||
'--color-nav-font': 'var(--color-primary)',
|
||||
'--background-image': 'none',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
'--color-btn-hide': '#3bc2b2',
|
||||
'--color-btn-min': '#85c43b',
|
||||
'--color-btn-close': '#fab4a0',
|
||||
|
||||
'--color-badge-primary': 'var(--color-primary)',
|
||||
'--color-badge-secondary': '#dfbb6b',
|
||||
'--color-badge-tertiary': '#dfbb6b',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'pink',
|
||||
name: '粉装玉琢',
|
||||
isDark: false,
|
||||
config: {
|
||||
primary: 'rgb(241, 130, 141)',
|
||||
font: 'rgb(33, 33, 33)',
|
||||
'--color-app-background': 'var(--color-primary-light-600-alpha-700)',
|
||||
'--color-main-background': 'rgba(255, 255, 255, 1)',
|
||||
'--color-nav-font': 'var(--color-primary)',
|
||||
'--background-image': 'none',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
'--color-btn-hide': '#3bc2b2',
|
||||
'--color-btn-min': '#85c43b',
|
||||
'--color-btn-close': '#fab4a0',
|
||||
|
||||
'--color-badge-primary': 'var(--color-primary)',
|
||||
'--color-badge-secondary': '#f5b684',
|
||||
'--color-badge-tertiary': '#f5b684',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'purple',
|
||||
name: '重斤球紫',
|
||||
isDark: false,
|
||||
config: {
|
||||
primary: 'rgb(155, 89, 182)',
|
||||
font: 'rgb(33, 33, 33)',
|
||||
'--color-app-background': 'var(--color-primary-light-600-alpha-700)',
|
||||
'--color-main-background': 'rgba(255, 255, 255, 1)',
|
||||
'--color-nav-font': 'var(--color-primary)',
|
||||
'--background-image': 'none',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
'--color-btn-hide': '#3bc2b2',
|
||||
'--color-btn-min': '#85c43b',
|
||||
'--color-btn-close': '#fab4a0',
|
||||
|
||||
'--color-badge-primary': 'var(--color-primary)',
|
||||
'--color-badge-secondary': '#e5a39f',
|
||||
'--color-badge-tertiary': '#e5a39f',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'grey',
|
||||
name: '灰常美丽',
|
||||
isDark: false,
|
||||
config: {
|
||||
primary: 'rgb(108, 122, 137)',
|
||||
font: 'rgb(33, 33, 33)',
|
||||
'--color-app-background': 'var(--color-primary-light-600-alpha-700)',
|
||||
'--color-main-background': 'rgba(255, 255, 255, 1)',
|
||||
'--color-nav-font': 'var(--color-primary)',
|
||||
'--background-image': 'none',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
'--color-btn-hide': '#3bc2b2',
|
||||
'--color-btn-min': '#85c43b',
|
||||
'--color-btn-close': '#fab4a0',
|
||||
|
||||
'--color-badge-primary': 'var(--color-primary)',
|
||||
'--color-badge-secondary': '#b19b9f',
|
||||
'--color-badge-tertiary': '#b19b9f',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'ming',
|
||||
name: '青出于黑',
|
||||
isDark: false,
|
||||
config: {
|
||||
primary: 'rgb(51, 110, 123)',
|
||||
font: 'rgb(33, 33, 33)',
|
||||
'--color-app-background': 'var(--color-primary-light-600-alpha-700)',
|
||||
'--color-main-background': 'rgba(255, 255, 255, 1)',
|
||||
'--color-nav-font': 'var(--color-primary)',
|
||||
'--background-image': 'none',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
'--color-btn-hide': '#3bc2b2',
|
||||
'--color-btn-min': '#85c43b',
|
||||
'--color-btn-close': '#fab4a0',
|
||||
|
||||
'--color-badge-primary': 'var(--color-primary)',
|
||||
'--color-badge-secondary': '#6376a2',
|
||||
'--color-badge-tertiary': '#6376a2',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'blue2',
|
||||
name: '清热板蓝',
|
||||
isDark: false,
|
||||
config: {
|
||||
primary: 'rgb(79, 98, 208)',
|
||||
font: 'rgb(33, 33, 33)',
|
||||
'--color-app-background': 'var(--color-primary-light-600-alpha-700)',
|
||||
'--color-main-background': 'rgba(255, 255, 255, 1)',
|
||||
'--color-nav-font': 'var(--color-primary)',
|
||||
'--background-image': 'none',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
'--color-btn-hide': '#3bc2b2',
|
||||
'--color-btn-min': '#85c43b',
|
||||
'--color-btn-close': '#fab4a0',
|
||||
|
||||
'--color-badge-primary': 'var(--color-primary)',
|
||||
'--color-badge-secondary': '#b080db',
|
||||
'--color-badge-tertiary': '#b080db',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'black',
|
||||
name: '黑灯瞎火',
|
||||
isDark: true,
|
||||
config: {
|
||||
primary: 'rgb(150, 150, 150)',
|
||||
font: 'rgb(229, 229, 229)',
|
||||
'--color-app-background': 'rgba(0, 0, 0, 0)',
|
||||
'--color-main-background': 'rgba(19, 19, 19, 0.9)',
|
||||
'--color-nav-font': 'var(--color-primary)',
|
||||
'--background-image': 'url(./theme_images/landingMoon.png)',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
'--color-btn-hide': '#3bc2b2',
|
||||
'--color-btn-min': '#85c43b',
|
||||
'--color-btn-close': '#fab4a0',
|
||||
|
||||
'--color-badge-primary': 'var(--color-primary-dark-200)',
|
||||
'--color-badge-secondary': 'var(--color-primary)',
|
||||
'--color-badge-tertiary': 'var(--color-primary-dark-300)',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'mid_autumn',
|
||||
name: '月里嫦娥',
|
||||
isDark: false,
|
||||
config: {
|
||||
primary: 'rgb(74, 55, 82)',
|
||||
font: 'rgb(33, 33, 33)',
|
||||
'--color-app-background': 'rgba(255, 255, 255, 0)',
|
||||
'--color-main-background': 'rgba(255, 255, 255, 0.9)',
|
||||
'--color-nav-font': 'var(--color-primary-light-600)',
|
||||
'--background-image': 'url(./theme_images/jqbg.jpg)',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
|
||||
'--color-btn-hide': '#3bc2b2',
|
||||
'--color-btn-min': '#85c43b',
|
||||
'--color-btn-close': '#fab4a0',
|
||||
|
||||
'--color-badge-primary': 'var(--color-primary)',
|
||||
'--color-badge-secondary': '#af9479',
|
||||
'--color-badge-tertiary': '#af9479',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'naruto',
|
||||
name: '木叶之村',
|
||||
isDark: false,
|
||||
config: {
|
||||
primary: 'rgb(87, 144, 167)',
|
||||
font: 'rgb(33, 33, 33)',
|
||||
'--color-app-background': 'rgba(255, 255, 255, 0.15)',
|
||||
'--color-main-background': 'rgba(255, 255, 255, 0.8)',
|
||||
'--color-nav-font': 'var(--color-primary)',
|
||||
'--background-image': 'url(./theme_images/myzcbg.jpg)',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
'--color-btn-hide': '#3bc2b2',
|
||||
'--color-btn-min': '#85c43b',
|
||||
'--color-btn-close': '#fab4a0',
|
||||
|
||||
'--color-badge-primary': 'var(--color-primary)',
|
||||
'--color-badge-secondary': 'var(--color-primary-light-100)',
|
||||
'--color-badge-tertiary': 'var(--color-primary-light-100)',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'china_ink',
|
||||
name: '近墨者黑',
|
||||
isDark: false,
|
||||
config: {
|
||||
primary: 'rgba(47, 47, 47, 1)',
|
||||
font: 'rgb(33, 33, 33)',
|
||||
'--color-app-background': 'rgba(255, 255, 255, 0)',
|
||||
'--color-main-background': 'rgba(255, 255, 255, 0.8)',
|
||||
'--color-nav-font': 'var(--color-primary)',
|
||||
'--background-image': 'url(./theme_images/china_ink.jpg)',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
|
||||
'--color-btn-hide': 'rgba(183, 212, 208, 1)',
|
||||
'--color-btn-min': 'rgba(200, 214, 183, 1)',
|
||||
'--color-btn-close': 'rgba(218, 195, 188, 1)',
|
||||
|
||||
'--color-badge-primary': 'rgba(137, 70, 70, 1)',
|
||||
'--color-badge-secondary': 'rgba(67, 139, 65, 1)',
|
||||
'--color-badge-tertiary': 'rgba(132, 135, 65, 1)',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'happy_new_year',
|
||||
name: '新年快乐',
|
||||
isDark: false,
|
||||
config: {
|
||||
primary: 'rgb(192, 57, 43)',
|
||||
font: 'rgb(33, 33, 33)',
|
||||
'--color-app-background': 'rgba(255, 255, 255, 0.15)',
|
||||
'--color-main-background': 'rgba(255, 255, 255, 0.8)',
|
||||
'--color-nav-font': 'var(--color-primary)',
|
||||
'--background-image': 'url(./theme_images/xnkl.png)',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
'--color-btn-hide': '#3bc2b2',
|
||||
'--color-btn-min': '#85c43b',
|
||||
'--color-btn-close': '#fab4a0',
|
||||
|
||||
'--color-badge-primary': '#7fb575',
|
||||
'--color-badge-secondary': '#dfbb6b',
|
||||
'--color-badge-tertiary': 'var(--color-primary-light-100)',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const themes = defaultThemes.map(({ config: { primary, font, ...extInfo }, ...themeInfo }) => {
|
||||
return {
|
||||
...themeInfo,
|
||||
isCustom: false,
|
||||
config: {
|
||||
themeColors: createThemeColors(primary, font, themeInfo.isDark),
|
||||
extInfo,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
fs.writeFileSync(path.join(__dirname, 'index.json'), JSON.stringify(themes, null, 2))
|
||||
|
||||
BIN
src/common/theme/images/china_ink.jpg
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 353 KiB After Width: | Height: | Size: 353 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
BIN
src/common/theme/images/xnkl.png
Normal file
|
After Width: | Height: | Size: 997 KiB |
3842
src/common/theme/index.json
Normal file
75
src/common/theme/utils.js
Normal file
@@ -0,0 +1,75 @@
|
||||
const { RGB_Linear_Shade, RGB_Alpha_Shade } = require('./colorUtils')
|
||||
|
||||
exports.createThemeColors = (rgbaColor, fontRgbaColor, isDark) => {
|
||||
const colors = {
|
||||
'--color-primary': rgbaColor,
|
||||
}
|
||||
|
||||
let preColor = rgbaColor
|
||||
for (let i = 1; i < 11; i += 1) {
|
||||
preColor = RGB_Linear_Shade(isDark ? 0.2 : -0.1, preColor)
|
||||
colors[`--color-primary-dark-${i * 100}`] = preColor
|
||||
for (let j = 1; j < 10; j += 1) {
|
||||
colors[`--color-primary-dark-${i * 100}-alpha-${j * 100}`] = RGB_Alpha_Shade(0.1 * j, preColor)
|
||||
colors[`--color-primary-alpha-${j * 100}`] = RGB_Alpha_Shade(0.1 * j, rgbaColor)
|
||||
}
|
||||
}
|
||||
preColor = rgbaColor
|
||||
for (let i = 1; i < 10; i += 1) {
|
||||
preColor = RGB_Linear_Shade(isDark ? -0.1 : 0.2, preColor)
|
||||
colors[`--color-primary-light-${i * 100}`] = preColor
|
||||
for (let j = 1; j < 10; j += 1) {
|
||||
colors[`--color-primary-light-${i * 100}-alpha-${j * 100}`] = RGB_Alpha_Shade(0.1 * j, preColor)
|
||||
}
|
||||
}
|
||||
preColor = RGB_Linear_Shade(isDark ? -0.2 : 1, preColor)
|
||||
colors[`--color-primary-light-${1000}`] = preColor
|
||||
for (let j = 1; j < 10; j += 1) {
|
||||
colors[`--color-primary-light-${1000}-alpha-${j * 100}`] = RGB_Alpha_Shade(0.1 * j, preColor)
|
||||
}
|
||||
|
||||
colors['--color-theme'] = isDark ? colors['--color-primary-light-900'] : rgbaColor
|
||||
|
||||
return { ...colors, ...createFontColors(fontRgbaColor, isDark) }
|
||||
}
|
||||
|
||||
const createFontColors = (rgbaColor, isDark) => {
|
||||
// rgb(238, 238, 238)
|
||||
// let prec = 'rgb(255, 255, 255)'
|
||||
if (rgbaColor == null) rgbaColor = isDark ? 'rgb(229, 229, 229)' : 'rgb(33, 33, 33)'
|
||||
if (isDark) return createFontDarkColors(rgbaColor)
|
||||
|
||||
let colors = {
|
||||
'--color-1000': rgbaColor,
|
||||
}
|
||||
let step = isDark ? -0.05 : 0.05
|
||||
for (let i = 1; i < 21; i += 1) {
|
||||
colors[`--color-${String(1000 - 50 * i).padStart(3, '0')}`] = RGB_Linear_Shade(step * i, rgbaColor)
|
||||
}
|
||||
// console.log(colors)
|
||||
return colors
|
||||
}
|
||||
|
||||
const createFontDarkColors = (rgbaColor) => {
|
||||
// rgb(238, 238, 238)
|
||||
// let prec = 'rgb(255, 255, 255)'
|
||||
|
||||
let colors = {
|
||||
'--color-1000': rgbaColor,
|
||||
}
|
||||
const step = -0.05
|
||||
let preColor = rgbaColor
|
||||
for (let i = 1; i < 21; i += 1) {
|
||||
preColor = RGB_Linear_Shade(step, preColor)
|
||||
colors[`--color-${String(1000 - 50 * i).padStart(3, '0')}`] = preColor
|
||||
}
|
||||
|
||||
// console.log(colors)
|
||||
return colors
|
||||
}
|
||||
|
||||
// console.log(createFontColors('rgb(33, 33, 33)', false))
|
||||
// console.log(createFontColors('rgb(255, 255, 255)', true))
|
||||
|
||||
// console.log(createFontDarkColors('rgb(255, 255, 255)'))
|
||||
|
||||
16
src/common/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"moduleResolution": "nodenext",
|
||||
"typeRoots": [ /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
"./types"
|
||||
],
|
||||
},
|
||||
// "include": [
|
||||
// "**/*.ts",
|
||||
// "**/*.js",
|
||||
// "**/*.vue",
|
||||
// "**/*.json",
|
||||
// ],
|
||||
}
|
||||
484
src/common/types/app_setting.d.ts
vendored
Normal file
@@ -0,0 +1,484 @@
|
||||
import type { I18n } from '@/lang/i18n'
|
||||
|
||||
declare global {
|
||||
|
||||
declare namespace LX {
|
||||
type AddMusicLocationType = 'top' | 'bottom'
|
||||
|
||||
interface AppSetting {
|
||||
version: string
|
||||
|
||||
/**
|
||||
* 窗口大小id
|
||||
*/
|
||||
'common.windowSizeId': number
|
||||
|
||||
/**
|
||||
* 窗口大小id
|
||||
*/
|
||||
'common.fontSize': number
|
||||
|
||||
/**
|
||||
* 是否以全屏启动
|
||||
*/
|
||||
'common.startInFullscreen': boolean
|
||||
|
||||
/**
|
||||
* 语言id
|
||||
*/
|
||||
'common.langId': I18n['locale'] | null
|
||||
|
||||
/**
|
||||
* api id
|
||||
*/
|
||||
'common.apiSource': string
|
||||
|
||||
/**
|
||||
* 音源名称类型,原名、别名
|
||||
*/
|
||||
'common.sourceNameType': 'alias' | 'real'
|
||||
|
||||
/**
|
||||
* 显示的字体
|
||||
*/
|
||||
'common.font': string
|
||||
|
||||
/**
|
||||
* 是否启用动画
|
||||
*/
|
||||
'common.isShowAnimation': boolean
|
||||
|
||||
/**
|
||||
* 是否启用随机弹窗动画
|
||||
*/
|
||||
'common.randomAnimate': boolean
|
||||
|
||||
/**
|
||||
* 是否同意软件协议
|
||||
*/
|
||||
'common.isAgreePact': boolean
|
||||
|
||||
/**
|
||||
* 控制按钮位置,左边、右边
|
||||
*/
|
||||
'common.controlBtnPosition': 'left' | 'right'
|
||||
|
||||
/**
|
||||
* 播放栏进度条样式
|
||||
*/
|
||||
'common.playBarProgressStyle': 'mini' | 'full' | 'middle'
|
||||
|
||||
/**
|
||||
* 启动时自动播放歌曲
|
||||
*/
|
||||
'player.startupAutoPlay': boolean
|
||||
|
||||
/**
|
||||
* 切歌模式
|
||||
*/
|
||||
'player.togglePlayMethod': 'listLoop' | 'random' | 'list' | 'singleLoop' | 'none'
|
||||
|
||||
/**
|
||||
* 是否优先播放320k音质
|
||||
*/
|
||||
'player.highQuality': boolean
|
||||
|
||||
/**
|
||||
* 是否显示任务栏进度条
|
||||
*/
|
||||
'player.isShowTaskProgess': boolean
|
||||
|
||||
/**
|
||||
* 音量大小
|
||||
*/
|
||||
'player.volume': number
|
||||
|
||||
/**
|
||||
* 是否静音
|
||||
*/
|
||||
'player.isMute': boolean
|
||||
|
||||
/**
|
||||
* 音频输出设备id
|
||||
*/
|
||||
'player.mediaDeviceId': string
|
||||
|
||||
/**
|
||||
* 是否在音频输出设备更改时暂停播放
|
||||
*/
|
||||
'player.isMediaDeviceRemovedStopPlay': boolean
|
||||
|
||||
/**
|
||||
* 是否显示歌词翻译
|
||||
*/
|
||||
'player.isShowLyricTranslation': boolean
|
||||
|
||||
/**
|
||||
* 是否显示歌词罗马音
|
||||
*/
|
||||
'player.isShowLyricRoma': boolean
|
||||
|
||||
/**
|
||||
* 是否将歌词从简体转换为繁体
|
||||
*/
|
||||
'player.isS2t': boolean
|
||||
|
||||
/**
|
||||
* 是否播放卡拉OK歌词
|
||||
*/
|
||||
'player.isPlayLxlrc': boolean
|
||||
|
||||
/**
|
||||
* 启动软件时是否恢复上次播放进度
|
||||
*/
|
||||
'player.isSavePlayTime': boolean
|
||||
|
||||
/**
|
||||
* 是否启用音频可视化
|
||||
*/
|
||||
'player.audioVisualization': boolean
|
||||
|
||||
/**
|
||||
* 定时暂停播放-是否等待歌曲播放完毕再暂停
|
||||
*/
|
||||
'player.waitPlayEndStop': boolean
|
||||
|
||||
/**
|
||||
* 定时暂停播放-倒计时时间
|
||||
*/
|
||||
'player.waitPlayEndStopTime': string
|
||||
|
||||
/**
|
||||
* 是否启用音频加载失败时自动切歌
|
||||
*/
|
||||
'player.autoSkipOnError': boolean
|
||||
|
||||
/**
|
||||
* 播放详情页-是否缩放当前播放的歌词行
|
||||
*/
|
||||
'playDetail.isZoomActiveLrc': boolean
|
||||
|
||||
/**
|
||||
* 播放详情页-是否允许通过歌词调整播放进度
|
||||
*/
|
||||
'playDetail.isShowLyricProgressSetting': boolean
|
||||
|
||||
/**
|
||||
* 播放详情页-歌词字体大小
|
||||
*/
|
||||
'playDetail.style.fontSize': number
|
||||
|
||||
/**
|
||||
* 播放详情页-歌词对齐方式
|
||||
*/
|
||||
'playDetail.style.align': 'center' | 'left' | 'right'
|
||||
|
||||
|
||||
/**
|
||||
* 是否启用桌面歌词
|
||||
*/
|
||||
'desktopLyric.enable': boolean
|
||||
|
||||
/**
|
||||
* 是否锁定桌面歌词
|
||||
*/
|
||||
'desktopLyric.isLock': boolean
|
||||
|
||||
/**
|
||||
* 是在置顶桌面
|
||||
*/
|
||||
'desktopLyric.isAlwaysOnTop': boolean
|
||||
|
||||
/**
|
||||
* 是否自动刷新歌词置顶
|
||||
*/
|
||||
'desktopLyric.isAlwaysOnTopLoop': boolean
|
||||
|
||||
/**
|
||||
* 是否将歌词进程显示在任务栏
|
||||
*/
|
||||
'desktopLyric.isShowTaskbar': boolean
|
||||
|
||||
/**
|
||||
* 是否启用音频可视化
|
||||
*/
|
||||
'desktopLyric.audioVisualization': boolean
|
||||
|
||||
/**
|
||||
* 桌面歌词窗口宽度
|
||||
*/
|
||||
'desktopLyric.width': number
|
||||
|
||||
/**
|
||||
* 桌面歌词窗口高度
|
||||
*/
|
||||
'desktopLyric.height': number
|
||||
|
||||
/**
|
||||
* 桌面歌词窗口x坐标
|
||||
*/
|
||||
'desktopLyric.x': number | null
|
||||
|
||||
/**
|
||||
* 桌面歌词窗口y坐标
|
||||
*/
|
||||
'desktopLyric.y': number | null
|
||||
|
||||
/**
|
||||
* 是否允许桌面歌词窗口拖出主屏幕之外
|
||||
*/
|
||||
'desktopLyric.isLockScreen': boolean
|
||||
|
||||
/**
|
||||
* 是否延迟桌面歌词滚动
|
||||
*/
|
||||
'desktopLyric.isDelayScroll': boolean
|
||||
|
||||
/**
|
||||
* 歌词滚动位置
|
||||
*/
|
||||
'desktopLyric.scrollAlign': 'top' | 'center'
|
||||
|
||||
/**
|
||||
* 是否在鼠标划过桌面歌词窗口时降低歌词透明度
|
||||
*/
|
||||
'desktopLyric.isHoverHide': boolean
|
||||
|
||||
/**
|
||||
* 歌词方向
|
||||
*/
|
||||
'desktopLyric.direction': 'horizontal' | 'vertical'
|
||||
|
||||
/**
|
||||
* 歌词对齐方式
|
||||
*/
|
||||
'desktopLyric.style.align': 'center' | 'left' | 'right'
|
||||
|
||||
/**
|
||||
* 桌面歌词字体
|
||||
*/
|
||||
'desktopLyric.style.font': string
|
||||
|
||||
/**
|
||||
* 桌面歌词字体大小
|
||||
*/
|
||||
'desktopLyric.style.fontSize': number
|
||||
|
||||
/**
|
||||
* 歌词间距大小
|
||||
*/
|
||||
'desktopLyric.style.lineGap': number
|
||||
|
||||
/**
|
||||
* 桌面歌词未播放字体颜色
|
||||
*/
|
||||
'desktopLyric.style.lyricUnplayColor': string
|
||||
|
||||
/**
|
||||
* 桌面歌词已播放字体颜色
|
||||
*/
|
||||
'desktopLyric.style.lyricPlayedColor': string
|
||||
|
||||
/**
|
||||
* 桌面歌词字体阴影颜色
|
||||
*/
|
||||
'desktopLyric.style.lyricShadowColor': string
|
||||
|
||||
/**
|
||||
* 桌面歌词加粗字体
|
||||
*/
|
||||
// 'desktopLyric.style.fontWeight': boolean
|
||||
|
||||
/**
|
||||
* 桌面歌词字体透明度
|
||||
*/
|
||||
'desktopLyric.style.opacity': number
|
||||
|
||||
/**
|
||||
* 桌面歌词是否允许换行
|
||||
*/
|
||||
'desktopLyric.style.ellipsis': boolean
|
||||
|
||||
/**
|
||||
* 是否缩放当前正在播放的桌面歌词
|
||||
*/
|
||||
'desktopLyric.style.isZoomActiveLrc': boolean
|
||||
|
||||
/**
|
||||
* 是否启用双击列表里的歌曲时自动切换到当前列表播放(仅对歌单、排行榜有效)
|
||||
*/
|
||||
'list.isClickPlayList': boolean
|
||||
|
||||
/**
|
||||
* 是否显示歌曲来源(仅对我的列表有效)
|
||||
*/
|
||||
'list.isShowSource': boolean
|
||||
|
||||
/**
|
||||
* 是否自动恢复列表滚动位置(仅对我的列表有效)
|
||||
*/
|
||||
'list.isSaveScrollLocation': boolean
|
||||
|
||||
/**
|
||||
* 添加歌曲到我的列表时的方式
|
||||
*/
|
||||
'list.addMusicLocationType': LX.AddMusicLocationType
|
||||
|
||||
/**
|
||||
* 是否显示列表操作按钮列
|
||||
*/
|
||||
'list.actionButtonsVisible': boolean
|
||||
|
||||
/**
|
||||
* 是否启用下载功能
|
||||
*/
|
||||
'download.enable': boolean
|
||||
|
||||
/**
|
||||
* 下载路径
|
||||
*/
|
||||
'download.savePath': string
|
||||
|
||||
/**
|
||||
* 文件命名方式
|
||||
*/
|
||||
'download.fileName': '歌名 - 歌手' | '歌手 - 歌名' | '歌名'
|
||||
|
||||
/**
|
||||
* 最大并发下载数
|
||||
*/
|
||||
'download.maxDownloadNum': number
|
||||
|
||||
/**
|
||||
* 存在同名文件时跳过下载
|
||||
*/
|
||||
'download.skipExistFile': boolean
|
||||
|
||||
/**
|
||||
* 是否下载lrc文件
|
||||
*/
|
||||
'download.isDownloadLrc': boolean
|
||||
|
||||
/**
|
||||
* 是否下载翻译歌词文件
|
||||
*/
|
||||
'download.isDownloadTLrc': boolean
|
||||
|
||||
/**
|
||||
* 是否下载罗马音歌词文件
|
||||
*/
|
||||
'download.isDownloadRLrc': boolean
|
||||
|
||||
/**
|
||||
* 保存lrc时的文本编码格式
|
||||
*/
|
||||
'download.lrcFormat': 'utf8' | 'gbk'
|
||||
|
||||
/**
|
||||
* 是否在音频文件中嵌入歌曲封面
|
||||
*/
|
||||
'download.isEmbedPic': boolean
|
||||
|
||||
/**
|
||||
* 是否在音频文件中嵌入歌词
|
||||
*/
|
||||
'download.isEmbedLyric': boolean
|
||||
|
||||
/**
|
||||
* 歌曲源不可用时,是否启用换源下载
|
||||
*/
|
||||
'download.isUseOtherSource': boolean
|
||||
|
||||
/**
|
||||
* 主题id
|
||||
*/
|
||||
'theme.id': string
|
||||
|
||||
/**
|
||||
* 亮色主题id
|
||||
*/
|
||||
'theme.lightId': string
|
||||
|
||||
/**
|
||||
* 暗色主题id
|
||||
*/
|
||||
'theme.darkId': string
|
||||
|
||||
/**
|
||||
* 是否显示热门搜索
|
||||
*/
|
||||
'search.isShowHotSearch': boolean
|
||||
|
||||
/**
|
||||
* 是否显示搜索历史
|
||||
*/
|
||||
'search.isShowHistorySearch': boolean
|
||||
|
||||
/**
|
||||
* 软件启动时是否自动聚焦搜索框
|
||||
*/
|
||||
'search.isFocusSearchBox': boolean
|
||||
|
||||
/**
|
||||
* 是否启用代理
|
||||
*/
|
||||
'network.proxy.enable': boolean
|
||||
|
||||
/**
|
||||
* 代理服务器地址
|
||||
*/
|
||||
'network.proxy.host': string
|
||||
|
||||
/**
|
||||
* 代理服务器端口号
|
||||
*/
|
||||
'network.proxy.port': string
|
||||
|
||||
/**
|
||||
* 代理服务器用户名
|
||||
*/
|
||||
'network.proxy.username': string
|
||||
|
||||
/**
|
||||
* 代理服务器密码
|
||||
*/
|
||||
'network.proxy.password': string
|
||||
|
||||
/**
|
||||
* 是否启用托盘
|
||||
*/
|
||||
'tray.enable': boolean
|
||||
|
||||
/**
|
||||
* 是否关闭时是否最小化到托盘
|
||||
*/
|
||||
// 'tray.isToTray': boolean
|
||||
|
||||
/**
|
||||
* 托盘主题id
|
||||
*/
|
||||
'tray.themeId': number
|
||||
|
||||
/**
|
||||
* 是否启用同步服务
|
||||
*/
|
||||
'sync.enable': boolean
|
||||
|
||||
/**
|
||||
* 同步服务端口号
|
||||
*/
|
||||
'sync.port': '23332' | string
|
||||
|
||||
/**
|
||||
* 是否在离开搜索界面时自动清空搜索框
|
||||
*/
|
||||
'odc.isAutoClearSearchInput': boolean
|
||||
|
||||
/**
|
||||
* 是否在离开搜索界面时自动清空搜索结果列表
|
||||
*/
|
||||
'odc.isAutoClearSearchList': boolean
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
111
src/common/types/common.d.ts
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
// import './app_setting'
|
||||
|
||||
declare namespace LX {
|
||||
interface CmdParams {
|
||||
/**
|
||||
* 搜索,启动软件时自动在搜索框搜索指定的内容,例如:-search="突然的自我 - 伍佰"
|
||||
*/
|
||||
search?: string
|
||||
|
||||
/**
|
||||
* 禁用硬件加速启动
|
||||
*/
|
||||
dha?: boolean
|
||||
|
||||
/**
|
||||
* 以非透明模式启动
|
||||
*/
|
||||
dt?: boolean
|
||||
|
||||
/**
|
||||
* 禁用硬件媒体密钥处理
|
||||
*/
|
||||
dhmkh?: boolean
|
||||
|
||||
/**
|
||||
* 设置代理服务器,代理应用的所有流量,例:-proxy-server="127.0.0.1:1081"(不支持设置账号密码,v1.17.0起新增)。注:应用内“设置-网络-代理设置”仅代理接口请求的流量,优先级更高
|
||||
*/
|
||||
'proxy-server'?: string
|
||||
|
||||
/**
|
||||
* 以分号分隔的主机列表绕过代理服务器,例:-proxy-bypass-list="<local>;*.google.com;*foo.com;1.2.3.4:5678"(与-proxy-server一起使用才有效,v1.17.0起新增)。注:此设置对应用内接口请求无效
|
||||
*/
|
||||
'proxy-bypass-list'?: string
|
||||
|
||||
/**
|
||||
* 启动时播放指定列表的音乐
|
||||
*/
|
||||
play?: string
|
||||
|
||||
[key: string]: boolean | number | string
|
||||
}
|
||||
|
||||
type OnlineSource = 'kw' | 'kg' | 'tx' | 'wy' | 'mg'
|
||||
type Source = OnlineSource | 'local'
|
||||
type Quality = '128k' | '320k' | 'flac' | 'flac24bit' | '192k' | 'ape' | 'wav'
|
||||
|
||||
type QualityList = Partial<Record<LX.Source, LX.Quality[]>>
|
||||
|
||||
interface EnvParams {
|
||||
deeplink?: string | null
|
||||
cmdParams: CmdParams
|
||||
workAreaSize?: Electron.Size
|
||||
}
|
||||
|
||||
interface HotKey {
|
||||
name: string
|
||||
action: string
|
||||
type: keyof typeof keyName
|
||||
}
|
||||
|
||||
interface HotKeyDownInfo {
|
||||
type: 'local' | 'global'
|
||||
key: string
|
||||
}
|
||||
|
||||
interface HotKeyConfig {
|
||||
enable: boolean
|
||||
keys: {
|
||||
[key: string]: HotKey
|
||||
}
|
||||
}
|
||||
interface HotKeyConfigAll {
|
||||
local: HotKeyConfig
|
||||
global: HotKeyConfig
|
||||
}
|
||||
interface RegisterKeyInfo {
|
||||
key: string
|
||||
info: HotKey
|
||||
}
|
||||
type HotKeyState = Map<string, {
|
||||
status: boolean
|
||||
info: HotKey
|
||||
}>
|
||||
interface HotKeyActionWrap<T, D> {
|
||||
action: T
|
||||
data: D
|
||||
source?: string
|
||||
}
|
||||
type HotKeyActions = HotKeyActionWrap<'config', HotKeyConfigAll>
|
||||
| HotKeyActionWrap<'enable', boolean>
|
||||
| HotKeyActionWrap<'register', RegisterKeyInfo>
|
||||
| HotKeyActionWrap<'unregister', string>
|
||||
|
||||
interface HotKeyEvent {
|
||||
type: string
|
||||
key: string
|
||||
}
|
||||
|
||||
interface TaskBarButtonFlags {
|
||||
empty: boolean
|
||||
collect: boolean
|
||||
play: boolean
|
||||
next: boolean
|
||||
prev: boolean
|
||||
}
|
||||
|
||||
interface VersionInfo {
|
||||
version: string
|
||||
desc: string
|
||||
}
|
||||
}
|
||||
9
src/common/types/config_files.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
declare namespace LX {
|
||||
namespace ConfigFile {
|
||||
interface MyListInfoPart {
|
||||
type: 'playListPart_v2'
|
||||
data: LX.List.MyDefaultListInfoFull | LX.List.MyLoveListInfoFull | LX.List.UserListInfoFull
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
85
src/common/types/desktop_lyric.d.ts
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
declare namespace LX {
|
||||
namespace DesktopLyric {
|
||||
interface Config {
|
||||
'desktopLyric.enable': LX.AppSetting['desktopLyric.enable']
|
||||
'desktopLyric.isLock': LX.AppSetting['desktopLyric.isLock']
|
||||
'desktopLyric.isAlwaysOnTop': LX.AppSetting['desktopLyric.isAlwaysOnTop']
|
||||
'desktopLyric.isAlwaysOnTopLoop': LX.AppSetting['desktopLyric.isAlwaysOnTopLoop']
|
||||
'desktopLyric.isShowTaskbar': LX.AppSetting['desktopLyric.isShowTaskbar']
|
||||
'desktopLyric.audioVisualization': LX.AppSetting['desktopLyric.audioVisualization']
|
||||
'desktopLyric.width': LX.AppSetting['desktopLyric.width']
|
||||
'desktopLyric.height': LX.AppSetting['desktopLyric.height']
|
||||
'desktopLyric.x': LX.AppSetting['desktopLyric.x']
|
||||
'desktopLyric.y': LX.AppSetting['desktopLyric.y']
|
||||
'desktopLyric.isLockScreen': LX.AppSetting['desktopLyric.isLockScreen']
|
||||
'desktopLyric.isDelayScroll': LX.AppSetting['desktopLyric.isDelayScroll']
|
||||
'desktopLyric.scrollAlign': LX.AppSetting['desktopLyric.scrollAlign']
|
||||
'desktopLyric.isHoverHide': LX.AppSetting['desktopLyric.isHoverHide']
|
||||
'desktopLyric.direction': LX.AppSetting['desktopLyric.direction']
|
||||
'desktopLyric.style.align': LX.AppSetting['desktopLyric.style.align']
|
||||
'desktopLyric.style.font': LX.AppSetting['desktopLyric.style.font']
|
||||
'desktopLyric.style.fontSize': LX.AppSetting['desktopLyric.style.fontSize']
|
||||
'desktopLyric.style.lineGap': LX.AppSetting['desktopLyric.style.lineGap']
|
||||
'desktopLyric.style.lyricUnplayColor': LX.AppSetting['desktopLyric.style.lyricUnplayColor']
|
||||
'desktopLyric.style.lyricPlayedColor': LX.AppSetting['desktopLyric.style.lyricPlayedColor']
|
||||
'desktopLyric.style.lyricShadowColor': LX.AppSetting['desktopLyric.style.lyricShadowColor']
|
||||
// 'desktopLyric.style.fontWeight': LX.AppSetting['desktopLyric.style.fontWeight']
|
||||
'desktopLyric.style.opacity': LX.AppSetting['desktopLyric.style.opacity']
|
||||
'desktopLyric.style.ellipsis': LX.AppSetting['desktopLyric.style.ellipsis']
|
||||
'desktopLyric.style.isZoomActiveLrc': LX.AppSetting['desktopLyric.style.isZoomActiveLrc']
|
||||
'common.langId': LX.AppSetting['common.langId']
|
||||
'player.isShowLyricTranslation': LX.AppSetting['player.isShowLyricTranslation']
|
||||
'player.isShowLyricRoma': LX.AppSetting['player.isShowLyricRoma']
|
||||
'player.isPlayLxlrc': LX.AppSetting['player.isPlayLxlrc']
|
||||
}
|
||||
|
||||
type WinMainActions = 'get_info' | 'get_status' | 'get_analyser_data_array'
|
||||
|
||||
interface LyricActionBase <A> {
|
||||
action: A
|
||||
}
|
||||
interface LyricActionData<A, D> extends LyricActionBase<A> {
|
||||
data: D
|
||||
}
|
||||
type LyricAction<A, D = undefined> = D extends undefined ? LyricActionBase<A> : LyricActionData<A, D>
|
||||
|
||||
type LyricActions = LyricAction<'set_info', {
|
||||
id: string | null
|
||||
singer: string
|
||||
name: string
|
||||
album: string
|
||||
lrc: string | null
|
||||
tlrc: string | null
|
||||
rlrc: string | null
|
||||
lxlrc: string | null
|
||||
// pic: string | null
|
||||
isPlay: boolean
|
||||
line: number
|
||||
played_time: number
|
||||
}>
|
||||
| LyricAction<'set_status', {
|
||||
isPlay: boolean
|
||||
line: number
|
||||
played_time: number
|
||||
}>
|
||||
| LyricAction<'set_lyric', {
|
||||
lrc: string | null
|
||||
tlrc: string | null
|
||||
rlrc: string | null
|
||||
lxlrc: string | null
|
||||
}>
|
||||
| LyricAction<'set_offset', number>
|
||||
| LyricAction<'set_play', number>
|
||||
| LyricAction<'set_pause'>
|
||||
| LyricAction<'set_stop'>
|
||||
| LyricAction<'send_analyser_data_array', Uint8Array>
|
||||
|
||||
|
||||
interface NewBounds {
|
||||
x?: number | null
|
||||
y?: number
|
||||
w: number
|
||||
h: number
|
||||
}
|
||||
}
|
||||
}
|
||||
66
src/common/types/download_list.d.ts
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
|
||||
// interface DownloadList {
|
||||
|
||||
// }
|
||||
|
||||
|
||||
declare namespace LX {
|
||||
namespace Download {
|
||||
type DownloadTaskStatus = 'run'
|
||||
| 'waiting'
|
||||
| 'pause'
|
||||
| 'error'
|
||||
| 'completed'
|
||||
|
||||
type FileExt = 'mp3' | 'flac' | 'wav' | 'ape'
|
||||
|
||||
interface ProgressInfo {
|
||||
progress: number
|
||||
speed: string
|
||||
downloaded: number
|
||||
total: number
|
||||
}
|
||||
|
||||
interface DownloadTaskActionBase <A> {
|
||||
action: A
|
||||
}
|
||||
interface DownloadTaskActionData<A, D> extends DownloadTaskActionBase<A> {
|
||||
data: D
|
||||
}
|
||||
type DownloadTaskAction<A, D = undefined> = D extends undefined ? DownloadTaskActionBase<A> : DownloadTaskActionData<A, D>
|
||||
|
||||
type DownloadTaskActions = DownloadTaskAction<'start'>
|
||||
| DownloadTaskAction<'complete'>
|
||||
| DownloadTaskAction<'refreshUrl'>
|
||||
| DownloadTaskAction<'statusText', string>
|
||||
| DownloadTaskAction<'progress', ProgressInfo>
|
||||
| DownloadTaskAction<'error', {
|
||||
error?: string
|
||||
message?: string
|
||||
}>
|
||||
|
||||
interface ListItem {
|
||||
id: string
|
||||
isComplate: boolean
|
||||
status: DownloadTaskStatus
|
||||
statusText: string
|
||||
downloaded: number
|
||||
total: number
|
||||
progress: number
|
||||
speed: string
|
||||
metadata: {
|
||||
musicInfo: LX.Music.MusicInfoOnline
|
||||
url: string | null
|
||||
quality: LX.Quality
|
||||
ext: FileExt
|
||||
fileName: string
|
||||
filePath: string
|
||||
}
|
||||
}
|
||||
|
||||
interface saveDownloadMusicInfo {
|
||||
list: ListItem[]
|
||||
addMusicLocationType: LX.AddMusicLocationType
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/common/types/ipc_main.d.ts
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
declare namespace LX {
|
||||
interface IpcMainEvent {
|
||||
event: Electron.IpcMainEvent
|
||||
}
|
||||
interface IpcMainEventParams<T> {
|
||||
event: Electron.IpcMainEvent
|
||||
params: T
|
||||
}
|
||||
type IpcMainEventListener = (params: LX.IpcMainEvent) => void
|
||||
type IpcMainEventListenerParams<T> = (params: LX.IpcMainEventParams<T>) => void
|
||||
|
||||
interface IpcMainInvokeEvent {
|
||||
event: Electron.IpcMainInvokeEvent
|
||||
}
|
||||
interface IpcMainInvokeEventParams<T> {
|
||||
event: Electron.IpcMainInvokeEvent
|
||||
params: T
|
||||
}
|
||||
|
||||
type IpcMainInvokeEventListener = (params: LX.IpcMainInvokeEvent) => Promise<void>
|
||||
type IpcMainInvokeEventListenerParams<T> = (params: LX.IpcMainInvokeEventParams<T>) => Promise<void>
|
||||
type IpcMainInvokeEventListenerValue<V> = (params: LX.IpcMainInvokeEvent) => Promise<V>
|
||||
type IpcMainInvokeEventListenerParamsValue<T, V> = (params: LX.IpcMainInvokeEventParams<T>) => Promise<V>
|
||||
}
|
||||
11
src/common/types/ipc_renderer.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
declare namespace LX {
|
||||
interface IpcRendererEvent {
|
||||
event: Electron.IpcRendererEvent
|
||||
}
|
||||
interface IpcRendererEventParams<T> {
|
||||
event: Electron.IpcRendererEvent
|
||||
params: T
|
||||
}
|
||||
type IpcRendererEventListener = (params: LX.IpcRendererEvent) => any
|
||||
type IpcRendererEventListenerParams<T> = (params: LX.IpcRendererEventParams<T>) => any
|
||||
}
|
||||
142
src/common/types/list.d.ts
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
declare namespace LX {
|
||||
namespace List {
|
||||
interface UserListInfo {
|
||||
id: string
|
||||
name: string
|
||||
// list: LX.Music.MusicInfo[]
|
||||
source?: LX.Source
|
||||
sourceListId?: string
|
||||
// position?: number
|
||||
locationUpdateTime: number | null
|
||||
}
|
||||
|
||||
interface MyDefaultListInfo {
|
||||
id: 'default'
|
||||
name: '试听列表'
|
||||
// list: LX.Music.MusicInfo[]
|
||||
}
|
||||
|
||||
interface MyLoveListInfo {
|
||||
id: 'love'
|
||||
name: '我的收藏'
|
||||
// list: LX.Music.MusicInfo[]
|
||||
}
|
||||
|
||||
interface MyTempListInfo {
|
||||
id: 'temp'
|
||||
name: '临时列表'
|
||||
// list: LX.Music.MusicInfo[]
|
||||
// TODO: save default lists info
|
||||
meta: {
|
||||
id?: string
|
||||
}
|
||||
}
|
||||
|
||||
type MyListInfo = MyDefaultListInfo | MyLoveListInfo | UserListInfo
|
||||
|
||||
interface MyAllList {
|
||||
defaultList: MyDefaultListInfo
|
||||
loveList: MyLoveListInfo
|
||||
userList: UserListInfo[]
|
||||
tempList: MyTempListInfo
|
||||
}
|
||||
|
||||
|
||||
type SearchHistoryList = string[]
|
||||
type ListPositionInfo = Record<string, number>
|
||||
type ListUpdateInfo = Record<string, {
|
||||
updateTime: number
|
||||
isAutoUpdate: boolean
|
||||
}>
|
||||
|
||||
type ListSaveType = 'myList' | 'downloadList'
|
||||
type ListSaveInfo = {
|
||||
type: 'myList'
|
||||
data: Partial<MyAllList>
|
||||
} | {
|
||||
type: 'downloadList'
|
||||
data: LX.Download.ListItem[]
|
||||
}
|
||||
|
||||
|
||||
type ListActionDataOverwrite = MakeOptional<LX.List.ListDataFull, 'tempList'>
|
||||
interface ListActionAdd {
|
||||
position: number
|
||||
listInfos: UserListInfo[]
|
||||
}
|
||||
type ListActionRemove = string[]
|
||||
type ListActionUpdate = UserListInfo[]
|
||||
interface ListActionUpdatePosition {
|
||||
/**
|
||||
* 列表id
|
||||
*/
|
||||
ids: string[]
|
||||
/**
|
||||
* 位置
|
||||
*/
|
||||
position: number
|
||||
}
|
||||
|
||||
interface ListActionMusicAdd {
|
||||
id: string
|
||||
musicInfos: LX.Music.MusicInfo[]
|
||||
addMusicLocationType: LX.AddMusicLocationType
|
||||
}
|
||||
|
||||
interface ListActionMusicMove {
|
||||
fromId: string
|
||||
toId: string
|
||||
musicInfos: LX.Music.MusicInfo[]
|
||||
addMusicLocationType: LX.AddMusicLocationType
|
||||
}
|
||||
|
||||
interface ListActionCheckMusicExistList {
|
||||
listId: string
|
||||
musicInfoId: string
|
||||
}
|
||||
|
||||
interface ListActionMusicRemove {
|
||||
listId: string
|
||||
ids: string[]
|
||||
}
|
||||
|
||||
type ListActionMusicUpdate = Array<{
|
||||
id: string
|
||||
musicInfo: LX.Music.MusicInfo
|
||||
}>
|
||||
|
||||
interface ListActionMusicUpdatePosition {
|
||||
listId: string
|
||||
position: number
|
||||
ids: string[]
|
||||
}
|
||||
|
||||
interface ListActionMusicOverwrite {
|
||||
listId: string
|
||||
musicInfos: LX.Music.MusicInfo[]
|
||||
}
|
||||
|
||||
type ListActionMusicClear = string[]
|
||||
|
||||
interface MyDefaultListInfoFull extends MyDefaultListInfo {
|
||||
list: LX.Music.MusicInfo[]
|
||||
}
|
||||
interface MyLoveListInfoFull extends MyLoveListInfo {
|
||||
list: LX.Music.MusicInfo[]
|
||||
}
|
||||
interface UserListInfoFull extends UserListInfo {
|
||||
list: LX.Music.MusicInfo[]
|
||||
}
|
||||
interface MyTempListInfoFull extends MyTempListInfo {
|
||||
list: LX.Music.MusicInfo[]
|
||||
}
|
||||
|
||||
interface ListDataFull {
|
||||
defaultList: LX.Music.MusicInfo[]
|
||||
loveList: LX.Music.MusicInfo[]
|
||||
userList: UserListInfoFull[]
|
||||
tempList: LX.Music.MusicInfo[]
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
122
src/common/types/music.d.ts
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
declare namespace LX {
|
||||
namespace Music {
|
||||
interface MusicQualityType { // {"type": "128k", size: "3.56M"}
|
||||
type: LX.Quality
|
||||
size: string | null
|
||||
}
|
||||
interface MusicQualityTypeKg { // {"type": "128k", size: "3.56M"}
|
||||
type: LX.Quality
|
||||
size: string | null
|
||||
hash: string
|
||||
}
|
||||
type _MusicQualityType = Partial<Record<Quality, {
|
||||
size: string | null
|
||||
}>>
|
||||
type _MusicQualityTypeKg = Partial<Record<Quality, {
|
||||
size: string | null
|
||||
hash: string
|
||||
}>>
|
||||
|
||||
|
||||
interface MusicInfoMetaBase {
|
||||
songId: string | number // 歌曲ID,mg源为copyrightId,local为文件路径
|
||||
albumName: string // 歌曲专辑名称
|
||||
picUrl?: string | null // 歌曲图片链接
|
||||
}
|
||||
|
||||
interface MusicInfoMeta_online extends MusicInfoMetaBase {
|
||||
qualitys: MusicQualityType[]
|
||||
_qualitys: _MusicQualityType
|
||||
albumId?: string | number // 歌曲专辑ID
|
||||
}
|
||||
|
||||
interface MusicInfoMeta_local extends MusicInfoMetaBase {
|
||||
filePath: string
|
||||
ext: string
|
||||
}
|
||||
|
||||
|
||||
interface MusicInfoBase<S = LX.Source> {
|
||||
id: string
|
||||
name: string // 歌曲名
|
||||
singer: string // 艺术家名
|
||||
source: S // 源
|
||||
interval: string | null // 格式化后的歌曲时长,例:03:55
|
||||
meta: MusicInfoMetaBase
|
||||
}
|
||||
|
||||
interface MusicInfoLocal extends MusicInfoBase<'local'> {
|
||||
meta: MusicInfoMeta_local
|
||||
}
|
||||
|
||||
interface MusicInfo_online_common extends MusicInfoBase<'kw' | 'wy'> {
|
||||
meta: MusicInfoMeta_online
|
||||
}
|
||||
|
||||
interface MusicInfoMeta_kg extends MusicInfoMeta_online {
|
||||
qualitys: MusicQualityTypeKg[]
|
||||
_qualitys: _MusicQualityTypeKg
|
||||
hash: string // 歌曲hash
|
||||
}
|
||||
interface MusicInfo_kg extends MusicInfoBase<'kg'> {
|
||||
meta: MusicInfoMeta_kg
|
||||
}
|
||||
|
||||
interface MusicInfoMeta_tx extends MusicInfoMeta_online {
|
||||
strMediaMid: string // 歌曲strMediaMid
|
||||
id?: number // 歌曲songId
|
||||
albumMid?: string // 歌曲albumMid
|
||||
}
|
||||
interface MusicInfo_tx extends MusicInfoBase<'tx'> {
|
||||
meta: MusicInfoMeta_tx
|
||||
}
|
||||
|
||||
interface MusicInfoMeta_mg extends MusicInfoMeta_online {
|
||||
copyrightId: string // 歌曲copyrightId
|
||||
lrcUrl?: string // 歌曲lrcUrl
|
||||
mrcUrl?: string // 歌曲mrcUrl
|
||||
trcUrl?: string // 歌曲trcUrl
|
||||
}
|
||||
interface MusicInfo_mg extends MusicInfoBase<'mg'> {
|
||||
meta: MusicInfoMeta_mg
|
||||
}
|
||||
|
||||
type MusicInfoOnline = MusicInfo_online_common | MusicInfo_kg | MusicInfo_tx | MusicInfo_mg
|
||||
type MusicInfo = MusicInfoOnline | MusicInfoLocal
|
||||
|
||||
interface LyricInfo {
|
||||
// 歌曲歌词
|
||||
lyric: string
|
||||
// 翻译歌词
|
||||
tlyric?: string | null
|
||||
// 罗马音歌词
|
||||
rlyric?: string | null
|
||||
// 逐字歌词
|
||||
lxlyric?: string | null
|
||||
}
|
||||
|
||||
interface LyricInfoSave {
|
||||
id: string
|
||||
lyrics: LyricInfo
|
||||
}
|
||||
|
||||
interface MusicFileMeta {
|
||||
title: string
|
||||
artist: string | null
|
||||
album: string | null
|
||||
APIC: string | null
|
||||
lyrics: string | null
|
||||
}
|
||||
|
||||
interface MusicUrlInfo {
|
||||
id: string
|
||||
url: string
|
||||
}
|
||||
|
||||
interface MusicInfoOtherSourceSave {
|
||||
id: string
|
||||
list: MusicInfoOnline[]
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
11
src/common/types/music_metadata.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import {
|
||||
IAudioMetadata as iAudioMetadata,
|
||||
} from 'music-metadata'
|
||||
|
||||
declare global {
|
||||
namespace LX {
|
||||
namespace MusicMetadataModule {
|
||||
type IAudioMetadata = iAudioMetadata
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/common/types/player.d.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
declare namespace LX {
|
||||
namespace Player {
|
||||
interface ProgressBarOptions {
|
||||
progress: number
|
||||
mode?: Electron.ProgressBarOptions['mode']
|
||||
}
|
||||
|
||||
type StatusButtonActions = 'unCollect'
|
||||
| 'collect'
|
||||
| 'prev'
|
||||
| 'pause'
|
||||
| 'play'
|
||||
| 'next'
|
||||
|
||||
interface LyricInfo extends LX.Music.LyricInfo {
|
||||
rawlrcInfo: LX.Music.LyricInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/common/types/shims_vue.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
// declare module '*.vue' {
|
||||
// import { App } from 'vue'
|
||||
// export default App.Component
|
||||
// }
|
||||
|
||||
declare module '*.vue' {
|
||||
import { Component } from 'vue'
|
||||
const component: Component
|
||||
export default component
|
||||
}
|
||||
72
src/common/types/sync.d.ts
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
declare namespace LX {
|
||||
namespace Sync {
|
||||
|
||||
interface Enable {
|
||||
enable: boolean
|
||||
port: string
|
||||
}
|
||||
|
||||
interface SyncActionBase <A> {
|
||||
action: A
|
||||
}
|
||||
interface SyncActionData<A, D> extends SyncActionBase<A> {
|
||||
data: D
|
||||
}
|
||||
type SyncAction<A, D = undefined> = D extends undefined ? SyncActionBase<A> : SyncActionData<A, D>
|
||||
|
||||
type SyncMainWindowActions = SyncAction<'select_mode', KeyInfo>
|
||||
| SyncAction<'close_select_mode'>
|
||||
| SyncAction<'status', Status>
|
||||
|
||||
type SyncServiceActions = SyncAction<'select_mode', Mode>
|
||||
| SyncAction<'get_status'>
|
||||
| SyncAction<'generate_code'>
|
||||
| SyncAction<'enable', Enable>
|
||||
|
||||
type ActionList = SyncAction<'list_data_overwrite', LX.List.ListActionDataOverwrite>
|
||||
| SyncAction<'list_create', LX.List.ListActionAdd>
|
||||
| SyncAction<'list_remove', LX.List.ListActionRemove>
|
||||
| SyncAction<'list_update', LX.List.ListActionUpdate>
|
||||
| SyncAction<'list_update_position', LX.List.ListActionUpdatePosition>
|
||||
| SyncAction<'list_music_add', LX.List.ListActionMusicAdd>
|
||||
| SyncAction<'list_music_move', LX.List.ListActionMusicMove>
|
||||
| SyncAction<'list_music_remove', LX.List.ListActionMusicRemove>
|
||||
| SyncAction<'list_music_update', LX.List.ListActionMusicUpdate>
|
||||
| SyncAction<'list_music_update_position', LX.List.ListActionMusicUpdatePosition>
|
||||
| SyncAction<'list_music_overwrite', LX.List.ListActionMusicOverwrite>
|
||||
| SyncAction<'list_music_clear', LX.List.ListActionMusicClear>
|
||||
|
||||
|
||||
interface List {
|
||||
action: string
|
||||
data: any
|
||||
}
|
||||
|
||||
interface Status {
|
||||
status: boolean
|
||||
message: string
|
||||
address: string[]
|
||||
code: string
|
||||
devices: KeyInfo[]
|
||||
}
|
||||
|
||||
interface KeyInfo {
|
||||
clientId: string
|
||||
key: string
|
||||
deviceName: string
|
||||
connectionTime?: number
|
||||
}
|
||||
|
||||
type ListData = Omit<LX.List.ListDataFull, 'tempList'>
|
||||
|
||||
type Mode = 'merge_local_remote'
|
||||
| 'merge_remote_local'
|
||||
| 'overwrite_local_remote'
|
||||
| 'overwrite_remote_local'
|
||||
| 'overwrite_local_remote_full'
|
||||
| 'overwrite_remote_local_full'
|
||||
| 'none'
|
||||
| 'cancel'
|
||||
|
||||
}
|
||||
}
|
||||
305
src/common/types/theme.d.ts
vendored
Normal file
@@ -0,0 +1,305 @@
|
||||
declare namespace LX {
|
||||
|
||||
interface ThemeColors {
|
||||
'--color-000': string
|
||||
'--color-050': string
|
||||
'--color-100': string
|
||||
'--color-150': string
|
||||
'--color-200': string
|
||||
'--color-250': string
|
||||
'--color-300': string
|
||||
'--color-350': string
|
||||
'--color-400': string
|
||||
'--color-450': string
|
||||
'--color-500': string
|
||||
'--color-550': string
|
||||
'--color-600': string
|
||||
'--color-650': string
|
||||
'--color-700': string
|
||||
'--color-750': string
|
||||
'--color-800': string
|
||||
'--color-850': string
|
||||
'--color-900': string
|
||||
'--color-950': string
|
||||
'--color-1000': string
|
||||
|
||||
|
||||
'--color-theme': string
|
||||
|
||||
'--color-primary': string
|
||||
'--color-primary-alpha-100': string
|
||||
'--color-primary-alpha-200': string
|
||||
'--color-primary-alpha-300': string
|
||||
'--color-primary-alpha-400': string
|
||||
'--color-primary-alpha-500': string
|
||||
'--color-primary-alpha-600': string
|
||||
'--color-primary-alpha-700': string
|
||||
'--color-primary-alpha-800': string
|
||||
'--color-primary-alpha-900': string
|
||||
|
||||
'--color-primary-dark-100': string
|
||||
'--color-primary-dark-100-alpha-100': string
|
||||
'--color-primary-dark-100-alpha-200': string
|
||||
'--color-primary-dark-100-alpha-300': string
|
||||
'--color-primary-dark-100-alpha-400': string
|
||||
'--color-primary-dark-100-alpha-500': string
|
||||
'--color-primary-dark-100-alpha-600': string
|
||||
'--color-primary-dark-100-alpha-700': string
|
||||
'--color-primary-dark-100-alpha-800': string
|
||||
'--color-primary-dark-100-alpha-900': string
|
||||
|
||||
'--color-primary-dark-200': string
|
||||
'--color-primary-dark-200-alpha-100': string
|
||||
'--color-primary-dark-200-alpha-200': string
|
||||
'--color-primary-dark-200-alpha-300': string
|
||||
'--color-primary-dark-200-alpha-400': string
|
||||
'--color-primary-dark-200-alpha-500': string
|
||||
'--color-primary-dark-200-alpha-600': string
|
||||
'--color-primary-dark-200-alpha-700': string
|
||||
'--color-primary-dark-200-alpha-800': string
|
||||
'--color-primary-dark-200-alpha-900': string
|
||||
|
||||
'--color-primary-dark-300': string
|
||||
'--color-primary-dark-300-alpha-100': string
|
||||
'--color-primary-dark-300-alpha-200': string
|
||||
'--color-primary-dark-300-alpha-300': string
|
||||
'--color-primary-dark-300-alpha-400': string
|
||||
'--color-primary-dark-300-alpha-500': string
|
||||
'--color-primary-dark-300-alpha-600': string
|
||||
'--color-primary-dark-300-alpha-700': string
|
||||
'--color-primary-dark-300-alpha-800': string
|
||||
'--color-primary-dark-300-alpha-900': string
|
||||
|
||||
'--color-primary-dark-400': string
|
||||
'--color-primary-dark-400-alpha-100': string
|
||||
'--color-primary-dark-400-alpha-200': string
|
||||
'--color-primary-dark-400-alpha-300': string
|
||||
'--color-primary-dark-400-alpha-400': string
|
||||
'--color-primary-dark-400-alpha-500': string
|
||||
'--color-primary-dark-400-alpha-600': string
|
||||
'--color-primary-dark-400-alpha-700': string
|
||||
'--color-primary-dark-400-alpha-800': string
|
||||
'--color-primary-dark-400-alpha-900': string
|
||||
|
||||
'--color-primary-dark-500': string
|
||||
'--color-primary-dark-500-alpha-100': string
|
||||
'--color-primary-dark-500-alpha-200': string
|
||||
'--color-primary-dark-500-alpha-300': string
|
||||
'--color-primary-dark-500-alpha-400': string
|
||||
'--color-primary-dark-500-alpha-500': string
|
||||
'--color-primary-dark-500-alpha-600': string
|
||||
'--color-primary-dark-500-alpha-700': string
|
||||
'--color-primary-dark-500-alpha-800': string
|
||||
'--color-primary-dark-500-alpha-900': string
|
||||
|
||||
'--color-primary-dark-600': string
|
||||
'--color-primary-dark-600-alpha-100': string
|
||||
'--color-primary-dark-600-alpha-200': string
|
||||
'--color-primary-dark-600-alpha-300': string
|
||||
'--color-primary-dark-600-alpha-400': string
|
||||
'--color-primary-dark-600-alpha-500': string
|
||||
'--color-primary-dark-600-alpha-600': string
|
||||
'--color-primary-dark-600-alpha-700': string
|
||||
'--color-primary-dark-600-alpha-800': string
|
||||
'--color-primary-dark-600-alpha-900': string
|
||||
|
||||
'--color-primary-dark-700': string
|
||||
'--color-primary-dark-700-alpha-100': string
|
||||
'--color-primary-dark-700-alpha-200': string
|
||||
'--color-primary-dark-700-alpha-300': string
|
||||
'--color-primary-dark-700-alpha-400': string
|
||||
'--color-primary-dark-700-alpha-500': string
|
||||
'--color-primary-dark-700-alpha-600': string
|
||||
'--color-primary-dark-700-alpha-700': string
|
||||
'--color-primary-dark-700-alpha-800': string
|
||||
'--color-primary-dark-700-alpha-900': string
|
||||
|
||||
'--color-primary-dark-800': string
|
||||
'--color-primary-dark-800-alpha-100': string
|
||||
'--color-primary-dark-800-alpha-200': string
|
||||
'--color-primary-dark-800-alpha-300': string
|
||||
'--color-primary-dark-800-alpha-400': string
|
||||
'--color-primary-dark-800-alpha-500': string
|
||||
'--color-primary-dark-800-alpha-600': string
|
||||
'--color-primary-dark-800-alpha-700': string
|
||||
'--color-primary-dark-800-alpha-800': string
|
||||
'--color-primary-dark-800-alpha-900': string
|
||||
|
||||
'--color-primary-dark-900': string
|
||||
'--color-primary-dark-900-alpha-100': string
|
||||
'--color-primary-dark-900-alpha-200': string
|
||||
'--color-primary-dark-900-alpha-300': string
|
||||
'--color-primary-dark-900-alpha-400': string
|
||||
'--color-primary-dark-900-alpha-500': string
|
||||
'--color-primary-dark-900-alpha-600': string
|
||||
'--color-primary-dark-900-alpha-700': string
|
||||
'--color-primary-dark-900-alpha-800': string
|
||||
'--color-primary-dark-900-alpha-900': string
|
||||
|
||||
'--color-primary-dark-1000': string
|
||||
'--color-primary-dark-1000-alpha-100': string
|
||||
'--color-primary-dark-1000-alpha-200': string
|
||||
'--color-primary-dark-1000-alpha-300': string
|
||||
'--color-primary-dark-1000-alpha-400': string
|
||||
'--color-primary-dark-1000-alpha-500': string
|
||||
'--color-primary-dark-1000-alpha-600': string
|
||||
'--color-primary-dark-1000-alpha-700': string
|
||||
'--color-primary-dark-1000-alpha-800': string
|
||||
'--color-primary-dark-1000-alpha-900': string
|
||||
|
||||
'--color-primary-light-100': string
|
||||
'--color-primary-light-100-alpha-100': string
|
||||
'--color-primary-light-100-alpha-200': string
|
||||
'--color-primary-light-100-alpha-300': string
|
||||
'--color-primary-light-100-alpha-400': string
|
||||
'--color-primary-light-100-alpha-500': string
|
||||
'--color-primary-light-100-alpha-600': string
|
||||
'--color-primary-light-100-alpha-700': string
|
||||
'--color-primary-light-100-alpha-800': string
|
||||
'--color-primary-light-100-alpha-900': string
|
||||
|
||||
'--color-primary-light-200': string
|
||||
'--color-primary-light-200-alpha-100': string
|
||||
'--color-primary-light-200-alpha-200': string
|
||||
'--color-primary-light-200-alpha-300': string
|
||||
'--color-primary-light-200-alpha-400': string
|
||||
'--color-primary-light-200-alpha-500': string
|
||||
'--color-primary-light-200-alpha-600': string
|
||||
'--color-primary-light-200-alpha-700': string
|
||||
'--color-primary-light-200-alpha-800': string
|
||||
'--color-primary-light-200-alpha-900': string
|
||||
|
||||
'--color-primary-light-300': string
|
||||
'--color-primary-light-300-alpha-100': string
|
||||
'--color-primary-light-300-alpha-200': string
|
||||
'--color-primary-light-300-alpha-300': string
|
||||
'--color-primary-light-300-alpha-400': string
|
||||
'--color-primary-light-300-alpha-500': string
|
||||
'--color-primary-light-300-alpha-600': string
|
||||
'--color-primary-light-300-alpha-700': string
|
||||
'--color-primary-light-300-alpha-800': string
|
||||
'--color-primary-light-300-alpha-900': string
|
||||
|
||||
'--color-primary-light-400': string
|
||||
'--color-primary-light-400-alpha-100': string
|
||||
'--color-primary-light-400-alpha-200': string
|
||||
'--color-primary-light-400-alpha-300': string
|
||||
'--color-primary-light-400-alpha-400': string
|
||||
'--color-primary-light-400-alpha-500': string
|
||||
'--color-primary-light-400-alpha-600': string
|
||||
'--color-primary-light-400-alpha-700': string
|
||||
'--color-primary-light-400-alpha-800': string
|
||||
'--color-primary-light-400-alpha-900': string
|
||||
|
||||
'--color-primary-light-500': string
|
||||
'--color-primary-light-500-alpha-100': string
|
||||
'--color-primary-light-500-alpha-200': string
|
||||
'--color-primary-light-500-alpha-300': string
|
||||
'--color-primary-light-500-alpha-400': string
|
||||
'--color-primary-light-500-alpha-500': string
|
||||
'--color-primary-light-500-alpha-600': string
|
||||
'--color-primary-light-500-alpha-700': string
|
||||
'--color-primary-light-500-alpha-800': string
|
||||
'--color-primary-light-500-alpha-900': string
|
||||
|
||||
'--color-primary-light-600': string
|
||||
'--color-primary-light-600-alpha-100': string
|
||||
'--color-primary-light-600-alpha-200': string
|
||||
'--color-primary-light-600-alpha-300': string
|
||||
'--color-primary-light-600-alpha-400': string
|
||||
'--color-primary-light-600-alpha-500': string
|
||||
'--color-primary-light-600-alpha-600': string
|
||||
'--color-primary-light-600-alpha-700': string
|
||||
'--color-primary-light-600-alpha-800': string
|
||||
'--color-primary-light-600-alpha-900': string
|
||||
|
||||
'--color-primary-light-700': string
|
||||
'--color-primary-light-700-alpha-100': string
|
||||
'--color-primary-light-700-alpha-200': string
|
||||
'--color-primary-light-700-alpha-300': string
|
||||
'--color-primary-light-700-alpha-400': string
|
||||
'--color-primary-light-700-alpha-500': string
|
||||
'--color-primary-light-700-alpha-600': string
|
||||
'--color-primary-light-700-alpha-700': string
|
||||
'--color-primary-light-700-alpha-800': string
|
||||
'--color-primary-light-700-alpha-900': string
|
||||
|
||||
'--color-primary-light-800': string
|
||||
'--color-primary-light-800-alpha-100': string
|
||||
'--color-primary-light-800-alpha-200': string
|
||||
'--color-primary-light-800-alpha-300': string
|
||||
'--color-primary-light-800-alpha-400': string
|
||||
'--color-primary-light-800-alpha-500': string
|
||||
'--color-primary-light-800-alpha-600': string
|
||||
'--color-primary-light-800-alpha-700': string
|
||||
'--color-primary-light-800-alpha-800': string
|
||||
'--color-primary-light-800-alpha-900': string
|
||||
|
||||
'--color-primary-light-900': string
|
||||
'--color-primary-light-900-alpha-100': string
|
||||
'--color-primary-light-900-alpha-200': string
|
||||
'--color-primary-light-900-alpha-300': string
|
||||
'--color-primary-light-900-alpha-400': string
|
||||
'--color-primary-light-900-alpha-500': string
|
||||
'--color-primary-light-900-alpha-600': string
|
||||
'--color-primary-light-900-alpha-700': string
|
||||
'--color-primary-light-900-alpha-800': string
|
||||
'--color-primary-light-900-alpha-900': string
|
||||
|
||||
'--color-primary-light-1000': string
|
||||
'--color-primary-light-1000-alpha-100': string
|
||||
'--color-primary-light-1000-alpha-200': string
|
||||
'--color-primary-light-1000-alpha-300': string
|
||||
'--color-primary-light-1000-alpha-400': string
|
||||
'--color-primary-light-1000-alpha-500': string
|
||||
'--color-primary-light-1000-alpha-600': string
|
||||
'--color-primary-light-1000-alpha-700': string
|
||||
'--color-primary-light-1000-alpha-800': string
|
||||
'--color-primary-light-1000-alpha-900': string
|
||||
}
|
||||
|
||||
interface Theme {
|
||||
id: string
|
||||
name: string
|
||||
isDark: boolean
|
||||
isCustom: boolean
|
||||
config: {
|
||||
themeColors: ThemeColors
|
||||
extInfo: {
|
||||
'--color-app-background': string
|
||||
'--color-main-background': string
|
||||
'--color-nav-font': string
|
||||
'--background-image': string
|
||||
'--background-image-position': string
|
||||
'--background-image-size': string
|
||||
|
||||
// 关闭按钮颜色
|
||||
'--color-btn-hide': string
|
||||
'--color-btn-min': string
|
||||
'--color-btn-close': string
|
||||
|
||||
// 徽章颜色
|
||||
'--color-badge-primary': string
|
||||
'--color-badge-secondary': string
|
||||
'--color-badge-tertiary': string
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ThemeInfo {
|
||||
themes: LX.Theme[]
|
||||
userThemes: LX.Theme[]
|
||||
dataPath: string
|
||||
}
|
||||
|
||||
interface ThemeSetting {
|
||||
shouldUseDarkColors: boolean
|
||||
theme: {
|
||||
id: string
|
||||
name: string
|
||||
isDark: boolean
|
||||
colors: Record<string, string>
|
||||
}
|
||||
}
|
||||
}
|
||||
56
src/common/types/user_api.d.ts
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
declare namespace LX {
|
||||
namespace UserApi {
|
||||
type UserApiSourceInfoType = 'music'
|
||||
type UserApiSourceInfoActions = 'musicUrl'
|
||||
|
||||
interface UserApiSourceInfo {
|
||||
name: string
|
||||
type: UserApiSourceInfoType
|
||||
actions: UserApiSourceInfoActions[]
|
||||
qualitys: LX.Quality[]
|
||||
}
|
||||
|
||||
type UserApiSources = Record<LX.Source, UserApiSourceInfo>
|
||||
|
||||
|
||||
interface UserApiInfo {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
script: string
|
||||
allowShowUpdateAlert: boolean
|
||||
sources?: UserApiSources
|
||||
}
|
||||
|
||||
interface UserApiStatus {
|
||||
status: boolean
|
||||
message?: string
|
||||
apiInfo?: UserApiInfo
|
||||
}
|
||||
|
||||
interface UserApiUpdateInfo {
|
||||
name: string
|
||||
description: string
|
||||
log: string
|
||||
updateUrl?: string
|
||||
}
|
||||
|
||||
interface UserApiRequestParams {
|
||||
requestKey: string
|
||||
data: any
|
||||
}
|
||||
type UserApiRequestCancelParams = string
|
||||
type UserApiSetApiParams = string
|
||||
|
||||
interface UserApiSetAllowUpdateAlertParams {
|
||||
id: string
|
||||
enable: boolean
|
||||
}
|
||||
|
||||
interface ImportUserApi {
|
||||
apiInfo: UserApiInfo
|
||||
apiList: UserApiInfo[]
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
7
src/common/types/utils.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
type MakeOptional<Type, Key extends keyof Type> = Omit<Type, Key> & Partial<Pick<Type, Key>>
|
||||
|
||||
type DeepPartial<T> = {
|
||||
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
|
||||
}
|
||||
|
||||
type Modify<T, R> = Omit<T, keyof R> & R
|
||||
@@ -1,217 +0,0 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
227
src/common/utils/common.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
// 非业务工具方法
|
||||
|
||||
/**
|
||||
* 获取两个数之间的随机整数,大于等于min,小于max
|
||||
* @param {*} min
|
||||
* @param {*} max
|
||||
*/
|
||||
export const getRandom = (min: number, max: number): number => Math.floor(Math.random() * (max - min)) + min
|
||||
|
||||
|
||||
export const sizeFormate = (size: number): string => {
|
||||
// https://gist.github.com/thomseddon/3511330
|
||||
if (!size) return '0 B'
|
||||
let units = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||
let number = Math.floor(Math.log(size) / Math.log(1024))
|
||||
return `${(size / Math.pow(1024, Math.floor(number))).toFixed(2)} ${units[number]}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字符串、时间戳等格式转成时间对象
|
||||
* @param date 时间
|
||||
* @returns 时间对象或空字符串
|
||||
*/
|
||||
export const toDateObj = (date: any): Date | '' => {
|
||||
// console.log(date)
|
||||
if (!date) return ''
|
||||
switch (typeof date) {
|
||||
case 'string':
|
||||
if (!date.includes('T')) date = date.split('.')[0].replace(/-/g, '/')
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
case 'number':
|
||||
date = new Date(date)
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
case 'object':
|
||||
break
|
||||
default: return ''
|
||||
}
|
||||
return date
|
||||
}
|
||||
|
||||
const numFix = (n: number): string => n < 10 ? (`0${n}`) : n.toString()
|
||||
/**
|
||||
* 时间格式化
|
||||
* @param _date 时间
|
||||
* @param format Y-M-D h:m:s Y年 M月 D日 h时 m分 s秒
|
||||
*/
|
||||
export const dateFormat = (_date: any, format = 'Y-M-D h:m:s') => {
|
||||
// console.log(date)
|
||||
const date = toDateObj(_date)
|
||||
if (!date) return ''
|
||||
return format
|
||||
.replace('Y', date.getFullYear().toString())
|
||||
.replace('M', numFix(date.getMonth() + 1))
|
||||
.replace('D', numFix(date.getDate()))
|
||||
.replace('h', numFix(date.getHours()))
|
||||
.replace('m', numFix(date.getMinutes()))
|
||||
.replace('s', numFix(date.getSeconds()))
|
||||
}
|
||||
|
||||
|
||||
export const formatPlayTime = (time: number) => {
|
||||
let m = Math.trunc(time / 60)
|
||||
let s = Math.trunc(time % 60)
|
||||
return m == 0 && s == 0 ? '--/--' : numFix(m) + ':' + numFix(s)
|
||||
}
|
||||
|
||||
export const formatPlayTime2 = (time: number) => {
|
||||
let m = Math.trunc(time / 60)
|
||||
let s = Math.trunc(time % 60)
|
||||
return numFix(m) + ':' + numFix(s)
|
||||
}
|
||||
|
||||
|
||||
const encodeNames = {
|
||||
' ': ' ',
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
''': "'",
|
||||
''': "'",
|
||||
} as const
|
||||
export const decodeName = (str: string | null = '') => {
|
||||
return str?.replace(/(?:&|<|>|"|'|'| )/gm, (s: string) => encodeNames[s as keyof typeof encodeNames]) ?? ''
|
||||
}
|
||||
|
||||
export const isUrl = (path: string) => /https?:\/\//.test(path)
|
||||
|
||||
// 解析URL参数为对象
|
||||
export const parseUrlParams = (str: string): Record<string, string> => {
|
||||
const params: Record<string, string> = {}
|
||||
if (typeof str !== 'string') return params
|
||||
const paramsArr = str.split('&')
|
||||
for (const param of paramsArr) {
|
||||
let [key, value] = param.split('=')
|
||||
params[key] = value
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成节流函数
|
||||
* @param fn 回调
|
||||
* @param delay 延迟
|
||||
* @returns
|
||||
*/
|
||||
export function throttle<Args extends any[]>(fn: (...args: Args) => void | Promise<void>, delay = 100) {
|
||||
let timer: NodeJS.Timeout | null = null
|
||||
let _args: Args
|
||||
return (...args: Args) => {
|
||||
_args = args
|
||||
if (timer) return
|
||||
timer = setTimeout(() => {
|
||||
timer = null
|
||||
void fn(..._args)
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成防抖函数
|
||||
* @param fn 回调
|
||||
* @param delay 延迟
|
||||
* @returns
|
||||
*/
|
||||
export function debounce<Args extends any[]>(fn: (...args: Args) => void | Promise<void>, delay = 100) {
|
||||
let timer: NodeJS.Timeout | null = null
|
||||
let _args: Args
|
||||
return (...args: Args) => {
|
||||
_args = args
|
||||
if (timer) clearTimeout(timer)
|
||||
timer = setTimeout(() => {
|
||||
timer = null
|
||||
void fn(..._args)
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
|
||||
const fileNameRxp = /[\\/:*?#"<>|]/g
|
||||
export const filterFileName = (name: string): string => name.replace(fileNameRxp, '')
|
||||
|
||||
|
||||
// https://blog.csdn.net/xcxy2015/article/details/77164126#comments
|
||||
/**
|
||||
*
|
||||
* @param a
|
||||
* @param b
|
||||
*/
|
||||
export const similar = (a: string, b: string) => {
|
||||
if (!a || !b) return 0
|
||||
if (a.length > b.length) { // 保证 a <= b
|
||||
let t = b
|
||||
b = a
|
||||
a = t
|
||||
}
|
||||
let al = a.length
|
||||
let bl = b.length
|
||||
let mp = [] // 一个表
|
||||
let i, j, ai, lt, tmp // ai:字符串a的第i个字符。 lt:左上角的值。 tmp:暂存新的值。
|
||||
for (i = 0; i <= bl; i++) mp[i] = i
|
||||
for (i = 1; i <= al; i++) {
|
||||
ai = a.charAt(i - 1)
|
||||
lt = mp[0]
|
||||
mp[0] = mp[0] + 1
|
||||
for (j = 1; j <= bl; j++) {
|
||||
tmp = Math.min(mp[j] + 1, mp[j - 1] + 1, lt + (ai == b.charAt(j - 1) ? 0 : 1))
|
||||
lt = mp[j]
|
||||
mp[j] = tmp
|
||||
}
|
||||
}
|
||||
return 1 - (mp[bl] / bl)
|
||||
}
|
||||
|
||||
/**
|
||||
* 排序字符串
|
||||
* @param arr
|
||||
* @param data
|
||||
*/
|
||||
export const sortInsert = (arr: Array<{ num: number, data: any }>, data: { num: number, data: any }) => {
|
||||
let key = data.num
|
||||
let left = 0
|
||||
let right = arr.length - 1
|
||||
|
||||
while (left <= right) {
|
||||
let middle = Math.trunc((left + right) / 2)
|
||||
if (key == arr[middle].num) {
|
||||
left = middle
|
||||
break
|
||||
} else if (key < arr[middle].num) {
|
||||
right = middle - 1
|
||||
} else {
|
||||
left = middle + 1
|
||||
}
|
||||
}
|
||||
while (left > 0) {
|
||||
if (arr[left - 1].num != key) break
|
||||
left--
|
||||
}
|
||||
|
||||
arr.splice(left, 0, data)
|
||||
}
|
||||
|
||||
export const encodePath = (path: string) => {
|
||||
return encodeURI(path.replaceAll('\\', '/'))
|
||||
}
|
||||
|
||||
|
||||
export const arrPush = <T>(list: T[], newList: T[]) => {
|
||||
for (let i = 0; i * 1000 < newList.length; i++) {
|
||||
list.push(...newList.slice(i * 1000, (i + 1) * 1000))
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
export const arrUnshift = <T>(list: T[], newList: T[]) => {
|
||||
for (let i = 0; i * 1000 < newList.length; i++) {
|
||||
list.splice(i * 1000, 0, ...newList.slice(i * 1000, (i + 1) * 1000))
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
export const arrPushByPosition = <T>(list: T[], newList: T[], position: number) => {
|
||||
for (let i = 0; i * 1000 < newList.length; i++) {
|
||||
list.splice(position + i * 1000, 0, ...newList.slice(i * 1000, (i + 1) * 1000))
|
||||
}
|
||||
return list
|
||||
}
|
||||
@@ -1,70 +1,74 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import request from 'request'
|
||||
import { EventEmitter } from 'events'
|
||||
import { performance } from 'perf_hooks'
|
||||
import { STATUS } from './util'
|
||||
import http from 'http'
|
||||
import { request, Options as RequestOptions } from './request'
|
||||
|
||||
export interface Options {
|
||||
forceResume: boolean
|
||||
requestOptions: RequestOptions
|
||||
}
|
||||
|
||||
const defaultChunkInfo = {
|
||||
path: null,
|
||||
startByte: 0,
|
||||
path: '',
|
||||
startByte: '0',
|
||||
endByte: '',
|
||||
}
|
||||
|
||||
const defaultRequestOptions = {
|
||||
method: 'GET',
|
||||
const defaultRequestOptions: Options['requestOptions'] = {
|
||||
method: 'get',
|
||||
headers: {},
|
||||
}
|
||||
const defaultOptions = {
|
||||
|
||||
const defaultOptions: Options = {
|
||||
forceResume: true,
|
||||
requestOptions: { ...defaultRequestOptions },
|
||||
}
|
||||
|
||||
class Task extends EventEmitter {
|
||||
/**
|
||||
*
|
||||
* @param {String} url download url
|
||||
* @param {Object} chunkInfo
|
||||
* @param {Object} options
|
||||
*/
|
||||
constructor(url, savePath, filename, options = {}) {
|
||||
resumeLastChunk: Buffer | null
|
||||
downloadUrl: string
|
||||
chunkInfo: { path: string, startByte: string, endByte: string }
|
||||
status: typeof STATUS[keyof typeof STATUS]
|
||||
options: Options
|
||||
requestOptions: Options['requestOptions']
|
||||
ws: fs.WriteStream | null = null
|
||||
progress = { total: 0, downloaded: 0, speed: 0, progress: 0 }
|
||||
statsEstimate = { time: 0, bytes: 0, prevBytes: 0 }
|
||||
requestInstance: http.ClientRequest | null = null
|
||||
|
||||
|
||||
constructor(url: string, savePath: string, filename: string, options: Partial<Options> = {}) {
|
||||
super()
|
||||
|
||||
this.resumeLastChunk = null
|
||||
this.downloadUrl = url
|
||||
this.chunkInfo = Object.assign({}, defaultChunkInfo, {
|
||||
path: path.join(savePath, filename),
|
||||
startByte: 0,
|
||||
startByte: '0',
|
||||
})
|
||||
if (!this.chunkInfo.endByte) this.chunkInfo.endByte = ''
|
||||
// if (!this.chunkInfo.endByte) this.chunkInfo.endByte = ''
|
||||
|
||||
this.options = Object.assign({}, defaultOptions, options)
|
||||
|
||||
this.requestOptions = Object.assign({}, defaultRequestOptions, this.options.requestOptions || {})
|
||||
if (!this.requestOptions.headers) this.requestOptions.headers = {}
|
||||
this.requestOptions.headers = this.requestOptions.headers ? { ...this.requestOptions.headers } : {}
|
||||
|
||||
this.progress = {
|
||||
total: 0,
|
||||
downloaded: 0,
|
||||
speed: 0,
|
||||
}
|
||||
this.statsEstimate = {
|
||||
time: 0,
|
||||
bytes: 0,
|
||||
prevBytes: 0,
|
||||
}
|
||||
this.status = STATUS.idle
|
||||
}
|
||||
|
||||
__init() {
|
||||
this.status = STATUS.init
|
||||
async __init() {
|
||||
const { path, startByte, endByte } = this.chunkInfo
|
||||
this.progress.downloaded = 0
|
||||
if (startByte != null) this.requestOptions.headers.range = `bytes=${startByte}-${endByte}`
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!path) return resolve()
|
||||
this.progress.progress = 0
|
||||
this.progress.speed = 0
|
||||
if (startByte) this.requestOptions.headers!.range = `bytes=${startByte}-${endByte}`
|
||||
|
||||
if (!path) return
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fs.stat(path, (errStat, stats) => {
|
||||
if (errStat) {
|
||||
// console.log(errStat.code)
|
||||
if (errStat.code !== 'ENOENT') {
|
||||
this.__handleError(errStat)
|
||||
reject(errStat)
|
||||
@@ -94,7 +98,7 @@ class Task extends EventEmitter {
|
||||
// console.log(buffer)
|
||||
this.resumeLastChunk = buffer
|
||||
this.progress.downloaded = stats.size
|
||||
this.requestOptions.headers.range = `bytes=${stats.size - 10}-${endByte || ''}`
|
||||
this.requestOptions.headers!.range = `bytes=${stats.size - 10}-${endByte || ''}`
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
@@ -106,15 +110,15 @@ class Task extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
__httpFetch(url, options) {
|
||||
__httpFetch(url: string, options: Options['requestOptions']) {
|
||||
// console.log(options)
|
||||
this.request = request(url, options)
|
||||
this.requestInstance = request(url, options)
|
||||
.on('response', response => {
|
||||
if (response.statusCode !== 200 && response.statusCode !== 206) {
|
||||
if (response.statusCode == 416) {
|
||||
fs.unlink(this.chunkInfo.path, async err => {
|
||||
await this.__handleError(new Error(response.statusMessage))
|
||||
this.chunkInfo.startByte = 0
|
||||
fs.unlink(this.chunkInfo.path, (err) => {
|
||||
this.__handleError(new Error(response.statusMessage))
|
||||
this.chunkInfo.startByte = '0'
|
||||
this.resumeLastChunk = null
|
||||
this.progress.downloaded = 0
|
||||
if (err) this.__handleError(err)
|
||||
@@ -124,13 +128,13 @@ class Task extends EventEmitter {
|
||||
this.status = STATUS.failed
|
||||
this.emit('fail', response)
|
||||
this.__closeRequest()
|
||||
this.__closeWriteStream()
|
||||
void this.__closeWriteStream()
|
||||
return
|
||||
}
|
||||
this.emit('response', response)
|
||||
try {
|
||||
this.__initDownload(response)
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
return this.__handleError(error)
|
||||
}
|
||||
this.status = STATUS.running
|
||||
@@ -142,34 +146,44 @@ class Task extends EventEmitter {
|
||||
this.__handleComplete()
|
||||
} else {
|
||||
// this.__handleError(new Error('The connection was terminated while the message was still being sent'))
|
||||
this.stop()
|
||||
void this.stop()
|
||||
}
|
||||
})
|
||||
})
|
||||
.on('error', err => this.__handleError(err))
|
||||
.on('close', () => this.__closeWriteStream())
|
||||
.on('close', () => {
|
||||
void this.__closeWriteStream()
|
||||
})
|
||||
.end()
|
||||
}
|
||||
|
||||
__initDownload(response) {
|
||||
this.progress.total = parseInt(response.headers['content-length'] || 0)
|
||||
let options = {}
|
||||
let isResumable = this.options.forceResume || response.headers['accept-ranges'] !== 'none'
|
||||
__initDownload(response: http.IncomingMessage) {
|
||||
this.progress.total = response.headers['content-length'] ? parseInt(response.headers['content-length']) : 0
|
||||
if (!this.progress.total) return this.__handleError(new Error('Content length is 0'))
|
||||
let options: any = {}
|
||||
let isResumable = this.options.forceResume ||
|
||||
response.headers['accept-ranges'] !== 'none' ||
|
||||
(typeof response.headers['accept-ranges'] == 'string' &&
|
||||
parseInt(response.headers['accept-ranges'].replace(/^bytes=(\d+)/, '$1')) > 0)
|
||||
|
||||
if (isResumable) {
|
||||
options.flags = 'a'
|
||||
if (this.progress.downloaded) this.progress.total -= 10
|
||||
} else {
|
||||
if (this.chunkInfo.startByte > 0) return this.__handleError(new Error('The resource cannot be resumed download.'))
|
||||
if (this.chunkInfo.startByte != '0') return this.__handleError(new Error('The resource cannot be resumed download.'))
|
||||
}
|
||||
this.progress.total += this.progress.downloaded
|
||||
this.statsEstimate.prevBytes = this.progress.downloaded
|
||||
if (!this.chunkInfo.path) return this.__handleError(new Error('Chunk save Path is not set.'))
|
||||
this.ws = fs.createWriteStream(this.chunkInfo.path, options)
|
||||
|
||||
this.ws.on('finish', () => this.__closeWriteStream())
|
||||
this.ws.on('finish', () => {
|
||||
void this.__closeWriteStream()
|
||||
})
|
||||
this.ws.on('error', err => {
|
||||
fs.unlink(this.chunkInfo.path, async unlinkErr => {
|
||||
await this.__handleError(err)
|
||||
this.chunkInfo.startByte = 0
|
||||
fs.unlink(this.chunkInfo.path, (unlinkErr: any) => {
|
||||
this.__handleError(err)
|
||||
this.chunkInfo.startByte = '0'
|
||||
this.resumeLastChunk = null
|
||||
this.progress.downloaded = 0
|
||||
if (unlinkErr && unlinkErr.code !== 'ENOENT') this.__handleError(unlinkErr)
|
||||
@@ -179,7 +193,7 @@ class Task extends EventEmitter {
|
||||
|
||||
__handleComplete() {
|
||||
if (this.status == STATUS.error) return
|
||||
this.__closeWriteStream().then(() => {
|
||||
void this.__closeWriteStream().then(() => {
|
||||
if (this.progress.downloaded == this.progress.total) {
|
||||
this.status = STATUS.completed
|
||||
this.emit('completed')
|
||||
@@ -188,21 +202,22 @@ class Task extends EventEmitter {
|
||||
this.emit('stop')
|
||||
}
|
||||
})
|
||||
console.log('end')
|
||||
// console.log('end')
|
||||
}
|
||||
|
||||
__handleError(error) {
|
||||
__handleError(error: Error) {
|
||||
if (this.status == STATUS.error) return
|
||||
this.status = STATUS.error
|
||||
this.__closeRequest()
|
||||
this.__closeWriteStream()
|
||||
void this.__closeWriteStream()
|
||||
if (error.message == 'aborted') return
|
||||
this.emit('error', error)
|
||||
}
|
||||
|
||||
__closeWriteStream() {
|
||||
return new Promise((resolve, reject) => {
|
||||
async __closeWriteStream() {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (!this.ws) return resolve()
|
||||
console.log('close write stream')
|
||||
// console.log('close write stream')
|
||||
this.ws.close(err => {
|
||||
if (err) {
|
||||
this.status = STATUS.error
|
||||
@@ -217,18 +232,29 @@ class Task extends EventEmitter {
|
||||
}
|
||||
|
||||
__closeRequest() {
|
||||
if (!this.request) return
|
||||
console.log('close request')
|
||||
this.request.abort()
|
||||
this.request = null
|
||||
if (!this.requestInstance || this.requestInstance.destroyed) return
|
||||
// console.log('close request')
|
||||
this.requestInstance.destroy()
|
||||
this.requestInstance = null
|
||||
}
|
||||
|
||||
__handleWriteData(chunk) {
|
||||
__handleWriteData(chunk: Buffer) {
|
||||
if (this.resumeLastChunk) {
|
||||
chunk = this.__handleDiffChunk(chunk)
|
||||
if (!chunk) {
|
||||
this.__handleError(new Error('Resume failed, response chunk does not match.'))
|
||||
this.stop()
|
||||
const result = this.__handleDiffChunk(chunk)
|
||||
if (result) chunk = result
|
||||
else {
|
||||
this.__handleStop().finally(() => {
|
||||
// this.__handleError(new Error('Resume failed, response chunk does not match.'))
|
||||
// Resume failed, response chunk does not match, remove file and restart download
|
||||
console.log('Resume failed, response chunk does not match.')
|
||||
fs.unlink(this.chunkInfo.path, (unlinkErr: any) => {
|
||||
// this.__handleError(err)
|
||||
this.chunkInfo.startByte = '0'
|
||||
this.resumeLastChunk = null
|
||||
if (unlinkErr && unlinkErr.code !== 'ENOENT') return this.__handleError(unlinkErr)
|
||||
void this.start()
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -239,35 +265,32 @@ class Task extends EventEmitter {
|
||||
if (!err) return
|
||||
console.log(err)
|
||||
this.__handleError(err)
|
||||
this.stop()
|
||||
void this.stop()
|
||||
})
|
||||
}
|
||||
|
||||
__handleDiffChunk(chunk) {
|
||||
__handleDiffChunk(chunk: Buffer): Buffer | null {
|
||||
// console.log('diff', chunk)
|
||||
let resumeLastChunkLen = this.resumeLastChunk.length
|
||||
let resumeLastChunkLen = this.resumeLastChunk!.length
|
||||
let chunkLen = chunk.length
|
||||
let isOk
|
||||
if (chunkLen >= resumeLastChunkLen) {
|
||||
isOk = chunk.slice(0, resumeLastChunkLen).toString('hex') === this.resumeLastChunk.toString('hex')
|
||||
isOk = chunk.slice(0, resumeLastChunkLen).toString('hex') === this.resumeLastChunk!.toString('hex')
|
||||
if (!isOk) return null
|
||||
|
||||
this.resumeLastChunk = null
|
||||
return chunk.slice(resumeLastChunkLen)
|
||||
} else {
|
||||
isOk = chunk.slice(0, chunkLen).toString('hex') === this.resumeLastChunk.slice(0, chunkLen).toString('hex')
|
||||
isOk = chunk.slice(0, chunkLen).toString('hex') === this.resumeLastChunk!.slice(0, chunkLen).toString('hex')
|
||||
if (!isOk) return null
|
||||
this.resumeLastChunk = this.resumeLastChunk.slice(chunkLen)
|
||||
this.resumeLastChunk = this.resumeLastChunk!.slice(chunkLen)
|
||||
return chunk.slice(chunkLen)
|
||||
}
|
||||
}
|
||||
|
||||
__handleStop() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.request) {
|
||||
this.request.abort()
|
||||
this.request = null
|
||||
}
|
||||
async __handleStop() {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.__closeRequest()
|
||||
if (this.ws) {
|
||||
this.ws.close(err => {
|
||||
if (err) {
|
||||
@@ -284,7 +307,7 @@ class Task extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
__calculateProgress(receivedBytes) {
|
||||
__calculateProgress(receivedBytes: number) {
|
||||
const currentTime = performance.now()
|
||||
const elaspsedTime = currentTime - this.statsEstimate.time
|
||||
|
||||
@@ -308,24 +331,26 @@ class Task extends EventEmitter {
|
||||
}
|
||||
|
||||
async start() {
|
||||
this.status = STATUS.running
|
||||
this.status = STATUS.init
|
||||
await this.__init()
|
||||
if (this.status !== STATUS.init) return
|
||||
this.status = STATUS.running
|
||||
this.__httpFetch(this.downloadUrl, this.requestOptions)
|
||||
this.emit('start')
|
||||
}
|
||||
|
||||
async stop() {
|
||||
if (this.status === STATUS.stopped) return
|
||||
if (this.status == STATUS.stopped || this.status == STATUS.completed) return
|
||||
this.status = STATUS.stopped
|
||||
await this.__handleStop()
|
||||
this.emit('stop')
|
||||
}
|
||||
|
||||
refreshUrl(url) {
|
||||
refreshUrl(url: string) {
|
||||
this.downloadUrl = url
|
||||
}
|
||||
|
||||
updateSaveInfo(filePath, fileName) {
|
||||
updateSaveInfo(filePath: string, fileName: string) {
|
||||
this.chunkInfo.path = path.join(filePath, fileName)
|
||||
}
|
||||
}
|
||||
100
src/common/utils/download/index.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import Downloader, { Options as DownloaderOptions } from './Downloader'
|
||||
import { getRequestAgent } from './util'
|
||||
import { sizeFormate } from '@common/utils'
|
||||
import http from 'http'
|
||||
|
||||
// these are the default options
|
||||
// const options = {
|
||||
// method: 'GET', // Request Method Verb
|
||||
// // Custom HTTP Header ex: Authorization, User-Agent
|
||||
// headers: {},
|
||||
// fileName: '', // Custom filename when saved
|
||||
// override: false, // if true it will override the file, otherwise will append '(number)' to the end of file
|
||||
// forceResume: false, // If the server does not return the "accept-ranges" header, can be force if it does support it
|
||||
// // httpRequestOptions: {}, // Override the http request options
|
||||
// // httpsRequestOptions: {}, // Override the https request options, ex: to add SSL Certs
|
||||
// }
|
||||
|
||||
export interface Options {
|
||||
url: string
|
||||
path: string
|
||||
fileName: string
|
||||
method?: DownloaderOptions['requestOptions']['method']
|
||||
headers?: DownloaderOptions['requestOptions']['headers']
|
||||
forceResume?: boolean
|
||||
proxy?: { host: string, port: number }
|
||||
onCompleted?: () => void
|
||||
onError?: (error: Error) => void
|
||||
onFail?: (response: http.IncomingMessage) => void
|
||||
onStart?: () => void
|
||||
onStop?: () => void
|
||||
onProgress?: (progress: LX.Download.ProgressInfo) => void
|
||||
}
|
||||
const noop = () => {}
|
||||
export const createDownload = ({
|
||||
url,
|
||||
path,
|
||||
fileName,
|
||||
method = 'get',
|
||||
forceResume,
|
||||
proxy,
|
||||
// resumeTime = 5000,
|
||||
onCompleted = noop,
|
||||
onError = noop,
|
||||
onFail = noop,
|
||||
onStart = noop,
|
||||
onStop = noop,
|
||||
onProgress = noop,
|
||||
}: Options) => {
|
||||
const dl = new Downloader(url, path, fileName, {
|
||||
requestOptions: {
|
||||
method,
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
|
||||
},
|
||||
agent: getRequestAgent(url, proxy),
|
||||
timeout: 60 * 1000,
|
||||
},
|
||||
|
||||
forceResume,
|
||||
})
|
||||
|
||||
dl.on('completed', () => {
|
||||
onCompleted()
|
||||
}).on('error', (err: any) => {
|
||||
if (err.message === 'socket hang up') return
|
||||
onError(err)
|
||||
}).on('start', () => {
|
||||
onStart()
|
||||
// pauseResumeTimer(dl, resumeTime)
|
||||
}).on('progress', (stats) => {
|
||||
const speed = sizeFormate(stats.speed)
|
||||
onProgress({
|
||||
progress: parseInt(stats.progress.toFixed(2)),
|
||||
speed,
|
||||
downloaded: stats.downloaded,
|
||||
total: stats.total,
|
||||
})
|
||||
// if (debugDownload) {
|
||||
// const downloaded = sizeFormate(stats.downloaded)
|
||||
// const total = sizeFormate(stats.total)
|
||||
// console.log(`${speed}/s - ${progress}% [${downloaded}/${total}]`)
|
||||
// }
|
||||
}).on('stop', () => {
|
||||
onStop()
|
||||
// debugDownload && console.log('paused')
|
||||
}).on('fail', resp => {
|
||||
onFail(resp)
|
||||
// debugDownload && console.log('fail')
|
||||
})
|
||||
|
||||
// debugDownload && console.log('Downloading: ', url)
|
||||
|
||||
dl.start().catch(err => {
|
||||
onError(err)
|
||||
})
|
||||
|
||||
return dl
|
||||
}
|
||||
|
||||
export type DownloaderType = Downloader
|
||||
73
src/common/utils/download/request.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { URL } from 'url'
|
||||
import http from 'http'
|
||||
import https from 'https'
|
||||
|
||||
export interface Options {
|
||||
method: 'get' | 'head' | 'delete' | 'patch' | 'post' | 'put'
|
||||
params?: Record<string, string>
|
||||
// body?: Record<string, string>
|
||||
headers?: Record<string, string>
|
||||
timeout?: number
|
||||
agent?: http.Agent
|
||||
}
|
||||
|
||||
const defaultOptions: Options = {
|
||||
method: 'get',
|
||||
}
|
||||
|
||||
type HttpCallback = (res: http.IncomingMessage) => void
|
||||
|
||||
const sendRequest = (url: string, options: Options, callback?: HttpCallback) => {
|
||||
const urlParse = new URL(url)
|
||||
const httpOptions: http.RequestOptions | https.RequestOptions = {
|
||||
host: urlParse.hostname,
|
||||
port: urlParse.port,
|
||||
path: urlParse.pathname + urlParse.search,
|
||||
method: options.method,
|
||||
}
|
||||
|
||||
if (options.params) {
|
||||
(httpOptions.path as string) += `${urlParse.search ? '&' : '?'}${Object.entries(options.params)
|
||||
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
|
||||
.join('&')}`
|
||||
}
|
||||
|
||||
if (options.headers) httpOptions.headers = { ...options.headers }
|
||||
|
||||
if (options.agent) httpOptions.agent = options.agent
|
||||
|
||||
return urlParse.protocol == 'https:'
|
||||
? https.request(httpOptions, callback)
|
||||
: http.request(httpOptions, callback)
|
||||
}
|
||||
|
||||
const applyTimeout = (request: http.ClientRequest, time: number) => {
|
||||
let timeout: NodeJS.Timeout | null = setTimeout(() => {
|
||||
timeout = null
|
||||
if (request.destroyed) return
|
||||
request.destroy(new Error('Request timeout'))
|
||||
}, time)
|
||||
request.on('response', () => {
|
||||
if (!timeout) return
|
||||
clearTimeout(timeout)
|
||||
timeout = null
|
||||
})
|
||||
}
|
||||
|
||||
// const isRequireRedirect = (response: http.IncomingMessage) => {
|
||||
// return response.statusCode &&
|
||||
// response.statusCode > 300 &&
|
||||
// response.statusCode < 400 &&
|
||||
// Object.hasOwn(response.headers, 'location') &&
|
||||
// response.headers.location
|
||||
// }
|
||||
|
||||
// export function request(url: string, callback: HttpCallback)
|
||||
// export function request(url: string, options: Partial<Options>, callback: HttpCallback)
|
||||
export function request(url: string, _options: Partial<Options>, callback?: HttpCallback) {
|
||||
let options: Options = { ...defaultOptions, ..._options }
|
||||
const request = sendRequest(url, options, callback)
|
||||
if (options.timeout) applyTimeout(request, options.timeout)
|
||||
return request
|
||||
}
|
||||
|
||||
26
src/common/utils/download/util.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { httpOverHttp, httpsOverHttp } from 'tunnel'
|
||||
|
||||
export const STATUS = {
|
||||
idle: 'IDLE',
|
||||
init: 'INIT',
|
||||
running: 'RUNNING',
|
||||
paused: 'PAUSED',
|
||||
stopped: 'STOPPED',
|
||||
completed: 'COMPLETED',
|
||||
error: 'ERROR',
|
||||
failed: 'FAILED',
|
||||
} as const
|
||||
|
||||
const httpsRxp = /^https:/
|
||||
export const getRequestAgent = (url: string, proxy?: { host: string, port: number }) => {
|
||||
let options
|
||||
if (proxy) {
|
||||
options = {
|
||||
proxy: {
|
||||
host: proxy.host,
|
||||
port: proxy.port,
|
||||
},
|
||||
}
|
||||
}
|
||||
return options ? (httpsRxp.test(url) ? httpsOverHttp : httpOverHttp)(options) : undefined
|
||||
}
|
||||
183
src/common/utils/effects/cursor-effects/bubbleCursor.js
Normal file
@@ -0,0 +1,183 @@
|
||||
// https://github.com/tholman/cursor-effects/blob/master/src/bubbleCursor.js
|
||||
|
||||
class Particle {
|
||||
constructor(x, y, fillStyle, strokeStyle) {
|
||||
const lifeSpan = Math.floor(Math.random() * 60 + 60)
|
||||
this.initialLifeSpan = lifeSpan //
|
||||
this.lifeSpan = lifeSpan // ms
|
||||
this.velocity = {
|
||||
x: (Math.random() < 0.5 ? -1 : 1) * (Math.random() / 10),
|
||||
y: -0.4 + Math.random() * -1,
|
||||
}
|
||||
this.position = { x, y }
|
||||
this.fillStyle = fillStyle
|
||||
this.strokeStyle = strokeStyle
|
||||
|
||||
this.baseDimension = 4
|
||||
}
|
||||
|
||||
update(context, width) {
|
||||
this.position.x += this.velocity.x
|
||||
this.position.y += this.velocity.y
|
||||
|
||||
this.velocity.x += ((Math.random() < 0.5 ? -1 : 1) * 2) / 75
|
||||
this.velocity.y -= Math.random() / 600
|
||||
if (this.position.x >= width - 2 || this.position.x <= 2) this.lifeSpan = 0
|
||||
else if (this.position.y <= 5) this.lifeSpan = 0
|
||||
this.lifeSpan--
|
||||
|
||||
const scale =
|
||||
0.2 + (this.initialLifeSpan - this.lifeSpan) / this.initialLifeSpan
|
||||
|
||||
context.fillStyle = this.fillStyle
|
||||
context.strokeStyle = this.strokeStyle
|
||||
context.beginPath()
|
||||
context.arc(
|
||||
this.position.x - (this.baseDimension / 2) * scale,
|
||||
this.position.y - this.baseDimension / 2,
|
||||
this.baseDimension * scale,
|
||||
0,
|
||||
2 * Math.PI,
|
||||
)
|
||||
|
||||
context.stroke()
|
||||
context.fill()
|
||||
|
||||
context.closePath()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default class BubbleCursor {
|
||||
constructor({ element, fillStyle = 'rgba(77, 175, 124, 0.1)', strokeStyle = 'rgba(77, 175, 124, 0.3)' } = {}) {
|
||||
this.hasWrapperEl = element
|
||||
this.element = this.hasWrapperEl || document.body
|
||||
|
||||
this.width = window.innerWidth
|
||||
this.height = window.innerHeight
|
||||
this.cursor = { x: this.width / 2, y: this.width / 2 }
|
||||
this.particles = []
|
||||
this.canvas = null
|
||||
this.context = null
|
||||
this.fillStyle = fillStyle
|
||||
this.strokeStyle = strokeStyle
|
||||
|
||||
this.init()
|
||||
}
|
||||
|
||||
init() {
|
||||
this.canvas = document.createElement('canvas')
|
||||
this.context = this.canvas.getContext('2d')
|
||||
|
||||
this.canvas.style.top = '0px'
|
||||
this.canvas.style.left = '0px'
|
||||
this.canvas.style.pointerEvents = 'none'
|
||||
this.canvas.style.zIndex = 100
|
||||
|
||||
if (this.hasWrapperEl) {
|
||||
this.canvas.style.position = 'absolute'
|
||||
this.element.appendChild(this.canvas)
|
||||
this.canvas.width = this.element.clientWidth
|
||||
this.canvas.height = this.element.clientHeight
|
||||
} else {
|
||||
this.canvas.style.position = 'fixed'
|
||||
document.body.appendChild(this.canvas)
|
||||
this.canvas.width = this.width
|
||||
this.canvas.height = this.height
|
||||
}
|
||||
|
||||
this.bindEvents()
|
||||
this.loop()
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
this.element.addEventListener('mousemove', this.onMouseMove)
|
||||
this.element.addEventListener('touchmove', this.onTouchMove, { passive: true })
|
||||
this.element.addEventListener('touchstart', this.onTouchMove, { passive: true })
|
||||
window.addEventListener('resize', this.onWindowResize)
|
||||
}
|
||||
|
||||
|
||||
onWindowResize = (e) => {
|
||||
this.width = window.innerWidth
|
||||
this.height = window.innerHeight
|
||||
|
||||
if (this.hasWrapperEl) {
|
||||
this.canvas.width = this.element.clientWidth
|
||||
this.canvas.height = this.element.clientHeight
|
||||
} else {
|
||||
this.canvas.width = this.width
|
||||
this.canvas.height = this.height
|
||||
}
|
||||
}
|
||||
|
||||
onTouchMove = (e) => {
|
||||
if (e.touches.length > 0) {
|
||||
for (let i = 0; i < e.touches.length; i++) {
|
||||
this.addParticle(
|
||||
e.touches[i].clientX,
|
||||
e.touches[i].clientY,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMouseMove = (e) => {
|
||||
if (this.hasWrapperEl) {
|
||||
const boundingRect = this.element.getBoundingClientRect()
|
||||
this.cursor.x = e.clientX - boundingRect.left
|
||||
this.cursor.y = e.clientY - boundingRect.top
|
||||
} else {
|
||||
this.cursor.x = e.clientX
|
||||
this.cursor.y = e.clientY
|
||||
}
|
||||
|
||||
this.addParticle(this.cursor.x, this.cursor.y)
|
||||
}
|
||||
|
||||
addParticle(x, y) {
|
||||
this.particles.push(new Particle(x, y, this.fillStyle, this.strokeStyle))
|
||||
}
|
||||
|
||||
updateParticles() {
|
||||
this.context.clearRect(0, 0, this.width, this.height)
|
||||
|
||||
// Update
|
||||
for (let i = 0; i < this.particles.length; i++) {
|
||||
this.particles[i].update(this.context, this.width)
|
||||
}
|
||||
|
||||
// Remove dead particles
|
||||
for (let i = this.particles.length - 1; i >= 0; i--) {
|
||||
if (this.particles[i].lifeSpan < 0) {
|
||||
this.particles.splice(i, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loop = () => {
|
||||
this.updateParticles()
|
||||
window.requestAnimationFrame(this.loop)
|
||||
}
|
||||
|
||||
setColor(fillStyle, strokeStyle) {
|
||||
this.fillStyle = fillStyle
|
||||
this.strokeStyle = strokeStyle
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.element.removeEventListener('mousemove', this.onMouseMove)
|
||||
this.element.removeEventListener('touchmove', this.onTouchMove, { passive: true })
|
||||
this.element.removeEventListener('touchstart', this.onTouchMove, { passive: true })
|
||||
window.removeEventListener('resize', this.onWindowResize)
|
||||
|
||||
if (this.hasWrapperEl) {
|
||||
this.element.removeChild(this.canvas)
|
||||
} else {
|
||||
document.body.removeChild(this.canvas)
|
||||
}
|
||||
|
||||
this.canvas = null
|
||||
this.context = null
|
||||
}
|
||||
}
|
||||
91
src/common/utils/effects/snow.min.js
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// JavaScript Snow Effect - © Copyright 2020 - Jam-Es.com
|
||||
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
// Change the following variables to customize the appearance of the particles
|
||||
var numParticles = window.innerWidth / 50;
|
||||
var maxSpeed = 2.5;
|
||||
var minSpeed = 1;
|
||||
var maxSize = 5;
|
||||
var minSize = 3;
|
||||
var maxOpacity = 1;
|
||||
var minOpacity = 0.2;
|
||||
|
||||
// If the following is true, the canvas resolution will be set to match the full viewport width and height.
|
||||
// var fixCanvasResolution = true;
|
||||
|
||||
var snow_particles = [];
|
||||
|
||||
var canvas
|
||||
|
||||
window.onload = function() {
|
||||
canvas = document.createElement('canvas')
|
||||
canvas.style.position = 'fixed';
|
||||
canvas.style.top = '0px'
|
||||
canvas.style.left = '0px'
|
||||
canvas.style.pointerEvents = 'none'
|
||||
canvas.style.zIndex = 100
|
||||
canvas.width = document.documentElement.clientWidth
|
||||
canvas.height = document.documentElement.clientHeight
|
||||
document.body.appendChild(canvas)
|
||||
|
||||
// if (fixCanvasResolution) {
|
||||
// // canvas.width = window.innerWidth;
|
||||
// canvas.height = window.innerHeight;
|
||||
// }
|
||||
|
||||
InitPoints();
|
||||
|
||||
requestAnimationFrame(Redraw, 15);
|
||||
window.addEventListener('resize', this.onWindowResize)
|
||||
}
|
||||
function onWindowResize (e) {
|
||||
canvas.width = document.documentElement.clientWidth
|
||||
canvas.height = document.documentElement.clientHeight
|
||||
}
|
||||
|
||||
function Redraw() {
|
||||
var ctx = canvas.getContext("2d");
|
||||
ctx.clearRect(0,0,canvas.width,canvas.height);
|
||||
for (var i = 0; i < snow_particles.length; i++) {
|
||||
var newYPos = snow_particles[i].yPos + snow_particles[i].speed;
|
||||
if (newYPos > window.innerHeight) {
|
||||
newYPos = getRandomInt(-100,-10);
|
||||
snow_particles[i].xPos = getRandomInt(0, window.innerWidth);
|
||||
snow_particles[i].speed = Math.random() * (maxSpeed - minSpeed) + minSpeed;
|
||||
snow_particles[i].opacity = Math.random() * (maxOpacity - minOpacity) + minOpacity;
|
||||
snow_particles[i].size = Math.random() * (maxSize - minSize) + minSize;
|
||||
}
|
||||
snow_particles[i].yPos = newYPos;
|
||||
ctx.beginPath();
|
||||
ctx.arc(snow_particles[i].xPos, newYPos, snow_particles[i].size, 0, 2 * Math.PI);
|
||||
ctx.fillStyle = "rgba(255, 255, 255, " + snow_particles[i].opacity + ")";
|
||||
ctx.fill();
|
||||
}
|
||||
requestAnimationFrame(Redraw)
|
||||
}
|
||||
|
||||
function InitPoints() {
|
||||
for (var i = 0; i < numParticles; i++) {
|
||||
var startX = getRandomInt(0, window.innerWidth);
|
||||
var startY = getRandomInt(0, window.innerHeight);
|
||||
var speed = Math.random() * (maxSpeed - minSpeed) + minSpeed;
|
||||
var opacity = Math.random() * (maxOpacity - minOpacity) + minOpacity;
|
||||
var size = Math.random() * (maxSize - minSize) + minSize;
|
||||
|
||||
snow_particles.push({
|
||||
"xPos": startX,
|
||||
"yPos": startY,
|
||||
"speed": speed,
|
||||
"opacity": opacity,
|
||||
"size": size,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getRandomInt(min, max) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(Math.random() * (max - min)) + min;
|
||||
}
|
||||