mirror of
https://github.com/Eugeny/tabby.git
synced 2025-08-02 15:37:01 +00:00
Compare commits
786 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
467684d9ab | ||
![]() |
5069070040 | ||
![]() |
ecf5297bc3 | ||
![]() |
78bd90ac55 | ||
![]() |
712589eb93 | ||
![]() |
f103e71285 | ||
![]() |
0cf883cc4a | ||
![]() |
2b0ad0d558 | ||
![]() |
67bacb9dd3 | ||
![]() |
d0a597634d | ||
![]() |
322014c409 | ||
![]() |
c751a8725b | ||
![]() |
5417efe558 | ||
![]() |
bf356fcd19 | ||
![]() |
10ee66b9dd | ||
![]() |
763da0d80c | ||
![]() |
8d46bb2181 | ||
![]() |
fe936c7726 | ||
![]() |
2f3e32990a | ||
![]() |
22344f8d54 | ||
![]() |
f6d37a39f4 | ||
![]() |
0e4c60ad4b | ||
![]() |
e8c2171d8f | ||
![]() |
f7a5be2c67 | ||
![]() |
39fa0424a6 | ||
![]() |
bcb1b6a13b | ||
![]() |
a19f35ac44 | ||
![]() |
ea92f1a700 | ||
![]() |
b5701cf9f9 | ||
![]() |
4742530cf3 | ||
![]() |
a8d78ce185 | ||
![]() |
bba1eaccbe | ||
![]() |
cd6d05aa69 | ||
![]() |
412403c72a | ||
![]() |
93ae907dd1 | ||
![]() |
ce24b9cc52 | ||
![]() |
dc68372d76 | ||
![]() |
e0fe125cf2 | ||
![]() |
e594fcd0e7 | ||
![]() |
0219da4d85 | ||
![]() |
5e06b2248b | ||
![]() |
cdc3623986 | ||
![]() |
6d016002c0 | ||
![]() |
f3569f5d2d | ||
![]() |
4125582ef2 | ||
![]() |
c6331c9b1c | ||
![]() |
aaab475e5f | ||
![]() |
e6bf76c616 | ||
![]() |
e36bad2553 | ||
![]() |
154cc29333 | ||
![]() |
1b0402c2cf | ||
![]() |
15073cbc81 | ||
![]() |
3365b143d8 | ||
![]() |
4d9cc91e91 | ||
![]() |
946f4292ef | ||
![]() |
eb12b1ae60 | ||
![]() |
4765c97d31 | ||
![]() |
3fb32e1a97 | ||
![]() |
9ec1a0d253 | ||
![]() |
fef19615bb | ||
![]() |
4d237baf33 | ||
![]() |
03e654b5a0 | ||
![]() |
ef815eaa40 | ||
![]() |
4771a38747 | ||
![]() |
ce016793d4 | ||
![]() |
3a854f04e1 | ||
![]() |
b5658d61d9 | ||
![]() |
02750d8581 | ||
![]() |
077a3e6bba | ||
![]() |
5454be032a | ||
![]() |
8a0b4f82db | ||
![]() |
74fd1aeea5 | ||
![]() |
aac230e362 | ||
![]() |
ae82ed4a47 | ||
![]() |
9d1b0e9861 | ||
![]() |
8cb4e9f27d | ||
![]() |
c8c00a2c9b | ||
![]() |
bacb475896 | ||
![]() |
c8faa67083 | ||
![]() |
b6c0e3cdfb | ||
![]() |
323581d513 | ||
![]() |
bc7a537c4c | ||
![]() |
10ae6ffd99 | ||
![]() |
847628fbff | ||
![]() |
b502c3e84d | ||
![]() |
85e99ff0a8 | ||
![]() |
21b81f476c | ||
![]() |
02de15a6d2 | ||
![]() |
38a6e7fe67 | ||
![]() |
2283a5dad9 | ||
![]() |
e98c12d409 | ||
![]() |
aacc603309 | ||
![]() |
2ef3a81dd8 | ||
![]() |
22ef1bbb87 | ||
![]() |
7ff9f268c9 | ||
![]() |
7866bcd9a4 | ||
![]() |
5a20ac19d9 | ||
![]() |
915f6acf22 | ||
![]() |
59b73fcdc1 | ||
![]() |
8699634492 | ||
![]() |
d625e90464 | ||
![]() |
bc31c775e9 | ||
![]() |
49424e8da5 | ||
![]() |
2526c4e458 | ||
![]() |
24de3ba77c | ||
![]() |
157ed82000 | ||
![]() |
8275a9449a | ||
![]() |
6ed6b90840 | ||
![]() |
d7565e497d | ||
![]() |
de827ef61a | ||
![]() |
7b4e99fc5f | ||
![]() |
035a6f8da8 | ||
![]() |
bcdbab43de | ||
![]() |
a24f52c58a | ||
![]() |
0e745ea607 | ||
![]() |
8fe9232d9b | ||
![]() |
e6004fa980 | ||
![]() |
987b89b914 | ||
![]() |
9fddfa6fc2 | ||
![]() |
6d765bb1b8 | ||
![]() |
0ca971a289 | ||
![]() |
e87f6e7af0 | ||
![]() |
56be0a1085 | ||
![]() |
41f464d21d | ||
![]() |
c8dde73158 | ||
![]() |
0971a85db4 | ||
![]() |
51d54a8477 | ||
![]() |
b75775283f | ||
![]() |
66558290a0 | ||
![]() |
a1980afd9d | ||
![]() |
2b28802ce7 | ||
![]() |
0514a7c229 | ||
![]() |
326901b7e8 | ||
![]() |
bbe6b61d63 | ||
![]() |
204c1057db | ||
![]() |
dff6a2470c | ||
![]() |
cbebc09504 | ||
![]() |
f56dd71f43 | ||
![]() |
17f52a257e | ||
![]() |
8d09ddb686 | ||
![]() |
e6fd31e0b0 | ||
![]() |
c6188a49f5 | ||
![]() |
9a60b4d102 | ||
![]() |
7977c1d644 | ||
![]() |
ac85a1d7d3 | ||
![]() |
86b503093c | ||
![]() |
dd3e7a0f89 | ||
![]() |
8905106b48 | ||
![]() |
225760a9a5 | ||
![]() |
4aa79a76ea | ||
![]() |
1e2d6c7e75 | ||
![]() |
f1e79e9ada | ||
![]() |
37cc37650e | ||
![]() |
d607b7423a | ||
![]() |
a2e843ec84 | ||
![]() |
17cafbfa52 | ||
![]() |
91333abc4f | ||
![]() |
05b2b11af5 | ||
![]() |
3931e8088e | ||
![]() |
299ede2eb1 | ||
![]() |
4fed323a2a | ||
![]() |
b4bf8ec250 | ||
![]() |
780657ce1d | ||
![]() |
2d558563e4 | ||
![]() |
bc5e6e9535 | ||
![]() |
0c15fc2657 | ||
![]() |
5e115c63f1 | ||
![]() |
2bcf23cff1 | ||
![]() |
2c59022b78 | ||
![]() |
358d9f30d2 | ||
![]() |
afd6ce4346 | ||
![]() |
5c7256ffe5 | ||
![]() |
a15e79ad5a | ||
![]() |
f1ecbd1a93 | ||
![]() |
7da941d038 | ||
![]() |
3efc142630 | ||
![]() |
d592469237 | ||
![]() |
b3e63620b3 | ||
![]() |
22b79510ea | ||
![]() |
70cf63f8fa | ||
![]() |
c9067cf8b8 | ||
![]() |
4ccc406768 | ||
![]() |
1c25747de0 | ||
![]() |
8e4c36ec24 | ||
![]() |
444d92d393 | ||
![]() |
2597702676 | ||
![]() |
c9d75d81e4 | ||
![]() |
98eb68c845 | ||
![]() |
5185e1fe1d | ||
![]() |
5bde116a4e | ||
![]() |
179acc1382 | ||
![]() |
c6d918e401 | ||
![]() |
62b53575ac | ||
![]() |
32b29a91e9 | ||
![]() |
4346030459 | ||
![]() |
9e8c0ccb14 | ||
![]() |
6c8d00eb16 | ||
![]() |
b3fcfd0c8b | ||
![]() |
4eefab5655 | ||
![]() |
2745896ec3 | ||
![]() |
cdfaaabb70 | ||
![]() |
60ab6ece62 | ||
![]() |
436318b534 | ||
![]() |
248f431437 | ||
![]() |
7794280115 | ||
![]() |
c2cc4c977f | ||
![]() |
f9b7f97863 | ||
![]() |
59ce7eeee6 | ||
![]() |
0e012a90ea | ||
![]() |
6773d260cf | ||
![]() |
7379f6cd59 | ||
![]() |
3aee24bdbd | ||
![]() |
84dbfa5d6c | ||
![]() |
43958d88b8 | ||
![]() |
f4b0e4cb52 | ||
![]() |
d91c898f6d | ||
![]() |
f16989a45d | ||
![]() |
1ef524e832 | ||
![]() |
65fcdd2745 | ||
![]() |
7030f562e8 | ||
![]() |
047b31dd67 | ||
![]() |
ae0d33f026 | ||
![]() |
039a0b7eb5 | ||
![]() |
3d3fcc41b8 | ||
![]() |
3e3e8f3132 | ||
![]() |
da21895e40 | ||
![]() |
34752ed69e | ||
![]() |
008eb98f50 | ||
![]() |
e521cd4648 | ||
![]() |
cb97a784da | ||
![]() |
96d9d81be2 | ||
![]() |
71797eb93f | ||
![]() |
ae3870e297 | ||
![]() |
055de5013c | ||
![]() |
ad1ea01976 | ||
![]() |
064fbfac67 | ||
![]() |
a31c83476d | ||
![]() |
bc9f4c267e | ||
![]() |
3e92ae278e | ||
![]() |
6adc3543a8 | ||
![]() |
af5293948c | ||
![]() |
34620db925 | ||
![]() |
4a5a96ea16 | ||
![]() |
fcc9d7cf7d | ||
![]() |
9cae50bfc5 | ||
![]() |
3cff5909bd | ||
![]() |
0130cd9d54 | ||
![]() |
926d4f51b3 | ||
![]() |
efe390f68d | ||
![]() |
6d0b2608a2 | ||
![]() |
ddd306dbf6 | ||
![]() |
30c632a5cc | ||
![]() |
65b3254b77 | ||
![]() |
f776a30c9f | ||
![]() |
c02525440c | ||
![]() |
dbda8dad34 | ||
![]() |
bc44f55989 | ||
![]() |
a3c998adab | ||
![]() |
b62b59f9b8 | ||
![]() |
bb1557b0a4 | ||
![]() |
d0fe64355b | ||
![]() |
7b4e6e8f3a | ||
![]() |
bd11c90846 | ||
![]() |
21f33618d4 | ||
![]() |
11c8ca6582 | ||
![]() |
6e9ac1b59a | ||
![]() |
91591a81ff | ||
![]() |
1ce0ff2e00 | ||
![]() |
92cef766f6 | ||
![]() |
d1d55d39b1 | ||
![]() |
4ad55bff6e | ||
![]() |
f73c41a709 | ||
![]() |
3ca55d972a | ||
![]() |
2b25c25337 | ||
![]() |
3621877f32 | ||
![]() |
cf2a8ddc96 | ||
![]() |
d461515881 | ||
![]() |
5828571a95 | ||
![]() |
3aba7c9b93 | ||
![]() |
d371bf2f41 | ||
![]() |
51934dccbd | ||
![]() |
b6caf47ce6 | ||
![]() |
e8362268bb | ||
![]() |
88c57a6794 | ||
![]() |
69a0b46a20 | ||
![]() |
a5a662c05d | ||
![]() |
4ab0b51d87 | ||
![]() |
c3285b24d9 | ||
![]() |
313345da3d | ||
![]() |
7173be8c22 | ||
![]() |
b97ef8c643 | ||
![]() |
d221f8e561 | ||
![]() |
95ed0a58b9 | ||
![]() |
77d4176b55 | ||
![]() |
48013e2102 | ||
![]() |
31dcb2b514 | ||
![]() |
43a9a7cb19 | ||
![]() |
88cecba2b6 | ||
![]() |
91e1870f91 | ||
![]() |
705050a96a | ||
![]() |
8d2550fb99 | ||
![]() |
c4a89d4ee3 | ||
![]() |
6140cdfabc | ||
![]() |
5786d61620 | ||
![]() |
32f6e16275 | ||
![]() |
668986fc08 | ||
![]() |
9190893ccf | ||
![]() |
546837ab55 | ||
![]() |
3825feae08 | ||
![]() |
fdeabae061 | ||
![]() |
8b307be92d | ||
![]() |
45516c4013 | ||
![]() |
5d8f7a4e34 | ||
![]() |
792c9279e2 | ||
![]() |
8825a8163e | ||
![]() |
7e42328c6f | ||
![]() |
2dd99d43ed | ||
![]() |
991b4fc965 | ||
![]() |
e9d7af5320 | ||
![]() |
bff23fe825 | ||
![]() |
79bd94ee6f | ||
![]() |
fdd833ef7c | ||
![]() |
c386504296 | ||
![]() |
9cc8649422 | ||
![]() |
625a9179c5 | ||
![]() |
66ed73f7c9 | ||
![]() |
95bd48d6c6 | ||
![]() |
328490a85e | ||
![]() |
d08413aeab | ||
![]() |
922f1fbade | ||
![]() |
cb96c8d470 | ||
![]() |
f3ebc43667 | ||
![]() |
e7696dcdf3 | ||
![]() |
4abf7d7738 | ||
![]() |
be206161d6 | ||
![]() |
dcd17f886e | ||
![]() |
2c94dea941 | ||
![]() |
7a2291f7db | ||
![]() |
af0d9142ed | ||
![]() |
6ba7d5b78f | ||
![]() |
bbf035d5fd | ||
![]() |
84bc4369cb | ||
![]() |
e01f77998c | ||
![]() |
08acd4df46 | ||
![]() |
2c9d968aa8 | ||
![]() |
19a3996861 | ||
![]() |
6d187e8117 | ||
![]() |
a7687a6fc2 | ||
![]() |
7cbec63039 | ||
![]() |
91320f1cd7 | ||
![]() |
5518ce5e0c | ||
![]() |
d59c52d7a5 | ||
![]() |
dd3a4cb289 | ||
![]() |
fc6ded4b1a | ||
![]() |
d4f61b3846 | ||
![]() |
ffad7b6ba7 | ||
![]() |
499f541328 | ||
![]() |
7c893a3c4b | ||
![]() |
1f5c55826b | ||
![]() |
197824004e | ||
![]() |
ee1d465bf6 | ||
![]() |
12bb070def | ||
![]() |
13ab29bcab | ||
![]() |
8cf0445b6d | ||
![]() |
90cc06c3fd | ||
![]() |
7d7b2cbcfd | ||
![]() |
3a76e0bb2e | ||
![]() |
9e2d070ed4 | ||
![]() |
f796718cae | ||
![]() |
a4c2ccdb93 | ||
![]() |
ccbaf1c2c2 | ||
![]() |
7ccd97eb49 | ||
![]() |
6b320e9cf3 | ||
![]() |
a6a5f2b132 | ||
![]() |
6fd7e97ef2 | ||
![]() |
fc821b5abd | ||
![]() |
f69a9b38fe | ||
![]() |
710b9d79ab | ||
![]() |
fddae662c8 | ||
![]() |
c3b3a3cea6 | ||
![]() |
2201cfe142 | ||
![]() |
a9ae3b2475 | ||
![]() |
3238706b25 | ||
![]() |
a5aec13b7f | ||
![]() |
454887ad21 | ||
![]() |
0ad585d647 | ||
![]() |
5aa3b889f5 | ||
![]() |
d371857fa8 | ||
![]() |
e116a42f8b | ||
![]() |
76acbc6c9f | ||
![]() |
572a6e24c4 | ||
![]() |
b3731a21ba | ||
![]() |
f58ba65d97 | ||
![]() |
45a99bb0cb | ||
![]() |
ab70be983a | ||
![]() |
cad1f96df7 | ||
![]() |
71866521f4 | ||
![]() |
56206d4fb8 | ||
![]() |
347681d199 | ||
![]() |
e6abdcf3e9 | ||
![]() |
dbae3b66cd | ||
![]() |
40ec457d20 | ||
![]() |
063caf3bcd | ||
![]() |
9325fd0977 | ||
![]() |
6231583590 | ||
![]() |
fce3a2c822 | ||
![]() |
73f45c9a24 | ||
![]() |
ca65f23c70 | ||
![]() |
f525398827 | ||
![]() |
cf2baa74a8 | ||
![]() |
fbd896d593 | ||
![]() |
f747107042 | ||
![]() |
760ad140cd | ||
![]() |
a0df434ed2 | ||
![]() |
0bcd5cfd8f | ||
![]() |
e0b71783c0 | ||
![]() |
45b76b2d3e | ||
![]() |
8228c99350 | ||
![]() |
c4dbd8180d | ||
![]() |
d3b1545a1e | ||
![]() |
57d7936239 | ||
![]() |
9e5315043d | ||
![]() |
89ba81f2e1 | ||
![]() |
1ee734cd18 | ||
![]() |
0bcfb6babf | ||
![]() |
fddd9e9db2 | ||
![]() |
2433fd1442 | ||
![]() |
437d832ac1 | ||
![]() |
c90aef1dfa | ||
![]() |
2d25f15041 | ||
![]() |
993d5bfd25 | ||
![]() |
4d8681b5ee | ||
![]() |
1384e26dd8 | ||
![]() |
809bf3360d | ||
![]() |
444875b82a | ||
![]() |
c261b64c8e | ||
![]() |
ab1b8a4500 | ||
![]() |
e65d5ba93b | ||
![]() |
6f8ba6b44b | ||
![]() |
aede1c47a2 | ||
![]() |
7b9ff043ad | ||
![]() |
d759104c76 | ||
![]() |
676bbba7a4 | ||
![]() |
b8d9f6442a | ||
![]() |
fc501b5e51 | ||
![]() |
3ddec27b69 | ||
![]() |
6574cf6b50 | ||
![]() |
d36ef2e48e | ||
![]() |
f8645df60c | ||
![]() |
f69942a3a3 | ||
![]() |
a36431a08c | ||
![]() |
f570d7e428 | ||
![]() |
fb502bc926 | ||
![]() |
2e12041688 | ||
![]() |
ca444bcf65 | ||
![]() |
da1b7b9a80 | ||
![]() |
e02d458109 | ||
![]() |
51e1a19e3e | ||
![]() |
68869a52e2 | ||
![]() |
8527c3f531 | ||
![]() |
4317094f1f | ||
![]() |
00cf7ef67d | ||
![]() |
21e7e762cd | ||
![]() |
ebb35cf9be | ||
![]() |
bd7f9a8030 | ||
![]() |
b1e0ed457e | ||
![]() |
59e40d53a1 | ||
![]() |
666a180f3f | ||
![]() |
ffc767c738 | ||
![]() |
49b252f7cf | ||
![]() |
81e9a0c796 | ||
![]() |
f3f5b21910 | ||
![]() |
b29ab2690f | ||
![]() |
f58cab0820 | ||
![]() |
b21631acd8 | ||
![]() |
52258f9949 | ||
![]() |
1308e842ce | ||
![]() |
c4ba51f4ee | ||
![]() |
65d411b00d | ||
![]() |
52d596b01b | ||
![]() |
1607dd90ba | ||
![]() |
11767f7d27 | ||
![]() |
7cbcf6844d | ||
![]() |
9f36258c60 | ||
![]() |
8f964ffc37 | ||
![]() |
4c137996ff | ||
![]() |
966bd5f917 | ||
![]() |
b55011d595 | ||
![]() |
38bd59641e | ||
![]() |
c449b60940 | ||
![]() |
ee618cdd1f | ||
![]() |
a5bddc21bb | ||
![]() |
35bf195f42 | ||
![]() |
044c2dda0e | ||
![]() |
9f2a70fc88 | ||
![]() |
409476c729 | ||
![]() |
91f8f25d26 | ||
![]() |
f853839939 | ||
![]() |
6099b44723 | ||
![]() |
e4e8140145 | ||
![]() |
8b34ab5102 | ||
![]() |
6dc987163d | ||
![]() |
a082c71a52 | ||
![]() |
cc37725014 | ||
![]() |
c6fd86dca6 | ||
![]() |
7f55d6f1e2 | ||
![]() |
129a7c1a09 | ||
![]() |
4969c4e2fc | ||
![]() |
d290fbe933 | ||
![]() |
358d3d82d6 | ||
![]() |
9b560c79ab | ||
![]() |
8b58bc420d | ||
![]() |
78a74ffe1b | ||
![]() |
565c675ce1 | ||
![]() |
a25a13188d | ||
![]() |
2daf85f753 | ||
![]() |
3189258fbb | ||
![]() |
d678bf68c5 | ||
![]() |
9498b17b98 | ||
![]() |
b48a335aed | ||
![]() |
71488e749a | ||
![]() |
0f1cbfa3ee | ||
![]() |
32d33bf85e | ||
![]() |
c7f1aa895d | ||
![]() |
675bc3d281 | ||
![]() |
c63ba8c22b | ||
![]() |
e9be73226e | ||
![]() |
4a72e554b6 | ||
![]() |
33bb3b0722 | ||
![]() |
46b4288c98 | ||
![]() |
9477117236 | ||
![]() |
33e048238e | ||
![]() |
5895d42444 | ||
![]() |
5d6abca503 | ||
![]() |
8ca91af927 | ||
![]() |
c886fd6915 | ||
![]() |
e403ca6eff | ||
![]() |
8f1b401137 | ||
![]() |
0952899204 | ||
![]() |
a11c643e41 | ||
![]() |
17fbdeafac | ||
![]() |
06e09f3a45 | ||
![]() |
4fa63aa939 | ||
![]() |
46622cd5d9 | ||
![]() |
6d3aace1c9 | ||
![]() |
e3a569be18 | ||
![]() |
2a256ef2bd | ||
![]() |
4f3fcc8b22 | ||
![]() |
045cc0d243 | ||
![]() |
51e33abbe6 | ||
![]() |
4900c043ca | ||
![]() |
09d55979ce | ||
![]() |
5d431fa9cf | ||
![]() |
113573b2d2 | ||
![]() |
2c3d93608b | ||
![]() |
d38af18582 | ||
![]() |
140f7c51f4 | ||
![]() |
ffa4350420 | ||
![]() |
99737323de | ||
![]() |
e9e2429632 | ||
![]() |
07597ac79a | ||
![]() |
248ec60612 | ||
![]() |
726847d5df | ||
![]() |
6065a95132 | ||
![]() |
e1259475d2 | ||
![]() |
ee018e7c02 | ||
![]() |
db43381f0d | ||
![]() |
28c58d4ec0 | ||
![]() |
68efe2b3c4 | ||
![]() |
795979be07 | ||
![]() |
c87a1b92d3 | ||
![]() |
2548ad6605 | ||
![]() |
b6519c6626 | ||
![]() |
2b8bb47aed | ||
![]() |
bbf332171e | ||
![]() |
ef5c9b52a5 | ||
![]() |
4632523d70 | ||
![]() |
56b996e6e4 | ||
![]() |
0ca0996493 | ||
![]() |
e1a8e72742 | ||
![]() |
50959f4490 | ||
![]() |
baaebb402e | ||
![]() |
9e862772eb | ||
![]() |
6cb5505ded | ||
![]() |
eb0d8615e1 | ||
![]() |
f2885c2fce | ||
![]() |
c04018bc70 | ||
![]() |
3e5032ca8b | ||
![]() |
a5014243d9 | ||
![]() |
2692eb141c | ||
![]() |
b447f1d52e | ||
![]() |
606b9af3f1 | ||
![]() |
3deab9af24 | ||
![]() |
afab0c5cde | ||
![]() |
e1a03f0dfb | ||
![]() |
bfe8dfab02 | ||
![]() |
a4335edc07 | ||
![]() |
8613698be9 | ||
![]() |
731ddc3e28 | ||
![]() |
2303e32256 | ||
![]() |
9064f123b3 | ||
![]() |
9d3ee4a612 | ||
![]() |
20602eed6d | ||
![]() |
f2cd86738c | ||
![]() |
fc55446342 | ||
![]() |
dbc12c06cb | ||
![]() |
efb551cd94 | ||
![]() |
a87c1aa864 | ||
![]() |
555f55592a | ||
![]() |
ab46739986 | ||
![]() |
7ac7958462 | ||
![]() |
46d5dace8f | ||
![]() |
aace3f42d0 | ||
![]() |
c4297f2b2b | ||
![]() |
3c90e904fc | ||
![]() |
f8c8065e4a | ||
![]() |
d45a5a35d8 | ||
![]() |
a9c6b868fb | ||
![]() |
ffe8168f0f | ||
![]() |
d1f5ebd546 | ||
![]() |
2e8b465b3f | ||
![]() |
4a535c94a6 | ||
![]() |
531d47cbd1 | ||
![]() |
7a2491fe49 | ||
![]() |
2773c61677 | ||
![]() |
788b063384 | ||
![]() |
0e112899df | ||
![]() |
d8c635bc1d | ||
![]() |
dfe55b94ff | ||
![]() |
6b4b6b522f | ||
![]() |
0d65fe348b | ||
![]() |
e4b7693685 | ||
![]() |
566aa83fa9 | ||
![]() |
eb2d88eac2 | ||
![]() |
316b77ec7b | ||
![]() |
a4dc6832f3 | ||
![]() |
7a895dda1a | ||
![]() |
b2fc016aa8 | ||
![]() |
32096ea4fd | ||
![]() |
30fd36ed26 | ||
![]() |
afdf09076a | ||
![]() |
04a0a0cc64 | ||
![]() |
fda4d2dcef | ||
![]() |
2c341e23b5 | ||
![]() |
953b06f43f | ||
![]() |
e1cc1d56ea | ||
![]() |
df2f4d4a6c | ||
![]() |
092820173f | ||
![]() |
58c7c23bd8 | ||
![]() |
920afe450a | ||
![]() |
e0eedca7c9 | ||
![]() |
3edcce29fa | ||
![]() |
e14c7fec10 | ||
![]() |
b3ed62244d | ||
![]() |
ff1bfa990c | ||
![]() |
bd3463880e | ||
![]() |
95a61ec369 | ||
![]() |
1b29797a81 | ||
![]() |
10b21ee085 | ||
![]() |
f5ffdc1707 | ||
![]() |
b3f17b84ff | ||
![]() |
1a332128e3 | ||
![]() |
b5db7e80d0 | ||
![]() |
5bba719624 | ||
![]() |
09d6838b08 | ||
![]() |
325d2dc2a4 | ||
![]() |
94c7e2b5c3 | ||
![]() |
9da2e8d489 | ||
![]() |
ff1c5df30b | ||
![]() |
159adf9911 | ||
![]() |
994a94f8f1 | ||
![]() |
571db20523 | ||
![]() |
a422d62db0 | ||
![]() |
feb2a52b37 | ||
![]() |
267ca2e95c | ||
![]() |
9691a932ac | ||
![]() |
94d51c029e | ||
![]() |
efd7b2ca2b | ||
![]() |
ed88784431 | ||
![]() |
e48f844ea0 | ||
![]() |
fd21be5408 | ||
![]() |
f4c2d01df8 | ||
![]() |
4828bb4df7 | ||
![]() |
037e91508b | ||
![]() |
735010dd1f | ||
![]() |
11364dd788 | ||
![]() |
0eced6f4a9 | ||
![]() |
3157c89be3 | ||
![]() |
a7f909d06a | ||
![]() |
527b55a46e | ||
![]() |
a610306f29 | ||
![]() |
3daf0b394e | ||
![]() |
f151928b6b | ||
![]() |
041a3ce2b6 | ||
![]() |
6348e7b8f0 | ||
![]() |
8be72618e6 | ||
![]() |
a50c7cb474 | ||
![]() |
8cef4e5cf9 | ||
![]() |
9705a1b5b5 | ||
![]() |
d0ddd82906 | ||
![]() |
dfa17948e2 | ||
![]() |
3607391d55 | ||
![]() |
e7f2f54f1b | ||
![]() |
b467c7de65 | ||
![]() |
c3e793229d | ||
![]() |
14bcfb01c2 | ||
![]() |
8a5846ff81 | ||
![]() |
298209b03a | ||
![]() |
dc03a7f135 | ||
![]() |
2d357d0ed2 | ||
![]() |
58d2590495 | ||
![]() |
0c892225f3 | ||
![]() |
851d92a140 | ||
![]() |
665ce2f022 | ||
![]() |
5ea1ff1ea5 | ||
![]() |
5d28369b8b | ||
![]() |
5ed92342ea | ||
![]() |
d9843c4ff0 | ||
![]() |
448371dffa | ||
![]() |
425c288aa0 | ||
![]() |
09b118987e | ||
![]() |
70dbb6bfcc | ||
![]() |
4b55f9cd97 | ||
![]() |
fbb6614553 | ||
![]() |
8d849b40a2 | ||
![]() |
5e678c15f3 | ||
![]() |
6642be3050 | ||
![]() |
9998fa97f7 | ||
![]() |
2b3113b67d | ||
![]() |
f933c29e5e | ||
![]() |
a908d737a4 | ||
![]() |
469b4e4f44 | ||
![]() |
13b31d16e8 | ||
![]() |
afa2eb3a6e | ||
![]() |
01eb26e5c1 | ||
![]() |
b967a1ecfa | ||
![]() |
dfe91e98c3 | ||
![]() |
6706aa67eb | ||
![]() |
15fe4f34f9 | ||
![]() |
9b7c446fd7 | ||
![]() |
9225239433 | ||
![]() |
e764b22698 | ||
![]() |
a4d88e4631 | ||
![]() |
20da700ad4 | ||
![]() |
e4a4937023 | ||
![]() |
822b60b454 | ||
![]() |
72a024c713 | ||
![]() |
6132881b8a | ||
![]() |
1ac22ec563 | ||
![]() |
ca68905b05 | ||
![]() |
2470f5f941 | ||
![]() |
fd1ea4fc49 | ||
![]() |
3f8b933d05 | ||
![]() |
3b13f0d73f | ||
![]() |
a261efbc01 | ||
![]() |
04d4474648 | ||
![]() |
4a50c84cfe | ||
![]() |
9ca091e592 | ||
![]() |
fa56f30f63 | ||
![]() |
9c19307181 | ||
![]() |
bfd34df41e | ||
![]() |
26ee36458d | ||
![]() |
e99b83dfdc | ||
![]() |
ceb75323fe | ||
![]() |
bfb6417865 | ||
![]() |
498564be9a | ||
![]() |
6c38aa4008 | ||
![]() |
32aaa3d0ff | ||
![]() |
7ce5d647da | ||
![]() |
bf0be7fa0e | ||
![]() |
a10eb5ff90 | ||
![]() |
c6a27d8893 | ||
![]() |
7ee1fb4b76 | ||
![]() |
e0f9f558f1 | ||
![]() |
d511bb9fc0 | ||
![]() |
15f99c8ae8 | ||
![]() |
1027fbfb60 | ||
![]() |
d89abde860 | ||
![]() |
1d76f6b056 | ||
![]() |
f09fb735a7 | ||
![]() |
9ae92ef88c | ||
![]() |
b58d6e1bfd | ||
![]() |
a3a5da8550 | ||
![]() |
178a2e34c6 | ||
![]() |
b39276feca | ||
![]() |
2c40f0dddc |
@@ -243,6 +243,106 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"code"
|
"code"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "nstefanou",
|
||||||
|
"name": "nstefanou",
|
||||||
|
"avatar_url": "https://avatars3.githubusercontent.com/u/51129173?v=4",
|
||||||
|
"profile": "https://github.com/nstefanou",
|
||||||
|
"contributions": [
|
||||||
|
"code",
|
||||||
|
"plugin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "orin220444",
|
||||||
|
"name": "orin220444",
|
||||||
|
"avatar_url": "https://avatars3.githubusercontent.com/u/30747229?v=4",
|
||||||
|
"profile": "https://github.com/orin220444",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "Goobles",
|
||||||
|
"name": "Gobius Dolhain",
|
||||||
|
"avatar_url": "https://avatars3.githubusercontent.com/u/8776771?v=4",
|
||||||
|
"profile": "https://github.com/Goobles",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "3l0w",
|
||||||
|
"name": "Gwilherm Folliot",
|
||||||
|
"avatar_url": "https://avatars2.githubusercontent.com/u/37798980?v=4",
|
||||||
|
"profile": "https://github.com/3l0w",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "dimitory",
|
||||||
|
"name": "Dmitry Pronin",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/475955?v=4",
|
||||||
|
"profile": "https://github.com/Dimitory",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "JonathanBeverley",
|
||||||
|
"name": "Jonathan Beverley",
|
||||||
|
"avatar_url": "https://avatars1.githubusercontent.com/u/20328966?v=4",
|
||||||
|
"profile": "https://github.com/JonathanBeverley",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "zend",
|
||||||
|
"name": "Zenghai Liang",
|
||||||
|
"avatar_url": "https://avatars1.githubusercontent.com/u/25160?v=4",
|
||||||
|
"profile": "https://github.com/zend",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "matishadow",
|
||||||
|
"name": "Mateusz Tracz",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/9083085?v=4",
|
||||||
|
"profile": "https://about.me/matishadow",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "pinpins",
|
||||||
|
"name": "pinpin",
|
||||||
|
"avatar_url": "https://avatars3.githubusercontent.com/u/36234677?v=4",
|
||||||
|
"profile": "https://zergpool.com",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "TakuroOnoda",
|
||||||
|
"name": "Takuro Onoda",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/1407926?v=4",
|
||||||
|
"profile": "https://github.com/TakuroOnoda",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "frauhottelmann",
|
||||||
|
"name": "frauhottelmann",
|
||||||
|
"avatar_url": "https://avatars2.githubusercontent.com/u/902705?v=4",
|
||||||
|
"profile": "https://github.com/frauhottelmann",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 7,
|
||||||
|
@@ -29,7 +29,6 @@ rules:
|
|||||||
'@typescript-eslint/no-magic-numbers': off
|
'@typescript-eslint/no-magic-numbers': off
|
||||||
'@typescript-eslint/member-delimiter-style': off
|
'@typescript-eslint/member-delimiter-style': off
|
||||||
'@typescript-eslint/promise-function-async': off
|
'@typescript-eslint/promise-function-async': off
|
||||||
'@typescript-eslint/no-unnecessary-type-assertion': off
|
|
||||||
'@typescript-eslint/require-array-sort-compare': off
|
'@typescript-eslint/require-array-sort-compare': off
|
||||||
'@typescript-eslint/no-floating-promises': off
|
'@typescript-eslint/no-floating-promises': off
|
||||||
'@typescript-eslint/prefer-readonly': off
|
'@typescript-eslint/prefer-readonly': off
|
||||||
@@ -37,6 +36,7 @@ rules:
|
|||||||
'@typescript-eslint/strict-boolean-expressions': off
|
'@typescript-eslint/strict-boolean-expressions': off
|
||||||
'@typescript-eslint/no-misused-promises': off
|
'@typescript-eslint/no-misused-promises': off
|
||||||
'@typescript-eslint/typedef': off
|
'@typescript-eslint/typedef': off
|
||||||
|
'@typescript-eslint/consistent-type-imports': off
|
||||||
'@typescript-eslint/no-use-before-define':
|
'@typescript-eslint/no-use-before-define':
|
||||||
- error
|
- error
|
||||||
- classes: false
|
- classes: false
|
||||||
@@ -53,7 +53,8 @@ rules:
|
|||||||
computed-property-spacing:
|
computed-property-spacing:
|
||||||
- error
|
- error
|
||||||
- never
|
- never
|
||||||
comma-dangle:
|
comma-dangle: off
|
||||||
|
'@typescript-eslint/comma-dangle':
|
||||||
- error
|
- error
|
||||||
- always-multiline
|
- always-multiline
|
||||||
curly: error
|
curly: error
|
||||||
@@ -79,6 +80,7 @@ rules:
|
|||||||
args: after-used
|
args: after-used
|
||||||
argsIgnorePattern: ^_
|
argsIgnorePattern: ^_
|
||||||
no-undef: error
|
no-undef: error
|
||||||
|
no-var: error
|
||||||
object-curly-spacing:
|
object-curly-spacing:
|
||||||
- error
|
- error
|
||||||
- always
|
- always
|
||||||
@@ -92,9 +94,22 @@ rules:
|
|||||||
- error
|
- error
|
||||||
- single
|
- single
|
||||||
- allowTemplateLiterals: true
|
- allowTemplateLiterals: true
|
||||||
|
'@typescript-eslint/no-confusing-void-expression': off
|
||||||
'@typescript-eslint/no-non-null-assertion': off
|
'@typescript-eslint/no-non-null-assertion': off
|
||||||
'@typescript-eslint/no-unnecessary-condition': off
|
'@typescript-eslint/no-unnecessary-condition':
|
||||||
'@typescript-eslint/no-untyped-public-signature': off # bugs out on constructors
|
- error
|
||||||
|
- allowConstantLoopConditions: true
|
||||||
'@typescript-eslint/restrict-template-expressions': off
|
'@typescript-eslint/restrict-template-expressions': off
|
||||||
'@typescript-eslint/no-dynamic-delete': off
|
'@typescript-eslint/prefer-readonly-parameter-types': off
|
||||||
'@typescript-eslint/prefer-nullish-coalescing': off
|
'@typescript-eslint/no-unsafe-member-access': off
|
||||||
|
'@typescript-eslint/no-unsafe-call': off
|
||||||
|
'@typescript-eslint/no-unsafe-return': off
|
||||||
|
'@typescript-eslint/no-unsafe-assignment': off
|
||||||
|
'@typescript-eslint/naming-convention': off
|
||||||
|
'@typescript-eslint/lines-between-class-members':
|
||||||
|
- error
|
||||||
|
- exceptAfterSingleLine: true
|
||||||
|
'@typescript-eslint/dot-notation': off
|
||||||
|
'@typescript-eslint/no-implicit-any-catch': off
|
||||||
|
'@typescript-eslint/member-ordering': off
|
||||||
|
'@typescript-eslint/no-var-requires': off
|
||||||
|
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
- name: Installing Node
|
- name: Installing Node
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
version: 10
|
node-version: 10
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
|
5
.github/workflows/lint.yml
vendored
5
.github/workflows/lint.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
- name: Installing Node
|
- name: Installing Node
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
version: 10
|
node-version: 15
|
||||||
|
|
||||||
- name: Install deps
|
- name: Install deps
|
||||||
run: |
|
run: |
|
||||||
@@ -22,5 +22,8 @@ jobs:
|
|||||||
rm app/node_modules/.yarn-integrity
|
rm app/node_modules/.yarn-integrity
|
||||||
yarn
|
yarn
|
||||||
|
|
||||||
|
- name: Build typings
|
||||||
|
run: yarn run build:typings
|
||||||
|
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: yarn run lint
|
run: yarn run lint
|
||||||
|
15
.github/workflows/linux.yml
vendored
15
.github/workflows/linux.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
version: 10
|
node-version: 15
|
||||||
|
|
||||||
- name: Install deps
|
- name: Install deps
|
||||||
run: |
|
run: |
|
||||||
@@ -25,9 +25,6 @@ jobs:
|
|||||||
- name: Build native deps
|
- name: Build native deps
|
||||||
run: scripts/build-native.js
|
run: scripts/build-native.js
|
||||||
|
|
||||||
- name: Build typings
|
|
||||||
run: yarn run build:typings
|
|
||||||
|
|
||||||
- name: Webpack
|
- name: Webpack
|
||||||
run: yarn run build
|
run: yarn run build
|
||||||
|
|
||||||
@@ -39,6 +36,16 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
DEBUG: electron-builder,electron-builder:*
|
DEBUG: electron-builder,electron-builder:*
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
|
USE_HARD_LINKS: false
|
||||||
|
|
||||||
|
- name: Upload symbols
|
||||||
|
run: |
|
||||||
|
sudo npm install -g @sentry/cli --unsafe-perm
|
||||||
|
./scripts/sentry-upload.js
|
||||||
|
env:
|
||||||
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
|
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||||
|
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||||
|
|
||||||
- name: Package artifacts
|
- name: Package artifacts
|
||||||
run: |
|
run: |
|
||||||
|
47
.github/workflows/macos.yml
vendored
47
.github/workflows/macos.yml
vendored
@@ -2,7 +2,14 @@ name: macOS Build
|
|||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: macOS-latest
|
runs-on: macos-11.0
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- arch: x86_64
|
||||||
|
electron_setup_cmd: 'true'
|
||||||
|
- arch: arm64
|
||||||
|
electron_setup_cmd: 'yarn add -D electron@11.1.1'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -11,43 +18,63 @@ jobs:
|
|||||||
- name: Installing Node
|
- name: Installing Node
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
version: 10
|
node-version: 15
|
||||||
|
|
||||||
- name: Install deps
|
- name: Install deps
|
||||||
run: |
|
run: |
|
||||||
sudo npm i -g yarn@1.19.1
|
sudo npm i -g yarn@1.22.1
|
||||||
cd app
|
cd app
|
||||||
yarn
|
yarn
|
||||||
cd ..
|
cd ..
|
||||||
rm app/node_modules/.yarn-integrity
|
rm app/node_modules/.yarn-integrity
|
||||||
yarn
|
yarn
|
||||||
|
|
||||||
|
- name: Upgrade Electron for ARM builds
|
||||||
|
run: ${{ matrix.electron_setup_cmd }}
|
||||||
|
|
||||||
- name: Build native deps
|
- name: Build native deps
|
||||||
run: scripts/build-native.js
|
run: scripts/build-native.js
|
||||||
|
env:
|
||||||
- name: Build typings
|
ARCH: ${{matrix.arch}}
|
||||||
run: yarn run build:typings
|
|
||||||
|
|
||||||
- name: Webpack
|
- name: Webpack
|
||||||
run: yarn run build
|
run: yarn run build
|
||||||
|
|
||||||
- name: Prepackage plugins
|
- name: Prepackage plugins
|
||||||
run: scripts/prepackage-plugins.js
|
run: scripts/prepackage-plugins.js
|
||||||
|
env:
|
||||||
|
ARCH: ${{matrix.arch}}
|
||||||
|
|
||||||
|
- run: sed -i '' 's/updateInfo = await/\/\/updateInfo = await/g' node_modules/app-builder-lib/out/targets/ArchiveTarget.js
|
||||||
|
|
||||||
- name: Build and sign packages
|
- name: Build and sign packages
|
||||||
run: scripts/build-macos.js
|
run: scripts/build-macos.js
|
||||||
if: github.repository == 'Eugeny/terminus' && github.event_name == 'push'
|
if: github.repository == 'Eugeny/terminus' && github.event_name == 'push'
|
||||||
env:
|
env:
|
||||||
DEBUG: electron-builder,electron-builder:*
|
ARCH: ${{matrix.arch}}
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||||
|
APPSTORE_USERNAME: ${{ secrets.APPSTORE_USERNAME }}
|
||||||
|
APPSTORE_PASSWORD: ${{ secrets.APPSTORE_PASSWORD }}
|
||||||
|
USE_HARD_LINKS: false
|
||||||
|
# DEBUG: electron-builder,electron-builder:*
|
||||||
|
|
||||||
- name: Build packages without signing
|
- name: Build packages without signing
|
||||||
run: scripts/build-macos.js
|
run: scripts/build-macos.js
|
||||||
if: github.repository != 'Eugeny/terminus' || github.event_name != 'push'
|
if: github.repository != 'Eugeny/terminus' || github.event_name != 'push'
|
||||||
env:
|
env:
|
||||||
DEBUG: electron-builder,electron-builder:*
|
ARCH: ${{matrix.arch}}
|
||||||
|
# DEBUG: electron-builder,electron-builder:*
|
||||||
|
|
||||||
|
- name: Upload symbols
|
||||||
|
run: |
|
||||||
|
sudo npm install -g @sentry/cli --unsafe-perm
|
||||||
|
./scripts/sentry-upload.js
|
||||||
|
env:
|
||||||
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
|
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||||
|
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||||
|
|
||||||
- name: Package artifacts
|
- name: Package artifacts
|
||||||
run: |
|
run: |
|
||||||
@@ -59,11 +86,11 @@ jobs:
|
|||||||
- uses: actions/upload-artifact@master
|
- uses: actions/upload-artifact@master
|
||||||
name: Upload PKG
|
name: Upload PKG
|
||||||
with:
|
with:
|
||||||
name: macOS .pkg
|
name: macOS .pkg (${{matrix.arch}})
|
||||||
path: artifact-pkg
|
path: artifact-pkg
|
||||||
|
|
||||||
- uses: actions/upload-artifact@master
|
- uses: actions/upload-artifact@master
|
||||||
name: Upload ZIP
|
name: Upload ZIP
|
||||||
with:
|
with:
|
||||||
name: macOS .zip
|
name: macOS .zip (${{matrix.arch}})
|
||||||
path: artifact-zip
|
path: artifact-zip
|
||||||
|
13
.github/workflows/windows.yml
vendored
13
.github/workflows/windows.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
- name: Installing Node
|
- name: Installing Node
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
version: 10
|
node-version: 14
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
shell: powershell
|
shell: powershell
|
||||||
@@ -34,12 +34,21 @@ jobs:
|
|||||||
run: node scripts/build-windows.js
|
run: node scripts/build-windows.js
|
||||||
if: github.repository != 'Eugeny/terminus' || github.event_name != 'push'
|
if: github.repository != 'Eugeny/terminus' || github.event_name != 'push'
|
||||||
|
|
||||||
|
- name: Upload symbols
|
||||||
|
run: |
|
||||||
|
npm install @sentry/cli
|
||||||
|
node scripts/sentry-upload.js
|
||||||
|
env:
|
||||||
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
|
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||||
|
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||||
|
|
||||||
- name: Package artifacts
|
- name: Package artifacts
|
||||||
run: |
|
run: |
|
||||||
mkdir artifact-setup
|
mkdir artifact-setup
|
||||||
mv dist/*-setup.exe artifact-setup/
|
mv dist/*-setup.exe artifact-setup/
|
||||||
mkdir artifact-portable
|
mkdir artifact-portable
|
||||||
mv dist/*-portable.exe artifact-portable/
|
mv dist/*-portable.zip artifact-portable/
|
||||||
|
|
||||||
- uses: actions/upload-artifact@master
|
- uses: actions/upload-artifact@master
|
||||||
name: Upload installer
|
name: Upload installer
|
||||||
|
5
.gitignore
vendored
5
.gitignore
vendored
@@ -13,6 +13,9 @@ dist
|
|||||||
*.xcuserstate
|
*.xcuserstate
|
||||||
*.wixpdb
|
*.wixpdb
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
.DS_Store?
|
||||||
|
|
||||||
coverage
|
coverage
|
||||||
.nyc_output
|
.nyc_output
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
@@ -28,3 +31,5 @@ docs/api
|
|||||||
.electron-symbols
|
.electron-symbols
|
||||||
sentry.properties
|
sentry.properties
|
||||||
sentry-symbols.js
|
sentry-symbols.js
|
||||||
|
|
||||||
|
terminus-ssh/util/pagent.exe
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js: 11
|
node_js: 15
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- Build
|
- Build
|
||||||
|
78
README.md
78
README.md
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://raw.githubusercontent.com/Eugeny/terminus/master/LICENSE"><img alt="GitHub" src="https://img.shields.io/github/license/eugeny/terminus.svg?label=License&style=flat-square"></a> <a href="https://ci.appveyor.com/project/Eugeny/terminus"><img alt="AppVeyor" src="https://img.shields.io/appveyor/ci/eugeny/terminus.svg?label=CI&logo=appveyor&logoColor=white&style=flat-square"></a>
|
<a href="https://raw.githubusercontent.com/Eugeny/terminus/master/LICENSE"><img alt="GitHub" src="https://img.shields.io/github/license/eugeny/terminus.svg?label=License&style=flat-square"></a> <a href="https://ci.appveyor.com/project/Eugeny/terminus"><img alt="AppVeyor" src="https://img.shields.io/appveyor/ci/eugeny/****terminus****.svg?label=CI&logo=appveyor&logoColor=white&style=flat-square"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/Eugeny/terminus/releases/latest"><img alt="GitHub All Releases" src="https://img.shields.io/github/downloads/eugeny/terminus/total.svg?label=DOWNLOAD&logo=github&style=for-the-badge"></a> <a href="https://ci.appveyor.com/project/Eugeny/terminus/build/artifacts"><img src="https://img.shields.io/badge/download-nightly%20build-magenta.svg?logo=appveyor&style=for-the-badge"/></a> <a href="https://gitter.im/terminus-terminal/community"><img alt="Gitter" src="https://img.shields.io/gitter/room/terminus/community.svg?color=blue&logo=gitter&style=for-the-badge"></a>
|
<a href="https://github.com/Eugeny/terminus/releases/latest"><img alt="GitHub All Releases" src="https://img.shields.io/github/downloads/eugeny/terminus/total.svg?label=RELEASE&logo=github&style=for-the-badge"></a> <a href="https://nightly.link/Eugeny/terminus/workflows/windows/master"><img src="https://shields.io/badge/-Nightly-blue?logo=windows&style=for-the-badge"/></a> <a href="https://nightly.link/Eugeny/terminus/workflows/macos/master"><img src="https://shields.io/badge/-Nightly-black?logo=apple&style=for-the-badge"/></a> <a href="https://nightly.link/Eugeny/terminus/workflows/linux/master"><img src="https://shields.io/badge/-Nightly-orange?logo=linux&style=for-the-badge"/></a> <a href="https://gitter.im/terminus-terminal/community"><img alt="Gitter" src="https://img.shields.io/gitter/room/terminus/community.svg?color=magenta&logo=gitter&style=for-the-badge"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
----
|
----
|
||||||
@@ -35,6 +35,10 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
# Portable
|
||||||
|
|
||||||
|
For portable in windows, user can create folder `data` at the same directory as `Terminal.exe` to save the settings.
|
||||||
|
|
||||||
# Plugins
|
# Plugins
|
||||||
|
|
||||||
Plugins and themes can be installed directly from the Settings view inside Terminus.
|
Plugins and themes can be installed directly from the Settings view inside Terminus.
|
||||||
@@ -70,43 +74,59 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||||||
<!-- markdownlint-disable -->
|
<!-- markdownlint-disable -->
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center"><a href="http://www.russellmyers.com"><img src="https://avatars2.githubusercontent.com/u/184085?v=4" width="100px;" alt=""/><br /><sub><b>Russell Myers</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=mezner" title="Code">💻</a></td>
|
<td align="center"><a href="http://www.russellmyers.com"><img src="https://avatars2.githubusercontent.com/u/184085?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Russell Myers</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=mezner" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="http://www.morwire.com"><img src="https://avatars1.githubusercontent.com/u/3991658?v=4" width="100px;" alt=""/><br /><sub><b>Austin Warren</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=ehwarren" title="Code">💻</a></td>
|
<td align="center"><a href="http://www.morwire.com"><img src="https://avatars1.githubusercontent.com/u/3991658?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Austin Warren</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=ehwarren" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="https://github.com/Drachenkaetzchen"><img src="https://avatars1.githubusercontent.com/u/162974?v=4" width="100px;" alt=""/><br /><sub><b>Felicia Hummel</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=Drachenkaetzchen" title="Code">💻</a></td>
|
<td align="center"><a href="https://github.com/Drachenkaetzchen"><img src="https://avatars1.githubusercontent.com/u/162974?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Felicia Hummel</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=Drachenkaetzchen" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="https://github.com/mikemaccana"><img src="https://avatars2.githubusercontent.com/u/172594?v=4" width="100px;" alt=""/><br /><sub><b>Mike MacCana</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=mikemaccana" title="Tests">⚠️</a> <a href="#design-mikemaccana" title="Design">🎨</a></td>
|
<td align="center"><a href="https://github.com/mikemaccana"><img src="https://avatars2.githubusercontent.com/u/172594?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mike MacCana</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=mikemaccana" title="Tests">⚠️</a> <a href="#design-mikemaccana" title="Design">🎨</a></td>
|
||||||
<td align="center"><a href="https://github.com/yxuko"><img src="https://avatars1.githubusercontent.com/u/1786317?v=4" width="100px;" alt=""/><br /><sub><b>Yacine Kanzari</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=yxuko" title="Code">💻</a></td>
|
<td align="center"><a href="https://github.com/yxuko"><img src="https://avatars1.githubusercontent.com/u/1786317?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yacine Kanzari</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=yxuko" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="https://github.com/BBJip"><img src="https://avatars2.githubusercontent.com/u/32908927?v=4" width="100px;" alt=""/><br /><sub><b>BBJip</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=BBJip" title="Code">💻</a></td>
|
<td align="center"><a href="https://github.com/BBJip"><img src="https://avatars2.githubusercontent.com/u/32908927?v=4?s=100" width="100px;" alt=""/><br /><sub><b>BBJip</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=BBJip" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="https://github.com/Futagirl"><img src="https://avatars2.githubusercontent.com/u/33533958?v=4" width="100px;" alt=""/><br /><sub><b>Futagirl</b></sub></a><br /><a href="#design-Futagirl" title="Design">🎨</a></td>
|
<td align="center"><a href="https://github.com/Futagirl"><img src="https://avatars2.githubusercontent.com/u/33533958?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Futagirl</b></sub></a><br /><a href="#design-Futagirl" title="Design">🎨</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center"><a href="https://www.levrik.io"><img src="https://avatars3.githubusercontent.com/u/9491603?v=4" width="100px;" alt=""/><br /><sub><b>Levin Rickert</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=levrik" title="Code">💻</a></td>
|
<td align="center"><a href="https://www.levrik.io"><img src="https://avatars3.githubusercontent.com/u/9491603?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Levin Rickert</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=levrik" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="https://kwonoj.github.io"><img src="https://avatars2.githubusercontent.com/u/1210596?v=4" width="100px;" alt=""/><br /><sub><b>OJ Kwon</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=kwonoj" title="Code">💻</a></td>
|
<td align="center"><a href="https://kwonoj.github.io"><img src="https://avatars2.githubusercontent.com/u/1210596?v=4?s=100" width="100px;" alt=""/><br /><sub><b>OJ Kwon</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=kwonoj" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="https://github.com/Domain"><img src="https://avatars2.githubusercontent.com/u/903197?v=4" width="100px;" alt=""/><br /><sub><b>domain</b></sub></a><br /><a href="#plugin-Domain" title="Plugin/utility libraries">🔌</a> <a href="https://github.com/Eugeny/terminus/commits?author=Domain" title="Code">💻</a></td>
|
<td align="center"><a href="https://github.com/Domain"><img src="https://avatars2.githubusercontent.com/u/903197?v=4?s=100" width="100px;" alt=""/><br /><sub><b>domain</b></sub></a><br /><a href="#plugin-Domain" title="Plugin/utility libraries">🔌</a> <a href="https://github.com/Eugeny/terminus/commits?author=Domain" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="http://www.jbrumond.me"><img src="https://avatars1.githubusercontent.com/u/195127?v=4" width="100px;" alt=""/><br /><sub><b>James Brumond</b></sub></a><br /><a href="#plugin-kbjr" title="Plugin/utility libraries">🔌</a></td>
|
<td align="center"><a href="http://www.jbrumond.me"><img src="https://avatars1.githubusercontent.com/u/195127?v=4?s=100" width="100px;" alt=""/><br /><sub><b>James Brumond</b></sub></a><br /><a href="#plugin-kbjr" title="Plugin/utility libraries">🔌</a></td>
|
||||||
<td align="center"><a href="http://www.growingwiththeweb.com"><img src="https://avatars0.githubusercontent.com/u/2193314?v=4" width="100px;" alt=""/><br /><sub><b>Daniel Imms</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=Tyriar" title="Code">💻</a> <a href="#plugin-Tyriar" title="Plugin/utility libraries">🔌</a> <a href="https://github.com/Eugeny/terminus/commits?author=Tyriar" title="Tests">⚠️</a></td>
|
<td align="center"><a href="http://www.growingwiththeweb.com"><img src="https://avatars0.githubusercontent.com/u/2193314?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Daniel Imms</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=Tyriar" title="Code">💻</a> <a href="#plugin-Tyriar" title="Plugin/utility libraries">🔌</a> <a href="https://github.com/Eugeny/terminus/commits?author=Tyriar" title="Tests">⚠️</a></td>
|
||||||
<td align="center"><a href="https://github.com/baflo"><img src="https://avatars2.githubusercontent.com/u/834350?v=4" width="100px;" alt=""/><br /><sub><b>Florian Bachmann</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=baflo" title="Code">💻</a></td>
|
<td align="center"><a href="https://github.com/baflo"><img src="https://avatars2.githubusercontent.com/u/834350?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Florian Bachmann</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=baflo" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="http://michael-kuehnel.de"><img src="https://avatars2.githubusercontent.com/u/441011?v=4" width="100px;" alt=""/><br /><sub><b>Michael Kühnel</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=mischah" title="Code">💻</a> <a href="#design-mischah" title="Design">🎨</a></td>
|
<td align="center"><a href="http://michael-kuehnel.de"><img src="https://avatars2.githubusercontent.com/u/441011?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Michael Kühnel</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=mischah" title="Code">💻</a> <a href="#design-mischah" title="Design">🎨</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center"><a href="https://github.com/NieLeben"><img src="https://avatars3.githubusercontent.com/u/47182955?v=4" width="100px;" alt=""/><br /><sub><b>Tilmann Meyer</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=NieLeben" title="Code">💻</a></td>
|
<td align="center"><a href="https://github.com/NieLeben"><img src="https://avatars3.githubusercontent.com/u/47182955?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tilmann Meyer</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=NieLeben" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="http://www.jubeat.net"><img src="https://avatars3.githubusercontent.com/u/11289158?v=4" width="100px;" alt=""/><br /><sub><b>PM Extra</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/issues?q=author%3APMExtra" title="Bug reports">🐛</a></td>
|
<td align="center"><a href="http://www.jubeat.net"><img src="https://avatars3.githubusercontent.com/u/11289158?v=4?s=100" width="100px;" alt=""/><br /><sub><b>PM Extra</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/issues?q=author%3APMExtra" title="Bug reports">🐛</a></td>
|
||||||
<td align="center"><a href="https://jjuhas.keybase.pub//"><img src="https://avatars1.githubusercontent.com/u/6438760?v=4" width="100px;" alt=""/><br /><sub><b>Jonathan</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=IgnusG" title="Code">💻</a></td>
|
<td align="center"><a href="https://jjuhas.keybase.pub//"><img src="https://avatars1.githubusercontent.com/u/6438760?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jonathan</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=IgnusG" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="https://hans-koch.me"><img src="https://avatars0.githubusercontent.com/u/1093709?v=4" width="100px;" alt=""/><br /><sub><b>Hans Koch</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=hammster" title="Code">💻</a></td>
|
<td align="center"><a href="https://hans-koch.me"><img src="https://avatars0.githubusercontent.com/u/1093709?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Hans Koch</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=hammster" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="http://thepuzzlemaker.info"><img src="https://avatars3.githubusercontent.com/u/12666617?v=4" width="100px;" alt=""/><br /><sub><b>Dak Smyth</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=ThePuzzlemaker" title="Code">💻</a></td>
|
<td align="center"><a href="http://thepuzzlemaker.info"><img src="https://avatars3.githubusercontent.com/u/12666617?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dak Smyth</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=ThePuzzlemaker" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="http://yfwz100.github.io"><img src="https://avatars2.githubusercontent.com/u/983211?v=4" width="100px;" alt=""/><br /><sub><b>Wang Zhi</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=yfwz100" title="Code">💻</a></td>
|
<td align="center"><a href="http://yfwz100.github.io"><img src="https://avatars2.githubusercontent.com/u/983211?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Wang Zhi</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=yfwz100" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="https://github.com/jack1142"><img src="https://avatars0.githubusercontent.com/u/6032823?v=4" width="100px;" alt=""/><br /><sub><b>jack1142</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=jack1142" title="Code">💻</a></td>
|
<td align="center"><a href="https://github.com/jack1142"><img src="https://avatars0.githubusercontent.com/u/6032823?v=4?s=100" width="100px;" alt=""/><br /><sub><b>jack1142</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=jack1142" title="Code">💻</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center"><a href="https://github.com/hdougie"><img src="https://avatars1.githubusercontent.com/u/450799?v=4" width="100px;" alt=""/><br /><sub><b>Howie Douglas</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=hdougie" title="Code">💻</a></td>
|
<td align="center"><a href="https://github.com/hdougie"><img src="https://avatars1.githubusercontent.com/u/450799?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Howie Douglas</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=hdougie" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="https://chriskaczor.com"><img src="https://avatars2.githubusercontent.com/u/180906?v=4" width="100px;" alt=""/><br /><sub><b>Chris Kaczor</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=ckaczor" title="Code">💻</a></td>
|
<td align="center"><a href="https://chriskaczor.com"><img src="https://avatars2.githubusercontent.com/u/180906?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Chris Kaczor</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=ckaczor" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="https://www.boxmein.net"><img src="https://avatars1.githubusercontent.com/u/358714?v=4" width="100px;" alt=""/><br /><sub><b>Johannes Kadak</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=boxmein" title="Code">💻</a></td>
|
<td align="center"><a href="https://www.boxmein.net"><img src="https://avatars1.githubusercontent.com/u/358714?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Johannes Kadak</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=boxmein" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="https://github.com/LeSeulArtichaut"><img src="https://avatars1.githubusercontent.com/u/38361244?v=4" width="100px;" alt=""/><br /><sub><b>LeSeulArtichaut</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=LeSeulArtichaut" title="Code">💻</a></td>
|
<td align="center"><a href="https://github.com/LeSeulArtichaut"><img src="https://avatars1.githubusercontent.com/u/38361244?v=4?s=100" width="100px;" alt=""/><br /><sub><b>LeSeulArtichaut</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=LeSeulArtichaut" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="https://github.com/CyrilTaylor"><img src="https://avatars0.githubusercontent.com/u/12631466?v=4" width="100px;" alt=""/><br /><sub><b>Cyril Taylor</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=CyrilTaylor" title="Code">💻</a></td>
|
<td align="center"><a href="https://github.com/CyrilTaylor"><img src="https://avatars0.githubusercontent.com/u/12631466?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cyril Taylor</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=CyrilTaylor" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/nstefanou"><img src="https://avatars3.githubusercontent.com/u/51129173?v=4?s=100" width="100px;" alt=""/><br /><sub><b>nstefanou</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=nstefanou" title="Code">💻</a> <a href="#plugin-nstefanou" title="Plugin/utility libraries">🔌</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/orin220444"><img src="https://avatars3.githubusercontent.com/u/30747229?v=4?s=100" width="100px;" alt=""/><br /><sub><b>orin220444</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=orin220444" title="Code">💻</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center"><a href="https://github.com/Goobles"><img src="https://avatars3.githubusercontent.com/u/8776771?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Gobius Dolhain</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=Goobles" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/3l0w"><img src="https://avatars2.githubusercontent.com/u/37798980?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Gwilherm Folliot</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=3l0w" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/Dimitory"><img src="https://avatars0.githubusercontent.com/u/475955?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dmitry Pronin</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=dimitory" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/JonathanBeverley"><img src="https://avatars1.githubusercontent.com/u/20328966?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jonathan Beverley</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=JonathanBeverley" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/zend"><img src="https://avatars1.githubusercontent.com/u/25160?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Zenghai Liang</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=zend" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://about.me/matishadow"><img src="https://avatars0.githubusercontent.com/u/9083085?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mateusz Tracz</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=matishadow" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://zergpool.com"><img src="https://avatars3.githubusercontent.com/u/36234677?v=4?s=100" width="100px;" alt=""/><br /><sub><b>pinpin</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=pinpins" title="Code">💻</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center"><a href="https://github.com/TakuroOnoda"><img src="https://avatars0.githubusercontent.com/u/1407926?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Takuro Onoda</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=TakuroOnoda" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/frauhottelmann"><img src="https://avatars2.githubusercontent.com/u/902705?v=4?s=100" width="100px;" alt=""/><br /><sub><b>frauhottelmann</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=frauhottelmann" title="Code">💻</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<!-- markdownlint-enable -->
|
<!-- markdownlint-restore -->
|
||||||
<!-- prettier-ignore-end -->
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||||
|
|
||||||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
||||||
|
@@ -8,17 +8,15 @@ html
|
|||||||
window.nodeRequire = require
|
window.nodeRequire = require
|
||||||
script(src='./preload.js')
|
script(src='./preload.js')
|
||||||
script(src='./bundle.js', defer)
|
script(src='./bundle.js', defer)
|
||||||
style#custom-css
|
|
||||||
style.
|
style.
|
||||||
body { transition: 0.5s background; }
|
body { transition: 0.5s background; }
|
||||||
body
|
body
|
||||||
|
style#custom-css
|
||||||
app-root
|
app-root
|
||||||
.preload-logo
|
.preload-logo
|
||||||
div
|
div
|
||||||
.terminus-logo
|
.terminus-logo
|
||||||
h1.terminus-title Terminus
|
h1.terminus-title Terminus
|
||||||
sup α
|
sup α
|
||||||
.progress
|
.progress
|
||||||
.bar(style='width: 0%')
|
.bar(style='width: 0%')
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,15 +1,33 @@
|
|||||||
import { app, ipcMain, Menu, Tray, shell } from 'electron'
|
import { app, ipcMain, Menu, Tray, shell, screen, globalShortcut, MenuItemConstructorOptions } from 'electron'
|
||||||
import * as electron from 'electron'
|
import * as promiseIpc from 'electron-promise-ipc'
|
||||||
import { loadConfig } from './config'
|
import { loadConfig } from './config'
|
||||||
import { Window, WindowOptions } from './window'
|
import { Window, WindowOptions } from './window'
|
||||||
|
import { pluginManager } from './pluginManager'
|
||||||
|
|
||||||
export class Application {
|
export class Application {
|
||||||
private tray: Tray
|
private tray?: Tray
|
||||||
private windows: Window[] = []
|
private windows: Window[] = []
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
ipcMain.on('app:config-change', () => {
|
ipcMain.on('app:config-change', (_event, config) => {
|
||||||
this.broadcast('host:config-change')
|
this.broadcast('host:config-change', config)
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('app:register-global-hotkey', (_event, specs) => {
|
||||||
|
globalShortcut.unregisterAll()
|
||||||
|
for (const spec of specs) {
|
||||||
|
globalShortcut.register(spec, () => {
|
||||||
|
this.onGlobalHotkey()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
;(promiseIpc as any).on('plugin-manager:install', (path, name, version) => {
|
||||||
|
return pluginManager.install(path, name, version)
|
||||||
|
})
|
||||||
|
|
||||||
|
;(promiseIpc as any).on('plugin-manager:uninstall', (path, name) => {
|
||||||
|
return pluginManager.uninstall(path, name)
|
||||||
})
|
})
|
||||||
|
|
||||||
const configData = loadConfig()
|
const configData = loadConfig()
|
||||||
@@ -23,18 +41,21 @@ export class Application {
|
|||||||
|
|
||||||
app.commandLine.appendSwitch('disable-http-cache')
|
app.commandLine.appendSwitch('disable-http-cache')
|
||||||
app.commandLine.appendSwitch('lang', 'EN')
|
app.commandLine.appendSwitch('lang', 'EN')
|
||||||
|
app.allowRendererProcessReuse = false
|
||||||
|
|
||||||
for (const flag of configData.flags || [['force_discrete_gpu', '0']]) {
|
for (const flag of configData.flags || [['force_discrete_gpu', '0']]) {
|
||||||
app.commandLine.appendSwitch(flag[0], flag[1])
|
app.commandLine.appendSwitch(flag[0], flag[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init () {
|
init (): void {
|
||||||
electron.screen.on('display-metrics-changed', () => this.broadcast('host:display-metrics-changed'))
|
screen.on('display-metrics-changed', () => this.broadcast('host:display-metrics-changed'))
|
||||||
|
screen.on('display-added', () => this.broadcast('host:displays-changed'))
|
||||||
|
screen.on('display-removed', () => this.broadcast('host:displays-changed'))
|
||||||
}
|
}
|
||||||
|
|
||||||
async newWindow (options?: WindowOptions): Promise<Window> {
|
async newWindow (options?: WindowOptions): Promise<Window> {
|
||||||
let window = new Window(options)
|
const window = new Window(options)
|
||||||
this.windows.push(window)
|
this.windows.push(window)
|
||||||
window.visible$.subscribe(visible => {
|
window.visible$.subscribe(visible => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
@@ -43,6 +64,9 @@ export class Application {
|
|||||||
this.enableTray()
|
this.enableTray()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
window.closed$.subscribe(() => {
|
||||||
|
this.windows = this.windows.filter(x => x !== window)
|
||||||
|
})
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
this.setupMenu()
|
this.setupMenu()
|
||||||
}
|
}
|
||||||
@@ -50,20 +74,38 @@ export class Application {
|
|||||||
return window
|
return window
|
||||||
}
|
}
|
||||||
|
|
||||||
broadcast (event, ...args) {
|
onGlobalHotkey (): void {
|
||||||
for (let window of this.windows) {
|
if (this.windows.some(x => x.isFocused() && x.isVisible())) {
|
||||||
|
for (const window of this.windows) {
|
||||||
|
window.hide()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const window of this.windows) {
|
||||||
|
window.present()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
presentAllWindows (): void {
|
||||||
|
for (const window of this.windows) {
|
||||||
|
window.present()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcast (event: string, ...args: any[]): void {
|
||||||
|
for (const window of this.windows) {
|
||||||
window.send(event, ...args)
|
window.send(event, ...args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async send (event, ...args) {
|
async send (event: string, ...args: any[]): Promise<void> {
|
||||||
if (!this.hasWindows()) {
|
if (!this.hasWindows()) {
|
||||||
await this.newWindow()
|
await this.newWindow()
|
||||||
}
|
}
|
||||||
this.windows.filter(w => !w.isDestroyed())[0].send(event, ...args)
|
this.windows.filter(w => !w.isDestroyed())[0].send(event, ...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
enableTray () {
|
enableTray (): void {
|
||||||
if (this.tray) {
|
if (this.tray) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -74,7 +116,7 @@ export class Application {
|
|||||||
this.tray = new Tray(`${app.getAppPath()}/assets/tray.png`)
|
this.tray = new Tray(`${app.getAppPath()}/assets/tray.png`)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.tray.on('click', () => setTimeout(() => this.focus()));
|
this.tray.on('click', () => setTimeout(() => this.focus()))
|
||||||
|
|
||||||
const contextMenu = Menu.buildFromTemplate([{
|
const contextMenu = Menu.buildFromTemplate([{
|
||||||
label: 'Show',
|
label: 'Show',
|
||||||
@@ -88,25 +130,28 @@ export class Application {
|
|||||||
this.tray.setToolTip(`Terminus ${app.getVersion()}`)
|
this.tray.setToolTip(`Terminus ${app.getVersion()}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
disableTray () {
|
disableTray (): void {
|
||||||
if (this.tray) {
|
this.tray?.destroy()
|
||||||
this.tray.destroy()
|
this.tray = null
|
||||||
this.tray = null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hasWindows () {
|
hasWindows (): boolean {
|
||||||
return !!this.windows.length
|
return !!this.windows.length
|
||||||
}
|
}
|
||||||
|
|
||||||
focus () {
|
focus (): void {
|
||||||
for (let window of this.windows) {
|
for (const window of this.windows) {
|
||||||
window.show()
|
window.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleSecondInstance (argv: string[], cwd: string): void {
|
||||||
|
this.presentAllWindows()
|
||||||
|
this.windows[this.windows.length - 1].passCliArguments(argv, cwd, true)
|
||||||
|
}
|
||||||
|
|
||||||
private setupMenu () {
|
private setupMenu () {
|
||||||
let template: Electron.MenuItemConstructorOptions[] = [
|
const template: MenuItemConstructorOptions[] = [
|
||||||
{
|
{
|
||||||
label: 'Application',
|
label: 'Application',
|
||||||
submenu: [
|
submenu: [
|
||||||
@@ -185,7 +230,7 @@ export class Application {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
|
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
import { app } from 'electron'
|
import { app } from 'electron'
|
||||||
|
|
||||||
export function parseArgs (argv, cwd) {
|
export function parseArgs (argv: string[], cwd: string): any {
|
||||||
if (argv[0].includes('node')) {
|
if (argv[0].includes('node')) {
|
||||||
argv = argv.slice(1)
|
argv = argv.slice(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
return require('yargs')
|
return require('yargs/yargs')(argv.slice(1))
|
||||||
.usage('terminus [command] [arguments]')
|
.usage('terminus [command] [arguments]')
|
||||||
.command('open [directory]', 'open a shell in a directory', {
|
.command('open [directory]', 'open a shell in a directory', {
|
||||||
directory: { type: 'string', 'default': cwd },
|
directory: { type: 'string', 'default': cwd },
|
||||||
@@ -20,26 +20,26 @@ export function parseArgs (argv, cwd) {
|
|||||||
return yargs.option('escape', {
|
return yargs.option('escape', {
|
||||||
alias: 'e',
|
alias: 'e',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
describe: 'Perform shell escaping'
|
describe: 'Perform shell escaping',
|
||||||
}).positional('text', {
|
}).positional('text', {
|
||||||
type: 'string'
|
type: 'string',
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.version('version', '', app.getVersion())
|
.version('version', '', app.getVersion())
|
||||||
.option('debug', {
|
.option('debug', {
|
||||||
alias: 'd',
|
alias: 'd',
|
||||||
describe: 'Show DevTools on start',
|
describe: 'Show DevTools on start',
|
||||||
type: 'boolean'
|
type: 'boolean',
|
||||||
})
|
})
|
||||||
.option('hidden', {
|
.option('hidden', {
|
||||||
describe: 'Start minimized',
|
describe: 'Start minimized',
|
||||||
type: 'boolean'
|
type: 'boolean',
|
||||||
})
|
})
|
||||||
.option('version', {
|
.option('version', {
|
||||||
alias: 'v',
|
alias: 'v',
|
||||||
describe: 'Show version and exit',
|
describe: 'Show version and exit',
|
||||||
type: 'boolean'
|
type: 'boolean',
|
||||||
})
|
})
|
||||||
.help('help')
|
.help('help')
|
||||||
.parse(argv.slice(1))
|
.parse()
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,7 @@ import * as yaml from 'js-yaml'
|
|||||||
import { app } from 'electron'
|
import { app } from 'electron'
|
||||||
|
|
||||||
export function loadConfig (): any {
|
export function loadConfig (): any {
|
||||||
let configPath = path.join(app.getPath('userData'), 'config.yaml')
|
const configPath = path.join(app.getPath('userData'), 'config.yaml')
|
||||||
if (fs.existsSync(configPath)) {
|
if (fs.existsSync(configPath)) {
|
||||||
return yaml.safeLoad(fs.readFileSync(configPath, 'utf8'))
|
return yaml.safeLoad(fs.readFileSync(configPath, 'utf8'))
|
||||||
} else {
|
} else {
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
|
import './portable'
|
||||||
|
import 'source-map-support/register'
|
||||||
import './sentry'
|
import './sentry'
|
||||||
import './lru'
|
import './lru'
|
||||||
import { app, ipcMain, Menu } from 'electron'
|
import { app, ipcMain, Menu } from 'electron'
|
||||||
import { parseArgs } from './cli'
|
import { parseArgs } from './cli'
|
||||||
import { Application } from './app'
|
import { Application } from './app'
|
||||||
import electronDebug = require('electron-debug')
|
import electronDebug = require('electron-debug')
|
||||||
import * as path from 'path'
|
|
||||||
import * as fs from 'fs'
|
|
||||||
|
|
||||||
if (!process.env.TERMINUS_PLUGINS) {
|
if (!process.env.TERMINUS_PLUGINS) {
|
||||||
process.env.TERMINUS_PLUGINS = ''
|
process.env.TERMINUS_PLUGINS = ''
|
||||||
@@ -13,14 +13,6 @@ if (!process.env.TERMINUS_PLUGINS) {
|
|||||||
|
|
||||||
const application = new Application()
|
const application = new Application()
|
||||||
|
|
||||||
if (process.env.PORTABLE_EXECUTABLE_DIR) {
|
|
||||||
const portableData = path.join(process.env.PORTABLE_EXECUTABLE_DIR, 'terminus-data')
|
|
||||||
if (!fs.existsSync(portableData)) {
|
|
||||||
fs.mkdirSync(portableData)
|
|
||||||
}
|
|
||||||
app.setPath('userData', portableData)
|
|
||||||
}
|
|
||||||
|
|
||||||
ipcMain.on('app:new-window', () => {
|
ipcMain.on('app:new-window', () => {
|
||||||
application.newWindow()
|
application.newWindow()
|
||||||
})
|
})
|
||||||
@@ -43,7 +35,7 @@ process.on('uncaughtException' as any, err => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
app.on('second-instance', (_event, argv, cwd) => {
|
app.on('second-instance', (_event, argv, cwd) => {
|
||||||
application.send('host:second-instance', parseArgs(argv, cwd), cwd)
|
application.handleSecondInstance(argv, cwd)
|
||||||
})
|
})
|
||||||
|
|
||||||
const argv = parseArgs(process.argv, process.cwd())
|
const argv = parseArgs(process.argv, process.cwd())
|
||||||
@@ -61,17 +53,20 @@ if (argv.d) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
app.on('ready', () => {
|
app.on('ready', async () => {
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
app.dock.setMenu(Menu.buildFromTemplate([
|
app.dock.setMenu(Menu.buildFromTemplate([
|
||||||
{
|
{
|
||||||
label: 'New window',
|
label: 'New window',
|
||||||
click () {
|
click () {
|
||||||
this.app.newWindow()
|
this.app.newWindow()
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
application.init()
|
application.init()
|
||||||
application.newWindow({ hidden: argv.hidden })
|
|
||||||
|
const window = await application.newWindow({ hidden: argv.hidden })
|
||||||
|
await window.ready
|
||||||
|
window.passCliArguments(process.argv, process.cwd(), false)
|
||||||
})
|
})
|
||||||
|
@@ -1,13 +1,15 @@
|
|||||||
let lru = require('lru-cache')({ max: 256, maxAge: 250 })
|
import * as LRU from 'lru-cache'
|
||||||
|
import * as fs from 'fs'
|
||||||
let fs = require('fs')
|
const lru = new LRU({ max: 256, maxAge: 250 })
|
||||||
let origLstat = fs.realpathSync.bind(fs)
|
const origLstat = fs.realpathSync.bind(fs)
|
||||||
|
|
||||||
// NB: The biggest offender of thrashing realpathSync is the node module system
|
// NB: The biggest offender of thrashing realpathSync is the node module system
|
||||||
// itself, which we can't get into via any sane means.
|
// itself, which we can't get into via any sane means.
|
||||||
require('fs').realpathSync = function (p) {
|
require('fs').realpathSync = function (p) {
|
||||||
let r = lru.get(p)
|
let r = lru.get(p)
|
||||||
if (r) return r
|
if (r) {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
r = origLstat(p)
|
r = origLstat(p)
|
||||||
lru.set(p, r)
|
lru.set(p, r)
|
||||||
|
40
app/lib/pluginManager.ts
Normal file
40
app/lib/pluginManager.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { promisify } from 'util'
|
||||||
|
|
||||||
|
|
||||||
|
export class PluginManager {
|
||||||
|
npm: any
|
||||||
|
npmReady?: Promise<void>
|
||||||
|
|
||||||
|
async ensureLoaded (): Promise<void> {
|
||||||
|
if (!this.npmReady) {
|
||||||
|
this.npmReady = new Promise(resolve => {
|
||||||
|
const npm = require('npm')
|
||||||
|
npm.load(err => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
npm.config.set('global', false)
|
||||||
|
this.npm = npm
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return this.npmReady
|
||||||
|
}
|
||||||
|
|
||||||
|
async install (path: string, name: string, version: string): Promise<void> {
|
||||||
|
await this.ensureLoaded()
|
||||||
|
this.npm.prefix = path
|
||||||
|
return promisify(this.npm.commands.install)([`${name}@${version}`])
|
||||||
|
}
|
||||||
|
|
||||||
|
async uninstall (path: string, name: string): Promise<void> {
|
||||||
|
await this.ensureLoaded()
|
||||||
|
this.npm.prefix = path
|
||||||
|
return promisify(this.npm.commands.remove)([name])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const pluginManager = new PluginManager()
|
22
app/lib/portable.ts
Executable file
22
app/lib/portable.ts
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
import * as path from 'path'
|
||||||
|
import * as fs from 'fs'
|
||||||
|
|
||||||
|
let appPath: string | null = null
|
||||||
|
try {
|
||||||
|
appPath = path.dirname(require('electron').app.getPath('exe'))
|
||||||
|
} catch {
|
||||||
|
appPath = path.dirname(require('electron').remote.app.getPath('exe'))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs.existsSync(path.join(appPath, 'terminus-data'))) {
|
||||||
|
fs.renameSync(path.join(appPath, 'terminus-data'), path.join(appPath, 'data'))
|
||||||
|
}
|
||||||
|
const portableData = path.join(appPath, 'data')
|
||||||
|
if (fs.existsSync(portableData)) {
|
||||||
|
console.log('reset user data to ' + portableData)
|
||||||
|
try {
|
||||||
|
require('electron').app.setPath('userData', portableData)
|
||||||
|
} catch {
|
||||||
|
require('electron').remote.app.setPath('userData', portableData)
|
||||||
|
}
|
||||||
|
}
|
4
app/lib/sentry.ts
Normal file → Executable file
4
app/lib/sentry.ts
Normal file → Executable file
@@ -1,9 +1,9 @@
|
|||||||
const { init } = process.type === 'main' ? require('@sentry/electron/dist/main') : require('@sentry/electron/dist/renderer')
|
const { init } = String(process.type) === 'main' ? require('@sentry/electron/dist/main') : require('@sentry/electron/dist/renderer')
|
||||||
import * as isDev from 'electron-is-dev'
|
import * as isDev from 'electron-is-dev'
|
||||||
|
|
||||||
|
|
||||||
const SENTRY_DSN = 'https://4717a0a7ee0b4429bd3a0f06c3d7eec3@sentry.io/181876'
|
const SENTRY_DSN = 'https://4717a0a7ee0b4429bd3a0f06c3d7eec3@sentry.io/181876'
|
||||||
let release
|
let release = null
|
||||||
try {
|
try {
|
||||||
release = require('electron').app.getVersion()
|
release = require('electron').app.getVersion()
|
||||||
} catch {
|
} catch {
|
||||||
|
@@ -1,18 +1,19 @@
|
|||||||
|
import * as glasstron from 'glasstron'
|
||||||
|
|
||||||
import { Subject, Observable } from 'rxjs'
|
import { Subject, Observable } from 'rxjs'
|
||||||
import { debounceTime } from 'rxjs/operators'
|
import { debounceTime } from 'rxjs/operators'
|
||||||
import { BrowserWindow, app, ipcMain, Rectangle, screen } from 'electron'
|
import { BrowserWindow, app, ipcMain, Rectangle, Menu, screen, BrowserWindowConstructorOptions } from 'electron'
|
||||||
import ElectronConfig = require('electron-config')
|
import ElectronConfig = require('electron-config')
|
||||||
import * as os from 'os'
|
import * as os from 'os'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
|
import macOSRelease from 'macos-release'
|
||||||
|
import * as compareVersions from 'compare-versions'
|
||||||
|
|
||||||
|
import { parseArgs } from './cli'
|
||||||
import { loadConfig } from './config'
|
import { loadConfig } from './config'
|
||||||
|
|
||||||
let SetWindowCompositionAttribute: any
|
let DwmEnableBlurBehindWindow: any = null
|
||||||
let AccentState: any
|
|
||||||
let DwmEnableBlurBehindWindow: any
|
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
SetWindowCompositionAttribute = require('windows-swca').SetWindowCompositionAttribute
|
|
||||||
AccentState = require('windows-swca').ACCENT_STATE
|
|
||||||
DwmEnableBlurBehindWindow = require('windows-blurbehind').DwmEnableBlurBehindWindow
|
DwmEnableBlurBehindWindow = require('windows-blurbehind').DwmEnableBlurBehindWindow
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,26 +21,38 @@ export interface WindowOptions {
|
|||||||
hidden?: boolean
|
hidden?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class GlasstronWindow extends BrowserWindow {
|
||||||
|
blurType: string
|
||||||
|
abstract setBlur (_: boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
const macOSVibrancyType = process.platform === 'darwin' ? compareVersions.compare(macOSRelease().version, '10.14', '>=') ? 'fullscreen-ui' : 'dark' : null
|
||||||
|
|
||||||
export class Window {
|
export class Window {
|
||||||
ready: Promise<void>
|
ready: Promise<void>
|
||||||
private visible = new Subject<boolean>()
|
private visible = new Subject<boolean>()
|
||||||
private window: BrowserWindow
|
private closed = new Subject<void>()
|
||||||
|
private window?: GlasstronWindow
|
||||||
private windowConfig: ElectronConfig
|
private windowConfig: ElectronConfig
|
||||||
private windowBounds: Rectangle
|
private windowBounds?: Rectangle
|
||||||
private closing = false
|
private closing = false
|
||||||
|
private lastVibrancy: {enabled: boolean, type?: string} | null = null
|
||||||
|
private disableVibrancyWhileDragging = false
|
||||||
|
private configStore: any
|
||||||
|
|
||||||
get visible$ (): Observable<boolean> { return this.visible }
|
get visible$ (): Observable<boolean> { return this.visible }
|
||||||
|
get closed$ (): Observable<void> { return this.closed }
|
||||||
|
|
||||||
constructor (options?: WindowOptions) {
|
constructor (options?: WindowOptions) {
|
||||||
let configData = loadConfig()
|
this.configStore = loadConfig()
|
||||||
|
|
||||||
options = options || {}
|
options = options ?? {}
|
||||||
|
|
||||||
this.windowConfig = new ElectronConfig({ name: 'window' })
|
this.windowConfig = new ElectronConfig({ name: 'window' })
|
||||||
this.windowBounds = this.windowConfig.get('windowBoundaries')
|
this.windowBounds = this.windowConfig.get('windowBoundaries')
|
||||||
|
|
||||||
let maximized = this.windowConfig.get('maximized')
|
const maximized = this.windowConfig.get('maximized')
|
||||||
let bwOptions: Electron.BrowserWindowConstructorOptions = {
|
const bwOptions: BrowserWindowConstructorOptions = {
|
||||||
width: 800,
|
width: 800,
|
||||||
height: 600,
|
height: 600,
|
||||||
title: 'Terminus',
|
title: 'Terminus',
|
||||||
@@ -48,7 +61,11 @@ export class Window {
|
|||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: true,
|
nodeIntegration: true,
|
||||||
preload: path.join(__dirname, 'sentry.js'),
|
preload: path.join(__dirname, 'sentry.js'),
|
||||||
|
backgroundThrottling: false,
|
||||||
|
enableRemoteModule: true,
|
||||||
|
contextIsolation: false,
|
||||||
},
|
},
|
||||||
|
maximizable: true,
|
||||||
frame: false,
|
frame: false,
|
||||||
show: false,
|
show: false,
|
||||||
backgroundColor: '#00000000',
|
backgroundColor: '#00000000',
|
||||||
@@ -56,18 +73,18 @@ export class Window {
|
|||||||
|
|
||||||
if (this.windowBounds) {
|
if (this.windowBounds) {
|
||||||
Object.assign(bwOptions, this.windowBounds)
|
Object.assign(bwOptions, this.windowBounds)
|
||||||
const closestDisplay = screen.getDisplayNearestPoint( {x: this.windowBounds.x, y: this.windowBounds.y} )
|
const closestDisplay = screen.getDisplayNearestPoint( { x: this.windowBounds.x, y: this.windowBounds.y } )
|
||||||
|
|
||||||
const [left1, top1, right1, bottom1] = [this.windowBounds.x, this.windowBounds.y, this.windowBounds.x + this.windowBounds.width, this.windowBounds.y + this.windowBounds.height];
|
const [left1, top1, right1, bottom1] = [this.windowBounds.x, this.windowBounds.y, this.windowBounds.x + this.windowBounds.width, this.windowBounds.y + this.windowBounds.height]
|
||||||
const [left2, top2, right2, bottom2] = [closestDisplay.bounds.x, closestDisplay.bounds.y, closestDisplay.bounds.x + closestDisplay.bounds.width, closestDisplay.bounds.y + closestDisplay.bounds.height];
|
const [left2, top2, right2, bottom2] = [closestDisplay.bounds.x, closestDisplay.bounds.y, closestDisplay.bounds.x + closestDisplay.bounds.width, closestDisplay.bounds.y + closestDisplay.bounds.height]
|
||||||
|
|
||||||
if ((left2 > right1 || right2 < left1 || top2 > bottom1 || bottom2 < top1) && !maximized) {
|
if ((left2 > right1 || right2 < left1 || top2 > bottom1 || bottom2 < top1) && !maximized) {
|
||||||
bwOptions.x = closestDisplay.bounds.width / 2 - bwOptions.width / 2;
|
bwOptions.x = closestDisplay.bounds.width / 2 - bwOptions.width / 2
|
||||||
bwOptions.y = closestDisplay.bounds.height / 2 - bwOptions.height / 2;
|
bwOptions.y = closestDisplay.bounds.height / 2 - bwOptions.height / 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((configData.appearance || {}).frame === 'native') {
|
if ((this.configStore.appearance || {}).frame === 'native') {
|
||||||
bwOptions.frame = true
|
bwOptions.frame = true
|
||||||
} else {
|
} else {
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
@@ -75,15 +92,16 @@ export class Window {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.platform === 'linux') {
|
if (process.platform === 'darwin') {
|
||||||
bwOptions.backgroundColor = '#131d27'
|
this.window = new BrowserWindow(bwOptions) as GlasstronWindow
|
||||||
|
} else {
|
||||||
|
this.window = new glasstron.BrowserWindow(bwOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.window = new BrowserWindow(bwOptions)
|
|
||||||
this.window.once('ready-to-show', () => {
|
this.window.once('ready-to-show', () => {
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
this.window.setVibrancy('window')
|
this.window.setVibrancy(macOSVibrancyType)
|
||||||
} else if (process.platform === 'win32' && (configData.appearance || {}).vibrancy) {
|
} else if (process.platform === 'win32' && (this.configStore.appearance || {}).vibrancy) {
|
||||||
this.setVibrancy(true)
|
this.setVibrancy(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,6 +112,13 @@ export class Window {
|
|||||||
this.window.show()
|
this.window.show()
|
||||||
}
|
}
|
||||||
this.window.focus()
|
this.window.focus()
|
||||||
|
this.window.moveTop()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.window.on('blur', () => {
|
||||||
|
if (this.configStore.appearance?.dockHideOnBlur) {
|
||||||
|
this.hide()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -116,41 +141,97 @@ export class Window {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setVibrancy (enabled: boolean, type?: string) {
|
setVibrancy (enabled: boolean, type?: string, userRequested?: boolean): void {
|
||||||
|
if (userRequested ?? true) {
|
||||||
|
this.lastVibrancy = { enabled, type }
|
||||||
|
}
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
if (parseFloat(os.release()) >= 10) {
|
if (parseFloat(os.release()) >= 10) {
|
||||||
let attribValue = AccentState.ACCENT_DISABLED
|
this.window.blurType = enabled ? type === 'fluent' ? 'acrylic' : 'blurbehind' : null
|
||||||
if (enabled) {
|
try {
|
||||||
if (parseInt(os.release().split('.')[2]) >= 17063 && type === 'fluent') {
|
this.window.setBlur(enabled)
|
||||||
attribValue = AccentState.ACCENT_ENABLE_ACRYLICBLURBEHIND
|
} catch (error) {
|
||||||
} else {
|
console.error('Failed to set window blur', error)
|
||||||
attribValue = AccentState.ACCENT_ENABLE_BLURBEHIND
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
SetWindowCompositionAttribute(this.window.getNativeWindowHandle(), attribValue, 0x00000000)
|
|
||||||
} else {
|
} else {
|
||||||
DwmEnableBlurBehindWindow(this.window, enabled)
|
DwmEnableBlurBehindWindow(this.window, enabled)
|
||||||
}
|
}
|
||||||
|
} else if (process.platform === 'linux') {
|
||||||
|
this.window.setBackgroundColor(enabled ? '#00000000' : '#131d27')
|
||||||
|
this.window.setBlur(enabled)
|
||||||
|
} else {
|
||||||
|
this.window.setVibrancy(enabled ? macOSVibrancyType : null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
show () {
|
show (): void {
|
||||||
this.window.show()
|
this.window.show()
|
||||||
|
this.window.moveTop()
|
||||||
}
|
}
|
||||||
|
|
||||||
focus () {
|
focus (): void {
|
||||||
this.window.focus()
|
this.window.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
send (event, ...args) {
|
send (event: string, ...args: any[]): void {
|
||||||
if (!this.window) {
|
if (!this.window) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.window.webContents.send(event, ...args)
|
this.window.webContents.send(event, ...args)
|
||||||
|
if (event === 'host:config-change') {
|
||||||
|
this.configStore = args[0]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isDestroyed () {
|
isDestroyed (): boolean {
|
||||||
return !this.window || this.window.isDestroyed();
|
return !this.window || this.window.isDestroyed()
|
||||||
|
}
|
||||||
|
|
||||||
|
isFocused (): boolean {
|
||||||
|
return this.window.isFocused()
|
||||||
|
}
|
||||||
|
|
||||||
|
isVisible (): boolean {
|
||||||
|
return this.window.isVisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
hide (): void {
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
// Lose focus
|
||||||
|
Menu.sendActionToFirstResponder('hide:')
|
||||||
|
}
|
||||||
|
this.window.blur()
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
this.window.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
present (): void {
|
||||||
|
if (!this.window.isVisible()) {
|
||||||
|
// unfocused, invisible
|
||||||
|
this.window.show()
|
||||||
|
this.window.focus()
|
||||||
|
} else {
|
||||||
|
if (!this.configStore.appearance?.dock || this.configStore.appearance?.dock === 'off') {
|
||||||
|
// not docked, visible
|
||||||
|
setTimeout(() => {
|
||||||
|
this.window.show()
|
||||||
|
this.window.focus()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if (this.configStore.appearance?.dockAlwaysOnTop) {
|
||||||
|
// docked, visible, on top
|
||||||
|
this.window.hide()
|
||||||
|
} else {
|
||||||
|
// docked, visible, not on top
|
||||||
|
this.window.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
passCliArguments (argv: string[], cwd: string, secondInstance: boolean): void {
|
||||||
|
this.send('cli', parseArgs(argv, cwd), cwd, secondInstance)
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupWindowManagement () {
|
private setupWindowManagement () {
|
||||||
@@ -163,7 +244,7 @@ export class Window {
|
|||||||
this.visible.next(false)
|
this.visible.next(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
let moveSubscription = new Observable<void>(observer => {
|
const moveSubscription = new Observable<void>(observer => {
|
||||||
this.window.on('move', () => observer.next())
|
this.window.on('move', () => observer.next())
|
||||||
}).pipe(debounceTime(250)).subscribe(() => {
|
}).pipe(debounceTime(250)).subscribe(() => {
|
||||||
this.send('host:window-moved')
|
this.send('host:window-moved')
|
||||||
@@ -293,10 +374,32 @@ export class Window {
|
|||||||
})
|
})
|
||||||
|
|
||||||
this.window.webContents.on('new-window', event => event.preventDefault())
|
this.window.webContents.on('new-window', event => event.preventDefault())
|
||||||
|
|
||||||
|
ipcMain.on('window-set-disable-vibrancy-while-dragging', (_event, value) => {
|
||||||
|
this.disableVibrancyWhileDragging = value
|
||||||
|
})
|
||||||
|
|
||||||
|
let moveEndedTimeout: number|null = null
|
||||||
|
const onBoundsChange = () => {
|
||||||
|
if (!this.lastVibrancy?.enabled || !this.disableVibrancyWhileDragging) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.setVibrancy(false, undefined, false)
|
||||||
|
if (moveEndedTimeout) {
|
||||||
|
clearTimeout(moveEndedTimeout)
|
||||||
|
}
|
||||||
|
moveEndedTimeout = setTimeout(() => {
|
||||||
|
this.setVibrancy(this.lastVibrancy.enabled, this.lastVibrancy.type)
|
||||||
|
}, 50)
|
||||||
|
}
|
||||||
|
this.window.on('move', onBoundsChange)
|
||||||
|
this.window.on('resize', onBoundsChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
private destroy () {
|
private destroy () {
|
||||||
this.window = null
|
this.window = null
|
||||||
|
this.closed.next()
|
||||||
this.visible.complete()
|
this.visible.complete()
|
||||||
|
this.closed.complete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -13,42 +13,51 @@
|
|||||||
"watch": "webpack --progress --color --watch"
|
"watch": "webpack --progress --color --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "7.2.8",
|
"@angular/animations": "^9.1.9",
|
||||||
"@angular/common": "7.2.8",
|
"@angular/common": "^9.1.11",
|
||||||
"@angular/compiler": "7.2.8",
|
"@angular/compiler": "^9.1.9",
|
||||||
"@angular/core": "7.2.8",
|
"@angular/core": "^9.1.9",
|
||||||
"@angular/forms": "7.2.8",
|
"@angular/forms": "^9.1.11",
|
||||||
"@angular/platform-browser": "7.2.8",
|
"@angular/platform-browser": "^9.1.9",
|
||||||
"@angular/platform-browser-dynamic": "7.2.8",
|
"@angular/platform-browser-dynamic": "^9.1.9",
|
||||||
"@ng-bootstrap/ng-bootstrap": "^4.2.2",
|
"@ng-bootstrap/ng-bootstrap": "^6.1.0",
|
||||||
"devtron": "1.4.0",
|
"@terminus-term/node-pty": "0.10.0-beta10",
|
||||||
"electron-config": "2.0.0",
|
"electron-config": "2.0.0",
|
||||||
"electron-debug": "^3.0.1",
|
"electron-debug": "^3.0.1",
|
||||||
"electron-is-dev": "1.1.0",
|
"electron-is-dev": "1.1.0",
|
||||||
"electron-updater": "^4.2.0",
|
"electron-promise-ipc": "^2.2.4",
|
||||||
"fontmanager-redux": "0.4.0",
|
"fontmanager-redux": "1.0.0",
|
||||||
"js-yaml": "3.13.1",
|
"glasstron": "0.0.6",
|
||||||
"keytar": "^5.0.0",
|
"js-yaml": "3.14.0",
|
||||||
|
"keytar": "^7.2.0",
|
||||||
"mz": "^2.7.0",
|
"mz": "^2.7.0",
|
||||||
"ngx-toastr": "^10.2.0",
|
"ngx-toastr": "^12.0.1",
|
||||||
"node-pty": "^0.10.0-beta2",
|
"npm": "6",
|
||||||
"npm": "6.9.0",
|
|
||||||
"path": "0.12.7",
|
"path": "0.12.7",
|
||||||
"rxjs": "^6.5.4",
|
"rxjs": "^6.5.5",
|
||||||
"rxjs-compat": "^6.5.4",
|
"yargs": "^15.4.1",
|
||||||
"yargs": "^15.1.0",
|
"zone.js": "^0.11.3"
|
||||||
"zone.js": "^0.8.29"
|
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"macos-native-processlist": "^1.0.2",
|
"macos-native-processlist": "^2.0.0",
|
||||||
|
"serialport": "^9.0.4",
|
||||||
"windows-blurbehind": "^1.0.1",
|
"windows-blurbehind": "^1.0.1",
|
||||||
"windows-native-registry": "^1.0.17",
|
"windows-native-registry": "^3.0.0",
|
||||||
"windows-process-tree": "^0.2.4",
|
"windows-process-tree": "^0.2.4"
|
||||||
"windows-swca": "^2.0.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/mz": "0.0.32",
|
"@types/mz": "0.0.32",
|
||||||
"@types/node": "12.7.12",
|
"@types/node": "14.14.14",
|
||||||
"node-abi": "^2.13.0"
|
"node-abi": "2.19.3",
|
||||||
|
"source-map-support": "^0.5.19"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"terminus-community-color-schemes": "*",
|
||||||
|
"terminus-core": "*",
|
||||||
|
"terminus-plugin-manager": "*",
|
||||||
|
"terminus-serial": "*",
|
||||||
|
"terminus-settings": "*",
|
||||||
|
"terminus-ssh": "*",
|
||||||
|
"terminus-terminal": "*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
import { BrowserModule } from '@angular/platform-browser'
|
import { BrowserModule } from '@angular/platform-browser'
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
@@ -7,7 +8,7 @@ export function getRootModule (plugins: any[]) {
|
|||||||
const imports = [
|
const imports = [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
...plugins,
|
...plugins,
|
||||||
NgbModule.forRoot(),
|
NgbModule,
|
||||||
ToastrModule.forRoot({
|
ToastrModule.forRoot({
|
||||||
positionClass: 'toast-bottom-center',
|
positionClass: 'toast-bottom-center',
|
||||||
toastClass: 'toast',
|
toastClass: 'toast',
|
||||||
|
@@ -3,6 +3,7 @@ import 'source-sans-pro/source-sans-pro.css'
|
|||||||
import 'source-code-pro/source-code-pro.css'
|
import 'source-code-pro/source-code-pro.css'
|
||||||
import '@fortawesome/fontawesome-free/css/solid.css'
|
import '@fortawesome/fontawesome-free/css/solid.css'
|
||||||
import '@fortawesome/fontawesome-free/css/brands.css'
|
import '@fortawesome/fontawesome-free/css/brands.css'
|
||||||
|
import '@fortawesome/fontawesome-free/css/regular.css'
|
||||||
import '@fortawesome/fontawesome-free/css/fontawesome.css'
|
import '@fortawesome/fontawesome-free/css/fontawesome.css'
|
||||||
import 'ngx-toastr/toastr.css'
|
import 'ngx-toastr/toastr.css'
|
||||||
import './preload.scss'
|
import './preload.scss'
|
||||||
|
@@ -58,8 +58,8 @@ findPlugins().then(async plugins => {
|
|||||||
window['safeModeReason'] = error
|
window['safeModeReason'] = error
|
||||||
try {
|
try {
|
||||||
await bootstrap(plugins, true)
|
await bootstrap(plugins, true)
|
||||||
} catch (error) {
|
} catch (error2) {
|
||||||
console.error('Bootstrap failed:', error)
|
console.error('Bootstrap failed:', error2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@@ -3,13 +3,13 @@ import * as path from 'path'
|
|||||||
const nodeModule = require('module') // eslint-disable-line @typescript-eslint/no-var-requires
|
const nodeModule = require('module') // eslint-disable-line @typescript-eslint/no-var-requires
|
||||||
const nodeRequire = (global as any).require
|
const nodeRequire = (global as any).require
|
||||||
|
|
||||||
function normalizePath (path: string): string {
|
function normalizePath (p: string): string {
|
||||||
const cygwinPrefix = '/cygdrive/'
|
const cygwinPrefix = '/cygdrive/'
|
||||||
if (path.startsWith(cygwinPrefix)) {
|
if (p.startsWith(cygwinPrefix)) {
|
||||||
path = path.substring(cygwinPrefix.length).replace('/', '\\')
|
p = p.substring(cygwinPrefix.length).replace('/', '\\')
|
||||||
path = path[0] + ':' + path.substring(1)
|
p = p[0] + ':' + p.substring(1)
|
||||||
}
|
}
|
||||||
return path
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
global['module'].paths.map((x: string) => nodeModule.globalPaths.push(normalizePath(x)))
|
global['module'].paths.map((x: string) => nodeModule.globalPaths.push(normalizePath(x)))
|
||||||
@@ -21,8 +21,7 @@ if (process.env.TERMINUS_DEV) {
|
|||||||
const builtinPluginsPath = process.env.TERMINUS_DEV ? path.dirname(require('electron').remote.app.getAppPath()) : path.join((process as any).resourcesPath, 'builtin-plugins')
|
const builtinPluginsPath = process.env.TERMINUS_DEV ? path.dirname(require('electron').remote.app.getAppPath()) : path.join((process as any).resourcesPath, 'builtin-plugins')
|
||||||
|
|
||||||
const userPluginsPath = path.join(
|
const userPluginsPath = path.join(
|
||||||
require('electron').remote.app.getPath('appData'),
|
require('electron').remote.app.getPath('userData'),
|
||||||
'terminus',
|
|
||||||
'plugins',
|
'plugins',
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -64,7 +63,6 @@ const builtinModules = [
|
|||||||
'ngx-toastr',
|
'ngx-toastr',
|
||||||
'rxjs',
|
'rxjs',
|
||||||
'rxjs/operators',
|
'rxjs/operators',
|
||||||
'rxjs-compat/Subject',
|
|
||||||
'terminus-core',
|
'terminus-core',
|
||||||
'terminus-settings',
|
'terminus-settings',
|
||||||
'terminus-terminal',
|
'terminus-terminal',
|
||||||
@@ -84,7 +82,7 @@ const originalRequire = (global as any).require
|
|||||||
if (cachedBuiltinModules[query]) {
|
if (cachedBuiltinModules[query]) {
|
||||||
return cachedBuiltinModules[query]
|
return cachedBuiltinModules[query]
|
||||||
}
|
}
|
||||||
return originalRequire.apply(this, arguments)
|
return originalRequire.apply(this, [query])
|
||||||
}
|
}
|
||||||
|
|
||||||
const originalModuleRequire = nodeModule.prototype.require
|
const originalModuleRequire = nodeModule.prototype.require
|
||||||
@@ -174,8 +172,8 @@ export async function loadPlugins (foundPlugins: PluginInfo[], progress: Progres
|
|||||||
console.time(label)
|
console.time(label)
|
||||||
const packageModule = nodeRequire(foundPlugin.path)
|
const packageModule = nodeRequire(foundPlugin.path)
|
||||||
const pluginModule = packageModule.default.forRoot ? packageModule.default.forRoot() : packageModule.default
|
const pluginModule = packageModule.default.forRoot ? packageModule.default.forRoot() : packageModule.default
|
||||||
pluginModule['pluginName'] = foundPlugin.name
|
pluginModule.pluginName = foundPlugin.name
|
||||||
pluginModule['bootstrap'] = packageModule.bootstrap
|
pluginModule.bootstrap = packageModule.bootstrap
|
||||||
plugins.push(pluginModule)
|
plugins.push(pluginModule)
|
||||||
console.timeEnd(label)
|
console.timeEnd(label)
|
||||||
await new Promise(x => setTimeout(x, 50))
|
await new Promise(x => setTimeout(x, 50))
|
||||||
|
@@ -9,6 +9,7 @@
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
background-image: none;
|
background-image: none;
|
||||||
width: auto;
|
width: auto;
|
||||||
|
flex-basis: auto;
|
||||||
|
|
||||||
&.toast-error {
|
&.toast-error {
|
||||||
background-color: #BD362F;
|
background-color: #BD362F;
|
||||||
|
@@ -12,7 +12,6 @@
|
|||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"noUnusedParameters": true,
|
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"lib": [
|
"lib": [
|
||||||
"dom",
|
"dom",
|
||||||
|
@@ -35,11 +35,15 @@ module.exports = {
|
|||||||
externals: {
|
externals: {
|
||||||
electron: 'commonjs electron',
|
electron: 'commonjs electron',
|
||||||
'electron-config': 'commonjs electron-config',
|
'electron-config': 'commonjs electron-config',
|
||||||
|
'electron-promise-ipc': 'commonjs electron-promise-ipc',
|
||||||
'electron-vibrancy': 'commonjs electron-vibrancy',
|
'electron-vibrancy': 'commonjs electron-vibrancy',
|
||||||
fs: 'commonjs fs',
|
fs: 'commonjs fs',
|
||||||
|
glasstron: 'commonjs glasstron',
|
||||||
mz: 'commonjs mz',
|
mz: 'commonjs mz',
|
||||||
|
npm: 'commonjs npm',
|
||||||
path: 'commonjs path',
|
path: 'commonjs path',
|
||||||
yargs: 'commonjs yargs',
|
util: 'commonjs util',
|
||||||
|
'source-map-support': 'commonjs source-map-support',
|
||||||
'windows-swca': 'commonjs windows-swca',
|
'windows-swca': 'commonjs windows-swca',
|
||||||
'windows-blurbehind': 'commonjs windows-blurbehind',
|
'windows-blurbehind': 'commonjs windows-blurbehind',
|
||||||
},
|
},
|
||||||
@@ -49,4 +53,6 @@ module.exports = {
|
|||||||
'process.type': '"main"',
|
'process.type': '"main"',
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
// Ignore warnings due to yarg's dynamic module loading
|
||||||
|
ignoreWarnings: [/node_modules\/yargs/],
|
||||||
}
|
}
|
||||||
|
2492
app/yarn.lock
2492
app/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ platform:
|
|||||||
- x64
|
- x64
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
nodejs_version: "10"
|
nodejs_version: "15"
|
||||||
|
|
||||||
version: "{build}"
|
version: "{build}"
|
||||||
|
|
||||||
|
16
build/mac/afterBuildHook.js
Normal file
16
build/mac/afterBuildHook.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
const fs = require('fs')
|
||||||
|
const signHook = require('./afterSignHook')
|
||||||
|
|
||||||
|
module.exports = async function (params) {
|
||||||
|
// notarize the app on Mac OS only.
|
||||||
|
if (process.platform !== 'darwin' || !process.env.GITHUB_REF || !process.env.GITHUB_REF.startsWith('refs/tags/')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log('afterBuild hook triggered')
|
||||||
|
|
||||||
|
let pkgName = fs.readdirSync('dist').find(x => x.endsWith('.pkg'))
|
||||||
|
signHook({
|
||||||
|
appOutDir: 'dist',
|
||||||
|
_pathOverride: pkgName,
|
||||||
|
})
|
||||||
|
}
|
@@ -6,14 +6,14 @@ const notarizer = require('electron-notarize')
|
|||||||
|
|
||||||
module.exports = async function (params) {
|
module.exports = async function (params) {
|
||||||
// notarize the app on Mac OS only.
|
// notarize the app on Mac OS only.
|
||||||
if (process.platform !== 'darwin' || process.env.GITHUB_REF !== 'refs/heads/master' || process.env.GITHUB_REF && !process.env.GITHUB_REF.startsWith('refs/tags/')) {
|
if (process.platform !== 'darwin' || !process.env.GITHUB_REF || !process.env.GITHUB_REF.startsWith('refs/tags/')) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.log('afterSign hook triggered', params)
|
console.log('afterSign hook triggered', params)
|
||||||
|
|
||||||
let appId = 'org.terminus'
|
let appId = 'org.terminus'
|
||||||
|
|
||||||
let appPath = path.join(params.appOutDir, `${params.packager.appInfo.productFilename}.app`)
|
let appPath = path.join(params.appOutDir, params._pathOverride || `${params.packager.appInfo.productFilename}.app`)
|
||||||
if (!fs.existsSync(appPath)) {
|
if (!fs.existsSync(appPath)) {
|
||||||
throw new Error(`Cannot find application at: ${appPath}`)
|
throw new Error(`Cannot find application at: ${appPath}`)
|
||||||
}
|
}
|
||||||
|
@@ -10,5 +10,9 @@
|
|||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.cs.disable-library-validation</key>
|
<key>com.apple.security.cs.disable-library-validation</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>com.apple.security.device.microphone</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.device.camera</key>
|
||||||
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
89
electron-builder.yml
Normal file
89
electron-builder.yml
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
---
|
||||||
|
appId: org.terminus
|
||||||
|
productName: Terminus
|
||||||
|
compression: normal
|
||||||
|
npmRebuild: false
|
||||||
|
afterSign: "./build/mac/afterSignHook.js"
|
||||||
|
afterAllArtifactBuild: "./build/mac/afterBuildHook.js"
|
||||||
|
files:
|
||||||
|
- '**/*'
|
||||||
|
- dist
|
||||||
|
- '!src'
|
||||||
|
- '!**/node_modules/*/{CHANGELOG.md,README.md,README,readme.md,readme}'
|
||||||
|
- '!**/node_modules/*/{test,__tests__,tests,powered-test,example,examples,docs}'
|
||||||
|
- '!**/node_modules/@angular/common/locales'
|
||||||
|
- '!**/node_modules/@angular/compiler/src'
|
||||||
|
- '!**/node_modules/node-gyp'
|
||||||
|
- '!**/node_modules/**/*.d.ts'
|
||||||
|
- '!**/node_modules/**/*.map'
|
||||||
|
- '!**/node_modules/**/include/node'
|
||||||
|
- '!**/node_modules/.bin'
|
||||||
|
- '!**/node_modules/*/*/{esm5,fesm5,esm2015,fesm2015,_esm2015,_fesm2015}'
|
||||||
|
- '!**/*.{woff,ttf,otf,eot}'
|
||||||
|
- '!**/*.{iml,o,hprof,orig,pyc,pyo,rbc,swp,csproj,sln,xproj}'
|
||||||
|
- '!.editorconfig'
|
||||||
|
- '!**/._*'
|
||||||
|
- '!**/{.DS_Store,.git,.hg,.svn,CVS,RCS,SCCS,.gitignore,.gitattributes}'
|
||||||
|
- '!**/{__pycache__,thumbs.db,.flowconfig,.idea,.vs,.nyc_output}'
|
||||||
|
- '!**/{appveyor.yml,.travis.yml,circle.yml}'
|
||||||
|
- '!**/{npm-debug.log,yarn.lock,.yarn-integrity,.yarn-metadata.json'
|
||||||
|
extraResources:
|
||||||
|
- builtin-plugins
|
||||||
|
- extras
|
||||||
|
publish:
|
||||||
|
- provider: github
|
||||||
|
|
||||||
|
win:
|
||||||
|
icon: "./build/windows/icon.ico"
|
||||||
|
artifactName: terminus-${version}-portable.${ext}
|
||||||
|
rfc3161TimeStampServer: http://sha256timestamp.ws.symantec.com/sha256/timestamp
|
||||||
|
nsis:
|
||||||
|
oneClick: false
|
||||||
|
artifactName: terminus-${version}-setup.${ext}
|
||||||
|
installerIcon: "./build/windows/icon.ico"
|
||||||
|
allowToChangeInstallationDirectory: true
|
||||||
|
|
||||||
|
mac:
|
||||||
|
category: public.app-category.video
|
||||||
|
icon: "./build/mac/icon.icns"
|
||||||
|
artifactName: terminus-${version}-macos-${env.ARCH}.${ext}
|
||||||
|
hardenedRuntime: true
|
||||||
|
entitlements: "./build/mac/entitlements.plist"
|
||||||
|
entitlementsInherit: "./build/mac/entitlements.plist"
|
||||||
|
extendInfo:
|
||||||
|
NSRequiresAquaSystemAppearance: false
|
||||||
|
NSCameraUsageDescription: "A subprocess requests access to the device's camera."
|
||||||
|
NSMicrophoneUsageDescription: "A subprocess requests access to the device's microphone."
|
||||||
|
NSLocationUsageDescription: "A subprocess requests access to the user's location information."
|
||||||
|
NSDesktopFolderUsageDescription: "A subprocess requests access to the user's Desktop folder."
|
||||||
|
NSDocumentsFolderUsageDescription: "A subprocess requests access to the user's Documents folder."
|
||||||
|
NSDownloadsFolderUsageDescription: "A subprocess requests access to the user's Downloads folder."
|
||||||
|
NSNetworkVolumesUsageDescription: 'A subprocess requests access to files on a network volume.'
|
||||||
|
NSRemovableVolumesUsageDescription: 'A subprocess requests access to files on a removable volume.'
|
||||||
|
|
||||||
|
linux:
|
||||||
|
category: Utilities
|
||||||
|
icon: "./build/icons"
|
||||||
|
artifactName: terminus-${version}-linux.${ext}
|
||||||
|
executableArgs:
|
||||||
|
- "--no-sandbox"
|
||||||
|
snap:
|
||||||
|
plugs:
|
||||||
|
- default
|
||||||
|
- system-files
|
||||||
|
- system-observe
|
||||||
|
deb:
|
||||||
|
depends:
|
||||||
|
- gconf2
|
||||||
|
- gconf-service
|
||||||
|
- gnome-keyring
|
||||||
|
- libnotify4
|
||||||
|
- libsecret-1-0
|
||||||
|
- libappindicator1
|
||||||
|
- libxtst6
|
||||||
|
- libnss3
|
||||||
|
afterInstall: build/linux/after-install.tpl
|
||||||
|
rpm:
|
||||||
|
depends:
|
||||||
|
- screen
|
||||||
|
- gnome-keyring
|
BIN
extras/UAC.exe
BIN
extras/UAC.exe
Binary file not shown.
BIN
extras/ssh-keygen/libcrypto.dll
Normal file
BIN
extras/ssh-keygen/libcrypto.dll
Normal file
Binary file not shown.
BIN
extras/ssh-keygen/ssh-keygen.exe
Normal file
BIN
extras/ssh-keygen/ssh-keygen.exe
Normal file
Binary file not shown.
163
package.json
163
package.json
@@ -1,143 +1,80 @@
|
|||||||
{
|
{
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^5.12.0",
|
"@fortawesome/fontawesome-free": "^5.13.0",
|
||||||
"@sentry/cli": "^1.49.0",
|
"@sentry/cli": "^1.61.0",
|
||||||
"@sentry/electron": "^1.0.0",
|
"@sentry/electron": "^2.0.4",
|
||||||
"@types/electron-config": "^3.2.2",
|
"@types/electron-config": "^3.2.2",
|
||||||
"@types/electron-debug": "^2.1.0",
|
"@types/electron-debug": "^2.1.0",
|
||||||
"@types/js-yaml": "^3.12.1",
|
"@types/fs-extra": "^8.1.1",
|
||||||
"@types/node": "12.7.12",
|
"@types/js-yaml": "^3.12.5",
|
||||||
"@types/webpack-env": "1.15.0",
|
"@types/node": "14.14.14",
|
||||||
"@typescript-eslint/eslint-plugin": "^2.13.0",
|
"@types/webpack-env": "^1.16.0",
|
||||||
"@typescript-eslint/parser": "^2.17.0",
|
"@typescript-eslint/eslint-plugin": "^4.11.0",
|
||||||
|
"@typescript-eslint/parser": "^4.11.0",
|
||||||
"apply-loader": "2.0.0",
|
"apply-loader": "2.0.0",
|
||||||
"awesome-typescript-loader": "^5.0.0",
|
"awesome-typescript-loader": "^5.2.1",
|
||||||
"core-js": "^3.6.4",
|
"compare-versions": "^3.6.0",
|
||||||
"cross-env": "6.0.3",
|
"core-js": "^3.8.1",
|
||||||
|
"cross-env": "7.0.2",
|
||||||
"css-loader": "3.4.2",
|
"css-loader": "3.4.2",
|
||||||
"electron": "^7.1.10",
|
"electron": "12.0.0-beta.16",
|
||||||
"electron-builder": "22.1.0",
|
"electron-builder": "22.10.4",
|
||||||
"electron-download": "^4.1.1",
|
"electron-download": "^4.1.1",
|
||||||
"electron-installer-snap": "^5.0.0",
|
"electron-installer-snap": "^5.1.0",
|
||||||
"electron-notarize": "^0.1.1",
|
"electron-notarize": "^1.0.0",
|
||||||
"electron-rebuild": "^1.9.0",
|
"electron-rebuild": "^2.3.4",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^7.6.0",
|
||||||
"eslint-plugin-import": "^2.20.0",
|
"eslint-plugin-import": "^2.21.1",
|
||||||
"file-loader": "^5.0.2",
|
"file-loader": "^5.1.0",
|
||||||
"graceful-fs": "^4.2.2",
|
"graceful-fs": "^4.2.4",
|
||||||
"html-loader": "0.5.5",
|
"html-loader": "0.5.5",
|
||||||
"json-loader": "0.5.7",
|
"json-loader": "0.5.7",
|
||||||
"node-abi": "^2.12.0",
|
"lru-cache": "^6.0.0",
|
||||||
"node-gyp": "^6.1.0",
|
"macos-release": "^2.4.1",
|
||||||
"node-sass": "^4.13.0",
|
"node-abi": "^2.19.3",
|
||||||
|
"node-gyp": "^7.1.2",
|
||||||
|
"node-sass": "^5.0.0",
|
||||||
"npmlog": "4.1.2",
|
"npmlog": "4.1.2",
|
||||||
"npx": "^10.2.0",
|
"npx": "^10.2.2",
|
||||||
"pug": "^2.0.4",
|
"pug": "^2.0.4",
|
||||||
"pug-html-loader": "1.1.5",
|
"pug-html-loader": "1.1.5",
|
||||||
"pug-lint": "^2.6.0",
|
"pug-lint": "^2.6.0",
|
||||||
"pug-loader": "^2.4.0",
|
"pug-loader": "^2.4.0",
|
||||||
"pug-static-loader": "2.0.0",
|
"pug-static-loader": "2.0.0",
|
||||||
"raw-loader": "4.0.0",
|
"raw-loader": "4.0.1",
|
||||||
"sass-loader": "^8.0.0",
|
"sass-loader": "^10.1.0",
|
||||||
"shelljs": "0.8.3",
|
"shelljs": "0.8.4",
|
||||||
"source-code-pro": "^2.30.2",
|
"source-code-pro": "^2.30.2",
|
||||||
"source-sans-pro": "3.6.0",
|
"source-sans-pro": "3.6.0",
|
||||||
"style-loader": "^1.1.2",
|
"ssh2-streams": "^0.4.10",
|
||||||
"svg-inline-loader": "^0.8.0",
|
"style-loader": "^2.0.0",
|
||||||
|
"svg-inline-loader": "^0.8.2",
|
||||||
"to-string-loader": "1.1.6",
|
"to-string-loader": "1.1.6",
|
||||||
"tslib": "^1.10.0",
|
"tslib": "^2.0.3",
|
||||||
"typedoc": "^0.16.7",
|
"typedoc": "^0.18.0",
|
||||||
"typescript": "^3.7.4",
|
"typescript": "^3.9.7",
|
||||||
"url-loader": "^3.0.0",
|
"url-loader": "^3.0.0",
|
||||||
"val-loader": "2.1.0",
|
"val-loader": "2.1.1",
|
||||||
"webpack": "^5.0.0-beta.12",
|
"webpack": "^5.11.0",
|
||||||
"webpack-cli": "^3.3.10",
|
"webpack-cli": "^4.2.0",
|
||||||
"yaml-loader": "0.5.0"
|
"yaml-loader": "0.6.0"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"*/node-abi": "^2.8.0"
|
"*/node-abi": "^2.19.3",
|
||||||
},
|
"**/graceful-fs": "^4.2.4"
|
||||||
"build": {
|
|
||||||
"appId": "org.terminus",
|
|
||||||
"productName": "Terminus",
|
|
||||||
"compression": "normal",
|
|
||||||
"afterSign": "./build/mac/afterSignHook.js",
|
|
||||||
"files": [
|
|
||||||
"**/*",
|
|
||||||
"dist"
|
|
||||||
],
|
|
||||||
"extraResources": [
|
|
||||||
"builtin-plugins",
|
|
||||||
"extras"
|
|
||||||
],
|
|
||||||
"win": {
|
|
||||||
"icon": "./build/windows/icon.ico",
|
|
||||||
"artifactName": "terminus-${version}-setup.exe",
|
|
||||||
"rfc3161TimeStampServer": "http://sha256timestamp.ws.symantec.com/sha256/timestamp"
|
|
||||||
},
|
|
||||||
"nsis": {
|
|
||||||
"oneClick": false,
|
|
||||||
"artifactName": "terminus-${version}-setup.${ext}",
|
|
||||||
"installerIcon": "./build/windows/icon.ico"
|
|
||||||
},
|
|
||||||
"publish": [
|
|
||||||
{
|
|
||||||
"provider": "github"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"portable": {
|
|
||||||
"artifactName": "terminus-${version}-portable.exe"
|
|
||||||
},
|
|
||||||
"mac": {
|
|
||||||
"category": "public.app-category.video",
|
|
||||||
"icon": "./build/mac/icon.icns",
|
|
||||||
"artifactName": "terminus-${version}-macos.${ext}",
|
|
||||||
"hardenedRuntime": true,
|
|
||||||
"entitlements": "./build/mac/entitlements.plist",
|
|
||||||
"entitlementsInherit": "./build/mac/entitlements.plist",
|
|
||||||
"extendInfo": {
|
|
||||||
"NSRequiresAquaSystemAppearance": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"pkg": {
|
|
||||||
"artifactName": "terminus-${version}-macos.pkg"
|
|
||||||
},
|
|
||||||
"linux": {
|
|
||||||
"category": "Utilities",
|
|
||||||
"icon": "./build/icons",
|
|
||||||
"artifactName": "terminus-${version}-linux.${ext}"
|
|
||||||
},
|
|
||||||
"snap": {
|
|
||||||
"plugs": ["default", "system-files", "system-observe"]
|
|
||||||
},
|
|
||||||
"deb": {
|
|
||||||
"depends": [
|
|
||||||
"gconf2",
|
|
||||||
"gconf-service",
|
|
||||||
"libnotify4",
|
|
||||||
"libsecret-1-0",
|
|
||||||
"libappindicator1",
|
|
||||||
"libxtst6",
|
|
||||||
"libnss3"
|
|
||||||
],
|
|
||||||
"afterInstall": "build/linux/after-install.tpl"
|
|
||||||
},
|
|
||||||
"rpm": {
|
|
||||||
"depends": [
|
|
||||||
"screen",
|
|
||||||
"gnome-python2-gnomekeyring"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npm run build:typings && webpack --color --config app/webpack.main.config.js && webpack --color --config app/webpack.config.js && webpack --color --config terminus-core/webpack.config.js && webpack --color --config terminus-settings/webpack.config.js && webpack --color --config terminus-terminal/webpack.config.js && webpack --color --config terminus-plugin-manager/webpack.config.js && webpack --color --config terminus-community-color-schemes/webpack.config.js && webpack --color --config terminus-ssh/webpack.config.js",
|
"build": "npm run build:typings && webpack --color --config app/webpack.main.config.js && webpack --color --config app/webpack.config.js && webpack --color --config terminus-core/webpack.config.js && webpack --color --config terminus-settings/webpack.config.js && webpack --color --config terminus-terminal/webpack.config.js && webpack --color --config terminus-plugin-manager/webpack.config.js && webpack --color --config terminus-community-color-schemes/webpack.config.js && webpack --color --config terminus-ssh/webpack.config.js && webpack --color --config terminus-serial/webpack.config.js",
|
||||||
"build:typings": "tsc --project terminus-core/tsconfig.typings.json && tsc --project terminus-settings/tsconfig.typings.json && tsc --project terminus-terminal/tsconfig.typings.json && tsc --project terminus-plugin-manager/tsconfig.typings.json && tsc --project terminus-ssh/tsconfig.typings.json",
|
"build:typings": "node scripts/build-typings.js",
|
||||||
"watch": "cross-env TERMINUS_DEV=1 webpack --progress --color --watch",
|
"watch": "cross-env TERMINUS_DEV=1 webpack --progress --color --watch",
|
||||||
"start": "cross-env TERMINUS_DEV=1 electron app --debug",
|
"start": "cross-env TERMINUS_DEV=1 electron app --debug",
|
||||||
|
"start:prod": "electron app --debug",
|
||||||
"prod": "cross-env TERMINUS_DEV=1 electron app",
|
"prod": "cross-env TERMINUS_DEV=1 electron app",
|
||||||
"docs": "typedoc --out docs/api terminus-core/src && typedoc --out docs/api/terminal --tsconfig terminus-terminal/tsconfig.typings.json terminus-terminal/src && typedoc --out docs/api/settings --tsconfig terminus-settings/tsconfig.typings.json terminus-settings/src",
|
"docs": "typedoc --out docs/api terminus-core/src && typedoc --out docs/api/terminal --tsconfig terminus-terminal/tsconfig.typings.json terminus-terminal/src && typedoc --out docs/api/settings --tsconfig terminus-settings/tsconfig.typings.json terminus-settings/src",
|
||||||
"lint": "eslint --ext ts */src",
|
"lint": "eslint --ext ts */src */lib",
|
||||||
"postinstall": "node ./scripts/install-deps.js"
|
"postinstall": "node ./scripts/install-deps.js"
|
||||||
},
|
},
|
||||||
"repository": "eugeny/terminus"
|
"repository": "eugeny/terminus",
|
||||||
|
"author": "Eugene Pankov",
|
||||||
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
@@ -1,17 +1,24 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
const builder = require('electron-builder').build
|
const builder = require('electron-builder').build
|
||||||
const vars = require('./vars')
|
const vars = require('./vars')
|
||||||
|
const fs = require('fs')
|
||||||
|
const signHook = require('../build/mac/afterSignHook')
|
||||||
|
|
||||||
const isTag = (process.env.GITHUB_REF || '').startsWith('refs/tags/')
|
const isTag = (process.env.GITHUB_REF || '').startsWith('refs/tags/')
|
||||||
const isCI = !!process.env.GITHUB_REF
|
|
||||||
|
process.env.ARCH = process.env.ARCH || process.arch
|
||||||
|
|
||||||
builder({
|
builder({
|
||||||
dir: true,
|
dir: true,
|
||||||
mac: ['pkg', 'zip'],
|
mac: ['pkg', 'zip'],
|
||||||
|
arm64: process.env.ARCH === 'arm64',
|
||||||
config: {
|
config: {
|
||||||
extraMetadata: {
|
extraMetadata: {
|
||||||
version: vars.version,
|
version: vars.version,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
publish: isTag ? 'always' : 'onTag',
|
publish: isTag ? 'always' : 'onTag',
|
||||||
}).catch(() => process.exit(1))
|
}).catch(e => {
|
||||||
|
console.error(e)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
|
@@ -8,6 +8,7 @@ for (let dir of ['app', 'terminus-core', 'terminus-ssh', 'terminus-terminal']) {
|
|||||||
const build = rebuild({
|
const build = rebuild({
|
||||||
buildPath: path.resolve(__dirname, '../' + dir),
|
buildPath: path.resolve(__dirname, '../' + dir),
|
||||||
electronVersion: vars.electronVersion,
|
electronVersion: vars.electronVersion,
|
||||||
|
arch: process.env.ARCH ?? process.arch,
|
||||||
force: true,
|
force: true,
|
||||||
})
|
})
|
||||||
build.catch(e => {
|
build.catch(e => {
|
||||||
|
9
scripts/build-typings.js
Executable file
9
scripts/build-typings.js
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
const sh = require('shelljs')
|
||||||
|
const vars = require('./vars')
|
||||||
|
const log = require('npmlog')
|
||||||
|
|
||||||
|
vars.builtinPlugins.forEach(plugin => {
|
||||||
|
log.info('typings', plugin)
|
||||||
|
sh.exec(`npx tsc --project ${plugin}/tsconfig.typings.json`)
|
||||||
|
})
|
@@ -7,7 +7,7 @@ const isCI = !!process.env.GITHUB_REF
|
|||||||
|
|
||||||
builder({
|
builder({
|
||||||
dir: true,
|
dir: true,
|
||||||
win: ['nsis', 'portable'],
|
win: ['nsis', 'zip'],
|
||||||
config: {
|
config: {
|
||||||
extraMetadata: {
|
extraMetadata: {
|
||||||
version: vars.version,
|
version: vars.version,
|
||||||
|
@@ -10,13 +10,13 @@ const npx = `${localBinPath}/npx`;
|
|||||||
log.info('deps', 'app')
|
log.info('deps', 'app')
|
||||||
|
|
||||||
sh.cd('app')
|
sh.cd('app')
|
||||||
sh.exec(`${npx} yarn install`)
|
sh.exec(`${npx} yarn install --force`)
|
||||||
sh.cd('..')
|
sh.cd('..')
|
||||||
|
|
||||||
vars.builtinPlugins.forEach(plugin => {
|
vars.builtinPlugins.forEach(plugin => {
|
||||||
log.info('deps', plugin)
|
log.info('deps', plugin)
|
||||||
sh.cd(plugin)
|
sh.cd(plugin)
|
||||||
sh.exec(`${npx} yarn install`)
|
sh.exec(`${npx} yarn install --force`)
|
||||||
sh.cd('..')
|
sh.cd('..')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@@ -15,10 +15,17 @@ vars.builtinPlugins.forEach(plugin => {
|
|||||||
sh.cp('-r', path.join('..', plugin), '.')
|
sh.cp('-r', path.join('..', plugin), '.')
|
||||||
sh.rm('-rf', path.join(plugin, 'node_modules'))
|
sh.rm('-rf', path.join(plugin, 'node_modules'))
|
||||||
sh.cd(plugin)
|
sh.cd(plugin)
|
||||||
sh.exec(`npm install --only=prod`)
|
sh.exec(`yarn install --force --production`)
|
||||||
|
|
||||||
|
|
||||||
log.info('rebuild', 'native')
|
log.info('rebuild', 'native')
|
||||||
if (fs.existsSync('node_modules')) {
|
if (fs.existsSync('node_modules')) {
|
||||||
rebuild(path.resolve('.'), vars.electronVersion, process.arch, [], true)
|
rebuild({
|
||||||
|
buildPath: path.resolve('.'),
|
||||||
|
electronVersion: vars.electronVersion,
|
||||||
|
arch: process.env.ARCH ?? process.arch,
|
||||||
|
force: true,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
sh.cd('..')
|
sh.cd('..')
|
||||||
})
|
})
|
||||||
|
24
scripts/sentry-upload.js
Executable file
24
scripts/sentry-upload.js
Executable file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
const sh = require('shelljs')
|
||||||
|
const vars = require('./vars')
|
||||||
|
|
||||||
|
const sentryCli = process.platform === 'win32' ? 'node_modules\\.bin\\sentry-cli.cmd' : 'sentry-cli'
|
||||||
|
|
||||||
|
sh.exec(`${sentryCli} releases new ${vars.version}`)
|
||||||
|
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
for (const path of [
|
||||||
|
'app/node_modules/@serialport/bindings/build/Release/bindings.node',
|
||||||
|
'app/node_modules/@terminus-term/node-pty/build/Release/pty.node',
|
||||||
|
'app/node_modules/fontmanager-redux/build/Release/fontmanager.node',
|
||||||
|
'app/node_modules/macos-native-processlist/build/Release/native.node',
|
||||||
|
]) {
|
||||||
|
sh.exec('dsymutil ' + path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sh.exec(`${sentryCli} upload-dif app/node_modules`)
|
||||||
|
sh.exec(`${sentryCli} releases set-commits --auto ${vars.version}`)
|
||||||
|
for (const p of vars.builtinPlugins) {
|
||||||
|
sh.exec(`${sentryCli} releases files ${vars.version} upload-sourcemaps ${p}/dist -u ${p}/dist/ -d ${process.platform}-${p}`)
|
||||||
|
}
|
@@ -21,6 +21,7 @@ exports.builtinPlugins = [
|
|||||||
'terminus-community-color-schemes',
|
'terminus-community-color-schemes',
|
||||||
'terminus-plugin-manager',
|
'terminus-plugin-manager',
|
||||||
'terminus-ssh',
|
'terminus-ssh',
|
||||||
|
'terminus-serial',
|
||||||
]
|
]
|
||||||
exports.bundledModules = [
|
exports.bundledModules = [
|
||||||
'@angular',
|
'@angular',
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-community-color-schemes",
|
"name": "terminus-community-color-schemes",
|
||||||
"version": "1.0.99-nightly.0",
|
"version": "1.0.123-nightly.0",
|
||||||
"description": "Community color schemes for Terminus",
|
"description": "Community color schemes for Terminus",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"terminus-builtin-plugin"
|
"terminus-builtin-plugin"
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
"author": "Eugene Pankov",
|
"author": "Eugene Pankov",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/core": "^7",
|
"@angular/core": "^9.1.9",
|
||||||
"terminus-core": "*",
|
"terminus-core": "*",
|
||||||
"terminus-terminal": "*"
|
"terminus-terminal": "*"
|
||||||
}
|
}
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { TerminalColorSchemeProvider, TerminalColorScheme } from 'terminus-terminal'
|
import { TerminalColorSchemeProvider, TerminalColorScheme } from 'terminus-terminal'
|
||||||
|
|
||||||
const schemeContents = require.context('../schemes/', true, /.*/)
|
const schemeContents = require.context('../schemes/', false, /.*/)
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ColorSchemes extends TerminalColorSchemeProvider {
|
export class ColorSchemes extends TerminalColorSchemeProvider {
|
||||||
async getSchemes (): Promise<TerminalColorScheme[]> {
|
async getSchemes (): Promise<TerminalColorScheme[]> {
|
||||||
const schemes: TerminalColorScheme[] = []
|
const schemes: TerminalColorScheme[] = []
|
||||||
|
|
||||||
schemeContents.keys().forEach(schemeFile => {
|
schemeContents.keys().filter(x => !x.startsWith('./')).forEach(schemeFile => {
|
||||||
const lines = (schemeContents(schemeFile).default as string).split('\n')
|
const lines = (schemeContents(schemeFile).default as string).split('\n')
|
||||||
|
|
||||||
// process #define variables
|
// process #define variables
|
||||||
|
14
terminus-community-color-schemes/tsconfig.typings.json
Normal file
14
terminus-community-color-schemes/tsconfig.typings.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"exclude": ["node_modules", "dist", "typings"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": "src",
|
||||||
|
"emitDeclarationOnly": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationDir": "./typings",
|
||||||
|
"paths": {
|
||||||
|
"terminus-*": ["../../terminus-*"],
|
||||||
|
"*": ["../../app/node_modules/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -4,7 +4,7 @@ module.exports = {
|
|||||||
target: 'node',
|
target: 'node',
|
||||||
entry: 'src/index.ts',
|
entry: 'src/index.ts',
|
||||||
context: __dirname,
|
context: __dirname,
|
||||||
devtool: 'eval-cheap-module-source-map',
|
devtool: 'cheap-module-source-map',
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve(__dirname, 'dist'),
|
path: path.resolve(__dirname, 'dist'),
|
||||||
filename: 'index.js',
|
filename: 'index.js',
|
||||||
|
1
terminus-core/.gitignore
vendored
1
terminus-core/.gitignore
vendored
@@ -1,2 +1 @@
|
|||||||
dist
|
dist
|
||||||
node_modules
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-core",
|
"name": "terminus-core",
|
||||||
"version": "1.0.99-nightly.0",
|
"version": "1.0.123-nightly.0",
|
||||||
"description": "Terminus core",
|
"description": "Terminus core",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"terminus-builtin-plugin"
|
"terminus-builtin-plugin"
|
||||||
@@ -29,17 +29,18 @@
|
|||||||
"mixpanel": "^0.10.2",
|
"mixpanel": "^0.10.2",
|
||||||
"ng2-dnd": "^5.0.2",
|
"ng2-dnd": "^5.0.2",
|
||||||
"ngx-perfect-scrollbar": "^8.0.0",
|
"ngx-perfect-scrollbar": "^8.0.0",
|
||||||
|
"readable-stream": "2.3.7",
|
||||||
"shell-escape": "^0.2.0",
|
"shell-escape": "^0.2.0",
|
||||||
"uuid": "^3.3.2",
|
"uuid": "^8.0.0",
|
||||||
"winston": "^3.2.1"
|
"winston": "^3.3.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/animations": "^7",
|
"@angular/animations": "^9.1.9",
|
||||||
"@angular/common": "^7",
|
"@angular/common": "^9.1.11",
|
||||||
"@angular/core": "^7",
|
"@angular/core": "^9.1.9",
|
||||||
"@angular/forms": "^7",
|
"@angular/forms": "^9.1.11",
|
||||||
"@angular/platform-browser": "^7",
|
"@angular/platform-browser": "^9.1.11",
|
||||||
"@angular/platform-browser-dynamic": "^7",
|
"@angular/platform-browser-dynamic": "^9.1.11",
|
||||||
"rxjs": "^5"
|
"rxjs": "^6.6.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -33,5 +33,5 @@ export abstract class ConfigProvider {
|
|||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
platformDefaults: {[platform: string]: any} = {}
|
platformDefaults: Record<string, any> = {}
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,5 @@ export interface HotkeyDescription {
|
|||||||
* must also provide the `hotkeys.foo` config options with the default values
|
* must also provide the `hotkeys.foo` config options with the default values
|
||||||
*/
|
*/
|
||||||
export abstract class HotkeyProvider {
|
export abstract class HotkeyProvider {
|
||||||
hotkeys: HotkeyDescription[] = []
|
|
||||||
|
|
||||||
abstract provide (): Promise<HotkeyDescription[]>
|
abstract provide (): Promise<HotkeyDescription[]>
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,13 @@
|
|||||||
export { BaseTabComponent, BaseTabProcess } from '../components/baseTab.component'
|
export { BaseTabComponent, BaseTabProcess } from '../components/baseTab.component'
|
||||||
export { TabHeaderComponent } from '../components/tabHeader.component'
|
export { TabHeaderComponent } from '../components/tabHeader.component'
|
||||||
export { SplitTabComponent, SplitContainer } from '../components/splitTab.component'
|
export { SplitTabComponent, SplitContainer } from '../components/splitTab.component'
|
||||||
export { TabRecoveryProvider, RecoveredTab } from './tabRecovery'
|
export { TabRecoveryProvider, RecoveredTab, RecoveryToken } from './tabRecovery'
|
||||||
export { ToolbarButtonProvider, ToolbarButton } from './toolbarButtonProvider'
|
export { ToolbarButtonProvider, ToolbarButton } from './toolbarButtonProvider'
|
||||||
export { ConfigProvider } from './configProvider'
|
export { ConfigProvider } from './configProvider'
|
||||||
export { HotkeyProvider, HotkeyDescription } from './hotkeyProvider'
|
export { HotkeyProvider, HotkeyDescription } from './hotkeyProvider'
|
||||||
export { Theme } from './theme'
|
export { Theme } from './theme'
|
||||||
export { TabContextMenuItemProvider } from './tabContextMenuProvider'
|
export { TabContextMenuItemProvider } from './tabContextMenuProvider'
|
||||||
|
export { SelectorOption } from './selector'
|
||||||
|
|
||||||
export { AppService } from '../services/app.service'
|
export { AppService } from '../services/app.service'
|
||||||
export { ConfigService } from '../services/config.service'
|
export { ConfigService } from '../services/config.service'
|
||||||
@@ -19,3 +20,4 @@ export { HostAppService, Platform } from '../services/hostApp.service'
|
|||||||
export { ShellIntegrationService } from '../services/shellIntegration.service'
|
export { ShellIntegrationService } from '../services/shellIntegration.service'
|
||||||
export { ThemesService } from '../services/themes.service'
|
export { ThemesService } from '../services/themes.service'
|
||||||
export { TabsService } from '../services/tabs.service'
|
export { TabsService } from '../services/tabs.service'
|
||||||
|
export * from '../utils'
|
||||||
|
8
terminus-core/src/api/selector.ts
Normal file
8
terminus-core/src/api/selector.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export interface SelectorOption<T> {
|
||||||
|
name: string
|
||||||
|
description?: string
|
||||||
|
result?: T
|
||||||
|
icon?: string
|
||||||
|
freeInputPattern?: string
|
||||||
|
callback?: (string?) => void
|
||||||
|
}
|
@@ -1,3 +1,4 @@
|
|||||||
|
import type { MenuItemConstructorOptions } from 'electron'
|
||||||
import { BaseTabComponent } from '../components/baseTab.component'
|
import { BaseTabComponent } from '../components/baseTab.component'
|
||||||
import { TabHeaderComponent } from '../components/tabHeader.component'
|
import { TabHeaderComponent } from '../components/tabHeader.component'
|
||||||
|
|
||||||
@@ -7,5 +8,5 @@ import { TabHeaderComponent } from '../components/tabHeader.component'
|
|||||||
export abstract class TabContextMenuItemProvider {
|
export abstract class TabContextMenuItemProvider {
|
||||||
weight = 0
|
weight = 0
|
||||||
|
|
||||||
abstract async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<Electron.MenuItemConstructorOptions[]>
|
abstract async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<MenuItemConstructorOptions[]>
|
||||||
}
|
}
|
||||||
|
@@ -12,6 +12,12 @@ export interface RecoveredTab {
|
|||||||
options?: any
|
options?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RecoveryToken {
|
||||||
|
[_: string]: any
|
||||||
|
type: string
|
||||||
|
tabColor?: string|null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extend to enable recovery for your custom tab.
|
* Extend to enable recovery for your custom tab.
|
||||||
* This works in conjunction with [[getRecoveryToken()]]
|
* This works in conjunction with [[getRecoveryToken()]]
|
||||||
@@ -34,5 +40,5 @@ export abstract class TabRecoveryProvider {
|
|||||||
* @returns [[RecoveredTab]] descriptor containing tab type and component inputs
|
* @returns [[RecoveredTab]] descriptor containing tab type and component inputs
|
||||||
* or `null` if this token is from a different tab type or is not supported
|
* or `null` if this token is from a different tab type or is not supported
|
||||||
*/
|
*/
|
||||||
abstract async recover (recoveryToken: any): Promise<RecoveredTab|null>
|
abstract async recover (recoveryToken: RecoveryToken): Promise<RecoveredTab|null>
|
||||||
}
|
}
|
||||||
|
@@ -4,10 +4,13 @@ title-bar(
|
|||||||
)
|
)
|
||||||
|
|
||||||
.content(
|
.content(
|
||||||
[class.tabs-on-top]='config.store.appearance.tabsLocation == "top"'
|
[class.tabs-on-top]='config.store.appearance.tabsLocation == "top" || config.store.appearance.tabsLocation == "left"',
|
||||||
|
[class.tabs-on-side]='hasVerticalTabs()',
|
||||||
)
|
)
|
||||||
.tab-bar
|
.tab-bar
|
||||||
.inset.background(*ngIf='hostApp.platform == Platform.macOS && config.store.appearance.frame == "thin" && config.store.appearance.tabsLocation == "top"')
|
.inset.background(*ngIf='hostApp.platform == Platform.macOS \
|
||||||
|
&& config.store.appearance.frame == "thin" \
|
||||||
|
&& (config.store.appearance.tabsLocation == "top" || config.store.appearance.tabsLocation == "left")')
|
||||||
.tabs(
|
.tabs(
|
||||||
dnd-sortable-container,
|
dnd-sortable-container,
|
||||||
[sortableData]='app.tabs',
|
[sortableData]='app.tabs',
|
||||||
@@ -18,12 +21,12 @@ title-bar(
|
|||||||
[sortableIndex]='idx',
|
[sortableIndex]='idx',
|
||||||
(onDragStart)='onTabDragStart()',
|
(onDragStart)='onTabDragStart()',
|
||||||
(onDragEnd)='onTabDragEnd()',
|
(onDragEnd)='onTabDragEnd()',
|
||||||
|
|
||||||
[index]='idx',
|
[index]='idx',
|
||||||
[tab]='tab',
|
[tab]='tab',
|
||||||
[active]='tab == app.activeTab',
|
[active]='tab == app.activeTab',
|
||||||
[hasActivity]='tab.activity$|async',
|
[hasActivity]='tab.activity$|async',
|
||||||
@animateTab,
|
@animateTab,
|
||||||
|
[@.disabled]='hasVerticalTabs()',
|
||||||
(click)='app.selectTab(tab)',
|
(click)='app.selectTab(tab)',
|
||||||
[class.fully-draggable]='hostApp.platform != Platform.macOS',
|
[class.fully-draggable]='hostApp.platform != Platform.macOS',
|
||||||
[class.drag-region]='hostApp.platform == Platform.macOS && !tabsDragging',
|
[class.drag-region]='hostApp.platform == Platform.macOS && !tabsDragging',
|
||||||
@@ -87,7 +90,8 @@ title-bar(
|
|||||||
)
|
)
|
||||||
|
|
||||||
window-controls.background(
|
window-controls.background(
|
||||||
*ngIf='config.store.appearance.frame == "thin" && (hostApp.platform == Platform.Windows || hostApp.platform == Platform.Linux)',
|
*ngIf='config.store.appearance.frame == "thin" \
|
||||||
|
&& (hostApp.platform == Platform.Windows || hostApp.platform == Platform.Linux)',
|
||||||
)
|
)
|
||||||
|
|
||||||
start-page(*ngIf='ready && app.tabs.length == 0')
|
start-page(*ngIf='ready && app.tabs.length == 0')
|
||||||
|
@@ -15,26 +15,73 @@
|
|||||||
|
|
||||||
$tabs-height: 38px;
|
$tabs-height: 38px;
|
||||||
$tab-border-radius: 4px;
|
$tab-border-radius: 4px;
|
||||||
|
$side-tab-width: 200px;
|
||||||
|
|
||||||
|
.wrap {
|
||||||
|
display: flex;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
height: 100%;
|
width: 100vw;
|
||||||
flex: auto;
|
flex: 1 1 0;
|
||||||
|
min-height: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column-reverse;
|
flex-direction: column-reverse;
|
||||||
|
|
||||||
&.tabs-on-top {
|
&.tabs-on-top {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.tabs-on-side {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
|
||||||
|
&.tabs-on-top {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content.tabs-on-side > .tab-bar {
|
||||||
|
height: 100%;
|
||||||
|
width: $side-tab-width;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
flex-direction: column;
|
||||||
|
background: rgba(0, 0, 0, 0.25);
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
width: $side-tab-width;
|
||||||
|
flex: none;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
tab-header {
|
||||||
|
flex: 0 0 $tabs-height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-space {
|
||||||
|
flex: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&>.inset {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.tab-bar {
|
.tab-bar {
|
||||||
flex: none;
|
flex: none;
|
||||||
height: $tabs-height;
|
height: $tabs-height;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
.btn-tab-bar {
|
.btn-tab-bar {
|
||||||
line-height: $tabs-height + 2px;
|
line-height: $tabs-height + 2px;
|
||||||
|
height: $tabs-height;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -50,6 +97,8 @@ $tab-border-radius: 4px;
|
|||||||
border: none;
|
border: none;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
&.dropdown-toggle::after {
|
&.dropdown-toggle::after {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -74,7 +123,9 @@ $tab-border-radius: 4px;
|
|||||||
|
|
||||||
& > .inset {
|
& > .inset {
|
||||||
width: 85px;
|
width: 85px;
|
||||||
|
height: $tabs-height;
|
||||||
flex: none;
|
flex: none;
|
||||||
|
-webkit-app-region: drag;
|
||||||
}
|
}
|
||||||
|
|
||||||
window-controls {
|
window-controls {
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import { Component, Inject, Input, HostListener, HostBinding } from '@angular/core'
|
import { Component, Inject, Input, HostListener, HostBinding } from '@angular/core'
|
||||||
import { trigger, style, animate, transition, state } from '@angular/animations'
|
import { trigger, style, animate, transition, state } from '@angular/animations'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
@@ -107,6 +108,15 @@ export class AppRootComponent {
|
|||||||
if (hotkey === 'previous-tab') {
|
if (hotkey === 'previous-tab') {
|
||||||
this.app.previousTab()
|
this.app.previousTab()
|
||||||
}
|
}
|
||||||
|
if (hotkey === 'move-tab-left') {
|
||||||
|
this.app.moveSelectedTabLeft()
|
||||||
|
}
|
||||||
|
if (hotkey === 'move-tab-right') {
|
||||||
|
this.app.moveSelectedTabRight()
|
||||||
|
}
|
||||||
|
if (hotkey === 'reopen-tab') {
|
||||||
|
this.app.reopenLastTab()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (hotkey === 'toggle-fullscreen') {
|
if (hotkey === 'toggle-fullscreen') {
|
||||||
this.hostApp.toggleFullscreen()
|
this.hostApp.toggleFullscreen()
|
||||||
@@ -118,17 +128,8 @@ export class AppRootComponent {
|
|||||||
this.docking.dock()
|
this.docking.dock()
|
||||||
})
|
})
|
||||||
|
|
||||||
this.hostApp.secondInstance$.subscribe(() => {
|
|
||||||
this.presentWindow()
|
|
||||||
})
|
|
||||||
this.hotkeys.globalHotkey.subscribe(() => {
|
|
||||||
this.onGlobalHotkey()
|
|
||||||
})
|
|
||||||
|
|
||||||
this.hostApp.windowCloseRequest$.subscribe(async () => {
|
this.hostApp.windowCloseRequest$.subscribe(async () => {
|
||||||
if (await this.app.closeAllTabs()) {
|
this.app.closeWindow()
|
||||||
this.hostApp.closeWindow()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (window['safeModeReason']) {
|
if (window['safeModeReason']) {
|
||||||
@@ -167,40 +168,6 @@ export class AppRootComponent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onGlobalHotkey () {
|
|
||||||
if (this.hostApp.getWindow().isFocused()) {
|
|
||||||
this.hideWindow()
|
|
||||||
} else {
|
|
||||||
this.presentWindow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
presentWindow () {
|
|
||||||
if (!this.hostApp.getWindow().isVisible()) {
|
|
||||||
// unfocused, invisible
|
|
||||||
this.hostApp.getWindow().show()
|
|
||||||
this.hostApp.getWindow().focus()
|
|
||||||
} else {
|
|
||||||
if (this.config.store.appearance.dock === 'off') {
|
|
||||||
// not docked, visible
|
|
||||||
setTimeout(() => {
|
|
||||||
this.hostApp.getWindow().focus()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// docked, visible
|
|
||||||
this.hostApp.getWindow().hide()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hideWindow () {
|
|
||||||
this.electron.loseFocus()
|
|
||||||
this.hostApp.getWindow().blur()
|
|
||||||
if (this.hostApp.platform !== Platform.macOS) {
|
|
||||||
this.hostApp.getWindow().hide()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async ngOnInit () {
|
async ngOnInit () {
|
||||||
this.ready = true
|
this.ready = true
|
||||||
|
|
||||||
@@ -217,6 +184,10 @@ export class AppRootComponent {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasVerticalTabs () {
|
||||||
|
return this.config.store.appearance.tabsLocation === 'left' || this.config.store.appearance.tabsLocation === 'right'
|
||||||
|
}
|
||||||
|
|
||||||
async updateApp () {
|
async updateApp () {
|
||||||
if ((await this.electron.showMessageBox(
|
if ((await this.electron.showMessageBox(
|
||||||
this.hostApp.getWindow(),
|
this.hostApp.getWindow(),
|
||||||
@@ -258,8 +229,8 @@ export class AppRootComponent {
|
|||||||
buttons = buttons.concat(provider.provide())
|
buttons = buttons.concat(provider.provide())
|
||||||
})
|
})
|
||||||
return buttons
|
return buttons
|
||||||
.filter(button => (button.weight || 0) > 0 === aboveZero)
|
.filter(button => (button.weight ?? 0) > 0 === aboveZero)
|
||||||
.sort((a: ToolbarButton, b: ToolbarButton) => (a.weight || 0) - (b.weight || 0))
|
.sort((a: ToolbarButton, b: ToolbarButton) => (a.weight ?? 0) - (b.weight ?? 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateVibrancy () {
|
private updateVibrancy () {
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { Observable, Subject } from 'rxjs'
|
import { Observable, Subject } from 'rxjs'
|
||||||
import { ViewRef } from '@angular/core'
|
import { ViewRef } from '@angular/core'
|
||||||
|
import { RecoveryToken } from '../api/tabRecovery'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an active "process" inside a tab,
|
* Represents an active "process" inside a tab,
|
||||||
@@ -13,6 +14,11 @@ export interface BaseTabProcess {
|
|||||||
* Abstract base class for custom tab components
|
* Abstract base class for custom tab components
|
||||||
*/
|
*/
|
||||||
export abstract class BaseTabComponent {
|
export abstract class BaseTabComponent {
|
||||||
|
/**
|
||||||
|
* Parent tab (usually a SplitTabComponent)
|
||||||
|
*/
|
||||||
|
parent: BaseTabComponent|null = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current tab title
|
* Current tab title
|
||||||
*/
|
*/
|
||||||
@@ -38,7 +44,7 @@ export abstract class BaseTabComponent {
|
|||||||
*/
|
*/
|
||||||
color: string|null = null
|
color: string|null = null
|
||||||
|
|
||||||
protected hasFocus = false
|
hasFocus = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ping this if your recovery state has been changed and you want
|
* Ping this if your recovery state has been changed and you want
|
||||||
@@ -62,7 +68,7 @@ export abstract class BaseTabComponent {
|
|||||||
get destroyed$ (): Observable<void> { return this.destroyed }
|
get destroyed$ (): Observable<void> { return this.destroyed }
|
||||||
get recoveryStateChangedHint$ (): Observable<void> { return this.recoveryStateChangedHint }
|
get recoveryStateChangedHint$ (): Observable<void> { return this.recoveryStateChangedHint }
|
||||||
|
|
||||||
constructor () {
|
protected constructor () {
|
||||||
this.focused$.subscribe(() => {
|
this.focused$.subscribe(() => {
|
||||||
this.hasFocus = true
|
this.hasFocus = true
|
||||||
})
|
})
|
||||||
@@ -71,7 +77,7 @@ export abstract class BaseTabComponent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setTitle (title: string) {
|
setTitle (title: string): void {
|
||||||
this.title = title
|
this.title = title
|
||||||
if (!this.customTitle) {
|
if (!this.customTitle) {
|
||||||
this.titleChange.next(title)
|
this.titleChange.next(title)
|
||||||
@@ -83,7 +89,7 @@ export abstract class BaseTabComponent {
|
|||||||
*
|
*
|
||||||
* @param {type} progress: value between 0 and 1, or `null` to remove
|
* @param {type} progress: value between 0 and 1, or `null` to remove
|
||||||
*/
|
*/
|
||||||
setProgress (progress: number|null) {
|
setProgress (progress: number|null): void {
|
||||||
this.progress.next(progress)
|
this.progress.next(progress)
|
||||||
if (progress) {
|
if (progress) {
|
||||||
if (this.progressClearTimeout) {
|
if (this.progressClearTimeout) {
|
||||||
@@ -118,7 +124,7 @@ export abstract class BaseTabComponent {
|
|||||||
* @return JSON serializable tab state representation
|
* @return JSON serializable tab state representation
|
||||||
* for your [[TabRecoveryProvider]] to parse
|
* for your [[TabRecoveryProvider]] to parse
|
||||||
*/
|
*/
|
||||||
async getRecoveryToken (): Promise<any> {
|
async getRecoveryToken (): Promise<RecoveryToken|null> {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,11 +142,11 @@ export abstract class BaseTabComponent {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
emitFocused () {
|
emitFocused (): void {
|
||||||
this.focused.next()
|
this.focused.next()
|
||||||
}
|
}
|
||||||
|
|
||||||
emitBlurred () {
|
emitBlurred (): void {
|
||||||
this.blurred.next()
|
this.blurred.next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import { NgZone, Component, Input, HostBinding, HostListener } from '@angular/core'
|
import { NgZone, Component, Input, HostBinding, HostListener } from '@angular/core'
|
||||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
|
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
|
||||||
|
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import { Component, Input, ElementRef, ViewChild } from '@angular/core'
|
import { Component, Input, ElementRef, ViewChild } from '@angular/core'
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
|
||||||
|
@@ -14,7 +14,7 @@ export class SafeModeModalComponent {
|
|||||||
this.error = window['safeModeReason']
|
this.error = window['safeModeReason']
|
||||||
}
|
}
|
||||||
|
|
||||||
close () {
|
close (): void {
|
||||||
this.modalInstance.dismiss()
|
this.modalInstance.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
26
terminus-core/src/components/selectorModal.component.pug
Normal file
26
terminus-core/src/components/selectorModal.component.pug
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
.modal-body
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
[(ngModel)]='filter',
|
||||||
|
autofocus,
|
||||||
|
[placeholder]='name',
|
||||||
|
(ngModelChange)='onFilterChange()'
|
||||||
|
)
|
||||||
|
|
||||||
|
.list-group.mt-3(*ngIf='filteredOptions.length')
|
||||||
|
a.list-group-item.list-group-item-action.d-flex.align-items-center(
|
||||||
|
#item,
|
||||||
|
(click)='selectOption(option)',
|
||||||
|
[class.active]='selectedIndex == i',
|
||||||
|
*ngFor='let option of filteredOptions; let i = index'
|
||||||
|
)
|
||||||
|
i.icon(
|
||||||
|
class='fa-fw fas fa-{{option.icon}}',
|
||||||
|
*ngIf='!iconIsSVG(option.icon)'
|
||||||
|
)
|
||||||
|
.icon(
|
||||||
|
[fastHtmlBind]='option.icon',
|
||||||
|
*ngIf='iconIsSVG(option.icon)'
|
||||||
|
)
|
||||||
|
.mr-2.title {{getOptionText(option)}}
|
||||||
|
.text-muted {{option.description}}
|
13
terminus-core/src/components/selectorModal.component.scss
Normal file
13
terminus-core/src/components/selectorModal.component.scss
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
.list-group {
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 1.25rem;
|
||||||
|
margin-right: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
78
terminus-core/src/components/selectorModal.component.ts
Normal file
78
terminus-core/src/components/selectorModal.component.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { Component, Input, HostListener, ViewChildren, QueryList, ElementRef } from '@angular/core'
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { SelectorOption } from '../api/selector'
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
@Component({
|
||||||
|
template: require('./selectorModal.component.pug'),
|
||||||
|
styles: [require('./selectorModal.component.scss')],
|
||||||
|
})
|
||||||
|
export class SelectorModalComponent<T> {
|
||||||
|
@Input() options: SelectorOption<T>[]
|
||||||
|
@Input() filteredOptions: SelectorOption<T>[]
|
||||||
|
@Input() filter = ''
|
||||||
|
@Input() name: string
|
||||||
|
@Input() selectedIndex = 0
|
||||||
|
@ViewChildren('item') itemChildren: QueryList<ElementRef>
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
public modalInstance: NgbActiveModal,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit (): void {
|
||||||
|
this.onFilterChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('keyup', ['$event']) onKeyUp (event: KeyboardEvent): void {
|
||||||
|
if (event.key === 'ArrowUp') {
|
||||||
|
this.selectedIndex--
|
||||||
|
}
|
||||||
|
if (event.key === 'ArrowDown') {
|
||||||
|
this.selectedIndex++
|
||||||
|
}
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
this.selectOption(this.filteredOptions[this.selectedIndex])
|
||||||
|
}
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
this.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectedIndex = (this.selectedIndex + this.filteredOptions.length) % this.filteredOptions.length
|
||||||
|
Array.from(this.itemChildren)[this.selectedIndex]?.nativeElement.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'nearest',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onFilterChange (): void {
|
||||||
|
const f = this.filter.trim().toLowerCase()
|
||||||
|
if (!f) {
|
||||||
|
this.filteredOptions = this.options.filter(x => !x.freeInputPattern)
|
||||||
|
} else {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
||||||
|
this.filteredOptions = this.options.filter(x => x.freeInputPattern ?? (x.name + (x.description ?? '')).toLowerCase().includes(f))
|
||||||
|
}
|
||||||
|
this.selectedIndex = Math.max(0, this.selectedIndex)
|
||||||
|
this.selectedIndex = Math.min(this.filteredOptions.length - 1, this.selectedIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
getOptionText (option: SelectorOption<T>): string {
|
||||||
|
if (option.freeInputPattern) {
|
||||||
|
return option.freeInputPattern.replace('%s', this.filter)
|
||||||
|
}
|
||||||
|
return option.name
|
||||||
|
}
|
||||||
|
|
||||||
|
selectOption (option: SelectorOption<T>): void {
|
||||||
|
option.callback?.(this.filter)
|
||||||
|
this.modalInstance.close(option.result)
|
||||||
|
}
|
||||||
|
|
||||||
|
close (): void {
|
||||||
|
this.modalInstance.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
iconIsSVG (icon?: string): boolean {
|
||||||
|
return icon?.startsWith('<') ?? false
|
||||||
|
}
|
||||||
|
}
|
@@ -1,7 +1,7 @@
|
|||||||
import { Observable, Subject, Subscription } from 'rxjs'
|
import { Observable, Subject, Subscription } from 'rxjs'
|
||||||
import { Component, Injectable, ViewChild, ViewContainerRef, EmbeddedViewRef, OnInit, OnDestroy } from '@angular/core'
|
import { Component, Injectable, ViewChild, ViewContainerRef, EmbeddedViewRef, AfterViewInit, OnDestroy } from '@angular/core'
|
||||||
import { BaseTabComponent, BaseTabProcess } from './baseTab.component'
|
import { BaseTabComponent, BaseTabProcess } from './baseTab.component'
|
||||||
import { TabRecoveryProvider, RecoveredTab } from '../api/tabRecovery'
|
import { TabRecoveryProvider, RecoveredTab, RecoveryToken } from '../api/tabRecovery'
|
||||||
import { TabsService } from '../services/tabs.service'
|
import { TabsService } from '../services/tabs.service'
|
||||||
import { HotkeysService } from '../services/hotkeys.service'
|
import { HotkeysService } from '../services/hotkeys.service'
|
||||||
import { TabRecoveryService } from '../services/tabRecovery.service'
|
import { TabRecoveryService } from '../services/tabRecovery.service'
|
||||||
@@ -48,7 +48,7 @@ export class SplitContainer {
|
|||||||
/**
|
/**
|
||||||
* Remove unnecessarily nested child containers and renormalizes [[ratios]]
|
* Remove unnecessarily nested child containers and renormalizes [[ratios]]
|
||||||
*/
|
*/
|
||||||
normalize () {
|
normalize (): void {
|
||||||
for (let i = 0; i < this.children.length; i++) {
|
for (let i = 0; i < this.children.length; i++) {
|
||||||
const child = this.children[i]
|
const child = this.children[i]
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ export class SplitContainer {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
async serialize () {
|
async serialize (): Promise<RecoveryToken> {
|
||||||
const children: any[] = []
|
const children: any[] = []
|
||||||
for (const child of this.children) {
|
for (const child of this.children) {
|
||||||
if (child instanceof SplitContainer) {
|
if (child instanceof SplitContainer) {
|
||||||
@@ -140,7 +140,7 @@ export interface SplitSpannerInfo {
|
|||||||
`,
|
`,
|
||||||
styles: [require('./splitTab.component.scss')],
|
styles: [require('./splitTab.component.scss')],
|
||||||
})
|
})
|
||||||
export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDestroy {
|
export class SplitTabComponent extends BaseTabComponent implements AfterViewInit, OnDestroy {
|
||||||
static DIRECTIONS: SplitDirection[] = ['t', 'r', 'b', 'l']
|
static DIRECTIONS: SplitDirection[] = ['t', 'r', 'b', 'l']
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@@ -157,7 +157,11 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
/** @hidden */
|
/** @hidden */
|
||||||
_spanners: SplitSpannerInfo[] = []
|
_spanners: SplitSpannerInfo[] = []
|
||||||
|
|
||||||
private focusedTab: BaseTabComponent
|
/** @hidden */
|
||||||
|
_allFocusMode = false
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
private focusedTab: BaseTabComponent|null = null
|
||||||
private maximizedTab: BaseTabComponent|null = null
|
private maximizedTab: BaseTabComponent|null = null
|
||||||
private hotkeysSubscription: Subscription
|
private hotkeysSubscription: Subscription
|
||||||
private viewRefs: Map<BaseTabComponent, EmbeddedViewRef<any>> = new Map()
|
private viewRefs: Map<BaseTabComponent, EmbeddedViewRef<any>> = new Map()
|
||||||
@@ -166,6 +170,7 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
private tabRemoved = new Subject<BaseTabComponent>()
|
private tabRemoved = new Subject<BaseTabComponent>()
|
||||||
private splitAdjusted = new Subject<SplitSpannerInfo>()
|
private splitAdjusted = new Subject<SplitSpannerInfo>()
|
||||||
private focusChanged = new Subject<BaseTabComponent>()
|
private focusChanged = new Subject<BaseTabComponent>()
|
||||||
|
private initialized = new Subject<void>()
|
||||||
|
|
||||||
get tabAdded$ (): Observable<BaseTabComponent> { return this.tabAdded }
|
get tabAdded$ (): Observable<BaseTabComponent> { return this.tabAdded }
|
||||||
get tabRemoved$ (): Observable<BaseTabComponent> { return this.tabRemoved }
|
get tabRemoved$ (): Observable<BaseTabComponent> { return this.tabRemoved }
|
||||||
@@ -180,6 +185,11 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
*/
|
*/
|
||||||
get focusChanged$ (): Observable<BaseTabComponent> { return this.focusChanged }
|
get focusChanged$ (): Observable<BaseTabComponent> { return this.focusChanged }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired once tab layout is created and child tabs can be added
|
||||||
|
*/
|
||||||
|
get initialized$ (): Observable<void> { return this.initialized }
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
constructor (
|
constructor (
|
||||||
private hotkeys: HotkeysService,
|
private hotkeys: HotkeysService,
|
||||||
@@ -201,7 +211,7 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
this.blurred$.subscribe(() => this.getAllTabs().forEach(x => x.emitBlurred()))
|
this.blurred$.subscribe(() => this.getAllTabs().forEach(x => x.emitBlurred()))
|
||||||
|
|
||||||
this.hotkeysSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => {
|
this.hotkeysSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => {
|
||||||
if (!this.hasFocus) {
|
if (!this.hasFocus || !this.focusedTab) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch (hotkey) {
|
switch (hotkey) {
|
||||||
@@ -244,30 +254,33 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
async ngOnInit () {
|
async ngAfterViewInit (): Promise<void> {
|
||||||
if (this._recoveredState) {
|
if (this._recoveredState) {
|
||||||
await this.recoverContainer(this.root, this._recoveredState)
|
await this.recoverContainer(this.root, this._recoveredState)
|
||||||
this.layout()
|
this.layout()
|
||||||
setImmediate(() => {
|
setTimeout(() => {
|
||||||
if (this.hasFocus) {
|
if (this.hasFocus) {
|
||||||
this.getAllTabs().forEach(x => x.emitFocused())
|
for (const tab of this.getAllTabs()) {
|
||||||
this.focusAnyIn(this.root)
|
this.focus(tab)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
}, 100)
|
||||||
}
|
}
|
||||||
|
this.initialized.next()
|
||||||
|
this.initialized.complete()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
ngOnDestroy () {
|
ngOnDestroy (): void {
|
||||||
this.hotkeysSubscription.unsubscribe()
|
this.hotkeysSubscription.unsubscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns Flat list of all sub-tabs */
|
/** @returns Flat list of all sub-tabs */
|
||||||
getAllTabs () {
|
getAllTabs (): BaseTabComponent[] {
|
||||||
return this.root.getAllTabs()
|
return this.root.getAllTabs()
|
||||||
}
|
}
|
||||||
|
|
||||||
getFocusedTab (): BaseTabComponent {
|
getFocusedTab (): BaseTabComponent|null {
|
||||||
return this.focusedTab
|
return this.focusedTab
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,17 +288,15 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
return this.maximizedTab
|
return this.maximizedTab
|
||||||
}
|
}
|
||||||
|
|
||||||
focus (tab: BaseTabComponent) {
|
focus (tab: BaseTabComponent): void {
|
||||||
this.focusedTab = tab
|
this.focusedTab = tab
|
||||||
for (const x of this.getAllTabs()) {
|
for (const x of this.getAllTabs()) {
|
||||||
if (x !== tab) {
|
if (x !== tab) {
|
||||||
x.emitBlurred()
|
x.emitBlurred()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (tab) {
|
tab.emitFocused()
|
||||||
tab.emitFocused()
|
this.focusChanged.next(tab)
|
||||||
this.focusChanged.next(tab)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.maximizedTab !== tab) {
|
if (this.maximizedTab !== tab) {
|
||||||
this.maximizedTab = null
|
this.maximizedTab = null
|
||||||
@@ -293,7 +304,7 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
this.layout()
|
this.layout()
|
||||||
}
|
}
|
||||||
|
|
||||||
maximize (tab: BaseTabComponent|null) {
|
maximize (tab: BaseTabComponent|null): void {
|
||||||
this.maximizedTab = tab
|
this.maximizedTab = tab
|
||||||
this.layout()
|
this.layout()
|
||||||
}
|
}
|
||||||
@@ -301,7 +312,7 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
/**
|
/**
|
||||||
* Focuses the first available tab inside the given [[SplitContainer]]
|
* Focuses the first available tab inside the given [[SplitContainer]]
|
||||||
*/
|
*/
|
||||||
focusAnyIn (parent: BaseTabComponent | SplitContainer) {
|
focusAnyIn (parent?: BaseTabComponent | SplitContainer): void {
|
||||||
if (!parent) {
|
if (!parent) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -315,8 +326,10 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
/**
|
/**
|
||||||
* Inserts a new `tab` to the `side` of the `relative` tab
|
* Inserts a new `tab` to the `side` of the `relative` tab
|
||||||
*/
|
*/
|
||||||
addTab (tab: BaseTabComponent, relative: BaseTabComponent|null, side: SplitDirection) {
|
async addTab (tab: BaseTabComponent, relative: BaseTabComponent|null, side: SplitDirection): Promise<void> {
|
||||||
let target = (relative ? this.getParentOf(relative) : null) || this.root
|
tab.parent = this
|
||||||
|
|
||||||
|
let target = (relative ? this.getParentOf(relative) : null) ?? this.root
|
||||||
let insertIndex = relative ? target.children.indexOf(relative) : -1
|
let insertIndex = relative ? target.children.indexOf(relative) : -1
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -345,6 +358,9 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
target.children.splice(insertIndex, 0, tab)
|
target.children.splice(insertIndex, 0, tab)
|
||||||
|
|
||||||
this.recoveryStateChangedHint.next()
|
this.recoveryStateChangedHint.next()
|
||||||
|
|
||||||
|
await this.initialized$.toPromise()
|
||||||
|
|
||||||
this.attachTabView(tab)
|
this.attachTabView(tab)
|
||||||
|
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
@@ -354,7 +370,7 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
removeTab (tab: BaseTabComponent) {
|
removeTab (tab: BaseTabComponent): void {
|
||||||
const parent = this.getParentOf(tab)
|
const parent = this.getParentOf(tab)
|
||||||
if (!parent) {
|
if (!parent) {
|
||||||
return
|
return
|
||||||
@@ -364,11 +380,11 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
parent.children.splice(index, 1)
|
parent.children.splice(index, 1)
|
||||||
|
|
||||||
this.detachTabView(tab)
|
this.detachTabView(tab)
|
||||||
|
tab.parent = null
|
||||||
|
|
||||||
this.layout()
|
this.layout()
|
||||||
|
|
||||||
this.tabRemoved.next(tab)
|
this.tabRemoved.next(tab)
|
||||||
|
|
||||||
if (this.root.children.length === 0) {
|
if (this.root.children.length === 0) {
|
||||||
this.destroy()
|
this.destroy()
|
||||||
} else {
|
} else {
|
||||||
@@ -379,7 +395,11 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
/**
|
/**
|
||||||
* Moves focus in the given direction
|
* Moves focus in the given direction
|
||||||
*/
|
*/
|
||||||
navigate (dir: SplitDirection) {
|
navigate (dir: SplitDirection): void {
|
||||||
|
if (!this.focusedTab) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let rel: BaseTabComponent | SplitContainer = this.focusedTab
|
let rel: BaseTabComponent | SplitContainer = this.focusedTab
|
||||||
let parent = this.getParentOf(rel)
|
let parent = this.getParentOf(rel)
|
||||||
if (!parent) {
|
if (!parent) {
|
||||||
@@ -412,18 +432,19 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async splitTab (tab: BaseTabComponent, dir: SplitDirection) {
|
async splitTab (tab: BaseTabComponent, dir: SplitDirection): Promise<BaseTabComponent|null> {
|
||||||
const newTab = await this.tabsService.duplicate(tab)
|
const newTab = await this.tabsService.duplicate(tab)
|
||||||
if (newTab) {
|
if (newTab) {
|
||||||
this.addTab(newTab, tab, dir)
|
this.addTab(newTab, tab, dir)
|
||||||
}
|
}
|
||||||
|
return newTab
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns the immediate parent of `tab`
|
* @returns the immediate parent of `tab`
|
||||||
*/
|
*/
|
||||||
getParentOf (tab: BaseTabComponent | SplitContainer, root?: SplitContainer): SplitContainer|null {
|
getParentOf (tab: BaseTabComponent | SplitContainer, root?: SplitContainer): SplitContainer|null {
|
||||||
root = root || this.root
|
root = root ?? this.root
|
||||||
for (const child of root.children) {
|
for (const child of root.children) {
|
||||||
if (child instanceof SplitContainer) {
|
if (child instanceof SplitContainer) {
|
||||||
const r = this.getParentOf(tab, child)
|
const r = this.getParentOf(tab, child)
|
||||||
@@ -450,22 +471,28 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
async getCurrentProcess (): Promise<BaseTabProcess|null> {
|
async getCurrentProcess (): Promise<BaseTabProcess|null> {
|
||||||
return (await Promise.all(this.getAllTabs().map(x => x.getCurrentProcess()))).find(x => !!x) || null
|
return (await Promise.all(this.getAllTabs().map(x => x.getCurrentProcess()))).find(x => !!x) ?? null
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
onSpannerAdjusted (spanner: SplitSpannerInfo) {
|
onSpannerAdjusted (spanner: SplitSpannerInfo): void {
|
||||||
this.layout()
|
this.layout()
|
||||||
this.splitAdjusted.next(spanner)
|
this.splitAdjusted.next(spanner)
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy () {
|
destroy (): void {
|
||||||
super.destroy()
|
super.destroy()
|
||||||
for (const x of this.getAllTabs()) {
|
for (const x of this.getAllTabs()) {
|
||||||
x.destroy()
|
x.destroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
layout (): void {
|
||||||
|
this.root.normalize()
|
||||||
|
this._spanners = []
|
||||||
|
this.layoutInternal(this.root, 0, 0, 100, 100)
|
||||||
|
}
|
||||||
|
|
||||||
private attachTabView (tab: BaseTabComponent) {
|
private attachTabView (tab: BaseTabComponent) {
|
||||||
const ref = this.viewContainer.insert(tab.hostView) as EmbeddedViewRef<any> // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion
|
const ref = this.viewContainer.insert(tab.hostView) as EmbeddedViewRef<any> // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion
|
||||||
this.viewRefs.set(tab, ref)
|
this.viewRefs.set(tab, ref)
|
||||||
@@ -491,15 +518,9 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private layout () {
|
|
||||||
this.root.normalize()
|
|
||||||
this._spanners = []
|
|
||||||
this.layoutInternal(this.root, 0, 0, 100, 100)
|
|
||||||
}
|
|
||||||
|
|
||||||
private layoutInternal (root: SplitContainer, x: number, y: number, w: number, h: number) {
|
private layoutInternal (root: SplitContainer, x: number, y: number, w: number, h: number) {
|
||||||
const size = root.orientation === 'v' ? h : w
|
const size = root.orientation === 'v' ? h : w
|
||||||
const sizes = root.ratios.map(x => x * size)
|
const sizes = root.ratios.map(ratio => ratio * size)
|
||||||
|
|
||||||
root.x = x
|
root.x = x
|
||||||
root.y = y
|
root.y = y
|
||||||
@@ -515,21 +536,24 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
if (child instanceof SplitContainer) {
|
if (child instanceof SplitContainer) {
|
||||||
this.layoutInternal(child, childX, childY, childW, childH)
|
this.layoutInternal(child, childX, childY, childW, childH)
|
||||||
} else {
|
} else {
|
||||||
const element = this.viewRefs.get(child)!.rootNodes[0]
|
const viewRef = this.viewRefs.get(child)
|
||||||
element.classList.toggle('child', true)
|
if (viewRef) {
|
||||||
element.classList.toggle('maximized', child === this.maximizedTab)
|
const element = viewRef.rootNodes[0]
|
||||||
element.classList.toggle('minimized', this.maximizedTab && child !== this.maximizedTab)
|
element.classList.toggle('child', true)
|
||||||
element.classList.toggle('focused', child === this.focusedTab)
|
element.classList.toggle('maximized', child === this.maximizedTab)
|
||||||
element.style.left = `${childX}%`
|
element.classList.toggle('minimized', this.maximizedTab && child !== this.maximizedTab)
|
||||||
element.style.top = `${childY}%`
|
element.classList.toggle('focused', this._allFocusMode || child === this.focusedTab)
|
||||||
element.style.width = `${childW}%`
|
element.style.left = `${childX}%`
|
||||||
element.style.height = `${childH}%`
|
element.style.top = `${childY}%`
|
||||||
|
element.style.width = `${childW}%`
|
||||||
|
element.style.height = `${childH}%`
|
||||||
|
|
||||||
if (child === this.maximizedTab) {
|
if (child === this.maximizedTab) {
|
||||||
element.style.left = '5%'
|
element.style.left = '5%'
|
||||||
element.style.top = '5%'
|
element.style.top = '5%'
|
||||||
element.style.width = '90%'
|
element.style.width = '90%'
|
||||||
element.style.height = '90%'
|
element.style.height = '90%'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
offset += sizes[i]
|
offset += sizes[i]
|
||||||
@@ -558,20 +582,25 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
if (recovered) {
|
if (recovered) {
|
||||||
const tab = this.tabsService.create(recovered.type, recovered.options)
|
const tab = this.tabsService.create(recovered.type, recovered.options)
|
||||||
children.push(tab)
|
children.push(tab)
|
||||||
|
tab.parent = this
|
||||||
this.attachTabView(tab)
|
this.attachTabView(tab)
|
||||||
} else {
|
} else {
|
||||||
state.ratios.splice(state.children.indexOf(childState), 0)
|
state.ratios.splice(state.children.indexOf(childState), 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
while (root.ratios.length < root.children.length) {
|
||||||
|
root.ratios.push(1)
|
||||||
|
}
|
||||||
|
root.normalize()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SplitTabRecoveryProvider extends TabRecoveryProvider {
|
export class SplitTabRecoveryProvider extends TabRecoveryProvider {
|
||||||
async recover (recoveryToken: any): Promise<RecoveredTab|null> {
|
async recover (recoveryToken: RecoveryToken): Promise<RecoveredTab|null> {
|
||||||
if (recoveryToken && recoveryToken.type === 'app:split-tab') {
|
if (recoveryToken.type === 'app:split-tab') {
|
||||||
return {
|
return {
|
||||||
type: SplitTabComponent,
|
type: SplitTabComponent,
|
||||||
options: { _recoveredState: recoveryToken },
|
options: { _recoveredState: recoveryToken },
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import { Component, Input, HostBinding, ElementRef, Output, EventEmitter } from '@angular/core'
|
import { Component, Input, HostBinding, ElementRef, Output, EventEmitter } from '@angular/core'
|
||||||
import { SplitContainer } from './splitTab.component'
|
import { SplitContainer } from './splitTab.component'
|
||||||
|
|
||||||
@@ -33,8 +34,8 @@ export class SplitTabSpannerComponent {
|
|||||||
let current = start
|
let current = start
|
||||||
const oldPosition: number = this.isVertical ? this.element.nativeElement.offsetTop : this.element.nativeElement.offsetLeft
|
const oldPosition: number = this.isVertical ? this.element.nativeElement.offsetTop : this.element.nativeElement.offsetLeft
|
||||||
|
|
||||||
const dragHandler = (e: MouseEvent) => {
|
const dragHandler = (dragEvent: MouseEvent) => {
|
||||||
current = this.isVertical ? e.pageY : e.pageX
|
current = this.isVertical ? dragEvent.pageY : dragEvent.pageX
|
||||||
const newPosition = oldPosition + (current - start)
|
const newPosition = oldPosition + (current - start)
|
||||||
if (this.isVertical) {
|
if (this.isVertical) {
|
||||||
this.element.nativeElement.style.top = `${newPosition - this.marginOffset}px`
|
this.element.nativeElement.style.top = `${newPosition - this.marginOffset}px`
|
||||||
|
@@ -13,7 +13,7 @@ import { ToolbarButton, ToolbarButtonProvider } from '../api'
|
|||||||
export class StartPageComponent {
|
export class StartPageComponent {
|
||||||
version: string
|
version: string
|
||||||
|
|
||||||
constructor (
|
private constructor (
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
private domSanitizer: DomSanitizer,
|
private domSanitizer: DomSanitizer,
|
||||||
public homeBase: HomeBaseService,
|
public homeBase: HomeBaseService,
|
||||||
@@ -26,10 +26,10 @@ export class StartPageComponent {
|
|||||||
.map(provider => provider.provide())
|
.map(provider => provider.provide())
|
||||||
.reduce((a, b) => a.concat(b))
|
.reduce((a, b) => a.concat(b))
|
||||||
.filter(x => !!x.click)
|
.filter(x => !!x.click)
|
||||||
.sort((a: ToolbarButton, b: ToolbarButton) => (a.weight || 0) - (b.weight || 0))
|
.sort((a: ToolbarButton, b: ToolbarButton) => (a.weight ?? 0) - (b.weight ?? 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
sanitizeIcon (icon: string): any {
|
sanitizeIcon (icon?: string): any {
|
||||||
return this.domSanitizer.bypassSecurityTrustHtml(icon || '')
|
return this.domSanitizer.bypassSecurityTrustHtml(icon ?? '')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import { Component, Input, ViewChild, HostBinding, ViewContainerRef, OnChanges } from '@angular/core'
|
import { Component, Input, ViewChild, HostBinding, ViewContainerRef, OnChanges } from '@angular/core'
|
||||||
import { BaseTabComponent } from '../components/baseTab.component'
|
import { BaseTabComponent } from '../components/baseTab.component'
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
.progressbar([style.width]='progress + "%"', *ngIf='progress != null')
|
.progressbar([style.width]='progress + "%"', *ngIf='progress != null')
|
||||||
.index(
|
.index(*ngIf='!config.store.terminal.hideTabIndex',
|
||||||
#handle,
|
#handle,
|
||||||
[style.background-color]='tab.color',
|
[style.background-color]='tab.color',
|
||||||
) {{index + 1}}
|
) {{index + 1}}
|
||||||
.name([title]='tab.customTitle || tab.title') {{tab.customTitle || tab.title}}
|
.name([title]='tab.customTitle || tab.title') {{tab.customTitle || tab.title}}
|
||||||
button((click)='app.closeTab(tab, true)') ×
|
button(*ngIf='!config.store.terminal.hideCloseButton',(click)='app.closeTab(tab, true)') ×
|
||||||
|
@@ -13,6 +13,11 @@ $tabs-height: 38px;
|
|||||||
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
&.vertical {
|
||||||
|
flex: none;
|
||||||
|
height: $tabs-height;
|
||||||
|
}
|
||||||
|
|
||||||
.index {
|
.index {
|
||||||
flex: none;
|
flex: none;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
import type { MenuItemConstructorOptions } from 'electron'
|
||||||
import { Component, Input, Optional, Inject, HostBinding, HostListener, ViewChild, ElementRef } from '@angular/core'
|
import { Component, Input, Optional, Inject, HostBinding, HostListener, ViewChild, ElementRef } from '@angular/core'
|
||||||
import { SortableComponent } from 'ng2-dnd'
|
import { SortableComponent } from 'ng2-dnd'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
@@ -8,10 +10,11 @@ import { HotkeysService } from '../services/hotkeys.service'
|
|||||||
import { ElectronService } from '../services/electron.service'
|
import { ElectronService } from '../services/electron.service'
|
||||||
import { AppService } from '../services/app.service'
|
import { AppService } from '../services/app.service'
|
||||||
import { HostAppService, Platform } from '../services/hostApp.service'
|
import { HostAppService, Platform } from '../services/hostApp.service'
|
||||||
|
import { ConfigService } from '../services/config.service'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
export interface SortableComponentProxy {
|
export interface SortableComponentProxy {
|
||||||
setDragHandle (_: HTMLElement)
|
setDragHandle: (_: HTMLElement) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@@ -30,6 +33,7 @@ export class TabHeaderComponent {
|
|||||||
|
|
||||||
private constructor (
|
private constructor (
|
||||||
public app: AppService,
|
public app: AppService,
|
||||||
|
public config: ConfigService,
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
private hostApp: HostAppService,
|
private hostApp: HostAppService,
|
||||||
private ngbModal: NgbModal,
|
private ngbModal: NgbModal,
|
||||||
@@ -48,14 +52,17 @@ export class TabHeaderComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
if (this.hostApp.platform === Platform.macOS) {
|
|
||||||
this.parentDraggable.setDragHandle(this.handle.nativeElement)
|
|
||||||
}
|
|
||||||
this.tab.progress$.subscribe(progress => {
|
this.tab.progress$.subscribe(progress => {
|
||||||
this.progress = progress
|
this.progress = progress
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit () {
|
||||||
|
if (this.hostApp.platform === Platform.macOS) {
|
||||||
|
this.parentDraggable.setDragHandle(this.handle.nativeElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
showRenameTabModal (): void {
|
showRenameTabModal (): void {
|
||||||
const modal = this.ngbModal.open(RenameTabModalComponent)
|
const modal = this.ngbModal.open(RenameTabModalComponent)
|
||||||
modal.componentInstance.value = this.tab.customTitle || this.tab.title
|
modal.componentInstance.value = this.tab.customTitle || this.tab.title
|
||||||
@@ -65,8 +72,8 @@ export class TabHeaderComponent {
|
|||||||
}).catch(() => null)
|
}).catch(() => null)
|
||||||
}
|
}
|
||||||
|
|
||||||
async buildContextMenu (): Promise<Electron.MenuItemConstructorOptions[]> {
|
async buildContextMenu (): Promise<MenuItemConstructorOptions[]> {
|
||||||
let items: Electron.MenuItemConstructorOptions[] = []
|
let items: MenuItemConstructorOptions[] = []
|
||||||
for (const section of await Promise.all(this.contextMenuProviders.map(x => x.getItems(this.tab, this)))) {
|
for (const section of await Promise.all(this.contextMenuProviders.map(x => x.getItems(this.tab, this)))) {
|
||||||
items.push({ type: 'separator' })
|
items.push({ type: 'separator' })
|
||||||
items = items.concat(section)
|
items = items.concat(section)
|
||||||
|
@@ -1,19 +1,36 @@
|
|||||||
.mb-4
|
.container.mt-5.mb-5
|
||||||
.terminus-logo
|
.mb-4
|
||||||
h1.terminus-title Terminus
|
.terminus-logo
|
||||||
sup α
|
h1.terminus-title Terminus
|
||||||
|
sup α
|
||||||
|
|
||||||
.container
|
|
||||||
.text-center.mb-5 Thank you for downloading Terminus!
|
.text-center.mb-5 Thank you for downloading Terminus!
|
||||||
|
|
||||||
.form-line
|
.form-line
|
||||||
.header
|
.header
|
||||||
.title Enable analytics
|
.title Enable analytics
|
||||||
.description Help us track the number of Terminus installs across the world!
|
.description Help track the number of Terminus installs across the world!
|
||||||
toggle(
|
toggle([(ngModel)]='config.store.enableAnalytics')
|
||||||
[(ngModel)]='config.store.enableAnalytics',
|
|
||||||
(ngModelChange)='config.save(); config.requestRestart()',
|
|
||||||
)
|
.form-line
|
||||||
|
.header
|
||||||
|
.title Enable global hotkey (#[strong Ctrl-Space])
|
||||||
|
.description Toggles the Terminus window visibility
|
||||||
|
toggle([(ngModel)]='enableGlobalHotkey')
|
||||||
|
|
||||||
|
.form-line
|
||||||
|
.header
|
||||||
|
.title Enable #[strong SSH] plugin
|
||||||
|
.description Adds an SSH connection manager UI to Terminus
|
||||||
|
toggle([(ngModel)]='enableSSH')
|
||||||
|
|
||||||
|
.form-line
|
||||||
|
.header
|
||||||
|
.title Enable #[strong Serial] plugin
|
||||||
|
.description Allows attaching Terminus to serial ports
|
||||||
|
toggle([(ngModel)]='enableSerial')
|
||||||
|
|
||||||
|
|
||||||
.text-center.mt-5
|
.text-center.mt-5
|
||||||
button.btn.btn-primary((click)='closeAndDisable()') Close and never show again
|
button.btn.btn-primary((click)='closeAndDisable()') Close and never show again
|
||||||
|
@@ -2,5 +2,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
flex: 0 1 500px;
|
flex: auto;
|
||||||
|
max-height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { BaseTabComponent } from './baseTab.component'
|
import { BaseTabComponent } from './baseTab.component'
|
||||||
import { ConfigService } from '../services/config.service'
|
import { ConfigService } from '../services/config.service'
|
||||||
import { AppService } from '../services/app.service'
|
import { HostAppService } from '../services/hostApp.service'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Component({
|
@Component({
|
||||||
@@ -10,17 +11,33 @@ import { AppService } from '../services/app.service'
|
|||||||
styles: [require('./welcomeTab.component.scss')],
|
styles: [require('./welcomeTab.component.scss')],
|
||||||
})
|
})
|
||||||
export class WelcomeTabComponent extends BaseTabComponent {
|
export class WelcomeTabComponent extends BaseTabComponent {
|
||||||
|
enableSSH = false
|
||||||
|
enableSerial = false
|
||||||
|
enableGlobalHotkey = true
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private app: AppService,
|
private hostApp: HostAppService,
|
||||||
public config: ConfigService,
|
public config: ConfigService,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this.setTitle('Welcome')
|
this.setTitle('Welcome')
|
||||||
|
this.enableSSH = !config.store.pluginBlacklist.includes('ssh')
|
||||||
|
this.enableSerial = !config.store.pluginBlacklist.includes('serial')
|
||||||
}
|
}
|
||||||
|
|
||||||
closeAndDisable () {
|
closeAndDisable () {
|
||||||
this.config.store.enableWelcomeTab = false
|
this.config.store.enableWelcomeTab = false
|
||||||
|
this.config.store.pluginBlacklist = []
|
||||||
|
if (!this.enableSSH) {
|
||||||
|
this.config.store.pluginBlacklist.push('ssh')
|
||||||
|
}
|
||||||
|
if (!this.enableSerial) {
|
||||||
|
this.config.store.pluginBlacklist.push('serial')
|
||||||
|
}
|
||||||
|
if (!this.enableGlobalHotkey) {
|
||||||
|
this.config.store.hotkeys['toggle-window'] = []
|
||||||
|
}
|
||||||
this.config.save()
|
this.config.save()
|
||||||
this.app.closeTab(this)
|
this.hostApp.getWindow().reload()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,7 +12,8 @@ button {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
&:not(:hover):not(:active) {
|
&:not(:hover):not(:active) {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { HostAppService } from '../services/hostApp.service'
|
import { HostAppService } from '../services/hostApp.service'
|
||||||
import { AppService } from '../services/app.service'
|
import { AppService } from '../services/app.service'
|
||||||
@@ -9,11 +10,9 @@ import { AppService } from '../services/app.service'
|
|||||||
styles: [require('./windowControls.component.scss')],
|
styles: [require('./windowControls.component.scss')],
|
||||||
})
|
})
|
||||||
export class WindowControlsComponent {
|
export class WindowControlsComponent {
|
||||||
constructor (public hostApp: HostAppService, public app: AppService) { }
|
private constructor (public hostApp: HostAppService, public app: AppService) { }
|
||||||
|
|
||||||
async closeWindow () {
|
async closeWindow () {
|
||||||
if (await this.app.closeAllTabs()) {
|
this.app.closeWindow()
|
||||||
this.hostApp.closeWindow()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,6 +7,8 @@ hotkeys:
|
|||||||
- 'F11'
|
- 'F11'
|
||||||
close-tab:
|
close-tab:
|
||||||
- 'Ctrl-Shift-W'
|
- 'Ctrl-Shift-W'
|
||||||
|
reopen-tab:
|
||||||
|
- 'Ctrl-Shift-T'
|
||||||
toggle-last-tab: []
|
toggle-last-tab: []
|
||||||
rename-tab:
|
rename-tab:
|
||||||
- 'Ctrl-Shift-R'
|
- 'Ctrl-Shift-R'
|
||||||
@@ -16,6 +18,10 @@ hotkeys:
|
|||||||
previous-tab:
|
previous-tab:
|
||||||
- 'Ctrl-Shift-Left'
|
- 'Ctrl-Shift-Left'
|
||||||
- 'Ctrl-Shift-Tab'
|
- 'Ctrl-Shift-Tab'
|
||||||
|
move-tab-left:
|
||||||
|
- 'Ctrl-Shift-PageUp'
|
||||||
|
move-tab-right:
|
||||||
|
- 'Ctrl-Shift-PageDown'
|
||||||
tab-1:
|
tab-1:
|
||||||
- 'Alt-1'
|
- 'Alt-1'
|
||||||
tab-2:
|
tab-2:
|
||||||
|
@@ -7,6 +7,8 @@ hotkeys:
|
|||||||
- 'Ctrl+⌘+F'
|
- 'Ctrl+⌘+F'
|
||||||
close-tab:
|
close-tab:
|
||||||
- '⌘-W'
|
- '⌘-W'
|
||||||
|
reopen-tab:
|
||||||
|
- '⌘-Shift-T'
|
||||||
toggle-last-tab: []
|
toggle-last-tab: []
|
||||||
rename-tab:
|
rename-tab:
|
||||||
- '⌘-R'
|
- '⌘-R'
|
||||||
@@ -14,6 +16,10 @@ hotkeys:
|
|||||||
- 'Ctrl-Tab'
|
- 'Ctrl-Tab'
|
||||||
previous-tab:
|
previous-tab:
|
||||||
- 'Ctrl-Shift-Tab'
|
- 'Ctrl-Shift-Tab'
|
||||||
|
move-tab-left:
|
||||||
|
- '⌘-Shift-Left'
|
||||||
|
move-tab-right:
|
||||||
|
- '⌘-Shift-Right'
|
||||||
tab-1:
|
tab-1:
|
||||||
- '⌘-1'
|
- '⌘-1'
|
||||||
tab-2:
|
tab-2:
|
||||||
|
@@ -8,6 +8,8 @@ hotkeys:
|
|||||||
- 'Alt-Enter'
|
- 'Alt-Enter'
|
||||||
close-tab:
|
close-tab:
|
||||||
- 'Ctrl-Shift-W'
|
- 'Ctrl-Shift-W'
|
||||||
|
reopen-tab:
|
||||||
|
- 'Ctrl-Shift-T'
|
||||||
toggle-last-tab: []
|
toggle-last-tab: []
|
||||||
rename-tab:
|
rename-tab:
|
||||||
- 'Ctrl-Shift-R'
|
- 'Ctrl-Shift-R'
|
||||||
@@ -17,6 +19,10 @@ hotkeys:
|
|||||||
previous-tab:
|
previous-tab:
|
||||||
- 'Ctrl-Shift-Left'
|
- 'Ctrl-Shift-Left'
|
||||||
- 'Ctrl-Shift-Tab'
|
- 'Ctrl-Shift-Tab'
|
||||||
|
move-tab-left:
|
||||||
|
- 'Ctrl-Shift-PageUp'
|
||||||
|
move-tab-right:
|
||||||
|
- 'Ctrl-Shift-PageDown'
|
||||||
tab-1:
|
tab-1:
|
||||||
- 'Alt-1'
|
- 'Alt-1'
|
||||||
tab-2:
|
tab-2:
|
||||||
|
@@ -2,6 +2,8 @@ appearance:
|
|||||||
dock: off
|
dock: off
|
||||||
dockScreen: current
|
dockScreen: current
|
||||||
dockFill: 0.5
|
dockFill: 0.5
|
||||||
|
dockHideOnBlur: false
|
||||||
|
dockAlwaysOnTop: true
|
||||||
tabsLocation: top
|
tabsLocation: top
|
||||||
cycleTabs: true
|
cycleTabs: true
|
||||||
theme: Standard
|
theme: Standard
|
||||||
|
@@ -7,7 +7,7 @@ import { Directive, AfterViewInit, ElementRef } from '@angular/core'
|
|||||||
export class AutofocusDirective implements AfterViewInit {
|
export class AutofocusDirective implements AfterViewInit {
|
||||||
constructor (private el: ElementRef) { }
|
constructor (private el: ElementRef) { }
|
||||||
|
|
||||||
ngAfterViewInit () {
|
ngAfterViewInit (): void {
|
||||||
this.el.nativeElement.blur()
|
this.el.nativeElement.blur()
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.el.nativeElement.focus()
|
this.el.nativeElement.focus()
|
||||||
|
@@ -8,7 +8,7 @@ export class FastHtmlBindDirective implements OnChanges {
|
|||||||
@Input() fastHtmlBind: string
|
@Input() fastHtmlBind: string
|
||||||
constructor (private el: ElementRef) { }
|
constructor (private el: ElementRef) { }
|
||||||
|
|
||||||
ngOnChanges () {
|
ngOnChanges (): void {
|
||||||
this.el.nativeElement.innerHTML = this.fastHtmlBind || ''
|
this.el.nativeElement.innerHTML = this.fastHtmlBind || ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -25,6 +25,10 @@ export class AppHotkeyProvider extends HotkeyProvider {
|
|||||||
id: 'close-tab',
|
id: 'close-tab',
|
||||||
name: 'Close tab',
|
name: 'Close tab',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'reopen-tab',
|
||||||
|
name: 'Reopen last tab',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'toggle-last-tab',
|
id: 'toggle-last-tab',
|
||||||
name: 'Toggle last tab',
|
name: 'Toggle last tab',
|
||||||
@@ -37,6 +41,14 @@ export class AppHotkeyProvider extends HotkeyProvider {
|
|||||||
id: 'previous-tab',
|
id: 'previous-tab',
|
||||||
name: 'Previous tab',
|
name: 'Previous tab',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'move-tab-left',
|
||||||
|
name: 'Move tab to the left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'move-tab-right',
|
||||||
|
name: 'Move tab to the right',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'tab-1',
|
id: 'tab-1',
|
||||||
name: 'Tab 1',
|
name: 'Tab 1',
|
||||||
|
@@ -16,6 +16,7 @@ import { TitleBarComponent } from './components/titleBar.component'
|
|||||||
import { ToggleComponent } from './components/toggle.component'
|
import { ToggleComponent } from './components/toggle.component'
|
||||||
import { WindowControlsComponent } from './components/windowControls.component'
|
import { WindowControlsComponent } from './components/windowControls.component'
|
||||||
import { RenameTabModalComponent } from './components/renameTabModal.component'
|
import { RenameTabModalComponent } from './components/renameTabModal.component'
|
||||||
|
import { SelectorModalComponent } from './components/selectorModal.component'
|
||||||
import { SplitTabComponent, SplitTabRecoveryProvider } from './components/splitTab.component'
|
import { SplitTabComponent, SplitTabRecoveryProvider } from './components/splitTab.component'
|
||||||
import { SplitTabSpannerComponent } from './components/splitTabSpanner.component'
|
import { SplitTabSpannerComponent } from './components/splitTabSpanner.component'
|
||||||
import { WelcomeTabComponent } from './components/welcomeTab.component'
|
import { WelcomeTabComponent } from './components/welcomeTab.component'
|
||||||
@@ -35,7 +36,7 @@ import { ConfigService } from './services/config.service'
|
|||||||
import { StandardTheme, StandardCompactTheme, PaperTheme } from './theme'
|
import { StandardTheme, StandardCompactTheme, PaperTheme } from './theme'
|
||||||
import { CoreConfigProvider } from './config'
|
import { CoreConfigProvider } from './config'
|
||||||
import { AppHotkeyProvider } from './hotkeys'
|
import { AppHotkeyProvider } from './hotkeys'
|
||||||
import { TaskCompletionContextMenu, CommonOptionsContextMenu, CloseContextMenu } from './tabContextMenu'
|
import { TaskCompletionContextMenu, CommonOptionsContextMenu, TabManagementContextMenu } from './tabContextMenu'
|
||||||
|
|
||||||
import 'perfect-scrollbar/css/perfect-scrollbar.css'
|
import 'perfect-scrollbar/css/perfect-scrollbar.css'
|
||||||
import 'ng2-dnd/bundles/style.css'
|
import 'ng2-dnd/bundles/style.css'
|
||||||
@@ -53,7 +54,7 @@ const PROVIDERS = [
|
|||||||
{ provide: Theme, useClass: PaperTheme, multi: true },
|
{ provide: Theme, useClass: PaperTheme, multi: true },
|
||||||
{ provide: ConfigProvider, useClass: CoreConfigProvider, multi: true },
|
{ provide: ConfigProvider, useClass: CoreConfigProvider, multi: true },
|
||||||
{ provide: TabContextMenuItemProvider, useClass: CommonOptionsContextMenu, multi: true },
|
{ provide: TabContextMenuItemProvider, useClass: CommonOptionsContextMenu, multi: true },
|
||||||
{ provide: TabContextMenuItemProvider, useClass: CloseContextMenu, multi: true },
|
{ provide: TabContextMenuItemProvider, useClass: TabManagementContextMenu, multi: true },
|
||||||
{ provide: TabContextMenuItemProvider, useClass: TaskCompletionContextMenu, multi: true },
|
{ provide: TabContextMenuItemProvider, useClass: TaskCompletionContextMenu, multi: true },
|
||||||
{ provide: TabRecoveryProvider, useClass: SplitTabRecoveryProvider, multi: true },
|
{ provide: TabRecoveryProvider, useClass: SplitTabRecoveryProvider, multi: true },
|
||||||
{ provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true } },
|
{ provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true } },
|
||||||
@@ -65,7 +66,7 @@ const PROVIDERS = [
|
|||||||
BrowserModule,
|
BrowserModule,
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
NgbModule.forRoot(),
|
NgbModule,
|
||||||
PerfectScrollbarModule,
|
PerfectScrollbarModule,
|
||||||
DndModule.forRoot(),
|
DndModule.forRoot(),
|
||||||
],
|
],
|
||||||
@@ -82,6 +83,7 @@ const PROVIDERS = [
|
|||||||
SafeModeModalComponent,
|
SafeModeModalComponent,
|
||||||
AutofocusDirective,
|
AutofocusDirective,
|
||||||
FastHtmlBindDirective,
|
FastHtmlBindDirective,
|
||||||
|
SelectorModalComponent,
|
||||||
SplitTabComponent,
|
SplitTabComponent,
|
||||||
SplitTabSpannerComponent,
|
SplitTabSpannerComponent,
|
||||||
WelcomeTabComponent,
|
WelcomeTabComponent,
|
||||||
@@ -89,6 +91,7 @@ const PROVIDERS = [
|
|||||||
entryComponents: [
|
entryComponents: [
|
||||||
RenameTabModalComponent,
|
RenameTabModalComponent,
|
||||||
SafeModeModalComponent,
|
SafeModeModalComponent,
|
||||||
|
SelectorModalComponent,
|
||||||
SplitTabComponent,
|
SplitTabComponent,
|
||||||
WelcomeTabComponent,
|
WelcomeTabComponent,
|
||||||
],
|
],
|
||||||
|
@@ -2,9 +2,13 @@
|
|||||||
import { Observable, Subject, AsyncSubject } from 'rxjs'
|
import { Observable, Subject, AsyncSubject } from 'rxjs'
|
||||||
import { takeUntil } from 'rxjs/operators'
|
import { takeUntil } from 'rxjs/operators'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
|
||||||
import { BaseTabComponent } from '../components/baseTab.component'
|
import { BaseTabComponent } from '../components/baseTab.component'
|
||||||
import { SplitTabComponent } from '../components/splitTab.component'
|
import { SplitTabComponent } from '../components/splitTab.component'
|
||||||
|
import { SelectorModalComponent } from '../components/selectorModal.component'
|
||||||
|
import { SelectorOption } from '../api/selector'
|
||||||
|
import { RecoveryToken } from '../api/tabRecovery'
|
||||||
|
|
||||||
import { ConfigService } from './config.service'
|
import { ConfigService } from './config.service'
|
||||||
import { HostAppService } from './hostApp.service'
|
import { HostAppService } from './hostApp.service'
|
||||||
@@ -42,12 +46,13 @@ class CompletionObserver {
|
|||||||
export class AppService {
|
export class AppService {
|
||||||
tabs: BaseTabComponent[] = []
|
tabs: BaseTabComponent[] = []
|
||||||
|
|
||||||
get activeTab (): BaseTabComponent { return this._activeTab }
|
get activeTab (): BaseTabComponent|null { return this._activeTab ?? null }
|
||||||
|
|
||||||
private lastTabIndex = 0
|
private lastTabIndex = 0
|
||||||
private _activeTab: BaseTabComponent
|
private _activeTab: BaseTabComponent | null = null
|
||||||
|
private closedTabsStack: RecoveryToken[] = []
|
||||||
|
|
||||||
private activeTabChange = new Subject<BaseTabComponent>()
|
private activeTabChange = new Subject<BaseTabComponent|null>()
|
||||||
private tabsChanged = new Subject<void>()
|
private tabsChanged = new Subject<void>()
|
||||||
private tabOpened = new Subject<BaseTabComponent>()
|
private tabOpened = new Subject<BaseTabComponent>()
|
||||||
private tabClosed = new Subject<BaseTabComponent>()
|
private tabClosed = new Subject<BaseTabComponent>()
|
||||||
@@ -55,7 +60,7 @@ export class AppService {
|
|||||||
|
|
||||||
private completionObservers = new Map<BaseTabComponent, CompletionObserver>()
|
private completionObservers = new Map<BaseTabComponent, CompletionObserver>()
|
||||||
|
|
||||||
get activeTabChange$ (): Observable<BaseTabComponent> { return this.activeTabChange }
|
get activeTabChange$ (): Observable<BaseTabComponent|null> { return this.activeTabChange }
|
||||||
get tabOpened$ (): Observable<BaseTabComponent> { return this.tabOpened }
|
get tabOpened$ (): Observable<BaseTabComponent> { return this.tabOpened }
|
||||||
get tabsChanged$ (): Observable<void> { return this.tabsChanged }
|
get tabsChanged$ (): Observable<void> { return this.tabsChanged }
|
||||||
get tabClosed$ (): Observable<BaseTabComponent> { return this.tabClosed }
|
get tabClosed$ (): Observable<BaseTabComponent> { return this.tabClosed }
|
||||||
@@ -64,41 +69,45 @@ export class AppService {
|
|||||||
get ready$ (): Observable<void> { return this.ready }
|
get ready$ (): Observable<void> { return this.ready }
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
constructor (
|
private constructor (
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
private hostApp: HostAppService,
|
private hostApp: HostAppService,
|
||||||
private tabRecovery: TabRecoveryService,
|
private tabRecovery: TabRecoveryService,
|
||||||
private tabsService: TabsService,
|
private tabsService: TabsService,
|
||||||
|
private ngbModal: NgbModal,
|
||||||
) {
|
) {
|
||||||
if (hostApp.getWindow().id === 1) {
|
|
||||||
if (config.store.terminal.recoverTabs) {
|
|
||||||
this.tabRecovery.recoverTabs().then(tabs => {
|
|
||||||
for (const tab of tabs) {
|
|
||||||
this.openNewTabRaw(tab.type, tab.options)
|
|
||||||
}
|
|
||||||
this.startTabStorage()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
/** Continue to store the tabs even if the setting is currently off */
|
|
||||||
this.startTabStorage()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hostApp.windowFocused$.subscribe(() => {
|
|
||||||
this._activeTab?.emitFocused()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
startTabStorage () {
|
|
||||||
this.tabsChanged$.subscribe(() => {
|
this.tabsChanged$.subscribe(() => {
|
||||||
this.tabRecovery.saveTabs(this.tabs)
|
this.tabRecovery.saveTabs(this.tabs)
|
||||||
})
|
})
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
this.tabRecovery.saveTabs(this.tabs)
|
this.tabRecovery.saveTabs(this.tabs)
|
||||||
}, 30000)
|
}, 30000)
|
||||||
|
|
||||||
|
if (hostApp.getWindow().id === 1) {
|
||||||
|
if (config.store.terminal.recoverTabs) {
|
||||||
|
this.tabRecovery.recoverTabs().then(tabs => {
|
||||||
|
for (const tab of tabs) {
|
||||||
|
this.openNewTabRaw(tab.type, tab.options)
|
||||||
|
}
|
||||||
|
this.tabRecovery.enabled = true
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
/** Continue to store the tabs even if the setting is currently off */
|
||||||
|
this.tabRecovery.enabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hostApp.windowFocused$.subscribe(() => this._activeTab?.emitFocused())
|
||||||
|
|
||||||
|
this.tabClosed$.subscribe(async tab => {
|
||||||
|
const token = await tab.getRecoveryToken()
|
||||||
|
if (token) {
|
||||||
|
this.closedTabsStack.push(token)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
addTabRaw (tab: BaseTabComponent, index: number|null = null) {
|
addTabRaw (tab: BaseTabComponent, index: number|null = null): void {
|
||||||
if (index !== null) {
|
if (index !== null) {
|
||||||
this.tabs.splice(index, 0, tab)
|
this.tabs.splice(index, 0, tab)
|
||||||
} else {
|
} else {
|
||||||
@@ -141,7 +150,7 @@ export class AppService {
|
|||||||
* Adds a new tab **without** wrapping it in a SplitTabComponent
|
* Adds a new tab **without** wrapping it in a SplitTabComponent
|
||||||
* @param inputs Properties to be assigned on the new tab component instance
|
* @param inputs Properties to be assigned on the new tab component instance
|
||||||
*/
|
*/
|
||||||
openNewTabRaw (type: TabComponentType, inputs?: any): BaseTabComponent {
|
openNewTabRaw (type: TabComponentType, inputs?: Record<string, any>): BaseTabComponent {
|
||||||
const tab = this.tabsService.create(type, inputs)
|
const tab = this.tabsService.create(type, inputs)
|
||||||
this.addTabRaw(tab)
|
this.addTabRaw(tab)
|
||||||
return tab
|
return tab
|
||||||
@@ -151,7 +160,7 @@ export class AppService {
|
|||||||
* Adds a new tab while wrapping it in a SplitTabComponent
|
* Adds a new tab while wrapping it in a SplitTabComponent
|
||||||
* @param inputs Properties to be assigned on the new tab component instance
|
* @param inputs Properties to be assigned on the new tab component instance
|
||||||
*/
|
*/
|
||||||
openNewTab (type: TabComponentType, inputs?: any): BaseTabComponent {
|
openNewTab (type: TabComponentType, inputs?: Record<string, any>): BaseTabComponent {
|
||||||
const splitTab = this.tabsService.create(SplitTabComponent) as SplitTabComponent
|
const splitTab = this.tabsService.create(SplitTabComponent) as SplitTabComponent
|
||||||
const tab = this.tabsService.create(type, inputs)
|
const tab = this.tabsService.create(type, inputs)
|
||||||
splitTab.addTab(tab, null, 'r')
|
splitTab.addTab(tab, null, 'r')
|
||||||
@@ -159,12 +168,29 @@ export class AppService {
|
|||||||
return tab
|
return tab
|
||||||
}
|
}
|
||||||
|
|
||||||
selectTab (tab: BaseTabComponent) {
|
async reopenLastTab (): Promise<BaseTabComponent|null> {
|
||||||
if (this._activeTab === tab) {
|
const token = this.closedTabsStack.pop()
|
||||||
|
if (token) {
|
||||||
|
const recoveredTab = await this.tabRecovery.recoverTab(token)
|
||||||
|
if (recoveredTab) {
|
||||||
|
const tab = this.tabsService.create(recoveredTab.type, recoveredTab.options)
|
||||||
|
if (this.activeTab) {
|
||||||
|
this.addTabRaw(tab, this.tabs.indexOf(this.activeTab) + 1)
|
||||||
|
} else {
|
||||||
|
this.addTabRaw(tab)
|
||||||
|
}
|
||||||
|
return tab
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
selectTab (tab: BaseTabComponent|null): void {
|
||||||
|
if (tab && this._activeTab === tab) {
|
||||||
this._activeTab.emitFocused()
|
this._activeTab.emitFocused()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (this.tabs.includes(this._activeTab)) {
|
if (this._activeTab && this.tabs.includes(this._activeTab)) {
|
||||||
this.lastTabIndex = this.tabs.indexOf(this._activeTab)
|
this.lastTabIndex = this.tabs.indexOf(this._activeTab)
|
||||||
} else {
|
} else {
|
||||||
this.lastTabIndex = 0
|
this.lastTabIndex = 0
|
||||||
@@ -175,12 +201,10 @@ export class AppService {
|
|||||||
}
|
}
|
||||||
this._activeTab = tab
|
this._activeTab = tab
|
||||||
this.activeTabChange.next(tab)
|
this.activeTabChange.next(tab)
|
||||||
if (this._activeTab) {
|
setImmediate(() => {
|
||||||
setImmediate(() => {
|
this._activeTab?.emitFocused()
|
||||||
this._activeTab.emitFocused()
|
})
|
||||||
})
|
this.hostApp.setTitle(this._activeTab?.title)
|
||||||
this.hostApp.setTitle(this._activeTab.title)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getParentTab (tab: BaseTabComponent): SplitTabComponent|null {
|
getParentTab (tab: BaseTabComponent): SplitTabComponent|null {
|
||||||
@@ -195,14 +219,17 @@ export class AppService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Switches between the current tab and the previously active one */
|
/** Switches between the current tab and the previously active one */
|
||||||
toggleLastTab () {
|
toggleLastTab (): void {
|
||||||
if (!this.lastTabIndex || this.lastTabIndex >= this.tabs.length) {
|
if (!this.lastTabIndex || this.lastTabIndex >= this.tabs.length) {
|
||||||
this.lastTabIndex = 0
|
this.lastTabIndex = 0
|
||||||
}
|
}
|
||||||
this.selectTab(this.tabs[this.lastTabIndex])
|
this.selectTab(this.tabs[this.lastTabIndex])
|
||||||
}
|
}
|
||||||
|
|
||||||
nextTab () {
|
nextTab (): void {
|
||||||
|
if (!this._activeTab) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (this.tabs.length > 1) {
|
if (this.tabs.length > 1) {
|
||||||
const tabIndex = this.tabs.indexOf(this._activeTab)
|
const tabIndex = this.tabs.indexOf(this._activeTab)
|
||||||
if (tabIndex < this.tabs.length - 1) {
|
if (tabIndex < this.tabs.length - 1) {
|
||||||
@@ -213,7 +240,10 @@ export class AppService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
previousTab () {
|
previousTab (): void {
|
||||||
|
if (!this._activeTab) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (this.tabs.length > 1) {
|
if (this.tabs.length > 1) {
|
||||||
const tabIndex = this.tabs.indexOf(this._activeTab)
|
const tabIndex = this.tabs.indexOf(this._activeTab)
|
||||||
if (tabIndex > 0) {
|
if (tabIndex > 0) {
|
||||||
@@ -224,8 +254,43 @@ export class AppService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
moveSelectedTabLeft (): void {
|
||||||
|
if (!this._activeTab) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.tabs.length > 1) {
|
||||||
|
const tabIndex = this.tabs.indexOf(this._activeTab)
|
||||||
|
if (tabIndex > 0) {
|
||||||
|
this.swapTabs(this._activeTab, this.tabs[tabIndex - 1])
|
||||||
|
} else if (this.config.store.appearance.cycleTabs) {
|
||||||
|
this.swapTabs(this._activeTab, this.tabs[this.tabs.length - 1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
moveSelectedTabRight (): void {
|
||||||
|
if (!this._activeTab) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.tabs.length > 1) {
|
||||||
|
const tabIndex = this.tabs.indexOf(this._activeTab)
|
||||||
|
if (tabIndex < this.tabs.length - 1) {
|
||||||
|
this.swapTabs(this._activeTab, this.tabs[tabIndex + 1])
|
||||||
|
} else if (this.config.store.appearance.cycleTabs) {
|
||||||
|
this.swapTabs(this._activeTab, this.tabs[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
swapTabs (a: BaseTabComponent, b: BaseTabComponent): void {
|
||||||
|
const i1 = this.tabs.indexOf(a)
|
||||||
|
const i2 = this.tabs.indexOf(b)
|
||||||
|
this.tabs[i1] = b
|
||||||
|
this.tabs[i2] = a
|
||||||
|
}
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
emitTabsChanged () {
|
emitTabsChanged (): void {
|
||||||
this.tabsChanged.next()
|
this.tabsChanged.next()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,11 +304,12 @@ export class AppService {
|
|||||||
tab.destroy()
|
tab.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
async duplicateTab (tab: BaseTabComponent) {
|
async duplicateTab (tab: BaseTabComponent): Promise<BaseTabComponent|null> {
|
||||||
const dup = await this.tabsService.duplicate(tab)
|
const dup = await this.tabsService.duplicate(tab)
|
||||||
if (dup) {
|
if (dup) {
|
||||||
this.addTabRaw(dup, this.tabs.indexOf(tab) + 1)
|
this.addTabRaw(dup, this.tabs.indexOf(tab) + 1)
|
||||||
}
|
}
|
||||||
|
return dup
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -261,8 +327,18 @@ export class AppService {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async closeWindow (): Promise<void> {
|
||||||
|
this.tabRecovery.enabled = false
|
||||||
|
await this.tabRecovery.saveTabs(this.tabs)
|
||||||
|
if (await this.closeAllTabs()) {
|
||||||
|
this.hostApp.closeWindow()
|
||||||
|
} else {
|
||||||
|
this.tabRecovery.enabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
emitReady () {
|
emitReady (): void {
|
||||||
this.ready.next()
|
this.ready.next()
|
||||||
this.ready.complete()
|
this.ready.complete()
|
||||||
this.hostApp.emitReady()
|
this.hostApp.emitReady()
|
||||||
@@ -283,7 +359,15 @@ export class AppService {
|
|||||||
return this.completionObservers.get(tab)!.done$
|
return this.completionObservers.get(tab)!.done$
|
||||||
}
|
}
|
||||||
|
|
||||||
stopObservingTabCompletion (tab: BaseTabComponent) {
|
stopObservingTabCompletion (tab: BaseTabComponent): void {
|
||||||
this.completionObservers.delete(tab)
|
this.completionObservers.delete(tab)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showSelector <T> (name: string, options: SelectorOption<T>[]): Promise<T> {
|
||||||
|
const modal = this.ngbModal.open(SelectorModalComponent)
|
||||||
|
const instance: SelectorModalComponent<T> = modal.componentInstance
|
||||||
|
instance.name = name
|
||||||
|
instance.options = options
|
||||||
|
return modal.result as Promise<T>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -20,7 +20,7 @@ function isNonStructuralObjectMember (v): boolean {
|
|||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
export class ConfigProxy {
|
export class ConfigProxy {
|
||||||
constructor (real: any, defaults: any) {
|
constructor (real: Record<string, any>, defaults: Record<string, any>) {
|
||||||
for (const key in defaults) {
|
for (const key in defaults) {
|
||||||
if (isStructuralMember(defaults[key])) {
|
if (isStructuralMember(defaults[key])) {
|
||||||
if (!real[key]) {
|
if (!real[key]) {
|
||||||
@@ -71,8 +71,10 @@ export class ConfigProxy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getValue (_key: string): any { } // eslint-disable-line @typescript-eslint/no-empty-function
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function
|
||||||
setValue (_key: string, _value: any) { } // eslint-disable-line @typescript-eslint/no-empty-function
|
getValue (_key: string): any { }
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function
|
||||||
|
setValue (_key: string, _value: any) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
@@ -95,22 +97,19 @@ export class ConfigService {
|
|||||||
private changed = new Subject<void>()
|
private changed = new Subject<void>()
|
||||||
private _store: any
|
private _store: any
|
||||||
private defaults: any
|
private defaults: any
|
||||||
private servicesCache: { [id: string]: Function[] }|null = null
|
private servicesCache: Record<string, Function[]>|null = null // eslint-disable-line @typescript-eslint/ban-types
|
||||||
|
|
||||||
get changed$ (): Observable<void> { return this.changed }
|
get changed$ (): Observable<void> { return this.changed }
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
constructor (
|
private constructor (
|
||||||
electron: ElectronService,
|
electron: ElectronService,
|
||||||
private hostApp: HostAppService,
|
private hostApp: HostAppService,
|
||||||
@Inject(ConfigProvider) configProviders: ConfigProvider[],
|
@Inject(ConfigProvider) configProviders: ConfigProvider[],
|
||||||
) {
|
) {
|
||||||
this.path = path.join(electron.app.getPath('userData'), 'config.yaml')
|
this.path = path.join(electron.app.getPath('userData'), 'config.yaml')
|
||||||
this.defaults = configProviders.map(provider => {
|
this.defaults = configProviders.map(provider => {
|
||||||
let defaults = {}
|
let defaults = provider.platformDefaults[hostApp.platform] || {}
|
||||||
if (provider.platformDefaults) {
|
|
||||||
defaults = configMerge(defaults, provider.platformDefaults[hostApp.platform] || {})
|
|
||||||
}
|
|
||||||
if (provider.defaults) {
|
if (provider.defaults) {
|
||||||
defaults = configMerge(defaults, provider.defaults)
|
defaults = configMerge(defaults, provider.defaults)
|
||||||
}
|
}
|
||||||
@@ -124,7 +123,7 @@ export class ConfigService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaults () {
|
getDefaults (): Record<string, any> {
|
||||||
const cleanup = o => {
|
const cleanup = o => {
|
||||||
if (o instanceof Array) {
|
if (o instanceof Array) {
|
||||||
return o.map(cleanup)
|
return o.map(cleanup)
|
||||||
@@ -153,9 +152,11 @@ export class ConfigService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
save (): void {
|
save (): void {
|
||||||
|
// Scrub undefined values
|
||||||
|
this._store = JSON.parse(JSON.stringify(this._store))
|
||||||
fs.writeFileSync(this.path, yaml.safeDump(this._store), 'utf8')
|
fs.writeFileSync(this.path, yaml.safeDump(this._store), 'utf8')
|
||||||
this.emitChange()
|
this.emitChange()
|
||||||
this.hostApp.broadcastConfigChange()
|
this.hostApp.broadcastConfigChange(JSON.parse(JSON.stringify(this.store)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -185,15 +186,15 @@ export class ConfigService {
|
|||||||
*
|
*
|
||||||
* @typeparam T Base provider type
|
* @typeparam T Base provider type
|
||||||
*/
|
*/
|
||||||
enabledServices<T extends object> (services: T[]): T[] {
|
enabledServices<T extends object> (services: T[]): T[] { // eslint-disable-line @typescript-eslint/ban-types
|
||||||
if (!this.servicesCache) {
|
if (!this.servicesCache) {
|
||||||
this.servicesCache = {}
|
this.servicesCache = {}
|
||||||
const ngModule = window['rootModule'].ngInjectorDef
|
const ngModule = window['rootModule'].ɵinj
|
||||||
for (const imp of ngModule.imports) {
|
for (const imp of ngModule.imports) {
|
||||||
const module = imp['ngModule'] || imp
|
const module = imp.ngModule || imp
|
||||||
if (module.ngInjectorDef && module.ngInjectorDef.providers) {
|
if (module.ɵinj?.providers) {
|
||||||
this.servicesCache[module['pluginName']] = module.ngInjectorDef.providers.map(provider => {
|
this.servicesCache[module.pluginName] = module.ɵinj.providers.map(provider => {
|
||||||
return provider['useClass'] || provider
|
return provider.useClass || provider
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import type { Display } from 'electron'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { ConfigService } from '../services/config.service'
|
import { ConfigService } from '../services/config.service'
|
||||||
import { ElectronService } from '../services/electron.service'
|
import { ElectronService } from '../services/electron.service'
|
||||||
@@ -6,16 +7,16 @@ import { HostAppService, Bounds } from '../services/hostApp.service'
|
|||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class DockingService {
|
export class DockingService {
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
constructor (
|
private constructor (
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
private hostApp: HostAppService,
|
private hostApp: HostAppService,
|
||||||
) {
|
) {
|
||||||
electron.screen.on('display-removed', () => this.repositionWindow())
|
hostApp.displaysChanged$.subscribe(() => this.repositionWindow())
|
||||||
electron.screen.on('display-metrics-changed', () => this.repositionWindow())
|
hostApp.displayMetricsChanged$.subscribe(() => this.repositionWindow())
|
||||||
}
|
}
|
||||||
|
|
||||||
dock () {
|
dock (): void {
|
||||||
const dockSide = this.config.store.appearance.dock
|
const dockSide = this.config.store.appearance.dock
|
||||||
|
|
||||||
if (dockSide === 'off') {
|
if (dockSide === 'off') {
|
||||||
@@ -25,6 +26,7 @@ export class DockingService {
|
|||||||
|
|
||||||
let display = this.electron.screen.getAllDisplays()
|
let display = this.electron.screen.getAllDisplays()
|
||||||
.filter(x => x.id === this.config.store.appearance.dockScreen)[0]
|
.filter(x => x.id === this.config.store.appearance.dockScreen)[0]
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
if (!display) {
|
if (!display) {
|
||||||
display = this.getCurrentScreen()
|
display = this.getCurrentScreen()
|
||||||
}
|
}
|
||||||
@@ -53,24 +55,27 @@ export class DockingService {
|
|||||||
newBounds.y = display.bounds.y
|
newBounds.y = display.bounds.y
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hostApp.setAlwaysOnTop(true)
|
const alwaysOnTop = this.config.store.appearance.dockAlwaysOnTop
|
||||||
|
|
||||||
|
this.hostApp.setAlwaysOnTop(alwaysOnTop)
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
this.hostApp.setBounds(newBounds)
|
this.hostApp.setBounds(newBounds)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentScreen () {
|
getCurrentScreen (): Display {
|
||||||
return this.electron.screen.getDisplayNearestPoint(this.electron.screen.getCursorScreenPoint())
|
return this.electron.screen.getDisplayNearestPoint(this.electron.screen.getCursorScreenPoint())
|
||||||
}
|
}
|
||||||
|
|
||||||
getScreens () {
|
getScreens (): Display[] {
|
||||||
const primaryDisplayID = this.electron.screen.getPrimaryDisplay().id
|
const primaryDisplayID = this.electron.screen.getPrimaryDisplay().id
|
||||||
return this.electron.screen.getAllDisplays().sort((a, b) =>
|
return this.electron.screen.getAllDisplays().sort((a, b) =>
|
||||||
a.bounds.x === b.bounds.x ? a.bounds.y - b.bounds.y : a.bounds.x - b.bounds.x
|
a.bounds.x === b.bounds.x ? a.bounds.y - b.bounds.y : a.bounds.x - b.bounds.x
|
||||||
).map((display,index) => {
|
).map((display, index) => {
|
||||||
return {
|
return {
|
||||||
|
...display,
|
||||||
id: display.id,
|
id: display.id,
|
||||||
name: display.id === primaryDisplayID ? 'Primary Display' : `Display ${index +1}`,
|
name: display.id === primaryDisplayID ? 'Primary Display' : `Display ${index + 1}`,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { TouchBar, BrowserWindow, Menu, MenuItem, NativeImage } from 'electron'
|
import { App, IpcRenderer, Shell, Dialog, Clipboard, GlobalShortcut, Screen, Remote, AutoUpdater, TouchBar, BrowserWindow, Menu, MenuItem, NativeImage, MessageBoxOptions } from 'electron'
|
||||||
|
|
||||||
export interface MessageBoxResponse {
|
export interface MessageBoxResponse {
|
||||||
response: number
|
response: number
|
||||||
@@ -8,16 +8,16 @@ export interface MessageBoxResponse {
|
|||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class ElectronService {
|
export class ElectronService {
|
||||||
app: Electron.App
|
app: App
|
||||||
ipcRenderer: Electron.IpcRenderer
|
ipcRenderer: IpcRenderer
|
||||||
shell: Electron.Shell
|
shell: Shell
|
||||||
dialog: Electron.Dialog
|
dialog: Dialog
|
||||||
clipboard: Electron.Clipboard
|
clipboard: Clipboard
|
||||||
globalShortcut: Electron.GlobalShortcut
|
globalShortcut: GlobalShortcut
|
||||||
nativeImage: typeof NativeImage
|
nativeImage: typeof NativeImage
|
||||||
screen: Electron.Screen
|
screen: Screen
|
||||||
remote: Electron.Remote
|
remote: Remote
|
||||||
autoUpdater: Electron.AutoUpdater
|
autoUpdater: AutoUpdater
|
||||||
TouchBar: typeof TouchBar
|
TouchBar: typeof TouchBar
|
||||||
BrowserWindow: typeof BrowserWindow
|
BrowserWindow: typeof BrowserWindow
|
||||||
Menu: typeof Menu
|
Menu: typeof Menu
|
||||||
@@ -25,7 +25,7 @@ export class ElectronService {
|
|||||||
private electron: any
|
private electron: any
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
constructor () {
|
private constructor () {
|
||||||
this.electron = require('electron')
|
this.electron = require('electron')
|
||||||
this.remote = this.electron.remote
|
this.remote = this.electron.remote
|
||||||
this.app = this.remote.app
|
this.app = this.remote.app
|
||||||
@@ -43,18 +43,9 @@ export class ElectronService {
|
|||||||
this.MenuItem = this.remote.MenuItem
|
this.MenuItem = this.remote.MenuItem
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes OS focus from Terminus' window
|
|
||||||
*/
|
|
||||||
loseFocus () {
|
|
||||||
if (process.platform === 'darwin') {
|
|
||||||
this.remote.Menu.sendActionToFirstResponder('hide:')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async showMessageBox (
|
async showMessageBox (
|
||||||
browserWindow: Electron.BrowserWindow,
|
browserWindow: BrowserWindow,
|
||||||
options: Electron.MessageBoxOptions
|
options: MessageBoxOptions
|
||||||
): Promise<MessageBoxResponse> {
|
): Promise<MessageBoxResponse> {
|
||||||
return this.dialog.showMessageBox(browserWindow, options)
|
return this.dialog.showMessageBox(browserWindow, options)
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@ import { Injectable } from '@angular/core'
|
|||||||
import { ElectronService } from './electron.service'
|
import { ElectronService } from './electron.service'
|
||||||
import { ConfigService } from './config.service'
|
import { ConfigService } from './config.service'
|
||||||
import * as mixpanel from 'mixpanel'
|
import * as mixpanel from 'mixpanel'
|
||||||
import * as uuidv4 from 'uuid/v4'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class HomeBaseService {
|
export class HomeBaseService {
|
||||||
@@ -11,7 +11,7 @@ export class HomeBaseService {
|
|||||||
mixpanel: any
|
mixpanel: any
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
constructor (
|
private constructor (
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
) {
|
) {
|
||||||
@@ -22,11 +22,11 @@ export class HomeBaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
openGitHub () {
|
openGitHub (): void {
|
||||||
this.electron.shell.openExternal('https://github.com/eugeny/terminus')
|
this.electron.shell.openExternal('https://github.com/eugeny/terminus')
|
||||||
}
|
}
|
||||||
|
|
||||||
reportBug () {
|
reportBug (): void {
|
||||||
let body = `Version: ${this.appVersion}\n`
|
let body = `Version: ${this.appVersion}\n`
|
||||||
body += `Platform: ${os.platform()} ${os.release()}\n`
|
body += `Platform: ${os.platform()} ${os.release()}\n`
|
||||||
const label = {
|
const label = {
|
||||||
@@ -44,7 +44,7 @@ export class HomeBaseService {
|
|||||||
this.electron.shell.openExternal(`https://github.com/eugeny/terminus/issues/new?body=${encodeURIComponent(body)}&labels=${label}`)
|
this.electron.shell.openExternal(`https://github.com/eugeny/terminus/issues/new?body=${encodeURIComponent(body)}&labels=${label}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
enableAnalytics () {
|
enableAnalytics (): void {
|
||||||
if (!window.localStorage.analyticsUserID) {
|
if (!window.localStorage.analyticsUserID) {
|
||||||
window.localStorage.analyticsUserID = uuidv4()
|
window.localStorage.analyticsUserID = uuidv4()
|
||||||
}
|
}
|
||||||
@@ -56,9 +56,9 @@ export class HomeBaseService {
|
|||||||
this.mixpanel.track('launch', this.getAnalyticsProperties())
|
this.mixpanel.track('launch', this.getAnalyticsProperties())
|
||||||
}
|
}
|
||||||
|
|
||||||
getAnalyticsProperties () {
|
getAnalyticsProperties (): Record<string, string> {
|
||||||
return {
|
return {
|
||||||
distinct_id: window.localStorage.analyticsUserID, // eslint-disable-line @typescript-eslint/camelcase
|
distinct_id: window.localStorage.analyticsUserID,
|
||||||
platform: process.platform,
|
platform: process.platform,
|
||||||
os: os.release(),
|
os: os.release(),
|
||||||
version: this.appVersion,
|
version: this.appVersion,
|
||||||
|
@@ -1,12 +1,17 @@
|
|||||||
|
import type { BrowserWindow, TouchBar, MenuItemConstructorOptions } from 'electron'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
|
import * as fs from 'mz/fs'
|
||||||
import shellEscape from 'shell-escape'
|
import shellEscape from 'shell-escape'
|
||||||
import { Observable, Subject } from 'rxjs'
|
import { Observable, Subject } from 'rxjs'
|
||||||
import { Injectable, NgZone, EventEmitter } from '@angular/core'
|
import { Injectable, NgZone, EventEmitter } from '@angular/core'
|
||||||
import { ElectronService } from './electron.service'
|
import { ElectronService } from './electron.service'
|
||||||
import { Logger, LogService } from './log.service'
|
import { Logger, LogService } from './log.service'
|
||||||
|
import { isWindowsBuild, WIN_BUILD_FLUENT_BG_SUPPORTED } from '../utils'
|
||||||
|
|
||||||
export enum Platform {
|
export enum Platform {
|
||||||
Linux, macOS, Windows,
|
Linux = 'Linux',
|
||||||
|
macOS = 'macOS',
|
||||||
|
Windows = 'Windows',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Bounds {
|
export interface Bounds {
|
||||||
@@ -41,6 +46,7 @@ export class HostAppService {
|
|||||||
private windowMoved = new Subject<void>()
|
private windowMoved = new Subject<void>()
|
||||||
private windowFocused = new Subject<void>()
|
private windowFocused = new Subject<void>()
|
||||||
private displayMetricsChanged = new Subject<void>()
|
private displayMetricsChanged = new Subject<void>()
|
||||||
|
private displaysChanged = new Subject<void>()
|
||||||
private logger: Logger
|
private logger: Logger
|
||||||
private windowId: number
|
private windowId: number
|
||||||
|
|
||||||
@@ -90,6 +96,8 @@ export class HostAppService {
|
|||||||
|
|
||||||
get displayMetricsChanged$ (): Observable<void> { return this.displayMetricsChanged }
|
get displayMetricsChanged$ (): Observable<void> { return this.displayMetricsChanged }
|
||||||
|
|
||||||
|
get displaysChanged$ (): Observable<void> { return this.displaysChanged }
|
||||||
|
|
||||||
private constructor (
|
private constructor (
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
@@ -139,9 +147,14 @@ export class HostAppService {
|
|||||||
this.zone.run(() => this.displayMetricsChanged.next())
|
this.zone.run(() => this.displayMetricsChanged.next())
|
||||||
})
|
})
|
||||||
|
|
||||||
electron.ipcRenderer.on('host:second-instance', (_$event, argv: any, cwd: string) => this.zone.run(() => {
|
electron.ipcRenderer.on('host:displays-changed', () => {
|
||||||
|
this.zone.run(() => this.displaysChanged.next())
|
||||||
|
})
|
||||||
|
|
||||||
|
electron.ipcRenderer.on('cli', (_$event, argv: any, cwd: string, secondInstance: boolean) => this.zone.run(async () => {
|
||||||
this.logger.info('Second instance', argv)
|
this.logger.info('Second instance', argv)
|
||||||
const op = argv._[0]
|
const op = argv._[0]
|
||||||
|
const opAsPath = path.resolve(cwd, op)
|
||||||
if (op === 'open') {
|
if (op === 'open') {
|
||||||
this.cliOpenDirectory.next(path.resolve(cwd, argv.directory))
|
this.cliOpenDirectory.next(path.resolve(cwd, argv.directory))
|
||||||
} else if (op === 'run') {
|
} else if (op === 'run') {
|
||||||
@@ -156,7 +169,11 @@ export class HostAppService {
|
|||||||
this.cliOpenProfile.next(argv.profileName)
|
this.cliOpenProfile.next(argv.profileName)
|
||||||
} else if (op === undefined) {
|
} else if (op === undefined) {
|
||||||
this.newWindow()
|
this.newWindow()
|
||||||
} else {
|
} else if ((await fs.lstat(opAsPath)).isDirectory()) {
|
||||||
|
this.cliOpenDirectory.next(opAsPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (secondInstance) {
|
||||||
this.secondInstance.next()
|
this.secondInstance.next()
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
@@ -164,53 +181,57 @@ export class HostAppService {
|
|||||||
electron.ipcRenderer.on('host:config-change', () => this.zone.run(() => {
|
electron.ipcRenderer.on('host:config-change', () => this.zone.run(() => {
|
||||||
this.configChangeBroadcast.next()
|
this.configChangeBroadcast.next()
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
if (isWindowsBuild(WIN_BUILD_FLUENT_BG_SUPPORTED)) {
|
||||||
|
electron.ipcRenderer.send('window-set-disable-vibrancy-while-dragging', true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current remote [[BrowserWindow]]
|
* Returns the current remote [[BrowserWindow]]
|
||||||
*/
|
*/
|
||||||
getWindow () {
|
getWindow (): BrowserWindow {
|
||||||
return this.electron.BrowserWindow.fromId(this.windowId)
|
return this.electron.BrowserWindow.fromId(this.windowId)!
|
||||||
}
|
}
|
||||||
|
|
||||||
newWindow () {
|
newWindow (): void {
|
||||||
this.electron.ipcRenderer.send('app:new-window')
|
this.electron.ipcRenderer.send('app:new-window')
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleFullscreen () {
|
toggleFullscreen (): void {
|
||||||
const window = this.getWindow()
|
const window = this.getWindow()
|
||||||
window.setFullScreen(!this.isFullScreen)
|
window.setFullScreen(!this.isFullScreen)
|
||||||
}
|
}
|
||||||
|
|
||||||
openDevTools () {
|
openDevTools (): void {
|
||||||
this.getWindow().webContents.openDevTools({ mode: 'undocked' })
|
this.getWindow().webContents.openDevTools({ mode: 'undocked' })
|
||||||
}
|
}
|
||||||
|
|
||||||
focusWindow () {
|
focusWindow (): void {
|
||||||
this.electron.ipcRenderer.send('window-focus')
|
this.electron.ipcRenderer.send('window-focus')
|
||||||
}
|
}
|
||||||
|
|
||||||
minimize () {
|
minimize (): void {
|
||||||
this.electron.ipcRenderer.send('window-minimize')
|
this.electron.ipcRenderer.send('window-minimize')
|
||||||
}
|
}
|
||||||
|
|
||||||
maximize () {
|
maximize (): void {
|
||||||
this.electron.ipcRenderer.send('window-maximize')
|
this.electron.ipcRenderer.send('window-maximize')
|
||||||
}
|
}
|
||||||
|
|
||||||
unmaximize () {
|
unmaximize (): void {
|
||||||
this.electron.ipcRenderer.send('window-unmaximize')
|
this.electron.ipcRenderer.send('window-unmaximize')
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleMaximize () {
|
toggleMaximize (): void {
|
||||||
this.electron.ipcRenderer.send('window-toggle-maximize')
|
this.electron.ipcRenderer.send('window-toggle-maximize')
|
||||||
}
|
}
|
||||||
|
|
||||||
setBounds (bounds: Bounds) {
|
setBounds (bounds: Bounds): void {
|
||||||
this.electron.ipcRenderer.send('window-set-bounds', bounds)
|
this.electron.ipcRenderer.send('window-set-bounds', bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
setAlwaysOnTop (flag: boolean) {
|
setAlwaysOnTop (flag: boolean): void {
|
||||||
this.electron.ipcRenderer.send('window-set-always-on-top', flag)
|
this.electron.ipcRenderer.send('window-set-always-on-top', flag)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,48 +240,50 @@ export class HostAppService {
|
|||||||
*
|
*
|
||||||
* @param type `null`, or `fluent` when supported (Windowd only)
|
* @param type `null`, or `fluent` when supported (Windowd only)
|
||||||
*/
|
*/
|
||||||
setVibrancy (enable: boolean, type: string) {
|
setVibrancy (enable: boolean, type: string|null): void {
|
||||||
|
if (this.platform === Platform.Windows && !isWindowsBuild(WIN_BUILD_FLUENT_BG_SUPPORTED)) {
|
||||||
|
type = null
|
||||||
|
}
|
||||||
document.body.classList.toggle('vibrant', enable)
|
document.body.classList.toggle('vibrant', enable)
|
||||||
if (this.platform === Platform.macOS) {
|
this.electron.ipcRenderer.send('window-set-vibrancy', enable, type)
|
||||||
this.getWindow().setVibrancy(enable ? 'dark' : null as any) // electron issue 20269
|
|
||||||
}
|
|
||||||
if (this.platform === Platform.Windows) {
|
|
||||||
this.electron.ipcRenderer.send('window-set-vibrancy', enable, type)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setTitle (title: string) {
|
setTitle (title?: string): void {
|
||||||
this.electron.ipcRenderer.send('window-set-title', title)
|
this.electron.ipcRenderer.send('window-set-title', title ?? 'Terminus')
|
||||||
}
|
}
|
||||||
|
|
||||||
setTouchBar (touchBar: Electron.TouchBar) {
|
setTouchBar (touchBar: TouchBar): void {
|
||||||
this.getWindow().setTouchBar(touchBar)
|
this.getWindow().setTouchBar(touchBar)
|
||||||
}
|
}
|
||||||
|
|
||||||
popupContextMenu (menuDefinition: Electron.MenuItemConstructorOptions[]) {
|
popupContextMenu (menuDefinition: MenuItemConstructorOptions[]): void {
|
||||||
this.electron.Menu.buildFromTemplate(menuDefinition).popup({})
|
this.electron.Menu.buildFromTemplate(menuDefinition).popup({})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies other windows of config file changes
|
* Notifies other windows of config file changes
|
||||||
*/
|
*/
|
||||||
broadcastConfigChange () {
|
broadcastConfigChange (configStore: Record<string, any>): void {
|
||||||
this.electron.ipcRenderer.send('app:config-change')
|
this.electron.ipcRenderer.send('app:config-change', configStore)
|
||||||
}
|
}
|
||||||
|
|
||||||
emitReady () {
|
emitReady (): void {
|
||||||
this.electron.ipcRenderer.send('app:ready')
|
this.electron.ipcRenderer.send('app:ready')
|
||||||
}
|
}
|
||||||
|
|
||||||
bringToFront () {
|
bringToFront (): void {
|
||||||
this.electron.ipcRenderer.send('window-bring-to-front')
|
this.electron.ipcRenderer.send('window-bring-to-front')
|
||||||
}
|
}
|
||||||
|
|
||||||
closeWindow () {
|
closeWindow (): void {
|
||||||
this.electron.ipcRenderer.send('window-close')
|
this.electron.ipcRenderer.send('window-close')
|
||||||
}
|
}
|
||||||
|
|
||||||
relaunch () {
|
registerGlobalHotkey (specs: string[]): void {
|
||||||
|
this.electron.ipcRenderer.send('app:register-global-hotkey', specs)
|
||||||
|
}
|
||||||
|
|
||||||
|
relaunch (): void {
|
||||||
if (this.isPortable) {
|
if (this.isPortable) {
|
||||||
this.electron.app.relaunch({ execPath: process.env.PORTABLE_EXECUTABLE_FILE })
|
this.electron.app.relaunch({ execPath: process.env.PORTABLE_EXECUTABLE_FILE })
|
||||||
} else {
|
} else {
|
||||||
@@ -269,7 +292,7 @@ export class HostAppService {
|
|||||||
this.electron.app.exit()
|
this.electron.app.exit()
|
||||||
}
|
}
|
||||||
|
|
||||||
quit () {
|
quit (): void {
|
||||||
this.logger.info('Quitting')
|
this.logger.info('Quitting')
|
||||||
this.electron.app.quit()
|
this.electron.app.quit()
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
import { Injectable, Inject, NgZone, EventEmitter } from '@angular/core'
|
import { Injectable, Inject, NgZone, EventEmitter } from '@angular/core'
|
||||||
|
import { Observable, Subject } from 'rxjs'
|
||||||
import { HotkeyDescription, HotkeyProvider } from '../api/hotkeyProvider'
|
import { HotkeyDescription, HotkeyProvider } from '../api/hotkeyProvider'
|
||||||
import { stringifyKeySequence } from './hotkeys.util'
|
import { stringifyKeySequence } from './hotkeys.util'
|
||||||
import { ConfigService } from '../services/config.service'
|
import { ConfigService } from './config.service'
|
||||||
import { ElectronService } from '../services/electron.service'
|
import { ElectronService } from './electron.service'
|
||||||
|
import { HostAppService } from './hostApp.service'
|
||||||
|
|
||||||
export interface PartialHotkeyMatch {
|
export interface PartialHotkeyMatch {
|
||||||
id: string
|
id: string
|
||||||
@@ -20,14 +22,23 @@ interface EventBufferEntry {
|
|||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class HotkeysService {
|
export class HotkeysService {
|
||||||
key = new EventEmitter<KeyboardEvent>()
|
key = new EventEmitter<KeyboardEvent>()
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
matchedHotkey = new EventEmitter<string>()
|
matchedHotkey = new EventEmitter<string>()
|
||||||
globalHotkey = new EventEmitter<void>()
|
|
||||||
|
/**
|
||||||
|
* Fired for each recognized hotkey
|
||||||
|
*/
|
||||||
|
get hotkey$ (): Observable<string> { return this._hotkey }
|
||||||
|
|
||||||
|
private _hotkey = new Subject<string>()
|
||||||
private currentKeystrokes: EventBufferEntry[] = []
|
private currentKeystrokes: EventBufferEntry[] = []
|
||||||
private disabledLevel = 0
|
private disabledLevel = 0
|
||||||
private hotkeyDescriptions: HotkeyDescription[] = []
|
private hotkeyDescriptions: HotkeyDescription[] = []
|
||||||
|
|
||||||
private constructor (
|
private constructor (
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
|
private hostApp: HostAppService,
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
@Inject(HotkeyProvider) private hotkeyProviders: HotkeyProvider[],
|
@Inject(HotkeyProvider) private hotkeyProviders: HotkeyProvider[],
|
||||||
@@ -49,6 +60,9 @@ export class HotkeysService {
|
|||||||
this.getHotkeyDescriptions().then(hotkeys => {
|
this.getHotkeyDescriptions().then(hotkeys => {
|
||||||
this.hotkeyDescriptions = hotkeys
|
this.hotkeyDescriptions = hotkeys
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// deprecated
|
||||||
|
this.hotkey$.subscribe(h => this.matchedHotkey.emit(h))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,7 +71,7 @@ export class HotkeysService {
|
|||||||
* @param name DOM event name
|
* @param name DOM event name
|
||||||
* @param nativeEvent event object
|
* @param nativeEvent event object
|
||||||
*/
|
*/
|
||||||
pushKeystroke (name: string, nativeEvent: KeyboardEvent) {
|
pushKeystroke (name: string, nativeEvent: KeyboardEvent): void {
|
||||||
(nativeEvent as any).event = name
|
(nativeEvent as any).event = name
|
||||||
this.currentKeystrokes.push({ event: nativeEvent, time: performance.now() })
|
this.currentKeystrokes.push({ event: nativeEvent, time: performance.now() })
|
||||||
}
|
}
|
||||||
@@ -65,26 +79,26 @@ export class HotkeysService {
|
|||||||
/**
|
/**
|
||||||
* Check the buffer for new complete keystrokes
|
* Check the buffer for new complete keystrokes
|
||||||
*/
|
*/
|
||||||
processKeystrokes () {
|
processKeystrokes (): void {
|
||||||
if (this.isEnabled()) {
|
if (this.isEnabled()) {
|
||||||
this.zone.run(() => {
|
this.zone.run(() => {
|
||||||
const matched = this.getCurrentFullyMatchedHotkey()
|
const matched = this.getCurrentFullyMatchedHotkey()
|
||||||
if (matched) {
|
if (matched) {
|
||||||
console.log('Matched hotkey', matched)
|
console.log('Matched hotkey', matched)
|
||||||
this.matchedHotkey.emit(matched)
|
this._hotkey.next(matched)
|
||||||
this.clearCurrentKeystrokes()
|
this.clearCurrentKeystrokes()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emitKeyEvent (nativeEvent: KeyboardEvent) {
|
emitKeyEvent (nativeEvent: KeyboardEvent): void {
|
||||||
this.zone.run(() => {
|
this.zone.run(() => {
|
||||||
this.key.emit(nativeEvent)
|
this.key.emit(nativeEvent)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
clearCurrentKeystrokes () {
|
clearCurrentKeystrokes (): void {
|
||||||
this.currentKeystrokes = []
|
this.currentKeystrokes = []
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,15 +156,15 @@ export class HotkeysService {
|
|||||||
return this.hotkeyDescriptions.filter((x) => x.id === id)[0]
|
return this.hotkeyDescriptions.filter((x) => x.id === id)[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
enable () {
|
enable (): void {
|
||||||
this.disabledLevel--
|
this.disabledLevel--
|
||||||
}
|
}
|
||||||
|
|
||||||
disable () {
|
disable (): void {
|
||||||
this.disabledLevel++
|
this.disabledLevel++
|
||||||
}
|
}
|
||||||
|
|
||||||
isEnabled () {
|
isEnabled (): boolean {
|
||||||
return this.disabledLevel === 0
|
return this.disabledLevel === 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +172,7 @@ export class HotkeysService {
|
|||||||
return (
|
return (
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
this.config.enabledServices(this.hotkeyProviders)
|
this.config.enabledServices(this.hotkeyProviders)
|
||||||
.map(async x => x.provide ? x.provide() : x.hotkeys)
|
.map(async x => x.provide())
|
||||||
)
|
)
|
||||||
).reduce((a, b) => a.concat(b))
|
).reduce((a, b) => a.concat(b))
|
||||||
}
|
}
|
||||||
@@ -169,21 +183,23 @@ export class HotkeysService {
|
|||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
value = [value]
|
value = [value]
|
||||||
}
|
}
|
||||||
|
const specs: string[] = []
|
||||||
value.forEach((item: string | string[]) => {
|
value.forEach((item: string | string[]) => {
|
||||||
item = typeof item === 'string' ? [item] : item
|
item = typeof item === 'string' ? [item] : item
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let electronKeySpec = item[0]
|
let electronKeySpec = item[0]
|
||||||
|
electronKeySpec = electronKeySpec.replace('Meta', 'Super')
|
||||||
electronKeySpec = electronKeySpec.replace('⌘', 'Command')
|
electronKeySpec = electronKeySpec.replace('⌘', 'Command')
|
||||||
electronKeySpec = electronKeySpec.replace('⌥', 'Alt')
|
electronKeySpec = electronKeySpec.replace('⌥', 'Alt')
|
||||||
electronKeySpec = electronKeySpec.replace(/-/g, '+')
|
electronKeySpec = electronKeySpec.replace(/-/g, '+')
|
||||||
this.electron.globalShortcut.register(electronKeySpec, () => {
|
specs.push(electronKeySpec)
|
||||||
this.globalHotkey.emit()
|
|
||||||
})
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Could not register the global hotkey:', err)
|
console.error('Could not register the global hotkey:', err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.hostApp.registerGlobalHotkey(specs)
|
||||||
}
|
}
|
||||||
|
|
||||||
private getHotkeysConfig () {
|
private getHotkeysConfig () {
|
||||||
@@ -203,7 +219,10 @@ export class HotkeysService {
|
|||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
value = [value]
|
value = [value]
|
||||||
}
|
}
|
||||||
if (value) {
|
if (!(value instanceof Array)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (value.length > 0) {
|
||||||
value = value.map((item: string | string[]) => typeof item === 'string' ? [item] : item)
|
value = value.map((item: string | string[]) => typeof item === 'string' ? [item] : item)
|
||||||
keys[key] = value
|
keys[key] = value
|
||||||
}
|
}
|
||||||
|
@@ -28,44 +28,42 @@ const initializeWinston = (electron: ElectronService) => {
|
|||||||
|
|
||||||
export class Logger {
|
export class Logger {
|
||||||
constructor (
|
constructor (
|
||||||
private winstonLogger: any,
|
private winstonLogger: winston.Logger,
|
||||||
private name: string,
|
private name: string,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
debug (...args: any[]) {
|
debug (...args: any[]): void {
|
||||||
this.doLog('debug', ...args)
|
this.doLog('debug', ...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
info (...args: any[]) {
|
info (...args: any[]): void {
|
||||||
this.doLog('info', ...args)
|
this.doLog('info', ...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
warn (...args: any[]) {
|
warn (...args: any[]): void {
|
||||||
this.doLog('warn', ...args)
|
this.doLog('warn', ...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
error (...args: any[]) {
|
error (...args: any[]): void {
|
||||||
this.doLog('error', ...args)
|
this.doLog('error', ...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
log (...args: any[]) {
|
log (...args: any[]): void {
|
||||||
this.doLog('log', ...args)
|
this.doLog('log', ...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
private doLog (level: string, ...args: any[]) {
|
private doLog (level: string, ...args: any[]): void {
|
||||||
console[level](`%c[${this.name}]`, 'color: #aaa', ...args)
|
console[level](`%c[${this.name}]`, 'color: #aaa', ...args)
|
||||||
if (this.winstonLogger) {
|
this.winstonLogger[level](...args)
|
||||||
this.winstonLogger[level](...args)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class LogService {
|
export class LogService {
|
||||||
private log: any
|
private log: winston.Logger
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
constructor (electron: ElectronService) {
|
private constructor (electron: ElectronService) {
|
||||||
this.log = initializeWinston(electron)
|
this.log = initializeWinston(electron)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -8,7 +8,7 @@ import { HostAppService, Platform } from './hostApp.service'
|
|||||||
/* eslint-disable block-scoped-var */
|
/* eslint-disable block-scoped-var */
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var wnr = require('windows-native-registry') // eslint-disable-line @typescript-eslint/no-var-requires
|
var wnr = require('windows-native-registry') // eslint-disable-line @typescript-eslint/no-var-requires, no-var
|
||||||
} catch (_) { }
|
} catch (_) { }
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
@@ -33,7 +33,7 @@ export class ShellIntegrationService {
|
|||||||
command: 'paste "%V"',
|
command: 'paste "%V"',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
constructor (
|
private constructor (
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
private hostApp: HostAppService,
|
private hostApp: HostAppService,
|
||||||
) {
|
) {
|
||||||
@@ -44,7 +44,7 @@ export class ShellIntegrationService {
|
|||||||
'extras',
|
'extras',
|
||||||
'automator-workflows',
|
'automator-workflows',
|
||||||
)
|
)
|
||||||
this.automatorWorkflowsDestination = path.join(process.env.HOME as string, 'Library', 'Services')
|
this.automatorWorkflowsDestination = path.join(process.env.HOME!, 'Library', 'Services')
|
||||||
}
|
}
|
||||||
this.updatePaths()
|
this.updatePaths()
|
||||||
}
|
}
|
||||||
@@ -58,8 +58,8 @@ export class ShellIntegrationService {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
async install () {
|
async install (): Promise<void> {
|
||||||
const exe: string = process.env.PORTABLE_EXECUTABLE_FILE || this.electron.app.getPath('exe')
|
const exe: string = process.env.PORTABLE_EXECUTABLE_FILE ?? this.electron.app.getPath('exe')
|
||||||
if (this.hostApp.platform === Platform.macOS) {
|
if (this.hostApp.platform === Platform.macOS) {
|
||||||
for (const wf of this.automatorWorkflows) {
|
for (const wf of this.automatorWorkflows) {
|
||||||
await exec(`cp -r "${this.automatorWorkflowsLocation}/${wf}" "${this.automatorWorkflowsDestination}"`)
|
await exec(`cp -r "${this.automatorWorkflowsLocation}/${wf}" "${this.automatorWorkflowsDestination}"`)
|
||||||
@@ -73,16 +73,16 @@ export class ShellIntegrationService {
|
|||||||
wnr.setRegistryValue(wnr.HK.CU, registryKey.path + '\\command', '', wnr.REG.SZ, exe + ' ' + registryKey.command)
|
wnr.setRegistryValue(wnr.HK.CU, registryKey.path + '\\command', '', wnr.REG.SZ, exe + ' ' + registryKey.command)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(wnr.getRegistryKey(wnr.HK.CU, 'Software\\Classes\\Directory\\Background\\shell\\Open Terminus here')) {
|
if (wnr.getRegistryKey(wnr.HK.CU, 'Software\\Classes\\Directory\\Background\\shell\\Open Terminus here')) {
|
||||||
wnr.deleteRegistryKey(wnr.HK.CU, 'Software\\Classes\\Directory\\Background\\shell\\Open Terminus here')
|
wnr.deleteRegistryKey(wnr.HK.CU, 'Software\\Classes\\Directory\\Background\\shell\\Open Terminus here')
|
||||||
}
|
}
|
||||||
if(wnr.getRegistryKey(wnr.HK.CU, 'Software\\Classes\\*\\shell\\Paste path into Terminus')) {
|
if (wnr.getRegistryKey(wnr.HK.CU, 'Software\\Classes\\*\\shell\\Paste path into Terminus')) {
|
||||||
wnr.deleteRegistryKey(wnr.HK.CU, 'Software\\Classes\\*\\shell\\Paste path into Terminus')
|
wnr.deleteRegistryKey(wnr.HK.CU, 'Software\\Classes\\*\\shell\\Paste path into Terminus')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async remove () {
|
async remove (): Promise<void> {
|
||||||
if (this.hostApp.platform === Platform.macOS) {
|
if (this.hostApp.platform === Platform.macOS) {
|
||||||
for (const wf of this.automatorWorkflows) {
|
for (const wf of this.automatorWorkflows) {
|
||||||
await exec(`rm -rf "${this.automatorWorkflowsDestination}/${wf}"`)
|
await exec(`rm -rf "${this.automatorWorkflowsDestination}/${wf}"`)
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Injectable, Inject } from '@angular/core'
|
import { Injectable, Inject } from '@angular/core'
|
||||||
import { TabRecoveryProvider, RecoveredTab } from '../api/tabRecovery'
|
import { TabRecoveryProvider, RecoveredTab, RecoveryToken } from '../api/tabRecovery'
|
||||||
import { BaseTabComponent } from '../components/baseTab.component'
|
import { BaseTabComponent } from '../components/baseTab.component'
|
||||||
import { Logger, LogService } from '../services/log.service'
|
import { Logger, LogService } from '../services/log.service'
|
||||||
import { ConfigService } from '../services/config.service'
|
import { ConfigService } from '../services/config.service'
|
||||||
@@ -8,30 +8,44 @@ import { ConfigService } from '../services/config.service'
|
|||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class TabRecoveryService {
|
export class TabRecoveryService {
|
||||||
logger: Logger
|
logger: Logger
|
||||||
|
enabled = false
|
||||||
|
|
||||||
constructor (
|
private constructor (
|
||||||
@Inject(TabRecoveryProvider) private tabRecoveryProviders: TabRecoveryProvider[],
|
@Inject(TabRecoveryProvider) private tabRecoveryProviders: TabRecoveryProvider[]|null,
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
log: LogService
|
log: LogService
|
||||||
) {
|
) {
|
||||||
this.logger = log.create('tabRecovery')
|
this.logger = log.create('tabRecovery')
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveTabs (tabs: BaseTabComponent[]) {
|
async saveTabs (tabs: BaseTabComponent[]): Promise<void> {
|
||||||
|
if (!this.enabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
window.localStorage.tabsRecovery = JSON.stringify(
|
window.localStorage.tabsRecovery = JSON.stringify(
|
||||||
await Promise.all(
|
(await Promise.all(
|
||||||
tabs
|
tabs
|
||||||
.map(tab => tab.getRecoveryToken())
|
.map(async tab => tab.getRecoveryToken().then(r => {
|
||||||
.filter(token => !!token)
|
if (r) {
|
||||||
)
|
r.tabTitle = tab.title
|
||||||
|
if (tab.color) {
|
||||||
|
r.tabColor = tab.color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}))
|
||||||
|
)).filter(token => !!token)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async recoverTab (token: any): Promise<RecoveredTab|null> {
|
async recoverTab (token: RecoveryToken): Promise<RecoveredTab|null> {
|
||||||
for (const provider of this.config.enabledServices(this.tabRecoveryProviders)) {
|
for (const provider of this.config.enabledServices(this.tabRecoveryProviders ?? [])) {
|
||||||
try {
|
try {
|
||||||
const tab = await provider.recover(token)
|
const tab = await provider.recover(token)
|
||||||
if (tab) {
|
if (tab !== null) {
|
||||||
|
tab.options = tab.options || {}
|
||||||
|
tab.options.color = token.tabColor ?? null
|
||||||
|
tab.options.title = token.tabTitle || ''
|
||||||
return tab
|
return tab
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@@ -8,7 +8,7 @@ export type TabComponentType = new (...args: any[]) => BaseTabComponent
|
|||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class TabsService {
|
export class TabsService {
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
constructor (
|
private constructor (
|
||||||
private componentFactoryResolver: ComponentFactoryResolver,
|
private componentFactoryResolver: ComponentFactoryResolver,
|
||||||
private injector: Injector,
|
private injector: Injector,
|
||||||
private tabRecovery: TabRecoveryService,
|
private tabRecovery: TabRecoveryService,
|
||||||
@@ -17,12 +17,12 @@ export class TabsService {
|
|||||||
/**
|
/**
|
||||||
* Instantiates a tab component and assigns given inputs
|
* Instantiates a tab component and assigns given inputs
|
||||||
*/
|
*/
|
||||||
create (type: TabComponentType, inputs?: any): BaseTabComponent {
|
create (type: TabComponentType, inputs?: Record<string, any>): BaseTabComponent {
|
||||||
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(type)
|
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(type)
|
||||||
const componentRef = componentFactory.create(this.injector)
|
const componentRef = componentFactory.create(this.injector)
|
||||||
const tab = componentRef.instance
|
const tab = componentRef.instance
|
||||||
tab.hostView = componentRef.hostView
|
tab.hostView = componentRef.hostView
|
||||||
Object.assign(tab, inputs || {})
|
Object.assign(tab, inputs ?? {})
|
||||||
return tab
|
return tab
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,7 +7,7 @@ export class ThemesService {
|
|||||||
private styleElement: HTMLElement|null = null
|
private styleElement: HTMLElement|null = null
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
constructor (
|
private constructor (
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
@Inject(Theme) private themes: Theme[],
|
@Inject(Theme) private themes: Theme[],
|
||||||
) {
|
) {
|
||||||
@@ -18,11 +18,11 @@ export class ThemesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
findTheme (name: string): Theme|null {
|
findTheme (name: string): Theme|null {
|
||||||
return this.config.enabledServices(this.themes).find(x => x.name === name) || null
|
return this.config.enabledServices(this.themes).find(x => x.name === name) ?? null
|
||||||
}
|
}
|
||||||
|
|
||||||
findCurrentTheme (): Theme {
|
findCurrentTheme (): Theme {
|
||||||
return this.findTheme(this.config.store.appearance.theme) || this.findTheme('Standard')!
|
return this.findTheme(this.config.store.appearance.theme) ?? this.findTheme('Standard')!
|
||||||
}
|
}
|
||||||
|
|
||||||
applyTheme (theme: Theme): void {
|
applyTheme (theme: Theme): void {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user