mirror of
https://github.com/Eugeny/tabby.git
synced 2025-09-10 18:34:36 +00:00
Compare commits
1618 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7ab3238617 | ||
![]() |
6d89d7a8d0 | ||
![]() |
e277c52f71 | ||
![]() |
2cafd97751 | ||
![]() |
ad78f38210 | ||
![]() |
4f32908c48 | ||
![]() |
8f0ba06f63 | ||
![]() |
d221ad3078 | ||
![]() |
5b4d5efcc9 | ||
![]() |
8987cc39aa | ||
![]() |
2f2dd442ad | ||
![]() |
8bfc86e623 | ||
![]() |
50b1fb2410 | ||
![]() |
327daf03ef | ||
![]() |
224a72d029 | ||
![]() |
3c5615e464 | ||
![]() |
e99908761d | ||
![]() |
0ff81abb0c | ||
![]() |
b4e703674b | ||
![]() |
dc53668685 | ||
![]() |
2df848f4c0 | ||
![]() |
247cf6f93e | ||
![]() |
797265abb1 | ||
![]() |
62bdcb1af4 | ||
![]() |
25ae56718d | ||
![]() |
b24978dc0e | ||
![]() |
46a4df108b | ||
![]() |
3091e2be69 | ||
![]() |
9a49e94642 | ||
![]() |
cf13fca835 | ||
![]() |
174a1bcca7 | ||
![]() |
80c781a8ca | ||
![]() |
d71ee6b6f1 | ||
![]() |
3a7204c2ee | ||
![]() |
5236956469 | ||
![]() |
4dfb8df7fa | ||
![]() |
61e65c7ec8 | ||
![]() |
64ab172b8e | ||
![]() |
bd46b08c9d | ||
![]() |
4a97cc4383 | ||
![]() |
2c11feed83 | ||
![]() |
481aa654a3 | ||
![]() |
7d1ec5b869 | ||
![]() |
5f098ef791 | ||
![]() |
15029066e4 | ||
![]() |
dbf90a5ce3 | ||
![]() |
c6d4eb7083 | ||
![]() |
e56fac03b9 | ||
![]() |
1132a18a0a | ||
![]() |
ebc8d846e3 | ||
![]() |
123f68d705 | ||
![]() |
8f9a7539c4 | ||
![]() |
98d46468e5 | ||
![]() |
a6d5a93e6e | ||
![]() |
558ef9894c | ||
![]() |
7357dc178a | ||
![]() |
cd7df7c700 | ||
![]() |
a4c4c93bac | ||
![]() |
a97619a474 | ||
![]() |
cd1cac96e9 | ||
![]() |
9861766da0 | ||
![]() |
bcd5f3b8bf | ||
![]() |
3e4f2c467d | ||
![]() |
e84270609f | ||
![]() |
62b1538462 | ||
![]() |
d6a62344b8 | ||
![]() |
d2e16cd73a | ||
![]() |
87d3b08c37 | ||
![]() |
3a7db137c5 | ||
![]() |
da0f4e7afc | ||
![]() |
e1980a6611 | ||
![]() |
08cc19946f | ||
![]() |
f6d4a51239 | ||
![]() |
36f77c5b63 | ||
![]() |
3676b90c9f | ||
![]() |
c4204167cd | ||
![]() |
604d7c464f | ||
![]() |
e42bd11725 | ||
![]() |
864932d663 | ||
![]() |
9e68c735dc | ||
![]() |
be43f8b50d | ||
![]() |
c290633e7e | ||
![]() |
d03211631a | ||
![]() |
ca27c05f0f | ||
![]() |
62a21b03ea | ||
![]() |
2f7d29c523 | ||
![]() |
6236ee95c3 | ||
![]() |
836014c270 | ||
![]() |
0bf870738e | ||
![]() |
7c1697b9cb | ||
![]() |
c800dd6e44 | ||
![]() |
68e9a1f66b | ||
![]() |
f925d63fb0 | ||
![]() |
e79fc13de9 | ||
![]() |
0c963dcbcc | ||
![]() |
b7b672ce9b | ||
![]() |
4c8bb45d01 | ||
![]() |
d4f8a88451 | ||
![]() |
fac8fafa36 | ||
![]() |
bc854fcfff | ||
![]() |
ebc83b8064 | ||
![]() |
6757deab67 | ||
![]() |
fe68d9a34e | ||
![]() |
f42c3cdde4 | ||
![]() |
2840ae2aa7 | ||
![]() |
b275f5311d | ||
![]() |
4e084cdc93 | ||
![]() |
910b207160 | ||
![]() |
f54ff37010 | ||
![]() |
03d9d46a8c | ||
![]() |
7111f35268 | ||
![]() |
44427ac3b6 | ||
![]() |
8816910e43 | ||
![]() |
31464ee37a | ||
![]() |
4bdb72021b | ||
![]() |
7d5e70c718 | ||
![]() |
6af92c9f95 | ||
![]() |
4a8fe78b07 | ||
![]() |
1aa6bc3c10 | ||
![]() |
93d74bf3cb | ||
![]() |
9e16ef5a57 | ||
![]() |
741c77506c | ||
![]() |
507cc16190 | ||
![]() |
bfeb353858 | ||
![]() |
806bfef8bf | ||
![]() |
975b5a117d | ||
![]() |
46e4832a66 | ||
![]() |
6ab7093cc6 | ||
![]() |
00cc2474cc | ||
![]() |
4e4d02041a | ||
![]() |
09ac506dd3 | ||
![]() |
769ce6bc0b | ||
![]() |
26605124c1 | ||
![]() |
7465c32645 | ||
![]() |
846ac62a7f | ||
![]() |
eae2c5ee16 | ||
![]() |
4fd89105e5 | ||
![]() |
87c1bef16e | ||
![]() |
eed35c2613 | ||
![]() |
678f2f7a85 | ||
![]() |
0b1bacaf0d | ||
![]() |
98b5a27382 | ||
![]() |
76a81b9402 | ||
![]() |
634b221aa3 | ||
![]() |
ffcb3e72c2 | ||
![]() |
b4b7bdeb16 | ||
![]() |
21daa4aa18 | ||
![]() |
867dbcddcc | ||
![]() |
b906dbd8d6 | ||
![]() |
e5c7d0649f | ||
![]() |
55286bc7e7 | ||
![]() |
359e41f884 | ||
![]() |
cc93578ada | ||
![]() |
2e54fa317c | ||
![]() |
c3bd725ce8 | ||
![]() |
18f69c5527 | ||
![]() |
0102a2a61b | ||
![]() |
1de709561c | ||
![]() |
9f985ee8f0 | ||
![]() |
1a35ed3c73 | ||
![]() |
a241144827 | ||
![]() |
18e341f886 | ||
![]() |
3182d4428d | ||
![]() |
6dfcb9422a | ||
![]() |
ad5993b94f | ||
![]() |
eb9826fb2a | ||
![]() |
1c81baa6f2 | ||
![]() |
e07c5db0a8 | ||
![]() |
b93989d02b | ||
![]() |
15bfde6077 | ||
![]() |
4243e79a11 | ||
![]() |
eb49001614 | ||
![]() |
3aaa670400 | ||
![]() |
5371b2d1a6 | ||
![]() |
e46281a9a7 | ||
![]() |
9d4aa179a0 | ||
![]() |
20118afd1f | ||
![]() |
b29daac3c6 | ||
![]() |
8ba9e8b210 | ||
![]() |
f3f3edfbb6 | ||
![]() |
472cb18a16 | ||
![]() |
7c8d8ebc81 | ||
![]() |
18fc0fa886 | ||
![]() |
5a90c246b9 | ||
![]() |
9bc31556f2 | ||
![]() |
2c81491825 | ||
![]() |
9ed18d965c | ||
![]() |
b4f76847bf | ||
![]() |
b9bcb14dd7 | ||
![]() |
ee3e17981b | ||
![]() |
415d833ae6 | ||
![]() |
ed9117670e | ||
![]() |
0ecd47346d | ||
![]() |
bc0024ec06 | ||
![]() |
bdec20f7c7 | ||
![]() |
45ebe3caf9 | ||
![]() |
7315bc8d8b | ||
![]() |
a969dc1ea4 | ||
![]() |
2733dc69be | ||
![]() |
bb86848cd3 | ||
![]() |
e844ad6063 | ||
![]() |
a096a691cc | ||
![]() |
a28e25e96a | ||
![]() |
a09ce13709 | ||
![]() |
123d8ceb5f | ||
![]() |
c0155c8d4e | ||
![]() |
fcda35b02e | ||
![]() |
9d6a1031da | ||
![]() |
c119d3283e | ||
![]() |
3667fb45ee | ||
![]() |
92d34343f1 | ||
![]() |
8afad944b7 | ||
![]() |
707d077eb2 | ||
![]() |
92eabab509 | ||
![]() |
888cfc6548 | ||
![]() |
9cb10c3e21 | ||
![]() |
661d5af9ba | ||
![]() |
26429bf209 | ||
![]() |
4779c41f48 | ||
![]() |
b11eda8653 | ||
![]() |
23bff8750c | ||
![]() |
aab1ae3ceb | ||
![]() |
9b5b5a9d00 | ||
![]() |
9c87cf3f3a | ||
![]() |
a8c7134218 | ||
![]() |
9734830a74 | ||
![]() |
5fa056751d | ||
![]() |
6fdccd0a02 | ||
![]() |
41ee7b7bbf | ||
![]() |
a755fdaa4e | ||
![]() |
1149d68d1c | ||
![]() |
1d92a3f89d | ||
![]() |
3557345d70 | ||
![]() |
86ebdd92b4 | ||
![]() |
4764ec8249 | ||
![]() |
b1752bd0b4 | ||
![]() |
1e697a952a | ||
![]() |
6bad2a2167 | ||
![]() |
61a46e3b4a | ||
![]() |
cba90cec0a | ||
![]() |
7583d92747 | ||
![]() |
e2b99d71ad | ||
![]() |
c829daac41 | ||
![]() |
aac38fa190 | ||
![]() |
7098622c8f | ||
![]() |
8695003c74 | ||
![]() |
43a27a7b7c | ||
![]() |
dd2d2ce20d | ||
![]() |
73574374f0 | ||
![]() |
5bd1bfd565 | ||
![]() |
0611afa8b5 | ||
![]() |
91c9e8affd | ||
![]() |
322ffc5847 | ||
![]() |
21084b5d24 | ||
![]() |
4c840a0db1 | ||
![]() |
3b69172f62 | ||
![]() |
e0efb4073a | ||
![]() |
c42b62afe6 | ||
![]() |
e2b11c83d5 | ||
![]() |
891fa5770a | ||
![]() |
6b395cc2b3 | ||
![]() |
448a1a094f | ||
![]() |
788dd61a13 | ||
![]() |
467684d9ab | ||
![]() |
5069070040 | ||
![]() |
ecf5297bc3 | ||
![]() |
78bd90ac55 | ||
![]() |
712589eb93 | ||
![]() |
f103e71285 | ||
![]() |
0cf883cc4a | ||
![]() |
2b0ad0d558 | ||
![]() |
67bacb9dd3 | ||
![]() |
d0a597634d | ||
![]() |
322014c409 | ||
![]() |
c751a8725b | ||
![]() |
5417efe558 | ||
![]() |
bf356fcd19 | ||
![]() |
10ee66b9dd | ||
![]() |
6545a2fda6 | ||
![]() |
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 | ||
![]() |
ceb638e08d | ||
![]() |
040098050d | ||
![]() |
00de7c148f | ||
![]() |
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 | ||
![]() |
f6d08af986 | ||
![]() |
7ddb67f28e | ||
![]() |
ff2c2031b5 | ||
![]() |
1ab528b05c | ||
![]() |
9ae92ef88c | ||
![]() |
f4f52321f5 | ||
![]() |
286b6cfdde | ||
![]() |
b97149aa73 | ||
![]() |
2094c44992 | ||
![]() |
a8409a0aec | ||
![]() |
5c2ac72bac | ||
![]() |
b58d6e1bfd | ||
![]() |
be09e7829f | ||
![]() |
a9320a33c9 | ||
![]() |
0e881e47e2 | ||
![]() |
a3a5da8550 | ||
![]() |
178a2e34c6 | ||
![]() |
b39276feca | ||
![]() |
3b7f4be643 | ||
![]() |
2c40f0dddc | ||
![]() |
8f361f841c | ||
![]() |
0e0d59f2c5 | ||
![]() |
ca9b8307c2 | ||
![]() |
d837a3d7fd | ||
![]() |
555e1e733d | ||
![]() |
2e546fb72e | ||
![]() |
a2d4b76070 | ||
![]() |
34edee2f37 | ||
![]() |
f17b7fb8ce | ||
![]() |
cbe5ea7fdd | ||
![]() |
c00405afff | ||
![]() |
555db7bef9 | ||
![]() |
94e9e7d439 | ||
![]() |
c4850f2172 | ||
![]() |
2d33a0030d | ||
![]() |
1f576edf85 | ||
![]() |
6d139434d7 | ||
![]() |
e9ba07c7a3 | ||
![]() |
4f083098c3 | ||
![]() |
03369da32c | ||
![]() |
edb2cf2b7b | ||
![]() |
91a1ae1cbe | ||
![]() |
4a9f17ace9 | ||
![]() |
d5babde1fa | ||
![]() |
902c1fceb1 | ||
![]() |
1d340e4666 | ||
![]() |
b922fe7776 | ||
![]() |
de3829a94c | ||
![]() |
964e0ad2bc | ||
![]() |
742affb88b | ||
![]() |
e9999ee883 | ||
![]() |
1c8288bfe1 | ||
![]() |
37044fbb01 | ||
![]() |
5507171fee | ||
![]() |
472b421484 | ||
![]() |
b076541962 | ||
![]() |
8201e0b9ef | ||
![]() |
6293a43571 | ||
![]() |
b61bc943ec | ||
![]() |
be668403c5 | ||
![]() |
90a173d8b7 | ||
![]() |
bbcc84e9b0 | ||
![]() |
b0a8832499 | ||
![]() |
8cd1c4a9af | ||
![]() |
1ada4338b7 | ||
![]() |
4a701fa7b4 | ||
![]() |
b718448904 | ||
![]() |
7770cf2573 | ||
![]() |
49755f855f | ||
![]() |
daa1b4572e | ||
![]() |
d48e22a9d2 | ||
![]() |
fc82010729 | ||
![]() |
4a8f3fbd7f | ||
![]() |
4c435672a5 | ||
![]() |
0311754ce0 | ||
![]() |
ff5da104c1 | ||
![]() |
b01b902829 | ||
![]() |
595707eed4 | ||
![]() |
441ee4fb6e | ||
![]() |
51827d6750 | ||
![]() |
9807bbe32a | ||
![]() |
181b55890d | ||
![]() |
c8c5f1a0fd | ||
![]() |
222c6a9f3c | ||
![]() |
0400c8fe63 | ||
![]() |
099d9b06d6 | ||
![]() |
ad26b39cca | ||
![]() |
f465c359ef | ||
![]() |
67aead225c | ||
![]() |
280c421ae4 | ||
![]() |
b6fc43faa2 | ||
![]() |
b5a985b8a3 | ||
![]() |
2f7dcf3339 | ||
![]() |
7e38f11c06 | ||
![]() |
86cd560089 | ||
![]() |
c0c4580461 | ||
![]() |
5cb65dfd84 | ||
![]() |
2b5f623b50 | ||
![]() |
a8d5cf469e | ||
![]() |
d261b89803 | ||
![]() |
21cb452d62 | ||
![]() |
b07a2113d2 | ||
![]() |
f545b3eacf | ||
![]() |
87fe8deaa8 | ||
![]() |
1068450ddd | ||
![]() |
b355fff0f8 | ||
![]() |
f80b0eb65b | ||
![]() |
285691326f | ||
![]() |
3ecffbfda6 | ||
![]() |
3d89a15d18 | ||
![]() |
491d4c3b3a | ||
![]() |
995f329835 | ||
![]() |
28f2ea595d | ||
![]() |
42b7c573ea | ||
![]() |
c40294628a | ||
![]() |
c11a10144e | ||
![]() |
7b6cdb274c | ||
![]() |
a3c74ecdba | ||
![]() |
94d91f8182 | ||
![]() |
e4f32c9ade | ||
![]() |
65fd7b05b1 | ||
![]() |
2150fab55b | ||
![]() |
644cb76fd3 | ||
![]() |
4106d97f6b | ||
![]() |
98103fd139 | ||
![]() |
9453e8ba7b | ||
![]() |
2f78575cd7 | ||
![]() |
500acee064 | ||
![]() |
42eb5f6b78 | ||
![]() |
ef19b92e85 | ||
![]() |
f263f954d4 | ||
![]() |
2ce0f03282 | ||
![]() |
150999d3a3 | ||
![]() |
8cc76555d2 | ||
![]() |
f0f8f06890 | ||
![]() |
176a55c91d | ||
![]() |
fc6dfc50dd | ||
![]() |
34d020f66a | ||
![]() |
fa0ef69c46 | ||
![]() |
e5c1e421f7 | ||
![]() |
f3994f1bd9 | ||
![]() |
6956ef9e0f | ||
![]() |
a080129882 | ||
![]() |
ef9bfe6120 | ||
![]() |
37d69e858f | ||
![]() |
ee594f5bcd | ||
![]() |
adf022de2c | ||
![]() |
c5a9b890c4 | ||
![]() |
2d1a96a12b | ||
![]() |
5289981485 | ||
![]() |
a89047b205 | ||
![]() |
a7b4496d22 | ||
![]() |
09cd9d0e18 | ||
![]() |
fb2a4d268d | ||
![]() |
9b61615701 | ||
![]() |
077d2421e1 | ||
![]() |
202ba18a8c | ||
![]() |
63f33f8f4b | ||
![]() |
3bd89a0194 | ||
![]() |
604bc28c9a | ||
![]() |
f81f5d122a | ||
![]() |
3633be750e | ||
![]() |
404fd72ea9 | ||
![]() |
402b76bcc9 | ||
![]() |
b6c97ffa49 | ||
![]() |
20aa1d814f | ||
![]() |
786daaac32 | ||
![]() |
0360ad2dd0 | ||
![]() |
0a451c5876 | ||
![]() |
5a9625424c | ||
![]() |
62c1f6463b | ||
![]() |
9fe82f2c0a | ||
![]() |
09838197a2 | ||
![]() |
27114797a2 | ||
![]() |
4dc77d11cf | ||
![]() |
245698b67d | ||
![]() |
017fabaf6f | ||
![]() |
6c11189b3e | ||
![]() |
dd70f5f5d8 | ||
![]() |
47277ac5aa | ||
![]() |
b69cbbcdd1 | ||
![]() |
efba980a1d | ||
![]() |
f31da67508 | ||
![]() |
2ba76cc0b9 | ||
![]() |
62d14ac0cb | ||
![]() |
c1b4ffd248 | ||
![]() |
87cacdb568 | ||
![]() |
2a11bc4fcc | ||
![]() |
f716baa7d4 | ||
![]() |
5b60daf366 | ||
![]() |
11f9f4e824 | ||
![]() |
0daf48f699 | ||
![]() |
8fb0ea4d75 | ||
![]() |
d9948cf6e2 | ||
![]() |
54b618cffc | ||
![]() |
690dde628e | ||
![]() |
3dfbcf9d41 | ||
![]() |
d91ba71ec0 | ||
![]() |
99698913a8 | ||
![]() |
0dbb16d859 | ||
![]() |
0f8cff2d5b | ||
![]() |
471f9effcf | ||
![]() |
04faf1a04a | ||
![]() |
656f5c2561 | ||
![]() |
99bc2c1c65 | ||
![]() |
87837bf66b | ||
![]() |
c8735243f3 | ||
![]() |
b197a16e5c | ||
![]() |
1a361e67b3 | ||
![]() |
fc471b2c16 | ||
![]() |
ae17faa7e5 | ||
![]() |
5fb70f1812 | ||
![]() |
03fc68bb6d | ||
![]() |
bb9c80623d | ||
![]() |
4dd0a5951f | ||
![]() |
b010791767 | ||
![]() |
ef61a141a6 | ||
![]() |
85cad2c8e3 | ||
![]() |
ac990c2bbc | ||
![]() |
cf1f3825c6 | ||
![]() |
fa8c30b279 | ||
![]() |
99f5a9ebb2 | ||
![]() |
15ed6ac632 | ||
![]() |
18aa78fa2e | ||
![]() |
8dcb6060b8 | ||
![]() |
d14424a891 | ||
![]() |
e569fe60a7 | ||
![]() |
9a666f3467 | ||
![]() |
edb098bf6f | ||
![]() |
dec575d7a4 | ||
![]() |
dee608c1c8 | ||
![]() |
d90f68c439 | ||
![]() |
6a7ac612ee | ||
![]() |
8c8f972448 | ||
![]() |
3c97bb4cd2 | ||
![]() |
ffeed1611d | ||
![]() |
7f4a3f0529 | ||
![]() |
3801d490e5 | ||
![]() |
43899a6683 | ||
![]() |
2e717eaeb9 | ||
![]() |
09fa765a3c | ||
![]() |
e2c4a08754 | ||
![]() |
1c1514bb3a | ||
![]() |
e6711f760d | ||
![]() |
133c5067b6 | ||
![]() |
dd1e7706a4 | ||
![]() |
c154efeb14 | ||
![]() |
78274b8504 | ||
![]() |
1ebf756f59 | ||
![]() |
33666529e5 | ||
![]() |
56b2b2a717 | ||
![]() |
2ac26685b0 | ||
![]() |
606392d1a5 | ||
![]() |
881e7bf91c | ||
![]() |
ba57f7b0c4 | ||
![]() |
be2a100738 | ||
![]() |
6749ef3b15 | ||
![]() |
a241f2b36f | ||
![]() |
efe444567d | ||
![]() |
afb4343828 | ||
![]() |
3a67f1eb41 | ||
![]() |
d7c8bc9da0 | ||
![]() |
8ed6a78610 | ||
![]() |
7574a692f0 | ||
![]() |
f4ea106816 | ||
![]() |
84b8e8b0aa | ||
![]() |
8a0152278f | ||
![]() |
d2416580c0 | ||
![]() |
bfae131b8b | ||
![]() |
d93a549406 | ||
![]() |
c29a430b92 | ||
![]() |
a4f8bc9dc1 | ||
![]() |
a186ae70c7 | ||
![]() |
f68c28cf6e | ||
![]() |
8101014a29 | ||
![]() |
ab1f8dba16 | ||
![]() |
cb4c36bf66 | ||
![]() |
eac6f92bcc | ||
![]() |
f6e6259678 | ||
![]() |
9c8692f049 | ||
![]() |
8d479c7392 | ||
![]() |
d1f7131386 | ||
![]() |
1a0eb415b0 | ||
![]() |
58e5a56ac1 | ||
![]() |
8924b74fb4 | ||
![]() |
c4af0886b4 | ||
![]() |
cf53e7a0da | ||
![]() |
617557998d | ||
![]() |
682d665fb7 | ||
![]() |
a72ccf32d7 | ||
![]() |
5f384c8cf5 | ||
![]() |
64309b364f | ||
![]() |
b5707c6884 | ||
![]() |
d12dcc2e06 | ||
![]() |
ee5e58d312 | ||
![]() |
da469c9f46 | ||
![]() |
4633d6e45e | ||
![]() |
6d10bc6592 | ||
![]() |
67f5e79f03 | ||
![]() |
3d604102c9 | ||
![]() |
aef7ea8fbf | ||
![]() |
354be07caa | ||
![]() |
4470abbd11 | ||
![]() |
e815394750 | ||
![]() |
1a67ab5503 | ||
![]() |
20a1ea49a5 | ||
![]() |
ec956d463a | ||
![]() |
555d072ef9 | ||
![]() |
866a374863 | ||
![]() |
dc6cee9f21 | ||
![]() |
7276eb6bef | ||
![]() |
3254e8ac19 | ||
![]() |
4f678cc8ce | ||
![]() |
384ec443a1 | ||
![]() |
64030c758a | ||
![]() |
31ecf46f12 | ||
![]() |
ab55650be8 | ||
![]() |
dde89b58b2 | ||
![]() |
36434fb93c | ||
![]() |
5e848f14df | ||
![]() |
a8f4c43e4b | ||
![]() |
35c92db737 | ||
![]() |
42e7e03cbd | ||
![]() |
455ce5fc7c | ||
![]() |
8546898841 | ||
![]() |
9fa2b85aeb | ||
![]() |
e2a6db3fbd | ||
![]() |
9ee3e2ac84 | ||
![]() |
205da833eb | ||
![]() |
9e38ff658e | ||
![]() |
331e6c6bdd | ||
![]() |
52689a587a | ||
![]() |
911369d9dd | ||
![]() |
3d013195ce | ||
![]() |
406927be3c | ||
![]() |
c3a00eb31d | ||
![]() |
5ff3593024 | ||
![]() |
e452a825c6 | ||
![]() |
87ba3f72d1 | ||
![]() |
86dfc49861 | ||
![]() |
53c03b4349 | ||
![]() |
9c3e63fd74 | ||
![]() |
2f9e9cbbda | ||
![]() |
c5b4eb5905 | ||
![]() |
a47862e0a8 | ||
![]() |
83492b9f26 | ||
![]() |
cccdab5739 | ||
![]() |
85799f49f3 | ||
![]() |
63a0cde5ff | ||
![]() |
c7c1e6ebd6 | ||
![]() |
70d3f30034 | ||
![]() |
184960c3f5 | ||
![]() |
25405440c7 | ||
![]() |
2dd1733926 | ||
![]() |
d815fe0836 | ||
![]() |
6c5f5d5570 | ||
![]() |
aa83e235f2 | ||
![]() |
a2de93d14f | ||
![]() |
24f0f17063 | ||
![]() |
89990fd148 | ||
![]() |
de0db8b7a5 | ||
![]() |
62946d0d4e | ||
![]() |
97adb8e508 | ||
![]() |
9eaa084e84 | ||
![]() |
a9dc550b01 | ||
![]() |
e7911f8fbc | ||
![]() |
4568e37586 | ||
![]() |
1faf9681a2 | ||
![]() |
ac3713e4a0 | ||
![]() |
db86150832 | ||
![]() |
607ea4b549 | ||
![]() |
4ee8e90665 | ||
![]() |
ead8c9e867 | ||
![]() |
69c2331279 | ||
![]() |
ebc2052874 | ||
![]() |
1491e1dc59 | ||
![]() |
96f5296062 | ||
![]() |
baf368d430 | ||
![]() |
2aaeb86f27 | ||
![]() |
e7f4158ad7 | ||
![]() |
bac97a2340 | ||
![]() |
d34650ef5f | ||
![]() |
73de8d8a81 | ||
![]() |
9077e78a85 | ||
![]() |
6545484a87 | ||
![]() |
996a8e9801 | ||
![]() |
c8ec91c54b | ||
![]() |
d9227f70ce | ||
![]() |
8599837e08 | ||
![]() |
4a9f4bced5 | ||
![]() |
d0a93cf258 | ||
![]() |
5c2e8a1db1 | ||
![]() |
c4dfb44bc8 | ||
![]() |
35289a54b3 | ||
![]() |
cbc60c606c | ||
![]() |
8cb783ddf6 | ||
![]() |
3aa4c6105b | ||
![]() |
4bf67b0904 | ||
![]() |
c3b4b3deac | ||
![]() |
35722f6257 | ||
![]() |
0a07d0cd7f | ||
![]() |
504f0a5183 | ||
![]() |
dd5ee69b11 | ||
![]() |
545cd36309 | ||
![]() |
74f87b848b | ||
![]() |
14d734365a | ||
![]() |
85d1763533 | ||
![]() |
86e1abd44f | ||
![]() |
e49e066e7a | ||
![]() |
6406e63e29 | ||
![]() |
389b2f06d2 | ||
![]() |
ebf6f41e8a | ||
![]() |
6d5fa04492 | ||
![]() |
d9fbd7626a | ||
![]() |
1ce48c02b5 | ||
![]() |
097b671a84 | ||
![]() |
ea15efb407 | ||
![]() |
6a8b37cd2b | ||
![]() |
96571dd543 | ||
![]() |
caa2bb7284 | ||
![]() |
fc2a335956 | ||
![]() |
2bdf3ec704 | ||
![]() |
0b26df4cde | ||
![]() |
425f399276 | ||
![]() |
68441af22a | ||
![]() |
3cb3d7b086 | ||
![]() |
9f2f97e0bb | ||
![]() |
6887c1d57b | ||
![]() |
638dd79cf4 | ||
![]() |
22e9d43f5b | ||
![]() |
47345afd3c | ||
![]() |
f74edad743 | ||
![]() |
96da7d2c52 | ||
![]() |
90a16b59f8 | ||
![]() |
3687de884a | ||
![]() |
36e73e22f1 | ||
![]() |
82d38f9c11 | ||
![]() |
bc25b3d304 | ||
![]() |
904563aef2 | ||
![]() |
c7881a9716 | ||
![]() |
145c982341 | ||
![]() |
55fefa65ff | ||
![]() |
8375c2bb38 | ||
![]() |
8280b542f9 | ||
![]() |
6e36165bb1 | ||
![]() |
63a3abe8f9 | ||
![]() |
a23935d4cb | ||
![]() |
27907b6979 | ||
![]() |
7b0c41d2a9 | ||
![]() |
998614bf2e | ||
![]() |
d069f39bc7 | ||
![]() |
7e92d4c49f | ||
![]() |
6c9c2b46f0 | ||
![]() |
94caaabef8 | ||
![]() |
76d4457651 | ||
![]() |
89691bd1a3 | ||
![]() |
2ed6014e85 | ||
![]() |
181c523020 | ||
![]() |
76fbdf926f | ||
![]() |
44b1bb8917 | ||
![]() |
ef400ae901 | ||
![]() |
9b904856fa | ||
![]() |
b2b6582bdb | ||
![]() |
d3ac784e79 | ||
![]() |
b40ad69b89 | ||
![]() |
0237aad34f | ||
![]() |
b9b3db78a1 | ||
![]() |
d2d1bd391e | ||
![]() |
e8dfc41504 | ||
![]() |
c575fa5ece | ||
![]() |
c161023a90 | ||
![]() |
3b073d68b4 | ||
![]() |
c20e4e75a6 | ||
![]() |
0fd712fb2e | ||
![]() |
5c49029060 | ||
![]() |
621ba8c1f3 | ||
![]() |
61c0511abc | ||
![]() |
47ca84cc64 | ||
![]() |
64643a2922 | ||
![]() |
f3bb875116 | ||
![]() |
534fc7f270 | ||
![]() |
2ff608c6ff | ||
![]() |
a9300f89bc | ||
![]() |
f3e99de219 | ||
![]() |
2a990f25ad | ||
![]() |
acbb9d4ce4 | ||
![]() |
6eafd88ec2 | ||
![]() |
0cf169ac8d | ||
![]() |
028d0b839c | ||
![]() |
a53eb6c188 | ||
![]() |
f6ed710063 | ||
![]() |
693bff2fee | ||
![]() |
9800b8cd89 | ||
![]() |
44be7a2cfa | ||
![]() |
8f257f29d0 | ||
![]() |
7dd118ab2a | ||
![]() |
579f59c520 | ||
![]() |
ee78f43c4b | ||
![]() |
60af21c139 | ||
![]() |
f44b495b99 | ||
![]() |
ed6229ee7c | ||
![]() |
99ddf637df | ||
![]() |
fb56957752 | ||
![]() |
6ae9c27860 | ||
![]() |
e0f1a684f8 | ||
![]() |
5350468c14 | ||
![]() |
a636b310a2 | ||
![]() |
31e28ff8eb | ||
![]() |
870f67f8de | ||
![]() |
93f558b359 | ||
![]() |
d8f99c5fa3 | ||
![]() |
1cfa08197a | ||
![]() |
7a5db36656 | ||
![]() |
28fa198568 | ||
![]() |
e74a5d1658 | ||
![]() |
61187d3f3e | ||
![]() |
850168a41b | ||
![]() |
fbdc143c85 | ||
![]() |
e7e89d6c80 | ||
![]() |
5f89f2877c | ||
![]() |
17065f4cee | ||
![]() |
299414639c | ||
![]() |
163fa009fb | ||
![]() |
c7eabbd248 | ||
![]() |
99e105492a | ||
![]() |
af772c41d6 | ||
![]() |
5deb763cac | ||
![]() |
e0aa475450 | ||
![]() |
7f7c10b775 | ||
![]() |
70a1ec60b5 | ||
![]() |
3d48f36df7 | ||
![]() |
b76830958c | ||
![]() |
ff6221255b | ||
![]() |
252edad93f | ||
![]() |
9ee0f8a937 | ||
![]() |
c834d89466 | ||
![]() |
331291f34d | ||
![]() |
e6303980fa | ||
![]() |
1280e13c1f | ||
![]() |
1dec46908a | ||
![]() |
e18c6c2db9 | ||
![]() |
ee24eec39b | ||
![]() |
47865c5718 | ||
![]() |
8c055dddda | ||
![]() |
b45914ece5 | ||
![]() |
6d55058de6 | ||
![]() |
f512663499 | ||
![]() |
4bd036c78c | ||
![]() |
e5575f93fb | ||
![]() |
7bc353b0dd | ||
![]() |
dc6a81d17f | ||
![]() |
4da21a2537 | ||
![]() |
8ea270fb74 | ||
![]() |
bc0e1936c2 | ||
![]() |
587f295c30 | ||
![]() |
27ff57e47e | ||
![]() |
dfbbb68286 | ||
![]() |
7bc92b12e3 | ||
![]() |
540732d129 | ||
![]() |
7eb70e2925 | ||
![]() |
28a9c55006 | ||
![]() |
3e4427deaa | ||
![]() |
b56471aef6 | ||
![]() |
1c812faf3d | ||
![]() |
95967882e3 | ||
![]() |
d1ddbe213f | ||
![]() |
ba073d546a | ||
![]() |
981f673dbb | ||
![]() |
eece7b7b3e | ||
![]() |
27c9df9bc8 | ||
![]() |
b9a570be9b |
@@ -180,6 +180,205 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"code"
|
"code"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "yfwz100",
|
||||||
|
"name": "Wang Zhi",
|
||||||
|
"avatar_url": "https://avatars2.githubusercontent.com/u/983211?v=4",
|
||||||
|
"profile": "http://yfwz100.github.io",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "jack1142",
|
||||||
|
"name": "jack1142",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/6032823?v=4",
|
||||||
|
"profile": "https://github.com/jack1142",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "hdougie",
|
||||||
|
"name": "Howie Douglas",
|
||||||
|
"avatar_url": "https://avatars1.githubusercontent.com/u/450799?v=4",
|
||||||
|
"profile": "https://github.com/hdougie",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "ckaczor",
|
||||||
|
"name": "Chris Kaczor",
|
||||||
|
"avatar_url": "https://avatars2.githubusercontent.com/u/180906?v=4",
|
||||||
|
"profile": "https://chriskaczor.com",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "boxmein",
|
||||||
|
"name": "Johannes Kadak",
|
||||||
|
"avatar_url": "https://avatars1.githubusercontent.com/u/358714?v=4",
|
||||||
|
"profile": "https://www.boxmein.net",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "LeSeulArtichaut",
|
||||||
|
"name": "LeSeulArtichaut",
|
||||||
|
"avatar_url": "https://avatars1.githubusercontent.com/u/38361244?v=4",
|
||||||
|
"profile": "https://github.com/LeSeulArtichaut",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "CyrilTaylor",
|
||||||
|
"name": "Cyril Taylor",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/12631466?v=4",
|
||||||
|
"profile": "https://github.com/CyrilTaylor",
|
||||||
|
"contributions": [
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "VectorKappa",
|
||||||
|
"name": "Piotr Patalong",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/29167842?v=4",
|
||||||
|
"profile": "http://patalong.pl",
|
||||||
|
"contributions": [
|
||||||
|
"design"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "clarkwang",
|
||||||
|
"name": "Clark Wang",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/157076?v=4",
|
||||||
|
"profile": "https://github.com/clarkwang",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "iamchating",
|
||||||
|
"name": "iamchating",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/7088153?v=4",
|
||||||
|
"profile": "https://github.com/iamchating",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "starxg",
|
||||||
|
"name": "starxg",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/34997494?v=4",
|
||||||
|
"profile": "https://github.com/starxg",
|
||||||
|
"contributions": [
|
||||||
|
"plugin"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 7,
|
||||||
@@ -187,5 +386,6 @@
|
|||||||
"projectOwner": "Eugeny",
|
"projectOwner": "Eugeny",
|
||||||
"repoType": "github",
|
"repoType": "github",
|
||||||
"repoHost": "https://github.com",
|
"repoHost": "https://github.com",
|
||||||
"commitConvention": "none"
|
"commitConvention": "none",
|
||||||
|
"skipCi": true
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
parser: '@typescript-eslint/parser'
|
parser: '@typescript-eslint/parser'
|
||||||
parserOptions:
|
parserOptions:
|
||||||
project: tsconfig.json
|
project:
|
||||||
|
- tsconfig.json
|
||||||
|
- '*/tsconfig.typings.json'
|
||||||
extends:
|
extends:
|
||||||
- 'plugin:@typescript-eslint/all'
|
- 'plugin:@typescript-eslint/all'
|
||||||
plugins:
|
plugins:
|
||||||
@@ -29,13 +31,15 @@ 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
|
||||||
'@typescript-eslint/require-await': off
|
'@typescript-eslint/require-await': off
|
||||||
'@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/consistent-type-imports': off
|
||||||
|
'@typescript-eslint/sort-type-union-intersection-members': off
|
||||||
'@typescript-eslint/no-use-before-define':
|
'@typescript-eslint/no-use-before-define':
|
||||||
- error
|
- error
|
||||||
- classes: false
|
- classes: false
|
||||||
@@ -44,14 +48,16 @@ rules:
|
|||||||
- error
|
- error
|
||||||
- never
|
- never
|
||||||
block-scoped-var: error
|
block-scoped-var: error
|
||||||
brace-style:
|
brace-style: off
|
||||||
|
'@typescript-eslint/brace-style':
|
||||||
- error
|
- error
|
||||||
- 1tbs
|
- 1tbs
|
||||||
- allowSingleLine: true
|
- allowSingleLine: true
|
||||||
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
|
||||||
@@ -77,7 +83,9 @@ rules:
|
|||||||
args: after-used
|
args: after-used
|
||||||
argsIgnorePattern: ^_
|
argsIgnorePattern: ^_
|
||||||
no-undef: error
|
no-undef: error
|
||||||
object-curly-spacing:
|
no-var: error
|
||||||
|
object-curly-spacing: off
|
||||||
|
'@typescript-eslint/object-curly-spacing':
|
||||||
- error
|
- error
|
||||||
- always
|
- always
|
||||||
quote-props:
|
quote-props:
|
||||||
@@ -85,3 +93,27 @@ rules:
|
|||||||
- as-needed
|
- as-needed
|
||||||
- keywords: true
|
- keywords: true
|
||||||
numbers: true
|
numbers: true
|
||||||
|
quotes: off
|
||||||
|
'@typescript-eslint/quotes':
|
||||||
|
- error
|
||||||
|
- single
|
||||||
|
- allowTemplateLiterals: true
|
||||||
|
'@typescript-eslint/no-confusing-void-expression': off
|
||||||
|
'@typescript-eslint/no-non-null-assertion': off
|
||||||
|
'@typescript-eslint/no-unnecessary-condition':
|
||||||
|
- error
|
||||||
|
- allowConstantLoopConditions: true
|
||||||
|
'@typescript-eslint/restrict-template-expressions': off
|
||||||
|
'@typescript-eslint/prefer-readonly-parameter-types': 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
|
||||||
|
4
.github/stale.yml
vendored
4
.github/stale.yml
vendored
@@ -1,7 +1,7 @@
|
|||||||
# Number of days of inactivity before an issue becomes stale
|
# Number of days of inactivity before an issue becomes stale
|
||||||
daysUntilStale: 60
|
daysUntilStale: 180
|
||||||
# Number of days of inactivity before a stale issue is closed
|
# Number of days of inactivity before a stale issue is closed
|
||||||
daysUntilClose: 14
|
daysUntilClose: 90
|
||||||
# Issues with these labels will never be considered stale
|
# Issues with these labels will never be considered stale
|
||||||
exemptLabels:
|
exemptLabels:
|
||||||
- "T: Enhancement"
|
- "T: Enhancement"
|
||||||
|
8
.github/workflows/docs.yml
vendored
8
.github/workflows/docs.yml
vendored
@@ -11,13 +11,19 @@ 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: |
|
||||||
eval $(ssh-agent -s)
|
eval $(ssh-agent -s)
|
||||||
ssh-add <(echo "$DOCS_PRIVATE_KEY")
|
ssh-add <(echo "$DOCS_PRIVATE_KEY")
|
||||||
|
yarn cache clean
|
||||||
|
cd app
|
||||||
yarn
|
yarn
|
||||||
|
cd ..
|
||||||
|
rm app/node_modules/.yarn-integrity
|
||||||
|
yarn
|
||||||
|
yarn run build:typings
|
||||||
yarn run docs
|
yarn run docs
|
||||||
rsync -e "ssh -o StrictHostKeyChecking=no" -arv docs/api/ root@ajenti.org:/srv/terminus-docs/
|
rsync -e "ssh -o StrictHostKeyChecking=no" -arv docs/api/ root@ajenti.org:/srv/terminus-docs/
|
||||||
|
|
||||||
|
29
.github/workflows/lint.yml
vendored
Normal file
29
.github/workflows/lint.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
name: Lint
|
||||||
|
on: [push, pull_request]
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: macOS-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Installing Node
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 15
|
||||||
|
|
||||||
|
- name: Install deps
|
||||||
|
run: |
|
||||||
|
npm i -g yarn@1.19.1
|
||||||
|
cd app
|
||||||
|
yarn
|
||||||
|
cd ..
|
||||||
|
rm app/node_modules/.yarn-integrity
|
||||||
|
yarn
|
||||||
|
|
||||||
|
- name: Build typings
|
||||||
|
run: yarn run build:typings
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: yarn run lint
|
51
.github/workflows/linux.yml
vendored
51
.github/workflows/linux.yml
vendored
@@ -2,44 +2,65 @@ name: Linux Build
|
|||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-16.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
- name: Installing Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
version: 10
|
node-version: 15
|
||||||
|
|
||||||
- name: Build
|
- name: Install deps
|
||||||
run: |
|
run: |
|
||||||
|
npm i -g yarn@1.19.1
|
||||||
cd app
|
cd app
|
||||||
yarn
|
yarn
|
||||||
cd ..
|
cd ..
|
||||||
rm app/node_modules/.yarn-integrity
|
rm app/node_modules/.yarn-integrity
|
||||||
yarn
|
yarn
|
||||||
yarn run lint
|
./node_modules/.bin/patch-package
|
||||||
scripts/build-native.js
|
cd app
|
||||||
yarn run build:typings
|
../node_modules/.bin/patch-package
|
||||||
yarn run build
|
cd ..
|
||||||
scripts/prepackage-plugins.js
|
|
||||||
scripts/build-linux.js
|
- name: Build native deps
|
||||||
|
run: scripts/build-native.js
|
||||||
|
|
||||||
|
- name: Webpack
|
||||||
|
run: yarn run build
|
||||||
|
|
||||||
|
- name: Prepackage plugins
|
||||||
|
run: scripts/prepackage-plugins.js
|
||||||
|
|
||||||
|
- name: Build packages
|
||||||
|
run: scripts/build-linux.js
|
||||||
env:
|
env:
|
||||||
|
DEBUG: electron-builder,electron-builder:*
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
BT_TOKEN: ${{ secrets.BT_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: |
|
||||||
mkdir artifact-deb
|
mkdir artifact-deb
|
||||||
mv dist/*.deb artifact-deb/
|
mv dist/*.deb artifact-deb/ || true
|
||||||
mkdir artifact-rpm
|
mkdir artifact-rpm
|
||||||
mv dist/*.rpm artifact-rpm/
|
mv dist/*.rpm artifact-rpm/ || true
|
||||||
mkdir artifact-snap
|
mkdir artifact-snap
|
||||||
mv dist/*.snap artifact-snap/
|
mv dist/*.snap artifact-snap/ || true
|
||||||
mkdir artifact-tar.gz
|
mkdir artifact-tar.gz
|
||||||
mv dist/*.tar.gz artifact-tar.gz/
|
mv dist/*.tar.gz artifact-tar.gz/ || true
|
||||||
|
|
||||||
- uses: actions/upload-artifact@master
|
- uses: actions/upload-artifact@master
|
||||||
name: Upload DEB
|
name: Upload DEB
|
||||||
|
67
.github/workflows/macos.yml
vendored
67
.github/workflows/macos.yml
vendored
@@ -2,7 +2,12 @@ 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
|
||||||
|
- arch: arm64
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -11,26 +16,64 @@ 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: Build
|
- name: Install deps
|
||||||
run: |
|
run: |
|
||||||
|
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
|
||||||
yarn run lint
|
./node_modules/.bin/patch-package
|
||||||
scripts/build-native.js
|
cd app
|
||||||
yarn run build:typings
|
../node_modules/.bin/patch-package
|
||||||
yarn run build
|
cd ..
|
||||||
scripts/prepackage-plugins.js
|
|
||||||
scripts/build-macos.js
|
- name: Build native deps
|
||||||
|
run: scripts/build-native.js
|
||||||
env:
|
env:
|
||||||
|
ARCH: ${{matrix.arch}}
|
||||||
|
|
||||||
|
- name: Webpack
|
||||||
|
run: yarn run build
|
||||||
|
|
||||||
|
- name: Prepackage plugins
|
||||||
|
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
|
||||||
|
run: scripts/build-macos.js
|
||||||
|
if: github.repository == 'Eugeny/terminus' && github.event_name == 'push'
|
||||||
|
env:
|
||||||
|
ARCH: ${{matrix.arch}}
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
BT_TOKEN: ${{ secrets.BT_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
|
||||||
|
run: scripts/build-macos.js
|
||||||
|
if: github.repository != 'Eugeny/terminus' || github.event_name != 'push'
|
||||||
|
env:
|
||||||
|
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: |
|
||||||
@@ -42,11 +85,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
|
||||||
|
29
.github/workflows/windows.yml
vendored
29
.github/workflows/windows.yml
vendored
@@ -11,33 +11,44 @@ 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
|
||||||
run: |
|
run: |
|
||||||
cd app
|
npm i -g yarn@1.19.1
|
||||||
yarn
|
|
||||||
cd ..
|
|
||||||
del app/node_modules/.yarn-integrity
|
|
||||||
yarn
|
yarn
|
||||||
node scripts/build-native.js
|
node scripts/build-native.js
|
||||||
yarn run build:typings
|
|
||||||
yarn run build
|
yarn run build
|
||||||
node scripts/prepackage-plugins.js
|
node scripts/prepackage-plugins.js
|
||||||
node scripts/build-windows.js
|
|
||||||
|
- name: Build and sign packages
|
||||||
|
run: node scripts/build-windows.js
|
||||||
|
if: github.repository == 'Eugeny/terminus' && github.event_name == 'push'
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
BT_TOKEN: ${{ secrets.BT_TOKEN }}
|
|
||||||
WIN_CSC_LINK: ${{ secrets.WIN_CSC_LINK }}
|
WIN_CSC_LINK: ${{ secrets.WIN_CSC_LINK }}
|
||||||
WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }}
|
WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Build packages without signing
|
||||||
|
run: node scripts/build-windows.js
|
||||||
|
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
|
||||||
|
9
.gitignore
vendored
9
.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
|
||||||
@@ -24,3 +27,9 @@ yarn-error.log
|
|||||||
docs/api
|
docs/api
|
||||||
.travis.ssh.key
|
.travis.ssh.key
|
||||||
*.code-workspace
|
*.code-workspace
|
||||||
|
|
||||||
|
.electron-symbols
|
||||||
|
sentry.properties
|
||||||
|
sentry-symbols.js
|
||||||
|
|
||||||
|
terminus-ssh/util/pagent.exe
|
||||||
|
14
.mergify.yml
Normal file
14
.mergify.yml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
pull_request_rules:
|
||||||
|
- name: automatic merge on CI success and review
|
||||||
|
conditions:
|
||||||
|
- "status-success=Windows Build / Build"
|
||||||
|
- "status-success=macOS Build / Build"
|
||||||
|
- "status-success=Linux Build / Build"
|
||||||
|
- "status-success=continuous-integration/appveyor/pr"
|
||||||
|
- "#approved-reviews-by>=1"
|
||||||
|
- "#changes-requested-reviews-by=0"
|
||||||
|
- base=master
|
||||||
|
actions:
|
||||||
|
merge:
|
||||||
|
method: merge
|
||||||
|
strict: true
|
@@ -1,5 +1,5 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js: 11
|
node_js: 15
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- Build
|
- Build
|
||||||
|
10
HACKING.md
10
HACKING.md
@@ -10,27 +10,25 @@ First, install the dependencies:
|
|||||||
|
|
||||||
```
|
```
|
||||||
# macOS/Linux:
|
# macOS/Linux:
|
||||||
npm install
|
yarn
|
||||||
./scripts/install-deps.js
|
|
||||||
./scripts/build-native.js
|
./scripts/build-native.js
|
||||||
|
|
||||||
# Windows:
|
# Windows:
|
||||||
npm -g install windows-build-tools
|
npm -g install windows-build-tools
|
||||||
npm install
|
yarn
|
||||||
node scripts\install-deps.js
|
|
||||||
node scripts\build-native.js
|
node scripts\build-native.js
|
||||||
```
|
```
|
||||||
|
|
||||||
Now, check if your build is working:
|
Now, check if your build is working:
|
||||||
|
|
||||||
```
|
```
|
||||||
npm run build
|
yarn run build
|
||||||
```
|
```
|
||||||
|
|
||||||
Start Terminus with
|
Start Terminus with
|
||||||
|
|
||||||
```
|
```
|
||||||
npm start
|
yarn start
|
||||||
```
|
```
|
||||||
|
|
||||||
# Project layout
|
# Project layout
|
||||||
|
166
README.md
166
README.md
@@ -1,50 +1,104 @@
|
|||||||

|

|
||||||
|
|
||||||
|
|
||||||
<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://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 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>
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
**Terminus** is a highly configurable terminal emulator for Windows, macOS and Linux
|
**Terminus** is a highly configurable terminal emulator, SSH and serial client for Windows, macOS and Linux
|
||||||
|
|
||||||
* Theming and color schemes
|
* Integrated SSH client and connection manager
|
||||||
* Fully configurable shortcuts
|
* Integrated serial terminal
|
||||||
* Split panes
|
* Theming and color schemes
|
||||||
* Remembers your tabs
|
* Fully configurable shortcuts and multi-chord shortcuts
|
||||||
* PowerShell (and PS Core), WSL, Git-Bash, Cygwin, Cmder and CMD support
|
* Split panes
|
||||||
* Integrated SSH client and connection manager
|
* Remembers your tabs
|
||||||
* Full Unicode support including double-width characters
|
* PowerShell (and PS Core), WSL, Git-Bash, Cygwin, Cmder and CMD support
|
||||||
* Doesn't choke on fast-flowing outputs
|
* Direct file transfer from/to SSH sessions via Zmodem
|
||||||
* Proper shell experience on Windows including tab completion (via Clink)
|
* Full Unicode support including double-width characters
|
||||||
|
* Doesn't choke on fast-flowing outputs
|
||||||
|
* Proper shell experience on Windows including tab completion (via Clink)
|
||||||
|
|
||||||
|
|
||||||
[](https://ko-fi.com/eugeny)
|
[](https://ko-fi.com/eugeny)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
# Contents
|
||||||
|
|
||||||
|
- [Contents](#contents)
|
||||||
|
- [What Terminus is and isn't](#what-terminus-is-and-isnt)
|
||||||
|
- [Terminal features](#terminal-features)
|
||||||
|
- [SSH Client](#ssh-client)
|
||||||
|
- [Serial Terminal](#serial-terminal)
|
||||||
|
- [Portable](#portable)
|
||||||
|
- [Plugins](#plugins)
|
||||||
|
- [Themes](#themes)
|
||||||
|
- [Contributing](#contributing)
|
||||||
|
|
||||||
|
<a name="about"></a>
|
||||||
|
# What Terminus is and isn't
|
||||||
|
|
||||||
* **Terminus is** an alternative to Windows' standard terminal (conhost), PowerShell ISE, PuTTY or iTerm
|
* **Terminus is** an alternative to Windows' standard terminal (conhost), PowerShell ISE, PuTTY or iTerm
|
||||||
|
|
||||||
* **Terminus is not** a new shell or a MinGW or Cygwin replacement. Neither is it lightweight - if RAM usage is of importance, consider [Conemu](https://conemu.github.io) or [Alacritty](https://github.com/jwilm/alacritty)
|
* **Terminus is not** a new shell or a MinGW or Cygwin replacement. Neither is it lightweight - if RAM usage is of importance, consider [Conemu](https://conemu.github.io) or [Alacritty](https://github.com/jwilm/alacritty)
|
||||||
|
|
||||||
---
|
<a name="terminal"></a>
|
||||||
|
# Terminal features
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
* A V220 terminal + various extensions
|
||||||
|
* Multiple nested split panes
|
||||||
|
* Tabs on any side of the window
|
||||||
|
* Optional dockable window with a global spawn hotkey ("Quake console")
|
||||||
|
* Progress detection
|
||||||
|
* Notification on process completion
|
||||||
|
* Bracketed paste, multiline paste warnings
|
||||||
|
* Font ligatures
|
||||||
|
* Custom shell profiles
|
||||||
|
* Optional RMB paste and copy-on select (PuTTY style)
|
||||||
|
|
||||||
|
<a name="ssh"></a>
|
||||||
|
# SSH Client
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
* SSH2 client with a connection manager
|
||||||
|
* X11 and port forwarding
|
||||||
|
* Automatic jump host management
|
||||||
|
* Agent forwarding (incl. Pageant and Windows native OpenSSH Agent)
|
||||||
|
* Login scripts
|
||||||
|
|
||||||
|
<a name="serial"></a>
|
||||||
|
# Serial Terminal
|
||||||
|
|
||||||
|
* Saved connections
|
||||||
|
* Readline input support
|
||||||
|
* Optional hex byte-by-byte input and hexdump output
|
||||||
|
* Newline conversion
|
||||||
|
* Automatic reconnection
|
||||||
|
|
||||||
|
<a name="portable"></a>
|
||||||
|
# Portable
|
||||||
|
|
||||||
|
Terminus will run as a portable app on Windows, if you create a `data` folder in the same location where `Terminus.exe` lives.
|
||||||
|
|
||||||
|
<a name="plugins"></a>
|
||||||
# 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.
|
||||||
|
|
||||||
* [clickable-links](https://github.com/Eugeny/terminus-clickable-links) - makes paths and URLs in the terminal clickable
|
* [clickable-links](https://github.com/Eugeny/terminus-clickable-links) - makes paths and URLs in the terminal clickable
|
||||||
* [shell-selector](https://github.com/Eugeny/terminus-shell-selector) - a quick shell selector pane
|
|
||||||
* [title-control](https://github.com/kbjr/terminus-title-control) - allows modifying the title of the terminal tabs by providing a prefix, suffix, and/or strings to be removed
|
* [title-control](https://github.com/kbjr/terminus-title-control) - allows modifying the title of the terminal tabs by providing a prefix, suffix, and/or strings to be removed
|
||||||
* [quick-cmds](https://github.com/Domain/terminus-quick-cmds) - quickly send commands to one or all terminal tabs
|
* [quick-cmds](https://github.com/Domain/terminus-quick-cmds) - quickly send commands to one or all terminal tabs
|
||||||
* [save-output](https://github.com/Eugeny/terminus-save-output) - record terminal output into a file
|
* [save-output](https://github.com/Eugeny/terminus-save-output) - record terminal output into a file
|
||||||
* [scrollbar](https://github.com/kbjr/terminus-scrollbar) - adds a scrollbar to hterm tabs
|
* [scrollbar](https://github.com/kbjr/terminus-scrollbar) - adds a scrollbar to hterm tabs
|
||||||
|
* [sync-config](https://github.com/starxg/terminus-sync-config) - sync the config to Gist or Gitee
|
||||||
|
|
||||||
|
<a name="themes"></a>
|
||||||
# Themes
|
# Themes
|
||||||
|
|
||||||
* [hype](https://github.com/Eugeny/terminus-theme-hype) - a Hyper inspired theme
|
* [hype](https://github.com/Eugeny/terminus-theme-hype) - a Hyper inspired theme
|
||||||
@@ -53,8 +107,7 @@ Plugins and themes can be installed directly from the Settings view inside Termi
|
|||||||
* [windows10](https://www.npmjs.com/package/terminus-theme-windows10)
|
* [windows10](https://www.npmjs.com/package/terminus-theme-windows10)
|
||||||
* [altair](https://github.com/yxuko/terminus-altair)
|
* [altair](https://github.com/yxuko/terminus-altair)
|
||||||
|
|
||||||
---
|
<a name="contributing"></a>
|
||||||
|
|
||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
Pull requests and plugins are welcome!
|
Pull requests and plugins are welcome!
|
||||||
@@ -62,39 +115,72 @@ Pull requests and plugins are welcome!
|
|||||||
See [HACKING.md](https://github.com/Eugeny/terminus/blob/master/HACKING.md) and [API docs](http://ajenti.org/terminus-docs/) for information of how the project is laid out, and a very brief plugin development tutorial.
|
See [HACKING.md](https://github.com/Eugeny/terminus/blob/master/HACKING.md) and [API docs](http://ajenti.org/terminus-docs/) for information of how the project is laid out, and a very brief plugin development tutorial.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
<a name="contributors"></a>
|
||||||
|
|
||||||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||||
|
|
||||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore-start -->
|
||||||
|
<!-- 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="Russell Myers"/><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="Austin Warren"/><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="Felicia Hummel"/><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="Mike MacCana"/><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="Yacine Kanzari"/><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="BBJip"/><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="Futagirl"/><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="Levin Rickert"/><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="OJ Kwon"/><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="domain"/><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="James Brumond"/><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="Daniel Imms"/><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="Florian Bachmann"/><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="Michael Kühnel"/><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="Tilmann Meyer"/><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="PM Extra"/><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="Jonathan"/><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="Hans Koch"/><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="Dak Smyth"/><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?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?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>
|
||||||
|
<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?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?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?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?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>
|
||||||
|
<td align="center"><a href="http://patalong.pl"><img src="https://avatars.githubusercontent.com/u/29167842?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Piotr Patalong</b></sub></a><br /><a href="#design-VectorKappa" title="Design">🎨</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/clarkwang"><img src="https://avatars.githubusercontent.com/u/157076?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Clark Wang</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=clarkwang" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/iamchating"><img src="https://avatars.githubusercontent.com/u/7088153?v=4?s=100" width="100px;" alt=""/><br /><sub><b>iamchating</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=iamchating" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/starxg"><img src="https://avatars.githubusercontent.com/u/34997494?v=4?s=100" width="100px;" alt=""/><br /><sub><b>starxg</b></sub></a><br /><a href="#plugin-starxg" title="Plugin/utility libraries">🔌</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<!-- markdownlint-restore -->
|
||||||
|
<!-- 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!
|
||||||
|
@@ -1,55 +1 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="Layer_1" x="0" y="0" version="1.1" viewBox="0 0 1024 1024" xml:space="preserve" style="enable-background:new 0 0 1024 1024"><style type="text/css">.st0{fill:url(#SVGID_1_)}.st1{opacity:.16;fill:url(#SVGID_2_)}.st2{fill:url(#SVGID_3_)}.st3{opacity:.16;fill:url(#SVGID_4_)}.st4{fill:url(#SVGID_5_)}.st5{opacity:.15;fill:url(#SVGID_6_)}.st6{fill:url(#SVGID_7_)}</style><g><linearGradient id="SVGID_1_" x1="260.967" x2="919.184" y1="871.181" y2="491.16" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#669abd"/><stop offset="1" style="stop-color:#77dbdb"/></linearGradient><polygon points="297.54 934.52 882.6 596.72 882.61 427.82 297.54 765.65" class="st0"/><linearGradient id="SVGID_2_" x1="553.505" x2="626.647" y1="617.828" y2="744.513" gradientUnits="userSpaceOnUse"><stop offset=".559" style="stop-color:#000;stop-opacity:0"/><stop offset="1" style="stop-color:#000"/></linearGradient><polygon points="297.54 934.52 882.6 596.72 882.61 427.82 297.54 765.65" class="st1"/></g><g><linearGradient id="SVGID_3_" x1="114.663" x2="334.091" y1="744.528" y2="871.214" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#6a8fad"/><stop offset="1" style="stop-color:#669abd"/></linearGradient><polygon points="151.23 681.18 151.22 850.09 297.54 934.52 297.54 765.65" class="st2"/><linearGradient id="SVGID_4_" x1="260.948" x2="187.806" y1="744.528" y2="871.213" gradientUnits="userSpaceOnUse"><stop offset=".559" style="stop-color:#000;stop-opacity:0"/><stop offset="1" style="stop-color:#000"/></linearGradient><polygon points="151.23 681.18 151.22 850.09 297.54 934.52 297.54 765.65" class="st3"/></g><g><linearGradient id="SVGID_5_" x1="114.663" x2="553.503" y1="237.793" y2="491.157" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#6a8fad"/><stop offset="1" style="stop-color:#669abd"/></linearGradient><polygon points="151.23 174.45 151.21 343.36 443.79 512.27 590.08 427.81" class="st4"/><linearGradient id="SVGID_6_" x1="370.656" x2="297.509" y1="301.128" y2="427.822" gradientUnits="userSpaceOnUse"><stop offset=".559" style="stop-color:#000;stop-opacity:0"/><stop offset="1" style="stop-color:#000"/></linearGradient><polygon points="151.23 174.45 151.21 343.36 443.79 512.27 590.08 427.81" class="st5"/></g><linearGradient id="SVGID_7_" x1="78.091" x2="736.337" y1="554.498" y2="174.459" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#ccecff"/><stop offset="1" style="stop-color:#9feced"/></linearGradient><polygon points="297.51 765.64 151.23 681.18 590.08 427.81 151.23 174.45 297.5 90 882.61 427.82" class="st6"/></svg>
|
||||||
<!-- Generator: Adobe Illustrator 23.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
||||||
viewBox="0 0 1024 1024" style="enable-background:new 0 0 1024 1024;" xml:space="preserve">
|
|
||||||
<style type="text/css">
|
|
||||||
.st0{fill:url(#SVGID_1_);}
|
|
||||||
.st1{opacity:0.16;fill:url(#SVGID_2_);}
|
|
||||||
.st2{fill:url(#SVGID_3_);}
|
|
||||||
.st3{opacity:0.16;fill:url(#SVGID_4_);}
|
|
||||||
.st4{fill:url(#SVGID_5_);}
|
|
||||||
.st5{opacity:0.15;fill:url(#SVGID_6_);}
|
|
||||||
.st6{fill:url(#SVGID_7_);}
|
|
||||||
</style>
|
|
||||||
<g>
|
|
||||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="260.9675" y1="871.1813" x2="919.1845" y2="491.1596">
|
|
||||||
<stop offset="0" style="stop-color:#669ABD"/>
|
|
||||||
<stop offset="1" style="stop-color:#77DBDB"/>
|
|
||||||
</linearGradient>
|
|
||||||
<polygon class="st0" points="297.54,934.52 882.6,596.72 882.61,427.82 297.54,765.65 "/>
|
|
||||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="553.5051" y1="617.8278" x2="626.647" y2="744.5132">
|
|
||||||
<stop offset="0.5588" style="stop-color:#000000;stop-opacity:0"/>
|
|
||||||
<stop offset="1" style="stop-color:#000000"/>
|
|
||||||
</linearGradient>
|
|
||||||
<polygon class="st1" points="297.54,934.52 882.6,596.72 882.61,427.82 297.54,765.65 "/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="114.6631" y1="744.5275" x2="334.0905" y2="871.2141">
|
|
||||||
<stop offset="0" style="stop-color:#6A8FAD"/>
|
|
||||||
<stop offset="1" style="stop-color:#669ABD"/>
|
|
||||||
</linearGradient>
|
|
||||||
<polygon class="st2" points="151.23,681.18 151.22,850.09 297.54,934.52 297.54,765.65 "/>
|
|
||||||
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="260.9478" y1="744.5281" x2="187.8059" y2="871.2135">
|
|
||||||
<stop offset="0.5588" style="stop-color:#000000;stop-opacity:0"/>
|
|
||||||
<stop offset="1" style="stop-color:#000000"/>
|
|
||||||
</linearGradient>
|
|
||||||
<polygon class="st3" points="151.23,681.18 151.22,850.09 297.54,934.52 297.54,765.65 "/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="114.663" y1="237.793" x2="553.5026" y2="491.1571">
|
|
||||||
<stop offset="0" style="stop-color:#6A8FAD"/>
|
|
||||||
<stop offset="1" style="stop-color:#669ABD"/>
|
|
||||||
</linearGradient>
|
|
||||||
<polygon class="st4" points="151.23,174.45 151.21,343.36 443.79,512.27 590.08,427.81 "/>
|
|
||||||
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="370.6562" y1="301.1281" x2="297.5094" y2="427.8221">
|
|
||||||
<stop offset="0.5588" style="stop-color:#000000;stop-opacity:0"/>
|
|
||||||
<stop offset="1" style="stop-color:#000000"/>
|
|
||||||
</linearGradient>
|
|
||||||
<polygon class="st5" points="151.23,174.45 151.21,343.36 443.79,512.27 590.08,427.81 "/>
|
|
||||||
</g>
|
|
||||||
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="78.0912" y1="554.4979" x2="736.3375" y2="174.4593">
|
|
||||||
<stop offset="0" style="stop-color:#CCECFF"/>
|
|
||||||
<stop offset="1" style="stop-color:#9FECED"/>
|
|
||||||
</linearGradient>
|
|
||||||
<polygon class="st6" points="297.51,765.64 151.23,681.18 590.08,427.81 151.23,174.45 297.5,90 882.61,427.82 "/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.6 KiB |
@@ -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%')
|
||||||
|
|
||||||
|
|
||||||
|
111
app/lib/app.ts
111
app/lib/app.ts
@@ -1,38 +1,68 @@
|
|||||||
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 * as remote from '@electron/remote/main'
|
||||||
|
|
||||||
import { loadConfig } from './config'
|
import { loadConfig } from './config'
|
||||||
import { Window, WindowOptions } from './window'
|
import { Window, WindowOptions } from './window'
|
||||||
|
import { pluginManager } from './pluginManager'
|
||||||
|
import { PTYManager } from './pty'
|
||||||
|
|
||||||
export class Application {
|
export class Application {
|
||||||
private tray: Tray
|
private tray?: Tray
|
||||||
|
private ptyManager = new PTYManager()
|
||||||
private windows: Window[] = []
|
private windows: Window[] = []
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
ipcMain.on('app:config-change', () => {
|
remote.initialize()
|
||||||
this.broadcast('host:config-change')
|
this.ptyManager.init(this)
|
||||||
|
|
||||||
|
ipcMain.on('app:config-change', (_event, config) => {
|
||||||
|
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()
|
||||||
if (process.platform === 'linux' && ((configData.appearance || {}).opacity || 1) !== 1) {
|
if (process.platform === 'linux') {
|
||||||
app.commandLine.appendSwitch('enable-transparent-visuals')
|
app.commandLine.appendSwitch('no-sandbox')
|
||||||
app.disableHardwareAcceleration()
|
if (((configData.appearance || {}).opacity || 1) !== 1) {
|
||||||
|
app.commandLine.appendSwitch('enable-transparent-visuals')
|
||||||
|
app.disableHardwareAcceleration()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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']]) {
|
||||||
console.log('Setting Electron flag:', flag.join('='))
|
|
||||||
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) {
|
||||||
@@ -41,6 +71,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()
|
||||||
}
|
}
|
||||||
@@ -48,21 +81,39 @@ 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 || process.platform === 'linux') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
@@ -72,7 +123,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',
|
||||||
@@ -86,25 +137,31 @@ export class Application {
|
|||||||
this.tray.setToolTip(`Terminus ${app.getVersion()}`)
|
this.tray.setToolTip(`Terminus ${app.getVersion()}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
disableTray () {
|
disableTray (): void {
|
||||||
if (this.tray) {
|
if (process.platform === 'linux') {
|
||||||
this.tray.destroy()
|
return
|
||||||
this.tray = null
|
|
||||||
}
|
}
|
||||||
|
this.tray?.destroy()
|
||||||
|
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: [
|
||||||
@@ -183,7 +240,7 @@ export class Application {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
|
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
|
||||||
|
57
app/lib/bufferizedPTY.js
Normal file
57
app/lib/bufferizedPTY.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/** @hidden */
|
||||||
|
module.exports = function patchPTYModule (mod) {
|
||||||
|
const oldSpawn = mod.spawn
|
||||||
|
if (mod.patched) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mod.patched = true
|
||||||
|
mod.spawn = (file, args, opt) => {
|
||||||
|
let terminal = oldSpawn(file, args, opt)
|
||||||
|
let timeout = null
|
||||||
|
let buffer = Buffer.from('')
|
||||||
|
let lastFlush = 0
|
||||||
|
let nextTimeout = 0
|
||||||
|
|
||||||
|
// Minimum prebuffering window (ms) if the input is non-stop flowing
|
||||||
|
const minWindow = 5
|
||||||
|
|
||||||
|
// Maximum buffering time (ms) until output must be flushed unconditionally
|
||||||
|
const maxWindow = 100
|
||||||
|
|
||||||
|
function flush () {
|
||||||
|
if (buffer.length) {
|
||||||
|
terminal.emit('data-buffered', buffer)
|
||||||
|
}
|
||||||
|
lastFlush = Date.now()
|
||||||
|
buffer = Buffer.from('')
|
||||||
|
}
|
||||||
|
|
||||||
|
function reschedule () {
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
}
|
||||||
|
nextTimeout = Date.now() + minWindow
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
timeout = null
|
||||||
|
flush()
|
||||||
|
}, minWindow)
|
||||||
|
}
|
||||||
|
|
||||||
|
terminal.on('data', data => {
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
data = Buffer.from(data)
|
||||||
|
}
|
||||||
|
buffer = Buffer.concat([buffer, data])
|
||||||
|
if (Date.now() - lastFlush > maxWindow) {
|
||||||
|
// Taking too much time buffering, flush to keep things interactive
|
||||||
|
flush()
|
||||||
|
} else {
|
||||||
|
if (Date.now() > nextTimeout - maxWindow / 10) {
|
||||||
|
// Extend the window if it's expiring
|
||||||
|
reschedule()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return terminal
|
||||||
|
}
|
||||||
|
}
|
@@ -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,9 +4,9 @@ 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.load(fs.readFileSync(configPath, 'utf8'))
|
||||||
} else {
|
} else {
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,6 @@
|
|||||||
|
import './portable'
|
||||||
|
import 'source-map-support/register'
|
||||||
|
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'
|
||||||
@@ -32,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())
|
||||||
@@ -46,21 +49,24 @@ if (argv.d) {
|
|||||||
electronDebug({
|
electronDebug({
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
showDevTools: true,
|
showDevTools: true,
|
||||||
devToolsMode: 'undocked'
|
devToolsMode: 'undocked',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
14
app/lib/portable.ts
Executable file
14
app/lib/portable.ts
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
import * as path from 'path'
|
||||||
|
import * as fs from 'fs'
|
||||||
|
import * as electron from 'electron'
|
||||||
|
|
||||||
|
const appPath = path.dirname(electron.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)
|
||||||
|
electron.app.setPath('userData', portableData)
|
||||||
|
}
|
154
app/lib/pty.ts
Normal file
154
app/lib/pty.ts
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
import * as nodePTY from '@terminus-term/node-pty'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
import { ipcMain } from 'electron'
|
||||||
|
import { Application } from './app'
|
||||||
|
|
||||||
|
class PTYDataQueue {
|
||||||
|
private buffers: Buffer[] = []
|
||||||
|
private delta = 0
|
||||||
|
private maxChunk = 1024
|
||||||
|
private maxDelta = 1024 * 50
|
||||||
|
private flowPaused = false
|
||||||
|
|
||||||
|
constructor (private pty: nodePTY.IPty, private onData: (data: Buffer) => void) { }
|
||||||
|
|
||||||
|
push (data: Buffer) {
|
||||||
|
this.buffers.push(data)
|
||||||
|
this.maybeEmit()
|
||||||
|
}
|
||||||
|
|
||||||
|
ack (length: number) {
|
||||||
|
this.delta -= length
|
||||||
|
this.maybeEmit()
|
||||||
|
}
|
||||||
|
|
||||||
|
private maybeEmit () {
|
||||||
|
if (this.delta <= this.maxDelta && this.flowPaused) {
|
||||||
|
this.resume()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.buffers.length > 0) {
|
||||||
|
if (this.delta > this.maxDelta && !this.flowPaused) {
|
||||||
|
this.pause()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const buffersToSend = []
|
||||||
|
let totalLength = 0
|
||||||
|
while (totalLength < this.maxChunk && this.buffers.length) {
|
||||||
|
totalLength += this.buffers[0].length
|
||||||
|
buffersToSend.push(this.buffers.shift())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffersToSend.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let toSend = Buffer.concat(buffersToSend)
|
||||||
|
if (toSend.length > this.maxChunk) {
|
||||||
|
this.buffers.unshift(toSend.slice(this.maxChunk))
|
||||||
|
toSend = toSend.slice(0, this.maxChunk)
|
||||||
|
}
|
||||||
|
this.onData(toSend)
|
||||||
|
this.delta += toSend.length
|
||||||
|
|
||||||
|
if (this.buffers.length) {
|
||||||
|
setImmediate(() => this.maybeEmit())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private pause () {
|
||||||
|
this.pty.pause()
|
||||||
|
this.flowPaused = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private resume () {
|
||||||
|
this.pty.resume()
|
||||||
|
this.flowPaused = false
|
||||||
|
this.maybeEmit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PTY {
|
||||||
|
private pty: nodePTY.IPty
|
||||||
|
private outputQueue: PTYDataQueue
|
||||||
|
|
||||||
|
constructor (private id: string, private app: Application, ...args: any[]) {
|
||||||
|
this.pty = (nodePTY as any).spawn(...args)
|
||||||
|
for (const key of ['close', 'exit']) {
|
||||||
|
(this.pty as any).on(key, (...eventArgs) => this.emit(key, ...eventArgs))
|
||||||
|
}
|
||||||
|
|
||||||
|
this.outputQueue = new PTYDataQueue(this.pty, data => {
|
||||||
|
setImmediate(() => this.emit('data-buffered', data))
|
||||||
|
})
|
||||||
|
|
||||||
|
this.pty.on('data', data => this.outputQueue.push(Buffer.from(data)))
|
||||||
|
}
|
||||||
|
|
||||||
|
getPID (): number {
|
||||||
|
return this.pty.pid
|
||||||
|
}
|
||||||
|
|
||||||
|
resize (columns: number, rows: number): void {
|
||||||
|
if ((this.pty as any)._writable) {
|
||||||
|
this.pty.resize(columns, rows)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write (buffer: Buffer): void {
|
||||||
|
if ((this.pty as any)._writable) {
|
||||||
|
this.pty.write(buffer.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ackData (length: number): void {
|
||||||
|
this.outputQueue.ack(length)
|
||||||
|
}
|
||||||
|
|
||||||
|
kill (signal?: string): void {
|
||||||
|
this.pty.kill(signal)
|
||||||
|
}
|
||||||
|
|
||||||
|
private emit (event: string, ...args: any[]) {
|
||||||
|
this.app.broadcast(`pty:${this.id}:${event}`, ...args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PTYManager {
|
||||||
|
private ptys: Record<string, PTY> = {}
|
||||||
|
|
||||||
|
init (app: Application): void {
|
||||||
|
//require('./bufferizedPTY')(nodePTY) // eslint-disable-line @typescript-eslint/no-var-requires
|
||||||
|
ipcMain.on('pty:spawn', (event, ...options) => {
|
||||||
|
const id = uuidv4().toString()
|
||||||
|
event.returnValue = id
|
||||||
|
this.ptys[id] = new PTY(id, app, ...options)
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('pty:exists', (event, id) => {
|
||||||
|
event.returnValue = !!this.ptys[id]
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('pty:get-pid', (event, id) => {
|
||||||
|
event.returnValue = this.ptys[id].getPID()
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('pty:resize', (_event, id, columns, rows) => {
|
||||||
|
this.ptys[id].resize(columns, rows)
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('pty:write', (_event, id, data) => {
|
||||||
|
this.ptys[id].write(Buffer.from(data))
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('pty:kill', (_event, id, signal) => {
|
||||||
|
this.ptys[id].kill(signal)
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('pty:ack-data', (_event, id, length) => {
|
||||||
|
this.ptys[id].ackData(length)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
19
app/lib/sentry.ts
Executable file
19
app/lib/sentry.ts
Executable file
@@ -0,0 +1,19 @@
|
|||||||
|
const { init } = String(process.type) === 'main' ? require('@sentry/electron/dist/main') : require('@sentry/electron/dist/renderer')
|
||||||
|
|
||||||
|
const SENTRY_DSN = 'https://4717a0a7ee0b4429bd3a0f06c3d7eec3@sentry.io/181876'
|
||||||
|
let release = null
|
||||||
|
try {
|
||||||
|
release = require('electron').app.getVersion()
|
||||||
|
} catch {
|
||||||
|
release = require('@electron/remote').app.getVersion()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!process.env.TERMINUS_DEV) {
|
||||||
|
init({
|
||||||
|
dsn: SENTRY_DSN,
|
||||||
|
release,
|
||||||
|
integrations (integrations) {
|
||||||
|
return integrations.filter(integration => integration.name !== 'Breadcrumbs')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
@@ -1,17 +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 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,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',
|
||||||
@@ -46,42 +60,48 @@ export class Window {
|
|||||||
minHeight: 300,
|
minHeight: 300,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: true,
|
nodeIntegration: true,
|
||||||
|
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',
|
||||||
}
|
}
|
||||||
|
|
||||||
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') {
|
||||||
bwOptions.titleBarStyle = 'hiddenInset'
|
bwOptions.titleBarStyle = 'hidden'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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('dark')
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,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()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -106,7 +133,7 @@ export class Window {
|
|||||||
this.ready = new Promise(resolve => {
|
this.ready = new Promise(resolve => {
|
||||||
const listener = event => {
|
const listener = event => {
|
||||||
if (event.sender === this.window.webContents) {
|
if (event.sender === this.window.webContents) {
|
||||||
ipcMain.removeListener('app:ready', listener)
|
ipcMain.removeListener('app:ready', listener as any)
|
||||||
resolve()
|
resolve()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -114,70 +141,126 @@ 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 () {
|
||||||
this.window.on('show', () => {
|
this.window.on('show', () => {
|
||||||
this.visible.next(true)
|
this.visible.next(true)
|
||||||
this.window.webContents.send('host:window-shown')
|
this.send('host:window-shown')
|
||||||
})
|
})
|
||||||
|
|
||||||
this.window.on('hide', () => {
|
this.window.on('hide', () => {
|
||||||
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.window.webContents.send('host:window-moved')
|
this.send('host:window-moved')
|
||||||
})
|
})
|
||||||
|
|
||||||
this.window.on('closed', () => {
|
this.window.on('closed', () => {
|
||||||
moveSubscription.unsubscribe()
|
moveSubscription.unsubscribe()
|
||||||
})
|
})
|
||||||
|
|
||||||
this.window.on('enter-full-screen', () => this.window.webContents.send('host:window-enter-full-screen'))
|
this.window.on('enter-full-screen', () => this.send('host:window-enter-full-screen'))
|
||||||
this.window.on('leave-full-screen', () => this.window.webContents.send('host:window-leave-full-screen'))
|
this.window.on('leave-full-screen', () => this.send('host:window-leave-full-screen'))
|
||||||
|
|
||||||
this.window.on('close', event => {
|
this.window.on('close', event => {
|
||||||
if (!this.closing) {
|
if (!this.closing) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
this.window.webContents.send('host:window-close-request')
|
this.send('host:window-close-request')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.windowConfig.set('windowBoundaries', this.windowBounds)
|
this.windowConfig.set('windowBoundaries', this.windowBounds)
|
||||||
@@ -200,6 +283,10 @@ export class Window {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.window.on('focus', () => {
|
||||||
|
this.send('host:window-focused')
|
||||||
|
})
|
||||||
|
|
||||||
ipcMain.on('window-focus', event => {
|
ipcMain.on('window-focus', event => {
|
||||||
if (!this.window || event.sender !== this.window.webContents) {
|
if (!this.window || event.sender !== this.window.webContents) {
|
||||||
return
|
return
|
||||||
@@ -287,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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus",
|
"name": "terminus",
|
||||||
"description": "A terminal for a modern age",
|
"description": "A terminal for a modern age",
|
||||||
|
"private": true,
|
||||||
"repository": "https://github.com/eugeny/terminus",
|
"repository": "https://github.com/eugeny/terminus",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Eugene Pankov",
|
"name": "Eugene Pankov",
|
||||||
@@ -13,41 +14,56 @@
|
|||||||
"watch": "webpack --progress --color --watch"
|
"watch": "webpack --progress --color --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "7.2.8",
|
"@angular/animations": "^11.1.1",
|
||||||
"@angular/common": "7.2.8",
|
"@angular/common": "^11.1.1",
|
||||||
"@angular/compiler": "7.2.8",
|
"@angular/compiler": "^11.1.1",
|
||||||
"@angular/core": "7.2.8",
|
"@angular/core": "^11.1.1",
|
||||||
"@angular/forms": "7.2.8",
|
"@angular/forms": "^11.1.1",
|
||||||
"@angular/platform-browser": "7.2.8",
|
"@angular/platform-browser": "^11.1.1",
|
||||||
"@angular/platform-browser-dynamic": "7.2.8",
|
"@angular/platform-browser-dynamic": "^11.1.1",
|
||||||
"@ng-bootstrap/ng-bootstrap": "^4.2.0",
|
"@electron/remote": "^1.0.4",
|
||||||
"devtron": "1.4.0",
|
"@ng-bootstrap/ng-bootstrap": "^7.0.0",
|
||||||
|
"any-promise": "^1.3.0",
|
||||||
"electron-config": "2.0.0",
|
"electron-config": "2.0.0",
|
||||||
"electron-debug": "^3.0.1",
|
"electron-debug": "^3.2.0",
|
||||||
"electron-is-dev": "1.1.0",
|
"electron-promise-ipc": "^2.2.4",
|
||||||
"electron-updater": "^4.0.6",
|
"fontmanager-redux": "1.0.0",
|
||||||
"fontmanager-redux": "0.3.3",
|
"glasstron": "0.0.7",
|
||||||
"js-yaml": "3.13.1",
|
"js-yaml": "4.0.0",
|
||||||
"keytar": "^5.0.0-beta.0",
|
"keytar": "^7.6.0",
|
||||||
"mz": "^2.7.0",
|
"mz": "^2.7.0",
|
||||||
"ngx-toastr": "^10.1.0",
|
"native-process-working-directory": "^1.0.2",
|
||||||
"node-pty": "^0.9.0-beta22",
|
"ngx-toastr": "^13.2.1",
|
||||||
"npm": "6.9.0",
|
"@terminus-term/node-pty": "0.10.0-terminus.3",
|
||||||
|
"npm": "6",
|
||||||
"path": "0.12.7",
|
"path": "0.12.7",
|
||||||
"rxjs": "^6.5.2",
|
"rxjs": "^6.6.6",
|
||||||
"rxjs-compat": "^6.5.2",
|
"yargs": "^16.2.0",
|
||||||
"yargs": "^14.0.0",
|
"zone.js": "^0.11.4"
|
||||||
"zone.js": "^0.8.29"
|
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"macos-native-processlist": "^1.0.1",
|
"macos-native-processlist": "^2.0.0",
|
||||||
|
"serialport": "^9.0.7",
|
||||||
"windows-blurbehind": "^1.0.1",
|
"windows-blurbehind": "^1.0.1",
|
||||||
"windows-native-registry": "^1.0.14",
|
"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": "2.7.3",
|
||||||
"node-abi": "^2.11.0"
|
"@types/node": "14.14.35",
|
||||||
|
"node-abi": "^2.21.0",
|
||||||
|
"source-map-support": "^0.5.19"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"terminus-community-color-schemes": "*",
|
||||||
|
"terminus-core": "*",
|
||||||
|
"terminus-plugin-manager": "*",
|
||||||
|
"terminus-serial": "*",
|
||||||
|
"terminus-settings": "*",
|
||||||
|
"terminus-ssh": "*",
|
||||||
|
"terminus-terminal": "*"
|
||||||
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"*/node-abi": "^2.20.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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,12 +8,12 @@ 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',
|
||||||
preventDuplicates: true,
|
preventDuplicates: true,
|
||||||
extendedTimeOut: 5000,
|
extendedTimeOut: 1000,
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
const bootstrap = [
|
const bootstrap = [
|
||||||
|
@@ -3,36 +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'
|
||||||
|
|
||||||
import * as Raven from 'raven-js'
|
|
||||||
|
|
||||||
const SENTRY_DSN = 'https://4717a0a7ee0b4429bd3a0f06c3d7eec3@sentry.io/181876'
|
|
||||||
|
|
||||||
Raven.config(
|
|
||||||
SENTRY_DSN,
|
|
||||||
{
|
|
||||||
release: require('electron').remote.app.getVersion(),
|
|
||||||
dataCallback: (data: any) => {
|
|
||||||
const normalize = (filename: string) => {
|
|
||||||
const splitArray = filename.split('/')
|
|
||||||
return splitArray[splitArray.length - 1]
|
|
||||||
}
|
|
||||||
|
|
||||||
data.exception.values[0].stacktrace.frames.forEach((frame: any) => {
|
|
||||||
frame.filename = normalize(frame.filename)
|
|
||||||
})
|
|
||||||
|
|
||||||
data.culprit = data.exception.values[0].stacktrace.frames[0].filename
|
|
||||||
|
|
||||||
return data
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
process.on('uncaughtException' as any, (err) => {
|
|
||||||
Raven.captureException(err as any)
|
|
||||||
console.error(err)
|
|
||||||
})
|
|
||||||
|
@@ -2,12 +2,11 @@ import 'zone.js'
|
|||||||
import 'core-js/proposals/reflect-metadata'
|
import 'core-js/proposals/reflect-metadata'
|
||||||
import 'rxjs'
|
import 'rxjs'
|
||||||
|
|
||||||
import * as isDev from 'electron-is-dev'
|
|
||||||
|
|
||||||
import './global.scss'
|
import './global.scss'
|
||||||
import './toastr.scss'
|
import './toastr.scss'
|
||||||
|
|
||||||
import { enableProdMode, NgModuleRef } from '@angular/core'
|
import { enableProdMode, NgModuleRef, ApplicationRef } from '@angular/core'
|
||||||
|
import { enableDebugTools } from '@angular/platform-browser'
|
||||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
|
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
|
||||||
|
|
||||||
import { getRootModule } from './app.module'
|
import { getRootModule } from './app.module'
|
||||||
@@ -18,11 +17,11 @@ location.hash = ''
|
|||||||
|
|
||||||
;(process as any).enablePromiseAPI = true
|
;(process as any).enablePromiseAPI = true
|
||||||
|
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32' && !('HOME' in process.env)) {
|
||||||
process.env.HOME = process.env.HOMEDRIVE + process.env.HOMEPATH
|
process.env.HOME = `${process.env.HOMEDRIVE}${process.env.HOMEPATH}`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDev) {
|
if (process.env.TERMINUS_DEV) {
|
||||||
console.warn('Running in debug mode')
|
console.warn('Running in debug mode')
|
||||||
} else {
|
} else {
|
||||||
enableProdMode()
|
enableProdMode()
|
||||||
@@ -37,7 +36,14 @@ async function bootstrap (plugins: PluginInfo[], safeMode = false): Promise<NgMo
|
|||||||
})
|
})
|
||||||
const module = getRootModule(pluginsModules)
|
const module = getRootModule(pluginsModules)
|
||||||
window['rootModule'] = module
|
window['rootModule'] = module
|
||||||
return platformBrowserDynamic().bootstrapModule(module)
|
return platformBrowserDynamic().bootstrapModule(module).then(moduleRef => {
|
||||||
|
if (process.env.TERMINUS_DEV) {
|
||||||
|
const applicationRef = moduleRef.injector.get(ApplicationRef)
|
||||||
|
const componentRef = applicationRef.components[0]
|
||||||
|
enableDebugTools(componentRef)
|
||||||
|
}
|
||||||
|
return moduleRef
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
findPlugins().then(async plugins => {
|
findPlugins().then(async plugins => {
|
||||||
@@ -50,8 +56,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@@ -95,3 +95,12 @@ input[type=range] {
|
|||||||
&::-moz-range-track { @include track(); }
|
&::-moz-range-track { @include track(); }
|
||||||
&::-ms-track { @include track(); }
|
&::-ms-track { @include track(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a[ngbdropdownitem] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngb-typeahead-window {
|
||||||
|
max-height: 60vh;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
@@ -1,28 +1,28 @@
|
|||||||
import * as fs from 'mz/fs'
|
import * as fs from 'mz/fs'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
|
import * as remote from '@electron/remote'
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeRequire.main.paths.map((x: string) => nodeModule.globalPaths.push(normalizePath(x)))
|
global['module'].paths.map((x: string) => nodeModule.globalPaths.push(normalizePath(x)))
|
||||||
|
|
||||||
if (process.env.TERMINUS_DEV) {
|
if (process.env.TERMINUS_DEV) {
|
||||||
nodeModule.globalPaths.unshift(path.dirname(require('electron').remote.app.getAppPath()))
|
nodeModule.globalPaths.unshift(path.dirname(remote.app.getAppPath()))
|
||||||
}
|
}
|
||||||
|
|
||||||
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(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'),
|
remote.app.getPath('userData'),
|
||||||
'terminus',
|
|
||||||
'plugins',
|
'plugins',
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -64,7 +64,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 +83,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
|
||||||
@@ -156,7 +155,9 @@ export async function findPlugins (): Promise<PluginInfo[]> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(window as any).installedPlugins = foundPlugins
|
foundPlugins.sort((a, b) => a.name > b.name ? 1 : -1)
|
||||||
|
|
||||||
|
;(window as any).installedPlugins = foundPlugins
|
||||||
return foundPlugins
|
return foundPlugins
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,8 +173,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))
|
||||||
|
@@ -7,6 +7,7 @@
|
|||||||
height: 100vh;
|
height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
animation: 0.5s ease-out fadeIn;
|
animation: 0.5s ease-out fadeIn;
|
||||||
|
background: radial-gradient(#3a66820a 0%, #000e17 30%, black 100%);
|
||||||
|
|
||||||
&>div {
|
&>div {
|
||||||
width: 200px;
|
width: 200px;
|
||||||
@@ -23,6 +24,7 @@
|
|||||||
transition: 1s ease-out width;
|
transition: 1s ease-out width;
|
||||||
background: #a1c5e4;
|
background: #a1c5e4;
|
||||||
height: 3px;
|
height: 3px;
|
||||||
|
box-shadow: 0 0 2px #ffffff1f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,8 +39,8 @@
|
|||||||
|
|
||||||
|
|
||||||
.terminus-logo {
|
.terminus-logo {
|
||||||
width: 160px;
|
width: 120px;
|
||||||
height: 160px;
|
height: 120px;
|
||||||
background: url('../assets/logo.svg');
|
background: url('../assets/logo.svg');
|
||||||
background-repeat: none;
|
background-repeat: none;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
@@ -51,7 +53,7 @@
|
|||||||
font-family: 'Source Sans Pro';
|
font-family: 'Source Sans Pro';
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-size: 42px;
|
font-size: 32px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
sup {
|
sup {
|
||||||
|
@@ -2,13 +2,20 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 20px;
|
padding: 20px 0 50px;
|
||||||
|
|
||||||
.toast {
|
.toast {
|
||||||
box-shadow: 0 1px 0 rgba(0,0,0,.25);
|
box-shadow: 0 1px 0 rgba(0,0,0,.25);
|
||||||
padding: 10px;
|
padding: 7px 12px;
|
||||||
background-image: none;
|
background-image: none;
|
||||||
width: auto;
|
width: auto;
|
||||||
|
flex-basis: auto;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
|
||||||
|
&.toast-error {
|
||||||
|
background-color: #BD362F;
|
||||||
|
}
|
||||||
|
|
||||||
&.toast-info {
|
&.toast-info {
|
||||||
background-color: #555;
|
background-color: #555;
|
||||||
|
@@ -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",
|
||||||
|
@@ -6,6 +6,7 @@ module.exports = {
|
|||||||
target: 'node',
|
target: 'node',
|
||||||
entry: {
|
entry: {
|
||||||
'index.ignore': 'file-loader?name=index.html!pug-html-loader!' + path.resolve(__dirname, './index.pug'),
|
'index.ignore': 'file-loader?name=index.html!pug-html-loader!' + path.resolve(__dirname, './index.pug'),
|
||||||
|
sentry: path.resolve(__dirname, 'lib/sentry.ts'),
|
||||||
preload: path.resolve(__dirname, 'src/entry.preload.ts'),
|
preload: path.resolve(__dirname, 'src/entry.preload.ts'),
|
||||||
bundle: path.resolve(__dirname, 'src/entry.ts'),
|
bundle: path.resolve(__dirname, 'src/entry.ts'),
|
||||||
},
|
},
|
||||||
@@ -67,7 +68,6 @@ module.exports = {
|
|||||||
'@ng-bootstrap/ng-bootstrap': 'commonjs @ng-bootstrap/ng-bootstrap',
|
'@ng-bootstrap/ng-bootstrap': 'commonjs @ng-bootstrap/ng-bootstrap',
|
||||||
child_process: 'commonjs child_process',
|
child_process: 'commonjs child_process',
|
||||||
electron: 'commonjs electron',
|
electron: 'commonjs electron',
|
||||||
'electron-is-dev': 'commonjs electron-is-dev',
|
|
||||||
fs: 'commonjs fs',
|
fs: 'commonjs fs',
|
||||||
'ngx-toastr': 'commonjs ngx-toastr',
|
'ngx-toastr': 'commonjs ngx-toastr',
|
||||||
module: 'commonjs module',
|
module: 'commonjs module',
|
||||||
@@ -78,5 +78,8 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.optimize.ModuleConcatenationPlugin(),
|
new webpack.optimize.ModuleConcatenationPlugin(),
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
'process.type': '"renderer"'
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const webpack = require('webpack')
|
const webpack = require('webpack')
|
||||||
|
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
name: 'terminus-main',
|
name: 'terminus-main',
|
||||||
target: 'node',
|
target: 'electron-main',
|
||||||
entry: {
|
entry: {
|
||||||
main: path.resolve(__dirname, 'lib/index.ts'),
|
main: path.resolve(__dirname, 'lib/index.ts'),
|
||||||
},
|
},
|
||||||
@@ -33,17 +34,34 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
externals: {
|
externals: {
|
||||||
|
'any-promise': 'commonjs any-promise',
|
||||||
electron: 'commonjs electron',
|
electron: 'commonjs electron',
|
||||||
'electron-config': 'commonjs electron-config',
|
'electron-config': 'commonjs electron-config',
|
||||||
|
'electron-debug': 'commonjs electron-debug',
|
||||||
|
'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',
|
||||||
|
'@terminus-term/node-pty': 'commonjs @terminus-term/node-pty',
|
||||||
path: 'commonjs path',
|
path: 'commonjs path',
|
||||||
yargs: 'commonjs yargs',
|
rxjs: 'commonjs rxjs',
|
||||||
|
'rxjs/operators': 'commonjs rxjs/operators',
|
||||||
|
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',
|
||||||
|
'yargs/yargs': 'commonjs yargs/yargs',
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.optimize.ModuleConcatenationPlugin(),
|
new webpack.optimize.ModuleConcatenationPlugin(),
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
'process.type': '"main"',
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.env.BUNDLE_ANALYZER) {
|
||||||
|
module.exports.plugins.push(new BundleAnalyzerPlugin())
|
||||||
|
}
|
||||||
|
2580
app/yarn.lock
2580
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}"
|
||||||
|
|
||||||
|
@@ -1,57 +1 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="Layer_1" x="0" y="0" version="1.1" viewBox="0 0 1024 1024" xml:space="preserve" style="enable-background:new 0 0 1024 1024"><style type="text/css">.st0{opacity:.8;fill:#00232e}.st1{fill:url(#SVGID_1_)}.st2{opacity:.16;fill:url(#SVGID_2_)}.st3{fill:url(#SVGID_3_)}.st4{opacity:.16;fill:url(#SVGID_4_)}.st5{fill:url(#SVGID_5_)}.st6{opacity:.15;fill:url(#SVGID_6_)}.st7{fill:url(#SVGID_7_)}</style><polygon points="449.5 645.47 407.51 621.23 533.47 548.5 407.51 475.77 449.5 451.53 617.45 548.5" class="st0"/><g><linearGradient id="SVGID_1_" x1="439.007" x2="627.946" y1="603.039" y2="493.955" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#669abd"/><stop offset="1" style="stop-color:#77dbdb"/></linearGradient><polygon points="449.5 621.22 617.45 524.25 617.45 475.77 449.5 572.75" class="st1"/><linearGradient id="SVGID_2_" x1="522.979" x2="543.974" y1="530.315" y2="566.679" gradientUnits="userSpaceOnUse"><stop offset=".559" style="stop-color:#000;stop-opacity:0"/><stop offset="1" style="stop-color:#000"/></linearGradient><polygon points="449.5 621.22 617.45 524.25 617.45 475.77 449.5 572.75" class="st2"/></g><g><linearGradient id="SVGID_3_" x1="397.01" x2="459.996" y1="566.684" y2="603.049" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#6a8fad"/><stop offset="1" style="stop-color:#669abd"/></linearGradient><polygon points="407.51 548.5 407.5 596.99 449.5 621.22 449.5 572.75" class="st3"/><linearGradient id="SVGID_4_" x1="439.001" x2="418.006" y1="566.684" y2="603.049" gradientUnits="userSpaceOnUse"><stop offset=".559" style="stop-color:#000;stop-opacity:0"/><stop offset="1" style="stop-color:#000"/></linearGradient><polygon points="407.51 548.5 407.5 596.99 449.5 621.22 449.5 572.75" class="st4"/></g><g><linearGradient id="SVGID_5_" x1="397.01" x2="522.978" y1="421.226" y2="493.954" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#6a8fad"/><stop offset="1" style="stop-color:#669abd"/></linearGradient><polygon points="407.51 403.04 407.5 451.53 491.49 500.01 533.48 475.77" class="st5"/><linearGradient id="SVGID_6_" x1="470.492" x2="449.496" y1="439.407" y2="475.774" gradientUnits="userSpaceOnUse"><stop offset=".559" style="stop-color:#000;stop-opacity:0"/><stop offset="1" style="stop-color:#000"/></linearGradient><polygon points="407.51 403.04 407.5 451.53 491.49 500.01 533.48 475.77" class="st6"/></g><linearGradient id="SVGID_7_" x1="386.512" x2="575.461" y1="512.136" y2="403.047" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#ccecff"/><stop offset="1" style="stop-color:#9feced"/></linearGradient><polygon points="449.5 572.74 407.51 548.5 533.48 475.77 407.51 403.04 449.49 378.8 617.45 475.77" class="st7"/></svg>
|
||||||
<!-- Generator: Adobe Illustrator 23.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
||||||
viewBox="0 0 1024 1024" style="enable-background:new 0 0 1024 1024;" xml:space="preserve">
|
|
||||||
<style type="text/css">
|
|
||||||
.st0{opacity:0.8;fill:#00232E;}
|
|
||||||
.st1{fill:url(#SVGID_1_);}
|
|
||||||
.st2{opacity:0.16;fill:url(#SVGID_2_);}
|
|
||||||
.st3{fill:url(#SVGID_3_);}
|
|
||||||
.st4{opacity:0.16;fill:url(#SVGID_4_);}
|
|
||||||
.st5{fill:url(#SVGID_5_);}
|
|
||||||
.st6{opacity:0.15;fill:url(#SVGID_6_);}
|
|
||||||
.st7{fill:url(#SVGID_7_);}
|
|
||||||
</style>
|
|
||||||
<polygon class="st0" points="449.5,645.47 407.51,621.23 533.47,548.5 407.51,475.77 449.5,451.53 617.45,548.5 "/>
|
|
||||||
<g>
|
|
||||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="439.0065" y1="603.0394" x2="627.9464" y2="493.9549">
|
|
||||||
<stop offset="0" style="stop-color:#669ABD"/>
|
|
||||||
<stop offset="1" style="stop-color:#77DBDB"/>
|
|
||||||
</linearGradient>
|
|
||||||
<polygon class="st1" points="449.5,621.22 617.45,524.25 617.45,475.77 449.5,572.75 "/>
|
|
||||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="522.9788" y1="530.3148" x2="543.9741" y2="566.6795">
|
|
||||||
<stop offset="0.5588" style="stop-color:#000000;stop-opacity:0"/>
|
|
||||||
<stop offset="1" style="stop-color:#000000"/>
|
|
||||||
</linearGradient>
|
|
||||||
<polygon class="st2" points="449.5,621.22 617.45,524.25 617.45,475.77 449.5,572.75 "/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="397.0101" y1="566.6837" x2="459.9963" y2="603.0487">
|
|
||||||
<stop offset="0" style="stop-color:#6A8FAD"/>
|
|
||||||
<stop offset="1" style="stop-color:#669ABD"/>
|
|
||||||
</linearGradient>
|
|
||||||
<polygon class="st3" points="407.51,548.5 407.5,596.99 449.5,621.22 449.5,572.75 "/>
|
|
||||||
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="439.0009" y1="566.6838" x2="418.0056" y2="603.0486">
|
|
||||||
<stop offset="0.5588" style="stop-color:#000000;stop-opacity:0"/>
|
|
||||||
<stop offset="1" style="stop-color:#000000"/>
|
|
||||||
</linearGradient>
|
|
||||||
<polygon class="st4" points="407.51,548.5 407.5,596.99 449.5,621.22 449.5,572.75 "/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="397.0101" y1="421.2265" x2="522.9781" y2="493.9542">
|
|
||||||
<stop offset="0" style="stop-color:#6A8FAD"/>
|
|
||||||
<stop offset="1" style="stop-color:#669ABD"/>
|
|
||||||
</linearGradient>
|
|
||||||
<polygon class="st5" points="407.51,403.04 407.5,451.53 491.49,500.01 533.48,475.77 "/>
|
|
||||||
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="470.4924" y1="439.4067" x2="449.4958" y2="475.774">
|
|
||||||
<stop offset="0.5588" style="stop-color:#000000;stop-opacity:0"/>
|
|
||||||
<stop offset="1" style="stop-color:#000000"/>
|
|
||||||
</linearGradient>
|
|
||||||
<polygon class="st6" points="407.51,403.04 407.5,451.53 491.49,500.01 533.48,475.77 "/>
|
|
||||||
</g>
|
|
||||||
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="386.5123" y1="512.136" x2="575.4605" y2="403.0467">
|
|
||||||
<stop offset="0" style="stop-color:#CCECFF"/>
|
|
||||||
<stop offset="1" style="stop-color:#9FECED"/>
|
|
||||||
</linearGradient>
|
|
||||||
<polygon class="st7" points="449.5,572.74 407.51,548.5 533.48,475.77 407.51,403.04 449.49,378.8 617.45,475.77 "/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 2.7 KiB |
@@ -1,4 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
cat > '/usr/bin/${executable}' << END
|
||||||
|
#!/bin/sh
|
||||||
|
'/opt/${productFilename}/${executable}' --no-sandbox $@
|
||||||
|
END
|
||||||
|
|
||||||
# Link to the binary
|
chmod +x '/usr/bin/${executable}'
|
||||||
ln -sf '/opt/${productFilename}/${executable}' '/usr/bin/${executable}'
|
|
||||||
|
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>
|
||||||
|
BIN
docs/readme-ssh.png
Normal file
BIN
docs/readme-ssh.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 371 KiB |
BIN
docs/readme-terminal.png
Normal file
BIN
docs/readme-terminal.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 728 KiB |
95
electron-builder.yml
Normal file
95
electron-builder.yml
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
---
|
||||||
|
appId: org.terminus
|
||||||
|
productName: Terminus
|
||||||
|
compression: normal
|
||||||
|
npmRebuild: false
|
||||||
|
afterSign: "./build/mac/afterSignHook.js"
|
||||||
|
afterAllArtifactBuild: "./build/mac/afterBuildHook.js"
|
||||||
|
files:
|
||||||
|
- '**/*'
|
||||||
|
- dist
|
||||||
|
- '!lib'
|
||||||
|
- '!src'
|
||||||
|
- '!**/node_modules/*/{CHANGELOG.md,README.md,README,readme.md,readme,node.lib}'
|
||||||
|
- '!**/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
|
||||||
|
asarUnpack:
|
||||||
|
- dist/*.map
|
||||||
|
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: Utility
|
||||||
|
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
|
||||||
|
fpm:
|
||||||
|
- '--rpm-rpmbuild-define'
|
||||||
|
- '_build_id_links none'
|
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.
186
package.json
186
package.json
@@ -1,146 +1,80 @@
|
|||||||
{
|
{
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^5.10.2",
|
"@fortawesome/fontawesome-free": "^5.15.3",
|
||||||
|
"@sentry/cli": "^1.63.1",
|
||||||
|
"@sentry/electron": "^2.4.0",
|
||||||
|
"@terminus-term/to-string-loader": "1.1.7-beta.1",
|
||||||
"@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/fs-promise": "1.0.3",
|
"@types/fs-extra": "^9.0.9",
|
||||||
"@types/js-yaml": "^3.12.1",
|
"@types/js-yaml": "^4.0.0",
|
||||||
"@types/node": "12.7.2",
|
"@types/node": "14.14.35",
|
||||||
"@types/webpack-env": "1.13.9",
|
"@types/webpack-env": "^1.16.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^1.13.0",
|
"@typescript-eslint/eslint-plugin": "^4.14.1",
|
||||||
"@typescript-eslint/parser": "^1.13.0",
|
"@typescript-eslint/parser": "^4.17.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.2.0",
|
"compare-versions": "^3.6.0",
|
||||||
"cross-env": "5.2.0",
|
"core-js": "^3.9.1",
|
||||||
"css-loader": "3.2.0",
|
"cross-env": "7.0.3",
|
||||||
"electron": "^6.0.5",
|
"css-loader": "5.2.0",
|
||||||
"electron-builder": "^21.2.0",
|
"electron": "12.0.4",
|
||||||
"electron-installer-snap": "^4.0.0",
|
"electron-builder": "22.10.5",
|
||||||
"electron-notarize": "^0.1.1",
|
"electron-download": "^4.1.1",
|
||||||
"electron-rebuild": "^1.8.5",
|
"electron-installer-snap": "^5.1.0",
|
||||||
"eslint": "^5.16.0",
|
"electron-notarize": "^1.0.0",
|
||||||
"file-loader": "^4.1.0",
|
"electron-rebuild": "^2.3.5",
|
||||||
"graceful-fs": "^4.2.2",
|
"eslint": "^7.18.0",
|
||||||
"html-loader": "0.5.5",
|
"eslint-plugin-import": "^2.21.1",
|
||||||
|
"file-loader": "^6.2.0",
|
||||||
|
"graceful-fs": "^4.2.6",
|
||||||
|
"html-loader": "2.1.2",
|
||||||
"json-loader": "0.5.7",
|
"json-loader": "0.5.7",
|
||||||
"node-abi": "^2.11.0",
|
"lru-cache": "^6.0.0",
|
||||||
"node-gyp": "^5.0.3",
|
"macos-release": "^2.4.1",
|
||||||
"node-sass": "^4.12.0",
|
"node-abi": "^2.21.0",
|
||||||
|
"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",
|
"patch-package": "^6.4.7",
|
||||||
|
"pug": "^3.0.2",
|
||||||
"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",
|
||||||
"raven-js": "3.27.2",
|
"raw-loader": "4.0.2",
|
||||||
"raw-loader": "3.1.0",
|
"sass-loader": "^11.0.1",
|
||||||
"sass-loader": "^7.3.1",
|
"shelljs": "0.8.4",
|
||||||
"shelljs": "0.8.3",
|
"source-code-pro": "^2.30.2",
|
||||||
"source-code-pro": "^2.30.1",
|
"source-sans-pro": "3.6.0",
|
||||||
"source-sans-pro": "2.45.0",
|
"ssh2-streams": "^0.4.10",
|
||||||
"style-loader": "^1.0.0",
|
"style-loader": "^2.0.0",
|
||||||
"svg-inline-loader": "^0.8.0",
|
"svg-inline-loader": "^0.8.2",
|
||||||
"to-string-loader": "1.1.5",
|
"tslib": "^2.1.0",
|
||||||
"tslib": "^1.10.0",
|
"typedoc": "^0.20.32",
|
||||||
"typedoc": "^0.15.0",
|
"typescript": "^3.9.9",
|
||||||
"typescript": "^3.5.3",
|
"url-loader": "^4.1.1",
|
||||||
"url-loader": "^2.1.0",
|
"val-loader": "3.1.0",
|
||||||
"val-loader": "1.1.1",
|
"webpack": "^5.31.0",
|
||||||
"webpack": "^4.39.3",
|
"webpack-bundle-analyzer": "^4.4.0",
|
||||||
"webpack-cli": "^3.3.6",
|
"webpack-cli": "^4.6.0",
|
||||||
"yaml-loader": "0.5.0"
|
"yaml-loader": "0.6.0"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"*/node-abi": "^2.8.0"
|
"*/node-abi": "^2.20.0",
|
||||||
},
|
"**/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"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"provider": "bintray",
|
|
||||||
"token": "d993c4faa708a4cba84fa3a8e822457e7298d75c",
|
|
||||||
"component": "main"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"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}"
|
|
||||||
},
|
|
||||||
"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 --inspect",
|
||||||
|
"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 --tsconfig terminus-core/src/tsconfig.typings.json terminus-core/src/index.ts && typedoc --out docs/api/terminal --tsconfig terminus-terminal/tsconfig.typings.json terminus-terminal/src/index.ts && typedoc --out docs/api/settings --tsconfig terminus-settings/tsconfig.typings.json terminus-settings/src/index.ts",
|
||||||
"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",
|
"private": true
|
||||||
"dependencies": {
|
|
||||||
"eslint-plugin-import": "^2.18.2"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,7 @@ const isCI = !!process.env.GITHUB_REF
|
|||||||
|
|
||||||
builder({
|
builder({
|
||||||
dir: true,
|
dir: true,
|
||||||
linux: ['snap', 'deb', 'rpm', 'tar.gz'],
|
linux: ['deb', 'tar.gz', 'rpm'],
|
||||||
config: {
|
config: {
|
||||||
extraMetadata: {
|
extraMetadata: {
|
||||||
version: vars.version,
|
version: vars.version,
|
||||||
|
@@ -3,15 +3,21 @@ const builder = require('electron-builder').build
|
|||||||
const vars = require('./vars')
|
const vars = require('./vars')
|
||||||
|
|
||||||
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,
|
||||||
},
|
},
|
||||||
|
npmRebuild: process.env.ARCH !== 'arm64',
|
||||||
},
|
},
|
||||||
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('..')
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -25,8 +25,5 @@ if (['darwin', 'linux'].includes(process.platform)) {
|
|||||||
for (let x of vars.builtinPlugins) {
|
for (let x of vars.builtinPlugins) {
|
||||||
sh.ln('-fs', '../' + x, x)
|
sh.ln('-fs', '../' + x, x)
|
||||||
}
|
}
|
||||||
for (let x of vars.bundledModules) {
|
|
||||||
sh.ln('-fs', '../app/node_modules/' + x, x)
|
|
||||||
}
|
|
||||||
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.83-nightly.0",
|
"version": "1.0.135-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": "*"
|
||||||
}
|
}
|
||||||
|
42
terminus-community-color-schemes/schemes/Nord
Normal file
42
terminus-community-color-schemes/schemes/Nord
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
!
|
||||||
|
!
|
||||||
|
*.foreground: #d8dee9
|
||||||
|
*.background: #2e3440
|
||||||
|
*.cursorColor: #d8dee9
|
||||||
|
!
|
||||||
|
! Black
|
||||||
|
*.color0: #3b4252
|
||||||
|
*.color8: #373e4d
|
||||||
|
!
|
||||||
|
! Red
|
||||||
|
*.color1: #bf616a
|
||||||
|
*.color9: #94545d
|
||||||
|
!
|
||||||
|
! Green
|
||||||
|
*.color2: #a3be8c
|
||||||
|
*.color10: #809575
|
||||||
|
!
|
||||||
|
! Yellow
|
||||||
|
*.color3: #ebcb8b
|
||||||
|
*.color11: #b29e75
|
||||||
|
!
|
||||||
|
! Blue
|
||||||
|
*.color4: #81a1c1
|
||||||
|
*.color12: #68809a
|
||||||
|
!
|
||||||
|
! Magenta
|
||||||
|
*.color5: #b48ead
|
||||||
|
*.color13: #8c738c
|
||||||
|
!
|
||||||
|
! Cyan
|
||||||
|
*.color6: #88c0d0
|
||||||
|
*.color14: #6d96a5
|
||||||
|
!
|
||||||
|
! White
|
||||||
|
*.color7: #e5e9f0
|
||||||
|
*.color15: #aeb3bb
|
||||||
|
!
|
||||||
|
! Bold, Italic, Underline
|
||||||
|
*.colorBD: #a5abb6
|
||||||
|
!*.colorIT:
|
||||||
|
!*.colorUL:
|
@@ -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/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,51 +1,6 @@
|
|||||||
const path = require('path')
|
const config = require('../webpack.plugin.config')
|
||||||
|
module.exports = config({
|
||||||
module.exports = {
|
name: 'community-color-schemes',
|
||||||
target: 'node',
|
dirname: __dirname,
|
||||||
entry: 'src/index.ts',
|
})
|
||||||
context: __dirname,
|
module.exports.module.rules.push({ test: /[\\\/]schemes[\\\/]/, use: 'raw-loader' })
|
||||||
devtool: 'cheap-module-eval-source-map',
|
|
||||||
output: {
|
|
||||||
path: path.resolve(__dirname, 'dist'),
|
|
||||||
filename: 'index.js',
|
|
||||||
pathinfo: true,
|
|
||||||
libraryTarget: 'umd',
|
|
||||||
devtoolModuleFilenameTemplate: 'webpack-terminus-community-color-schemes:///[resource-path]',
|
|
||||||
},
|
|
||||||
mode: process.env.TERMINUS_DEV ? 'development' : 'production',
|
|
||||||
optimization:{
|
|
||||||
minimize: false,
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
|
|
||||||
extensions: ['.ts', '.js'],
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.ts$/,
|
|
||||||
use: {
|
|
||||||
loader: 'awesome-typescript-loader',
|
|
||||||
options: {
|
|
||||||
configFileName: path.resolve(__dirname, 'tsconfig.json'),
|
|
||||||
typeRoots: [
|
|
||||||
path.resolve(__dirname, 'node_modules/@types'),
|
|
||||||
path.resolve(__dirname, '../node_modules/@types'),
|
|
||||||
],
|
|
||||||
paths: {
|
|
||||||
"terminus-*": [path.resolve(__dirname, '../terminus-*')],
|
|
||||||
"*": [path.resolve(__dirname, '../app/node_modules/*')],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ test: /[\\\/]schemes[\\\/]/, use: "raw-loader" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
externals: [
|
|
||||||
/^rxjs/,
|
|
||||||
/^@angular/,
|
|
||||||
/^@ng-bootstrap/,
|
|
||||||
/^terminus-/,
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
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.83-nightly.4",
|
"version": "1.0.135-nightly.0",
|
||||||
"description": "Terminus core",
|
"description": "Terminus core",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"terminus-builtin-plugin"
|
"terminus-builtin-plugin"
|
||||||
@@ -17,29 +17,32 @@
|
|||||||
"author": "Eugene Pankov",
|
"author": "Eugene Pankov",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/js-yaml": "^3.9.0",
|
"@electron/remote": "^1.0.4",
|
||||||
|
"@types/js-yaml": "^4.0.0",
|
||||||
"@types/shell-escape": "^0.2.0",
|
"@types/shell-escape": "^0.2.0",
|
||||||
"@types/winston": "^2.3.6",
|
"@types/winston": "^2.3.6",
|
||||||
"axios": "^0.19.0",
|
"axios": "^0.21.1",
|
||||||
"bootstrap": "^4.1.3",
|
"bootstrap": "^4.1.3",
|
||||||
|
"clone-deep": "^4.0.1",
|
||||||
"core-js": "^3.1.2",
|
"core-js": "^3.1.2",
|
||||||
"deepmerge": "^3.2.0",
|
"deepmerge": "^4.1.1",
|
||||||
"electron-updater": "^4.0.6",
|
"electron-updater": "^4.0.6",
|
||||||
"js-yaml": "^3.9.0",
|
"js-yaml": "^4.0.0",
|
||||||
"mixpanel": "^0.10.2",
|
"mixpanel": "^0.10.2",
|
||||||
"ng2-dnd": "^5.0.2",
|
"ng2-dnd": "^5.0.2",
|
||||||
"ngx-perfect-scrollbar": "^6.0.0",
|
"ngx-perfect-scrollbar": "^10.1.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,11 +1,13 @@
|
|||||||
export { BaseTabComponent, BaseTabProcess } from '../components/baseTab.component'
|
export { BaseTabComponent, BaseTabProcess } from '../components/baseTab.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'
|
||||||
@@ -15,6 +17,9 @@ export { Logger, LogService } from '../services/log.service'
|
|||||||
export { HomeBaseService } from '../services/homeBase.service'
|
export { HomeBaseService } from '../services/homeBase.service'
|
||||||
export { HotkeysService } from '../services/hotkeys.service'
|
export { HotkeysService } from '../services/hotkeys.service'
|
||||||
export { HostAppService, Platform } from '../services/hostApp.service'
|
export { HostAppService, Platform } from '../services/hostApp.service'
|
||||||
|
export { NotificationsService } from '../services/notifications.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 { UpdaterService } from '../services/updater.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[]>
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import deepClone from 'clone-deep'
|
||||||
import { TabComponentType } from '../services/tabs.service'
|
import { TabComponentType } from '../services/tabs.service'
|
||||||
|
|
||||||
export interface RecoveredTab {
|
export interface RecoveredTab {
|
||||||
@@ -12,6 +13,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()]]
|
||||||
@@ -29,10 +36,26 @@ export interface RecoveredTab {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export abstract class TabRecoveryProvider {
|
export abstract class TabRecoveryProvider {
|
||||||
|
/**
|
||||||
|
* @param recoveryToken a recovery token found in the saved tabs list
|
||||||
|
* @returns [[boolean]] whether this [[TabRecoveryProvider]] can recover a tab from this token
|
||||||
|
*/
|
||||||
|
abstract async applicableTo (recoveryToken: RecoveryToken): Promise<boolean>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param recoveryToken a recovery token found in the saved tabs list
|
* @param recoveryToken a recovery token found in the saved tabs list
|
||||||
* @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>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param recoveryToken a recovery token found in the saved tabs list
|
||||||
|
* @returns [[RecoveryToken]] a new recovery token to create the duplicate tab from
|
||||||
|
*
|
||||||
|
* The default implementation just returns a deep copy of the original token
|
||||||
|
*/
|
||||||
|
duplicate (recoveryToken: RecoveryToken): RecoveryToken {
|
||||||
|
return deepClone(recoveryToken)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,4 +10,7 @@ export abstract class Theme {
|
|||||||
css: string
|
css: string
|
||||||
|
|
||||||
terminalBackground: string
|
terminalBackground: string
|
||||||
|
|
||||||
|
macOSWindowButtonsInsetX?: number
|
||||||
|
macOSWindowButtonsInsetY?: number
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,7 @@ export interface ToolbarButton {
|
|||||||
/**
|
/**
|
||||||
* Raw SVG icon code
|
* Raw SVG icon code
|
||||||
*/
|
*/
|
||||||
icon: string
|
icon?: string
|
||||||
|
|
||||||
title: string
|
title: string
|
||||||
|
|
||||||
|
@@ -1,13 +1,17 @@
|
|||||||
title-bar(
|
title-bar(
|
||||||
*ngIf='!hostApp.isFullScreen && config.store.appearance.frame == "full" && config.store.appearance.dock == "off"',
|
*ngIf='!hostApp.isFullScreen && config.store.appearance.frame == "full" && config.store.appearance.dock == "off"',
|
||||||
[class.inset]='hostApp.platform == Platform.macOS'
|
[class.inset]='hostApp.platform == Platform.macOS && !hostApp.isFullScreen'
|
||||||
)
|
)
|
||||||
|
|
||||||
.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 \
|
||||||
|
&& !hostApp.isFullScreen \
|
||||||
|
&& 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 +22,11 @@ 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',
|
|
||||||
@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',
|
||||||
@@ -38,7 +41,7 @@ title-bar(
|
|||||||
button.btn.btn-secondary.btn-tab-bar(
|
button.btn.btn-secondary.btn-tab-bar(
|
||||||
[title]='button.title',
|
[title]='button.title',
|
||||||
(click)='button.click && button.click()',
|
(click)='button.click && button.click()',
|
||||||
[innerHTML]='sanitizeIcon(button.icon)',
|
[fastHtmlBind]='button.icon',
|
||||||
ngbDropdownToggle,
|
ngbDropdownToggle,
|
||||||
)
|
)
|
||||||
div(*ngIf='button.submenu', ngbDropdownMenu)
|
div(*ngIf='button.submenu', ngbDropdownMenu)
|
||||||
@@ -47,8 +50,11 @@ title-bar(
|
|||||||
(click)='item.click()',
|
(click)='item.click()',
|
||||||
ngbDropdownItem,
|
ngbDropdownItem,
|
||||||
)
|
)
|
||||||
.icon-wrapper([innerHTML]='sanitizeIcon(item.icon)')
|
.icon-wrapper(
|
||||||
.ml-3 {{item.title}}
|
*ngIf='hasIcons(button.submenuItems)',
|
||||||
|
[fastHtmlBind]='item.icon'
|
||||||
|
)
|
||||||
|
div([class.ml-3]='hasIcons(button.submenuItems)') {{item.title}}
|
||||||
|
|
||||||
.drag-space.background([class.persistent]='config.store.appearance.frame == "thin" && hostApp.platform != Platform.macOS')
|
.drag-space.background([class.persistent]='config.store.appearance.frame == "thin" && hostApp.platform != Platform.macOS')
|
||||||
|
|
||||||
@@ -61,7 +67,7 @@ title-bar(
|
|||||||
button.btn.btn-secondary.btn-tab-bar(
|
button.btn.btn-secondary.btn-tab-bar(
|
||||||
[title]='button.title',
|
[title]='button.title',
|
||||||
(click)='button.click && button.click()',
|
(click)='button.click && button.click()',
|
||||||
[innerHTML]='sanitizeIcon(button.icon)',
|
[fastHtmlBind]='button.icon',
|
||||||
ngbDropdownToggle,
|
ngbDropdownToggle,
|
||||||
)
|
)
|
||||||
div(*ngIf='button.submenu', ngbDropdownMenu)
|
div(*ngIf='button.submenu', ngbDropdownMenu)
|
||||||
@@ -70,26 +76,32 @@ title-bar(
|
|||||||
(click)='item.click()',
|
(click)='item.click()',
|
||||||
ngbDropdownItem,
|
ngbDropdownItem,
|
||||||
)
|
)
|
||||||
.icon-wrapper([innerHTML]='sanitizeIcon(item.icon)')
|
.icon-wrapper(
|
||||||
.ml-3 {{item.title}}
|
*ngIf='hasIcons(button.submenuItems)',
|
||||||
|
[fastHtmlBind]='item.icon'
|
||||||
|
)
|
||||||
|
div([class.ml-3]='hasIcons(button.submenuItems)') {{item.title}}
|
||||||
|
|
||||||
button.btn.btn-secondary.btn-tab-bar.btn-update(
|
button.btn.btn-secondary.btn-tab-bar.btn-update(
|
||||||
*ngIf='updatesAvailable',
|
*ngIf='updatesAvailable',
|
||||||
title='Update available - Click to install',
|
title='Update available - Click to install',
|
||||||
(click)='updateApp()',
|
(click)='updater.update()',
|
||||||
[innerHTML]='sanitizeIcon(updateIcon)'
|
[fastHtmlBind]='updateIcon'
|
||||||
)
|
)
|
||||||
|
|
||||||
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')
|
.content
|
||||||
|
start-page.content-tab.content-tab-active(*ngIf='ready && app.tabs.length == 0')
|
||||||
|
|
||||||
tab-body(
|
tab-body.content-tab(
|
||||||
*ngFor='let tab of unsortedTabs',
|
*ngFor='let tab of unsortedTabs',
|
||||||
[active]='tab == app.activeTab',
|
[class.content-tab-active]='tab == app.activeTab',
|
||||||
[tab]='tab',
|
[active]='tab == app.activeTab',
|
||||||
)
|
[tab]='tab',
|
||||||
|
)
|
||||||
|
|
||||||
ng-template(ngbModalContainer)
|
ng-template(ngbModalContainer)
|
||||||
|
@@ -15,28 +15,77 @@
|
|||||||
|
|
||||||
$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 {
|
||||||
flex: auto;
|
width: 100vw;
|
||||||
|
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;
|
||||||
|
align-items: center;
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
border-bottom: 2px solid transparent;
|
border-bottom: 2px solid transparent;
|
||||||
@@ -49,6 +98,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;
|
||||||
}
|
}
|
||||||
@@ -63,6 +114,7 @@ $tab-border-radius: 4px;
|
|||||||
&>.drag-space {
|
&>.drag-space {
|
||||||
min-width: 1px;
|
min-width: 1px;
|
||||||
flex: 1 0 1%;
|
flex: 1 0 1%;
|
||||||
|
margin-top: 2px; // for window resizing
|
||||||
-webkit-app-region: drag;
|
-webkit-app-region: drag;
|
||||||
|
|
||||||
&.persistent {
|
&.persistent {
|
||||||
@@ -72,7 +124,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 {
|
||||||
@@ -80,9 +134,24 @@ $tab-border-radius: 4px;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs-content {
|
.content {
|
||||||
flex: auto;
|
flex: 1 1 0;
|
||||||
display: flex;
|
position: relative;
|
||||||
|
min-height: 0;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
> .content-tab {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
left: 100%;
|
||||||
|
|
||||||
|
&.content-tab-active {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hotkey-hint {
|
hotkey-hint {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
|
/* 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 { DomSanitizer } from '@angular/platform-browser'
|
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
|
||||||
import { ElectronService } from '../services/electron.service'
|
import { ElectronService } from '../services/electron.service'
|
||||||
@@ -68,15 +68,14 @@ export class AppRootComponent {
|
|||||||
|
|
||||||
private constructor (
|
private constructor (
|
||||||
private docking: DockingService,
|
private docking: DockingService,
|
||||||
private electron: ElectronService,
|
|
||||||
private hotkeys: HotkeysService,
|
private hotkeys: HotkeysService,
|
||||||
private updater: UpdaterService,
|
private updater: UpdaterService,
|
||||||
private touchbar: TouchbarService,
|
private touchbar: TouchbarService,
|
||||||
public hostApp: HostAppService,
|
public hostApp: HostAppService,
|
||||||
public config: ConfigService,
|
public config: ConfigService,
|
||||||
public app: AppService,
|
public app: AppService,
|
||||||
private domSanitizer: DomSanitizer,
|
|
||||||
@Inject(ToolbarButtonProvider) private toolbarButtonProviders: ToolbarButtonProvider[],
|
@Inject(ToolbarButtonProvider) private toolbarButtonProviders: ToolbarButtonProvider[],
|
||||||
|
electron: ElectronService,
|
||||||
log: LogService,
|
log: LogService,
|
||||||
ngbModal: NgbModal,
|
ngbModal: NgbModal,
|
||||||
_themes: ThemesService,
|
_themes: ThemesService,
|
||||||
@@ -109,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()
|
||||||
@@ -120,31 +128,30 @@ 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 () => {
|
||||||
await this.app.closeAllTabs() && this.hostApp.closeWindow()
|
this.app.closeWindow()
|
||||||
})
|
})
|
||||||
|
|
||||||
if (window['safeModeReason']) {
|
if (window['safeModeReason']) {
|
||||||
ngbModal.open(SafeModeModalComponent)
|
ngbModal.open(SafeModeModalComponent)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updater.check().then(available => {
|
setInterval(() => {
|
||||||
this.updatesAvailable = available
|
if (this.config.store.enableAutomaticUpdates) {
|
||||||
})
|
this.updater.check().then(available => {
|
||||||
|
this.updatesAvailable = available
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, 3600 * 12)
|
||||||
|
|
||||||
this.touchbar.update()
|
this.touchbar.update()
|
||||||
|
|
||||||
|
this.hostApp.useBuiltinGraphics()
|
||||||
|
|
||||||
config.changed$.subscribe(() => this.updateVibrancy())
|
config.changed$.subscribe(() => this.updateVibrancy())
|
||||||
this.updateVibrancy()
|
this.updateVibrancy()
|
||||||
|
|
||||||
let lastProgress = null
|
let lastProgress: number|null = null
|
||||||
this.app.tabOpened$.subscribe(tab => {
|
this.app.tabOpened$.subscribe(tab => {
|
||||||
this.unsortedTabs.push(tab)
|
this.unsortedTabs.push(tab)
|
||||||
tab.progress$.subscribe(progress => {
|
tab.progress$.subscribe(progress => {
|
||||||
@@ -167,40 +174,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,18 +190,8 @@ export class AppRootComponent {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateApp () {
|
hasVerticalTabs () {
|
||||||
if ((await this.electron.showMessageBox(
|
return this.config.store.appearance.tabsLocation === 'left' || this.config.store.appearance.tabsLocation === 'right'
|
||||||
this.hostApp.getWindow(),
|
|
||||||
{
|
|
||||||
type: 'warning',
|
|
||||||
message: 'Installing the update will close all tabs and restart Terminus.',
|
|
||||||
buttons: ['Cancel', 'Update'],
|
|
||||||
defaultId: 1,
|
|
||||||
}
|
|
||||||
)).response === 1) {
|
|
||||||
this.updater.update()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onTabDragStart () {
|
onTabDragStart () {
|
||||||
@@ -248,8 +211,8 @@ export class AppRootComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sanitizeIcon (icon: string): any {
|
hasIcons (submenuItems: ToolbarButton[]): boolean {
|
||||||
return this.domSanitizer.bypassSecurityTrustHtml(icon || '')
|
return submenuItems.some(x => !!x.icon)
|
||||||
}
|
}
|
||||||
|
|
||||||
private getToolbarButtons (aboveZero: boolean): ToolbarButton[] {
|
private getToolbarButtons (aboveZero: boolean): ToolbarButton[] {
|
||||||
@@ -258,8 +221,8 @@ export class AppRootComponent {
|
|||||||
buttons = buttons.concat(provider.provide())
|
buttons = buttons.concat(provider.provide())
|
||||||
})
|
})
|
||||||
return buttons
|
return buttons
|
||||||
.filter(button => button.weight > 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
|
||||||
*/
|
*/
|
||||||
@@ -36,9 +42,9 @@ export abstract class BaseTabComponent {
|
|||||||
/**
|
/**
|
||||||
* CSS color override for the tab's header
|
* CSS color override for the tab's header
|
||||||
*/
|
*/
|
||||||
color: string = 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
|
||||||
@@ -50,19 +56,19 @@ export abstract class BaseTabComponent {
|
|||||||
private titleChange = new Subject<string>()
|
private titleChange = new Subject<string>()
|
||||||
private focused = new Subject<void>()
|
private focused = new Subject<void>()
|
||||||
private blurred = new Subject<void>()
|
private blurred = new Subject<void>()
|
||||||
private progress = new Subject<number>()
|
private progress = new Subject<number|null>()
|
||||||
private activity = new Subject<boolean>()
|
private activity = new Subject<boolean>()
|
||||||
private destroyed = new Subject<void>()
|
private destroyed = new Subject<void>()
|
||||||
|
|
||||||
get focused$ (): Observable<void> { return this.focused }
|
get focused$ (): Observable<void> { return this.focused }
|
||||||
get blurred$ (): Observable<void> { return this.blurred }
|
get blurred$ (): Observable<void> { return this.blurred }
|
||||||
get titleChange$ (): Observable<string> { return this.titleChange }
|
get titleChange$ (): Observable<string> { return this.titleChange }
|
||||||
get progress$ (): Observable<number> { return this.progress }
|
get progress$ (): Observable<number|null> { return this.progress }
|
||||||
get activity$ (): Observable<boolean> { return this.activity }
|
get activity$ (): Observable<boolean> { return this.activity }
|
||||||
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) {
|
setProgress (progress: number|null): void {
|
||||||
this.progress.next(progress)
|
this.progress.next(progress)
|
||||||
if (progress) {
|
if (progress) {
|
||||||
if (this.progressClearTimeout) {
|
if (this.progressClearTimeout) {
|
||||||
@@ -118,14 +124,14 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override this to enable task completion notifications for the tab
|
* Override this to enable task completion notifications for the tab
|
||||||
*/
|
*/
|
||||||
async getCurrentProcess (): Promise<BaseTabProcess> {
|
async getCurrentProcess (): Promise<BaseTabProcess|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,4 +0,0 @@
|
|||||||
.icon(tabindex='0', [class.active]='model', (keyup.space)='click()')
|
|
||||||
i.fas.fa-square.off
|
|
||||||
i.fas.fa-check-square.on
|
|
||||||
.text {{text}}
|
|
@@ -1,55 +0,0 @@
|
|||||||
:host {
|
|
||||||
cursor: pointer;
|
|
||||||
margin: 5px 0;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
background: rgba(255,255,255,.05);
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background: rgba(255,255,255,.1);
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[disabled] {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.off {
|
|
||||||
color: rgba(0, 0, 0, .5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
position: relative;
|
|
||||||
flex: none;
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
|
|
||||||
i {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: -2px;
|
|
||||||
transition: 0.25s opacity;
|
|
||||||
display: block;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
i.on, &.active i.off {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
i.off, &.active i.on {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
flex: auto;
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,18 +1,23 @@
|
|||||||
|
/* 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'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'checkbox',
|
selector: 'checkbox',
|
||||||
template: require('./checkbox.component.pug'),
|
template: `
|
||||||
styles: [require('./checkbox.component.scss')],
|
<div class="custom-control custom-checkbox">
|
||||||
|
<input type="checkbox" class="custom-control-input" [(ngModel)]='model'>
|
||||||
|
<label class="custom-control-label">{{text}}</label>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: NG_VALUE_ACCESSOR, useExisting: CheckboxComponent, multi: true },
|
{ provide: NG_VALUE_ACCESSOR, useExisting: CheckboxComponent, multi: true },
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class CheckboxComponent implements ControlValueAccessor {
|
export class CheckboxComponent implements ControlValueAccessor {
|
||||||
@HostBinding('class.active') @Input() model: boolean
|
@HostBinding('class.active') @Input() model: boolean
|
||||||
@Input() disabled: boolean
|
@HostBinding('class.disabled') @Input() disabled: boolean
|
||||||
@Input() text: string
|
@Input() text: string
|
||||||
private changed = new Array<(val: boolean) => void>()
|
private changed = new Array<(val: boolean) => void>()
|
||||||
|
|
||||||
|
@@ -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(*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}}
|
19
terminus-core/src/components/selectorModal.component.scss
Normal file
19
terminus-core/src/components/selectorModal.component.scss
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
.modal-body {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-group {
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow: auto;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 1.25rem;
|
||||||
|
margin-right: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
79
terminus-core/src/components/selectorModal.component.ts
Normal file
79
terminus-core/src/components/selectorModal.component.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import { Component, Input, HostListener, ViewChildren, QueryList, ElementRef } from '@angular/core' // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { SelectorOption } from '../api/selector'
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
@Component({
|
||||||
|
selector: 'selector-modal',
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@@ -3,3 +3,24 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
flex: auto;
|
flex: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::ng-deep split-tab > .child {
|
||||||
|
position: absolute;
|
||||||
|
transition: 0.125s all;
|
||||||
|
opacity: .75;
|
||||||
|
|
||||||
|
&.focused {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.minimized {
|
||||||
|
opacity: .1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.maximized {
|
||||||
|
z-index: 2;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.25) 0px 0px 30px;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -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'
|
||||||
@@ -33,8 +33,8 @@ export class SplitContainer {
|
|||||||
/**
|
/**
|
||||||
* @return Flat list of all tabs inside this container
|
* @return Flat list of all tabs inside this container
|
||||||
*/
|
*/
|
||||||
getAllTabs () {
|
getAllTabs (): BaseTabComponent[] {
|
||||||
let r = []
|
let r: BaseTabComponent[] = []
|
||||||
for (const child of this.children) {
|
for (const child of this.children) {
|
||||||
if (child instanceof SplitContainer) {
|
if (child instanceof SplitContainer) {
|
||||||
r = r.concat(child.getAllTabs())
|
r = r.concat(child.getAllTabs())
|
||||||
@@ -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,8 +93,8 @@ export class SplitContainer {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
async serialize () {
|
async serialize (): Promise<RecoveryToken> {
|
||||||
const children = []
|
const children: any[] = []
|
||||||
for (const child of this.children) {
|
for (const child of this.children) {
|
||||||
if (child instanceof SplitContainer) {
|
if (child instanceof SplitContainer) {
|
||||||
children.push(await child.serialize())
|
children.push(await child.serialize())
|
||||||
@@ -140,7 +140,9 @@ 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']
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@ViewChild('vc', { read: ViewContainerRef }) viewContainer: ViewContainerRef
|
@ViewChild('vc', { read: ViewContainerRef }) viewContainer: ViewContainerRef
|
||||||
|
|
||||||
@@ -155,7 +157,12 @@ 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 hotkeysSubscription: Subscription
|
private hotkeysSubscription: Subscription
|
||||||
private viewRefs: Map<BaseTabComponent, EmbeddedViewRef<any>> = new Map()
|
private viewRefs: Map<BaseTabComponent, EmbeddedViewRef<any>> = new Map()
|
||||||
|
|
||||||
@@ -163,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 }
|
||||||
@@ -177,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,
|
||||||
@@ -198,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) {
|
||||||
@@ -226,6 +239,13 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
case 'pane-nav-down':
|
case 'pane-nav-down':
|
||||||
this.navigate('b')
|
this.navigate('b')
|
||||||
break
|
break
|
||||||
|
case 'pane-maximize':
|
||||||
|
if (this.maximizedTab) {
|
||||||
|
this.maximize(null)
|
||||||
|
} else if (this.getAllTabs().length > 1) {
|
||||||
|
this.maximize(this.focusedTab)
|
||||||
|
}
|
||||||
|
break
|
||||||
case 'close-pane':
|
case 'close-pane':
|
||||||
this.removeTab(this.focusedTab)
|
this.removeTab(this.focusedTab)
|
||||||
break
|
break
|
||||||
@@ -234,51 +254,65 @@ 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._recoveredState.duplicate)
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
focus (tab: BaseTabComponent) {
|
getMaximizedTab (): BaseTabComponent|null {
|
||||||
|
return this.maximizedTab
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
this.maximizedTab = null
|
||||||
}
|
}
|
||||||
this.layout()
|
this.layout()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
maximize (tab: BaseTabComponent|null): void {
|
||||||
|
this.maximizedTab = tab
|
||||||
|
this.layout()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
}
|
}
|
||||||
@@ -292,9 +326,11 @@ 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, side: SplitDirection) {
|
async addTab (tab: BaseTabComponent, relative: BaseTabComponent|null, side: SplitDirection): Promise<void> {
|
||||||
let target = this.getParentOf(relative) || this.root
|
tab.parent = this
|
||||||
let insertIndex = target.children.indexOf(relative)
|
|
||||||
|
let target = (relative ? this.getParentOf(relative) : null) ?? this.root
|
||||||
|
let insertIndex = relative ? target.children.indexOf(relative) : -1
|
||||||
|
|
||||||
if (
|
if (
|
||||||
target.orientation === 'v' && ['l', 'r'].includes(side) ||
|
target.orientation === 'v' && ['l', 'r'].includes(side) ||
|
||||||
@@ -302,7 +338,7 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
) {
|
) {
|
||||||
const newContainer = new SplitContainer()
|
const newContainer = new SplitContainer()
|
||||||
newContainer.orientation = target.orientation === 'v' ? 'h' : 'v'
|
newContainer.orientation = target.orientation === 'v' ? 'h' : 'v'
|
||||||
newContainer.children = [relative]
|
newContainer.children = relative ? [relative] : []
|
||||||
newContainer.ratios = [1]
|
newContainer.ratios = [1]
|
||||||
target.children[insertIndex] = newContainer
|
target.children[insertIndex] = newContainer
|
||||||
target = newContainer
|
target = newContainer
|
||||||
@@ -322,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(() => {
|
||||||
@@ -331,18 +370,21 @@ 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) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const index = parent.children.indexOf(tab)
|
const index = parent.children.indexOf(tab)
|
||||||
parent.ratios.splice(index, 1)
|
parent.ratios.splice(index, 1)
|
||||||
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 {
|
||||||
@@ -353,14 +395,25 @@ 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) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const orientation = ['l', 'r'].includes(dir) ? 'h' : 'v'
|
const orientation = ['l', 'r'].includes(dir) ? 'h' : 'v'
|
||||||
|
|
||||||
while (parent !== this.root && parent.orientation !== orientation) {
|
while (parent !== this.root && parent.orientation !== orientation) {
|
||||||
rel = parent
|
rel = parent
|
||||||
parent = this.getParentOf(rel)
|
parent = this.getParentOf(rel)
|
||||||
|
if (!parent) {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parent.orientation !== orientation) {
|
if (parent.orientation !== orientation) {
|
||||||
@@ -379,16 +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)
|
||||||
this.addTab(newTab, tab, dir)
|
if (newTab) {
|
||||||
|
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 {
|
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)
|
||||||
@@ -414,16 +470,29 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
async getCurrentProcess (): Promise<BaseTabProcess> {
|
async getCurrentProcess (): Promise<BaseTabProcess|null> {
|
||||||
return (await Promise.all(this.getAllTabs().map(x => x.getCurrentProcess()))).find(x => !!x)
|
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 (): void {
|
||||||
|
super.destroy()
|
||||||
|
for (const x of this.getAllTabs()) {
|
||||||
|
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)
|
||||||
@@ -436,6 +505,9 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
if (tab.title) {
|
if (tab.title) {
|
||||||
this.setTitle(tab.title)
|
this.setTitle(tab.title)
|
||||||
}
|
}
|
||||||
|
tab.recoveryStateChangedHint$.subscribe(() => {
|
||||||
|
this.recoveryStateChangedHint.next()
|
||||||
|
})
|
||||||
tab.destroyed$.subscribe(() => {
|
tab.destroyed$.subscribe(() => {
|
||||||
this.removeTab(tab)
|
this.removeTab(tab)
|
||||||
})
|
})
|
||||||
@@ -443,19 +515,15 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
|
|
||||||
private detachTabView (tab: BaseTabComponent) {
|
private detachTabView (tab: BaseTabComponent) {
|
||||||
const ref = this.viewRefs.get(tab)
|
const ref = this.viewRefs.get(tab)
|
||||||
this.viewRefs.delete(tab)
|
if (ref) {
|
||||||
this.viewContainer.remove(this.viewContainer.indexOf(ref))
|
this.viewRefs.delete(tab)
|
||||||
}
|
this.viewContainer.remove(this.viewContainer.indexOf(ref))
|
||||||
|
}
|
||||||
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
|
||||||
@@ -471,14 +539,25 @@ 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.style.position = 'absolute'
|
if (viewRef) {
|
||||||
element.style.left = `${childX}%`
|
const element = viewRef.rootNodes[0]
|
||||||
element.style.top = `${childY}%`
|
element.classList.toggle('child', true)
|
||||||
element.style.width = `${childW}%`
|
element.classList.toggle('maximized', child === this.maximizedTab)
|
||||||
element.style.height = `${childH}%`
|
element.classList.toggle('minimized', this.maximizedTab && child !== this.maximizedTab)
|
||||||
|
element.classList.toggle('focused', this._allFocusMode || child === this.focusedTab)
|
||||||
|
element.style.left = `${childX}%`
|
||||||
|
element.style.top = `${childY}%`
|
||||||
|
element.style.width = `${childW}%`
|
||||||
|
element.style.height = `${childH}%`
|
||||||
|
|
||||||
element.style.opacity = child === this.focusedTab ? 1 : 0.75
|
if (child === this.maximizedTab) {
|
||||||
|
element.style.left = '5%'
|
||||||
|
element.style.top = '5%'
|
||||||
|
element.style.width = '90%'
|
||||||
|
element.style.height = '90%'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
offset += sizes[i]
|
offset += sizes[i]
|
||||||
|
|
||||||
@@ -491,7 +570,7 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private async recoverContainer (root: SplitContainer, state: any) {
|
private async recoverContainer (root: SplitContainer, state: any, duplicate = false) {
|
||||||
const children: (SplitContainer | BaseTabComponent)[] = []
|
const children: (SplitContainer | BaseTabComponent)[] = []
|
||||||
root.orientation = state.orientation
|
root.orientation = state.orientation
|
||||||
root.ratios = state.ratios
|
root.ratios = state.ratios
|
||||||
@@ -499,32 +578,45 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
for (const childState of state.children) {
|
for (const childState of state.children) {
|
||||||
if (childState.type === 'app:split-tab') {
|
if (childState.type === 'app:split-tab') {
|
||||||
const child = new SplitContainer()
|
const child = new SplitContainer()
|
||||||
await this.recoverContainer(child, childState)
|
await this.recoverContainer(child, childState, duplicate)
|
||||||
children.push(child)
|
children.push(child)
|
||||||
} else {
|
} else {
|
||||||
const recovered = await this.tabRecovery.recoverTab(childState)
|
const recovered = await this.tabRecovery.recoverTab(childState, duplicate)
|
||||||
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> {
|
async applicableTo (recoveryToken: RecoveryToken): Promise<boolean> {
|
||||||
if (recoveryToken && recoveryToken.type === 'app:split-tab') {
|
return recoveryToken.type === 'app:split-tab'
|
||||||
return {
|
}
|
||||||
type: SplitTabComponent,
|
|
||||||
options: { _recoveredState: recoveryToken },
|
async recover (recoveryToken: RecoveryToken): Promise<RecoveredTab> {
|
||||||
}
|
return {
|
||||||
|
type: SplitTabComponent,
|
||||||
|
options: { _recoveredState: recoveryToken },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
duplicate (recoveryToken: RecoveryToken): RecoveryToken {
|
||||||
|
return {
|
||||||
|
...recoveryToken,
|
||||||
|
duplicate: true,
|
||||||
}
|
}
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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'
|
||||||
|
|
||||||
@@ -16,21 +17,25 @@ export class SplitTabSpannerComponent {
|
|||||||
@HostBinding('class.v') isVertical = true
|
@HostBinding('class.v') isVertical = true
|
||||||
@HostBinding('style.left') cssLeft: string
|
@HostBinding('style.left') cssLeft: string
|
||||||
@HostBinding('style.top') cssTop: string
|
@HostBinding('style.top') cssTop: string
|
||||||
@HostBinding('style.width') cssWidth: string
|
@HostBinding('style.width') cssWidth: string | null
|
||||||
@HostBinding('style.height') cssHeight: string
|
@HostBinding('style.height') cssHeight: string | null
|
||||||
private marginOffset = -5
|
private marginOffset = -5
|
||||||
|
|
||||||
constructor (private element: ElementRef) { }
|
constructor (private element: ElementRef) { }
|
||||||
|
|
||||||
ngAfterViewInit () {
|
ngAfterViewInit () {
|
||||||
|
this.element.nativeElement.addEventListener('dblclick', () => {
|
||||||
|
this.reset()
|
||||||
|
})
|
||||||
|
|
||||||
this.element.nativeElement.addEventListener('mousedown', (e: MouseEvent) => {
|
this.element.nativeElement.addEventListener('mousedown', (e: MouseEvent) => {
|
||||||
this.isActive = true
|
this.isActive = true
|
||||||
const start = this.isVertical ? e.pageY : e.pageX
|
const start = this.isVertical ? e.pageY : e.pageX
|
||||||
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`
|
||||||
@@ -49,14 +54,16 @@ export class SplitTabSpannerComponent {
|
|||||||
diff = Math.max(diff, -this.container.ratios[this.index - 1] + 0.1)
|
diff = Math.max(diff, -this.container.ratios[this.index - 1] + 0.1)
|
||||||
diff = Math.min(diff, this.container.ratios[this.index] - 0.1)
|
diff = Math.min(diff, this.container.ratios[this.index] - 0.1)
|
||||||
|
|
||||||
this.container.ratios[this.index - 1] += diff
|
if (diff) {
|
||||||
this.container.ratios[this.index] -= diff
|
this.container.ratios[this.index - 1] += diff
|
||||||
this.change.emit()
|
this.container.ratios[this.index] -= diff
|
||||||
|
this.change.emit()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('mouseup', offHandler)
|
document.addEventListener('mouseup', offHandler, { passive: true })
|
||||||
this.element.nativeElement.parentElement.addEventListener('mousemove', dragHandler)
|
this.element.nativeElement.parentElement.addEventListener('mousemove', dragHandler)
|
||||||
})
|
}, { passive: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges () {
|
ngOnChanges () {
|
||||||
@@ -67,18 +74,25 @@ export class SplitTabSpannerComponent {
|
|||||||
this.container.x,
|
this.container.x,
|
||||||
this.container.y + this.container.h * this.container.getOffsetRatio(this.index),
|
this.container.y + this.container.h * this.container.getOffsetRatio(this.index),
|
||||||
this.container.w,
|
this.container.w,
|
||||||
null
|
0
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
this.setDimensions(
|
this.setDimensions(
|
||||||
this.container.x + this.container.w * this.container.getOffsetRatio(this.index),
|
this.container.x + this.container.w * this.container.getOffsetRatio(this.index),
|
||||||
this.container.y,
|
this.container.y,
|
||||||
null,
|
0,
|
||||||
this.container.h
|
this.container.h
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reset () {
|
||||||
|
const ratio = (this.container.ratios[this.index - 1] + this.container.ratios[this.index]) / 2
|
||||||
|
this.container.ratios[this.index - 1] = ratio
|
||||||
|
this.container.ratios[this.index] = ratio
|
||||||
|
this.change.emit()
|
||||||
|
}
|
||||||
|
|
||||||
private setDimensions (x: number, y: number, w: number, h: number) {
|
private setDimensions (x: number, y: number, w: number, h: number) {
|
||||||
this.cssLeft = `${x}%`
|
this.cssLeft = `${x}%`
|
||||||
this.cssTop = `${y}%`
|
this.cssTop = `${y}%`
|
||||||
|
@@ -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,15 +1,11 @@
|
|||||||
:host {
|
:host {
|
||||||
display: none;
|
display: flex;
|
||||||
flex: auto;
|
flex: auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
&.active {
|
>* {
|
||||||
display: flex;
|
flex: auto;
|
||||||
|
|
||||||
>* {
|
|
||||||
flex: auto;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> perfect-scrollbar {
|
> perfect-scrollbar {
|
||||||
|
@@ -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,10 @@
|
|||||||
|
.colorbar([style.background-color]='tab.color', *ngIf='tab.color != null')
|
||||||
.progressbar([style.width]='progress + "%"', *ngIf='progress != null')
|
.progressbar([style.width]='progress + "%"', *ngIf='progress != null')
|
||||||
.index(
|
.activity-indicator(*ngIf='tab.activity$|async')
|
||||||
#handle,
|
|
||||||
[style.background-color]='tab.color',
|
.index(*ngIf='!config.store.terminal.hideTabIndex', #handle) {{index + 1}}
|
||||||
) {{index + 1}}
|
.name(
|
||||||
.name([title]='tab.customTitle || tab.title') {{tab.customTitle || tab.title}}
|
[title]='tab.customTitle || tab.title',
|
||||||
button((click)='app.closeTab(tab, true)') ×
|
[class.no-hover]='config.store.terminal.hideCloseButton'
|
||||||
|
) {{tab.customTitle || tab.title}}
|
||||||
|
button(*ngIf='!config.store.terminal.hideCloseButton',(click)='app.closeTab(tab, true)') ×
|
||||||
|
@@ -4,8 +4,16 @@ $tabs-height: 38px;
|
|||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
> * { cursor: pointer; }
|
||||||
|
|
||||||
flex: 1000 1 200px;
|
flex: 1000 1 200px;
|
||||||
width: 200px;
|
width: 200px;
|
||||||
|
padding: 0 10px;
|
||||||
|
|
||||||
|
&.flex-width {
|
||||||
|
flex: 1000 1 auto;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@@ -13,14 +21,18 @@ $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;
|
||||||
-webkit-app-region: no-drag;
|
-webkit-app-region: no-drag;
|
||||||
cursor: -webkit-grab;
|
cursor: -webkit-grab;
|
||||||
|
|
||||||
margin-left: 10px;
|
width: 22px;
|
||||||
width: 20px;
|
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
transition: 0.25s all;
|
transition: 0.25s all;
|
||||||
@@ -29,7 +41,7 @@ $tabs-height: 38px;
|
|||||||
|
|
||||||
.name {
|
.name {
|
||||||
flex: auto;
|
flex: auto;
|
||||||
margin: 0 1px 0 10px;
|
margin-top: 1px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@@ -37,6 +49,10 @@ $tabs-height: 38px;
|
|||||||
align-self: center;
|
align-self: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.index + .name {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
display: block;
|
display: block;
|
||||||
flex: none;
|
flex: none;
|
||||||
@@ -44,13 +60,15 @@ $tabs-height: 38px;
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
-webkit-app-region: no-drag;
|
-webkit-app-region: no-drag;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
|
||||||
$button-size: 26px;
|
$button-size: 26px;
|
||||||
width: $button-size;
|
width: $button-size;
|
||||||
height: $button-size;
|
height: $button-size;
|
||||||
border-radius: $button-size / 2;
|
border-radius: $button-size / 2;
|
||||||
line-height: $button-size * 0.9;
|
line-height: $button-size;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
margin-right: 10px;
|
|
||||||
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
@@ -60,6 +78,13 @@ $tabs-height: 38px;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:hover .name:not(.no-hover) {
|
||||||
|
-webkit-mask-image: linear-gradient(black 0 0), linear-gradient(to left, transparent 0%, black 100%);
|
||||||
|
-webkit-mask-size: calc(100% - 60px) auto, 60px auto;
|
||||||
|
-webkit-mask-repeat: no-repeat;
|
||||||
|
-webkit-mask-position: left, right;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover button {
|
&:hover button {
|
||||||
transition: 0.25s opacity;
|
transition: 0.25s opacity;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -78,8 +103,30 @@ $tabs-height: 38px;
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
height: 5px;
|
height: 3px;
|
||||||
z-index: -1;
|
z-index: 1;
|
||||||
transition: 0.25s width;
|
transition: 0.25s width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.colorbar {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
height: 3px;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active .activity-indicator {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-indicator {
|
||||||
|
position: absolute;
|
||||||
|
left: 10px;
|
||||||
|
right: 10px;
|
||||||
|
bottom: 4px;
|
||||||
|
height: 2px;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
import { Component, Input, Optional, Inject, HostBinding, HostListener, ViewChild, ElementRef } from '@angular/core'
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
import type { MenuItemConstructorOptions } from 'electron'
|
||||||
|
import { Component, Input, Optional, Inject, HostBinding, HostListener, ViewChild, ElementRef, NgZone } 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'
|
||||||
import { TabContextMenuItemProvider } from '../api/tabContextMenuProvider'
|
import { TabContextMenuItemProvider } from '../api/tabContextMenuProvider'
|
||||||
@@ -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 */
|
||||||
@@ -23,17 +26,18 @@ export interface SortableComponentProxy {
|
|||||||
export class TabHeaderComponent {
|
export class TabHeaderComponent {
|
||||||
@Input() index: number
|
@Input() index: number
|
||||||
@Input() @HostBinding('class.active') active: boolean
|
@Input() @HostBinding('class.active') active: boolean
|
||||||
@Input() @HostBinding('class.has-activity') hasActivity: boolean
|
|
||||||
@Input() tab: BaseTabComponent
|
@Input() tab: BaseTabComponent
|
||||||
@Input() progress: number
|
@Input() progress: number|null
|
||||||
@ViewChild('handle') handle: ElementRef
|
@ViewChild('handle') handle?: ElementRef
|
||||||
|
|
||||||
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,
|
||||||
private hotkeys: HotkeysService,
|
private hotkeys: HotkeysService,
|
||||||
|
private zone: NgZone,
|
||||||
@Inject(SortableComponent) private parentDraggable: SortableComponentProxy,
|
@Inject(SortableComponent) private parentDraggable: SortableComponentProxy,
|
||||||
@Optional() @Inject(TabContextMenuItemProvider) protected contextMenuProviders: TabContextMenuItemProvider[],
|
@Optional() @Inject(TabContextMenuItemProvider) protected contextMenuProviders: TabContextMenuItemProvider[],
|
||||||
) {
|
) {
|
||||||
@@ -48,12 +52,17 @@ export class TabHeaderComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
if (this.hostApp.platform === Platform.macOS) {
|
this.tab.progress$.subscribe(progress => {
|
||||||
|
this.zone.run(() => {
|
||||||
|
this.progress = progress
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit () {
|
||||||
|
if (this.handle && this.hostApp.platform === Platform.macOS) {
|
||||||
this.parentDraggable.setDragHandle(this.handle.nativeElement)
|
this.parentDraggable.setDragHandle(this.handle.nativeElement)
|
||||||
}
|
}
|
||||||
this.tab.progress$.subscribe(progress => {
|
|
||||||
this.progress = progress
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
showRenameTabModal (): void {
|
showRenameTabModal (): void {
|
||||||
@@ -65,8 +74,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)
|
||||||
@@ -74,18 +83,31 @@ export class TabHeaderComponent {
|
|||||||
return items.slice(1)
|
return items.slice(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@HostBinding('class.flex-width') get isFlexWidthEnabled (): boolean {
|
||||||
|
return this.config.store.appearance.flexTabs
|
||||||
|
}
|
||||||
|
|
||||||
@HostListener('dblclick') onDoubleClick (): void {
|
@HostListener('dblclick') onDoubleClick (): void {
|
||||||
this.showRenameTabModal()
|
this.showRenameTabModal()
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('auxclick', ['$event']) async onAuxClick ($event: MouseEvent) {
|
@HostListener('mousedown', ['$event']) async onMouseDown ($event: MouseEvent) {
|
||||||
|
if ($event.which === 2) {
|
||||||
|
$event.preventDefault()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('mouseup', ['$event']) async onMouseUp ($event: MouseEvent) {
|
||||||
if ($event.which === 2) {
|
if ($event.which === 2) {
|
||||||
this.app.closeTab(this.tab, true)
|
this.app.closeTab(this.tab, true)
|
||||||
}
|
}
|
||||||
if ($event.which === 3) {
|
}
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
const contextMenu = this.electron.remote.Menu.buildFromTemplate(await this.buildContextMenu())
|
@HostListener('auxclick', ['$event']) async onAuxClick ($event: MouseEvent) {
|
||||||
|
if ($event.which === 3) {
|
||||||
|
$event.preventDefault()
|
||||||
|
|
||||||
|
const contextMenu = this.electron.Menu.buildFromTemplate(await this.buildContextMenu())
|
||||||
|
|
||||||
contextMenu.popup({
|
contextMenu.popup({
|
||||||
x: $event.pageX,
|
x: $event.pageX,
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
$toggle-size: 18px;
|
$toggle-size: 18px;
|
||||||
$height: 30px;
|
$height: 30px;
|
||||||
$padding: 2px;
|
$padding: 2px;
|
||||||
cursor: pointer;
|
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
@@ -16,55 +15,11 @@
|
|||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
margin-left: -10px;
|
margin-left: -10px;
|
||||||
|
|
||||||
&:focus {
|
&.disabled {
|
||||||
background: rgba(255,255,255,.05);
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[disabled] {
|
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.body {
|
* {
|
||||||
$border-width: 2px;
|
cursor: pointer;
|
||||||
border-radius: 5px;
|
|
||||||
border: $border-width solid rgba(255, 255, 255, .2);
|
|
||||||
padding: $padding;
|
|
||||||
height: $toggle-size + $border-width * 2 + $padding * 2;
|
|
||||||
width: $toggle-size * 2 + $border-width * 2 + $padding * 2;
|
|
||||||
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.toggle {
|
|
||||||
position: absolute;
|
|
||||||
border-radius: 2px;
|
|
||||||
width: $toggle-size;
|
|
||||||
height: $toggle-size;
|
|
||||||
background: #475158;
|
|
||||||
top: $padding;
|
|
||||||
left: $padding;
|
|
||||||
transition: 0.25s left;
|
|
||||||
line-height: 19px;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 10px;
|
|
||||||
|
|
||||||
i {
|
|
||||||
opacity: 0;
|
|
||||||
transition: 0.25s opacity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active .body .toggle {
|
|
||||||
left: $toggle-size + $padding;
|
|
||||||
|
|
||||||
i {
|
|
||||||
color: white;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background: rgba(255,255,255,.1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,13 +6,10 @@ import { CheckboxComponent } from './checkbox.component'
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'toggle',
|
selector: 'toggle',
|
||||||
template: `
|
template: `
|
||||||
<div class="switch">
|
<div class="custom-control custom-switch">
|
||||||
<div class="body">
|
<input type="checkbox" class="custom-control-input" [(ngModel)]='model'>
|
||||||
<div class="toggle" [class.bg-primary]='model'>
|
<label class="custom-control-label"></label>
|
||||||
<i class="fa fa-check"></i>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`,
|
`,
|
||||||
styles: [require('./toggle.component.scss')],
|
styles: [require('./toggle.component.scss')],
|
||||||
providers: [
|
providers: [
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user