Compare commits
1891 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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 | ||
![]() |
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 | ||
![]() |
6968dcdd13 | ||
![]() |
bcaa638ef0 | ||
![]() |
c243de3704 | ||
![]() |
4e35d2a367 | ||
![]() |
c2e42682d6 | ||
![]() |
5e566c2fd9 | ||
![]() |
effaa81582 | ||
![]() |
4cd9290972 | ||
![]() |
b9a570be9b | ||
![]() |
3ddbb62658 | ||
![]() |
2ee336bef8 | ||
![]() |
8d767ac497 | ||
![]() |
955ef9b361 | ||
![]() |
90cf46b826 | ||
![]() |
c08ba92f33 | ||
![]() |
4bf2963d1e | ||
![]() |
e81228782d | ||
![]() |
dd6dd01e16 | ||
![]() |
31ad686545 | ||
![]() |
b79b3302ab | ||
![]() |
68220d1afc | ||
![]() |
a8e1cee84a | ||
![]() |
bd713f8e07 | ||
![]() |
3bc387b1d9 | ||
![]() |
2844f699ff | ||
![]() |
1d3333e83b | ||
![]() |
4f0daf7a77 | ||
![]() |
3421a8e19f | ||
![]() |
927ca57e56 | ||
![]() |
c63c7dae3f | ||
![]() |
e0f05654f7 | ||
![]() |
2a1501c91e | ||
![]() |
f244f09362 | ||
![]() |
3c2af3045d | ||
![]() |
d82b0b4661 | ||
![]() |
5e2ec1a9bf | ||
![]() |
1d8a71fbc0 | ||
![]() |
7b9a6f1055 | ||
![]() |
8bd9a0d4bc | ||
![]() |
69c1c9f664 | ||
![]() |
d30836681d | ||
![]() |
0a597ffd5f | ||
![]() |
757c91815b | ||
![]() |
deb01266ea | ||
![]() |
d3cff11602 | ||
![]() |
f177a5c734 | ||
![]() |
bf4e0e4f3d | ||
![]() |
9d509dd3ce | ||
![]() |
02674d0f86 | ||
![]() |
ff6ec006e7 | ||
![]() |
a4ed9a5444 | ||
![]() |
d01e3a97d5 | ||
![]() |
3f84a9239a | ||
![]() |
84d4ca4053 | ||
![]() |
19c4a77d6f | ||
![]() |
c16c250847 | ||
![]() |
0905be77bc | ||
![]() |
3d11179a06 | ||
![]() |
516d7eae9a | ||
![]() |
2e486c5e53 | ||
![]() |
d87f329838 | ||
![]() |
e6de63c689 | ||
![]() |
bb845ee803 | ||
![]() |
9e67996e24 | ||
![]() |
b5523d9820 | ||
![]() |
ad26e4f6a3 | ||
![]() |
ae40f009a0 | ||
![]() |
7ee603df60 | ||
![]() |
a8eb0d8346 | ||
![]() |
924a8da2f5 | ||
![]() |
07ab28279c | ||
![]() |
dbb6c544de | ||
![]() |
8c3d2531dc | ||
![]() |
e8fc47665b | ||
![]() |
574d8d9c7a | ||
![]() |
16c9e20b2a | ||
![]() |
665e0ff692 | ||
![]() |
8c71b26109 | ||
![]() |
6aeb9a62e6 | ||
![]() |
337d1f4df5 | ||
![]() |
057bd3f6d1 | ||
![]() |
b01a2e42fb | ||
![]() |
62efe406f5 | ||
![]() |
43f6ad3530 | ||
![]() |
a060246269 | ||
![]() |
802752d76c | ||
![]() |
a7fc2f4ddc | ||
![]() |
6a2fcd9f16 | ||
![]() |
90f475e532 | ||
![]() |
af6294e404 | ||
![]() |
17f471ac24 | ||
![]() |
4274ed563b | ||
![]() |
2acda3be5f | ||
![]() |
2848f9f257 | ||
![]() |
6536e3d4b5 | ||
![]() |
cdaa4fe106 | ||
![]() |
a07e27ef4b | ||
![]() |
2d5e4477b8 | ||
![]() |
f1213fb83e | ||
![]() |
947c751bc7 | ||
![]() |
0ca00b81d5 | ||
![]() |
7c10b57fb9 | ||
![]() |
ffa6d41d8b | ||
![]() |
5ecab9f304 | ||
![]() |
dc9508f80d | ||
![]() |
b6aa1f764b | ||
![]() |
63f8ac2d92 | ||
![]() |
da4622a2ed | ||
![]() |
2896321076 | ||
![]() |
092e5fb8aa | ||
![]() |
43bb3f7f2d | ||
![]() |
b144724ed5 | ||
![]() |
04f233b4a5 | ||
![]() |
7645a1d2c7 | ||
![]() |
2953ea60e8 | ||
![]() |
8d5b2bc4c5 | ||
![]() |
1d1e620db6 | ||
![]() |
3532195760 | ||
![]() |
9d55c7c0ed | ||
![]() |
b9fcf6cac3 | ||
![]() |
e49deee303 | ||
![]() |
f4da5955de | ||
![]() |
5db0ccf96a | ||
![]() |
f8f9c81a66 | ||
![]() |
75b51983cc | ||
![]() |
7e66af2585 | ||
![]() |
ac8026b6d9 | ||
![]() |
119cfdc577 | ||
![]() |
09ce7e062f | ||
![]() |
9cd4fb5417 | ||
![]() |
7887cf58d2 | ||
![]() |
af9c9e580c | ||
![]() |
a4c98db69e | ||
![]() |
122e68de04 | ||
![]() |
d922b2de1f | ||
![]() |
84e2cdb191 | ||
![]() |
b424876cf8 | ||
![]() |
3e306c1479 | ||
![]() |
fb4258d0a2 | ||
![]() |
c9965d3d1a | ||
![]() |
5411ef4802 | ||
![]() |
fbc67970b4 | ||
![]() |
08910933e0 | ||
![]() |
ee23ca0770 | ||
![]() |
febdb96c2b | ||
![]() |
d771bd196c | ||
![]() |
4782a67fe0 | ||
![]() |
6cd5d819ea | ||
![]() |
f8cc637e19 | ||
![]() |
23907ffffe | ||
![]() |
9af1f40086 | ||
![]() |
2a1e1007b7 | ||
![]() |
4c2168b4b5 | ||
![]() |
8920cc7924 | ||
![]() |
8ee7022307 | ||
![]() |
380266a57c | ||
![]() |
5679d5edf1 | ||
![]() |
6a9d569345 | ||
![]() |
eccbd66c18 | ||
![]() |
041a2a92d5 | ||
![]() |
35984ab66e | ||
![]() |
a4ad435341 | ||
![]() |
1b3f28415e | ||
![]() |
b942a2eb1d | ||
![]() |
60e6e2d3ca | ||
![]() |
77d209e285 | ||
![]() |
a735c910d5 | ||
![]() |
876aee93df | ||
![]() |
b0f8dd4036 | ||
![]() |
9413c80376 | ||
![]() |
735fa76239 | ||
![]() |
5820b7ff9a | ||
![]() |
ce49dd9dae | ||
![]() |
0804b949fc | ||
![]() |
dc6871f4a3 | ||
![]() |
2197dceeb2 | ||
![]() |
e4f29b5862 | ||
![]() |
aba3e190ed | ||
![]() |
0ef8c7a570 | ||
![]() |
04b3ab45ea | ||
![]() |
8f2f66c269 | ||
![]() |
1ebbe07870 | ||
![]() |
dadc6c1665 | ||
![]() |
3f3bcfe805 | ||
![]() |
e9ac1f81f7 | ||
![]() |
cd3bab2f6d | ||
![]() |
dc190ad733 | ||
![]() |
1e81e65df7 | ||
![]() |
409c33782e | ||
![]() |
65f2b98f08 | ||
![]() |
891ef52a44 | ||
![]() |
4a18038f15 | ||
![]() |
3891818863 | ||
![]() |
b03e6d449e | ||
![]() |
a70c084a03 | ||
![]() |
fa752b5987 | ||
![]() |
05f8adc323 | ||
![]() |
90df7ac5b7 | ||
![]() |
ea7825e7a8 | ||
![]() |
93f262cb0f | ||
![]() |
f7d60efeb9 | ||
![]() |
be7e6b8d6e | ||
![]() |
4972d2ffea | ||
![]() |
08cebe202c | ||
![]() |
26ca65e10f | ||
![]() |
bb00b07ee8 | ||
![]() |
7bff0a1174 | ||
![]() |
4b67e02f6b | ||
![]() |
046f56215e | ||
![]() |
09b6428705 | ||
![]() |
6c5eb9c525 | ||
![]() |
4f5ee31a69 | ||
![]() |
8eeb8f3f10 | ||
![]() |
31b69f676c | ||
![]() |
9fc3b41247 | ||
![]() |
af946a4a51 | ||
![]() |
85a69ed922 | ||
![]() |
80a4b7bdfc | ||
![]() |
bcbc6e3b46 | ||
![]() |
4828156c7a | ||
![]() |
93119dbfca | ||
![]() |
393c113d77 | ||
![]() |
a6e5c317ae | ||
![]() |
5d22cab164 | ||
![]() |
745923d0b9 | ||
![]() |
e6e3c59e63 | ||
![]() |
55bc4f47bb | ||
![]() |
6484a1baff | ||
![]() |
493186ce53 | ||
![]() |
9a72184f94 | ||
![]() |
6e845eff7d | ||
![]() |
5596dce8ef | ||
![]() |
48b2547759 | ||
![]() |
f616cb9b8f | ||
![]() |
4441bc7e05 | ||
![]() |
7b249a82d7 | ||
![]() |
d8f4c29073 | ||
![]() |
f5485fae2f | ||
![]() |
0bd1700872 | ||
![]() |
e7ac91a322 | ||
![]() |
dc42a2a992 | ||
![]() |
846f1cca16 | ||
![]() |
c11f43b637 | ||
![]() |
6a02bb7f51 | ||
![]() |
2238a231f0 | ||
![]() |
c5c4168afe | ||
![]() |
f49f37992d | ||
![]() |
6fe08475ff | ||
![]() |
99618d75ec | ||
![]() |
f8f2d007a2 | ||
![]() |
4db27f4623 | ||
![]() |
5cd4209ec5 | ||
![]() |
1bd5204d24 | ||
![]() |
c6bd1358e3 | ||
![]() |
d6c9ce84ea | ||
![]() |
1fdf5367b6 | ||
![]() |
e5234b0945 | ||
![]() |
a146505c5a | ||
![]() |
20a1ae3374 | ||
![]() |
8a19a93b4d | ||
![]() |
f9c51e04e1 | ||
![]() |
1afc212e71 | ||
![]() |
d44224d23e | ||
![]() |
e1cca33379 | ||
![]() |
e48936484d | ||
![]() |
911b72508b | ||
![]() |
8575555204 | ||
![]() |
4775632c51 | ||
![]() |
b198864063 | ||
![]() |
0896844e96 | ||
![]() |
3c27e8105e | ||
![]() |
269971ab60 | ||
![]() |
b98bde71df | ||
![]() |
0238515b42 | ||
![]() |
be2f2f4575 | ||
![]() |
38494c9704 | ||
![]() |
fa6fa3f1ea | ||
![]() |
8632f1eedf | ||
![]() |
944c7b072b | ||
![]() |
a08df2fd12 | ||
![]() |
c0804b6940 | ||
![]() |
ebdc2e4105 | ||
![]() |
a0a4346cc4 | ||
![]() |
336cac78c7 | ||
![]() |
fc9389ab30 | ||
![]() |
eb52a37f77 | ||
![]() |
cc68ad492a | ||
![]() |
a1c3829572 | ||
![]() |
32890f17c0 | ||
![]() |
3f8ee9b4fc | ||
![]() |
3eb15a6e66 | ||
![]() |
b62f54ef05 | ||
![]() |
95abe8bb4d | ||
![]() |
8f3ab68705 | ||
![]() |
dce6df7783 | ||
![]() |
7b3426d2f9 | ||
![]() |
0cfbdf368f | ||
![]() |
e7bc93a5e2 | ||
![]() |
d9559f2a8f | ||
![]() |
6531c7eaa7 | ||
![]() |
986bdf8e24 | ||
![]() |
2188eef202 | ||
![]() |
87850edc0e | ||
![]() |
3a9c69d828 | ||
![]() |
ff64d949ec | ||
![]() |
596ef10ba4 | ||
![]() |
ba1008cc95 | ||
![]() |
89173d0f58 | ||
![]() |
736f3728f6 | ||
![]() |
d5d2341e69 | ||
![]() |
b9cdae6832 | ||
![]() |
c6565d0246 | ||
![]() |
84503a87c2 | ||
![]() |
d454a16ff6 | ||
![]() |
fb90ae7b93 | ||
![]() |
8241bb2679 | ||
![]() |
fac506e0b3 | ||
![]() |
34e9c47508 | ||
![]() |
b1512df789 | ||
![]() |
902d63d302 | ||
![]() |
0d83541ebb | ||
![]() |
69d51ec091 | ||
![]() |
5bf0ffeae9 | ||
![]() |
e1e21554e8 | ||
![]() |
6331cd1fab | ||
![]() |
44d335fb7f | ||
![]() |
7f729bb839 | ||
![]() |
515e46ea08 | ||
![]() |
b837a23fbe | ||
![]() |
8ace6dc92e | ||
![]() |
2cffcbefa4 | ||
![]() |
97d9173821 | ||
![]() |
8a2a0d8013 | ||
![]() |
f3096a0a08 | ||
![]() |
378f020f01 | ||
![]() |
0776ae0090 | ||
![]() |
12165e480b | ||
![]() |
9cb93d7971 | ||
![]() |
a20756aec4 | ||
![]() |
40df352872 | ||
![]() |
94c5353328 | ||
![]() |
c411394149 | ||
![]() |
8fce065c18 | ||
![]() |
a628df7591 | ||
![]() |
0c84032726 | ||
![]() |
4e0854d5ea | ||
![]() |
f210c82af3 | ||
![]() |
938d9bfcdc | ||
![]() |
bb97b55af2 | ||
![]() |
6e3aa05f4e | ||
![]() |
aae0f9a3cb | ||
![]() |
ef0d517982 | ||
![]() |
b8aeadf4d6 | ||
![]() |
27ebf58ff9 | ||
![]() |
cbbd9aa742 | ||
![]() |
20c397695f | ||
![]() |
87dd6d343d | ||
![]() |
9766184c59 | ||
![]() |
926fce143e | ||
![]() |
c466dcb594 | ||
![]() |
966941b646 | ||
![]() |
0468035b46 | ||
![]() |
686d444f0d | ||
![]() |
59f61f97b3 | ||
![]() |
a89849a129 | ||
![]() |
70622551ff | ||
![]() |
bca3f78c0b | ||
![]() |
e11c0548e0 | ||
![]() |
33ebe81793 | ||
![]() |
dcf071bdf2 | ||
![]() |
650d2ac24e | ||
![]() |
e18105632b | ||
![]() |
6d34826036 | ||
![]() |
36bd5f9ed7 | ||
![]() |
c180f3faff | ||
![]() |
76b81f8605 | ||
![]() |
e08227cf4c | ||
![]() |
7321fd3d4f | ||
![]() |
d680c345e1 | ||
![]() |
16fdacdafb | ||
![]() |
487400f00d | ||
![]() |
0df75cdd48 | ||
![]() |
8bcd849b83 | ||
![]() |
6610563241 | ||
![]() |
0e732a548b | ||
![]() |
dced067821 | ||
![]() |
f279f1a1e5 | ||
![]() |
db3b8cc718 | ||
![]() |
77058c0472 | ||
![]() |
c35c8791f9 | ||
![]() |
b94c97017c | ||
![]() |
37fc878f37 | ||
![]() |
fac5b7bb3f | ||
![]() |
ff860fbb0c | ||
![]() |
7624405bb4 | ||
![]() |
24bc15c4ea | ||
![]() |
3fd69a1c29 | ||
![]() |
6cdcd96902 | ||
![]() |
9257824f45 | ||
![]() |
fb474d96fd | ||
![]() |
ba67449f03 | ||
![]() |
2bdb85a420 | ||
![]() |
c008a3478e | ||
![]() |
a5ecdeb5ea | ||
![]() |
82e3348122 | ||
![]() |
4c659b80fd | ||
![]() |
ba10b89006 | ||
![]() |
496f0288dc | ||
![]() |
a90496dc1b | ||
![]() |
f7eeb8df14 | ||
![]() |
99c6c5220a | ||
![]() |
19f0ea4310 | ||
![]() |
e88fc8a2b4 | ||
![]() |
3ed5ccd2ee | ||
![]() |
f9a493a53e | ||
![]() |
75b7811541 | ||
![]() |
589b7ea46c | ||
![]() |
2505ee89d0 | ||
![]() |
ab9d436057 | ||
![]() |
7c75089b71 | ||
![]() |
548257ba8b | ||
![]() |
02ff97990c | ||
![]() |
dfbe19b4d0 | ||
![]() |
4ded6f6528 | ||
![]() |
f21e6efad3 | ||
![]() |
5089b444b1 | ||
![]() |
9ad5d10bda | ||
![]() |
9719017bf6 | ||
![]() |
08d476a9cf | ||
![]() |
a2b633d3a5 | ||
![]() |
f7f7bccbe3 | ||
![]() |
14723732f2 | ||
![]() |
19b23f61bb | ||
![]() |
45de0d3936 | ||
![]() |
b96e200d4a | ||
![]() |
bdea4109c8 | ||
![]() |
be7fabd4ce | ||
![]() |
68aab556d6 | ||
![]() |
2fad5b741e | ||
![]() |
1bf95721dd | ||
![]() |
0e4f141075 | ||
![]() |
7cd74373be | ||
![]() |
15d959e871 | ||
![]() |
aac6d024ae | ||
![]() |
80e67fed22 | ||
![]() |
05a19b188b | ||
![]() |
f706034efb | ||
![]() |
e8538a970b | ||
![]() |
f5013096d2 | ||
![]() |
639b8e561c | ||
![]() |
fe03ca12ed | ||
![]() |
375a98b013 | ||
![]() |
522ce2d8a6 | ||
![]() |
6a981ae30b | ||
![]() |
3851206185 | ||
![]() |
beed2d44ca | ||
![]() |
860f1e39fb | ||
![]() |
956d3dc6b1 | ||
![]() |
3582addb62 | ||
![]() |
e392ede4c1 | ||
![]() |
61ee0c4d02 | ||
![]() |
e451190e3c | ||
![]() |
cf83bd3798 | ||
![]() |
4165056c12 | ||
![]() |
596d75adc1 | ||
![]() |
45f6e59583 | ||
![]() |
ff3f606aea | ||
![]() |
e7311a21e9 | ||
![]() |
c4a4255a7a | ||
![]() |
247053665c | ||
![]() |
141c890b9c | ||
![]() |
ccb5d90634 | ||
![]() |
8c61b8a778 | ||
![]() |
093aa66797 | ||
![]() |
669c339f87 | ||
![]() |
e6ab403e3e | ||
![]() |
fcbcdfe660 | ||
![]() |
450073d418 | ||
![]() |
8b0ba30b9f | ||
![]() |
34abc9813b | ||
![]() |
3df0c71a5a | ||
![]() |
1d9206886a | ||
![]() |
4b346b5428 | ||
![]() |
5f51049c1a | ||
![]() |
5e1f652225 | ||
![]() |
1622977991 | ||
![]() |
9a5021110d | ||
![]() |
05d842b69b | ||
![]() |
4a24bd5d90 | ||
![]() |
0fdc780eca | ||
![]() |
6f8bf46c01 | ||
![]() |
7de78365b0 | ||
![]() |
5f87123fb2 | ||
![]() |
fa1cb5fb4b | ||
![]() |
d973d6c60f | ||
![]() |
b002c59578 | ||
![]() |
ec981dc24d | ||
![]() |
12d625f561 | ||
![]() |
37a7d32bc8 | ||
![]() |
044a39abc9 | ||
![]() |
7bedfc5122 | ||
![]() |
1031b2912c | ||
![]() |
732e494400 | ||
![]() |
0a700fc6c3 | ||
![]() |
af069f25e2 | ||
![]() |
c50257fec4 | ||
![]() |
025927577e | ||
![]() |
39bbe54992 | ||
![]() |
f94c7fd607 | ||
![]() |
e3a0a4bc5e | ||
![]() |
44ab6cf9b7 | ||
![]() |
1c23db3b55 | ||
![]() |
ac727885c4 | ||
![]() |
0e8482e28d | ||
![]() |
46720e3236 | ||
![]() |
c3693f5d44 | ||
![]() |
d4f3c6ebf4 | ||
![]() |
3cc4c5d1b4 | ||
![]() |
5cea920270 | ||
![]() |
0dcba9861a | ||
![]() |
03e299ecc6 | ||
![]() |
5e1e63c37b | ||
![]() |
59e73616cd | ||
![]() |
e03e96f61b | ||
![]() |
01cd2cc4b1 | ||
![]() |
f00a4cf38f | ||
![]() |
9d5860a452 | ||
![]() |
24bbbd3e96 | ||
![]() |
673cc2f9f9 | ||
![]() |
db12702baa | ||
![]() |
807cb0aac4 | ||
![]() |
4033885eb7 | ||
![]() |
4deee0d6c1 | ||
![]() |
58ef772764 | ||
![]() |
f9707f796e | ||
![]() |
37f4be68aa | ||
![]() |
84a4491104 | ||
![]() |
2b2453a397 | ||
![]() |
cc6443bb6c | ||
![]() |
58482ecc63 | ||
![]() |
00b9a3e773 | ||
![]() |
249d487813 | ||
![]() |
f6176eca6f | ||
![]() |
f4cf07b565 | ||
![]() |
581b31964e | ||
![]() |
01bc47a6b7 | ||
![]() |
7fc5228297 | ||
![]() |
6153a8ba9f | ||
![]() |
adf029cf72 | ||
![]() |
aa2989d485 | ||
![]() |
df9abb0241 | ||
![]() |
d54e6125f4 | ||
![]() |
fe6516ac44 | ||
![]() |
7a59edbe71 | ||
![]() |
f7f3ef181d | ||
![]() |
b303291ec4 | ||
![]() |
89b67e534e | ||
![]() |
928d60985b | ||
![]() |
403f988248 | ||
![]() |
2099b1b3c2 | ||
![]() |
8ab5e2f976 | ||
![]() |
92227484ac | ||
![]() |
2ede6f6a83 | ||
![]() |
dce39a6075 | ||
![]() |
15e6c42692 | ||
![]() |
f061ff53f2 | ||
![]() |
b8a0207078 | ||
![]() |
4ec09ad650 | ||
![]() |
59f1f5e986 | ||
![]() |
91bdefd9ba | ||
![]() |
382c47aae0 | ||
![]() |
b6bcb852e5 | ||
![]() |
a91a2604dc | ||
![]() |
43e9768819 | ||
![]() |
b45da4edc8 | ||
![]() |
f42abb3c52 | ||
![]() |
ac171b1d61 | ||
![]() |
62a1ce526a | ||
![]() |
ce4abc28a7 | ||
![]() |
c9bafa74d1 | ||
![]() |
37f324a458 | ||
![]() |
bb2a4c69ad | ||
![]() |
c1b70332fc | ||
![]() |
0270b56185 | ||
![]() |
e09351760d | ||
![]() |
bba98b46f5 | ||
![]() |
874cf0ed7d | ||
![]() |
9ba0cbb3c7 | ||
![]() |
3587f179a4 | ||
![]() |
5a9b4e56b7 | ||
![]() |
71780a707a | ||
![]() |
8e390eef05 | ||
![]() |
96f1342c84 | ||
![]() |
2f93202d1d | ||
![]() |
29ba16a68f | ||
![]() |
5da0ef1f0d | ||
![]() |
878e846150 | ||
![]() |
aebbe3dbfd | ||
![]() |
b9132ac5cb | ||
![]() |
bbbaaaa61c | ||
![]() |
9795efd965 | ||
![]() |
c350b99465 | ||
![]() |
6a64ea5254 | ||
![]() |
ad469c08fe | ||
![]() |
661ada154e | ||
![]() |
a8bbdea224 | ||
![]() |
4c0bf62b7b | ||
![]() |
f5b096e6d4 | ||
![]() |
463edc2822 | ||
![]() |
02ef0fcd5e | ||
![]() |
556f622527 | ||
![]() |
8bb194ddda | ||
![]() |
4a040be01e | ||
![]() |
8d90dcca0e | ||
![]() |
9f7643b0d3 | ||
![]() |
528852ba83 | ||
![]() |
7d92f24a02 | ||
![]() |
d66afd0e13 | ||
![]() |
e1c3329d75 | ||
![]() |
a8635d0882 | ||
![]() |
0c75e46a3b | ||
![]() |
450349b006 | ||
![]() |
b416570ebf | ||
![]() |
bb6b4921b4 | ||
![]() |
857fb7feeb | ||
![]() |
84dbe1f2df | ||
![]() |
96d86ca93f | ||
![]() |
47a5362863 | ||
![]() |
f7740e3030 | ||
![]() |
1187dfe4a9 | ||
![]() |
e652800a65 | ||
![]() |
12bc1025c7 | ||
![]() |
800720e578 | ||
![]() |
aba9c73974 | ||
![]() |
8faafb1014 | ||
![]() |
88f1816c62 | ||
![]() |
35e8e5f525 | ||
![]() |
2d845069c1 | ||
![]() |
64c65d35c9 | ||
![]() |
8925b20447 | ||
![]() |
ca0680519c | ||
![]() |
e9036ebeac | ||
![]() |
f35d47ee4f | ||
![]() |
d6e6e7e511 | ||
![]() |
903fa12ac4 | ||
![]() |
9071c8ca23 | ||
![]() |
6bff52c951 | ||
![]() |
7c450ec6b2 | ||
![]() |
e791190558 | ||
![]() |
a6e6c425a5 | ||
![]() |
14dcb22afe | ||
![]() |
1e25a45b4a | ||
![]() |
3e5a722b66 | ||
![]() |
524e5511c9 | ||
![]() |
946306c28b | ||
![]() |
c2d9416fec | ||
![]() |
d55f54d90b | ||
![]() |
cbb6a7ab9a | ||
![]() |
2e7caf7118 | ||
![]() |
bf56659907 | ||
![]() |
dfa95dbd78 | ||
![]() |
d41021b3cc | ||
![]() |
0ddc1bed76 | ||
![]() |
8f7f10dc8f | ||
![]() |
dea7ee8ee1 | ||
![]() |
b79d618aaa | ||
![]() |
b2c0af2307 | ||
![]() |
44cf23ec08 | ||
![]() |
7b9cc47875 | ||
![]() |
6e59ea007c | ||
![]() |
f580c72f1d | ||
![]() |
1ae91b3ea7 | ||
![]() |
60d7f546c1 | ||
![]() |
a3c834696d | ||
![]() |
00e33d6d1e | ||
![]() |
c9de6ef26b | ||
![]() |
8660d0ced4 | ||
![]() |
23fa0c100c | ||
![]() |
8376a049ad | ||
![]() |
7bd26b542a | ||
![]() |
949ec282c1 | ||
![]() |
00652f59c3 | ||
![]() |
0502410b22 | ||
![]() |
34eac02101 | ||
![]() |
480db40f10 | ||
![]() |
4ad047aed7 | ||
![]() |
d50e59eadb | ||
![]() |
067f5b3342 | ||
![]() |
c23c696adf | ||
![]() |
ea50332799 | ||
![]() |
9ce04e4945 | ||
![]() |
39d7ebb7e9 | ||
![]() |
6c97c8bd20 | ||
![]() |
c32d8f3497 | ||
![]() |
73d27a6a49 | ||
![]() |
aa3be39f89 | ||
![]() |
7c0317f38a | ||
![]() |
c2f759f52a | ||
![]() |
347f1c4840 | ||
![]() |
24bb1d70da | ||
![]() |
3187545bb7 | ||
![]() |
e6f5a3d93f | ||
![]() |
387ff7d950 | ||
![]() |
84f5d558fb | ||
![]() |
22452d9da6 | ||
![]() |
373429ce57 | ||
![]() |
a697e063a6 | ||
![]() |
25131a5e92 | ||
![]() |
8f9d6b2be8 | ||
![]() |
bc78ef49c6 | ||
![]() |
c25e7cab77 | ||
![]() |
e391524d69 | ||
![]() |
bbf3faf1e3 | ||
![]() |
902deb020b | ||
![]() |
a37d28d6db | ||
![]() |
f547122b41 | ||
![]() |
a9f9560728 | ||
![]() |
48cf9d4008 | ||
![]() |
2dd527864e | ||
![]() |
1364a69b35 | ||
![]() |
bd33689d8b | ||
![]() |
e1fad67107 | ||
![]() |
aa63b04e2a | ||
![]() |
81b0b63da4 | ||
![]() |
aaf575354d | ||
![]() |
d4428d1fc3 | ||
![]() |
debac7551b | ||
![]() |
eaf5b6166d | ||
![]() |
d931d88d69 | ||
![]() |
f64d5dfb82 | ||
![]() |
329ae39e96 | ||
![]() |
f381176333 | ||
![]() |
6ddfc8b924 | ||
![]() |
1df4e83e4a | ||
![]() |
ee8a6e7e7a | ||
![]() |
d54b17e2f3 | ||
![]() |
efc61d9e26 | ||
![]() |
6c52e0496d | ||
![]() |
01664bf104 | ||
![]() |
b19916513e | ||
![]() |
df715595de | ||
![]() |
58e5ab2e7c | ||
![]() |
3fd4318062 | ||
![]() |
ccb72b3c5d | ||
![]() |
13eb3ed832 | ||
![]() |
03c2e214e1 | ||
![]() |
589b5f698c | ||
![]() |
522eb03086 | ||
![]() |
621ac4feee | ||
![]() |
5e8489fff3 | ||
![]() |
56c4e802fe | ||
![]() |
dccf627506 | ||
![]() |
94def009c2 | ||
![]() |
82a57957b1 | ||
![]() |
04a0e91175 | ||
![]() |
77273fe052 | ||
![]() |
82ffa7a499 | ||
![]() |
6c86ea5e7a | ||
![]() |
1ba2b6a4cc | ||
![]() |
57bf1008b0 | ||
![]() |
078582e40c | ||
![]() |
c8a963ae14 | ||
![]() |
32ed3d16fd | ||
![]() |
70467e2924 | ||
![]() |
c9e12c666e | ||
![]() |
f2e082e732 | ||
![]() |
0f8a38b807 | ||
![]() |
ee936b74bf | ||
![]() |
3a24d91aa4 | ||
![]() |
42ae468780 | ||
![]() |
1fda886849 | ||
![]() |
3bf66f1346 | ||
![]() |
93084d57b0 | ||
![]() |
eb72951ec6 | ||
![]() |
ea78c46bb4 | ||
![]() |
fac1c8ec88 | ||
![]() |
a494e03f3d | ||
![]() |
8b5d50bf8b | ||
![]() |
b512353dfb | ||
![]() |
a18afbb6dc | ||
![]() |
70a333e790 | ||
![]() |
fc55327df5 | ||
![]() |
aa93bc3d74 | ||
![]() |
3ad1f4f59e | ||
![]() |
576e63584a | ||
![]() |
974879b54d | ||
![]() |
1cfe0637f4 | ||
![]() |
f58a9bcaf6 | ||
![]() |
dd6d71f023 | ||
![]() |
140eb5bd6a | ||
![]() |
16f41aad69 | ||
![]() |
323e57d2b1 | ||
![]() |
ba550b9617 | ||
![]() |
4afcfcb41a | ||
![]() |
06da6970cb | ||
![]() |
5a8e71f2f2 | ||
![]() |
38164d3136 | ||
![]() |
7ebc00a140 | ||
![]() |
49c05b9e5f | ||
![]() |
24381a9081 | ||
![]() |
1a0acad3c2 | ||
![]() |
206cf974c0 | ||
![]() |
d42fe4f107 | ||
![]() |
60e095fbc7 | ||
![]() |
b54d99ff3d | ||
![]() |
f3edf9a469 | ||
![]() |
a7ba9d88c3 | ||
![]() |
dc00aa4836 | ||
![]() |
d1a2932245 | ||
![]() |
26d7dc3031 | ||
![]() |
48ff7d7d5a | ||
![]() |
bc71547d92 | ||
![]() |
054383ed8e | ||
![]() |
005912dfe8 | ||
![]() |
32e7d2db5c | ||
![]() |
15a2662d10 | ||
![]() |
ee472bad35 | ||
![]() |
619c663438 | ||
![]() |
dd16d44b3a | ||
![]() |
01c4d029bd | ||
![]() |
4fbc2b99b0 | ||
![]() |
ea7ddc7d9a | ||
![]() |
c25c9a285e | ||
![]() |
c10e5e918f | ||
![]() |
7dc59bd5a8 | ||
![]() |
d2b9a2cb7d | ||
![]() |
6b21bbac58 | ||
![]() |
0d4ebe3d96 | ||
![]() |
c856f460e6 | ||
![]() |
6a969d2cd2 | ||
![]() |
4d84b14168 | ||
![]() |
b6cbd42d8b | ||
![]() |
7237c2b05a | ||
![]() |
19a217923e | ||
![]() |
7f160e9421 | ||
![]() |
d0c245d0d5 | ||
![]() |
693edab597 | ||
![]() |
d42e070e6c | ||
![]() |
c5958bc9a0 | ||
![]() |
9dc2337787 | ||
![]() |
1a38cc30a8 | ||
![]() |
4949f14184 | ||
![]() |
11902020a5 | ||
![]() |
3f96c21f33 | ||
![]() |
9e81f0aa0e | ||
![]() |
1cce23cef5 | ||
![]() |
b7a56adb60 | ||
![]() |
eb02752cbf | ||
![]() |
3a6eb8cb2f | ||
![]() |
a7d62b0234 | ||
![]() |
b9cbe4f12d | ||
![]() |
5f74b35ba9 | ||
![]() |
affb439ab2 |
283
.all-contributorsrc
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"README.md"
|
||||||
|
],
|
||||||
|
"imageSize": 100,
|
||||||
|
"commit": false,
|
||||||
|
"contributors": [
|
||||||
|
{
|
||||||
|
"login": "mezner",
|
||||||
|
"name": "Russell Myers",
|
||||||
|
"avatar_url": "https://avatars2.githubusercontent.com/u/184085?v=4",
|
||||||
|
"profile": "http://www.russellmyers.com",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "ehwarren",
|
||||||
|
"name": "Austin Warren",
|
||||||
|
"avatar_url": "https://avatars1.githubusercontent.com/u/3991658?v=4",
|
||||||
|
"profile": "http://www.morwire.com",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "Drachenkaetzchen",
|
||||||
|
"name": "Felicia Hummel",
|
||||||
|
"avatar_url": "https://avatars1.githubusercontent.com/u/162974?v=4",
|
||||||
|
"profile": "https://github.com/Drachenkaetzchen",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "mikemaccana",
|
||||||
|
"name": "Mike MacCana",
|
||||||
|
"avatar_url": "https://avatars2.githubusercontent.com/u/172594?v=4",
|
||||||
|
"profile": "https://github.com/mikemaccana",
|
||||||
|
"contributions": [
|
||||||
|
"test",
|
||||||
|
"design"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "yxuko",
|
||||||
|
"name": "Yacine Kanzari",
|
||||||
|
"avatar_url": "https://avatars1.githubusercontent.com/u/1786317?v=4",
|
||||||
|
"profile": "https://github.com/yxuko",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "BBJip",
|
||||||
|
"name": "BBJip",
|
||||||
|
"avatar_url": "https://avatars2.githubusercontent.com/u/32908927?v=4",
|
||||||
|
"profile": "https://github.com/BBJip",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "Futagirl",
|
||||||
|
"name": "Futagirl",
|
||||||
|
"avatar_url": "https://avatars2.githubusercontent.com/u/33533958?v=4",
|
||||||
|
"profile": "https://github.com/Futagirl",
|
||||||
|
"contributions": [
|
||||||
|
"design"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "levrik",
|
||||||
|
"name": "Levin Rickert",
|
||||||
|
"avatar_url": "https://avatars3.githubusercontent.com/u/9491603?v=4",
|
||||||
|
"profile": "https://www.levrik.io",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "kwonoj",
|
||||||
|
"name": "OJ Kwon",
|
||||||
|
"avatar_url": "https://avatars2.githubusercontent.com/u/1210596?v=4",
|
||||||
|
"profile": "https://kwonoj.github.io",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "Domain",
|
||||||
|
"name": "domain",
|
||||||
|
"avatar_url": "https://avatars2.githubusercontent.com/u/903197?v=4",
|
||||||
|
"profile": "https://github.com/Domain",
|
||||||
|
"contributions": [
|
||||||
|
"plugin",
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "kbjr",
|
||||||
|
"name": "James Brumond",
|
||||||
|
"avatar_url": "https://avatars1.githubusercontent.com/u/195127?v=4",
|
||||||
|
"profile": "http://www.jbrumond.me",
|
||||||
|
"contributions": [
|
||||||
|
"plugin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "Tyriar",
|
||||||
|
"name": "Daniel Imms",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/2193314?v=4",
|
||||||
|
"profile": "http://www.growingwiththeweb.com",
|
||||||
|
"contributions": [
|
||||||
|
"code",
|
||||||
|
"plugin",
|
||||||
|
"test"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "baflo",
|
||||||
|
"name": "Florian Bachmann",
|
||||||
|
"avatar_url": "https://avatars2.githubusercontent.com/u/834350?v=4",
|
||||||
|
"profile": "https://github.com/baflo",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "mischah",
|
||||||
|
"name": "Michael Kühnel",
|
||||||
|
"avatar_url": "https://avatars2.githubusercontent.com/u/441011?v=4",
|
||||||
|
"profile": "http://michael-kuehnel.de",
|
||||||
|
"contributions": [
|
||||||
|
"code",
|
||||||
|
"design"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "NieLeben",
|
||||||
|
"name": "Tilmann Meyer",
|
||||||
|
"avatar_url": "https://avatars3.githubusercontent.com/u/47182955?v=4",
|
||||||
|
"profile": "https://github.com/NieLeben",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "PMExtra",
|
||||||
|
"name": "PM Extra",
|
||||||
|
"avatar_url": "https://avatars3.githubusercontent.com/u/11289158?v=4",
|
||||||
|
"profile": "http://www.jubeat.net",
|
||||||
|
"contributions": [
|
||||||
|
"bug"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "IgnusG",
|
||||||
|
"name": "Jonathan",
|
||||||
|
"avatar_url": "https://avatars1.githubusercontent.com/u/6438760?v=4",
|
||||||
|
"profile": "https://jjuhas.keybase.pub//",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "hammster",
|
||||||
|
"name": "Hans Koch",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/1093709?v=4",
|
||||||
|
"profile": "https://hans-koch.me",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "ThePuzzlemaker",
|
||||||
|
"name": "Dak Smyth",
|
||||||
|
"avatar_url": "https://avatars3.githubusercontent.com/u/12666617?v=4",
|
||||||
|
"profile": "http://thepuzzlemaker.info",
|
||||||
|
"contributions": [
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"contributorsPerLine": 7,
|
||||||
|
"projectName": "terminus",
|
||||||
|
"projectOwner": "Eugeny",
|
||||||
|
"repoType": "github",
|
||||||
|
"repoHost": "https://github.com",
|
||||||
|
"commitConvention": "none",
|
||||||
|
"skipCi": true
|
||||||
|
}
|
106
.eslintrc.yml
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
parser: '@typescript-eslint/parser'
|
||||||
|
parserOptions:
|
||||||
|
project: tsconfig.json
|
||||||
|
extends:
|
||||||
|
- 'plugin:@typescript-eslint/all'
|
||||||
|
plugins:
|
||||||
|
- '@typescript-eslint'
|
||||||
|
env:
|
||||||
|
browser: true
|
||||||
|
es6: true
|
||||||
|
node: true
|
||||||
|
commonjs: true
|
||||||
|
rules:
|
||||||
|
'@typescript-eslint/semi':
|
||||||
|
- error
|
||||||
|
- never
|
||||||
|
'@typescript-eslint/indent':
|
||||||
|
- error
|
||||||
|
- 4
|
||||||
|
'@typescript-eslint/explicit-member-accessibility':
|
||||||
|
- error
|
||||||
|
- accessibility: no-public
|
||||||
|
overrides:
|
||||||
|
parameterProperties: explicit
|
||||||
|
'@typescript-eslint/no-require-imports': off
|
||||||
|
'@typescript-eslint/no-parameter-properties': off
|
||||||
|
'@typescript-eslint/explicit-function-return-type': off
|
||||||
|
'@typescript-eslint/no-explicit-any': off
|
||||||
|
'@typescript-eslint/no-magic-numbers': off
|
||||||
|
'@typescript-eslint/member-delimiter-style': off
|
||||||
|
'@typescript-eslint/promise-function-async': off
|
||||||
|
'@typescript-eslint/no-unnecessary-type-assertion': off
|
||||||
|
'@typescript-eslint/require-array-sort-compare': off
|
||||||
|
'@typescript-eslint/no-floating-promises': off
|
||||||
|
'@typescript-eslint/prefer-readonly': off
|
||||||
|
'@typescript-eslint/require-await': off
|
||||||
|
'@typescript-eslint/strict-boolean-expressions': off
|
||||||
|
'@typescript-eslint/no-misused-promises': off
|
||||||
|
'@typescript-eslint/typedef': off
|
||||||
|
'@typescript-eslint/no-use-before-define':
|
||||||
|
- error
|
||||||
|
- classes: false
|
||||||
|
no-duplicate-imports: error
|
||||||
|
array-bracket-spacing:
|
||||||
|
- error
|
||||||
|
- never
|
||||||
|
block-scoped-var: error
|
||||||
|
brace-style: off
|
||||||
|
'@typescript-eslint/brace-style':
|
||||||
|
- error
|
||||||
|
- 1tbs
|
||||||
|
- allowSingleLine: true
|
||||||
|
computed-property-spacing:
|
||||||
|
- error
|
||||||
|
- never
|
||||||
|
comma-dangle:
|
||||||
|
- error
|
||||||
|
- always-multiline
|
||||||
|
curly: error
|
||||||
|
eol-last: error
|
||||||
|
eqeqeq:
|
||||||
|
- error
|
||||||
|
- smart
|
||||||
|
linebreak-style:
|
||||||
|
- error
|
||||||
|
- unix
|
||||||
|
max-depth:
|
||||||
|
- 1
|
||||||
|
- 5
|
||||||
|
max-statements:
|
||||||
|
- 1
|
||||||
|
- 80
|
||||||
|
no-multiple-empty-lines: error
|
||||||
|
no-mixed-spaces-and-tabs: error
|
||||||
|
no-trailing-spaces: error
|
||||||
|
'@typescript-eslint/no-unused-vars':
|
||||||
|
- error
|
||||||
|
- vars: all
|
||||||
|
args: after-used
|
||||||
|
argsIgnorePattern: ^_
|
||||||
|
no-undef: error
|
||||||
|
no-var: error
|
||||||
|
object-curly-spacing:
|
||||||
|
- error
|
||||||
|
- always
|
||||||
|
quote-props:
|
||||||
|
- warn
|
||||||
|
- as-needed
|
||||||
|
- keywords: true
|
||||||
|
numbers: true
|
||||||
|
quotes: off
|
||||||
|
'@typescript-eslint/quotes':
|
||||||
|
- error
|
||||||
|
- single
|
||||||
|
- allowTemplateLiterals: true
|
||||||
|
'@typescript-eslint/no-non-null-assertion': off
|
||||||
|
'@typescript-eslint/no-unnecessary-condition': off
|
||||||
|
'@typescript-eslint/no-untyped-public-signature': off # bugs out on constructors
|
||||||
|
'@typescript-eslint/restrict-template-expressions': off
|
||||||
|
'@typescript-eslint/no-dynamic-delete': off
|
||||||
|
'@typescript-eslint/prefer-nullish-coalescing': 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-base-to-string': off # broken in typescript-eslint
|
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
github: eugeny
|
||||||
|
open_collective: terminus
|
||||||
|
ko_fi: eugeny
|
6
.github/stale.yml
vendored
@@ -1,11 +1,11 @@
|
|||||||
# 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"
|
||||||
- "S: Triaged"
|
- "S: Confirmed"
|
||||||
# Label to use when marking an issue as stale
|
# Label to use when marking an issue as stale
|
||||||
staleLabel: "S: Stale"
|
staleLabel: "S: Stale"
|
||||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||||
|
30
.github/workflows/docs.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
name: Docs
|
||||||
|
on: push
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-18.04
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Installing Node
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 10
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
eval $(ssh-agent -s)
|
||||||
|
ssh-add <(echo "$DOCS_PRIVATE_KEY")
|
||||||
|
yarn cache clean
|
||||||
|
cd app
|
||||||
|
yarn
|
||||||
|
cd ..
|
||||||
|
rm app/node_modules/.yarn-integrity
|
||||||
|
yarn
|
||||||
|
yarn run docs
|
||||||
|
rsync -e "ssh -o StrictHostKeyChecking=no" -arv docs/api/ root@ajenti.org:/srv/terminus-docs/
|
||||||
|
|
||||||
|
env:
|
||||||
|
DOCS_PRIVATE_KEY: ${{ secrets.DOCS_PRIVATE_KEY }}
|
26
.github/workflows/lint.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
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: 10
|
||||||
|
|
||||||
|
- name: Install deps
|
||||||
|
run: |
|
||||||
|
npm i -g yarn@1.19.1
|
||||||
|
cd app
|
||||||
|
yarn
|
||||||
|
cd ..
|
||||||
|
rm app/node_modules/.yarn-integrity
|
||||||
|
yarn
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: yarn run lint
|
73
.github/workflows/linux.yml
vendored
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
name: Linux Build
|
||||||
|
on: [push, pull_request]
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Install Node
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 10
|
||||||
|
|
||||||
|
- name: Install deps
|
||||||
|
run: |
|
||||||
|
npm i -g yarn@1.19.1
|
||||||
|
cd app
|
||||||
|
yarn
|
||||||
|
cd ..
|
||||||
|
rm app/node_modules/.yarn-integrity
|
||||||
|
yarn
|
||||||
|
|
||||||
|
- 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:
|
||||||
|
DEBUG: electron-builder,electron-builder:*
|
||||||
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
|
|
||||||
|
- name: Package artifacts
|
||||||
|
run: |
|
||||||
|
mkdir artifact-deb
|
||||||
|
mv dist/*.deb artifact-deb/ || true
|
||||||
|
mkdir artifact-rpm
|
||||||
|
mv dist/*.rpm artifact-rpm/ || true
|
||||||
|
mkdir artifact-snap
|
||||||
|
mv dist/*.snap artifact-snap/ || true
|
||||||
|
mkdir artifact-tar.gz
|
||||||
|
mv dist/*.tar.gz artifact-tar.gz/ || true
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@master
|
||||||
|
name: Upload DEB
|
||||||
|
with:
|
||||||
|
name: Linux .deb
|
||||||
|
path: artifact-deb
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@master
|
||||||
|
name: Upload RPM
|
||||||
|
with:
|
||||||
|
name: Linux .rpm
|
||||||
|
path: artifact-rpm
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@master
|
||||||
|
name: Upload Snap
|
||||||
|
with:
|
||||||
|
name: Linux .snap
|
||||||
|
path: artifact-snap
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@master
|
||||||
|
name: Upload tarball
|
||||||
|
with:
|
||||||
|
name: Linux tarball
|
||||||
|
path: artifact-tar.gz
|
70
.github/workflows/macos.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
name: macOS Build
|
||||||
|
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: 10
|
||||||
|
|
||||||
|
- name: Install deps
|
||||||
|
run: |
|
||||||
|
sudo npm i -g yarn@1.19.1
|
||||||
|
cd app
|
||||||
|
yarn
|
||||||
|
cd ..
|
||||||
|
rm app/node_modules/.yarn-integrity
|
||||||
|
yarn
|
||||||
|
|
||||||
|
- name: Build native deps
|
||||||
|
run: scripts/build-native.js
|
||||||
|
|
||||||
|
- name: Webpack
|
||||||
|
run: yarn run build
|
||||||
|
|
||||||
|
- name: Prepackage plugins
|
||||||
|
run: scripts/prepackage-plugins.js
|
||||||
|
|
||||||
|
- 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:
|
||||||
|
#DEBUG: electron-builder,electron-builder:*
|
||||||
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
|
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||||
|
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||||
|
APPSTORE_USERNAME: ${{ secrets.APPSTORE_USERNAME }}
|
||||||
|
APPSTORE_PASSWORD: ${{ secrets.APPSTORE_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Build packages without signing
|
||||||
|
run: scripts/build-macos.js
|
||||||
|
if: github.repository != 'Eugeny/terminus' || github.event_name != 'push'
|
||||||
|
env:
|
||||||
|
DEBUG: electron-builder,electron-builder:*
|
||||||
|
|
||||||
|
- name: Package artifacts
|
||||||
|
run: |
|
||||||
|
mkdir artifact-pkg
|
||||||
|
mv dist/*.pkg artifact-pkg/
|
||||||
|
mkdir artifact-zip
|
||||||
|
mv dist/*.zip artifact-zip/
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@master
|
||||||
|
name: Upload PKG
|
||||||
|
with:
|
||||||
|
name: macOS .pkg
|
||||||
|
path: artifact-pkg
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@master
|
||||||
|
name: Upload ZIP
|
||||||
|
with:
|
||||||
|
name: macOS .zip
|
||||||
|
path: artifact-zip
|
54
.github/workflows/windows.yml
vendored
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
name: Windows Build
|
||||||
|
on: [push, pull_request]
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: windows-2016
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Installing Node
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 10
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
npm i -g yarn@1.19.1
|
||||||
|
yarn
|
||||||
|
node scripts/build-native.js
|
||||||
|
yarn run build
|
||||||
|
node scripts/prepackage-plugins.js
|
||||||
|
|
||||||
|
- name: Build and sign packages
|
||||||
|
run: node scripts/build-windows.js
|
||||||
|
if: github.repository == 'Eugeny/terminus' && github.event_name == 'push'
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
|
WIN_CSC_LINK: ${{ secrets.WIN_CSC_LINK }}
|
||||||
|
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: Package artifacts
|
||||||
|
run: |
|
||||||
|
mkdir artifact-setup
|
||||||
|
mv dist/*-setup.exe artifact-setup/
|
||||||
|
mkdir artifact-portable
|
||||||
|
mv dist/*-portable.zip artifact-portable/
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@master
|
||||||
|
name: Upload installer
|
||||||
|
with:
|
||||||
|
name: Installer
|
||||||
|
path: artifact-setup
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@master
|
||||||
|
name: Upload portable build
|
||||||
|
with:
|
||||||
|
name: Portable build
|
||||||
|
path: artifact-portable
|
7
.gitignore
vendored
@@ -6,6 +6,8 @@ node_modules
|
|||||||
build/files.wxs
|
build/files.wxs
|
||||||
dist
|
dist
|
||||||
*/dist
|
*/dist
|
||||||
|
*/typings
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
*.xcworkspacedata
|
*.xcworkspacedata
|
||||||
*.xcuserstate
|
*.xcuserstate
|
||||||
@@ -21,3 +23,8 @@ yarn-error.log
|
|||||||
|
|
||||||
docs/api
|
docs/api
|
||||||
.travis.ssh.key
|
.travis.ssh.key
|
||||||
|
*.code-workspace
|
||||||
|
|
||||||
|
.electron-symbols
|
||||||
|
sentry.properties
|
||||||
|
sentry-symbols.js
|
||||||
|
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 +0,0 @@
|
|||||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDDFM4nHSbET5V7EYNgjA8NeVfOxV0wVMdZ2YvsDzD+qPJ4+MYbvsL7ZPaSxQSn7n6ATkLHjKje5RpF/Rl9K3kucGs0P6cqJVeE0qryEteQ3Q+fYAk+bD2J9ZQ/hv/0NtLl8T+7lJUZ3WUxFH73sgph77Sw0z+kMpPaK7U2vqMBQD/7+6iJgya31wP0qW0XKDz1BjKeXgwTg10Pm4vcGsR4c2q7YIzSzBHffcyo0vJyFvOX/ZKHlZRcq/wnQMeOl/hPgf1xCENjQZmFVReQlYSw5cNNDT9HZPKekOAZFFez7/AbPiTIo/bnBYIv0mdUjr3nw8nXF505q8LiD3z/ksaaWDqe9CCLM4W0Bh7/dhP7IGPdfX0fVHLhOnYIOsG21D8rWJjMPkVRSLyEvWNAnVuObJNHoQu8VATnOxfPNnMun72IHyyFWVoADk5JcsMbzcP7gZB+5oJO7U1qpcdndtBOA3ZlF0Uz2jVZnqavoEBWT39tl3vs69hAA3aTPGclg7HMuAJOl4HsKmaUgDxqV2wCX/S4pDqmKMbmumDLX+MM0xl0gXj/zpVJp9BzdnrArkC40ivmC6TSA4wrdN0tNBlqApkH5/jxGWrcu2AXVn9PGF3+QrjW0iu+QMZCaKWDhLIQC835uFwzhnNGlx41B7uxMLuNFxKXdQ3f/cC9QMG8ew== TravisCIDeployKey
|
|
27
.travis.yml
@@ -8,32 +8,12 @@ stages:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
include:
|
include:
|
||||||
- stage: 'Build'
|
|
||||||
os: linux
|
|
||||||
before_install:
|
|
||||||
- yarn
|
|
||||||
- rm app/node_modules/.yarn-integrity || true
|
|
||||||
- scripts/install-deps.js
|
|
||||||
script:
|
|
||||||
- scripts/build-native.js
|
|
||||||
- yarn run build
|
|
||||||
- scripts/prepackage-plugins.js
|
|
||||||
- scripts/build-linux.js
|
|
||||||
|
|
||||||
- stage: 'Build'
|
|
||||||
os: osx
|
|
||||||
before_install:
|
|
||||||
- rm app/node_modules/.yarn-integrity || true
|
|
||||||
- yarn
|
|
||||||
script:
|
|
||||||
- scripts/build-native.js
|
|
||||||
- yarn run build
|
|
||||||
- scripts/prepackage-plugins.js
|
|
||||||
- scripts/build-macos.js
|
|
||||||
|
|
||||||
- stage: 'Docs'
|
- stage: 'Docs'
|
||||||
os: linux
|
os: linux
|
||||||
|
if: branch = master
|
||||||
script:
|
script:
|
||||||
|
- '[ -z "${encrypted_4e2fb4889ef8_iv}" ] && exit 0 || true'
|
||||||
|
- set -e
|
||||||
- openssl aes-256-cbc -K $encrypted_4e2fb4889ef8_key -iv $encrypted_4e2fb4889ef8_iv -in .travis.ssh.key.enc -out .travis.ssh.key -d
|
- openssl aes-256-cbc -K $encrypted_4e2fb4889ef8_key -iv $encrypted_4e2fb4889ef8_iv -in .travis.ssh.key.enc -out .travis.ssh.key -d
|
||||||
- eval "$(ssh-agent -s)"
|
- eval "$(ssh-agent -s)"
|
||||||
- chmod 600 .travis.ssh.key
|
- chmod 600 .travis.ssh.key
|
||||||
@@ -50,6 +30,7 @@ addons:
|
|||||||
packages:
|
packages:
|
||||||
- rpm
|
- rpm
|
||||||
- yarn
|
- yarn
|
||||||
|
- libsecret-1-dev
|
||||||
sources:
|
sources:
|
||||||
- sourceline: 'deb https://dl.yarnpkg.com/debian/ stable main'
|
- sourceline: 'deb https://dl.yarnpkg.com/debian/ stable main'
|
||||||
key_url: 'https://dl.yarnpkg.com/debian/pubkey.gpg'
|
key_url: 'https://dl.yarnpkg.com/debian/pubkey.gpg'
|
||||||
|
@@ -92,11 +92,11 @@ Plugins provide functionality by exporting singular or multi providers:
|
|||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import { NgModule, Injectable } from '@angular/core'
|
import { NgModule, Injectable } from '@angular/core'
|
||||||
import { ToolbarButtonProvider, IToolbarButton } from 'terminus-core'
|
import { ToolbarButtonProvider, ToolbarButton } from 'terminus-core'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MyButtonProvider extends ToolbarButtonProvider {
|
export class MyButtonProvider extends ToolbarButtonProvider {
|
||||||
provide (): IToolbarButton[] {
|
provide (): ToolbarButton[] {
|
||||||
return [{
|
return [{
|
||||||
icon: 'star',
|
icon: 'star',
|
||||||
title: 'Foobar',
|
title: 'Foobar',
|
||||||
|
98
README.md
@@ -1,45 +1,61 @@
|
|||||||

|

|
||||||
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://raw.githubusercontent.com/Eugeny/terminus/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg"/></a> <a href="https://travis-ci.org/Eugeny/terminus"><img src="https://travis-ci.org/Eugeny/terminus.svg?branch=master"/></a>
|
<a href="https://raw.githubusercontent.com/Eugeny/terminus/master/LICENSE"><img alt="GitHub" src="https://img.shields.io/github/license/eugeny/terminus.svg?label=License&style=flat-square"></a> <a href="https://ci.appveyor.com/project/Eugeny/terminus"><img alt="AppVeyor" src="https://img.shields.io/appveyor/ci/eugeny/terminus.svg?label=CI&logo=appveyor&logoColor=white&style=flat-square"></a>
|
||||||
<a href="https://ci.appveyor.com/project/Eugeny/terminus"><img src="https://ci.appveyor.com/api/projects/status/wnnq4hm5mbd9rgoy?svg=true"/></a>
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/Eugeny/terminus/releases/latest">Downloads</a> | <a href="https://gitter.im/terminus-terminal/community">Community</a> | <a href="https://ci.appveyor.com/project/Eugeny/terminus/build/artifacts">Latest Windows nightly</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=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 terminal heavily inspired by Hyper. It is, however, designed for people who need to get things done.
|
**Terminus** is a highly configurable terminal emulator for Windows, macOS and Linux
|
||||||
|
|
||||||
* Runs on Windows, macOS and Linux
|
* Integrated SSH client and connection manager
|
||||||
* Theming and color schemes
|
* Theming and color schemes
|
||||||
* Fully configurable shortcuts
|
* Fully configurable shortcuts
|
||||||
|
* Split panes
|
||||||
|
* Remembers your tabs
|
||||||
|
* PowerShell (and PS Core), WSL, Git-Bash, Cygwin, Cmder and CMD support
|
||||||
|
* Direct file transfer from/to SSH sessions via Zmodem
|
||||||
* Full Unicode support including double-width characters
|
* Full Unicode support including double-width characters
|
||||||
* Doesn't choke on fast-flowing outputs
|
* Doesn't choke on fast-flowing outputs
|
||||||
* Proper shell-like experience on Windows including tab completion (via Clink)
|
* Proper shell experience on Windows including tab completion (via Clink)
|
||||||
* PowerShell (+Core), WSL (Bash on Windows), Git-Bash, Cygwin, Cmder and CMD support
|
|
||||||
* Remembers your tabs
|
|
||||||
* Integrated SSH client and connection manager
|
|
||||||
|
|
||||||
|
|
||||||
[](https://ko-fi.com/eugeny)
|
[](https://ko-fi.com/eugeny)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
* **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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
# Portable
|
||||||
|
|
||||||
|
For portable in windows, user can create folder `data` at the same directory as `Terminal.exe` to save the settings.
|
||||||
|
|
||||||
# Plugins
|
# Plugins
|
||||||
|
|
||||||
Plugins 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
|
||||||
* [theme-hype](https://github.com/Eugeny/terminus-theme-hype) - a Hyper inspired theme
|
|
||||||
* [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
|
||||||
* [scrollbar](https://github.com/kbjr/terminus-scrollbar) - adds a scrollbar to terminal tabs
|
|
||||||
* [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
|
||||||
|
|
||||||
|
# Themes
|
||||||
|
|
||||||
|
* [hype](https://github.com/Eugeny/terminus-theme-hype) - a Hyper inspired theme
|
||||||
|
* [relaxed](https://github.com/Relaxed-Theme/relaxed-terminal-themes#terminus) - the Relaxed theme for Terminus
|
||||||
|
* [gruvbox](https://github.com/porkloin/terminus-theme-gruvbox)
|
||||||
|
* [windows10](https://www.npmjs.com/package/terminus-theme-windows10)
|
||||||
|
* [altair](https://github.com/yxuko/terminus-altair)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -49,5 +65,57 @@ 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.
|
||||||
|
|
||||||
## License
|
---
|
||||||
[](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2FEugeny%2Fterminus?ref=badge_large)
|
|
||||||
|
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 -->
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
|
<!-- markdownlint-disable -->
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td align="center"><a href="http://www.russellmyers.com"><img src="https://avatars2.githubusercontent.com/u/184085?v=4" width="100px;" alt=""/><br /><sub><b>Russell Myers</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=mezner" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="http://www.morwire.com"><img src="https://avatars1.githubusercontent.com/u/3991658?v=4" width="100px;" alt=""/><br /><sub><b>Austin Warren</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=ehwarren" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/Drachenkaetzchen"><img src="https://avatars1.githubusercontent.com/u/162974?v=4" width="100px;" alt=""/><br /><sub><b>Felicia Hummel</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=Drachenkaetzchen" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/mikemaccana"><img src="https://avatars2.githubusercontent.com/u/172594?v=4" width="100px;" alt=""/><br /><sub><b>Mike MacCana</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=mikemaccana" title="Tests">⚠️</a> <a href="#design-mikemaccana" title="Design">🎨</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/yxuko"><img src="https://avatars1.githubusercontent.com/u/1786317?v=4" width="100px;" alt=""/><br /><sub><b>Yacine Kanzari</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=yxuko" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/BBJip"><img src="https://avatars2.githubusercontent.com/u/32908927?v=4" width="100px;" alt=""/><br /><sub><b>BBJip</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=BBJip" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/Futagirl"><img src="https://avatars2.githubusercontent.com/u/33533958?v=4" width="100px;" alt=""/><br /><sub><b>Futagirl</b></sub></a><br /><a href="#design-Futagirl" title="Design">🎨</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center"><a href="https://www.levrik.io"><img src="https://avatars3.githubusercontent.com/u/9491603?v=4" width="100px;" alt=""/><br /><sub><b>Levin Rickert</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=levrik" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://kwonoj.github.io"><img src="https://avatars2.githubusercontent.com/u/1210596?v=4" width="100px;" alt=""/><br /><sub><b>OJ Kwon</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=kwonoj" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/Domain"><img src="https://avatars2.githubusercontent.com/u/903197?v=4" width="100px;" alt=""/><br /><sub><b>domain</b></sub></a><br /><a href="#plugin-Domain" title="Plugin/utility libraries">🔌</a> <a href="https://github.com/Eugeny/terminus/commits?author=Domain" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="http://www.jbrumond.me"><img src="https://avatars1.githubusercontent.com/u/195127?v=4" width="100px;" alt=""/><br /><sub><b>James Brumond</b></sub></a><br /><a href="#plugin-kbjr" title="Plugin/utility libraries">🔌</a></td>
|
||||||
|
<td align="center"><a href="http://www.growingwiththeweb.com"><img src="https://avatars0.githubusercontent.com/u/2193314?v=4" width="100px;" alt=""/><br /><sub><b>Daniel Imms</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=Tyriar" title="Code">💻</a> <a href="#plugin-Tyriar" title="Plugin/utility libraries">🔌</a> <a href="https://github.com/Eugeny/terminus/commits?author=Tyriar" title="Tests">⚠️</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/baflo"><img src="https://avatars2.githubusercontent.com/u/834350?v=4" width="100px;" alt=""/><br /><sub><b>Florian Bachmann</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=baflo" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="http://michael-kuehnel.de"><img src="https://avatars2.githubusercontent.com/u/441011?v=4" width="100px;" alt=""/><br /><sub><b>Michael Kühnel</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=mischah" title="Code">💻</a> <a href="#design-mischah" title="Design">🎨</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center"><a href="https://github.com/NieLeben"><img src="https://avatars3.githubusercontent.com/u/47182955?v=4" width="100px;" alt=""/><br /><sub><b>Tilmann Meyer</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=NieLeben" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="http://www.jubeat.net"><img src="https://avatars3.githubusercontent.com/u/11289158?v=4" width="100px;" alt=""/><br /><sub><b>PM Extra</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/issues?q=author%3APMExtra" title="Bug reports">🐛</a></td>
|
||||||
|
<td align="center"><a href="https://jjuhas.keybase.pub//"><img src="https://avatars1.githubusercontent.com/u/6438760?v=4" width="100px;" alt=""/><br /><sub><b>Jonathan</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=IgnusG" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://hans-koch.me"><img src="https://avatars0.githubusercontent.com/u/1093709?v=4" width="100px;" alt=""/><br /><sub><b>Hans Koch</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=hammster" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="http://thepuzzlemaker.info"><img src="https://avatars3.githubusercontent.com/u/12666617?v=4" width="100px;" alt=""/><br /><sub><b>Dak Smyth</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=ThePuzzlemaker" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="http://yfwz100.github.io"><img src="https://avatars2.githubusercontent.com/u/983211?v=4" width="100px;" alt=""/><br /><sub><b>Wang Zhi</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=yfwz100" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/jack1142"><img src="https://avatars0.githubusercontent.com/u/6032823?v=4" width="100px;" alt=""/><br /><sub><b>jack1142</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=jack1142" title="Code">💻</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center"><a href="https://github.com/hdougie"><img src="https://avatars1.githubusercontent.com/u/450799?v=4" width="100px;" alt=""/><br /><sub><b>Howie Douglas</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=hdougie" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://chriskaczor.com"><img src="https://avatars2.githubusercontent.com/u/180906?v=4" width="100px;" alt=""/><br /><sub><b>Chris Kaczor</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=ckaczor" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://www.boxmein.net"><img src="https://avatars1.githubusercontent.com/u/358714?v=4" width="100px;" alt=""/><br /><sub><b>Johannes Kadak</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=boxmein" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/LeSeulArtichaut"><img src="https://avatars1.githubusercontent.com/u/38361244?v=4" width="100px;" alt=""/><br /><sub><b>LeSeulArtichaut</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=LeSeulArtichaut" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/CyrilTaylor"><img src="https://avatars0.githubusercontent.com/u/12631466?v=4" width="100px;" alt=""/><br /><sub><b>Cyril Taylor</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=CyrilTaylor" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/nstefanou"><img src="https://avatars3.githubusercontent.com/u/51129173?v=4" 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" 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" 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>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- markdownlint-enable -->
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||||
|
|
||||||
|
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
||||||
|
BIN
app/assets/activity.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
4
app/dev-app-update.yml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
owner: eugeny
|
||||||
|
repo: terminus
|
||||||
|
provider: github
|
||||||
|
updaterCacheDirName: terminus-updater
|
@@ -8,17 +8,15 @@ html
|
|||||||
window.nodeRequire = require
|
window.nodeRequire = require
|
||||||
script(src='./preload.js')
|
script(src='./preload.js')
|
||||||
script(src='./bundle.js', defer)
|
script(src='./bundle.js', defer)
|
||||||
style#custom-css
|
|
||||||
style.
|
style.
|
||||||
body { transition: 0.5s background; }
|
body { transition: 0.5s background; }
|
||||||
body
|
body
|
||||||
|
style#custom-css
|
||||||
app-root
|
app-root
|
||||||
.preload-logo
|
.preload-logo
|
||||||
div
|
div
|
||||||
.terminus-logo
|
.terminus-logo
|
||||||
h1.terminus-title Terminus
|
h1.terminus-title Terminus
|
||||||
sup α
|
sup α
|
||||||
.progress
|
.progress
|
||||||
.bar(style='width: 0%')
|
.bar(style='width: 0%')
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { app, ipcMain, Menu, Tray, shell } from 'electron'
|
import { app, ipcMain, Menu, Tray, shell, globalShortcut } from 'electron'
|
||||||
|
// eslint-disable-next-line no-duplicate-imports
|
||||||
import * as electron from 'electron'
|
import * as electron from 'electron'
|
||||||
import { loadConfig } from './config'
|
import { loadConfig } from './config'
|
||||||
import { Window, WindowOptions } from './window'
|
import { Window, WindowOptions } from './window'
|
||||||
@@ -8,23 +9,39 @@ export class Application {
|
|||||||
private windows: Window[] = []
|
private windows: Window[] = []
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
ipcMain.on('app:config-change', () => {
|
ipcMain.on('app:config-change', (_event, config) => {
|
||||||
this.broadcast('host:config-change')
|
this.broadcast('host:config-change', config)
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('app:register-global-hotkey', (_event, specs) => {
|
||||||
|
globalShortcut.unregisterAll()
|
||||||
|
for (let spec of specs) {
|
||||||
|
globalShortcut.register(spec, () => {
|
||||||
|
this.onGlobalHotkey()
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
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('force_discrete_gpu', '0')
|
|
||||||
app.commandLine.appendSwitch('lang', 'EN')
|
app.commandLine.appendSwitch('lang', 'EN')
|
||||||
|
app.allowRendererProcessReuse = false
|
||||||
|
|
||||||
|
for (const flag of configData.flags || [['force_discrete_gpu', '0']]) {
|
||||||
|
app.commandLine.appendSwitch(flag[0], flag[1])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init () {
|
init (): void {
|
||||||
electron.screen.on('display-metrics-changed', () => this.broadcast('host:display-metrics-changed'))
|
electron.screen.on('display-metrics-changed', () => this.broadcast('host:display-metrics-changed'))
|
||||||
}
|
}
|
||||||
|
|
||||||
async newWindow (options?: WindowOptions): Promise<Window> {
|
async newWindow (options?: WindowOptions): Promise<Window> {
|
||||||
@@ -37,6 +54,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()
|
||||||
}
|
}
|
||||||
@@ -44,20 +64,38 @@ export class Application {
|
|||||||
return window
|
return window
|
||||||
}
|
}
|
||||||
|
|
||||||
broadcast (event, ...args) {
|
onGlobalHotkey (): void {
|
||||||
|
if (this.windows.some(x => x.isFocused())) {
|
||||||
|
for (let window of this.windows) {
|
||||||
|
window.hide()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let window of this.windows) {
|
||||||
|
window.present()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
presentAllWindows (): void {
|
||||||
for (let window of this.windows) {
|
for (let window of this.windows) {
|
||||||
|
window.present()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcast (event: string, ...args): void {
|
||||||
|
for (const window of this.windows) {
|
||||||
window.send(event, ...args)
|
window.send(event, ...args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async send (event, ...args) {
|
async send (event: string, ...args): Promise<void> {
|
||||||
if (!this.hasWindows()) {
|
if (!this.hasWindows()) {
|
||||||
await this.newWindow()
|
await this.newWindow()
|
||||||
}
|
}
|
||||||
this.windows[0].send(event, ...args)
|
this.windows.filter(w => !w.isDestroyed())[0].send(event, ...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
enableTray () {
|
enableTray (): void {
|
||||||
if (this.tray) {
|
if (this.tray) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -68,7 +106,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', () => this.focus())
|
this.tray.on('click', () => setTimeout(() => this.focus()))
|
||||||
|
|
||||||
const contextMenu = Menu.buildFromTemplate([{
|
const contextMenu = Menu.buildFromTemplate([{
|
||||||
label: 'Show',
|
label: 'Show',
|
||||||
@@ -82,21 +120,27 @@ export class Application {
|
|||||||
this.tray.setToolTip(`Terminus ${app.getVersion()}`)
|
this.tray.setToolTip(`Terminus ${app.getVersion()}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
disableTray () {
|
disableTray (): void {
|
||||||
if (this.tray) {
|
if (this.tray) {
|
||||||
this.tray.destroy()
|
this.tray.destroy()
|
||||||
this.tray = null
|
this.tray = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hasWindows () {
|
hasWindows (): boolean {
|
||||||
return !!this.windows.length
|
return !!this.windows.length
|
||||||
}
|
}
|
||||||
|
|
||||||
focus () {
|
focus (): void {
|
||||||
for (let window of this.windows) {
|
for (let window of this.windows) {
|
||||||
window.show()
|
window.show()
|
||||||
window.focus()
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSecondInstance (argv: string[], cwd: string): void {
|
||||||
|
this.presentAllWindows()
|
||||||
|
for (let window of this.windows) {
|
||||||
|
window.handleSecondInstance(argv, cwd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +165,7 @@ export class Application {
|
|||||||
{ role: 'services', submenu: [] },
|
{ role: 'services', submenu: [] },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ role: 'hide' },
|
{ role: 'hide' },
|
||||||
{ role: 'hideothers' },
|
{ role: 'hideOthers' },
|
||||||
{ role: 'unhide' },
|
{ role: 'unhide' },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{
|
{
|
||||||
@@ -142,21 +186,21 @@ export class Application {
|
|||||||
{ role: 'cut' },
|
{ role: 'cut' },
|
||||||
{ role: 'copy' },
|
{ role: 'copy' },
|
||||||
{ role: 'paste' },
|
{ role: 'paste' },
|
||||||
{ role: 'pasteandmatchstyle' },
|
{ role: 'pasteAndMatchStyle' },
|
||||||
{ role: 'delete' },
|
{ role: 'delete' },
|
||||||
{ role: 'selectall' },
|
{ role: 'selectAll' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'View',
|
label: 'View',
|
||||||
submenu: [
|
submenu: [
|
||||||
{ role: 'reload' },
|
{ role: 'reload' },
|
||||||
{ role: 'forcereload' },
|
{ role: 'forceReload' },
|
||||||
{ role: 'toggledevtools' },
|
{ role: 'toggleDevTools' },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ role: 'resetzoom' },
|
{ role: 'resetZoom' },
|
||||||
{ role: 'zoomin' },
|
{ role: 'zoomIn' },
|
||||||
{ role: 'zoomout' },
|
{ role: 'zoomOut' },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ role: 'togglefullscreen' },
|
{ role: 'togglefullscreen' },
|
||||||
],
|
],
|
||||||
@@ -180,7 +224,7 @@ export class Application {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
|
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
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)
|
||||||
}
|
}
|
||||||
@@ -20,27 +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')
|
||||||
.strict()
|
|
||||||
.parse(argv.slice(1))
|
.parse(argv.slice(1))
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
|
import './portable'
|
||||||
|
import './sentry'
|
||||||
import './lru'
|
import './lru'
|
||||||
import { app, ipcMain, Menu } from 'electron'
|
import { app, ipcMain, Menu } from 'electron'
|
||||||
import electronDebug = require('electron-debug')
|
|
||||||
import { parseArgs } from './cli'
|
import { parseArgs } from './cli'
|
||||||
import { Application } from './app'
|
import { Application } from './app'
|
||||||
if (process.platform === 'win32' && require('electron-squirrel-startup')) process.exit(0)
|
import electronDebug = require('electron-debug')
|
||||||
|
|
||||||
if (!process.env.TERMINUS_PLUGINS) {
|
if (!process.env.TERMINUS_PLUGINS) {
|
||||||
process.env.TERMINUS_PLUGINS = ''
|
process.env.TERMINUS_PLUGINS = ''
|
||||||
@@ -33,18 +34,22 @@ 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())
|
||||||
|
|
||||||
if (!app.requestSingleInstanceLock()) {
|
if (!app.requestSingleInstanceLock()) {
|
||||||
app.quit()
|
app.quit()
|
||||||
process.exit(0)
|
app.exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argv.d) {
|
if (argv.d) {
|
||||||
electronDebug({ enabled: true, showDevTools: 'undocked' })
|
electronDebug({
|
||||||
|
isEnabled: true,
|
||||||
|
showDevTools: true,
|
||||||
|
devToolsMode: 'undocked',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
app.on('ready', () => {
|
app.on('ready', () => {
|
||||||
@@ -54,8 +59,8 @@ app.on('ready', () => {
|
|||||||
label: 'New window',
|
label: 'New window',
|
||||||
click () {
|
click () {
|
||||||
this.app.newWindow()
|
this.app.newWindow()
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
application.init()
|
application.init()
|
||||||
|
@@ -1,13 +1,15 @@
|
|||||||
let lru = require('lru-cache')({ max: 256, maxAge: 250 })
|
import * as createLRU from 'lru-cache'
|
||||||
|
import * as fs from 'fs'
|
||||||
let fs = require('fs')
|
const lru = createLRU({ 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)
|
||||||
|
24
app/lib/portable.ts
Executable file
@@ -0,0 +1,24 @@
|
|||||||
|
import * as path from 'path'
|
||||||
|
import * as fs from 'fs'
|
||||||
|
|
||||||
|
let appPath: string | null = null
|
||||||
|
try {
|
||||||
|
appPath = path.dirname(require('electron').app.getPath('exe'))
|
||||||
|
} catch {
|
||||||
|
appPath = path.dirname(require('electron').remote.app.getPath('exe'))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null != appPath) {
|
||||||
|
if(fs.existsSync(path.join(appPath, 'terminus-data'))) {
|
||||||
|
fs.renameSync(path.join(appPath, 'terminus-data'), path.join(appPath, 'data'))
|
||||||
|
}
|
||||||
|
const portableData = path.join(appPath, 'data')
|
||||||
|
if (fs.existsSync(portableData)) {
|
||||||
|
console.log('reset user data to ' + portableData)
|
||||||
|
try {
|
||||||
|
require('electron').app.setPath('userData', portableData)
|
||||||
|
} catch {
|
||||||
|
require('electron').remote.app.setPath('userData', portableData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
app/lib/sentry.ts
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
const { init } = process.type === 'main' ? require('@sentry/electron/dist/main') : require('@sentry/electron/dist/renderer')
|
||||||
|
import * as isDev from 'electron-is-dev'
|
||||||
|
|
||||||
|
|
||||||
|
const SENTRY_DSN = 'https://4717a0a7ee0b4429bd3a0f06c3d7eec3@sentry.io/181876'
|
||||||
|
let release
|
||||||
|
try {
|
||||||
|
release = require('electron').app.getVersion()
|
||||||
|
} catch {
|
||||||
|
release = require('electron').remote.app.getVersion()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDev) {
|
||||||
|
init({
|
||||||
|
dsn: SENTRY_DSN,
|
||||||
|
release,
|
||||||
|
integrations (integrations) {
|
||||||
|
return integrations.filter(integration => integration.name !== 'Breadcrumbs')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
@@ -1,9 +1,11 @@
|
|||||||
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 } from 'electron'
|
import { BrowserWindow, app, ipcMain, Rectangle, Menu, screen } 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 { parseArgs } from './cli'
|
||||||
import { loadConfig } from './config'
|
import { loadConfig } from './config'
|
||||||
|
|
||||||
let SetWindowCompositionAttribute: any
|
let SetWindowCompositionAttribute: any
|
||||||
@@ -22,15 +24,20 @@ export interface WindowOptions {
|
|||||||
export class Window {
|
export class Window {
|
||||||
ready: Promise<void>
|
ready: Promise<void>
|
||||||
private visible = new Subject<boolean>()
|
private visible = new Subject<boolean>()
|
||||||
|
private closed = new Subject<void>()
|
||||||
private window: BrowserWindow
|
private window: BrowserWindow
|
||||||
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 || {}
|
||||||
|
|
||||||
@@ -46,14 +53,28 @@ export class Window {
|
|||||||
minHeight: 300,
|
minHeight: 300,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: true,
|
nodeIntegration: true,
|
||||||
|
preload: path.join(__dirname, 'sentry.js'),
|
||||||
|
backgroundThrottling: false,
|
||||||
},
|
},
|
||||||
frame: false,
|
frame: false,
|
||||||
show: false,
|
show: false,
|
||||||
backgroundColor: '#00000000'
|
backgroundColor: '#00000000',
|
||||||
}
|
}
|
||||||
Object.assign(bwOptions, this.windowBounds)
|
|
||||||
|
|
||||||
if ((configData.appearance || {}).frame === 'native') {
|
if (this.windowBounds) {
|
||||||
|
Object.assign(bwOptions, this.windowBounds)
|
||||||
|
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 [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) {
|
||||||
|
bwOptions.x = closestDisplay.bounds.width / 2 - bwOptions.width / 2
|
||||||
|
bwOptions.y = closestDisplay.bounds.height / 2 - bwOptions.height / 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((this.configStore.appearance || {}).frame === 'native') {
|
||||||
bwOptions.frame = true
|
bwOptions.frame = true
|
||||||
} else {
|
} else {
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
@@ -68,8 +89,8 @@ export class Window {
|
|||||||
this.window = new 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('window')
|
||||||
} else if (process.platform === 'win32' && (configData.appearance || {}).vibrancy) {
|
} else if (process.platform === 'win32' && (this.configStore.appearance || {}).vibrancy) {
|
||||||
this.setVibrancy(true)
|
this.setVibrancy(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,8 +101,16 @@ 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()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
this.window.loadURL(`file://${app.getAppPath()}/dist/index.html?${this.window.id}`, { extraHeaders: 'pragma: no-cache\n' })
|
this.window.loadURL(`file://${app.getAppPath()}/dist/index.html?${this.window.id}`, { extraHeaders: 'pragma: no-cache\n' })
|
||||||
|
|
||||||
if (process.platform !== 'darwin') {
|
if (process.platform !== 'darwin') {
|
||||||
@@ -93,7 +122,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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,12 +130,13 @@ export class Window {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setVibrancy (enabled: boolean, type?: string) {
|
setVibrancy (enabled: boolean, type?: string): void {
|
||||||
|
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
|
let attribValue = AccentState.ACCENT_DISABLED
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
if (parseInt(os.release().split('.')[2]) >= 17063 && type === 'fluent') {
|
if (type === 'fluent') {
|
||||||
attribValue = AccentState.ACCENT_ENABLE_ACRYLICBLURBEHIND
|
attribValue = AccentState.ACCENT_ENABLE_ACRYLICBLURBEHIND
|
||||||
} else {
|
} else {
|
||||||
attribValue = AccentState.ACCENT_ENABLE_BLURBEHIND
|
attribValue = AccentState.ACCENT_ENABLE_BLURBEHIND
|
||||||
@@ -116,28 +146,83 @@ export class Window {
|
|||||||
} else {
|
} else {
|
||||||
DwmEnableBlurBehindWindow(this.window, enabled)
|
DwmEnableBlurBehindWindow(this.window, enabled)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this.window.setVibrancy(enabled ? 'dark' : null as any) // electron issue 20269
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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): 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 (): boolean {
|
||||||
|
return !this.window || this.window.isDestroyed()
|
||||||
|
}
|
||||||
|
|
||||||
|
isFocused (): boolean {
|
||||||
|
return this.window.isFocused()
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSecondInstance (argv: string[], cwd: string): void {
|
||||||
|
if (!this.configStore.appearance?.dock) {
|
||||||
|
this.send('host:second-instance', parseArgs(argv, cwd), cwd)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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', () => {
|
||||||
@@ -147,20 +232,20 @@ export class Window {
|
|||||||
let moveSubscription = new Observable<void>(observer => {
|
let 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)
|
||||||
@@ -183,6 +268,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
|
||||||
@@ -270,10 +359,35 @@ 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
|
||||||
|
})
|
||||||
|
|
||||||
|
this.window.on('will-move', () => {
|
||||||
|
if (!this.lastVibrancy?.enabled || !this.disableVibrancyWhileDragging) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let timeout: number|null = null
|
||||||
|
const oldVibrancy = this.lastVibrancy
|
||||||
|
this.setVibrancy(false)
|
||||||
|
const onMove = () => {
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
}
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
this.window.off('move', onMove)
|
||||||
|
this.setVibrancy(oldVibrancy.enabled, oldVibrancy.type)
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
this.window.on('move', onMove)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private destroy () {
|
private destroy () {
|
||||||
this.window = null
|
this.window = null
|
||||||
|
this.closed.next()
|
||||||
this.visible.complete()
|
this.visible.complete()
|
||||||
|
this.closed.complete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -13,32 +13,43 @@
|
|||||||
"watch": "webpack --progress --color --watch"
|
"watch": "webpack --progress --color --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "7.2.8",
|
"@angular/animations": "9.1.9",
|
||||||
"@angular/common": "7.2.8",
|
"@angular/common": "9.1.9",
|
||||||
"@angular/compiler": "7.2.8",
|
"@angular/compiler": "9.1.9",
|
||||||
"@angular/core": "7.2.8",
|
"@angular/core": "9.1.9",
|
||||||
"@angular/forms": "7.2.8",
|
"@angular/forms": "9.1.9",
|
||||||
"@angular/platform-browser": "7.2.8",
|
"@angular/platform-browser": "9.1.9",
|
||||||
"@angular/platform-browser-dynamic": "7.2.8",
|
"@angular/platform-browser-dynamic": "9.1.9",
|
||||||
"@ng-bootstrap/ng-bootstrap": "^3.3.1",
|
"@ng-bootstrap/ng-bootstrap": "^6.1.0",
|
||||||
"devtron": "1.4.0",
|
"devtron": "1.4.0",
|
||||||
"electron-config": "0.2.1",
|
"electron-config": "2.0.0",
|
||||||
"electron-debug": "^2.0.0",
|
"electron-debug": "^3.0.1",
|
||||||
"electron-is-dev": "0.1.2",
|
"electron-is-dev": "1.1.0",
|
||||||
"electron-squirrel-startup": "^1.0.0",
|
"electron-updater": "^4.3.1",
|
||||||
"js-yaml": "3.8.2",
|
"fontmanager-redux": "0.4.0",
|
||||||
"mz": "^2.6.0",
|
"js-yaml": "3.14.0",
|
||||||
"ngx-toastr": "^9.1.1",
|
"keytar": "^5.6.0",
|
||||||
|
"mz": "^2.7.0",
|
||||||
|
"ngx-toastr": "^12.0.1",
|
||||||
|
"node-pty": "^0.10.0-beta9",
|
||||||
|
"npm": "6.9.0",
|
||||||
"path": "0.12.7",
|
"path": "0.12.7",
|
||||||
"rxjs": "^6.3.3",
|
"rxjs": "^6.5.5",
|
||||||
"yargs": "^12.0.1",
|
"rxjs-compat": "^6.5.5",
|
||||||
"zone.js": "^0.8.26"
|
"yargs": "^15.3.1",
|
||||||
|
"zone.js": "^0.10.3"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"windows-blurbehind": "^1.0.0",
|
"macos-native-processlist": "^1.0.2",
|
||||||
"windows-swca": "^2.0.1"
|
"serialport": "^9.0.0",
|
||||||
|
"windows-blurbehind": "^1.0.1",
|
||||||
|
"windows-native-registry": "^1.0.17",
|
||||||
|
"windows-process-tree": "^0.2.4",
|
||||||
|
"windows-swca": "^2.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/mz": "0.0.31"
|
"@types/mz": "0.0.32",
|
||||||
|
"@types/node": "12.7.12",
|
||||||
|
"node-abi": "^2.17.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,21 +1,23 @@
|
|||||||
|
/* 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'
|
||||||
import { ToastrModule } from 'ngx-toastr'
|
import { ToastrModule } from 'ngx-toastr'
|
||||||
|
|
||||||
export function getRootModule (plugins: any[]) {
|
export function getRootModule (plugins: any[]) {
|
||||||
let imports = [
|
const imports = [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
...plugins,
|
...plugins,
|
||||||
NgbModule.forRoot(),
|
NgbModule,
|
||||||
ToastrModule.forRoot({
|
ToastrModule.forRoot({
|
||||||
positionClass: 'toast-bottom-center',
|
positionClass: 'toast-bottom-center',
|
||||||
|
toastClass: 'toast',
|
||||||
preventDuplicates: true,
|
preventDuplicates: true,
|
||||||
extendedTimeOut: 5000,
|
extendedTimeOut: 5000,
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
let bootstrap = [
|
const bootstrap = [
|
||||||
...(plugins.filter(x => x.bootstrap).map(x => x.bootstrap)),
|
...plugins.filter(x => x.bootstrap).map(x => x.bootstrap),
|
||||||
]
|
]
|
||||||
|
|
||||||
if (bootstrap.length === 0) {
|
if (bootstrap.length === 0) {
|
||||||
@@ -25,7 +27,7 @@ export function getRootModule (plugins: any[]) {
|
|||||||
@NgModule({
|
@NgModule({
|
||||||
imports,
|
imports,
|
||||||
bootstrap,
|
bootstrap,
|
||||||
}) class RootModule { }
|
}) class RootModule { } // eslint-disable-line @typescript-eslint/no-extraneous-class
|
||||||
|
|
||||||
return RootModule
|
return RootModule
|
||||||
}
|
}
|
||||||
|
@@ -1,38 +1,8 @@
|
|||||||
import '../lib/lru'
|
import '../lib/lru'
|
||||||
import 'source-sans-pro'
|
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/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) => {
|
|
||||||
let splitArray = filename.split('/')
|
|
||||||
return splitArray[splitArray.length - 1]
|
|
||||||
}
|
|
||||||
|
|
||||||
data.exception.values[0].stacktrace.frames.forEach(frame => {
|
|
||||||
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)
|
|
||||||
})
|
|
||||||
|
@@ -1,42 +1,51 @@
|
|||||||
import 'zone.js'
|
import 'zone.js'
|
||||||
import 'core-js/es7/reflect'
|
import 'core-js/proposals/reflect-metadata'
|
||||||
import 'core-js/core/delay'
|
|
||||||
import 'rxjs'
|
import 'rxjs'
|
||||||
|
|
||||||
|
import * as isDev from 'electron-is-dev'
|
||||||
|
|
||||||
import './global.scss'
|
import './global.scss'
|
||||||
import './toastr.scss'
|
import './toastr.scss'
|
||||||
|
|
||||||
// Always land on the start view
|
import { enableProdMode, NgModuleRef, ApplicationRef } from '@angular/core'
|
||||||
location.hash = ''
|
import { enableDebugTools } from '@angular/platform-browser'
|
||||||
|
|
||||||
import { enableProdMode, NgModuleRef } from '@angular/core'
|
|
||||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
|
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
|
||||||
|
|
||||||
import { getRootModule } from './app.module'
|
import { getRootModule } from './app.module'
|
||||||
import { findPlugins, loadPlugins, IPluginInfo } from './plugins'
|
import { findPlugins, loadPlugins, PluginInfo } from './plugins'
|
||||||
|
|
||||||
|
// Always land on the start view
|
||||||
|
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 (require('electron-is-dev')) {
|
if (isDev) {
|
||||||
console.warn('Running in debug mode')
|
console.warn('Running in debug mode')
|
||||||
} else {
|
} else {
|
||||||
enableProdMode()
|
enableProdMode()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function bootstrap (plugins: IPluginInfo[], safeMode = false): Promise<NgModuleRef<any>> {
|
async function bootstrap (plugins: PluginInfo[], safeMode = false): Promise<NgModuleRef<any>> {
|
||||||
if (safeMode) {
|
if (safeMode) {
|
||||||
plugins = plugins.filter(x => x.isBuiltin)
|
plugins = plugins.filter(x => x.isBuiltin)
|
||||||
}
|
}
|
||||||
let pluginsModules = await loadPlugins(plugins, (current, total) => {
|
const pluginsModules = await loadPlugins(plugins, (current, total) => {
|
||||||
(document.querySelector('.progress .bar') as HTMLElement).style.width = 100 * current / total + '%'
|
(document.querySelector('.progress .bar') as HTMLElement).style.width = `${100 * current / total}%` // eslint-disable-line
|
||||||
})
|
})
|
||||||
let module = getRootModule(pluginsModules)
|
const module = getRootModule(pluginsModules)
|
||||||
window['rootModule'] = module
|
window['rootModule'] = module
|
||||||
return platformBrowserDynamic().bootstrapModule(module)
|
return platformBrowserDynamic().bootstrapModule(module).then(moduleRef => {
|
||||||
|
if (isDev) {
|
||||||
|
const applicationRef = moduleRef.injector.get(ApplicationRef)
|
||||||
|
const componentRef = applicationRef.components[0]
|
||||||
|
enableDebugTools(componentRef)
|
||||||
|
}
|
||||||
|
return moduleRef
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
findPlugins().then(async plugins => {
|
findPlugins().then(async plugins => {
|
||||||
|
@@ -16,6 +16,12 @@ body {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
& > svg {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.form-line {
|
.form-line {
|
||||||
display: flex;
|
display: flex;
|
||||||
border-top: 1px solid rgba(0, 0, 0, 0.2);
|
border-top: 1px solid rgba(0, 0, 0, 0.2);
|
||||||
|
@@ -1,10 +1,8 @@
|
|||||||
import * as fs from 'mz/fs'
|
import * as fs from 'mz/fs'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
const nodeModule = require('module')
|
const nodeModule = require('module') // eslint-disable-line @typescript-eslint/no-var-requires
|
||||||
const nodeRequire = (global as any).require
|
const nodeRequire = (global as any).require
|
||||||
|
|
||||||
declare function delay (ms: number): Promise<void>
|
|
||||||
|
|
||||||
function normalizePath (path: string): string {
|
function normalizePath (path: string): string {
|
||||||
const cygwinPrefix = '/cygdrive/'
|
const cygwinPrefix = '/cygdrive/'
|
||||||
if (path.startsWith(cygwinPrefix)) {
|
if (path.startsWith(cygwinPrefix)) {
|
||||||
@@ -14,7 +12,7 @@ function normalizePath (path: string): string {
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeRequire.main.paths.map(x => 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(require('electron').remote.app.getAppPath()))
|
||||||
@@ -23,11 +21,14 @@ if (process.env.TERMINUS_DEV) {
|
|||||||
const builtinPluginsPath = process.env.TERMINUS_DEV ? path.dirname(require('electron').remote.app.getAppPath()) : path.join((process as any).resourcesPath, 'builtin-plugins')
|
const builtinPluginsPath = process.env.TERMINUS_DEV ? path.dirname(require('electron').remote.app.getAppPath()) : path.join((process as any).resourcesPath, 'builtin-plugins')
|
||||||
|
|
||||||
const userPluginsPath = path.join(
|
const userPluginsPath = path.join(
|
||||||
require('electron').remote.app.getPath('appData'),
|
require('electron').remote.app.getPath('userData'),
|
||||||
'terminus',
|
|
||||||
'plugins',
|
'plugins',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (!fs.existsSync(userPluginsPath)) {
|
||||||
|
fs.mkdir(userPluginsPath)
|
||||||
|
}
|
||||||
|
|
||||||
Object.assign(window, { builtinPluginsPath, userPluginsPath })
|
Object.assign(window, { builtinPluginsPath, userPluginsPath })
|
||||||
nodeModule.globalPaths.unshift(builtinPluginsPath)
|
nodeModule.globalPaths.unshift(builtinPluginsPath)
|
||||||
nodeModule.globalPaths.unshift(path.join(userPluginsPath, 'node_modules'))
|
nodeModule.globalPaths.unshift(path.join(userPluginsPath, 'node_modules'))
|
||||||
@@ -36,9 +37,9 @@ if (process.env.TERMINUS_PLUGINS) {
|
|||||||
process.env.TERMINUS_PLUGINS.split(':').map(x => nodeModule.globalPaths.push(normalizePath(x)))
|
process.env.TERMINUS_PLUGINS.split(':').map(x => nodeModule.globalPaths.push(normalizePath(x)))
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare type ProgressCallback = (current, total) => void
|
export type ProgressCallback = (current: number, total: number) => void // eslint-disable-line @typescript-eslint/no-type-alias
|
||||||
|
|
||||||
export interface IPluginInfo {
|
export interface PluginInfo {
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
packageName: string
|
packageName: string
|
||||||
@@ -62,6 +63,7 @@ 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',
|
||||||
@@ -70,47 +72,61 @@ const builtinModules = [
|
|||||||
|
|
||||||
const cachedBuiltinModules = {}
|
const cachedBuiltinModules = {}
|
||||||
builtinModules.forEach(m => {
|
builtinModules.forEach(m => {
|
||||||
|
const label = 'Caching ' + m
|
||||||
|
console.time(label)
|
||||||
cachedBuiltinModules[m] = nodeRequire(m)
|
cachedBuiltinModules[m] = nodeRequire(m)
|
||||||
|
console.timeEnd(label)
|
||||||
})
|
})
|
||||||
|
|
||||||
const originalRequire = nodeRequire('module').prototype.require
|
const originalRequire = (global as any).require
|
||||||
nodeRequire('module').prototype.require = function (query) {
|
;(global as any).require = function (query: string) {
|
||||||
if (cachedBuiltinModules[query]) {
|
if (cachedBuiltinModules[query]) {
|
||||||
return cachedBuiltinModules[query]
|
return cachedBuiltinModules[query]
|
||||||
}
|
}
|
||||||
return originalRequire.apply(this, arguments)
|
return originalRequire.apply(this, arguments)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function findPlugins (): Promise<IPluginInfo[]> {
|
const originalModuleRequire = nodeModule.prototype.require
|
||||||
let paths = nodeModule.globalPaths
|
nodeModule.prototype.require = function (query: string) {
|
||||||
let foundPlugins: IPluginInfo[] = []
|
if (cachedBuiltinModules[query]) {
|
||||||
let candidateLocations: { pluginDir: string, packageName: string }[] = []
|
return cachedBuiltinModules[query]
|
||||||
|
}
|
||||||
|
return originalModuleRequire.call(this, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function findPlugins (): Promise<PluginInfo[]> {
|
||||||
|
const paths = nodeModule.globalPaths
|
||||||
|
let foundPlugins: PluginInfo[] = []
|
||||||
|
const candidateLocations: { pluginDir: string, packageName: string }[] = []
|
||||||
|
const PREFIX = 'terminus-'
|
||||||
|
|
||||||
for (let pluginDir of paths) {
|
for (let pluginDir of paths) {
|
||||||
pluginDir = normalizePath(pluginDir)
|
pluginDir = normalizePath(pluginDir)
|
||||||
if (!await fs.exists(pluginDir)) {
|
if (!await fs.exists(pluginDir)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
let pluginNames = await fs.readdir(pluginDir)
|
const pluginNames = await fs.readdir(pluginDir)
|
||||||
if (await fs.exists(path.join(pluginDir, 'package.json'))) {
|
if (await fs.exists(path.join(pluginDir, 'package.json'))) {
|
||||||
candidateLocations.push({
|
candidateLocations.push({
|
||||||
pluginDir: path.dirname(pluginDir),
|
pluginDir: path.dirname(pluginDir),
|
||||||
packageName: path.basename(pluginDir)
|
packageName: path.basename(pluginDir),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
for (let packageName of pluginNames) {
|
for (const packageName of pluginNames) {
|
||||||
candidateLocations.push({ pluginDir, packageName })
|
if (packageName.startsWith(PREFIX)) {
|
||||||
|
candidateLocations.push({ pluginDir, packageName })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let { pluginDir, packageName } of candidateLocations) {
|
for (const { pluginDir, packageName } of candidateLocations) {
|
||||||
let pluginPath = path.join(pluginDir, packageName)
|
const pluginPath = path.join(pluginDir, packageName)
|
||||||
let infoPath = path.join(pluginPath, 'package.json')
|
const infoPath = path.join(pluginPath, 'package.json')
|
||||||
if (!await fs.exists(infoPath)) {
|
if (!await fs.exists(infoPath)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
let name = packageName.substring('terminus-'.length)
|
const name = packageName.substring(PREFIX.length)
|
||||||
|
|
||||||
if (foundPlugins.some(x => x.name === name)) {
|
if (foundPlugins.some(x => x.name === name)) {
|
||||||
console.info(`Plugin ${packageName} already exists, overriding`)
|
console.info(`Plugin ${packageName} already exists, overriding`)
|
||||||
@@ -118,7 +134,7 @@ export async function findPlugins (): Promise<IPluginInfo[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let info = JSON.parse(await fs.readFile(infoPath, { encoding: 'utf-8' }))
|
const info = JSON.parse(await fs.readFile(infoPath, { encoding: 'utf-8' }))
|
||||||
if (!info.keywords || !(info.keywords.includes('terminus-plugin') || info.keywords.includes('terminus-builtin-plugin'))) {
|
if (!info.keywords || !(info.keywords.includes('terminus-plugin') || info.keywords.includes('terminus-builtin-plugin'))) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -139,27 +155,32 @@ export async function findPlugins (): Promise<IPluginInfo[]> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(window as any).installedPlugins = foundPlugins
|
foundPlugins.sort((a, b) => a.name > b.name ? 1 : -1)
|
||||||
|
|
||||||
|
;(window as any).installedPlugins = foundPlugins
|
||||||
return foundPlugins
|
return foundPlugins
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadPlugins (foundPlugins: IPluginInfo[], progress: ProgressCallback): Promise<any[]> {
|
export async function loadPlugins (foundPlugins: PluginInfo[], progress: ProgressCallback): Promise<any[]> {
|
||||||
let plugins: any[] = []
|
const plugins: any[] = []
|
||||||
progress(0, 1)
|
progress(0, 1)
|
||||||
let index = 0
|
let index = 0
|
||||||
for (let foundPlugin of foundPlugins) {
|
for (const foundPlugin of foundPlugins) {
|
||||||
console.info(`Loading ${foundPlugin.name}: ${nodeRequire.resolve(foundPlugin.path)}`)
|
console.info(`Loading ${foundPlugin.name}: ${nodeRequire.resolve(foundPlugin.path)}`)
|
||||||
progress(index, foundPlugins.length)
|
progress(index, foundPlugins.length)
|
||||||
try {
|
try {
|
||||||
let packageModule = nodeRequire(foundPlugin.path)
|
const label = 'Loading ' + foundPlugin.name
|
||||||
let pluginModule = packageModule.default.forRoot ? packageModule.default.forRoot() : packageModule.default
|
console.time(label)
|
||||||
|
const packageModule = nodeRequire(foundPlugin.path)
|
||||||
|
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)
|
||||||
|
await new Promise(x => setTimeout(x, 50))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Could not load ${foundPlugin.name}:`, error)
|
console.error(`Could not load ${foundPlugin.name}:`, error)
|
||||||
}
|
}
|
||||||
await delay(1)
|
|
||||||
index++
|
index++
|
||||||
}
|
}
|
||||||
progress(1, 1)
|
progress(1, 1)
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
template: '<app-root></app-root>'
|
template: '<app-root></app-root>',
|
||||||
})
|
})
|
||||||
export class RootComponent { }
|
export class RootComponent { } // eslint-disable-line @typescript-eslint/no-extraneous-class
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
.toast {
|
.toast {
|
||||||
box-shadow: 0 1px 0 rgba(0,0,0,.25);
|
box-shadow: 0 1px 0 rgba(0,0,0,.25);
|
||||||
@@ -9,6 +10,10 @@
|
|||||||
background-image: none;
|
background-image: none;
|
||||||
width: auto;
|
width: auto;
|
||||||
|
|
||||||
|
&.toast-error {
|
||||||
|
background-color: #BD362F;
|
||||||
|
}
|
||||||
|
|
||||||
&.toast-info {
|
&.toast-info {
|
||||||
background-color: #555;
|
background-color: #555;
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,8 @@ module.exports = {
|
|||||||
name: 'terminus',
|
name: 'terminus',
|
||||||
target: 'node',
|
target: 'node',
|
||||||
entry: {
|
entry: {
|
||||||
'index.ignore': 'file-loader?name=index.html!val-loader!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'),
|
||||||
},
|
},
|
||||||
@@ -78,5 +79,8 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.optimize.ModuleConcatenationPlugin(),
|
new webpack.optimize.ModuleConcatenationPlugin(),
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
'process.type': '"renderer"'
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
@@ -36,7 +36,6 @@ module.exports = {
|
|||||||
electron: 'commonjs electron',
|
electron: 'commonjs electron',
|
||||||
'electron-config': 'commonjs electron-config',
|
'electron-config': 'commonjs electron-config',
|
||||||
'electron-vibrancy': 'commonjs electron-vibrancy',
|
'electron-vibrancy': 'commonjs electron-vibrancy',
|
||||||
'electron-squirrel-startup': 'commonjs electron-squirrel-startup',
|
|
||||||
fs: 'commonjs fs',
|
fs: 'commonjs fs',
|
||||||
mz: 'commonjs mz',
|
mz: 'commonjs mz',
|
||||||
path: 'commonjs path',
|
path: 'commonjs path',
|
||||||
@@ -46,5 +45,8 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.optimize.ModuleConcatenationPlugin(),
|
new webpack.optimize.ModuleConcatenationPlugin(),
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
'process.type': '"main"',
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
3332
app/yarn.lock
12
appveyor.yml
@@ -6,10 +6,6 @@ platform:
|
|||||||
environment:
|
environment:
|
||||||
nodejs_version: "10"
|
nodejs_version: "10"
|
||||||
|
|
||||||
cache:
|
|
||||||
- "%USERPROFILE%\\.electron"
|
|
||||||
- "%LOCALAPPDATA%\\Yarn"
|
|
||||||
|
|
||||||
version: "{build}"
|
version: "{build}"
|
||||||
|
|
||||||
install:
|
install:
|
||||||
@@ -23,6 +19,10 @@ build_script:
|
|||||||
- node scripts/build-windows.js
|
- node scripts/build-windows.js
|
||||||
|
|
||||||
artifacts:
|
artifacts:
|
||||||
- path: 'dist\win\*.exe'
|
|
||||||
- path: 'dist\squirrel-windows\*.exe'
|
|
||||||
- path: 'dist\*.exe'
|
- path: 'dist\*.exe'
|
||||||
|
|
||||||
|
cache:
|
||||||
|
- node_modules
|
||||||
|
- "*\\node_modules"
|
||||||
|
- "%USERPROFILE%\\.electron"
|
||||||
|
- "%LOCALAPPDATA%\\Yarn"
|
||||||
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 8.9 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 950 B |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 3.9 KiB |
3
build/installer.nsh
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
!macro customInit
|
||||||
|
nsExec::Exec '"$LOCALAPPDATA\terminus\Update.exe" --uninstall -s'
|
||||||
|
!macroend
|
@@ -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
@@ -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,
|
||||||
|
})
|
||||||
|
}
|
35
build/mac/afterSignHook.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// See: https://medium.com/@TwitterArchiveEraser/notarize-electron-apps-7a5f988406db
|
||||||
|
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
const notarizer = require('electron-notarize')
|
||||||
|
|
||||||
|
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('afterSign hook triggered', params)
|
||||||
|
|
||||||
|
let appId = 'org.terminus'
|
||||||
|
|
||||||
|
let appPath = path.join(params.appOutDir, params._pathOverride || `${params.packager.appInfo.productFilename}.app`)
|
||||||
|
if (!fs.existsSync(appPath)) {
|
||||||
|
throw new Error(`Cannot find application at: ${appPath}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Notarizing ${appId} found at ${appPath}`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await notarizer.notarize({
|
||||||
|
appBundleId: appId,
|
||||||
|
appPath: appPath,
|
||||||
|
appleId: process.env.APPSTORE_USERNAME,
|
||||||
|
appleIdPassword: process.env.APPSTORE_PASSWORD,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Done notarizing ${appId}`)
|
||||||
|
}
|
14
build/mac/entitlements.plist
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.cs.allow-jit</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.disable-library-validation</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
62
electron-builder.yml
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
---
|
||||||
|
appId: org.terminus
|
||||||
|
productName: Terminus
|
||||||
|
compression: normal
|
||||||
|
afterSign: "./build/mac/afterSignHook.js"
|
||||||
|
afterAllArtifactBuild: "./build/mac/afterBuildHook.js"
|
||||||
|
files:
|
||||||
|
- "**/*"
|
||||||
|
- dist
|
||||||
|
extraResources:
|
||||||
|
- builtin-plugins
|
||||||
|
- extras
|
||||||
|
publish:
|
||||||
|
- provider: github
|
||||||
|
|
||||||
|
win:
|
||||||
|
icon: "./build/windows/icon.ico"
|
||||||
|
artifactName: terminus-${version}-portable.${ext}
|
||||||
|
rfc3161TimeStampServer: http://sha256timestamp.ws.symantec.com/sha256/timestamp
|
||||||
|
nsis:
|
||||||
|
oneClick: false
|
||||||
|
artifactName: terminus-${version}-setup.${ext}
|
||||||
|
installerIcon: "./build/windows/icon.ico"
|
||||||
|
|
||||||
|
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}
|
||||||
|
executableArgs:
|
||||||
|
- "--no-sandbox"
|
||||||
|
snap:
|
||||||
|
plugs:
|
||||||
|
- default
|
||||||
|
- system-files
|
||||||
|
- system-observe
|
||||||
|
deb:
|
||||||
|
depends:
|
||||||
|
- gconf2
|
||||||
|
- gconf-service
|
||||||
|
- gnome-keyring
|
||||||
|
- libnotify4
|
||||||
|
- libsecret-1-0
|
||||||
|
- libappindicator1
|
||||||
|
- libxtst6
|
||||||
|
- libnss3
|
||||||
|
afterInstall: build/linux/after-install.tpl
|
||||||
|
rpm:
|
||||||
|
depends:
|
||||||
|
- screen
|
||||||
|
- gnome-keyring
|
BIN
extras/UAC.exe
BIN
extras/ssh-keygen/libcrypto.dll
Normal file
BIN
extras/ssh-keygen/ssh-keygen.exe
Normal file
180
package.json
@@ -1,139 +1,73 @@
|
|||||||
{
|
{
|
||||||
"name": "term",
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^5.6.3",
|
"@fortawesome/fontawesome-free": "^5.13.0",
|
||||||
"@types/electron-config": "^0.2.1",
|
"@sentry/cli": "^1.52.3",
|
||||||
"@types/electron-debug": "^1.1.0",
|
"@sentry/electron": "^1.2.1",
|
||||||
"@types/fs-promise": "1.0.1",
|
"@types/electron-config": "^3.2.2",
|
||||||
"@types/js-yaml": "^3.11.2",
|
"@types/electron-debug": "^2.1.0",
|
||||||
"@types/node": "^10.11.5",
|
"@types/js-yaml": "^3.12.4",
|
||||||
"@types/webpack-env": "1.13.0",
|
"@types/node": "12.7.12",
|
||||||
"app-builder-lib": "^20.28.4",
|
"@types/webpack-env": "^1.15.2",
|
||||||
"apply-loader": "0.1.0",
|
"@typescript-eslint/eslint-plugin": "^2.26.0",
|
||||||
|
"@typescript-eslint/parser": "^2.34.0",
|
||||||
|
"apply-loader": "2.0.0",
|
||||||
"awesome-typescript-loader": "^5.0.0",
|
"awesome-typescript-loader": "^5.0.0",
|
||||||
"core-js": "2.4.1",
|
"core-js": "^3.6.5",
|
||||||
"cross-env": "4.0.0",
|
"cross-env": "7.0.2",
|
||||||
"css-loader": "0.28.0",
|
"css-loader": "3.4.2",
|
||||||
"electron": "4.0.5",
|
"electron": "^8.2.5",
|
||||||
"electron-builder": "^20.38.4",
|
"electron-builder": "22.6.1",
|
||||||
"electron-builder-squirrel-windows": "^20.28.3",
|
"electron-download": "^4.1.1",
|
||||||
"electron-installer-snap": "^3.0.0",
|
"electron-installer-snap": "^5.0.0",
|
||||||
"electron-rebuild": "^1.8.2",
|
"electron-notarize": "^0.1.1",
|
||||||
"file-loader": "^1.1.11",
|
"electron-rebuild": "^1.10.1",
|
||||||
"graceful-fs": "^4.1.11",
|
"eslint": "^6.8.0",
|
||||||
"html-loader": "0.4.4",
|
"eslint-plugin-import": "^2.20.2",
|
||||||
"json-loader": "0.5.4",
|
"file-loader": "^5.0.2",
|
||||||
"node-abi": "^2.4.4",
|
"graceful-fs": "^4.2.4",
|
||||||
"node-gyp": "^3.8.0",
|
"html-loader": "0.5.5",
|
||||||
"node-sass": "^4.5.3",
|
"json-loader": "0.5.7",
|
||||||
"npmlog": "4.1.0",
|
"node-abi": "^2.16.0",
|
||||||
|
"node-gyp": "^6.1.0",
|
||||||
|
"node-sass": "^4.14.1",
|
||||||
|
"npmlog": "4.1.2",
|
||||||
"npx": "^10.2.0",
|
"npx": "^10.2.0",
|
||||||
"pug": "^2.0.3",
|
"pug": "^2.0.4",
|
||||||
"pug-html-loader": "1.0.9",
|
"pug-html-loader": "1.1.5",
|
||||||
"pug-lint": "^2.5.0",
|
"pug-lint": "^2.6.0",
|
||||||
"pug-loader": "^2.4.0",
|
"pug-loader": "^2.4.0",
|
||||||
"pug-static-loader": "0.0.1",
|
"pug-static-loader": "2.0.0",
|
||||||
"raven-js": "3.16.0",
|
"raw-loader": "4.0.1",
|
||||||
"raw-loader": "0.5.1",
|
"sass-loader": "^8.0.0",
|
||||||
"sass-loader": "^7.0.1",
|
"shelljs": "0.8.4",
|
||||||
"shelljs": "0.7.7",
|
"source-code-pro": "^2.30.2",
|
||||||
"source-code-pro": "^2.30.1",
|
"source-sans-pro": "3.6.0",
|
||||||
"source-sans-pro": "2.0.10",
|
"style-loader": "^1.1.4",
|
||||||
"style-loader": "^0.23.1",
|
|
||||||
"svg-inline-loader": "^0.8.0",
|
"svg-inline-loader": "^0.8.0",
|
||||||
"to-string-loader": "1.1.5",
|
"to-string-loader": "1.1.6",
|
||||||
"tslint": "^5.12.0",
|
"tslib": "^2.0.0",
|
||||||
"tslint-config-standard": "^8.0.1",
|
"typedoc": "^0.17.6",
|
||||||
"tslint-eslint-rules": "^5.4.0",
|
"typescript": "^3.9.3",
|
||||||
"typedoc": "^0.14.2",
|
"url-loader": "^3.0.0",
|
||||||
"typescript": "^3.1.3",
|
"val-loader": "2.1.1",
|
||||||
"url-loader": "^1.1.1",
|
"webpack": "^5.0.0-beta.16",
|
||||||
"val-loader": "0.5.0",
|
"webpack-cli": "^3.3.10",
|
||||||
"webpack": "^4.27.1",
|
"yaml-loader": "0.6.0"
|
||||||
"webpack-cli": "^3.1.2",
|
|
||||||
"yaml-loader": "0.4.0",
|
|
||||||
"yarn": "^1.10.1"
|
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"*/node-abi": "^2.5.0"
|
"*/node-abi": "^2.14.0"
|
||||||
},
|
|
||||||
"build": {
|
|
||||||
"appId": "org.terminus",
|
|
||||||
"productName": "Terminus",
|
|
||||||
"compression": "normal",
|
|
||||||
"files": [
|
|
||||||
"**/*",
|
|
||||||
"dist"
|
|
||||||
],
|
|
||||||
"extraResources": [
|
|
||||||
"builtin-plugins",
|
|
||||||
"extras"
|
|
||||||
],
|
|
||||||
"win": {
|
|
||||||
"icon": "./build/windows/icon.ico",
|
|
||||||
"publish": [
|
|
||||||
"github"
|
|
||||||
],
|
|
||||||
"artifactName": "terminus-${version}-setup.exe"
|
|
||||||
},
|
|
||||||
"squirrelWindows": {
|
|
||||||
"iconUrl": "https://github.com/Eugeny/terminus/raw/master/build/windows/icon.ico",
|
|
||||||
"loadingGif": "./build/windows/squirrel.gif",
|
|
||||||
"artifactName": "terminus-${version}-setup.exe"
|
|
||||||
},
|
|
||||||
"portable": {
|
|
||||||
"artifactName": "terminus-${version}-portable.exe"
|
|
||||||
},
|
|
||||||
"mac": {
|
|
||||||
"category": "public.app-category.video",
|
|
||||||
"icon": "./build/mac/icon.icns",
|
|
||||||
"artifactName": "terminus-${version}-macos.${ext}",
|
|
||||||
"publish": [
|
|
||||||
"github"
|
|
||||||
],
|
|
||||||
"extendInfo": {
|
|
||||||
"NSRequiresAquaSystemAppearance": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dmg": {
|
|
||||||
"artifactName": "terminus-${version}-macos.dmg"
|
|
||||||
},
|
|
||||||
"linux": {
|
|
||||||
"category": "Utilities",
|
|
||||||
"icon": "./build/icons",
|
|
||||||
"artifactName": "terminus-${version}-linux.${ext}",
|
|
||||||
"publish": [
|
|
||||||
"github"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"deb": {
|
|
||||||
"depends": [
|
|
||||||
"screen",
|
|
||||||
"gconf2",
|
|
||||||
"gconf-service",
|
|
||||||
"libnotify4",
|
|
||||||
"libappindicator1",
|
|
||||||
"libxtst6",
|
|
||||||
"libnss3",
|
|
||||||
"tmux"
|
|
||||||
],
|
|
||||||
"afterInstall": "build/linux/after-install.tpl"
|
|
||||||
},
|
|
||||||
"rpm": {
|
|
||||||
"depends": [
|
|
||||||
"screen",
|
|
||||||
"gnome-python2-gnomekeyring"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "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-settings/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": "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",
|
||||||
"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 terminus-terminal/src && typedoc --out docs/api/settings terminus-settings/src",
|
"docs": "typedoc --out docs/api terminus-core/src && typedoc --out docs/api/terminal --tsconfig terminus-terminal/tsconfig.typings.json terminus-terminal/src && typedoc --out docs/api/settings --tsconfig terminus-settings/tsconfig.typings.json terminus-settings/src",
|
||||||
"lint": "tslint -c tslint.json -t stylish terminus-*/src/**/*.ts terminus-*/src/*.ts app/src/*.ts",
|
"lint": "eslint --ext ts */src */lib",
|
||||||
"postinstall": "node ./scripts/install-deps.js"
|
"postinstall": "node ./scripts/install-deps.js"
|
||||||
},
|
},
|
||||||
"repository": "eugeny/terminus"
|
"repository": "eugeny/terminus",
|
||||||
|
"author": "Eugene Pankov",
|
||||||
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
@@ -2,13 +2,16 @@
|
|||||||
const builder = require('electron-builder').build
|
const builder = require('electron-builder').build
|
||||||
const vars = require('./vars')
|
const vars = require('./vars')
|
||||||
|
|
||||||
|
const isTag = (process.env.GITHUB_REF || '').startsWith('refs/tags/')
|
||||||
|
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,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
publish: isTag ? 'always' : 'onTag',
|
||||||
publish: 'onTag',
|
}).catch(() => process.exit(1))
|
||||||
})
|
|
||||||
|
@@ -1,14 +1,21 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
const builder = require('electron-builder').build
|
const builder = require('electron-builder').build
|
||||||
const vars = require('./vars')
|
const vars = require('./vars')
|
||||||
|
const fs = require('fs')
|
||||||
|
const signHook = require('../build/mac/afterSignHook')
|
||||||
|
|
||||||
|
const isTag = (process.env.GITHUB_REF || '').startsWith('refs/tags/')
|
||||||
|
|
||||||
builder({
|
builder({
|
||||||
dir: true,
|
dir: true,
|
||||||
mac: ['dmg', 'zip'],
|
mac: ['pkg', 'zip'],
|
||||||
config: {
|
config: {
|
||||||
extraMetadata: {
|
extraMetadata: {
|
||||||
version: vars.version,
|
version: vars.version,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
publish: isTag ? 'always' : 'onTag',
|
||||||
publish: 'onTag',
|
}).catch(e => {
|
||||||
|
console.error(e)
|
||||||
|
process.exit(1)
|
||||||
})
|
})
|
||||||
|
@@ -3,19 +3,24 @@ const rebuild = require('electron-rebuild').default
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const vars = require('./vars')
|
const vars = require('./vars')
|
||||||
|
|
||||||
lifecycles = []
|
let lifecycles = []
|
||||||
for (let dir of ['app', 'terminus-ssh', 'terminus-terminal']) {
|
for (let dir of ['app', 'terminus-core', 'terminus-ssh', 'terminus-terminal']) {
|
||||||
lifecycles.push([rebuild({
|
const build = rebuild({
|
||||||
buildPath: path.resolve(__dirname, '../' + dir),
|
buildPath: path.resolve(__dirname, '../' + dir),
|
||||||
electronVersion: vars.electronVersion,
|
electronVersion: vars.electronVersion,
|
||||||
force: true,
|
force: true,
|
||||||
}).lifecycle, dir])
|
})
|
||||||
|
build.catch(e => {
|
||||||
|
console.error(e)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
|
lifecycles.push([build.lifecycle, dir])
|
||||||
}
|
}
|
||||||
|
|
||||||
console.info('Building against Electron', vars.electronVersion)
|
console.info('Building against Electron', vars.electronVersion)
|
||||||
|
|
||||||
for (let [lc, dir] of lifecycles) {
|
for (let [lc, dir] of lifecycles) {
|
||||||
lc.on('module-found', name => {
|
lc.on('module-found', name => {
|
||||||
console.info('Rebuilding', dir + '/' + name)
|
console.info('Rebuilding', dir + '/' + name)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
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`)
|
||||||
|
})
|
@@ -2,13 +2,16 @@
|
|||||||
const builder = require('electron-builder').build
|
const builder = require('electron-builder').build
|
||||||
const vars = require('./vars')
|
const vars = require('./vars')
|
||||||
|
|
||||||
|
const isTag = (process.env.GITHUB_REF || process.env.BUILD_SOURCEBRANCH || '').startsWith('refs/tags/')
|
||||||
|
const isCI = !!process.env.GITHUB_REF
|
||||||
|
|
||||||
builder({
|
builder({
|
||||||
dir: true,
|
dir: true,
|
||||||
win: ['squirrel', 'portable'],
|
win: ['nsis', 'zip'],
|
||||||
config: {
|
config: {
|
||||||
extraMetadata: {
|
extraMetadata: {
|
||||||
version: vars.version,
|
version: vars.version,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
publish: isTag ? 'always' : 'onTag',
|
||||||
publish: 'onTag',
|
}).catch(() => process.exit(1))
|
||||||
})
|
|
||||||
|
@@ -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('..')
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
|
const semver = require('semver')
|
||||||
const childProcess = require('child_process')
|
const childProcess = require('child_process')
|
||||||
|
|
||||||
const appInfo = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../app/package.json')))
|
const appInfo = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../app/package.json')))
|
||||||
@@ -9,6 +10,10 @@ exports.version = childProcess.execSync('git describe --tags', {encoding:'utf-8'
|
|||||||
exports.version = exports.version.substring(1).trim()
|
exports.version = exports.version.substring(1).trim()
|
||||||
exports.version = exports.version.replace('-', '-c')
|
exports.version = exports.version.replace('-', '-c')
|
||||||
|
|
||||||
|
if (exports.version.includes('-c')) {
|
||||||
|
exports.version = semver.inc(exports.version, 'prepatch').replace('-0', '-nightly.0')
|
||||||
|
}
|
||||||
|
|
||||||
exports.builtinPlugins = [
|
exports.builtinPlugins = [
|
||||||
'terminus-core',
|
'terminus-core',
|
||||||
'terminus-settings',
|
'terminus-settings',
|
||||||
@@ -16,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',
|
||||||
|
26
snap/snapcraft.yaml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: terminus
|
||||||
|
version: '1.0.0'
|
||||||
|
summary: A terminal for a modern age
|
||||||
|
description: |
|
||||||
|
Terminus is a terminal heavily inspired by Hyper. It is, however, designed for people who need to get things done.
|
||||||
|
|
||||||
|
grade: devel
|
||||||
|
confinement: devmode
|
||||||
|
|
||||||
|
apps:
|
||||||
|
terminus:
|
||||||
|
command: opt/terminus/terminus
|
||||||
|
|
||||||
|
parts:
|
||||||
|
app:
|
||||||
|
plugin: nodejs
|
||||||
|
source: .
|
||||||
|
build-packages:
|
||||||
|
- libfontconfig-dev
|
||||||
|
override-build: |
|
||||||
|
yarn
|
||||||
|
./scripts/build-native.js
|
||||||
|
yarn run build
|
||||||
|
./scripts/build-linux.js
|
||||||
|
mkdir -p $SNAPCRAFT_PART_INSTALL/opt/terminus || true
|
||||||
|
cp -ar dist/linux-unpacked/* $SNAPCRAFT_PART_INSTALL/opt/terminus/
|
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-community-color-schemes",
|
"name": "terminus-community-color-schemes",
|
||||||
"version": "1.0.68-c17-g8b64a81",
|
"version": "1.0.104-nightly.0",
|
||||||
"description": "Community color schemes for Terminus",
|
"description": "Community color schemes for Terminus",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"terminus-builtin-plugin"
|
"terminus-builtin-plugin"
|
||||||
],
|
],
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"typings": "dist/index.d.ts",
|
"typings": "typings/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --progress --color",
|
"build": "webpack --progress --color",
|
||||||
"watch": "webpack --progress --color --watch"
|
"watch": "webpack --progress --color --watch"
|
||||||
@@ -17,13 +17,8 @@
|
|||||||
"author": "Eugene Pankov",
|
"author": "Eugene Pankov",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/core": "4.0.1",
|
"@angular/core": "^7",
|
||||||
"terminus-core": "*",
|
"terminus-core": "*",
|
||||||
"terminus-terminal": "*"
|
"terminus-terminal": "*"
|
||||||
},
|
}
|
||||||
"devDependencies": {
|
|
||||||
"@types/node": "7.0.12",
|
|
||||||
"@types/webpack-env": "^1.13.0"
|
|
||||||
},
|
|
||||||
"false": {}
|
|
||||||
}
|
}
|
||||||
|
36
terminus-community-color-schemes/schemes/Relaxed
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
! special
|
||||||
|
*.foreground: #d8d8d8
|
||||||
|
*.background: #343a43
|
||||||
|
*.cursorColor: #d8d8d8
|
||||||
|
|
||||||
|
! black
|
||||||
|
*.color0: #2c3037
|
||||||
|
*.color8: #626262
|
||||||
|
|
||||||
|
! red
|
||||||
|
*.color1: #bb5653
|
||||||
|
*.color9: #c35956
|
||||||
|
|
||||||
|
! green
|
||||||
|
*.color2: #909d62
|
||||||
|
*.color10: #9fab76
|
||||||
|
|
||||||
|
! yellow
|
||||||
|
*.color3: #eac179
|
||||||
|
*.color11: #ecc179
|
||||||
|
|
||||||
|
! blue
|
||||||
|
*.color4: #698698
|
||||||
|
*.color12: #7da9c7
|
||||||
|
|
||||||
|
! magenta
|
||||||
|
*.color5: #b06597
|
||||||
|
*.color13: #ba6ca0
|
||||||
|
|
||||||
|
! cyan
|
||||||
|
*.color6: #c9dfff
|
||||||
|
*.color14: #abbacf
|
||||||
|
|
||||||
|
! white
|
||||||
|
*.color7: #d8d8d8
|
||||||
|
*.color15: #f7f7f7
|
@@ -1,26 +1,26 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { TerminalColorSchemeProvider, ITerminalColorScheme } from 'terminus-terminal'
|
import { TerminalColorSchemeProvider, TerminalColorScheme } from 'terminus-terminal'
|
||||||
|
|
||||||
const schemeContents = require.context('../schemes/', true, /.*/)
|
const schemeContents = require.context('../schemes/', true, /.*/)
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ColorSchemes extends TerminalColorSchemeProvider {
|
export class ColorSchemes extends TerminalColorSchemeProvider {
|
||||||
async getSchemes (): Promise<ITerminalColorScheme[]> {
|
async getSchemes (): Promise<TerminalColorScheme[]> {
|
||||||
let schemes: ITerminalColorScheme[] = []
|
const schemes: TerminalColorScheme[] = []
|
||||||
|
|
||||||
schemeContents.keys().forEach(schemeFile => {
|
schemeContents.keys().forEach(schemeFile => {
|
||||||
let lines = (schemeContents(schemeFile) as string).split('\n')
|
const lines = (schemeContents(schemeFile).default as string).split('\n')
|
||||||
|
|
||||||
// process #define variables
|
// process #define variables
|
||||||
let variables: any = {}
|
const variables: any = {}
|
||||||
lines
|
lines
|
||||||
.filter(x => x.startsWith('#define'))
|
.filter(x => x.startsWith('#define'))
|
||||||
.map(x => x.split(' ').map(v => v.trim()))
|
.map(x => x.split(' ').map(v => v.trim()))
|
||||||
.forEach(([ignore, variableName, variableValue]) => {
|
.forEach(([_, variableName, variableValue]) => {
|
||||||
variables[variableName] = variableValue
|
variables[variableName] = variableValue
|
||||||
})
|
})
|
||||||
|
|
||||||
let values: any = {}
|
const values: any = {}
|
||||||
lines
|
lines
|
||||||
.filter(x => x.startsWith('*.'))
|
.filter(x => x.startsWith('*.'))
|
||||||
.map(x => x.substring(2))
|
.map(x => x.substring(2))
|
||||||
@@ -29,7 +29,7 @@ export class ColorSchemes extends TerminalColorSchemeProvider {
|
|||||||
values[key] = variables[value] ? variables[value] : value
|
values[key] = variables[value] ? variables[value] : value
|
||||||
})
|
})
|
||||||
|
|
||||||
let colors: string[] = []
|
const colors: string[] = []
|
||||||
let colorIndex = 0
|
let colorIndex = 0
|
||||||
while (values[`color${colorIndex}`]) {
|
while (values[`color${colorIndex}`]) {
|
||||||
colors.push(values[`color${colorIndex}`])
|
colors.push(values[`color${colorIndex}`])
|
||||||
|
@@ -8,4 +8,4 @@ import { ColorSchemes } from './colorSchemes'
|
|||||||
{ provide: TerminalColorSchemeProvider, useClass: ColorSchemes, multi: true },
|
{ provide: TerminalColorSchemeProvider, useClass: ColorSchemes, multi: true },
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export default class PopularThemesModule { }
|
export default class PopularThemesModule { } // eslint-disable-line @typescript-eslint/no-extraneous-class
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
"extends": "../tsconfig.json",
|
"extends": "../tsconfig.json",
|
||||||
"exclude": ["node_modules", "dist"],
|
"exclude": ["node_modules", "dist"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": "src",
|
"baseUrl": "src"
|
||||||
"declarationDir": "dist"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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,52 +1,51 @@
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const webpack = require('webpack')
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
target: 'node',
|
target: 'node',
|
||||||
entry: 'src/index.ts',
|
entry: 'src/index.ts',
|
||||||
devtool: 'source-map',
|
context: __dirname,
|
||||||
context: __dirname,
|
devtool: 'eval-cheap-module-source-map',
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve(__dirname, 'dist'),
|
path: path.resolve(__dirname, 'dist'),
|
||||||
filename: 'index.js',
|
filename: 'index.js',
|
||||||
pathinfo: true,
|
pathinfo: true,
|
||||||
libraryTarget: 'umd',
|
libraryTarget: 'umd',
|
||||||
devtoolModuleFilenameTemplate: 'webpack-terminus-community-color-schemes:///[resource-path]',
|
devtoolModuleFilenameTemplate: 'webpack-terminus-community-color-schemes:///[resource-path]',
|
||||||
},
|
},
|
||||||
mode: process.env.TERMINUS_DEV ? 'development' : 'production',
|
mode: process.env.TERMINUS_DEV ? 'development' : 'production',
|
||||||
optimization:{
|
optimization:{
|
||||||
minimize: false,
|
minimize: false,
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
|
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
|
||||||
extensions: ['.ts', '.js'],
|
extensions: ['.ts', '.js'],
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.ts$/,
|
test: /\.ts$/,
|
||||||
use: {
|
use: {
|
||||||
loader: 'awesome-typescript-loader',
|
loader: 'awesome-typescript-loader',
|
||||||
options: {
|
options: {
|
||||||
configFileName: path.resolve(__dirname, 'tsconfig.json'),
|
configFileName: path.resolve(__dirname, 'tsconfig.json'),
|
||||||
typeRoots: [path.resolve(__dirname, 'node_modules/@types')],
|
typeRoots: [
|
||||||
paths: {
|
path.resolve(__dirname, 'node_modules/@types'),
|
||||||
"terminus-*": [path.resolve(__dirname, '../terminus-*')],
|
path.resolve(__dirname, '../node_modules/@types'),
|
||||||
"*": [path.resolve(__dirname, '../app/node_modules/*')],
|
],
|
||||||
}
|
paths: {
|
||||||
}
|
"terminus-*": [path.resolve(__dirname, '../terminus-*')],
|
||||||
}
|
"*": [path.resolve(__dirname, '../app/node_modules/*')],
|
||||||
},
|
},
|
||||||
{ test: /[\\\/]schemes[\\\/]/, use: "raw-loader" },
|
},
|
||||||
]
|
},
|
||||||
},
|
},
|
||||||
externals: [
|
{ test: /[\\\/]schemes[\\\/]/, use: "raw-loader" },
|
||||||
/^rxjs/,
|
],
|
||||||
/^@angular/,
|
},
|
||||||
/^@ng-bootstrap/,
|
externals: [
|
||||||
/^terminus-/,
|
/^rxjs/,
|
||||||
],
|
/^@angular/,
|
||||||
plugins: [
|
/^@ng-bootstrap/,
|
||||||
new webpack.optimize.ModuleConcatenationPlugin(),
|
/^terminus-/,
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
@@ -2,12 +2,3 @@
|
|||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
"@types/node@7.0.12":
|
|
||||||
version "7.0.12"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.12.tgz#ae5f67a19c15f752148004db07cbbb372e69efc9"
|
|
||||||
integrity sha1-rl9noZwV91IUgATbB8u7Ny5p78k=
|
|
||||||
|
|
||||||
"@types/webpack-env@^1.13.0":
|
|
||||||
version "1.13.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.13.1.tgz#b45c222e24301bd006e3edfc762cc6b51bda236a"
|
|
||||||
integrity sha512-oHyg0NssP2RCpCvE35hhbSqMJRsc5lSW+GFe+Vc65JL+kHII1VMYM+0KeV/z4utFuUqPoQRmq8KMMp7ba0dj6Q==
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
Terminus Core Plugin
|
Terminus Core Plugin
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
See also: [Settings plugin API](./settings/), [Terminal plugin API](./settings/)
|
See also: [Settings plugin API](./settings/), [Terminal plugin API](./terminal/)
|
||||||
|
|
||||||
* tabbed interface services
|
* tabbed interface services
|
||||||
* toolbar UI
|
* toolbar UI
|
||||||
|
@@ -1,49 +1,45 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-core",
|
"name": "terminus-core",
|
||||||
"version": "1.0.68-c17-g8b64a81",
|
"version": "1.0.104-nightly.0",
|
||||||
"description": "Terminus core",
|
"description": "Terminus core",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"terminus-builtin-plugin"
|
"terminus-builtin-plugin"
|
||||||
],
|
],
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"typings": "dist/index.d.ts",
|
"typings": "typings/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --progress --color --display-modules",
|
"build": "webpack --progress --color --display-modules",
|
||||||
"watch": "webpack --progress --color --watch"
|
"watch": "webpack --progress --color --watch"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"typings"
|
||||||
],
|
],
|
||||||
"author": "Eugene Pankov",
|
"author": "Eugene Pankov",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/js-yaml": "^3.9.0",
|
"@types/js-yaml": "^3.9.0",
|
||||||
"@types/node": "^7.0.37",
|
"@types/shell-escape": "^0.2.0",
|
||||||
"@types/webpack-env": "^1.13.0",
|
|
||||||
"@types/winston": "^2.3.6",
|
"@types/winston": "^2.3.6",
|
||||||
"axios": "^0.18.0",
|
"axios": "^0.19.0",
|
||||||
"bootstrap": "^4.1.3",
|
"bootstrap": "^4.1.3",
|
||||||
"core-js": "^2.4.1",
|
"core-js": "^3.1.2",
|
||||||
"electron-updater": "^2.8.9",
|
"deepmerge": "^4.1.1",
|
||||||
|
"electron-updater": "^4.0.6",
|
||||||
|
"js-yaml": "^3.9.0",
|
||||||
|
"mixpanel": "^0.10.2",
|
||||||
"ng2-dnd": "^5.0.2",
|
"ng2-dnd": "^5.0.2",
|
||||||
"ngx-perfect-scrollbar": "^6.0.0",
|
"ngx-perfect-scrollbar": "^8.0.0",
|
||||||
"rage-edit": "^1.2.0",
|
|
||||||
"shell-escape": "^0.2.0",
|
"shell-escape": "^0.2.0",
|
||||||
"universal-analytics": "^0.4.17"
|
"uuid": "^8.0.0",
|
||||||
|
"winston": "^3.2.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/animations": "4.0.1",
|
"@angular/animations": "^7",
|
||||||
"@angular/common": "4.0.1",
|
"@angular/common": "^7",
|
||||||
"@angular/core": "4.0.1",
|
"@angular/core": "^7",
|
||||||
"@angular/forms": "4.0.1",
|
"@angular/forms": "^7",
|
||||||
"@angular/platform-browser": "4.0.1",
|
"@angular/platform-browser": "^7",
|
||||||
"@angular/platform-browser-dynamic": "4.0.1",
|
"@angular/platform-browser-dynamic": "^7",
|
||||||
"rxjs": "5.3.0",
|
"rxjs": "^5"
|
||||||
"zone.js": "0.8.4"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"deepmerge": "^1.5.0",
|
|
||||||
"js-yaml": "^3.9.0",
|
|
||||||
"winston": "^2.4.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
export interface IHotkeyDescription {
|
export interface HotkeyDescription {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@ export interface IHotkeyDescription {
|
|||||||
* 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: IHotkeyDescription[] = []
|
hotkeys: HotkeyDescription[] = []
|
||||||
|
|
||||||
abstract provide (): Promise<IHotkeyDescription[]>
|
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, IToolbarButton } from './toolbarButtonProvider'
|
export { ToolbarButtonProvider, ToolbarButton } from './toolbarButtonProvider'
|
||||||
export { ConfigProvider } from './configProvider'
|
export { ConfigProvider } from './configProvider'
|
||||||
export { HotkeyProvider, IHotkeyDescription } 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'
|
||||||
@@ -18,3 +20,4 @@ export { HostAppService, Platform } from '../services/hostApp.service'
|
|||||||
export { ShellIntegrationService } from '../services/shellIntegration.service'
|
export { ShellIntegrationService } from '../services/shellIntegration.service'
|
||||||
export { ThemesService } from '../services/themes.service'
|
export { ThemesService } from '../services/themes.service'
|
||||||
export { TabsService } from '../services/tabs.service'
|
export { TabsService } from '../services/tabs.service'
|
||||||
|
export * from '../utils'
|
||||||
|
8
terminus-core/src/api/selector.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export interface SelectorOption<T> {
|
||||||
|
name: string
|
||||||
|
description?: string
|
||||||
|
result?: T
|
||||||
|
icon?: string
|
||||||
|
freeInputPattern?: string
|
||||||
|
callback?: (string?) => void
|
||||||
|
}
|
@@ -12,6 +12,12 @@ export interface RecoveredTab {
|
|||||||
options?: any
|
options?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RecoveryToken {
|
||||||
|
[_: string]: any
|
||||||
|
type: string
|
||||||
|
tabColor?: string|null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extend to enable recovery for your custom tab.
|
* Extend to enable recovery for your custom tab.
|
||||||
* This works in conjunction with [[getRecoveryToken()]]
|
* This works in conjunction with [[getRecoveryToken()]]
|
||||||
@@ -34,5 +40,5 @@ export abstract class TabRecoveryProvider {
|
|||||||
* @returns [[RecoveredTab]] descriptor containing tab type and component inputs
|
* @returns [[RecoveredTab]] descriptor containing tab type and component inputs
|
||||||
* or `null` if this token is from a different tab type or is not supported
|
* or `null` if this token is from a different tab type or is not supported
|
||||||
*/
|
*/
|
||||||
abstract async recover (recoveryToken: any): Promise<RecoveredTab | null>
|
abstract async recover (recoveryToken: RecoveryToken): Promise<RecoveredTab|null>
|
||||||
}
|
}
|
||||||
|
@@ -1,13 +1,11 @@
|
|||||||
import { SafeHtml } from '@angular/platform-browser'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See [[ToolbarButtonProvider]]
|
* See [[ToolbarButtonProvider]]
|
||||||
*/
|
*/
|
||||||
export interface IToolbarButton {
|
export interface ToolbarButton {
|
||||||
/**
|
/**
|
||||||
* Raw SVG icon code
|
* Raw SVG icon code
|
||||||
*/
|
*/
|
||||||
icon: SafeHtml
|
icon?: string
|
||||||
|
|
||||||
title: string
|
title: string
|
||||||
|
|
||||||
@@ -23,12 +21,17 @@ export interface IToolbarButton {
|
|||||||
|
|
||||||
weight?: number
|
weight?: number
|
||||||
|
|
||||||
click: () => void
|
click?: () => void
|
||||||
|
|
||||||
|
submenu?: () => Promise<ToolbarButton[]>
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
submenuItems?: ToolbarButton[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extend to add buttons to the toolbar
|
* Extend to add buttons to the toolbar
|
||||||
*/
|
*/
|
||||||
export abstract class ToolbarButtonProvider {
|
export abstract class ToolbarButtonProvider {
|
||||||
abstract provide (): IToolbarButton[]
|
abstract provide (): ToolbarButton[]
|
||||||
}
|
}
|
||||||
|
@@ -6,9 +6,7 @@ title-bar(
|
|||||||
.content(
|
.content(
|
||||||
[class.tabs-on-top]='config.store.appearance.tabsLocation == "top"'
|
[class.tabs-on-top]='config.store.appearance.tabsLocation == "top"'
|
||||||
)
|
)
|
||||||
.tab-bar(
|
.tab-bar
|
||||||
*ngIf='!hostApp.isFullScreen',
|
|
||||||
)
|
|
||||||
.inset.background(*ngIf='hostApp.platform == Platform.macOS && config.store.appearance.frame == "thin" && config.store.appearance.tabsLocation == "top"')
|
.inset.background(*ngIf='hostApp.platform == Platform.macOS && config.store.appearance.frame == "thin" && config.store.appearance.tabsLocation == "top"')
|
||||||
.tabs(
|
.tabs(
|
||||||
dnd-sortable-container,
|
dnd-sortable-container,
|
||||||
@@ -32,28 +30,60 @@ title-bar(
|
|||||||
)
|
)
|
||||||
|
|
||||||
.btn-group.background
|
.btn-group.background
|
||||||
button.btn.btn-secondary.btn-tab-bar(
|
.d-flex(
|
||||||
*ngFor='let button of leftToolbarButtons',
|
*ngFor='let button of leftToolbarButtons',
|
||||||
[title]='button.title',
|
ngbDropdown,
|
||||||
(click)='button.click()',
|
(openChange)='generateButtonSubmenu(button)',
|
||||||
[innerHTML]='button.icon',
|
|
||||||
)
|
)
|
||||||
|
button.btn.btn-secondary.btn-tab-bar(
|
||||||
|
[title]='button.title',
|
||||||
|
(click)='button.click && button.click()',
|
||||||
|
[fastHtmlBind]='button.icon',
|
||||||
|
ngbDropdownToggle,
|
||||||
|
)
|
||||||
|
div(*ngIf='button.submenu', ngbDropdownMenu)
|
||||||
|
button.dropdown-item.d-flex.align-items-center(
|
||||||
|
*ngFor='let item of button.submenuItems',
|
||||||
|
(click)='item.click()',
|
||||||
|
ngbDropdownItem,
|
||||||
|
)
|
||||||
|
.icon-wrapper(
|
||||||
|
*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')
|
||||||
|
|
||||||
.btn-group.background
|
.btn-group.background
|
||||||
button.btn.btn-secondary.btn-tab-bar(
|
.d-flex(
|
||||||
*ngFor='let button of rightToolbarButtons',
|
*ngFor='let button of rightToolbarButtons',
|
||||||
[title]='button.title',
|
ngbDropdown,
|
||||||
(click)='button.click()',
|
(openChange)='generateButtonSubmenu(button)',
|
||||||
[innerHTML]='button.icon',
|
|
||||||
)
|
)
|
||||||
|
button.btn.btn-secondary.btn-tab-bar(
|
||||||
|
[title]='button.title',
|
||||||
|
(click)='button.click && button.click()',
|
||||||
|
[fastHtmlBind]='button.icon',
|
||||||
|
ngbDropdownToggle,
|
||||||
|
)
|
||||||
|
div(*ngIf='button.submenu', ngbDropdownMenu)
|
||||||
|
button.dropdown-item.d-flex.align-items-center(
|
||||||
|
*ngFor='let item of button.submenuItems',
|
||||||
|
(click)='item.click()',
|
||||||
|
ngbDropdownItem,
|
||||||
|
)
|
||||||
|
.icon-wrapper(
|
||||||
|
*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',
|
title='Update available - Click to install',
|
||||||
(click)='updateApp()',
|
(click)='updateApp()',
|
||||||
[innerHTML]='updateIcon'
|
[fastHtmlBind]='updateIcon'
|
||||||
)
|
)
|
||||||
|
|
||||||
window-controls.background(
|
window-controls.background(
|
||||||
|
@@ -18,6 +18,7 @@ $tab-border-radius: 4px;
|
|||||||
|
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
height: 100%;
|
||||||
flex: auto;
|
flex: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column-reverse;
|
flex-direction: column-reverse;
|
||||||
@@ -48,6 +49,10 @@ $tab-border-radius: 4px;
|
|||||||
color: #aaa;
|
color: #aaa;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
|
||||||
|
&.dropdown-toggle::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&>.tabs {
|
&>.tabs {
|
||||||
@@ -59,6 +64,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 {
|
||||||
@@ -88,12 +94,20 @@ hotkey-hint {
|
|||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::ng-deep .btn-tab-bar svg {
|
::ng-deep .btn-tab-bar svg,
|
||||||
|
::ng-deep .btn-tab-bar + .dropdown-menu svg {
|
||||||
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
fill: white;
|
fill: white;
|
||||||
fill-opacity: 0.75;
|
fill-opacity: 0.75;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-wrapper {
|
||||||
|
display: flex;
|
||||||
|
width: 16px;
|
||||||
|
height: 17px;
|
||||||
|
}
|
||||||
|
|
||||||
::ng-deep .btn-update svg {
|
::ng-deep .btn-update svg {
|
||||||
fill: cyan;
|
fill: cyan;
|
||||||
}
|
}
|
||||||
|
@@ -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, SafeHtml } 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'
|
||||||
@@ -15,7 +15,7 @@ import { TouchbarService } from '../services/touchbar.service'
|
|||||||
|
|
||||||
import { BaseTabComponent } from './baseTab.component'
|
import { BaseTabComponent } from './baseTab.component'
|
||||||
import { SafeModeModalComponent } from './safeModeModal.component'
|
import { SafeModeModalComponent } from './safeModeModal.component'
|
||||||
import { AppService, IToolbarButton, ToolbarButtonProvider } from '../api'
|
import { AppService, ToolbarButton, ToolbarButtonProvider } from '../api'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Component({
|
@Component({
|
||||||
@@ -26,47 +26,47 @@ import { AppService, IToolbarButton, ToolbarButtonProvider } from '../api'
|
|||||||
trigger('animateTab', [
|
trigger('animateTab', [
|
||||||
state('in', style({
|
state('in', style({
|
||||||
'flex-basis': '200px',
|
'flex-basis': '200px',
|
||||||
'width': '200px',
|
width: '200px',
|
||||||
})),
|
})),
|
||||||
transition(':enter', [
|
transition(':enter', [
|
||||||
style({
|
style({
|
||||||
'flex-basis': '1px',
|
'flex-basis': '1px',
|
||||||
'width': '1px',
|
width: '1px',
|
||||||
}),
|
}),
|
||||||
animate('250ms ease-in-out', style({
|
animate('250ms ease-in-out', style({
|
||||||
'flex-basis': '200px',
|
'flex-basis': '200px',
|
||||||
'width': '200px',
|
width: '200px',
|
||||||
}))
|
})),
|
||||||
]),
|
]),
|
||||||
transition(':leave', [
|
transition(':leave', [
|
||||||
style({
|
style({
|
||||||
'flex-basis': '200px',
|
'flex-basis': '200px',
|
||||||
'width': '200px',
|
width: '200px',
|
||||||
}),
|
}),
|
||||||
animate('250ms ease-in-out', style({
|
animate('250ms ease-in-out', style({
|
||||||
'flex-basis': '1px',
|
'flex-basis': '1px',
|
||||||
'width': '1px',
|
width: '1px',
|
||||||
}))
|
})),
|
||||||
])
|
]),
|
||||||
])
|
]),
|
||||||
]
|
],
|
||||||
})
|
})
|
||||||
export class AppRootComponent {
|
export class AppRootComponent {
|
||||||
Platform = Platform
|
Platform = Platform
|
||||||
@Input() ready = false
|
@Input() ready = false
|
||||||
@Input() leftToolbarButtons: IToolbarButton[]
|
@Input() leftToolbarButtons: ToolbarButton[]
|
||||||
@Input() rightToolbarButtons: IToolbarButton[]
|
@Input() rightToolbarButtons: ToolbarButton[]
|
||||||
@HostBinding('class.platform-win32') platformClassWindows = process.platform === 'win32'
|
@HostBinding('class.platform-win32') platformClassWindows = process.platform === 'win32'
|
||||||
@HostBinding('class.platform-darwin') platformClassMacOS = process.platform === 'darwin'
|
@HostBinding('class.platform-darwin') platformClassMacOS = process.platform === 'darwin'
|
||||||
@HostBinding('class.platform-linux') platformClassLinux = process.platform === 'linux'
|
@HostBinding('class.platform-linux') platformClassLinux = process.platform === 'linux'
|
||||||
@HostBinding('class.no-tabs') noTabs = true
|
@HostBinding('class.no-tabs') noTabs = true
|
||||||
tabsDragging = false
|
tabsDragging = false
|
||||||
unsortedTabs: BaseTabComponent[] = []
|
unsortedTabs: BaseTabComponent[] = []
|
||||||
updateIcon: SafeHtml
|
updateIcon: string
|
||||||
updatesAvailable = false
|
updatesAvailable = false
|
||||||
private logger: Logger
|
private logger: Logger
|
||||||
|
|
||||||
constructor (
|
private constructor (
|
||||||
private docking: DockingService,
|
private docking: DockingService,
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
private hotkeys: HotkeysService,
|
private hotkeys: HotkeysService,
|
||||||
@@ -78,7 +78,6 @@ export class AppRootComponent {
|
|||||||
@Inject(ToolbarButtonProvider) private toolbarButtonProviders: ToolbarButtonProvider[],
|
@Inject(ToolbarButtonProvider) private toolbarButtonProviders: ToolbarButtonProvider[],
|
||||||
log: LogService,
|
log: LogService,
|
||||||
ngbModal: NgbModal,
|
ngbModal: NgbModal,
|
||||||
domSanitizer: DomSanitizer,
|
|
||||||
_themes: ThemesService,
|
_themes: ThemesService,
|
||||||
) {
|
) {
|
||||||
this.logger = log.create('main')
|
this.logger = log.create('main')
|
||||||
@@ -87,11 +86,11 @@ export class AppRootComponent {
|
|||||||
this.leftToolbarButtons = this.getToolbarButtons(false)
|
this.leftToolbarButtons = this.getToolbarButtons(false)
|
||||||
this.rightToolbarButtons = this.getToolbarButtons(true)
|
this.rightToolbarButtons = this.getToolbarButtons(true)
|
||||||
|
|
||||||
this.updateIcon = domSanitizer.bypassSecurityTrustHtml(require('../icons/gift.svg')),
|
this.updateIcon = require('../icons/gift.svg')
|
||||||
|
|
||||||
this.hotkeys.matchedHotkey.subscribe((hotkey) => {
|
this.hotkeys.matchedHotkey.subscribe((hotkey: string) => {
|
||||||
if (hotkey.startsWith('tab-')) {
|
if (hotkey.startsWith('tab-')) {
|
||||||
let index = parseInt(hotkey.split('-')[1])
|
const index = parseInt(hotkey.split('-')[1])
|
||||||
if (index <= this.app.tabs.length) {
|
if (index <= this.app.tabs.length) {
|
||||||
this.app.selectTab(this.app.tabs[index - 1])
|
this.app.selectTab(this.app.tabs[index - 1])
|
||||||
}
|
}
|
||||||
@@ -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,16 +128,8 @@ export class AppRootComponent {
|
|||||||
this.docking.dock()
|
this.docking.dock()
|
||||||
})
|
})
|
||||||
|
|
||||||
this.hostApp.secondInstance$.subscribe(() => {
|
|
||||||
this.presentWindow()
|
|
||||||
})
|
|
||||||
this.hotkeys.globalHotkey.subscribe(() => {
|
|
||||||
this.onGlobalHotkey()
|
|
||||||
})
|
|
||||||
|
|
||||||
this.hostApp.windowCloseRequest$.subscribe(async () => {
|
this.hostApp.windowCloseRequest$.subscribe(async () => {
|
||||||
await this.app.closeAllTabs()
|
this.app.closeWindow()
|
||||||
this.hostApp.closeWindow()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (window['safeModeReason']) {
|
if (window['safeModeReason']) {
|
||||||
@@ -145,7 +145,7 @@ export class AppRootComponent {
|
|||||||
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 => {
|
||||||
@@ -168,40 +168,6 @@ export class AppRootComponent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onGlobalHotkey () {
|
|
||||||
if (this.hostApp.getWindow().isFocused()) {
|
|
||||||
this.hideWindow()
|
|
||||||
} else {
|
|
||||||
this.presentWindow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
presentWindow () {
|
|
||||||
if (!this.hostApp.getWindow().isVisible()) {
|
|
||||||
// unfocused, invisible
|
|
||||||
this.hostApp.getWindow().show()
|
|
||||||
this.hostApp.getWindow().focus()
|
|
||||||
} else {
|
|
||||||
if (this.config.store.appearance.dock === 'off') {
|
|
||||||
// not docked, visible
|
|
||||||
setTimeout(() => {
|
|
||||||
this.hostApp.getWindow().focus()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// docked, visible
|
|
||||||
this.hostApp.getWindow().hide()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hideWindow () {
|
|
||||||
this.electron.loseFocus()
|
|
||||||
this.hostApp.getWindow().blur()
|
|
||||||
if (this.hostApp.platform !== Platform.macOS) {
|
|
||||||
this.hostApp.getWindow().hide()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async ngOnInit () {
|
async ngOnInit () {
|
||||||
this.ready = true
|
this.ready = true
|
||||||
|
|
||||||
@@ -218,8 +184,18 @@ export class AppRootComponent {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
updateApp () {
|
async updateApp () {
|
||||||
this.updater.update()
|
if ((await this.electron.showMessageBox(
|
||||||
|
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 () {
|
||||||
@@ -233,14 +209,24 @@ export class AppRootComponent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private getToolbarButtons (aboveZero: boolean): IToolbarButton[] {
|
async generateButtonSubmenu (button: ToolbarButton) {
|
||||||
let buttons: IToolbarButton[] = []
|
if (button.submenu) {
|
||||||
|
button.submenuItems = await button.submenu()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasIcons (submenuItems: ToolbarButton[]): boolean {
|
||||||
|
return submenuItems.some(x => !!x.icon)
|
||||||
|
}
|
||||||
|
|
||||||
|
private getToolbarButtons (aboveZero: boolean): ToolbarButton[] {
|
||||||
|
let buttons: ToolbarButton[] = []
|
||||||
this.config.enabledServices(this.toolbarButtonProviders).forEach(provider => {
|
this.config.enabledServices(this.toolbarButtonProviders).forEach(provider => {
|
||||||
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: IToolbarButton, b: IToolbarButton) => (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) {
|
||||||
@@ -91,7 +97,7 @@ export abstract class BaseTabComponent {
|
|||||||
}
|
}
|
||||||
this.progressClearTimeout = setTimeout(() => {
|
this.progressClearTimeout = setTimeout(() => {
|
||||||
this.setProgress(null)
|
this.setProgress(null)
|
||||||
}, 5000)
|
}, 5000) as any
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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,24 +142,26 @@ 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called before the tab is closed
|
* Called before the tab is closed
|
||||||
*/
|
*/
|
||||||
destroy (): void {
|
destroy (skipDestroyedEvent = false): void {
|
||||||
this.focused.complete()
|
this.focused.complete()
|
||||||
this.blurred.complete()
|
this.blurred.complete()
|
||||||
this.titleChange.complete()
|
this.titleChange.complete()
|
||||||
this.progress.complete()
|
this.progress.complete()
|
||||||
this.recoveryStateChangedHint.complete()
|
this.recoveryStateChangedHint.complete()
|
||||||
this.destroyed.next()
|
if (!skipDestroyedEvent) {
|
||||||
|
this.destroyed.next()
|
||||||
|
}
|
||||||
this.destroyed.complete()
|
this.destroyed.complete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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,14 +1,19 @@
|
|||||||
|
/* 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
|
||||||
@@ -23,7 +28,7 @@ export class CheckboxComponent implements ControlValueAccessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.model = !this.model
|
this.model = !this.model
|
||||||
for (let fx of this.changed) {
|
for (const fx of this.changed) {
|
||||||
fx(this.model)
|
fx(this.model)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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
@@ -0,0 +1,26 @@
|
|||||||
|
.modal-body
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
[(ngModel)]='filter',
|
||||||
|
autofocus,
|
||||||
|
[placeholder]='name',
|
||||||
|
(ngModelChange)='onFilterChange()'
|
||||||
|
)
|
||||||
|
|
||||||
|
.list-group.mt-3(*ngIf='filteredOptions.length')
|
||||||
|
a.list-group-item.list-group-item-action.d-flex.align-items-center(
|
||||||
|
#item,
|
||||||
|
(click)='selectOption(option)',
|
||||||
|
[class.active]='selectedIndex == i',
|
||||||
|
*ngFor='let option of filteredOptions; let i = index'
|
||||||
|
)
|
||||||
|
i.icon(
|
||||||
|
class='fa-fw fas fa-{{option.icon}}',
|
||||||
|
*ngIf='!iconIsSVG(option.icon)'
|
||||||
|
)
|
||||||
|
.icon(
|
||||||
|
[fastHtmlBind]='option.icon',
|
||||||
|
*ngIf='iconIsSVG(option.icon)'
|
||||||
|
)
|
||||||
|
.mr-2.title {{getOptionText(option)}}
|
||||||
|
.text-muted {{option.description}}
|
13
terminus-core/src/components/selectorModal.component.scss
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
.list-group {
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 1.25rem;
|
||||||
|
margin-right: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
78
terminus-core/src/components/selectorModal.component.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { Component, Input, HostListener, ViewChildren, QueryList, ElementRef } from '@angular/core'
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { SelectorOption } from '../api/selector'
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
@Component({
|
||||||
|
template: require('./selectorModal.component.pug'),
|
||||||
|
styles: [require('./selectorModal.component.scss')],
|
||||||
|
})
|
||||||
|
export class SelectorModalComponent<T> {
|
||||||
|
@Input() options: SelectorOption<T>[]
|
||||||
|
@Input() filteredOptions: SelectorOption<T>[]
|
||||||
|
@Input() filter = ''
|
||||||
|
@Input() name: string
|
||||||
|
@Input() selectedIndex = 0
|
||||||
|
@ViewChildren('item') itemChildren: QueryList<ElementRef>
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
public modalInstance: NgbActiveModal,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit (): void {
|
||||||
|
this.onFilterChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('keyup', ['$event']) onKeyUp (event: KeyboardEvent): void {
|
||||||
|
if (event.key === 'ArrowUp') {
|
||||||
|
this.selectedIndex--
|
||||||
|
}
|
||||||
|
if (event.key === 'ArrowDown') {
|
||||||
|
this.selectedIndex++
|
||||||
|
}
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
this.selectOption(this.filteredOptions[this.selectedIndex])
|
||||||
|
}
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
this.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectedIndex = (this.selectedIndex + this.filteredOptions.length) % this.filteredOptions.length
|
||||||
|
Array.from(this.itemChildren)[this.selectedIndex]?.nativeElement.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'nearest',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onFilterChange (): void {
|
||||||
|
const f = this.filter.trim().toLowerCase()
|
||||||
|
if (!f) {
|
||||||
|
this.filteredOptions = this.options.filter(x => !x.freeInputPattern)
|
||||||
|
} else {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
||||||
|
this.filteredOptions = this.options.filter(x => x.freeInputPattern || (x.name + (x.description || '')).toLowerCase().includes(f))
|
||||||
|
}
|
||||||
|
this.selectedIndex = Math.max(0, this.selectedIndex)
|
||||||
|
this.selectedIndex = Math.min(this.filteredOptions.length - 1, this.selectedIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
getOptionText (option: SelectorOption<T>): string {
|
||||||
|
if (option.freeInputPattern) {
|
||||||
|
return option.freeInputPattern.replace('%s', this.filter)
|
||||||
|
}
|
||||||
|
return option.name
|
||||||
|
}
|
||||||
|
|
||||||
|
selectOption (option: SelectorOption<T>): void {
|
||||||
|
option.callback?.(this.filter)
|
||||||
|
this.modalInstance.close(option.result)
|
||||||
|
}
|
||||||
|
|
||||||
|
close (): void {
|
||||||
|
this.modalInstance.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
iconIsSVG (icon: string): boolean {
|
||||||
|
return icon?.startsWith('<')
|
||||||
|
}
|
||||||
|
}
|
@@ -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,13 +1,13 @@
|
|||||||
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'
|
||||||
|
|
||||||
export declare type SplitOrientation = 'v' | 'h'
|
export type SplitOrientation = 'v' | 'h' // eslint-disable-line @typescript-eslint/no-type-alias
|
||||||
export declare type SplitDirection = 'r' | 't' | 'b' | 'l'
|
export type SplitDirection = 'r' | 't' | 'b' | 'l' // eslint-disable-line @typescript-eslint/no-type-alias
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes a horizontal or vertical split row or column
|
* Describes a horizontal or vertical split row or column
|
||||||
@@ -33,9 +33,9 @@ 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 (let 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())
|
||||||
} else {
|
} else {
|
||||||
@@ -48,9 +48,9 @@ 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++) {
|
||||||
let child = this.children[i]
|
const child = this.children[i]
|
||||||
|
|
||||||
if (child instanceof SplitContainer) {
|
if (child instanceof SplitContainer) {
|
||||||
child.normalize()
|
child.normalize()
|
||||||
@@ -63,7 +63,7 @@ export class SplitContainer {
|
|||||||
} else if (child.children.length === 1) {
|
} else if (child.children.length === 1) {
|
||||||
this.children[i] = child.children[0]
|
this.children[i] = child.children[0]
|
||||||
} else if (child.orientation === this.orientation) {
|
} else if (child.orientation === this.orientation) {
|
||||||
let ratio = this.ratios[i]
|
const ratio = this.ratios[i]
|
||||||
this.children.splice(i, 1)
|
this.children.splice(i, 1)
|
||||||
this.ratios.splice(i, 1)
|
this.ratios.splice(i, 1)
|
||||||
for (let j = 0; j < child.children.length; j++) {
|
for (let j = 0; j < child.children.length; j++) {
|
||||||
@@ -76,7 +76,7 @@ export class SplitContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let s = 0
|
let s = 0
|
||||||
for (let x of this.ratios) {
|
for (const x of this.ratios) {
|
||||||
s += x
|
s += x
|
||||||
}
|
}
|
||||||
this.ratios = this.ratios.map(x => x / s)
|
this.ratios = this.ratios.map(x => x / s)
|
||||||
@@ -93,9 +93,9 @@ export class SplitContainer {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
async serialize () {
|
async serialize (): Promise<RecoveryToken> {
|
||||||
let children = []
|
const children: any[] = []
|
||||||
for (let 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())
|
||||||
} else {
|
} else {
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -156,6 +158,7 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
_spanners: SplitSpannerInfo[] = []
|
_spanners: SplitSpannerInfo[] = []
|
||||||
|
|
||||||
private focusedTab: BaseTabComponent
|
private focusedTab: BaseTabComponent
|
||||||
|
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 +166,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 +181,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,
|
||||||
@@ -189,7 +198,11 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
|
|
||||||
this.focused$.subscribe(() => {
|
this.focused$.subscribe(() => {
|
||||||
this.getAllTabs().forEach(x => x.emitFocused())
|
this.getAllTabs().forEach(x => x.emitFocused())
|
||||||
this.focus(this.focusedTab)
|
if (this.focusedTab) {
|
||||||
|
this.focus(this.focusedTab)
|
||||||
|
} else {
|
||||||
|
this.focusAnyIn(this.root)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
this.blurred$.subscribe(() => this.getAllTabs().forEach(x => x.emitBlurred()))
|
this.blurred$.subscribe(() => this.getAllTabs().forEach(x => x.emitBlurred()))
|
||||||
|
|
||||||
@@ -198,53 +211,67 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch (hotkey) {
|
switch (hotkey) {
|
||||||
case 'split-right':
|
case 'split-right':
|
||||||
this.splitTab(this.focusedTab, 'r')
|
this.splitTab(this.focusedTab, 'r')
|
||||||
break
|
break
|
||||||
case 'split-bottom':
|
case 'split-bottom':
|
||||||
this.splitTab(this.focusedTab, 'b')
|
this.splitTab(this.focusedTab, 'b')
|
||||||
break
|
break
|
||||||
case 'split-top':
|
case 'split-top':
|
||||||
this.splitTab(this.focusedTab, 't')
|
this.splitTab(this.focusedTab, 't')
|
||||||
break
|
break
|
||||||
case 'split-left':
|
case 'split-left':
|
||||||
this.splitTab(this.focusedTab, 'l')
|
this.splitTab(this.focusedTab, 'l')
|
||||||
break
|
break
|
||||||
case 'split-nav-left':
|
case 'pane-nav-left':
|
||||||
this.navigate('l')
|
this.navigate('l')
|
||||||
break
|
break
|
||||||
case 'split-nav-right':
|
case 'pane-nav-right':
|
||||||
this.navigate('r')
|
this.navigate('r')
|
||||||
break
|
break
|
||||||
case 'split-nav-up':
|
case 'pane-nav-up':
|
||||||
this.navigate('t')
|
this.navigate('t')
|
||||||
break
|
break
|
||||||
case 'split-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':
|
||||||
|
this.removeTab(this.focusedTab)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
async ngOnInit () {
|
async ngAfterViewInit (): Promise<void> {
|
||||||
if (this._recoveredState) {
|
if (this._recoveredState) {
|
||||||
await this.recoverContainer(this.root, this._recoveredState)
|
await this.recoverContainer(this.root, this._recoveredState)
|
||||||
this.layout()
|
this.layout()
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
this.getAllTabs().forEach(x => x.emitFocused())
|
if (this.hasFocus) {
|
||||||
this.focusAnyIn(this.root)
|
this.getAllTabs().forEach(x => x.emitFocused())
|
||||||
|
this.focusAnyIn(this.root)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,9 +279,13 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
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 (let x of this.getAllTabs()) {
|
for (const x of this.getAllTabs()) {
|
||||||
if (x !== tab) {
|
if (x !== tab) {
|
||||||
x.emitBlurred()
|
x.emitBlurred()
|
||||||
}
|
}
|
||||||
@@ -263,13 +294,22 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
tab.emitFocused()
|
tab.emitFocused()
|
||||||
this.focusChanged.next(tab)
|
this.focusChanged.next(tab)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.maximizedTab !== tab) {
|
||||||
|
this.maximizedTab = null
|
||||||
|
}
|
||||||
|
this.layout()
|
||||||
|
}
|
||||||
|
|
||||||
|
maximize (tab: BaseTabComponent|null): void {
|
||||||
|
this.maximizedTab = tab
|
||||||
this.layout()
|
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
|
||||||
}
|
}
|
||||||
@@ -283,17 +323,19 @@ 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) ||
|
||||||
(target.orientation === 'h' && ['t', 'b'].includes(side))
|
target.orientation === 'h' && ['t', 'b'].includes(side)
|
||||||
) {
|
) {
|
||||||
let 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
|
||||||
@@ -303,7 +345,7 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
if (insertIndex === -1) {
|
if (insertIndex === -1) {
|
||||||
insertIndex = 0
|
insertIndex = 0
|
||||||
} else {
|
} else {
|
||||||
insertIndex += (side === 'l' || side === 't') ? 0 : 1
|
insertIndex += side === 'l' || side === 't' ? 0 : 1
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < target.children.length; i++) {
|
for (let i = 0; i < target.children.length; i++) {
|
||||||
@@ -313,6 +355,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(() => {
|
||||||
@@ -322,41 +367,53 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
removeTab (tab: BaseTabComponent) {
|
removeTab (tab: BaseTabComponent): void {
|
||||||
let parent = this.getParentOf(tab)
|
const parent = this.getParentOf(tab)
|
||||||
let index = parent.children.indexOf(tab)
|
if (!parent) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
this.focusAnyIn(parent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves focus in the given direction
|
* Moves focus in the given direction
|
||||||
*/
|
*/
|
||||||
navigate (dir: SplitDirection) {
|
navigate (dir: SplitDirection): void {
|
||||||
let rel: BaseTabComponent | SplitContainer = this.focusedTab
|
let rel: BaseTabComponent | SplitContainer = this.focusedTab
|
||||||
let parent = this.getParentOf(rel)
|
let parent = this.getParentOf(rel)
|
||||||
let orientation = ['l', 'r'].includes(dir) ? 'h' : 'v'
|
if (!parent) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let index = parent.children.indexOf(rel)
|
const index = parent.children.indexOf(rel)
|
||||||
if (['l', 't'].includes(dir)) {
|
if (['l', 't'].includes(dir)) {
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
this.focusAnyIn(parent.children[index - 1])
|
this.focusAnyIn(parent.children[index - 1])
|
||||||
@@ -368,19 +425,22 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async splitTab (tab: BaseTabComponent, dir: SplitDirection) {
|
async splitTab (tab: BaseTabComponent, dir: SplitDirection): Promise<BaseTabComponent|null> {
|
||||||
let 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 (let child of root.children) {
|
for (const child of root.children) {
|
||||||
if (child instanceof SplitContainer) {
|
if (child instanceof SplitContainer) {
|
||||||
let r = this.getParentOf(tab, child)
|
const r = this.getParentOf(tab, child)
|
||||||
if (r) {
|
if (r) {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
@@ -403,18 +463,25 @@ 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private attachTabView (tab: BaseTabComponent) {
|
private attachTabView (tab: BaseTabComponent) {
|
||||||
let ref = this.viewContainer.insert(tab.hostView) as EmbeddedViewRef<any>
|
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)
|
||||||
|
|
||||||
ref.rootNodes[0].addEventListener('click', () => this.focus(tab))
|
ref.rootNodes[0].addEventListener('click', () => this.focus(tab))
|
||||||
@@ -431,9 +498,11 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
}
|
}
|
||||||
|
|
||||||
private detachTabView (tab: BaseTabComponent) {
|
private detachTabView (tab: BaseTabComponent) {
|
||||||
let 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 () {
|
private layout () {
|
||||||
@@ -443,8 +512,8 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
}
|
}
|
||||||
|
|
||||||
private layoutInternal (root: SplitContainer, x: number, y: number, w: number, h: number) {
|
private layoutInternal (root: SplitContainer, x: number, y: number, w: number, h: number) {
|
||||||
let size = (root.orientation === 'v') ? h : w
|
const size = root.orientation === 'v' ? h : w
|
||||||
let sizes = root.ratios.map(x => x * size)
|
const sizes = root.ratios.map(x => x * size)
|
||||||
|
|
||||||
root.x = x
|
root.x = x
|
||||||
root.y = y
|
root.y = y
|
||||||
@@ -453,21 +522,32 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
|
|
||||||
let offset = 0
|
let offset = 0
|
||||||
root.children.forEach((child, i) => {
|
root.children.forEach((child, i) => {
|
||||||
let childX = (root.orientation === 'v') ? x : (x + offset)
|
const childX = root.orientation === 'v' ? x : x + offset
|
||||||
let childY = (root.orientation === 'v') ? (y + offset) : y
|
const childY = root.orientation === 'v' ? y + offset : y
|
||||||
let childW = (root.orientation === 'v') ? w : sizes[i]
|
const childW = root.orientation === 'v' ? w : sizes[i]
|
||||||
let childH = (root.orientation === 'v') ? sizes[i] : h
|
const childH = root.orientation === 'v' ? sizes[i] : h
|
||||||
if (child instanceof SplitContainer) {
|
if (child instanceof SplitContainer) {
|
||||||
this.layoutInternal(child, childX, childY, childW, childH)
|
this.layoutInternal(child, childX, childY, childW, childH)
|
||||||
} else {
|
} else {
|
||||||
let 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', 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]
|
||||||
|
|
||||||
@@ -481,33 +561,38 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async recoverContainer (root: SplitContainer, state: any) {
|
private async recoverContainer (root: SplitContainer, state: any) {
|
||||||
let children: (SplitContainer | BaseTabComponent)[] = []
|
const children: (SplitContainer | BaseTabComponent)[] = []
|
||||||
root.orientation = state.orientation
|
root.orientation = state.orientation
|
||||||
root.ratios = state.ratios
|
root.ratios = state.ratios
|
||||||
root.children = children
|
root.children = children
|
||||||
for (let childState of state.children) {
|
for (const childState of state.children) {
|
||||||
if (childState.type === 'app:split-tab') {
|
if (childState.type === 'app:split-tab') {
|
||||||
let child = new SplitContainer()
|
const child = new SplitContainer()
|
||||||
await this.recoverContainer(child, childState)
|
await this.recoverContainer(child, childState)
|
||||||
children.push(child)
|
children.push(child)
|
||||||
} else {
|
} else {
|
||||||
let recovered = await this.tabRecovery.recoverTab(childState)
|
const recovered = await this.tabRecovery.recoverTab(childState)
|
||||||
if (recovered) {
|
if (recovered) {
|
||||||
let 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 recover (recoveryToken: RecoveryToken): Promise<RecoveredTab|null> {
|
||||||
if (recoveryToken && recoveryToken.type === 'app:split-tab') {
|
if (recoveryToken && recoveryToken.type === 'app:split-tab') {
|
||||||
return {
|
return {
|
||||||
type: SplitTabComponent,
|
type: SplitTabComponent,
|
||||||
|
@@ -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,22 +17,26 @@ 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('mousedown', e => {
|
this.element.nativeElement.addEventListener('dblclick', () => {
|
||||||
this.isActive = true
|
this.reset()
|
||||||
let start = this.isVertical ? e.pageY : e.pageX
|
})
|
||||||
let current = start
|
|
||||||
let oldPosition = this.isVertical ? this.element.nativeElement.offsetTop : this.element.nativeElement.offsetLeft
|
|
||||||
|
|
||||||
const dragHandler = e => {
|
this.element.nativeElement.addEventListener('mousedown', (e: MouseEvent) => {
|
||||||
|
this.isActive = true
|
||||||
|
const start = this.isVertical ? e.pageY : e.pageX
|
||||||
|
let current = start
|
||||||
|
const oldPosition: number = this.isVertical ? this.element.nativeElement.offsetTop : this.element.nativeElement.offsetLeft
|
||||||
|
|
||||||
|
const dragHandler = (e: MouseEvent) => {
|
||||||
current = this.isVertical ? e.pageY : e.pageX
|
current = this.isVertical ? e.pageY : e.pageX
|
||||||
let 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`
|
||||||
} else {
|
} else {
|
||||||
@@ -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}%`
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
div
|
div
|
||||||
.terminus-logo
|
.terminus-logo
|
||||||
h1.terminus-title Terminus
|
h1.terminus-title Terminus
|
||||||
sup α
|
sup α
|
||||||
|
|
||||||
.list-group
|
.list-group
|
||||||
a.list-group-item.list-group-item-action.d-flex(
|
a.list-group-item.list-group-item-action.d-flex(
|
||||||
*ngFor='let button of getButtons()',
|
*ngFor='let button of getButtons()',
|
||||||
(click)='button.click()',
|
(click)='button.click()',
|
||||||
)
|
)
|
||||||
.d-flex.align-self-center([innerHTML]='button.icon')
|
.d-flex.align-self-center([innerHTML]='sanitizeIcon(button.icon)')
|
||||||
span {{button.title}}
|
span {{button.title}}
|
||||||
|
|
||||||
footer.d-flex.align-items-center
|
footer.d-flex.align-items-center
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: auto;
|
flex: auto;
|
||||||
-webkit-app-region: drag;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,10 +24,6 @@ footer {
|
|||||||
background: rgba(0,0,0,.5);
|
background: rgba(0,0,0,.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
a, button {
|
|
||||||
-webkit-app-region: no-drag;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-group-item ::ng-deep svg {
|
.list-group-item ::ng-deep svg {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
import { Component, Inject } from '@angular/core'
|
import { Component, Inject } from '@angular/core'
|
||||||
|
import { DomSanitizer } from '@angular/platform-browser'
|
||||||
import { ConfigService } from '../services/config.service'
|
import { ConfigService } from '../services/config.service'
|
||||||
import { HomeBaseService } from '../services/homeBase.service'
|
import { HomeBaseService } from '../services/homeBase.service'
|
||||||
import { IToolbarButton, ToolbarButtonProvider } from '../api'
|
import { ToolbarButton, ToolbarButtonProvider } from '../api'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Component({
|
@Component({
|
||||||
@@ -12,17 +13,23 @@ import { IToolbarButton, 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,
|
||||||
public homeBase: HomeBaseService,
|
public homeBase: HomeBaseService,
|
||||||
@Inject(ToolbarButtonProvider) private toolbarButtonProviders: ToolbarButtonProvider[],
|
@Inject(ToolbarButtonProvider) private toolbarButtonProviders: ToolbarButtonProvider[],
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
getButtons (): IToolbarButton[] {
|
getButtons (): ToolbarButton[] {
|
||||||
return this.config.enabledServices(this.toolbarButtonProviders)
|
return this.config.enabledServices(this.toolbarButtonProviders)
|
||||||
.map(provider => provider.provide())
|
.map(provider => provider.provide())
|
||||||
.reduce((a, b) => a.concat(b))
|
.reduce((a, b) => a.concat(b))
|
||||||
.sort((a: IToolbarButton, b: IToolbarButton) => (a.weight || 0) - (b.weight || 0))
|
.filter(x => !!x.click)
|
||||||
|
.sort((a: ToolbarButton, b: ToolbarButton) => (a.weight || 0) - (b.weight || 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
sanitizeIcon (icon: string): any {
|
||||||
|
return this.domSanitizer.bypassSecurityTrustHtml(icon || '')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import { Component, Input, ViewChild, HostBinding, ViewContainerRef, OnChanges } from '@angular/core'
|
import { Component, Input, ViewChild, HostBinding, ViewContainerRef, OnChanges } from '@angular/core'
|
||||||
import { BaseTabComponent } from '../components/baseTab.component'
|
import { BaseTabComponent } from '../components/baseTab.component'
|
||||||
|
|
||||||
|
@@ -20,7 +20,7 @@ $tabs-height: 38px;
|
|||||||
cursor: -webkit-grab;
|
cursor: -webkit-grab;
|
||||||
|
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
width: 20px;
|
width: 22px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
transition: 0.25s all;
|
transition: 0.25s all;
|
||||||
@@ -48,7 +48,7 @@ $tabs-height: 38px;
|
|||||||
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;
|
margin-right: 10px;
|
||||||
|
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import { Component, Input, Optional, Inject, HostBinding, HostListener, ViewChild, ElementRef } from '@angular/core'
|
import { Component, Input, Optional, Inject, HostBinding, HostListener, ViewChild, ElementRef } from '@angular/core'
|
||||||
import { SortableComponent } from 'ng2-dnd'
|
import { SortableComponent } from 'ng2-dnd'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
@@ -9,6 +10,11 @@ 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'
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
export interface SortableComponentProxy {
|
||||||
|
setDragHandle (_: HTMLElement)
|
||||||
|
}
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'tab-header',
|
selector: 'tab-header',
|
||||||
@@ -20,16 +26,16 @@ export class TabHeaderComponent {
|
|||||||
@Input() @HostBinding('class.active') active: boolean
|
@Input() @HostBinding('class.active') active: boolean
|
||||||
@Input() @HostBinding('class.has-activity') hasActivity: 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
|
||||||
|
|
||||||
constructor (
|
private constructor (
|
||||||
public app: AppService,
|
public app: AppService,
|
||||||
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 parentDraggable: SortableComponent,
|
@Inject(SortableComponent) private parentDraggable: SortableComponentProxy,
|
||||||
@Optional() @Inject(TabContextMenuItemProvider) protected contextMenuProviders: TabContextMenuItemProvider[],
|
@Optional() @Inject(TabContextMenuItemProvider) protected contextMenuProviders: TabContextMenuItemProvider[],
|
||||||
) {
|
) {
|
||||||
this.hotkeys.matchedHotkey.subscribe((hotkey) => {
|
this.hotkeys.matchedHotkey.subscribe((hotkey) => {
|
||||||
@@ -43,16 +49,19 @@ export class TabHeaderComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
if (this.hostApp.platform === Platform.macOS) {
|
|
||||||
this.parentDraggable.setDragHandle(this.handle.nativeElement)
|
|
||||||
}
|
|
||||||
this.tab.progress$.subscribe(progress => {
|
this.tab.progress$.subscribe(progress => {
|
||||||
this.progress = progress
|
this.progress = progress
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit () {
|
||||||
|
if (this.hostApp.platform === Platform.macOS) {
|
||||||
|
this.parentDraggable.setDragHandle(this.handle.nativeElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
showRenameTabModal (): void {
|
showRenameTabModal (): void {
|
||||||
let modal = this.ngbModal.open(RenameTabModalComponent)
|
const modal = this.ngbModal.open(RenameTabModalComponent)
|
||||||
modal.componentInstance.value = this.tab.customTitle || this.tab.title
|
modal.componentInstance.value = this.tab.customTitle || this.tab.title
|
||||||
modal.result.then(result => {
|
modal.result.then(result => {
|
||||||
this.tab.setTitle(result)
|
this.tab.setTitle(result)
|
||||||
@@ -62,7 +71,7 @@ export class TabHeaderComponent {
|
|||||||
|
|
||||||
async buildContextMenu (): Promise<Electron.MenuItemConstructorOptions[]> {
|
async buildContextMenu (): Promise<Electron.MenuItemConstructorOptions[]> {
|
||||||
let items: Electron.MenuItemConstructorOptions[] = []
|
let items: Electron.MenuItemConstructorOptions[] = []
|
||||||
for (let 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)
|
||||||
}
|
}
|
||||||
@@ -73,19 +82,27 @@ export class TabHeaderComponent {
|
|||||||
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)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('auxclick', ['$event']) async onAuxClick ($event: MouseEvent) {
|
||||||
if ($event.which === 3) {
|
if ($event.which === 3) {
|
||||||
event.preventDefault()
|
$event.preventDefault()
|
||||||
|
|
||||||
const contextMenu = this.electron.remote.Menu.buildFromTemplate(await this.buildContextMenu())
|
const contextMenu = this.electron.remote.Menu.buildFromTemplate(await this.buildContextMenu())
|
||||||
|
|
||||||
contextMenu.popup({
|
contextMenu.popup({
|
||||||
x: $event.pageX,
|
x: $event.pageX,
|
||||||
y: $event.pageY,
|
y: $event.pageY,
|
||||||
async: true,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,4 +6,4 @@ import { Component } from '@angular/core'
|
|||||||
template: require('./titleBar.component.pug'),
|
template: require('./titleBar.component.pug'),
|
||||||
styles: [require('./titleBar.component.scss')],
|
styles: [require('./titleBar.component.scss')],
|
||||||
})
|
})
|
||||||
export class TitleBarComponent { }
|
export class TitleBarComponent { } // eslint-disable-line @typescript-eslint/no-extraneous-class
|
||||||
|
@@ -16,55 +16,8 @@
|
|||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
margin-left: -10px;
|
margin-left: -10px;
|
||||||
|
|
||||||
&:focus {
|
|
||||||
background: rgba(255,255,255,.05);
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[disabled] {
|
&[disabled] {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.body {
|
|
||||||
$border-width: 2px;
|
|
||||||
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,18 +6,15 @@ 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: [
|
||||||
{ provide: NG_VALUE_ACCESSOR, useExisting: ToggleComponent, multi: true },
|
{ provide: NG_VALUE_ACCESSOR, useExisting: ToggleComponent, multi: true },
|
||||||
]
|
],
|
||||||
})
|
})
|
||||||
export class ToggleComponent extends CheckboxComponent {
|
export class ToggleComponent extends CheckboxComponent {
|
||||||
}
|
}
|
||||||
|