mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-11-07 01:44:55 +00:00
Compare commits
1015 Commits
map-events
...
v1.75.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8d487639f | ||
|
|
cecfbb5375 | ||
|
|
8d35500e2f | ||
|
|
5dad5d8e03 | ||
|
|
7be64bde02 | ||
|
|
48eb7552a9 | ||
|
|
5347b0060c | ||
|
|
b826c03226 | ||
|
|
1c211a8667 | ||
|
|
fd4d5b90e2 | ||
|
|
1ee9f26b34 | ||
|
|
da1762934b | ||
|
|
511457c761 | ||
|
|
29b4cedb81 | ||
|
|
585de15e6b | ||
|
|
a9bf118f3a | ||
|
|
6d5a432bad | ||
|
|
f1f12abd16 | ||
|
|
09880a54e9 | ||
|
|
0f6847b16d | ||
|
|
ce82ed97f5 | ||
|
|
36b393dbde | ||
|
|
524c283a0d | ||
|
|
afda53a9bc | ||
|
|
1310d75012 | ||
|
|
80bbde549d | ||
|
|
2451487593 | ||
|
|
ecd626f105 | ||
|
|
123b312965 | ||
|
|
e94de8e629 | ||
|
|
956a5a04ca | ||
|
|
affeb7c624 | ||
|
|
e457d94df8 | ||
|
|
e9583c928e | ||
|
|
89c14628e1 | ||
|
|
ffba407eaf | ||
|
|
33f710127c | ||
|
|
7a82b2c102 | ||
|
|
2db2a47186 | ||
|
|
63faa43c1d | ||
|
|
eabb0e8470 | ||
|
|
9f75ae6b03 | ||
|
|
a1f28cd245 | ||
|
|
90a04b517e | ||
|
|
9f6e6a333f | ||
|
|
7b9e2c4fd9 | ||
|
|
63f13711cc | ||
|
|
c65b8e5ebd | ||
|
|
bfed1480ae | ||
|
|
5ff902f185 | ||
|
|
8d38345c7f | ||
|
|
14be9dbb8a | ||
|
|
720c26db94 | ||
|
|
6d0b8b845d | ||
|
|
b2767e000e | ||
|
|
169f23c2ca | ||
|
|
81f70eafff | ||
|
|
650170498a | ||
|
|
8b6f600989 | ||
|
|
fe3617b39f | ||
|
|
0f466c51ba | ||
|
|
a1a641bce3 | ||
|
|
4764c25eb1 | ||
|
|
d390455cf2 | ||
|
|
7fb8d24d73 | ||
|
|
472dbaa68b | ||
|
|
f03448007d | ||
|
|
c317a8bff9 | ||
|
|
618cca39a4 | ||
|
|
fe7a98098f | ||
|
|
df49939990 | ||
|
|
f23f2776f4 | ||
|
|
4419c86164 | ||
|
|
9848f49b49 | ||
|
|
679bd782a8 | ||
|
|
6a316e3906 | ||
|
|
c129db8474 | ||
|
|
10035b4c91 | ||
|
|
5839271de7 | ||
|
|
47db8ef709 | ||
|
|
2656491aaa | ||
|
|
a7637c9cae | ||
|
|
7b83ed8205 | ||
|
|
00cbc77f1d | ||
|
|
4d75b256c4 | ||
|
|
5aeff7c40c | ||
|
|
6a543bf644 | ||
|
|
dfb035525d | ||
|
|
4c23069a0a | ||
|
|
4a1d7be44c | ||
|
|
798aec1b74 | ||
|
|
7914d7e151 | ||
|
|
26d0392da1 | ||
|
|
83b1406cce | ||
|
|
8b579d6837 | ||
|
|
c0fd20dfff | ||
|
|
dd6b67c6e6 | ||
|
|
fa83185cf5 | ||
|
|
97d5010d41 | ||
|
|
e73ad93920 | ||
|
|
425af246fb | ||
|
|
a2912ba0ff | ||
|
|
48ff2f4413 | ||
|
|
61cd281a18 | ||
|
|
6e28134282 | ||
|
|
d1377f44d2 | ||
|
|
d261c6186b | ||
|
|
2a72a2612d | ||
|
|
66bb4f87d4 | ||
|
|
977b1ad083 | ||
|
|
94db18d42b | ||
|
|
7e0375108d | ||
|
|
094a5d7b62 | ||
|
|
8f947a5f04 | ||
|
|
5580ad62f9 | ||
|
|
c0953dc954 | ||
|
|
1df93da564 | ||
|
|
e2252a9d72 | ||
|
|
7cdba4b507 | ||
|
|
b110d5afec | ||
|
|
6112b3e399 | ||
|
|
af0869a39b | ||
|
|
d44c339990 | ||
|
|
0304f92ad9 | ||
|
|
4a41d6e5d5 | ||
|
|
30893ca68e | ||
|
|
1edd02fa5c | ||
|
|
10a957ff0d | ||
|
|
35b1b3619d | ||
|
|
777ebd0c41 | ||
|
|
9e7d8c08e1 | ||
|
|
ad3f4cda09 | ||
|
|
98502cc6ae | ||
|
|
0bac671eb0 | ||
|
|
09c9a1e752 | ||
|
|
41e77e8336 | ||
|
|
a6e9fee2a0 | ||
|
|
c403a1cee5 | ||
|
|
02d25b370a | ||
|
|
e5a3eec8a1 | ||
|
|
910352d66c | ||
|
|
c4f02e7d55 | ||
|
|
6a1197ad83 | ||
|
|
84c31bbb88 | ||
|
|
33f6c32306 | ||
|
|
5c71304d41 | ||
|
|
bbaf04e977 | ||
|
|
ad5b2d2eb3 | ||
|
|
3c064baf8a | ||
|
|
9e11b10d74 | ||
|
|
2fc45e00b4 | ||
|
|
45eb08fc3a | ||
|
|
fbcfae0200 | ||
|
|
9c4ce013ec | ||
|
|
5dba7c12f0 | ||
|
|
db793c80c8 | ||
|
|
aa4a3f1aa9 | ||
|
|
3424639309 | ||
|
|
0f309a29ba | ||
|
|
e13b8846b8 | ||
|
|
d5ea4d6129 | ||
|
|
9d50bfedbd | ||
|
|
b03410083c | ||
|
|
a314b1e448 | ||
|
|
e8a51a85c4 | ||
|
|
d4074f966c | ||
|
|
1413b41824 | ||
|
|
379c1edec3 | ||
|
|
58b5bade9e | ||
|
|
71aee4cd3e | ||
|
|
10bab0cfa1 | ||
|
|
698350b0f7 | ||
|
|
a97cf25031 | ||
|
|
8302d088bd | ||
|
|
64788e73de | ||
|
|
114fd471e8 | ||
|
|
b24a3120d3 | ||
|
|
c5f93b3d0a | ||
|
|
79290e4721 | ||
|
|
984e126f23 | ||
|
|
cd1ad31aed | ||
|
|
1e3f6cf9e7 | ||
|
|
9c6ccd9a8a | ||
|
|
681ba21d39 | ||
|
|
aef62189ee | ||
|
|
09f70ac817 | ||
|
|
1eacb22143 | ||
|
|
8524bad377 | ||
|
|
9d899243d1 | ||
|
|
9acf20a639 | ||
|
|
71ef6b2e82 | ||
|
|
5e34d95dd2 | ||
|
|
25a809c064 | ||
|
|
f760498150 | ||
|
|
328301a375 | ||
|
|
f28e7ebbbb | ||
|
|
bfa84af71e | ||
|
|
42cd1ba976 | ||
|
|
88cba866fd | ||
|
|
af2876a84b | ||
|
|
c5b15bfa78 | ||
|
|
85f00a63c2 | ||
|
|
05f427bcd7 | ||
|
|
69f4c41534 | ||
|
|
30b9239a8b | ||
|
|
2061a83c59 | ||
|
|
24e723de07 | ||
|
|
27b5694885 | ||
|
|
4093f28cee | ||
|
|
08aaf2f2dd | ||
|
|
5a927e5ba5 | ||
|
|
10fafcf59f | ||
|
|
be87591801 | ||
|
|
086d4378d3 | ||
|
|
e982275905 | ||
|
|
77c02703e9 | ||
|
|
0ef27d4f95 | ||
|
|
5edc27744e | ||
|
|
02ff887fee | ||
|
|
3a30eeb59f | ||
|
|
79af8fb601 | ||
|
|
f9f00faa0e | ||
|
|
a3c41e84e4 | ||
|
|
7f21f33351 | ||
|
|
568f682cee | ||
|
|
901c4c8ca4 | ||
|
|
3dbba97f9c | ||
|
|
3475620267 | ||
|
|
8936a5e5d8 | ||
|
|
719e34f9bc | ||
|
|
df955ff8b0 | ||
|
|
4dc74022b4 | ||
|
|
c2b7d07208 | ||
|
|
21e76a6f05 | ||
|
|
89c0d6fad6 | ||
|
|
b74d15b1ee | ||
|
|
10313438bf | ||
|
|
a764217948 | ||
|
|
d81d6fb937 | ||
|
|
4c0f7ab7f9 | ||
|
|
1c48945a96 | ||
|
|
850901f62f | ||
|
|
4822854e30 | ||
|
|
f580538331 | ||
|
|
0d70c555e6 | ||
|
|
c5f6cf0080 | ||
|
|
6ff7b3bc9a | ||
|
|
346c2c65b0 | ||
|
|
a6445fd500 | ||
|
|
358e43e508 | ||
|
|
20c5ba6b63 | ||
|
|
661658a6e8 | ||
|
|
0a6f224ed3 | ||
|
|
e7bb29693f | ||
|
|
32dfd50461 | ||
|
|
bfec385dce | ||
|
|
04278f99d7 | ||
|
|
9d3db19dc1 | ||
|
|
3953e33f37 | ||
|
|
611fdd56d0 | ||
|
|
36a4fd0f35 | ||
|
|
488984a988 | ||
|
|
1b183d6e58 | ||
|
|
3dcb6d30b5 | ||
|
|
1d11788f89 | ||
|
|
d3822128ab | ||
|
|
6ac7836505 | ||
|
|
5796fccae4 | ||
|
|
22ab6e544c | ||
|
|
5e735ab8bd | ||
|
|
450139402d | ||
|
|
1e0d4f1fde | ||
|
|
7e9b1af3a3 | ||
|
|
30b90cd4be | ||
|
|
f28affa222 | ||
|
|
2ae7b187b8 | ||
|
|
ad037be1f4 | ||
|
|
9e31542c5f | ||
|
|
aae6ed0cb3 | ||
|
|
7ea2ecb8e9 | ||
|
|
852fc28896 | ||
|
|
fcdab79802 | ||
|
|
bb0e06589f | ||
|
|
d65b72c4dd | ||
|
|
24a3d5b3de | ||
|
|
2814b46941 | ||
|
|
6ffc25448d | ||
|
|
d9a82f7c9f | ||
|
|
9a8106947e | ||
|
|
e21ca79ea1 | ||
|
|
dd579caeac | ||
|
|
ddf8a4b9fb | ||
|
|
3c04f4194e | ||
|
|
1e0de841eb | ||
|
|
c9c88cf0a6 | ||
|
|
fd1e124166 | ||
|
|
aa2bee258a | ||
|
|
12d696dc07 | ||
|
|
b33772a1d6 | ||
|
|
b41f89d7de | ||
|
|
e8ca213b74 | ||
|
|
7620b8d493 | ||
|
|
23306fd9e3 | ||
|
|
a68ccdca50 | ||
|
|
771e432546 | ||
|
|
20bdbfb81a | ||
|
|
90729b436c | ||
|
|
8ea4e97bbf | ||
|
|
17e12e9263 | ||
|
|
4cb3d021f4 | ||
|
|
97cec2e127 | ||
|
|
9c4294659c | ||
|
|
5b8526db96 | ||
|
|
54cce9e9fb | ||
|
|
54d1691d19 | ||
|
|
4d4431868e | ||
|
|
cdae69d346 | ||
|
|
3e86baabd8 | ||
|
|
6ec92b19fb | ||
|
|
630caa8686 | ||
|
|
497c3b2165 | ||
|
|
8823d06893 | ||
|
|
72a2bf50df | ||
|
|
15c1ed2011 | ||
|
|
3fb19663cc | ||
|
|
00cdbbc89c | ||
|
|
07f3cec16d | ||
|
|
e893e25ff4 | ||
|
|
1ad9a1eb13 | ||
|
|
d9288596e8 | ||
|
|
a2a642d9ce | ||
|
|
4f00007108 | ||
|
|
816ee77b6f | ||
|
|
26d470ecda | ||
|
|
3babd9d95e | ||
|
|
802e81b1cd | ||
|
|
41f0834c51 | ||
|
|
880de0b047 | ||
|
|
bbe7fda4e0 | ||
|
|
4fd214e328 | ||
|
|
92a9274dce | ||
|
|
8765d83083 | ||
|
|
a298152bc8 | ||
|
|
2b7abe5774 | ||
|
|
3e9241892e | ||
|
|
a8dcdcf339 | ||
|
|
6ea79a7960 | ||
|
|
2af562e313 | ||
|
|
064a36fcbb | ||
|
|
40672f6a47 | ||
|
|
6d66ae3f50 | ||
|
|
94c89e0325 | ||
|
|
3670ef40a3 | ||
|
|
16d464fba5 | ||
|
|
0b7e0b9cd0 | ||
|
|
dd5fd114d2 | ||
|
|
6e53879344 | ||
|
|
af2bfd4d59 | ||
|
|
a4a34c8ba7 | ||
|
|
8c609f4fdf | ||
|
|
197f5b583f | ||
|
|
4eb4a03e59 | ||
|
|
5719469452 | ||
|
|
6e9d525890 | ||
|
|
e82805dd48 | ||
|
|
3d4e66d438 | ||
|
|
ffbc9f169a | ||
|
|
99650187e9 | ||
|
|
92699317cd | ||
|
|
0e48315803 | ||
|
|
868ec246bd | ||
|
|
0030a688c6 | ||
|
|
3ba8f51a2f | ||
|
|
04576b335c | ||
|
|
ea29aa176f | ||
|
|
9a9b7289ba | ||
|
|
5edcd5572a | ||
|
|
d601790864 | ||
|
|
3cd9b819f5 | ||
|
|
bf58d3ae93 | ||
|
|
d6c32e2d39 | ||
|
|
6914f75bb7 | ||
|
|
3adf3946b5 | ||
|
|
bdc4948afb | ||
|
|
331db10029 | ||
|
|
c036a157c8 | ||
|
|
2daf9e34d2 | ||
|
|
acf35f8c51 | ||
|
|
9155515082 | ||
|
|
558cd9b8b3 | ||
|
|
a0f02d0d2f | ||
|
|
9feb8492aa | ||
|
|
e5aa726899 | ||
|
|
93d1c28ccd | ||
|
|
b5ba9200bc | ||
|
|
699d866670 | ||
|
|
c3071344cb | ||
|
|
9e998dd2b6 | ||
|
|
c9accf6079 | ||
|
|
1b41a51004 | ||
|
|
3338dce900 | ||
|
|
1364779f81 | ||
|
|
b49d3423fc | ||
|
|
cccab2a985 | ||
|
|
1abaa90a7d | ||
|
|
6e1993ca8a | ||
|
|
171c821ac4 | ||
|
|
7ebf9186bf | ||
|
|
57d2f2baef | ||
|
|
0aee13878a | ||
|
|
f93ef0ca76 | ||
|
|
4ec03d8338 | ||
|
|
cb318aa6c6 | ||
|
|
733482cd5c | ||
|
|
3969d1287d | ||
|
|
1aa7854b0d | ||
|
|
7b27d4a1a7 | ||
|
|
24ddb8771f | ||
|
|
7134714245 | ||
|
|
96b320ac26 | ||
|
|
61235828ce | ||
|
|
1a27b21efe | ||
|
|
b88e121b30 | ||
|
|
4ba4119c2b | ||
|
|
91d1ca201c | ||
|
|
8bf063a228 | ||
|
|
4f53de39b1 | ||
|
|
8c3804f107 | ||
|
|
1be4ec2b90 | ||
|
|
8f0ed44b11 | ||
|
|
cbadfc4ac4 | ||
|
|
3d88ae4452 | ||
|
|
e57f565812 | ||
|
|
da2605ee03 | ||
|
|
07e2196eb4 | ||
|
|
6d99c54af7 | ||
|
|
2b7901e9a8 | ||
|
|
fb06dd1dbc | ||
|
|
d3b825529e | ||
|
|
ccf9c0db22 | ||
|
|
f8ba36b8be | ||
|
|
927c07bfd5 | ||
|
|
5bf9d99b3d | ||
|
|
7cad05342a | ||
|
|
867780e525 | ||
|
|
ff4f9a79c9 | ||
|
|
6699c36fb3 | ||
|
|
abd4556994 | ||
|
|
ccf0d17371 | ||
|
|
898584bbb6 | ||
|
|
6d7a267e39 | ||
|
|
9f656ca3cb | ||
|
|
d00caf1f4c | ||
|
|
a5e9d72bc5 | ||
|
|
8ac9047831 | ||
|
|
fede6451e2 | ||
|
|
9797ad380c | ||
|
|
33bc4a4d22 | ||
|
|
65509ace59 | ||
|
|
ea173f971a | ||
|
|
6378754c57 | ||
|
|
30fc972d78 | ||
|
|
c022b31c79 | ||
|
|
049b06bb39 | ||
|
|
e17d5213c0 | ||
|
|
dcf681941e | ||
|
|
1cd7d40405 | ||
|
|
fbd80ba2c7 | ||
|
|
88ab85bd04 | ||
|
|
78f98744fd | ||
|
|
9c9634a927 | ||
|
|
be47be626c | ||
|
|
2fbd3d8e19 | ||
|
|
d5c3d4c051 | ||
|
|
fac60f7ddd | ||
|
|
c371478c61 | ||
|
|
5911e29f34 | ||
|
|
c45c97c5d8 | ||
|
|
99d68dfc0e | ||
|
|
c9b366f3e2 | ||
|
|
4e732e9491 | ||
|
|
dd5b12aa38 | ||
|
|
7bd960fba9 | ||
|
|
c338c33902 | ||
|
|
df6b7ae635 | ||
|
|
a3346f8223 | ||
|
|
196f2c2c3b | ||
|
|
77d549ac1b | ||
|
|
5c3cce66c1 | ||
|
|
cc2f09601e | ||
|
|
14af04cc73 | ||
|
|
37c9e68c21 | ||
|
|
2bd343b2da | ||
|
|
f5d502c5ad | ||
|
|
35cf460ccf | ||
|
|
28c7b90c3f | ||
|
|
4fbdaf42e1 | ||
|
|
90910620d9 | ||
|
|
eb4336fef7 | ||
|
|
69264cc8ec | ||
|
|
ab0cb74ca9 | ||
|
|
42101ab6fd | ||
|
|
8d69c70076 | ||
|
|
beb3077159 | ||
|
|
ecb3ca2b4e | ||
|
|
2ba42e0c25 | ||
|
|
3ef5590e18 | ||
|
|
8412e3867d | ||
|
|
90c40100d1 | ||
|
|
92cb49da90 | ||
|
|
abc09c067f | ||
|
|
edbd1e4bbc | ||
|
|
75edb91825 | ||
|
|
602a61b08d | ||
|
|
d8222d83f0 | ||
|
|
7da5512d45 | ||
|
|
8bf9ae7824 | ||
|
|
f57777e417 | ||
|
|
b3cc3d857a | ||
|
|
bf442d9e70 | ||
|
|
1a556d05ba | ||
|
|
dab301e6d3 | ||
|
|
8ab4b4c788 | ||
|
|
4b29060c96 | ||
|
|
8a5f96a847 | ||
|
|
149fa57075 | ||
|
|
affe184ccd | ||
|
|
1e5e73c4ae | ||
|
|
c76316da03 | ||
|
|
de6205f860 | ||
|
|
f994255091 | ||
|
|
6d4981a3db | ||
|
|
06fef2296f | ||
|
|
999a702291 | ||
|
|
020b9bb2c2 | ||
|
|
7713caab51 | ||
|
|
97a777d729 | ||
|
|
8241d1f08c | ||
|
|
2ac85bbfff | ||
|
|
3f68ae2235 | ||
|
|
0f7b6f75df | ||
|
|
b048e8f5ca | ||
|
|
9783dc45ff | ||
|
|
badbefbade | ||
|
|
b6a265cfad | ||
|
|
9b5ea2f84b | ||
|
|
d8acfa5c05 | ||
|
|
2a5b6924eb | ||
|
|
3b9aee1eb9 | ||
|
|
83801c9063 | ||
|
|
0f34350c58 | ||
|
|
1c4c0f0715 | ||
|
|
3825fc831a | ||
|
|
654670cbc8 | ||
|
|
947570072c | ||
|
|
01b6b45380 | ||
|
|
b9dc1f8357 | ||
|
|
b4bd810c9d | ||
|
|
490b037920 | ||
|
|
cdff5458bc | ||
|
|
09314a09e9 | ||
|
|
49ea8edb27 | ||
|
|
86e5ff2fff | ||
|
|
1ade0ae6b9 | ||
|
|
cadfb59b8d | ||
|
|
5c2013f19c | ||
|
|
8db46113f4 | ||
|
|
3028a0b1c0 | ||
|
|
e9b4e39061 | ||
|
|
9c6715e4e5 | ||
|
|
0b03d5ee90 | ||
|
|
30fecf6428 | ||
|
|
752eaaa0f5 | ||
|
|
006d10381f | ||
|
|
a1ffe3cc0e | ||
|
|
b4a1cbbf55 | ||
|
|
b2ae5a33ae | ||
|
|
aec0a75e87 | ||
|
|
7086413f0c | ||
|
|
d55f03b63c | ||
|
|
aa4fd2fe90 | ||
|
|
6abf628a38 | ||
|
|
ad46002c85 | ||
|
|
2f21bd0f44 | ||
|
|
993608f911 | ||
|
|
c6c6adb7d8 | ||
|
|
3937330ce4 | ||
|
|
1590c848c9 | ||
|
|
2bb45b312c | ||
|
|
1fc95c96eb | ||
|
|
ee7a453a72 | ||
|
|
4b79afbac0 | ||
|
|
c8fc31257b | ||
|
|
8e0b8fd7f9 | ||
|
|
ee8f9e4d24 | ||
|
|
994e03945d | ||
|
|
aff00a18b5 | ||
|
|
6c22e6554d | ||
|
|
2a0d7654e7 | ||
|
|
4eb1f641ae | ||
|
|
2da5a243ec | ||
|
|
5ac8ccbe5c | ||
|
|
0568533550 | ||
|
|
178abc2af2 | ||
|
|
adb2a5f459 | ||
|
|
ada1571e1e | ||
|
|
5931c00ff3 | ||
|
|
a6e7c1bf74 | ||
|
|
1a5374f2f6 | ||
|
|
c9e3683b8e | ||
|
|
aba93b342a | ||
|
|
dee78b77a9 | ||
|
|
d21705f355 | ||
|
|
9abcd4bd0b | ||
|
|
b052943e34 | ||
|
|
e1e9b4c2e8 | ||
|
|
42c30e0741 | ||
|
|
3b45e77e65 | ||
|
|
dcb2b6b912 | ||
|
|
638a4e2535 | ||
|
|
489fde16d1 | ||
|
|
35e1c363e5 | ||
|
|
6b97d36bf1 | ||
|
|
82f6a7f701 | ||
|
|
2d92dfbafa | ||
|
|
f81f41f555 | ||
|
|
54c7b44d69 | ||
|
|
9da6605ccb | ||
|
|
a90bf9762a | ||
|
|
c87cfb3c43 | ||
|
|
85cb9ccfa8 | ||
|
|
da2639786d | ||
|
|
3cf77da293 | ||
|
|
3dd7633194 | ||
|
|
ae7f4edf4a | ||
|
|
52eab28f27 | ||
|
|
6098d32bce | ||
|
|
1839834771 | ||
|
|
7cdfb87853 | ||
|
|
3d54783a3e | ||
|
|
f965461820 | ||
|
|
6d67f87d4b | ||
|
|
60697a50c2 | ||
|
|
778d23da06 | ||
|
|
0ee9a15d5d | ||
|
|
24bb902bb9 | ||
|
|
32fe6395a1 | ||
|
|
5f506bf4b2 | ||
|
|
0127ebfe46 | ||
|
|
8c5366fd9b | ||
|
|
dbcad892a9 | ||
|
|
6da3096db1 | ||
|
|
cd8efcd6e3 | ||
|
|
b52471ae5e | ||
|
|
438fecb61f | ||
|
|
70b589a359 | ||
|
|
cf7069b3b2 | ||
|
|
b2198e469e | ||
|
|
8ab337e8e7 | ||
|
|
51878ab503 | ||
|
|
401dfad298 | ||
|
|
18cff7d312 | ||
|
|
7896de00d6 | ||
|
|
3b079505c3 | ||
|
|
5b972b03e5 | ||
|
|
79b284c46d | ||
|
|
b29e57b3a4 | ||
|
|
c6f4baeee3 | ||
|
|
6d341be072 | ||
|
|
2437ec9c84 | ||
|
|
7e692b5805 | ||
|
|
01b7370ecd | ||
|
|
20ad8b07d7 | ||
|
|
cab1880fb0 | ||
|
|
78eefcd6a7 | ||
|
|
eec78d38a8 | ||
|
|
73f8b1f06b | ||
|
|
f96cb01860 | ||
|
|
6800be1bb6 | ||
|
|
143f0a5b3a | ||
|
|
b6495504f8 | ||
|
|
2f07ec1b74 | ||
|
|
7073a0e8e6 | ||
|
|
bb0d91a3c7 | ||
|
|
1cb12b97ba | ||
|
|
860d20dc66 | ||
|
|
a850071965 | ||
|
|
fc41573e70 | ||
|
|
97f1808fb5 | ||
|
|
d31046eebb | ||
|
|
a70fa50eab | ||
|
|
9a082c26f5 | ||
|
|
6af2dc1ed5 | ||
|
|
5fd1509d44 | ||
|
|
2448c0531b | ||
|
|
b685ea1013 | ||
|
|
55465688c8 | ||
|
|
ac3c7e0c44 | ||
|
|
2d6ab5646c | ||
|
|
67b373ac29 | ||
|
|
678169e6fa | ||
|
|
7ee3c8db82 | ||
|
|
304f4b01ab | ||
|
|
4af12c21b2 | ||
|
|
497da1e5f7 | ||
|
|
5bd968acae | ||
|
|
f74c20142c | ||
|
|
d4c40d7542 | ||
|
|
04f3fec0c0 | ||
|
|
cd0b4b0fc9 | ||
|
|
e7b115e6e6 | ||
|
|
dff8fc6396 | ||
|
|
afdaeb3d34 | ||
|
|
ac6053361e | ||
|
|
eb3e1ba3aa | ||
|
|
8468a9b5de | ||
|
|
5eafe59dcb | ||
|
|
b38bcaa8cf | ||
|
|
8a238a447d | ||
|
|
3731219216 | ||
|
|
73d5fd5f67 | ||
|
|
e8e4aed6d5 | ||
|
|
63571a462f | ||
|
|
606add4142 | ||
|
|
dac480b059 | ||
|
|
5f67cb1dd7 | ||
|
|
5886fff753 | ||
|
|
da2e12bdd1 | ||
|
|
05c3d20e56 | ||
|
|
4633d26517 | ||
|
|
30b0556d47 | ||
|
|
e094378dc5 | ||
|
|
0c48189503 | ||
|
|
a5c346627a | ||
|
|
4e526040bf | ||
|
|
869c25cd60 | ||
|
|
6aac698cd8 | ||
|
|
230016b90f | ||
|
|
4b1aef8dd9 | ||
|
|
d34509d7a0 | ||
|
|
fca98ec232 | ||
|
|
e2814e95bd | ||
|
|
68a3f84704 | ||
|
|
4bc76feefc | ||
|
|
da39a55fd0 | ||
|
|
ee3cf04cd4 | ||
|
|
d79e7fe2ff | ||
|
|
8de9fdef32 | ||
|
|
f51deeec2d | ||
|
|
a971c69a96 | ||
|
|
b7995f50de | ||
|
|
14997a2959 | ||
|
|
8fef6bcf82 | ||
|
|
1f82d23963 | ||
|
|
28317a2431 | ||
|
|
6aac496a57 | ||
|
|
ac9306b713 | ||
|
|
d55e804efa | ||
|
|
08407a5679 | ||
|
|
c37d175bec | ||
|
|
69c5326e72 | ||
|
|
305f63e11d | ||
|
|
698fd5e083 | ||
|
|
1af8342d30 | ||
|
|
68b59da78e | ||
|
|
e784a3f850 | ||
|
|
a45e2f3fc2 | ||
|
|
8a3d920c31 | ||
|
|
996d7c47bd | ||
|
|
8d2b9db430 | ||
|
|
423ce343c7 | ||
|
|
1c17912d9f | ||
|
|
6714eb5d9b | ||
|
|
1620e1fd21 | ||
|
|
859014874f | ||
|
|
ef44881f06 | ||
|
|
b0532325fa | ||
|
|
2c00bd426e | ||
|
|
6eccf2ac67 | ||
|
|
973a1e54b3 | ||
|
|
2b42b637df | ||
|
|
b950572818 | ||
|
|
e470a210f1 | ||
|
|
71ec2d413c | ||
|
|
9122412558 | ||
|
|
0ba5c963b4 | ||
|
|
39a0ce284f | ||
|
|
f9d580dbc0 | ||
|
|
5c41574328 | ||
|
|
f17d74c8b7 | ||
|
|
c88854c54c | ||
|
|
f3779961d6 | ||
|
|
d93fc29734 | ||
|
|
c67918aca5 | ||
|
|
a9f276c95a | ||
|
|
7cee4894a5 | ||
|
|
edf8bef813 | ||
|
|
2081218398 | ||
|
|
b100052453 | ||
|
|
71636e895e | ||
|
|
7ff9689b76 | ||
|
|
5a4d819622 | ||
|
|
3117d85648 | ||
|
|
114133ecd2 | ||
|
|
bf8a1197e4 | ||
|
|
54c06a1fc0 | ||
|
|
e77a42dfda | ||
|
|
f83b4a2ba7 | ||
|
|
d34e7b8d8a | ||
|
|
fa0c7f3c66 | ||
|
|
5f58645b41 | ||
|
|
7ae0ec7573 | ||
|
|
b1149cecaf | ||
|
|
8f28d2be65 | ||
|
|
d758b54ef8 | ||
|
|
58293b4dc4 | ||
|
|
f2083f4256 | ||
|
|
6c7bd5804e | ||
|
|
483ae21e89 | ||
|
|
fc36d51e24 | ||
|
|
f734565844 | ||
|
|
8c718ba181 | ||
|
|
8aaa2e7add | ||
|
|
c8d8734601 | ||
|
|
5c757e8255 | ||
|
|
22f608f302 | ||
|
|
82f90ef759 | ||
|
|
167c8eea6b | ||
|
|
d76079d4c7 | ||
|
|
bf9c4cda02 | ||
|
|
af00402546 | ||
|
|
a245842ca4 | ||
|
|
8ddd672f13 | ||
|
|
92f471c0b0 | ||
|
|
9e2a2c5b44 | ||
|
|
5f5d3df003 | ||
|
|
c66cc8868e | ||
|
|
0d6528ce4f | ||
|
|
34c385ac5f | ||
|
|
b6d12e73a9 | ||
|
|
1118858120 | ||
|
|
ae3a34d5bf | ||
|
|
43df42e49b | ||
|
|
e670f3bf03 | ||
|
|
c26a9404c5 | ||
|
|
c0fad4ca92 | ||
|
|
16dbf9378b | ||
|
|
4001fe5eac | ||
|
|
2992dd8f8b | ||
|
|
98a03d1e59 | ||
|
|
2088393c79 | ||
|
|
093042b88a | ||
|
|
e5ef35c186 | ||
|
|
1cd23d5efd | ||
|
|
ead5818a3f | ||
|
|
a5f66ada68 | ||
|
|
0919742853 | ||
|
|
f3efffd259 | ||
|
|
f85317983c | ||
|
|
76f709b768 | ||
|
|
e3b2356302 | ||
|
|
3d810211ee | ||
|
|
7453795dc5 | ||
|
|
9de7cd99ee | ||
|
|
51489c1aa5 | ||
|
|
25dd6de770 | ||
|
|
9727405194 | ||
|
|
2a825f5a02 | ||
|
|
908d249eb9 | ||
|
|
6cd119e8f4 | ||
|
|
9a59c8eb75 | ||
|
|
452c022d41 | ||
|
|
27e9bab82a | ||
|
|
edef860530 | ||
|
|
032cb63411 | ||
|
|
a1791ba578 | ||
|
|
3a69fd7786 | ||
|
|
8a90723c2e | ||
|
|
af2fc342c7 | ||
|
|
05ea2fcdbe | ||
|
|
6d4321fead | ||
|
|
3f6364c9ea | ||
|
|
0d11b12282 | ||
|
|
0796bcf7d0 | ||
|
|
0b5bec142a | ||
|
|
a5020b58f2 | ||
|
|
f039a74a8f | ||
|
|
0e6bb7390b | ||
|
|
b52b4eecca | ||
|
|
8186977d1d | ||
|
|
86adcfe4d7 | ||
|
|
ce2dd872c4 | ||
|
|
aadc53c90e | ||
|
|
cbc1b6b5c8 | ||
|
|
1aed7a9232 | ||
|
|
b549189644 | ||
|
|
35279d17b4 | ||
|
|
bb403aa0c5 | ||
|
|
04327c288b | ||
|
|
94d60e40d0 | ||
|
|
8505fcb6b7 | ||
|
|
e0a37f7635 | ||
|
|
9aec57166d | ||
|
|
a3739f2950 | ||
|
|
3d3b152758 | ||
|
|
0e03730543 | ||
|
|
97e07a6511 | ||
|
|
a77a51ba15 | ||
|
|
42e706e1c2 | ||
|
|
025dd06053 | ||
|
|
bcb421d879 | ||
|
|
66056ab54b | ||
|
|
bb92f76ceb | ||
|
|
84076b340b | ||
|
|
48caae5c0e | ||
|
|
77dd23795a | ||
|
|
2771d6304e | ||
|
|
9946edffa4 | ||
|
|
50bf2fd9d3 | ||
|
|
bdcde168aa | ||
|
|
5807142e20 | ||
|
|
ec2d9565b9 | ||
|
|
a18a71c73d | ||
|
|
93a6bd1156 | ||
|
|
581a410aef | ||
|
|
ab02fe988c | ||
|
|
b8d20fb21b | ||
|
|
12fa1a0be8 | ||
|
|
85a84f7507 | ||
|
|
2385313013 | ||
|
|
c7ce727571 | ||
|
|
8b165ff478 | ||
|
|
6d7d0cc72d | ||
|
|
f7eba5d4fd | ||
|
|
73ef6dae73 | ||
|
|
7fa6df1e5e | ||
|
|
e1a2ffb151 | ||
|
|
6d7727a32d | ||
|
|
6d7a94bd5a | ||
|
|
ecc3fb17e1 | ||
|
|
209e2bf0a5 | ||
|
|
b1947e57a4 | ||
|
|
74507501a5 | ||
|
|
c73481fd58 | ||
|
|
7795ad0b0c | ||
|
|
aff768f413 | ||
|
|
310b60f5b6 | ||
|
|
100f0be86a | ||
|
|
87e115e40d | ||
|
|
ef5f36e4c4 | ||
|
|
099650420d | ||
|
|
8ccf7fffa5 | ||
|
|
b97a055bf7 | ||
|
|
663fee6699 | ||
|
|
33d5f3938b | ||
|
|
ef6b45d7a1 | ||
|
|
c1ecd3690e | ||
|
|
3250fe1ec6 | ||
|
|
48e8cd93b9 | ||
|
|
afacbb16b6 | ||
|
|
dfad127f32 | ||
|
|
300c1b5a18 | ||
|
|
bb38e1710b | ||
|
|
0857a82de5 | ||
|
|
da5afcc91c | ||
|
|
0002979fda | ||
|
|
080af16d41 | ||
|
|
d03a0b7083 | ||
|
|
5ba21f5386 | ||
|
|
10eeae5295 | ||
|
|
a5bead15d0 | ||
|
|
0de674adde | ||
|
|
1db65965d0 | ||
|
|
bbed17f631 | ||
|
|
0af4a3a731 | ||
|
|
49d503705a | ||
|
|
c55dd7b8d9 | ||
|
|
7ddcab3537 | ||
|
|
040b46c345 | ||
|
|
cd11ab6775 | ||
|
|
a5d776f3b1 | ||
|
|
e02caf341d | ||
|
|
3c04caa67c | ||
|
|
e76b564cbf | ||
|
|
5b2de88c3d | ||
|
|
82080b232f | ||
|
|
666bc7af43 | ||
|
|
dc077d5a5b | ||
|
|
29c840c64a | ||
|
|
65e0f89f33 | ||
|
|
d3b9b36332 | ||
|
|
90bbf29ea1 | ||
|
|
57f73684e8 | ||
|
|
7833cdebb2 | ||
|
|
67a5ae2985 | ||
|
|
f58c52d26b | ||
|
|
41e7739461 | ||
|
|
332152b677 | ||
|
|
85b49fe1f0 | ||
|
|
e7924532be | ||
|
|
475d950ad6 | ||
|
|
e6cfb29c6f | ||
|
|
dee6e86db1 | ||
|
|
72f088331f | ||
|
|
bbf536d10e | ||
|
|
149ac98297 | ||
|
|
b90a2910c9 | ||
|
|
c4da8a3a8d | ||
|
|
3ca75583d2 | ||
|
|
5f4607ae6f | ||
|
|
d880c6873f | ||
|
|
937649b2ed | ||
|
|
78e912c886 | ||
|
|
696c7d2cd1 | ||
|
|
49c0cb026b |
13
.check.exs
13
.check.exs
@@ -13,8 +13,8 @@
|
|||||||
|
|
||||||
## list of tools (see `mix check` docs for a list of default curated tools)
|
## list of tools (see `mix check` docs for a list of default curated tools)
|
||||||
tools: [
|
tools: [
|
||||||
## curated tools may be disabled (e.g. the check for compilation warnings)
|
## Allow compilation warnings for now (error budget: unlimited warnings)
|
||||||
{:compiler, false},
|
{:compiler, "mix compile"},
|
||||||
|
|
||||||
## ...or have command & args adjusted (e.g. enable skip comments for sobelow)
|
## ...or have command & args adjusted (e.g. enable skip comments for sobelow)
|
||||||
# {:sobelow, "mix sobelow --exit --skip"},
|
# {:sobelow, "mix sobelow --exit --skip"},
|
||||||
@@ -22,10 +22,15 @@
|
|||||||
## ...or reordered (e.g. to see output from dialyzer before others)
|
## ...or reordered (e.g. to see output from dialyzer before others)
|
||||||
# {:dialyzer, order: -1},
|
# {:dialyzer, order: -1},
|
||||||
|
|
||||||
## ...or reconfigured (e.g. disable parallel execution of ex_unit in umbrella)
|
## Credo with relaxed error budget: max 200 issues
|
||||||
|
{:credo, "mix credo --strict --max-issues 200"},
|
||||||
|
|
||||||
|
## Dialyzer but don't halt on exit (allow warnings)
|
||||||
|
{:dialyzer, "mix dialyzer"},
|
||||||
|
|
||||||
|
## Tests without warnings-as-errors for now
|
||||||
|
{:ex_unit, "mix test"},
|
||||||
{:doctor, false},
|
{:doctor, false},
|
||||||
{:ex_unit, false},
|
|
||||||
{:npm_test, false},
|
{:npm_test, false},
|
||||||
{:sobelow, false}
|
{:sobelow, false}
|
||||||
|
|
||||||
|
|||||||
18
.credo.exs
18
.credo.exs
@@ -82,8 +82,6 @@
|
|||||||
# You can customize the priority of any check
|
# You can customize the priority of any check
|
||||||
# Priority values are: `low, normal, high, higher`
|
# Priority values are: `low, normal, high, higher`
|
||||||
#
|
#
|
||||||
{Credo.Check.Design.AliasUsage,
|
|
||||||
[priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]},
|
|
||||||
# You can also customize the exit_status of each check.
|
# You can also customize the exit_status of each check.
|
||||||
# If you don't want TODO comments to cause `mix credo` to fail, just
|
# If you don't want TODO comments to cause `mix credo` to fail, just
|
||||||
# set this value to 0 (zero).
|
# set this value to 0 (zero).
|
||||||
@@ -99,10 +97,9 @@
|
|||||||
{Credo.Check.Readability.LargeNumbers, []},
|
{Credo.Check.Readability.LargeNumbers, []},
|
||||||
{Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]},
|
{Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]},
|
||||||
{Credo.Check.Readability.ModuleAttributeNames, []},
|
{Credo.Check.Readability.ModuleAttributeNames, []},
|
||||||
{Credo.Check.Readability.ModuleDoc, []},
|
{Credo.Check.Readability.ModuleDoc, false},
|
||||||
{Credo.Check.Readability.ModuleNames, []},
|
{Credo.Check.Readability.ModuleNames, []},
|
||||||
{Credo.Check.Readability.ParenthesesInCondition, []},
|
{Credo.Check.Readability.ParenthesesInCondition, []},
|
||||||
{Credo.Check.Readability.ParenthesesOnZeroArityDefs, []},
|
|
||||||
{Credo.Check.Readability.PipeIntoAnonymousFunctions, []},
|
{Credo.Check.Readability.PipeIntoAnonymousFunctions, []},
|
||||||
{Credo.Check.Readability.PredicateFunctionNames, []},
|
{Credo.Check.Readability.PredicateFunctionNames, []},
|
||||||
{Credo.Check.Readability.PreferImplicitTry, []},
|
{Credo.Check.Readability.PreferImplicitTry, []},
|
||||||
@@ -121,14 +118,12 @@
|
|||||||
#
|
#
|
||||||
{Credo.Check.Refactor.Apply, []},
|
{Credo.Check.Refactor.Apply, []},
|
||||||
{Credo.Check.Refactor.CondStatements, []},
|
{Credo.Check.Refactor.CondStatements, []},
|
||||||
{Credo.Check.Refactor.CyclomaticComplexity, []},
|
|
||||||
{Credo.Check.Refactor.FunctionArity, []},
|
{Credo.Check.Refactor.FunctionArity, []},
|
||||||
{Credo.Check.Refactor.LongQuoteBlocks, []},
|
{Credo.Check.Refactor.LongQuoteBlocks, []},
|
||||||
{Credo.Check.Refactor.MatchInCondition, []},
|
{Credo.Check.Refactor.MatchInCondition, []},
|
||||||
{Credo.Check.Refactor.MapJoin, []},
|
{Credo.Check.Refactor.MapJoin, []},
|
||||||
{Credo.Check.Refactor.NegatedConditionsInUnless, []},
|
{Credo.Check.Refactor.NegatedConditionsInUnless, []},
|
||||||
{Credo.Check.Refactor.NegatedConditionsWithElse, []},
|
{Credo.Check.Refactor.NegatedConditionsWithElse, []},
|
||||||
{Credo.Check.Refactor.Nesting, []},
|
|
||||||
{Credo.Check.Refactor.UnlessWithElse, []},
|
{Credo.Check.Refactor.UnlessWithElse, []},
|
||||||
{Credo.Check.Refactor.WithClauses, []},
|
{Credo.Check.Refactor.WithClauses, []},
|
||||||
{Credo.Check.Refactor.FilterFilter, []},
|
{Credo.Check.Refactor.FilterFilter, []},
|
||||||
@@ -196,10 +191,19 @@
|
|||||||
{Credo.Check.Warning.LeakyEnvironment, []},
|
{Credo.Check.Warning.LeakyEnvironment, []},
|
||||||
{Credo.Check.Warning.MapGetUnsafePass, []},
|
{Credo.Check.Warning.MapGetUnsafePass, []},
|
||||||
{Credo.Check.Warning.MixEnv, []},
|
{Credo.Check.Warning.MixEnv, []},
|
||||||
{Credo.Check.Warning.UnsafeToAtom, []}
|
{Credo.Check.Warning.UnsafeToAtom, []},
|
||||||
|
|
||||||
# {Credo.Check.Refactor.MapInto, []},
|
# {Credo.Check.Refactor.MapInto, []},
|
||||||
|
|
||||||
|
#
|
||||||
|
# Temporarily disable checks that generate too many issues
|
||||||
|
# to get under the 200 issue budget
|
||||||
|
#
|
||||||
|
{Credo.Check.Readability.ParenthesesOnZeroArityDefs, []},
|
||||||
|
{Credo.Check.Design.AliasUsage, []},
|
||||||
|
{Credo.Check.Refactor.Nesting, []},
|
||||||
|
{Credo.Check.Refactor.CyclomaticComplexity, []}
|
||||||
|
|
||||||
#
|
#
|
||||||
# Custom checks can be created using `mix credo.gen.check`.
|
# Custom checks can be created using `mix credo.gen.check`.
|
||||||
#
|
#
|
||||||
|
|||||||
127
.credo.test.exs
Normal file
127
.credo.test.exs
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
# Credo configuration specific to test files
|
||||||
|
# This enforces stricter quality standards for test code
|
||||||
|
|
||||||
|
%{
|
||||||
|
configs: [
|
||||||
|
%{
|
||||||
|
name: "test",
|
||||||
|
files: %{
|
||||||
|
included: ["test/"],
|
||||||
|
excluded: ["test/support/"]
|
||||||
|
},
|
||||||
|
requires: [],
|
||||||
|
strict: true,
|
||||||
|
color: true,
|
||||||
|
checks: [
|
||||||
|
# Consistency checks
|
||||||
|
{Credo.Check.Consistency.ExceptionNames, []},
|
||||||
|
{Credo.Check.Consistency.LineEndings, []},
|
||||||
|
{Credo.Check.Consistency.MultiAliasImportRequireUse, []},
|
||||||
|
{Credo.Check.Consistency.ParameterPatternMatching, []},
|
||||||
|
{Credo.Check.Consistency.SpaceAroundOperators, []},
|
||||||
|
{Credo.Check.Consistency.SpaceInParentheses, []},
|
||||||
|
{Credo.Check.Consistency.TabsOrSpaces, []},
|
||||||
|
|
||||||
|
# Design checks - stricter for tests
|
||||||
|
{Credo.Check.Design.AliasUsage, priority: :high},
|
||||||
|
# Lower threshold for tests
|
||||||
|
{Credo.Check.Design.DuplicatedCode, mass_threshold: 25},
|
||||||
|
{Credo.Check.Design.TagTODO, []},
|
||||||
|
{Credo.Check.Design.TagFIXME, []},
|
||||||
|
|
||||||
|
# Readability checks - very important for tests
|
||||||
|
{Credo.Check.Readability.AliasOrder, []},
|
||||||
|
{Credo.Check.Readability.FunctionNames, []},
|
||||||
|
{Credo.Check.Readability.LargeNumbers, []},
|
||||||
|
# Slightly longer for test descriptions
|
||||||
|
{Credo.Check.Readability.MaxLineLength, max_length: 120},
|
||||||
|
{Credo.Check.Readability.ModuleAttributeNames, []},
|
||||||
|
# Not required for test modules
|
||||||
|
{Credo.Check.Readability.ModuleDoc, false},
|
||||||
|
{Credo.Check.Readability.ModuleNames, []},
|
||||||
|
{Credo.Check.Readability.ParenthesesInCondition, []},
|
||||||
|
{Credo.Check.Readability.ParenthesesOnZeroArityDefs, []},
|
||||||
|
{Credo.Check.Readability.PredicateFunctionNames, []},
|
||||||
|
{Credo.Check.Readability.PreferImplicitTry, []},
|
||||||
|
{Credo.Check.Readability.RedundantBlankLines, []},
|
||||||
|
{Credo.Check.Readability.Semicolons, []},
|
||||||
|
{Credo.Check.Readability.SpaceAfterCommas, []},
|
||||||
|
{Credo.Check.Readability.StringSigils, []},
|
||||||
|
{Credo.Check.Readability.TrailingBlankLine, []},
|
||||||
|
{Credo.Check.Readability.TrailingWhiteSpace, []},
|
||||||
|
{Credo.Check.Readability.UnnecessaryAliasExpansion, []},
|
||||||
|
{Credo.Check.Readability.VariableNames, []},
|
||||||
|
{Credo.Check.Readability.WithSingleClause, []},
|
||||||
|
|
||||||
|
# Test-specific readability checks
|
||||||
|
# Discourage single pipes in tests
|
||||||
|
{Credo.Check.Readability.SinglePipe, []},
|
||||||
|
# Specs not needed in tests
|
||||||
|
{Credo.Check.Readability.Specs, false},
|
||||||
|
{Credo.Check.Readability.StrictModuleLayout, []},
|
||||||
|
|
||||||
|
# Refactoring opportunities - important for test maintainability
|
||||||
|
# Higher limit for complex test setups
|
||||||
|
{Credo.Check.Refactor.ABCSize, max_size: 50},
|
||||||
|
{Credo.Check.Refactor.AppendSingleItem, []},
|
||||||
|
{Credo.Check.Refactor.CondStatements, []},
|
||||||
|
{Credo.Check.Refactor.CyclomaticComplexity, max_complexity: 10},
|
||||||
|
# Lower for test helpers
|
||||||
|
{Credo.Check.Refactor.FunctionArity, max_arity: 4},
|
||||||
|
{Credo.Check.Refactor.LongQuoteBlocks, []},
|
||||||
|
{Credo.Check.Refactor.MapInto, []},
|
||||||
|
{Credo.Check.Refactor.MatchInCondition, []},
|
||||||
|
{Credo.Check.Refactor.NegatedConditionsInUnless, []},
|
||||||
|
{Credo.Check.Refactor.NegatedConditionsWithElse, []},
|
||||||
|
# Keep tests flat
|
||||||
|
{Credo.Check.Refactor.Nesting, max_nesting: 3},
|
||||||
|
{Credo.Check.Refactor.UnlessWithElse, []},
|
||||||
|
{Credo.Check.Refactor.WithClauses, []},
|
||||||
|
{Credo.Check.Refactor.FilterFilter, []},
|
||||||
|
{Credo.Check.Refactor.RejectReject, []},
|
||||||
|
{Credo.Check.Refactor.RedundantWithClauseResult, []},
|
||||||
|
|
||||||
|
# Warnings - all should be fixed
|
||||||
|
{Credo.Check.Warning.ApplicationConfigInModuleAttribute, []},
|
||||||
|
{Credo.Check.Warning.BoolOperationOnSameValues, []},
|
||||||
|
{Credo.Check.Warning.ExpensiveEmptyEnumCheck, []},
|
||||||
|
{Credo.Check.Warning.IExPry, []},
|
||||||
|
{Credo.Check.Warning.IoInspect, []},
|
||||||
|
{Credo.Check.Warning.OperationOnSameValues, []},
|
||||||
|
{Credo.Check.Warning.OperationWithConstantResult, []},
|
||||||
|
{Credo.Check.Warning.RaiseInsideRescue, []},
|
||||||
|
{Credo.Check.Warning.UnusedEnumOperation, []},
|
||||||
|
{Credo.Check.Warning.UnusedFileOperation, []},
|
||||||
|
{Credo.Check.Warning.UnusedKeywordOperation, []},
|
||||||
|
{Credo.Check.Warning.UnusedListOperation, []},
|
||||||
|
{Credo.Check.Warning.UnusedPathOperation, []},
|
||||||
|
{Credo.Check.Warning.UnusedRegexOperation, []},
|
||||||
|
{Credo.Check.Warning.UnusedStringOperation, []},
|
||||||
|
{Credo.Check.Warning.UnusedTupleOperation, []},
|
||||||
|
{Credo.Check.Warning.UnsafeExec, []},
|
||||||
|
|
||||||
|
# Test-specific checks
|
||||||
|
# Important for test isolation
|
||||||
|
{Credo.Check.Warning.LeakyEnvironment, []},
|
||||||
|
|
||||||
|
# Custom checks for test patterns
|
||||||
|
{
|
||||||
|
Credo.Check.Refactor.PipeChainStart,
|
||||||
|
# Factory functions
|
||||||
|
excluded_functions: ["build", "create", "insert"],
|
||||||
|
excluded_argument_types: [:atom, :number]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
# Disable these checks for test files
|
||||||
|
disabled: [
|
||||||
|
# Tests don't need module docs
|
||||||
|
{Credo.Check.Readability.ModuleDoc, []},
|
||||||
|
# Tests don't need specs
|
||||||
|
{Credo.Check.Readability.Specs, []},
|
||||||
|
# Common in test setup
|
||||||
|
{Credo.Check.Refactor.VariableRebinding, []}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,12 +1,28 @@
|
|||||||
FROM elixir:1.16-otp-25
|
FROM elixir:1.17-otp-27
|
||||||
|
|
||||||
|
RUN apt install -yq curl gnupg
|
||||||
|
# Install OS packages and Node.js (via nodesource),
|
||||||
|
# plus inotify-tools and yarn
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
sudo \
|
||||||
|
curl \
|
||||||
|
make \
|
||||||
|
git \
|
||||||
|
bash \
|
||||||
|
build-essential \
|
||||||
|
ca-certificates \
|
||||||
|
jq \
|
||||||
|
vim \
|
||||||
|
net-tools \
|
||||||
|
procps \
|
||||||
|
# Optionally add any other tools you need, e.g. vim, wget...
|
||||||
|
&& curl -sL https://deb.nodesource.com/setup_18.x | bash - \
|
||||||
|
&& apt-get install -y --no-install-recommends nodejs inotify-tools \
|
||||||
|
&& npm install -g yarn \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
RUN apt update -yq
|
|
||||||
RUN apt install -yq curl gnupg mc inotify-tools
|
|
||||||
RUN apt --fix-broken install
|
RUN apt --fix-broken install
|
||||||
RUN apt remove -y nodejs nodejs-doc
|
|
||||||
RUN curl -sL https://deb.nodesource.com/setup_18.x | bash -
|
|
||||||
RUN apt install -y nodejs
|
|
||||||
RUN npm install --global yarn
|
|
||||||
|
|
||||||
RUN mix local.hex --force
|
RUN mix local.hex --force
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,30 @@
|
|||||||
{
|
{
|
||||||
"name": "wanderer-dev",
|
"name": "wanderer-dev",
|
||||||
"dockerComposeFile": ["./docker-compose.yml"],
|
"dockerComposeFile": ["./docker-compose.yml"],
|
||||||
"extensions": ["jakebecker.elixir-ls"],
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
"extensions": [
|
||||||
|
"jakebecker.elixir-ls",
|
||||||
|
"JakeBecker.elixir-ls",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"esbenp.prettier-vscode"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"search.exclude": {
|
||||||
|
"**/doc": true
|
||||||
|
},
|
||||||
|
"elixirLS.dialyzerEnabled": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"service": "wanderer",
|
"service": "wanderer",
|
||||||
"workspaceFolder": "/app",
|
"workspaceFolder": "/app",
|
||||||
"shutdownAction": "stopCompose",
|
"shutdownAction": "stopCompose",
|
||||||
"settings": {
|
"features": {
|
||||||
"editor.formatOnSave": true,
|
"ghcr.io/devcontainers/features/common-utils:2": {
|
||||||
"search.exclude": {
|
"networkArgs": ["--add-host=host.docker.internal:host-gateway"]
|
||||||
"**/doc": true
|
}
|
||||||
},
|
},
|
||||||
"elixirLS.dialyzerEnabled": false
|
"forwardPorts": [4444]
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ version: "0.1"
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
image: postgres:14.3
|
image: postgres:13-alpine
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
@@ -10,11 +10,11 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- "5432:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- db:/var/lib/postgresql/data
|
- db-new:/var/lib/postgresql/data
|
||||||
|
|
||||||
wanderer:
|
wanderer:
|
||||||
environment:
|
environment:
|
||||||
PORT: 8000
|
PORT: 4444
|
||||||
DB_HOST: db
|
DB_HOST: db
|
||||||
WEB_APP_URL: "http://localhost:4444"
|
WEB_APP_URL: "http://localhost:4444"
|
||||||
ERL_AFLAGS: "-kernel shell_history enabled"
|
ERL_AFLAGS: "-kernel shell_history enabled"
|
||||||
@@ -22,7 +22,7 @@ services:
|
|||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
ports:
|
ports:
|
||||||
- 8000:8000
|
- 4444:4444
|
||||||
volumes:
|
volumes:
|
||||||
- ..:/app:delegated
|
- ..:/app:delegated
|
||||||
- ~/.gitconfig:/root/.gitconfig
|
- ~/.gitconfig:/root/.gitconfig
|
||||||
@@ -33,4 +33,4 @@ services:
|
|||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
elixir-artifacts: {}
|
elixir-artifacts: {}
|
||||||
db: {}
|
db-new: {}
|
||||||
|
|||||||
39
.devcontainer/setup.sh
Executable file
39
.devcontainer/setup.sh
Executable file
@@ -0,0 +1,39 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "→ fetching & compiling deps"
|
||||||
|
mix deps.get
|
||||||
|
mix compile
|
||||||
|
|
||||||
|
# only run Ecto if the project actually has those tasks
|
||||||
|
if mix help | grep -q "ecto.create"; then
|
||||||
|
echo "→ waiting for database to be ready..."
|
||||||
|
|
||||||
|
# Wait for database to be ready
|
||||||
|
DB_HOST=${DB_HOST:-db}
|
||||||
|
timeout=60
|
||||||
|
while ! nc -z $DB_HOST 5432 2>/dev/null; do
|
||||||
|
if [ $timeout -eq 0 ]; then
|
||||||
|
echo "❌ Database connection timeout"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Waiting for database... ($timeout seconds remaining)"
|
||||||
|
sleep 1
|
||||||
|
timeout=$((timeout - 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
# Give the database a bit more time to fully initialize
|
||||||
|
echo "→ giving database 2 more seconds to fully initialize..."
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
echo "→ database is ready, running ecto.create && ecto.migrate"
|
||||||
|
mix ecto.create --quiet
|
||||||
|
mix ecto.migrate
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd assets
|
||||||
|
echo "→ installing JS & CSS dependencies"
|
||||||
|
yarn install --frozen-lockfile
|
||||||
|
echo "→ building assets"
|
||||||
|
|
||||||
|
echo "✅ setup complete"
|
||||||
@@ -6,3 +6,11 @@ export EVE_CLIENT_WITH_WALLET_ID="<EVE_CLIENT_WITH_WALLET_ID>"
|
|||||||
export EVE_CLIENT_WITH_WALLET_SECRET="<EVE_CLIENT_WITH_WALLET_SECRET>"
|
export EVE_CLIENT_WITH_WALLET_SECRET="<EVE_CLIENT_WITH_WALLET_SECRET>"
|
||||||
export GIT_SHA="1111"
|
export GIT_SHA="1111"
|
||||||
export WANDERER_INVITES="false"
|
export WANDERER_INVITES="false"
|
||||||
|
export WANDERER_PUBLIC_API_DISABLED="false"
|
||||||
|
export WANDERER_CHARACTER_API_DISABLED="false"
|
||||||
|
export WANDERER_KILLS_SERVICE_ENABLED="true"
|
||||||
|
export WANDERER_KILLS_BASE_URL="ws://host.docker.internal:4004"
|
||||||
|
export WANDERER_SSE_ENABLED="true"
|
||||||
|
export WANDERER_WEBHOOKS_ENABLED="true"
|
||||||
|
export WANDERER_SSE_MAX_CONNECTIONS="1000"
|
||||||
|
export WANDERER_WEBHOOK_TIMEOUT_MS="15000"
|
||||||
109
.github/workflows/advanced-test.yml
vendored
Normal file
109
.github/workflows/advanced-test.yml
vendored
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
name: Build Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
|
||||||
|
env:
|
||||||
|
MIX_ENV: prod
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
REGISTRY_IMAGE: wandererltd/community-edition
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy-test:
|
||||||
|
name: 🚀 Deploy to test env (fly.io)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.base_ref == 'develop' || (github.ref == 'refs/heads/develop' && github.event_name == 'push') }}
|
||||||
|
steps:
|
||||||
|
- name: ⬇️ Checkout repo
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- uses: superfly/flyctl-actions/setup-flyctl@master
|
||||||
|
|
||||||
|
- name: 👀 Read app name
|
||||||
|
uses: SebRollen/toml-action@v1.0.0
|
||||||
|
id: app_name
|
||||||
|
with:
|
||||||
|
file: "fly.toml"
|
||||||
|
field: "app"
|
||||||
|
|
||||||
|
- name: 🚀 Deploy Test
|
||||||
|
run: flyctl deploy --remote-only --wait-timeout=300 --ha=false
|
||||||
|
env:
|
||||||
|
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
|
||||||
|
|
||||||
|
build:
|
||||||
|
name: 🛠 Build
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
if: ${{ (github.ref == 'refs/heads/develop') && github.event_name == 'push' }}
|
||||||
|
permissions:
|
||||||
|
checks: write
|
||||||
|
contents: write
|
||||||
|
packages: write
|
||||||
|
attestations: write
|
||||||
|
id-token: write
|
||||||
|
pull-requests: write
|
||||||
|
repository-projects: write
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
otp: ["27"]
|
||||||
|
elixir: ["1.17"]
|
||||||
|
node-version: ["18.x"]
|
||||||
|
outputs:
|
||||||
|
commit_hash: ${{ steps.generate-changelog.outputs.commit_hash }}
|
||||||
|
steps:
|
||||||
|
- name: Prepare
|
||||||
|
run: |
|
||||||
|
platform=${{ matrix.platform }}
|
||||||
|
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Setup Elixir
|
||||||
|
uses: erlef/setup-beam@v1
|
||||||
|
with:
|
||||||
|
otp-version: ${{matrix.otp}}
|
||||||
|
elixir-version: ${{matrix.elixir}}
|
||||||
|
# nix build would also work here because `todos` is the default package
|
||||||
|
- name: ⬇️ Checkout repo
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: 😅 Cache deps
|
||||||
|
id: cache-deps
|
||||||
|
uses: actions/cache@v4
|
||||||
|
env:
|
||||||
|
cache-name: cache-elixir-deps
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
deps
|
||||||
|
key: ${{ runner.os }}-mix-${{ matrix.elixir }}-${{ matrix.otp }}-${{ hashFiles('**/mix.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-mix-${{ matrix.elixir }}-${{ matrix.otp }}-
|
||||||
|
- name: 😅 Cache compiled build
|
||||||
|
id: cache-build
|
||||||
|
uses: actions/cache@v4
|
||||||
|
env:
|
||||||
|
cache-name: cache-compiled-build
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
_build
|
||||||
|
key: ${{ runner.os }}-build-${{ hashFiles('**/mix.lock') }}-${{ hashFiles( '**/lib/**/*.{ex,eex}', '**/config/*.exs', '**/mix.exs' ) }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-build-${{ hashFiles('**/mix.lock') }}-
|
||||||
|
${{ runner.os }}-build-
|
||||||
|
# Step: Download project dependencies. If unchanged, uses
|
||||||
|
# the cached version.
|
||||||
|
- name: 🌐 Install dependencies
|
||||||
|
run: mix deps.get --only "prod"
|
||||||
|
|
||||||
|
# Step: Compile the project treating any warnings as errors.
|
||||||
|
# Customize this step if a different behavior is desired.
|
||||||
|
- name: 🛠 Compiles without warnings
|
||||||
|
if: steps.cache-build.outputs.cache-hit != 'true'
|
||||||
|
run: mix compile
|
||||||
142
.github/workflows/build.yml
vendored
142
.github/workflows/build.yml
vendored
@@ -1,11 +1,10 @@
|
|||||||
name: Build
|
name: Build
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
|
||||||
types: [opened, synchronize, reopened, labeled, unlabeled]
|
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
- develop
|
||||||
- "releases/*"
|
- "releases/*"
|
||||||
env:
|
env:
|
||||||
MIX_ENV: prod
|
MIX_ENV: prod
|
||||||
@@ -20,31 +19,10 @@ permissions:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy-test:
|
|
||||||
name: 🚀 Deploy to test env (fly.io)
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: ${{ github.base_ref == 'main' || (github.ref == 'refs/heads/main' && github.event_name == 'push') }}
|
|
||||||
steps:
|
|
||||||
- name: ⬇️ Checkout repo
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- uses: superfly/flyctl-actions/setup-flyctl@master
|
|
||||||
|
|
||||||
- name: 👀 Read app name
|
|
||||||
uses: SebRollen/toml-action@v1.0.0
|
|
||||||
id: app_name
|
|
||||||
with:
|
|
||||||
file: "fly.toml"
|
|
||||||
field: "app"
|
|
||||||
|
|
||||||
- name: 🚀 Deploy Test
|
|
||||||
run: flyctl deploy --remote-only --wait-timeout=300 --ha=false
|
|
||||||
env:
|
|
||||||
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: 🛠 Build
|
name: 🛠 Build
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
if: ${{ (github.ref == 'refs/heads/main') && github.event_name == 'push' }}
|
if: ${{ (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop') && github.event_name == 'push' }}
|
||||||
permissions:
|
permissions:
|
||||||
checks: write
|
checks: write
|
||||||
contents: write
|
contents: write
|
||||||
@@ -59,7 +37,7 @@ jobs:
|
|||||||
elixir: ["1.17"]
|
elixir: ["1.17"]
|
||||||
node-version: ["18.x"]
|
node-version: ["18.x"]
|
||||||
outputs:
|
outputs:
|
||||||
commit_hash: ${{ steps.generate-changelog.outputs.commit_hash }}
|
commit_hash: ${{ steps.generate-changelog.outputs.commit_hash || steps.set-commit-develop.outputs.commit_hash }}
|
||||||
steps:
|
steps:
|
||||||
- name: Prepare
|
- name: Prepare
|
||||||
run: |
|
run: |
|
||||||
@@ -78,22 +56,23 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: 😅 Cache deps
|
- name: 😅 Cache deps
|
||||||
id: cache-deps
|
id: cache-deps
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
env:
|
env:
|
||||||
cache-name: cache-elixir-deps
|
cache-name: cache-elixir-deps
|
||||||
with:
|
with:
|
||||||
path: deps
|
path: |
|
||||||
key: ${{ runner.os }}-mix-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }}
|
deps
|
||||||
|
key: ${{ runner.os }}-mix-${{ matrix.elixir }}-${{ matrix.otp }}-${{ hashFiles('**/mix.lock') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-mix-${{ env.cache-name }}-
|
${{ runner.os }}-mix-${{ matrix.elixir }}-${{ matrix.otp }}-
|
||||||
- name: 😅 Cache compiled build
|
- name: 😅 Cache compiled build
|
||||||
id: cache-build
|
id: cache-build
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
env:
|
env:
|
||||||
cache-name: cache-compiled-build
|
cache-name: cache-compiled-build
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
**/_build
|
_build
|
||||||
key: ${{ runner.os }}-build-${{ hashFiles('**/mix.lock') }}-${{ hashFiles( '**/lib/**/*.{ex,eex}', '**/config/*.exs', '**/mix.exs' ) }}
|
key: ${{ runner.os }}-build-${{ hashFiles('**/mix.lock') }}-${{ hashFiles( '**/lib/**/*.{ex,eex}', '**/config/*.exs', '**/mix.exs' ) }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-build-${{ hashFiles('**/mix.lock') }}-
|
${{ runner.os }}-build-${{ hashFiles('**/mix.lock') }}-
|
||||||
@@ -111,17 +90,27 @@ jobs:
|
|||||||
|
|
||||||
- name: Generate Changelog & Update Tag Version
|
- name: Generate Changelog & Update Tag Version
|
||||||
id: generate-changelog
|
id: generate-changelog
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
run: |
|
run: |
|
||||||
git config --global user.name 'CI'
|
git config --global user.name 'CI'
|
||||||
git config --global user.email 'ci@users.noreply.github.com'
|
git config --global user.email 'ci@users.noreply.github.com'
|
||||||
mix git_ops.release --force-patch --yes
|
mix git_ops.release --force-patch --yes
|
||||||
git push --follow-tags
|
git push --follow-tags
|
||||||
echo "commit_hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
echo "commit_hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Set commit hash for develop
|
||||||
|
id: set-commit-develop
|
||||||
|
if: github.ref == 'refs/heads/develop'
|
||||||
|
run: |
|
||||||
|
echo "commit_hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
name: 🛠 Build Docker Images
|
name: 🛠 Build Docker Images
|
||||||
needs: build
|
needs: build
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
outputs:
|
||||||
|
release-tag: ${{ steps.get-latest-tag.outputs.tag }}
|
||||||
|
release-notes: ${{ steps.get-content.outputs.string }}
|
||||||
permissions:
|
permissions:
|
||||||
checks: write
|
checks: write
|
||||||
contents: write
|
contents: write
|
||||||
@@ -135,6 +124,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
platform:
|
platform:
|
||||||
- linux/amd64
|
- linux/amd64
|
||||||
|
- linux/arm64
|
||||||
steps:
|
steps:
|
||||||
- name: Prepare
|
- name: Prepare
|
||||||
run: |
|
run: |
|
||||||
@@ -148,12 +138,14 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Prepare Changelog
|
- name: Prepare Changelog
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
run: |
|
run: |
|
||||||
yes | cp -rf CHANGELOG.md priv/changelog/CHANGELOG.md
|
yes | cp -rf CHANGELOG.md priv/changelog/CHANGELOG.md
|
||||||
sed -i '1i%{title: "Change Log"}\n\n---\n' priv/changelog/CHANGELOG.md
|
sed -i '1i%{title: "Change Log"}\n\n---\n' priv/changelog/CHANGELOG.md
|
||||||
|
|
||||||
- name: Get Release Tag
|
- name: Get Release Tag
|
||||||
id: get-latest-tag
|
id: get-latest-tag
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
uses: "WyriHaximus/github-action-get-previous-tag@v1"
|
uses: "WyriHaximus/github-action-get-previous-tag@v1"
|
||||||
with:
|
with:
|
||||||
fallback: 1.0.0
|
fallback: 1.0.0
|
||||||
@@ -183,24 +175,39 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
tags: ${{ env.REGISTRY_IMAGE }}:latest,${{ env.REGISTRY_IMAGE }}:${{ steps.get-latest-tag.outputs.tag }}
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
platforms: ${{ matrix.platform }}
|
platforms: ${{ matrix.platform }}
|
||||||
|
outputs: type=image,"name=${{ env.REGISTRY_IMAGE }}",push-by-digest=true,name-canonical=true,push=true
|
||||||
build-args: |
|
build-args: |
|
||||||
MIX_ENV=prod
|
MIX_ENV=prod
|
||||||
BUILD_METADATA=${{ steps.meta.outputs.json }}
|
BUILD_METADATA=${{ steps.meta.outputs.json }}
|
||||||
|
|
||||||
- name: Image digest
|
- name: Export digest
|
||||||
run: echo ${{ steps.build.outputs.digest }}
|
run: |
|
||||||
|
mkdir -p /tmp/digests
|
||||||
|
digest="${{ steps.build.outputs.digest }}"
|
||||||
|
touch "/tmp/digests/${digest#sha256:}"
|
||||||
|
|
||||||
|
- name: Upload digest
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: digests-${{ env.PLATFORM_PAIR }}
|
||||||
|
path: /tmp/digests/*
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
- uses: markpatterson27/markdown-to-output@v1
|
- uses: markpatterson27/markdown-to-output@v1
|
||||||
id: extract-changelog
|
id: extract-changelog
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
with:
|
with:
|
||||||
filepath: CHANGELOG.md
|
filepath: CHANGELOG.md
|
||||||
|
|
||||||
- name: Get content
|
- name: Get content
|
||||||
uses: 2428392/gh-truncate-string-action@v1.3.0
|
uses: 2428392/gh-truncate-string-action@v1.3.0
|
||||||
id: get-content
|
id: get-content
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
with:
|
with:
|
||||||
stringToTruncate: |
|
stringToTruncate: |
|
||||||
📣 Wanderer new release available 🎉
|
📣 Wanderer new release available 🎉
|
||||||
@@ -211,16 +218,56 @@ jobs:
|
|||||||
maxLength: 500
|
maxLength: 500
|
||||||
truncationSymbol: "…"
|
truncationSymbol: "…"
|
||||||
|
|
||||||
- name: Discord Webhook Action
|
merge:
|
||||||
uses: tsickert/discord-webhook@v5.3.0
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- docker
|
||||||
|
steps:
|
||||||
|
- name: Download digests
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
path: /tmp/digests
|
||||||
content: ${{ steps.get-content.outputs.string }}
|
pattern: digests-*
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.WANDERER_DOCKER_USER }}
|
||||||
|
password: ${{ secrets.WANDERER_DOCKER_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
${{ env.REGISTRY_IMAGE }}
|
||||||
|
tags: |
|
||||||
|
type=ref,event=branch
|
||||||
|
type=ref,event=pr
|
||||||
|
type=semver,pattern={{version}},enable=${{ github.ref == 'refs/heads/main' }}
|
||||||
|
type=semver,pattern={{major}}.{{minor}},enable=${{ github.ref == 'refs/heads/main' }}
|
||||||
|
type=semver,pattern={{version}},value=${{ needs.docker.outputs.release-tag }},enable=${{ github.ref == 'refs/heads/main' }}
|
||||||
|
type=raw,value=develop,enable=${{ github.ref == 'refs/heads/develop' }}
|
||||||
|
type=raw,value=develop-{{sha}},enable=${{ github.ref == 'refs/heads/develop' }}
|
||||||
|
|
||||||
|
- name: Create manifest list and push
|
||||||
|
working-directory: /tmp/digests
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||||
|
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
|
||||||
|
|
||||||
|
- name: Inspect image
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }}
|
||||||
|
|
||||||
create-release:
|
create-release:
|
||||||
name: 🏷 Create Release
|
name: 🏷 Create Release
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
needs: docker
|
needs: [docker, merge]
|
||||||
if: ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }}
|
if: ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }}
|
||||||
steps:
|
steps:
|
||||||
- name: ⬇️ Checkout repo
|
- name: ⬇️ Checkout repo
|
||||||
@@ -228,17 +275,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Get Release Tag
|
|
||||||
id: get-latest-tag
|
|
||||||
uses: "WyriHaximus/github-action-get-previous-tag@v1"
|
|
||||||
with:
|
|
||||||
fallback: 1.0.0
|
|
||||||
|
|
||||||
- name: 🏷 Create Draft Release
|
- name: 🏷 Create Draft Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ steps.get-latest-tag.outputs.tag }}
|
tag_name: ${{ needs.docker.outputs.release-tag }}
|
||||||
name: Release ${{ steps.get-latest-tag.outputs.tag }}
|
name: Release ${{ needs.docker.outputs.release-tag }}
|
||||||
body: |
|
body: |
|
||||||
## Info
|
## Info
|
||||||
Commit ${{ github.sha }} was deployed to `staging`. [See code diff](${{ github.event.compare }}).
|
Commit ${{ github.sha }} was deployed to `staging`. [See code diff](${{ github.event.compare }}).
|
||||||
@@ -248,3 +289,10 @@ jobs:
|
|||||||
## How to Promote?
|
## How to Promote?
|
||||||
In order to promote this to prod, edit the draft and press **"Publish release"**.
|
In order to promote this to prod, edit the draft and press **"Publish release"**.
|
||||||
draft: true
|
draft: true
|
||||||
|
|
||||||
|
- name: Discord Webhook Action
|
||||||
|
uses: tsickert/discord-webhook@v5.3.0
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
with:
|
||||||
|
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||||
|
content: ${{ needs.docker.outputs.release-notes }}
|
||||||
|
|||||||
300
.github/workflows/flaky-test-detection.yml
vendored
Normal file
300
.github/workflows/flaky-test-detection.yml
vendored
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
name: Flaky Test Detection
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# Run nightly at 2 AM UTC
|
||||||
|
- cron: '0 2 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
test_file:
|
||||||
|
description: 'Specific test file to check (optional)'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
iterations:
|
||||||
|
description: 'Number of test iterations'
|
||||||
|
required: false
|
||||||
|
default: '10'
|
||||||
|
type: string
|
||||||
|
|
||||||
|
env:
|
||||||
|
MIX_ENV: test
|
||||||
|
ELIXIR_VERSION: "1.17"
|
||||||
|
OTP_VERSION: "27"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
detect-flaky-tests:
|
||||||
|
name: 🔍 Detect Flaky Tests
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:16
|
||||||
|
env:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
POSTGRES_DB: wanderer_test
|
||||||
|
options: >-
|
||||||
|
--health-cmd pg_isready
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: ⬇️ Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: 🏗️ Setup Elixir & Erlang
|
||||||
|
uses: erlef/setup-beam@v1
|
||||||
|
with:
|
||||||
|
elixir-version: ${{ env.ELIXIR_VERSION }}
|
||||||
|
otp-version: ${{ env.OTP_VERSION }}
|
||||||
|
|
||||||
|
- name: 📦 Restore dependencies cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
id: deps-cache
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
deps
|
||||||
|
_build
|
||||||
|
key: ${{ runner.os }}-mix-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ hashFiles('**/mix.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-mix-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-
|
||||||
|
|
||||||
|
- name: 📦 Install dependencies
|
||||||
|
if: steps.deps-cache.outputs.cache-hit != 'true'
|
||||||
|
run: |
|
||||||
|
mix deps.get
|
||||||
|
mix deps.compile
|
||||||
|
|
||||||
|
- name: 🏗️ Compile project
|
||||||
|
run: mix compile --warnings-as-errors
|
||||||
|
|
||||||
|
- name: 🏗️ Setup test database
|
||||||
|
run: |
|
||||||
|
mix ecto.create
|
||||||
|
mix ecto.migrate
|
||||||
|
env:
|
||||||
|
DATABASE_URL: postgres://postgres:postgres@localhost:5432/wanderer_test
|
||||||
|
|
||||||
|
- name: 🔍 Run flaky test detection
|
||||||
|
id: flaky-detection
|
||||||
|
run: |
|
||||||
|
# Determine test target
|
||||||
|
TEST_FILE="${{ github.event.inputs.test_file }}"
|
||||||
|
ITERATIONS="${{ github.event.inputs.iterations || '10' }}"
|
||||||
|
|
||||||
|
if [ -n "$TEST_FILE" ]; then
|
||||||
|
echo "Checking specific file: $TEST_FILE"
|
||||||
|
mix test.stability --runs $ITERATIONS --file "$TEST_FILE" --detect --report flaky_report.json
|
||||||
|
else
|
||||||
|
echo "Checking all tests"
|
||||||
|
mix test.stability --runs $ITERATIONS --detect --report flaky_report.json
|
||||||
|
fi
|
||||||
|
env:
|
||||||
|
DATABASE_URL: postgres://postgres:postgres@localhost:5432/wanderer_test
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: 📊 Upload flaky test report
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: flaky-test-report
|
||||||
|
path: flaky_report.json
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
|
- name: 💬 Comment on flaky tests
|
||||||
|
if: always()
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// Read the report
|
||||||
|
let report;
|
||||||
|
try {
|
||||||
|
const reportContent = fs.readFileSync('flaky_report.json', 'utf8');
|
||||||
|
report = JSON.parse(reportContent);
|
||||||
|
} catch (error) {
|
||||||
|
console.log('No flaky test report found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!report.flaky_tests || report.flaky_tests.length === 0) {
|
||||||
|
console.log('No flaky tests detected!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create issue body
|
||||||
|
const issueBody = `## 🔍 Flaky Tests Detected
|
||||||
|
|
||||||
|
The automated flaky test detection found ${report.flaky_tests.length} potentially flaky test(s).
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
- **Total test runs**: ${report.summary.total_runs}
|
||||||
|
- **Success rate**: ${(report.summary.success_rate * 100).toFixed(1)}%
|
||||||
|
- **Average duration**: ${(report.summary.avg_duration_ms / 1000).toFixed(2)}s
|
||||||
|
|
||||||
|
### Flaky Tests
|
||||||
|
|
||||||
|
| Test | Failure Rate | Details |
|
||||||
|
|------|--------------|---------|
|
||||||
|
${report.flaky_tests.map(test =>
|
||||||
|
`| ${test.test} | ${(test.failure_rate * 100).toFixed(1)}% | Failed ${test.failures}/${report.summary.total_runs} runs |`
|
||||||
|
).join('\n')}
|
||||||
|
|
||||||
|
### Recommended Actions
|
||||||
|
|
||||||
|
1. Review the identified tests for race conditions
|
||||||
|
2. Check for timing dependencies or async issues
|
||||||
|
3. Ensure proper test isolation and cleanup
|
||||||
|
4. Consider adding explicit waits or synchronization
|
||||||
|
5. Use \`async: false\` if tests share resources
|
||||||
|
|
||||||
|
---
|
||||||
|
*This issue was automatically created by the flaky test detection workflow.*
|
||||||
|
*Run time: ${new Date().toISOString()}*
|
||||||
|
`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check if there's already an open issue
|
||||||
|
const issues = await github.rest.issues.listForRepo({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
labels: 'flaky-test',
|
||||||
|
state: 'open'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (issues.data.length > 0) {
|
||||||
|
// Update existing issue
|
||||||
|
const issue = issues.data[0];
|
||||||
|
try {
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: issue.number,
|
||||||
|
body: issueBody
|
||||||
|
});
|
||||||
|
console.log(`Updated existing issue #${issue.number}`);
|
||||||
|
} catch (commentError) {
|
||||||
|
console.error('Failed to create comment:', commentError.message);
|
||||||
|
throw commentError;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Create new issue
|
||||||
|
try {
|
||||||
|
const newIssue = await github.rest.issues.create({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
title: '🔍 Flaky Tests Detected',
|
||||||
|
body: issueBody,
|
||||||
|
labels: ['flaky-test', 'test-quality', 'automated']
|
||||||
|
});
|
||||||
|
console.log(`Created new issue #${newIssue.data.number}`);
|
||||||
|
} catch (createError) {
|
||||||
|
console.error('Failed to create issue:', createError.message);
|
||||||
|
throw createError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (listError) {
|
||||||
|
console.error('Failed to list issues:', listError.message);
|
||||||
|
console.error('API error details:', listError.response?.data || 'No response data');
|
||||||
|
throw listError;
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: 📈 Update metrics
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
# Parse and store metrics for tracking
|
||||||
|
if [ -f flaky_report.json ]; then
|
||||||
|
FLAKY_COUNT=$(jq '.flaky_tests | length' flaky_report.json)
|
||||||
|
SUCCESS_RATE=$(jq '.summary.success_rate' flaky_report.json)
|
||||||
|
|
||||||
|
echo "FLAKY_TEST_COUNT=$FLAKY_COUNT" >> $GITHUB_ENV
|
||||||
|
echo "TEST_SUCCESS_RATE=$SUCCESS_RATE" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
# Log metrics (could be sent to monitoring service)
|
||||||
|
echo "::notice title=Flaky Test Metrics::Found $FLAKY_COUNT flaky tests with ${SUCCESS_RATE}% success rate"
|
||||||
|
fi
|
||||||
|
|
||||||
|
analyze-test-history:
|
||||||
|
name: 📊 Analyze Test History
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
needs: detect-flaky-tests
|
||||||
|
if: always()
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: ⬇️ Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: 📥 Download previous reports
|
||||||
|
uses: dawidd6/action-download-artifact@v3
|
||||||
|
with:
|
||||||
|
workflow: flaky-test-detection.yml
|
||||||
|
workflow_conclusion: completed
|
||||||
|
name: flaky-test-report
|
||||||
|
path: historical-reports
|
||||||
|
if_no_artifact_found: warn
|
||||||
|
|
||||||
|
- name: 📊 Generate trend analysis
|
||||||
|
run: |
|
||||||
|
# Analyze historical trends
|
||||||
|
python3 <<'EOF'
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
import glob
|
||||||
|
|
||||||
|
reports = []
|
||||||
|
for report_file in glob.glob('historical-reports/*/flaky_report.json'):
|
||||||
|
try:
|
||||||
|
with open(report_file, 'r') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
reports.append(data)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not reports:
|
||||||
|
print("No historical data found")
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
# Sort by timestamp
|
||||||
|
reports.sort(key=lambda x: x.get('timestamp', ''), reverse=True)
|
||||||
|
|
||||||
|
# Analyze trends
|
||||||
|
print("## Test Stability Trend Analysis")
|
||||||
|
print(f"\nAnalyzed {len(reports)} historical reports")
|
||||||
|
print("\n### Flaky Test Counts Over Time")
|
||||||
|
|
||||||
|
for report in reports[:10]: # Last 10 reports
|
||||||
|
timestamp = report.get('timestamp', 'Unknown')
|
||||||
|
flaky_count = len(report.get('flaky_tests', []))
|
||||||
|
success_rate = report.get('summary', {}).get('success_rate', 0) * 100
|
||||||
|
print(f"- {timestamp[:10]}: {flaky_count} flaky tests ({success_rate:.1f}% success rate)")
|
||||||
|
|
||||||
|
# Identify persistently flaky tests
|
||||||
|
all_flaky = {}
|
||||||
|
for report in reports:
|
||||||
|
for test in report.get('flaky_tests', []):
|
||||||
|
test_name = test.get('test', '')
|
||||||
|
if test_name not in all_flaky:
|
||||||
|
all_flaky[test_name] = 0
|
||||||
|
all_flaky[test_name] += 1
|
||||||
|
|
||||||
|
if all_flaky:
|
||||||
|
print("\n### Persistently Flaky Tests")
|
||||||
|
sorted_flaky = sorted(all_flaky.items(), key=lambda x: x[1], reverse=True)
|
||||||
|
for test_name, count in sorted_flaky[:5]:
|
||||||
|
percentage = (count / len(reports)) * 100
|
||||||
|
print(f"- {test_name}: Flaky in {count}/{len(reports)} runs ({percentage:.1f}%)")
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: 💾 Save analysis
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: test-stability-analysis
|
||||||
|
path: |
|
||||||
|
flaky_report.json
|
||||||
|
historical-reports/
|
||||||
|
retention-days: 90
|
||||||
2
.github/workflows/release_actions.yml
vendored
2
.github/workflows/release_actions.yml
vendored
@@ -18,4 +18,4 @@ jobs:
|
|||||||
key: ${{ secrets.SSH_KEY }}
|
key: ${{ secrets.SSH_KEY }}
|
||||||
port: ${{ secrets.PORT }}
|
port: ${{ secrets.PORT }}
|
||||||
script: |
|
script: |
|
||||||
/app/release/linux/deploy.sh ${{ github.event.release.tag_name }}
|
/home/wanderer/app/deploy.sh ${{ github.event.release.tag_name }}
|
||||||
|
|||||||
333
.github/workflows/test.yml
vendored
Normal file
333
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,333 @@
|
|||||||
|
name: 🧪 Test Suite
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [main, develop]
|
||||||
|
push:
|
||||||
|
branches: [main, develop]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
issues: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
MIX_ENV: test
|
||||||
|
ELIXIR_VERSION: '1.16'
|
||||||
|
OTP_VERSION: '26'
|
||||||
|
NODE_VERSION: '18'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: Test Suite
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:15
|
||||||
|
env:
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
POSTGRES_DB: wanderer_test
|
||||||
|
options: >-
|
||||||
|
--health-cmd pg_isready
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Elixir/OTP
|
||||||
|
uses: erlef/setup-beam@v1
|
||||||
|
with:
|
||||||
|
elixir-version: ${{ env.ELIXIR_VERSION }}
|
||||||
|
otp-version: ${{ env.OTP_VERSION }}
|
||||||
|
|
||||||
|
- name: Cache Elixir dependencies
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
deps
|
||||||
|
_build
|
||||||
|
key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
|
||||||
|
restore-keys: ${{ runner.os }}-mix-
|
||||||
|
|
||||||
|
- name: Install Elixir dependencies
|
||||||
|
run: |
|
||||||
|
mix deps.get
|
||||||
|
mix deps.compile
|
||||||
|
|
||||||
|
- name: Check code formatting
|
||||||
|
id: format
|
||||||
|
run: |
|
||||||
|
if mix format --check-formatted; then
|
||||||
|
echo "status=✅ Passed" >> $GITHUB_OUTPUT
|
||||||
|
echo "count=0" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "status=❌ Failed" >> $GITHUB_OUTPUT
|
||||||
|
echo "count=1" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Compile code and capture warnings
|
||||||
|
id: compile
|
||||||
|
run: |
|
||||||
|
# Capture compilation output
|
||||||
|
output=$(mix compile 2>&1 || true)
|
||||||
|
echo "$output" > compile_output.txt
|
||||||
|
|
||||||
|
# Count warnings
|
||||||
|
warning_count=$(echo "$output" | grep -c "warning:" || echo "0")
|
||||||
|
|
||||||
|
# Check if compilation succeeded
|
||||||
|
if mix compile > /dev/null 2>&1; then
|
||||||
|
echo "status=✅ Success" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "status=❌ Failed" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "warnings=$warning_count" >> $GITHUB_OUTPUT
|
||||||
|
echo "output<<EOF" >> $GITHUB_OUTPUT
|
||||||
|
echo "$output" >> $GITHUB_OUTPUT
|
||||||
|
echo "EOF" >> $GITHUB_OUTPUT
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Setup database
|
||||||
|
run: |
|
||||||
|
mix ecto.create
|
||||||
|
mix ecto.migrate
|
||||||
|
|
||||||
|
- name: Run tests with coverage
|
||||||
|
id: tests
|
||||||
|
run: |
|
||||||
|
# Run tests with coverage
|
||||||
|
output=$(mix test --cover 2>&1 || true)
|
||||||
|
echo "$output" > test_output.txt
|
||||||
|
|
||||||
|
# Parse test results
|
||||||
|
if echo "$output" | grep -q "0 failures"; then
|
||||||
|
echo "status=✅ All Passed" >> $GITHUB_OUTPUT
|
||||||
|
test_status="success"
|
||||||
|
else
|
||||||
|
echo "status=❌ Some Failed" >> $GITHUB_OUTPUT
|
||||||
|
test_status="failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract test counts
|
||||||
|
test_line=$(echo "$output" | grep -E "[0-9]+ tests?, [0-9]+ failures?" | head -1 || echo "0 tests, 0 failures")
|
||||||
|
total_tests=$(echo "$test_line" | grep -o '[0-9]\+ tests\?' | grep -o '[0-9]\+' | head -1 || echo "0")
|
||||||
|
failures=$(echo "$test_line" | grep -o '[0-9]\+ failures\?' | grep -o '[0-9]\+' | head -1 || echo "0")
|
||||||
|
|
||||||
|
echo "total=$total_tests" >> $GITHUB_OUTPUT
|
||||||
|
echo "failures=$failures" >> $GITHUB_OUTPUT
|
||||||
|
echo "passed=$((total_tests - failures))" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
# Calculate success rate
|
||||||
|
if [ "$total_tests" -gt 0 ]; then
|
||||||
|
success_rate=$(echo "scale=1; ($total_tests - $failures) * 100 / $total_tests" | bc)
|
||||||
|
else
|
||||||
|
success_rate="0"
|
||||||
|
fi
|
||||||
|
echo "success_rate=$success_rate" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
exit_code=$?
|
||||||
|
echo "exit_code=$exit_code" >> $GITHUB_OUTPUT
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Generate coverage report
|
||||||
|
id: coverage
|
||||||
|
run: |
|
||||||
|
# Generate coverage report with GitHub format
|
||||||
|
output=$(mix coveralls.github 2>&1 || true)
|
||||||
|
echo "$output" > coverage_output.txt
|
||||||
|
|
||||||
|
# Extract coverage percentage
|
||||||
|
coverage=$(echo "$output" | grep -o '[0-9]\+\.[0-9]\+%' | head -1 | sed 's/%//' || echo "0")
|
||||||
|
if [ -z "$coverage" ]; then
|
||||||
|
coverage="0"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "percentage=$coverage" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
# Determine status
|
||||||
|
if (( $(echo "$coverage >= 80" | bc -l) )); then
|
||||||
|
echo "status=✅ Excellent" >> $GITHUB_OUTPUT
|
||||||
|
elif (( $(echo "$coverage >= 60" | bc -l) )); then
|
||||||
|
echo "status=⚠️ Good" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "status=❌ Needs Improvement" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Run Credo analysis
|
||||||
|
id: credo
|
||||||
|
run: |
|
||||||
|
# Run Credo and capture output
|
||||||
|
output=$(mix credo --strict --format=json 2>&1 || true)
|
||||||
|
echo "$output" > credo_output.txt
|
||||||
|
|
||||||
|
# Try to parse JSON output
|
||||||
|
if echo "$output" | jq . > /dev/null 2>&1; then
|
||||||
|
issues=$(echo "$output" | jq '.issues | length' 2>/dev/null || echo "0")
|
||||||
|
high_issues=$(echo "$output" | jq '.issues | map(select(.priority == "high")) | length' 2>/dev/null || echo "0")
|
||||||
|
normal_issues=$(echo "$output" | jq '.issues | map(select(.priority == "normal")) | length' 2>/dev/null || echo "0")
|
||||||
|
low_issues=$(echo "$output" | jq '.issues | map(select(.priority == "low")) | length' 2>/dev/null || echo "0")
|
||||||
|
else
|
||||||
|
# Fallback: try to count issues from regular output
|
||||||
|
regular_output=$(mix credo --strict 2>&1 || true)
|
||||||
|
issues=$(echo "$regular_output" | grep -c "┃" || echo "0")
|
||||||
|
high_issues="0"
|
||||||
|
normal_issues="0"
|
||||||
|
low_issues="0"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "total_issues=$issues" >> $GITHUB_OUTPUT
|
||||||
|
echo "high_issues=$high_issues" >> $GITHUB_OUTPUT
|
||||||
|
echo "normal_issues=$normal_issues" >> $GITHUB_OUTPUT
|
||||||
|
echo "low_issues=$low_issues" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
# Determine status
|
||||||
|
if [ "$issues" -eq 0 ]; then
|
||||||
|
echo "status=✅ Clean" >> $GITHUB_OUTPUT
|
||||||
|
elif [ "$issues" -lt 10 ]; then
|
||||||
|
echo "status=⚠️ Minor Issues" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "status=❌ Needs Attention" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Run Dialyzer analysis
|
||||||
|
id: dialyzer
|
||||||
|
run: |
|
||||||
|
# Ensure PLT is built
|
||||||
|
mix dialyzer --plt
|
||||||
|
|
||||||
|
# Run Dialyzer and capture output
|
||||||
|
output=$(mix dialyzer --format=github 2>&1 || true)
|
||||||
|
echo "$output" > dialyzer_output.txt
|
||||||
|
|
||||||
|
# Count warnings and errors
|
||||||
|
warnings=$(echo "$output" | grep -c "warning:" || echo "0")
|
||||||
|
errors=$(echo "$output" | grep -c "error:" || echo "0")
|
||||||
|
|
||||||
|
echo "warnings=$warnings" >> $GITHUB_OUTPUT
|
||||||
|
echo "errors=$errors" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
# Determine status
|
||||||
|
if [ "$errors" -eq 0 ] && [ "$warnings" -eq 0 ]; then
|
||||||
|
echo "status=✅ Clean" >> $GITHUB_OUTPUT
|
||||||
|
elif [ "$errors" -eq 0 ]; then
|
||||||
|
echo "status=⚠️ Warnings Only" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "status=❌ Has Errors" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Create test results summary
|
||||||
|
id: summary
|
||||||
|
run: |
|
||||||
|
# Calculate overall score
|
||||||
|
format_score=${{ steps.format.outputs.count == '0' && '100' || '0' }}
|
||||||
|
compile_score=${{ steps.compile.outputs.warnings == '0' && '100' || '80' }}
|
||||||
|
test_score=${{ steps.tests.outputs.success_rate }}
|
||||||
|
coverage_score=${{ steps.coverage.outputs.percentage }}
|
||||||
|
credo_score=$(echo "scale=0; (100 - ${{ steps.credo.outputs.total_issues }} * 2)" | bc | sed 's/^-.*$/0/')
|
||||||
|
dialyzer_score=$(echo "scale=0; (100 - ${{ steps.dialyzer.outputs.warnings }} * 2 - ${{ steps.dialyzer.outputs.errors }} * 10)" | bc | sed 's/^-.*$/0/')
|
||||||
|
|
||||||
|
overall_score=$(echo "scale=1; ($format_score + $compile_score + $test_score + $coverage_score + $credo_score + $dialyzer_score) / 6" | bc)
|
||||||
|
|
||||||
|
echo "overall_score=$overall_score" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
# Determine overall status
|
||||||
|
if (( $(echo "$overall_score >= 90" | bc -l) )); then
|
||||||
|
echo "overall_status=🌟 Excellent" >> $GITHUB_OUTPUT
|
||||||
|
elif (( $(echo "$overall_score >= 80" | bc -l) )); then
|
||||||
|
echo "overall_status=✅ Good" >> $GITHUB_OUTPUT
|
||||||
|
elif (( $(echo "$overall_score >= 70" | bc -l) )); then
|
||||||
|
echo "overall_status=⚠️ Needs Improvement" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "overall_status=❌ Poor" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Find existing PR comment
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
id: find_comment
|
||||||
|
uses: peter-evans/find-comment@v3
|
||||||
|
with:
|
||||||
|
issue-number: ${{ github.event.pull_request.number }}
|
||||||
|
comment-author: 'github-actions[bot]'
|
||||||
|
body-includes: '## 🧪 Test Results Summary'
|
||||||
|
|
||||||
|
- name: Create or update PR comment
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
uses: peter-evans/create-or-update-comment@v4
|
||||||
|
with:
|
||||||
|
comment-id: ${{ steps.find_comment.outputs.comment-id }}
|
||||||
|
issue-number: ${{ github.event.pull_request.number }}
|
||||||
|
edit-mode: replace
|
||||||
|
body: |
|
||||||
|
## 🧪 Test Results Summary
|
||||||
|
|
||||||
|
**Overall Quality Score: ${{ steps.summary.outputs.overall_score }}%** ${{ steps.summary.outputs.overall_status }}
|
||||||
|
|
||||||
|
### 📊 Metrics Dashboard
|
||||||
|
|
||||||
|
| Category | Status | Count | Details |
|
||||||
|
|----------|---------|-------|---------|
|
||||||
|
| 📝 **Code Formatting** | ${{ steps.format.outputs.status }} | ${{ steps.format.outputs.count }} issues | `mix format --check-formatted` |
|
||||||
|
| 🔨 **Compilation** | ${{ steps.compile.outputs.status }} | ${{ steps.compile.outputs.warnings }} warnings | `mix compile` |
|
||||||
|
| 🧪 **Tests** | ${{ steps.tests.outputs.status }} | ${{ steps.tests.outputs.failures }}/${{ steps.tests.outputs.total }} failed | Success rate: ${{ steps.tests.outputs.success_rate }}% |
|
||||||
|
| 📊 **Coverage** | ${{ steps.coverage.outputs.status }} | ${{ steps.coverage.outputs.percentage }}% | `mix coveralls` |
|
||||||
|
| 🎯 **Credo** | ${{ steps.credo.outputs.status }} | ${{ steps.credo.outputs.total_issues }} issues | High: ${{ steps.credo.outputs.high_issues }}, Normal: ${{ steps.credo.outputs.normal_issues }}, Low: ${{ steps.credo.outputs.low_issues }} |
|
||||||
|
| 🔍 **Dialyzer** | ${{ steps.dialyzer.outputs.status }} | ${{ steps.dialyzer.outputs.errors }} errors, ${{ steps.dialyzer.outputs.warnings }} warnings | `mix dialyzer` |
|
||||||
|
|
||||||
|
### 🎯 Quality Gates
|
||||||
|
|
||||||
|
Based on the project's quality thresholds:
|
||||||
|
- **Compilation Warnings**: ${{ steps.compile.outputs.warnings }}/148 (limit: 148)
|
||||||
|
- **Credo Issues**: ${{ steps.credo.outputs.total_issues }}/87 (limit: 87)
|
||||||
|
- **Dialyzer Warnings**: ${{ steps.dialyzer.outputs.warnings }}/161 (limit: 161)
|
||||||
|
- **Test Coverage**: ${{ steps.coverage.outputs.percentage }}%/50% (minimum: 50%)
|
||||||
|
- **Test Failures**: ${{ steps.tests.outputs.failures }}/0 (limit: 0)
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>📈 Progress Toward Goals</summary>
|
||||||
|
|
||||||
|
Target goals for the project:
|
||||||
|
- ✨ **Zero compilation warnings** (currently: ${{ steps.compile.outputs.warnings }})
|
||||||
|
- ✨ **≤10 Credo issues** (currently: ${{ steps.credo.outputs.total_issues }})
|
||||||
|
- ✨ **Zero Dialyzer warnings** (currently: ${{ steps.dialyzer.outputs.warnings }})
|
||||||
|
- ✨ **≥85% test coverage** (currently: ${{ steps.coverage.outputs.percentage }}%)
|
||||||
|
- ✅ **Zero test failures** (currently: ${{ steps.tests.outputs.failures }})
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>🔧 Quick Actions</summary>
|
||||||
|
|
||||||
|
To improve code quality:
|
||||||
|
```bash
|
||||||
|
# Fix formatting issues
|
||||||
|
mix format
|
||||||
|
|
||||||
|
# View detailed Credo analysis
|
||||||
|
mix credo --strict
|
||||||
|
|
||||||
|
# Check Dialyzer warnings
|
||||||
|
mix dialyzer
|
||||||
|
|
||||||
|
# Generate detailed coverage report
|
||||||
|
mix coveralls.html
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
🤖 *Auto-generated by GitHub Actions* • Updated: ${{ github.event.head_commit.timestamp }}
|
||||||
|
|
||||||
|
> **Note**: This comment will be updated automatically when new commits are pushed to this PR.
|
||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -4,7 +4,8 @@
|
|||||||
*.iml
|
*.iml
|
||||||
|
|
||||||
*.key
|
*.key
|
||||||
|
.repomixignore
|
||||||
|
repomix*
|
||||||
/.idea/
|
/.idea/
|
||||||
/node_modules/
|
/node_modules/
|
||||||
/assets/node_modules/
|
/assets/node_modules/
|
||||||
@@ -17,6 +18,9 @@
|
|||||||
/priv/static/*.js
|
/priv/static/*.js
|
||||||
/priv/static/*.css
|
/priv/static/*.css
|
||||||
|
|
||||||
|
# Dialyzer PLT files
|
||||||
|
/priv/plts/
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
**/.DS_Store
|
**/.DS_Store
|
||||||
|
|
||||||
@@ -26,6 +30,7 @@
|
|||||||
|
|
||||||
.env
|
.env
|
||||||
*.local.env
|
*.local.env
|
||||||
|
test/manual/.auto*
|
||||||
|
|
||||||
.direnv/
|
.direnv/
|
||||||
.cache/
|
.cache/
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
erlang 25.3
|
erlang 26.2.5.5
|
||||||
elixir 1.16-otp-25
|
elixir 1.17.3-otp-26
|
||||||
nodejs 18.0.0
|
nodejs 18.0.0
|
||||||
|
|||||||
2932
CHANGELOG.md
2932
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,9 @@ WORKDIR /app
|
|||||||
# set build ENV
|
# set build ENV
|
||||||
ENV MIX_ENV="prod"
|
ENV MIX_ENV="prod"
|
||||||
|
|
||||||
|
# Set ERL_FLAGS for ARM compatibility
|
||||||
|
ENV ERL_FLAGS="+JPperf true"
|
||||||
|
|
||||||
# install mix dependencies
|
# install mix dependencies
|
||||||
COPY mix.exs mix.lock ./
|
COPY mix.exs mix.lock ./
|
||||||
RUN rm -Rf _build deps && mix deps.get --only $MIX_ENV
|
RUN rm -Rf _build deps && mix deps.get --only $MIX_ENV
|
||||||
|
|||||||
7
Makefile
7
Makefile
@@ -1,4 +1,4 @@
|
|||||||
.PHONY: deploy install cleanup start yarn migrate format test coverage versions
|
.PHONY: deploy install cleanup start yarn migrate format test coverage versions standalone-tests
|
||||||
|
|
||||||
ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
|
ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
|
||||||
SHELL := /bin/bash
|
SHELL := /bin/bash
|
||||||
@@ -35,6 +35,11 @@ test t:
|
|||||||
coverage cover co:
|
coverage cover co:
|
||||||
mix test --cover
|
mix test --cover
|
||||||
|
|
||||||
|
unit-tests ut:
|
||||||
|
@echo "Running unit tests..."
|
||||||
|
@find test/unit -name "*.exs" -exec elixir {} \;
|
||||||
|
@echo "All unit tests completed."
|
||||||
|
|
||||||
versions v:
|
versions v:
|
||||||
@echo "Tool Versions"
|
@echo "Tool Versions"
|
||||||
@cat .tool-versions
|
@cat .tool-versions
|
||||||
|
|||||||
19
README.md
19
README.md
@@ -20,11 +20,11 @@ Interested to learn more? [Check more on our website](https://wanderer.ltd/news)
|
|||||||
|
|
||||||
Wanderer is open source project and we have a free as in beer and self-hosted solution called [Wanderer Community Edition (CE)](https://wanderer.ltd/news/community-edition). Here are the differences between Wanderer and Wanderer CE:
|
Wanderer is open source project and we have a free as in beer and self-hosted solution called [Wanderer Community Edition (CE)](https://wanderer.ltd/news/community-edition). Here are the differences between Wanderer and Wanderer CE:
|
||||||
|
|
||||||
| | Wanderer Cloud | Wanderer Community Edition |
|
| | Wanderer Cloud | Wanderer Community Edition |
|
||||||
| ------------- | ------------- | ------------- |
|
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| **Infrastructure management** | Easy and convenient. It takes 2 minutes to register your character and create a map. We manage everything so you don’t have to worry about anything and can focus on gameplay. | You do it all yourself. You need to get a server and you need to manage your infrastructure. You are responsible for installation, maintenance, upgrades, server capacity, uptime, backup, security, stability, consistency, loading time and so on.|
|
| **Infrastructure management** | Easy and convenient. It takes 2 minutes to register your character and create a map. We manage everything so you don’t have to worry about anything and can focus on gameplay. | You do it all yourself. You need to get a server and you need to manage your infrastructure. You are responsible for installation, maintenance, upgrades, server capacity, uptime, backup, security, stability, consistency, loading time and so on. |
|
||||||
| **Release schedule** | Continuously developed and improved with new features and updates multiple times per week. | Latest features and improvements won't be immediately available.|
|
| **Release schedule** | Continuously developed and improved with new features and updates multiple times per week. | Latest features and improvements won't be immediately available. |
|
||||||
| **Server location** | All visitor data is exclusively processed on EU-owned cloud infrastructure. We keep your site data on a secure, encrypted and green energy powered server in Germany. This ensures that your site data is protected by the strict European Union data privacy laws and ensures compliance with GDPR. Your website data never leaves the EU. | You have full control and can host your instance on any server in any country that you wish. Host it on a server in your basement or host it with any cloud provider wherever you want, even those that are not GDPR compliant.|
|
| **Server location** | All visitor data is exclusively processed on EU-owned cloud infrastructure. We keep your site data on a secure, encrypted and green energy powered server in Germany. This ensures that your site data is protected by the strict European Union data privacy laws and ensures compliance with GDPR. Your website data never leaves the EU. | You have full control and can host your instance on any server in any country that you wish. Host it on a server in your basement or host it with any cloud provider wherever you want, even those that are not GDPR compliant. |
|
||||||
|
|
||||||
Interested in self-hosting Wanderer CE on your server? Take a look at our [Wanderer CE installation instructions](https://github.com/wanderer-industries/community-edition/).
|
Interested in self-hosting Wanderer CE on your server? Take a look at our [Wanderer CE installation instructions](https://github.com/wanderer-industries/community-edition/).
|
||||||
|
|
||||||
@@ -54,7 +54,14 @@ Now you can visit [`localhost:8000`](http://localhost:8000) from your browser.
|
|||||||
#### Using .devcontainer
|
#### Using .devcontainer
|
||||||
|
|
||||||
- Run devcontainer
|
- Run devcontainer
|
||||||
- See how to start server in #setup section
|
- Install additional dependencies inside Dev container
|
||||||
|
- `root@0d0a785313b6:/app# apt update`
|
||||||
|
- `root@0d0a785313b6:/app# curl -sL https://deb.nodesource.com/setup_18.x | bash -`
|
||||||
|
- `root@0d0a785313b6:/app# apt-get install nodejs inotify-tools -y`
|
||||||
|
- `root@0d0a785313b6:/app# npm install -g yarn`
|
||||||
|
- `root@0d0a785313b6:/app# mix setup`
|
||||||
|
|
||||||
|
- See how to run server in #Run section
|
||||||
|
|
||||||
#### Using nix flakes
|
#### Using nix flakes
|
||||||
|
|
||||||
|
|||||||
@@ -17,5 +17,6 @@ module.exports = {
|
|||||||
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
|
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
|
||||||
'react/react-in-jsx-scope': 'off',
|
'react/react-in-jsx-scope': 'off',
|
||||||
'@typescript-eslint/ban-ts-comment': 'off',
|
'@typescript-eslint/ban-ts-comment': 'off',
|
||||||
|
"linebreak-style": "off",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,5 +7,5 @@
|
|||||||
"semi": true,
|
"semi": true,
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"useTabs": false,
|
"useTabs": false,
|
||||||
"endOfLine": "lf"
|
"endOfLine": "auto"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
// import '@fontsource-variable/inter'
|
|
||||||
// import '@fontsource-variable/jetbrains-mono'
|
|
||||||
// import './lib/tailwind/index.css';
|
|
||||||
import './css/app.css';
|
import './css/app.css';
|
||||||
|
|
||||||
import './lib/phoenix';
|
import './lib/phoenix';
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
@import 'primereact/resources/themes/arya-blue/theme.css' layer(primereact);
|
@import 'primereact/resources/themes/arya-blue/theme.css' layer(primereact);
|
||||||
/*@import 'primereact/resources/themes/bootstrap4-dark-blue/theme.css' layer(primereact);*/
|
/*@import 'primereact/resources/themes/bootstrap4-dark-blue/theme.css' layer(primereact);*/
|
||||||
|
|
||||||
|
@import '../js/hooks/Mapper/components/map/styles/index.scss';
|
||||||
|
|
||||||
@layer tailwind-base {
|
@layer tailwind-base {
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
}
|
}
|
||||||
@@ -23,6 +25,10 @@ body {
|
|||||||
width: 400px; /* As IE6 ignores !important it will set width as 400px; */
|
width: 400px; /* As IE6 ignores !important it will set width as 400px; */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body > div:first-of-type {
|
||||||
|
min-height: 500px !important;
|
||||||
|
}
|
||||||
|
|
||||||
.lending-normal {
|
.lending-normal {
|
||||||
font-family: 'Shentox', 'Rogan', sans-serif !important;
|
font-family: 'Shentox', 'Rogan', sans-serif !important;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@@ -106,19 +112,19 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.wd-characters-icons {
|
.wd-characters-icons {
|
||||||
display: flex;
|
/*display: flex;*/
|
||||||
transition:
|
/*transition:*/
|
||||||
border-color 250ms,
|
/* border-color 250ms,*/
|
||||||
opacity 250ms;
|
/* opacity 250ms;*/
|
||||||
width: 35px;
|
/*width: 35px;*/
|
||||||
height: 35px;
|
/*height: 35px;*/
|
||||||
border-radius: 50%;
|
/*border-radius: 50%;*/
|
||||||
border-width: 2px;
|
/*border-width: 2px;*/
|
||||||
border-style: solid;
|
/*border-style: solid;*/
|
||||||
border-color: #5a5a5a;
|
/*border-color: #5a5a5a;*/
|
||||||
background-color: rgba(0, 0, 0, 0);
|
/*background-color: rgba(0, 0, 0, 0);*/
|
||||||
cursor: pointer;
|
/*cursor: pointer;*/
|
||||||
opacity: 0.6;
|
/*opacity: 0.6;*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.wd-bg-default {
|
.wd-bg-default {
|
||||||
@@ -466,3 +472,530 @@ body {
|
|||||||
transform: rotate(-360deg);
|
transform: rotate(-360deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Map refresh */
|
||||||
|
.socket {
|
||||||
|
scale: 0.5;
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
left: 50%;
|
||||||
|
/* margin-left: -75px; */
|
||||||
|
top: 50%;
|
||||||
|
/* margin-top: -50px; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.hex-brick {
|
||||||
|
background: #000;
|
||||||
|
width: 30px;
|
||||||
|
height: 17px;
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
animation-name: fade;
|
||||||
|
animation-duration: 2s;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
-webkit-animation-name: fade;
|
||||||
|
-webkit-animation-duration: 2s;
|
||||||
|
-webkit-animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hex-brick--active {
|
||||||
|
animation-name: fade-active;
|
||||||
|
-webkit-animation-name: fade-active;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h2 {
|
||||||
|
transform: rotate(60deg);
|
||||||
|
-webkit-transform: rotate(60deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.h3 {
|
||||||
|
transform: rotate(-60deg);
|
||||||
|
-webkit-transform: rotate(-60deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gel {
|
||||||
|
height: 30px;
|
||||||
|
width: 30px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
-webkit-transition: all 0.3s;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center-gel {
|
||||||
|
margin-left: -15px;
|
||||||
|
margin-top: -15px;
|
||||||
|
|
||||||
|
animation-name: pulse-version;
|
||||||
|
animation-duration: 2s;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
-webkit-animation-name: pulse-version;
|
||||||
|
-webkit-animation-duration: 2s;
|
||||||
|
-webkit-animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c1 {
|
||||||
|
margin-left: -47px;
|
||||||
|
margin-top: -15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c2 {
|
||||||
|
margin-left: -31px;
|
||||||
|
margin-top: -43px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c3 {
|
||||||
|
margin-left: 1px;
|
||||||
|
margin-top: -43px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c4 {
|
||||||
|
margin-left: 17px;
|
||||||
|
margin-top: -15px;
|
||||||
|
}
|
||||||
|
.c5 {
|
||||||
|
margin-left: -31px;
|
||||||
|
margin-top: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c6 {
|
||||||
|
margin-left: 1px;
|
||||||
|
margin-top: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c7 {
|
||||||
|
margin-left: -63px;
|
||||||
|
margin-top: -43px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c8 {
|
||||||
|
margin-left: 33px;
|
||||||
|
margin-top: -43px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c9 {
|
||||||
|
margin-left: -15px;
|
||||||
|
margin-top: 41px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c10 {
|
||||||
|
margin-left: -63px;
|
||||||
|
margin-top: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c11 {
|
||||||
|
margin-left: 33px;
|
||||||
|
margin-top: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c12 {
|
||||||
|
margin-left: -15px;
|
||||||
|
margin-top: -71px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c13 {
|
||||||
|
margin-left: -47px;
|
||||||
|
margin-top: -71px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c14 {
|
||||||
|
margin-left: 17px;
|
||||||
|
margin-top: -71px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c15 {
|
||||||
|
margin-left: -47px;
|
||||||
|
margin-top: 41px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c16 {
|
||||||
|
margin-left: 17px;
|
||||||
|
margin-top: 41px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c17 {
|
||||||
|
margin-left: -79px;
|
||||||
|
margin-top: -15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c18 {
|
||||||
|
margin-left: 49px;
|
||||||
|
margin-top: -15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c19 {
|
||||||
|
margin-left: -63px;
|
||||||
|
margin-top: -99px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c20 {
|
||||||
|
margin-left: 33px;
|
||||||
|
margin-top: -99px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c21 {
|
||||||
|
margin-left: 1px;
|
||||||
|
margin-top: -99px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c22 {
|
||||||
|
margin-left: -31px;
|
||||||
|
margin-top: -99px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c23 {
|
||||||
|
margin-left: -63px;
|
||||||
|
margin-top: 69px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c24 {
|
||||||
|
margin-left: 33px;
|
||||||
|
margin-top: 69px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c25 {
|
||||||
|
margin-left: 1px;
|
||||||
|
margin-top: 69px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c26 {
|
||||||
|
margin-left: -31px;
|
||||||
|
margin-top: 69px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c27 {
|
||||||
|
margin-left: -79px;
|
||||||
|
margin-top: -15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c28 {
|
||||||
|
margin-left: -95px;
|
||||||
|
margin-top: -43px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c29 {
|
||||||
|
margin-left: -95px;
|
||||||
|
margin-top: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c30 {
|
||||||
|
margin-left: 49px;
|
||||||
|
margin-top: 41px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c31 {
|
||||||
|
margin-left: -79px;
|
||||||
|
margin-top: -71px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c32 {
|
||||||
|
margin-left: -111px;
|
||||||
|
margin-top: -15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c33 {
|
||||||
|
margin-left: 65px;
|
||||||
|
margin-top: -43px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c34 {
|
||||||
|
margin-left: 65px;
|
||||||
|
margin-top: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c35 {
|
||||||
|
margin-left: -79px;
|
||||||
|
margin-top: 41px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c36 {
|
||||||
|
margin-left: 49px;
|
||||||
|
margin-top: -71px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c37 {
|
||||||
|
margin-left: 81px;
|
||||||
|
margin-top: -15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.r1 {
|
||||||
|
animation-name: pulse-version;
|
||||||
|
animation-duration: 2s;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-delay: 0.2s;
|
||||||
|
-webkit-animation-name: pulse-version;
|
||||||
|
-webkit-animation-duration: 2s;
|
||||||
|
-webkit-animation-iteration-count: infinite;
|
||||||
|
-webkit-animation-delay: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.r2 {
|
||||||
|
animation-name: pulse-version;
|
||||||
|
animation-duration: 2s;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-delay: 0.4s;
|
||||||
|
-webkit-animation-name: pulse-version;
|
||||||
|
-webkit-animation-duration: 2s;
|
||||||
|
-webkit-animation-iteration-count: infinite;
|
||||||
|
-webkit-animation-delay: 0.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.r3 {
|
||||||
|
animation-name: pulse-version;
|
||||||
|
animation-duration: 2s;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-delay: 0.6s;
|
||||||
|
-webkit-animation-name: pulse-version;
|
||||||
|
-webkit-animation-duration: 2s;
|
||||||
|
-webkit-animation-iteration-count: infinite;
|
||||||
|
-webkit-animation-delay: 0.6s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.r1 > .hex-brick {
|
||||||
|
animation-name: fade;
|
||||||
|
animation-duration: 2s;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-delay: 0.2s;
|
||||||
|
-webkit-animation-name: fade;
|
||||||
|
-webkit-animation-duration: 2s;
|
||||||
|
-webkit-animation-iteration-count: infinite;
|
||||||
|
-webkit-animation-delay: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.r1 > .hex-brick--active {
|
||||||
|
animation-name: fade-active;
|
||||||
|
-webkit-animation-name: fade-active;
|
||||||
|
}
|
||||||
|
|
||||||
|
.r2 > .hex-brick {
|
||||||
|
animation-name: fade;
|
||||||
|
animation-duration: 2s;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-delay: 0.4s;
|
||||||
|
-webkit-animation-name: fade;
|
||||||
|
-webkit-animation-duration: 2s;
|
||||||
|
-webkit-animation-iteration-count: infinite;
|
||||||
|
-webkit-animation-delay: 0.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.r2 > .hex-brick--active {
|
||||||
|
animation-name: fade-active;
|
||||||
|
-webkit-animation-name: fade-active;
|
||||||
|
}
|
||||||
|
|
||||||
|
.r3 > .hex-brick {
|
||||||
|
animation-name: fade;
|
||||||
|
animation-duration: 2s;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-delay: 0.6s;
|
||||||
|
-webkit-animation-name: fade;
|
||||||
|
-webkit-animation-duration: 2s;
|
||||||
|
-webkit-animation-iteration-count: infinite;
|
||||||
|
-webkit-animation-delay: 0.6s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.r3 > .hex-brick--active {
|
||||||
|
animation-name: fade-active;
|
||||||
|
-webkit-animation-name: fade-active;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-version {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: scale(1);
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
-webkit-transform: scale(0.01);
|
||||||
|
transform: scale(0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
-webkit-transform: scale(1);
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade {
|
||||||
|
0% {
|
||||||
|
background: #09d0e2;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
background: #8ae6ee;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
background: #09d0e2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-active {
|
||||||
|
0% {
|
||||||
|
background: #ff52d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
background: #ff52d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
background: #ff52d9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes pulse {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: scale(1);
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
-webkit-transform: scale(0.01);
|
||||||
|
transform: scale(0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
-webkit-transform: scale(1);
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes fade {
|
||||||
|
0% {
|
||||||
|
background: #abf8ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
background: #389ca6;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
background: #abf8ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Map refresh END */
|
||||||
|
|
||||||
|
.inputContainer {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr auto;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.inputContainer > span:nth-child(1),
|
||||||
|
.inputContainer > label:nth-child(1) {
|
||||||
|
color: var(--gray-200);
|
||||||
|
font-size: 13px;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.inputContainer > :nth-child(2) {
|
||||||
|
border-bottom: 2px dotted #3f3f3f;
|
||||||
|
height: 1px;
|
||||||
|
margin: 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.smallInputSwitch {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.smallInputSwitch .p-inputswitch {
|
||||||
|
height: 1rem;
|
||||||
|
width: 2rem;
|
||||||
|
}
|
||||||
|
.smallInputSwitch .p-inputswitch.p-inputswitch-checked .p-inputswitch-slider::before {
|
||||||
|
transform: translateX(1rem);
|
||||||
|
}
|
||||||
|
.smallInputSwitch .p-inputswitch.p-highlight .p-inputswitch-slider:before {
|
||||||
|
transform: translateX(1rem);
|
||||||
|
}
|
||||||
|
.smallInputSwitch .p-inputswitch .p-inputswitch-slider::before {
|
||||||
|
width: 0.8rem;
|
||||||
|
height: 0.8rem;
|
||||||
|
margin-top: -0.4rem;
|
||||||
|
margin-left: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkboxRoot.sizeXS {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
.checkboxRoot.sizeXS .p-checkbox-box,
|
||||||
|
.checkboxRoot.sizeXS .p-checkbox-input {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
.checkboxRoot.sizeM {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
.checkboxRoot.sizeM .p-checkbox-box,
|
||||||
|
.checkboxRoot.sizeM .p-checkbox-input {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verticalTabsContainer {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 300px;
|
||||||
|
}
|
||||||
|
.verticalTabsContainer .p-tabview {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
.verticalTabsContainer .p-tabview-panels {
|
||||||
|
padding: 6px 1rem !important;
|
||||||
|
flex-grow: 1;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.verticalTabsContainer .p-tabview-nav-container {
|
||||||
|
border-right: none;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.verticalTabsContainer .p-tabview-nav {
|
||||||
|
flex-direction: column;
|
||||||
|
width: 150px;
|
||||||
|
min-height: 100%;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.verticalTabsContainer .p-tabview-nav li {
|
||||||
|
width: 100%;
|
||||||
|
border-right: 4px solid var(--surface-hover);
|
||||||
|
background-color: var(--surface-card);
|
||||||
|
transition:
|
||||||
|
background-color 200ms,
|
||||||
|
border-right-color 200ms;
|
||||||
|
}
|
||||||
|
.verticalTabsContainer .p-tabview-nav li:hover {
|
||||||
|
background-color: var(--surface-hover);
|
||||||
|
border-right: 4px solid var(--surface-100);
|
||||||
|
}
|
||||||
|
.verticalTabsContainer .p-tabview-nav li .p-tabview-nav-link {
|
||||||
|
transition: color 200ms;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: initial;
|
||||||
|
border: none;
|
||||||
|
color: var(--gray-400);
|
||||||
|
border-radius: initial;
|
||||||
|
font-weight: 400;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.verticalTabsContainer .p-tabview-nav li.p-tabview-selected {
|
||||||
|
background-color: var(--surface-50);
|
||||||
|
border-right: 4px solid var(--primary-color);
|
||||||
|
}
|
||||||
|
.verticalTabsContainer .p-tabview-nav li.p-tabview-selected .p-tabview-nav-link {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
.verticalTabsContainer .p-tabview-nav li.p-tabview-selected:hover {
|
||||||
|
border-right: 4px solid var(--primary-color);
|
||||||
|
}
|
||||||
|
.verticalTabsContainer .p-tabview-panel {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|||||||
14
assets/jest.config.js
Normal file
14
assets/jest.config.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
module.exports = {
|
||||||
|
preset: 'ts-jest',
|
||||||
|
testEnvironment: 'jsdom',
|
||||||
|
roots: ['<rootDir>'],
|
||||||
|
moduleDirectories: ['node_modules', 'js'],
|
||||||
|
moduleNameMapper: {
|
||||||
|
'^@/(.*)$': '<rootDir>/js/$1',
|
||||||
|
'\.scss$': 'identity-obj-proxy', // Mock SCSS files
|
||||||
|
},
|
||||||
|
transform: {
|
||||||
|
'^.+\.(ts|tsx)$': 'ts-jest',
|
||||||
|
'^.+\.(js|jsx)$': 'babel-jest', // Add babel-jest for JS/JSX files if needed
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,27 +1,26 @@
|
|||||||
import { ErrorBoundary } from 'react-error-boundary';
|
|
||||||
import { PrimeReactProvider } from 'primereact/api';
|
import { PrimeReactProvider } from 'primereact/api';
|
||||||
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
|
|
||||||
import { ReactFlowProvider } from 'reactflow';
|
|
||||||
import { MapHandlers } from '@/hooks/Mapper/types/mapHandlers.ts';
|
import { MapHandlers } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||||
import { ErrorInfo, useCallback, useEffect, useRef } from 'react';
|
import { ErrorInfo, useCallback, useEffect, useRef } from 'react';
|
||||||
|
import { ReactFlowProvider } from 'reactflow';
|
||||||
import { useMapperHandlers } from './useMapperHandlers';
|
import { useMapperHandlers } from './useMapperHandlers';
|
||||||
|
|
||||||
import './common-styles/main.scss';
|
|
||||||
import { MapRootProvider } from '@/hooks/Mapper/mapRootProvider';
|
|
||||||
import { MapRootContent } from '@/hooks/Mapper/components/mapRootContent/MapRootContent.tsx';
|
import { MapRootContent } from '@/hooks/Mapper/components/mapRootContent/MapRootContent.tsx';
|
||||||
|
import { MapRootProvider } from '@/hooks/Mapper/mapRootProvider';
|
||||||
|
import './common-styles/main.scss';
|
||||||
|
|
||||||
const ErrorFallback = () => {
|
const ErrorFallback = () => {
|
||||||
return <div className="!z-100 absolute w-screen h-screen bg-transparent"></div>;
|
return <div className="!z-100 absolute w-screen h-screen bg-transparent"></div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function MapRoot({ hooks }) {
|
export default function MapRoot({ hooks }) {
|
||||||
const mapRef = useRef<MapHandlers>(null);
|
|
||||||
const providerRef = useRef<MapHandlers>(null);
|
const providerRef = useRef<MapHandlers>(null);
|
||||||
const hooksRef = useRef<any>(hooks);
|
const hooksRef = useRef<any>(hooks);
|
||||||
|
|
||||||
const mapperHandlerRefs = useRef([mapRef, providerRef]);
|
const mapperHandlerRefs = useRef([providerRef]);
|
||||||
|
|
||||||
const { handleCommand, handleMapEvent, handleMapEvents } = useMapperHandlers(mapperHandlerRefs.current, hooksRef);
|
const { handleCommand, handleMapEvent } = useMapperHandlers(mapperHandlerRefs.current, hooksRef);
|
||||||
|
|
||||||
const logError = useCallback((error: Error, info: ErrorInfo) => {
|
const logError = useCallback((error: Error, info: ErrorInfo) => {
|
||||||
if (!hooksRef.current) {
|
if (!hooksRef.current) {
|
||||||
@@ -36,12 +35,11 @@ export default function MapRoot({ hooks }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hooksRef.current.handleEvent('map_event', handleMapEvent);
|
hooksRef.current.handleEvent('map_event', handleMapEvent);
|
||||||
hooksRef.current.handleEvent('map_events', handleMapEvents);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PrimeReactProvider>
|
<PrimeReactProvider>
|
||||||
<MapRootProvider fwdRef={providerRef} outCommand={handleCommand} mapRef={mapRef}>
|
<MapRootProvider fwdRef={providerRef} outCommand={handleCommand}>
|
||||||
<ErrorBoundary FallbackComponent={ErrorFallback} onError={logError}>
|
<ErrorBoundary FallbackComponent={ErrorFallback} onError={logError}>
|
||||||
<ReactFlowProvider>
|
<ReactFlowProvider>
|
||||||
<MapRootContent />
|
<MapRootContent />
|
||||||
|
|||||||
@@ -67,16 +67,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-sortable-column {
|
.p-datatable-thead {
|
||||||
font-size: 12px;
|
th, th.p-sortable-column {
|
||||||
font-weight: bold;
|
font-size: 12px;
|
||||||
padding: 3px 4px;
|
font-weight: bold;
|
||||||
|
padding: 3px 4px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-selectable-row td {
|
.p-selectable-row td {
|
||||||
padding: 4px 4px;
|
padding: 4px 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-datatable.p-datatable-sm .p-datatable-tbody > tr > td {
|
||||||
|
padding: 3px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.p-sortable-column > .p-column-header-content > span:last-child {
|
.p-sortable-column > .p-column-header-content > span:last-child {
|
||||||
transform: scale(0.7);
|
transform: scale(0.7);
|
||||||
|
|
||||||
@@ -93,6 +99,11 @@
|
|||||||
.p-dropdown-item {
|
.p-dropdown-item {
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.p-dropdown-item-label {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-dropdown-item-group {
|
.p-dropdown-item-group {
|
||||||
@@ -108,3 +119,168 @@
|
|||||||
.p-dropdown-empty-message {
|
.p-dropdown-empty-message {
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-autocomplete .p-autocomplete-multiple-container .p-autocomplete-token {
|
||||||
|
margin-right: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fixed sizes of Input switch */
|
||||||
|
.p-inputswitch {
|
||||||
|
width: 2.0rem;
|
||||||
|
height: 1.15rem;
|
||||||
|
|
||||||
|
.p-inputswitch-slider:before {
|
||||||
|
width: 0.8rem;
|
||||||
|
height: 0.8rem;
|
||||||
|
left: 0.14rem;
|
||||||
|
margin-top: -0.385rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.p-highlight .p-inputswitch-slider:before {
|
||||||
|
transform: translateX(0.8rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.p-disabled):has(.p-inputswitch-input:hover) .p-inputswitch-slider {
|
||||||
|
background: rgb(255 255 255 / 21%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.p-highlight .p-inputswitch-slider {
|
||||||
|
background: #966d3d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-datatable-wrapper {
|
||||||
|
height: 100%;
|
||||||
|
& {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: rgba(255, 255, 255, 0.5) transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(255, 255, 255, 0.5);
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
background-clip: content-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-button {
|
||||||
|
display: none;
|
||||||
|
height: 0;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-datatable .p-datatable-tbody > tr.p-highlight {
|
||||||
|
background: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suppress-menu-behaviour {
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
.p-menuitem-content {
|
||||||
|
pointer-events: initial;
|
||||||
|
background-color: initial !important;
|
||||||
|
}
|
||||||
|
.p-menuitem-content:hover {
|
||||||
|
background-color: initial !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.p-autocomplete .p-autocomplete-multiple-container:not(.p-disabled).p-focus {
|
||||||
|
box-shadow: 0 0 0 1px #335c7e;
|
||||||
|
border-color: #335c7e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-inputtext:enabled:focus {
|
||||||
|
box-shadow: 0 0 0 1px #335c7e;
|
||||||
|
border-color: #335c7e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-inputtext:enabled:hover {
|
||||||
|
border-color: #335c7e;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --------------- TOAST
|
||||||
|
.p-toast .p-toast-message {
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
color: #e0e0e0;
|
||||||
|
border-left: 4px solid transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-toast .p-toast-message .p-toast-summary {
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-toast .p-toast-message .p-toast-detail {
|
||||||
|
color: #c0c0c0;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-toast .p-toast-icon-close {
|
||||||
|
color: #ffaa00;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
.p-toast .p-toast-icon-close:hover {
|
||||||
|
background: #333;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-toast-message-success {
|
||||||
|
border-left-color: #f1c40f;
|
||||||
|
}
|
||||||
|
.p-toast-message-error {
|
||||||
|
border-left-color: #e74c3c;
|
||||||
|
}
|
||||||
|
.p-toast-message-info {
|
||||||
|
border-left-color: #3498db;
|
||||||
|
}
|
||||||
|
.p-toast-message-warn {
|
||||||
|
border-left-color: #e67e22;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-toast-message-success .p-toast-message-icon {
|
||||||
|
color: #f1c40f;
|
||||||
|
}
|
||||||
|
.p-toast-message-error .p-toast-message-icon {
|
||||||
|
color: #e74c3c;
|
||||||
|
}
|
||||||
|
.p-toast-message-info .p-toast-message-icon {
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
.p-toast-message-warn .p-toast-message-icon {
|
||||||
|
color: #e67e22;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-toast-message-success .p-toast-message-content {
|
||||||
|
border-left-color: #f1c40f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-toast-message-error .p-toast-message-content {
|
||||||
|
border-left-color: #e74c3c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-toast-message-info .p-toast-message-content {
|
||||||
|
border-left-color: #3498db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-toast-message-warn .p-toast-message-content {
|
||||||
|
border-left-color: #e67e22;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
/* Основной класс диалога */
|
|
||||||
body .p-dialog {
|
body .p-dialog {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
//position: absolute;
|
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
//visibility: hidden;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
box-shadow: 0 2px 10px 0 rgba(0,0,0,0.2);
|
box-shadow: 0 2px 10px 0 rgba(0,0,0,0.2);
|
||||||
@@ -29,12 +26,10 @@ body .p-dialog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Стиль видимого диалога */
|
|
||||||
.p-dialog-visible {
|
.p-dialog-visible {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Анимации */
|
|
||||||
.p-dialog-enter {
|
.p-dialog-enter {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
@@ -53,31 +48,27 @@ body .p-dialog {
|
|||||||
transition: opacity 0.3s;
|
transition: opacity 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Заголовок диалога */
|
|
||||||
.p-dialog-header {
|
.p-dialog-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
background: #f4f4f4;
|
background: #f4f4f4;
|
||||||
//border-bottom: 1px solid #ddd;
|
height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Содержимое диалога */
|
|
||||||
.p-dialog-content {
|
.p-dialog-content {
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Подвал диалога */
|
|
||||||
.p-dialog-footer {
|
.p-dialog-footer {
|
||||||
padding: 1rem;
|
padding: .75rem 1rem;
|
||||||
border-top: 1px solid #ddd;
|
border-top: none !important;
|
||||||
background: #f4f4f4;
|
//background: #f4f4f4;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Кнопка закрытия диалога */
|
|
||||||
.p-dialog-header-close {
|
.p-dialog-header-close {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -93,3 +84,12 @@ body .p-dialog {
|
|||||||
.p-dialog-header-close .pi {
|
.p-dialog-header-close .pi {
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-dialog {
|
||||||
|
.p-dialog-title {
|
||||||
|
font-size: 1rem !important;
|
||||||
|
}
|
||||||
|
.p-dialog-header-icons {
|
||||||
|
align-self: initial !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
.p-confirm-popup {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
@apply p-[12px];
|
||||||
|
|
||||||
|
&::before, &::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-confirm-popup-content, .p-confirm-popup-footer {
|
||||||
|
@apply p-0 m-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-confirm-popup-content {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-confirm-popup-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-confirm-popup-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-confirm-popup-message {
|
||||||
|
@apply m-0;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-confirm-popup-reject.p-button-sm,
|
||||||
|
.p-confirm-popup-accept.p-button-sm {
|
||||||
|
@apply px-1.5 py-1 m-0;
|
||||||
|
|
||||||
|
& > span {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
.vertical-tabs-container {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 300px;
|
||||||
|
|
||||||
|
.p-tabview {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-tabview-panels {
|
||||||
|
padding: 6px 1rem;
|
||||||
|
flex-grow: 1;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-tabview-nav-container {
|
||||||
|
border-right: none;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-tabview-nav {
|
||||||
|
flex-direction: column;
|
||||||
|
width: 150px;
|
||||||
|
min-height: 100%;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
li {
|
||||||
|
width: 100%;
|
||||||
|
border-right: 4px solid var(--surface-hover);
|
||||||
|
background-color: var(--surface-card);
|
||||||
|
|
||||||
|
transition: background-color 200ms, border-right-color 200ms;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--surface-hover);
|
||||||
|
border-right: 4px solid var(--surface-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-tabview-nav-link {
|
||||||
|
transition: color 200ms;
|
||||||
|
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 10px;
|
||||||
|
//background-color: var(--surface-card);
|
||||||
|
background-color: initial;
|
||||||
|
border: none;
|
||||||
|
color: var(--gray-400);
|
||||||
|
|
||||||
|
border-radius: initial;
|
||||||
|
font-weight: 400;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.p-tabview-selected {
|
||||||
|
background-color: var(--surface-50);
|
||||||
|
border-right: 4px solid var(--primary-color);
|
||||||
|
|
||||||
|
.p-tabview-nav-link {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
//background-color: var(--surface-hover);
|
||||||
|
border-right: 4px solid var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-tabview-panel {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
@import "fix-dialog";
|
@import "fix-dialog";
|
||||||
|
@import "fix-popup";
|
||||||
|
@import "fix-tabs";
|
||||||
//@import "fix-input";
|
//@import "fix-input";
|
||||||
|
|
||||||
//@import "theme";
|
//@import "theme";
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
.Docked {
|
||||||
|
content: " ";
|
||||||
|
display: inline-block;
|
||||||
|
width: 11px;
|
||||||
|
height: 11px;
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 1px;
|
||||||
|
|
||||||
|
background-image: url(/images/citadelLarge.png);
|
||||||
|
left: 2px;
|
||||||
|
top: 22px;
|
||||||
|
transform: rotateZ(0deg);
|
||||||
|
}
|
||||||
@@ -1,20 +1,39 @@
|
|||||||
import { useCallback } from 'react';
|
import { emitMapEvent } from '@/hooks/Mapper/events';
|
||||||
import clsx from 'clsx';
|
import { isDocked } from '@/hooks/Mapper/helpers/isDocked.ts';
|
||||||
import { useAutoAnimate } from '@formkit/auto-animate/react';
|
|
||||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
|
|
||||||
import { CharacterTypeRaw } from '@/hooks/Mapper/types';
|
import { CharacterTypeRaw } from '@/hooks/Mapper/types';
|
||||||
|
import { Commands, OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||||
|
import { useAutoAnimate } from '@formkit/auto-animate/react';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { PrimeIcons } from 'primereact/api';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import classes from './Characters.module.scss';
|
||||||
|
interface CharactersProps {
|
||||||
|
data: CharacterTypeRaw[];
|
||||||
|
}
|
||||||
|
|
||||||
const Characters = ({ data }: { data: CharacterTypeRaw[] }) => {
|
export const Characters = ({ data }: CharactersProps) => {
|
||||||
const [parent] = useAutoAnimate();
|
const [parent] = useAutoAnimate();
|
||||||
const { mapRef } = useMapRootState();
|
|
||||||
|
|
||||||
const handleSelect = useCallback(
|
const {
|
||||||
(character: CharacterTypeRaw) => {
|
outCommand,
|
||||||
mapRef.current?.command(Commands.centerSystem, character?.location?.solar_system_id?.toString());
|
data: { mainCharacterEveId, followingCharacterEveId },
|
||||||
},
|
} = useMapRootState();
|
||||||
[mapRef],
|
|
||||||
);
|
const handleSelect = useCallback(async (character: CharacterTypeRaw) => {
|
||||||
|
if (!character) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await outCommand({
|
||||||
|
type: OutCommand.startTracking,
|
||||||
|
data: { character_eve_id: character.eve_id },
|
||||||
|
});
|
||||||
|
emitMapEvent({
|
||||||
|
name: Commands.centerSystem,
|
||||||
|
data: character.location?.solar_system_id?.toString(),
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
const items = data.map(character => (
|
const items = data.map(character => (
|
||||||
<li
|
<li
|
||||||
@@ -22,21 +41,71 @@ const Characters = ({ data }: { data: CharacterTypeRaw[] }) => {
|
|||||||
className="flex flex-col items-center justify-center"
|
className="flex flex-col items-center justify-center"
|
||||||
onClick={() => handleSelect(character)}
|
onClick={() => handleSelect(character)}
|
||||||
>
|
>
|
||||||
<div className="tooltip tooltip-bottom" title={character.name}>
|
<div
|
||||||
<a
|
className={clsx(
|
||||||
className={clsx('wd-characters-icons wd-bg-default', { ['character-online']: character.online })}
|
'overflow-hidden relative',
|
||||||
|
'flex w-[35px] h-[35px] rounded-[4px] border-[1px] border-solid bg-transparent cursor-pointer',
|
||||||
|
'transition-colors duration-250 hover:bg-stone-300/90',
|
||||||
|
{
|
||||||
|
['border-stone-800/90']: !character.online,
|
||||||
|
['border-lime-600/70']: character.online,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
title={character.tracking_paused ? `${character.name} - Tracking Paused (click to resume)` : character.name}
|
||||||
|
>
|
||||||
|
{character.tracking_paused && (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
className={clsx(
|
||||||
|
'absolute flex flex-col p-[2px] top-[0px] left-[0px] w-[35px] h-[35px]',
|
||||||
|
'text-yellow-500 text-[9px] z-10 bg-gray-800/40',
|
||||||
|
'pi',
|
||||||
|
PrimeIcons.PAUSE,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{mainCharacterEveId === character.eve_id && (
|
||||||
|
<span
|
||||||
|
className={clsx(
|
||||||
|
'absolute top-[2px] left-[22px] w-[9px] h-[9px]',
|
||||||
|
'text-yellow-500 text-[9px] rounded-[1px] z-10',
|
||||||
|
'pi',
|
||||||
|
PrimeIcons.STAR_FILL,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{followingCharacterEveId === character.eve_id && (
|
||||||
|
<span
|
||||||
|
className={clsx(
|
||||||
|
'absolute top-[23px] left-[22px] w-[10px] h-[10px]',
|
||||||
|
'text-sky-300 text-[10px] rounded-[1px] z-10',
|
||||||
|
'pi pi-angle-double-right',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isDocked(character.location) && <div className={classes.Docked} />}
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
'flex w-full h-full bg-transparent cursor-pointer',
|
||||||
|
'bg-center bg-no-repeat bg-[length:100%]',
|
||||||
|
'transition-opacity',
|
||||||
|
'shadow-[inset_0_1px_6px_1px_#000000]',
|
||||||
|
{
|
||||||
|
['opacity-60']: !character.online,
|
||||||
|
['opacity-100']: character.online,
|
||||||
|
},
|
||||||
|
)}
|
||||||
style={{ backgroundImage: `url(https://images.evetech.net/characters/${character.eve_id}/portrait)` }}
|
style={{ backgroundImage: `url(https://images.evetech.net/characters/${character.eve_id}/portrait)` }}
|
||||||
></a>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
));
|
));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className="flex characters" id="characters" ref={parent}>
|
<ul className="flex gap-1 characters" id="characters" ref={parent}>
|
||||||
{items}
|
{items}
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line react/display-name
|
|
||||||
export default Characters;
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import React, { RefObject } from 'react';
|
import React, { RefObject } from 'react';
|
||||||
import { ContextMenu } from 'primereact/contextmenu';
|
import { ContextMenu } from 'primereact/contextmenu';
|
||||||
import { SolarSystemRawType } from '@/hooks/Mapper/types';
|
import { PingType, SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||||
import { useContextMenuSystemItems } from '@/hooks/Mapper/components/contexts/ContextMenuSystem/useContextMenuSystemItems.tsx';
|
import { useContextMenuSystemItems } from '@/hooks/Mapper/components/contexts/ContextMenuSystem/useContextMenuSystemItems.tsx';
|
||||||
import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
||||||
|
|
||||||
export interface ContextMenuSystemProps {
|
export interface ContextMenuSystemProps {
|
||||||
hubs: string[];
|
hubs: string[];
|
||||||
|
userHubs: string[];
|
||||||
contextMenuRef: RefObject<ContextMenu>;
|
contextMenuRef: RefObject<ContextMenu>;
|
||||||
systemId: string | undefined;
|
systemId: string | undefined;
|
||||||
systems: SolarSystemRawType[];
|
systems: SolarSystemRawType[];
|
||||||
@@ -13,10 +14,12 @@ export interface ContextMenuSystemProps {
|
|||||||
onLockToggle(): void;
|
onLockToggle(): void;
|
||||||
onOpenSettings(): void;
|
onOpenSettings(): void;
|
||||||
onHubToggle(): void;
|
onHubToggle(): void;
|
||||||
|
onUserHubToggle(): void;
|
||||||
onSystemTag(val?: string): void;
|
onSystemTag(val?: string): void;
|
||||||
onSystemStatus(val: number): void;
|
onSystemStatus(val: number): void;
|
||||||
onSystemLabels(val: string): void;
|
onSystemLabels(val: string): void;
|
||||||
onCustomLabelDialog(): void;
|
onCustomLabelDialog(): void;
|
||||||
|
onTogglePing(type: PingType, solar_system_id: string, hasPing: boolean): void;
|
||||||
onWaypointSet: WaypointSetContextHandler;
|
onWaypointSet: WaypointSetContextHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +28,7 @@ export const ContextMenuSystem: React.FC<ContextMenuSystemProps> = ({ contextMen
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ContextMenu model={items} ref={contextMenuRef} breakpoint="767px" />
|
<ContextMenu className="min-w-[200px]" model={items} ref={contextMenuRef} breakpoint="767px" />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export * from './useTagMenu';
|
export * from './useTagMenu';
|
||||||
export * from './useStatusMenu';
|
export * from './useStatusMenu';
|
||||||
export * from './useLabelsMenu';
|
export * from './useLabelsMenu';
|
||||||
|
export * from './useUserRoute';
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
.active {
|
|
||||||
background-color: rgba(98, 98, 98, 0.33);
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
.active {
|
|
||||||
background-color: rgba(98, 98, 98, 0.33);
|
|
||||||
}
|
|
||||||
@@ -1 +1 @@
|
|||||||
export * from './useTagMenu.ts';
|
export * from './useTagMenu.tsx';
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
.active {
|
|
||||||
background-color: rgba(98, 98, 98, 0.33);
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
import { MenuItem } from 'primereact/menuitem';
|
|
||||||
import { PrimeIcons } from 'primereact/api';
|
|
||||||
import { useCallback, useRef } from 'react';
|
|
||||||
import { SolarSystemRawType } from '@/hooks/Mapper/types';
|
|
||||||
import { getSystemById } from '@/hooks/Mapper/helpers';
|
|
||||||
import clsx from 'clsx';
|
|
||||||
import { GRADIENT_MENU_ACTIVE_CLASSES } from '@/hooks/Mapper/constants.ts';
|
|
||||||
|
|
||||||
const AVAILABLE_LETTERS = ['A', 'B', 'C', 'D', 'E', 'F', 'X', 'Y', 'Z'];
|
|
||||||
const AVAILABLE_NUMBERS = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
|
||||||
|
|
||||||
export const useTagMenu = (
|
|
||||||
systems: SolarSystemRawType[],
|
|
||||||
systemId: string | undefined,
|
|
||||||
onSystemTag: (val?: string) => void,
|
|
||||||
): (() => MenuItem) => {
|
|
||||||
const ref = useRef({ onSystemTag, systems, systemId });
|
|
||||||
ref.current = { onSystemTag, systems, systemId };
|
|
||||||
|
|
||||||
return useCallback(() => {
|
|
||||||
const { onSystemTag, systemId, systems } = ref.current;
|
|
||||||
const system = systemId ? getSystemById(systems, systemId) : undefined;
|
|
||||||
|
|
||||||
const isSelectedLetters = AVAILABLE_LETTERS.includes(system?.tag ?? '');
|
|
||||||
const isSelectedNumbers = AVAILABLE_NUMBERS.includes(system?.tag ?? '');
|
|
||||||
|
|
||||||
const menuItem: MenuItem = {
|
|
||||||
label: 'Tag',
|
|
||||||
icon: PrimeIcons.HASHTAG,
|
|
||||||
className: clsx({ [GRADIENT_MENU_ACTIVE_CLASSES]: isSelectedLetters || isSelectedNumbers }),
|
|
||||||
items: [
|
|
||||||
...(system?.tag !== '' && system?.tag !== null
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
label: 'Clear',
|
|
||||||
icon: PrimeIcons.BAN,
|
|
||||||
command: () => onSystemTag(),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
{
|
|
||||||
label: 'Letter',
|
|
||||||
icon: PrimeIcons.TAGS,
|
|
||||||
className: clsx({ [GRADIENT_MENU_ACTIVE_CLASSES]: isSelectedLetters }),
|
|
||||||
items: AVAILABLE_LETTERS.map(x => ({
|
|
||||||
label: x,
|
|
||||||
icon: PrimeIcons.TAG,
|
|
||||||
command: () => onSystemTag(x),
|
|
||||||
className: clsx({ [GRADIENT_MENU_ACTIVE_CLASSES]: system?.tag === x }),
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Digit',
|
|
||||||
icon: PrimeIcons.TAGS,
|
|
||||||
className: clsx({ [GRADIENT_MENU_ACTIVE_CLASSES]: isSelectedNumbers }),
|
|
||||||
items: AVAILABLE_NUMBERS.map(x => ({
|
|
||||||
label: x,
|
|
||||||
icon: PrimeIcons.TAG,
|
|
||||||
command: () => onSystemTag(x),
|
|
||||||
className: clsx({ [GRADIENT_MENU_ACTIVE_CLASSES]: system?.tag === x }),
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
return menuItem;
|
|
||||||
}, []);
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import { MenuItem } from 'primereact/menuitem';
|
||||||
|
import { PrimeIcons } from 'primereact/api';
|
||||||
|
import { useCallback, useRef } from 'react';
|
||||||
|
import { SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||||
|
import { getSystemById } from '@/hooks/Mapper/helpers';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { GRADIENT_MENU_ACTIVE_CLASSES } from '@/hooks/Mapper/constants.ts';
|
||||||
|
import { LayoutEventBlocker } from '@/hooks/Mapper/components/ui-kit';
|
||||||
|
import { Button } from 'primereact/button';
|
||||||
|
|
||||||
|
const AVAILABLE_TAGS = [
|
||||||
|
'A',
|
||||||
|
'B',
|
||||||
|
'C',
|
||||||
|
'D',
|
||||||
|
'E',
|
||||||
|
'F',
|
||||||
|
'G',
|
||||||
|
'H',
|
||||||
|
'I',
|
||||||
|
'X',
|
||||||
|
'Y',
|
||||||
|
'Z',
|
||||||
|
'0',
|
||||||
|
'1',
|
||||||
|
'2',
|
||||||
|
'3',
|
||||||
|
'4',
|
||||||
|
'5',
|
||||||
|
'6',
|
||||||
|
'7',
|
||||||
|
'8',
|
||||||
|
'9',
|
||||||
|
];
|
||||||
|
|
||||||
|
export const useTagMenu = (
|
||||||
|
systems: SolarSystemRawType[],
|
||||||
|
systemId: string | undefined,
|
||||||
|
onSystemTag: (val?: string) => void,
|
||||||
|
): (() => MenuItem) => {
|
||||||
|
const ref = useRef({ onSystemTag, systems, systemId });
|
||||||
|
ref.current = { onSystemTag, systems, systemId };
|
||||||
|
|
||||||
|
return useCallback(() => {
|
||||||
|
const { onSystemTag, systemId, systems } = ref.current;
|
||||||
|
const system = systemId ? getSystemById(systems, systemId) : undefined;
|
||||||
|
|
||||||
|
const isSelectedTag = AVAILABLE_TAGS.includes(system?.tag ?? '');
|
||||||
|
|
||||||
|
const menuItem: MenuItem = {
|
||||||
|
label: 'Tag',
|
||||||
|
icon: PrimeIcons.HASHTAG,
|
||||||
|
className: clsx({ [GRADIENT_MENU_ACTIVE_CLASSES]: isSelectedTag }),
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Digit',
|
||||||
|
icon: PrimeIcons.TAGS,
|
||||||
|
className: '!h-[128px] suppress-menu-behaviour',
|
||||||
|
template: () => {
|
||||||
|
return (
|
||||||
|
<LayoutEventBlocker className="flex flex-col gap-1 w-[200px] h-full px-2">
|
||||||
|
<div className="grid grid-cols-[auto_auto_auto_auto_auto_auto] gap-1">
|
||||||
|
{AVAILABLE_TAGS.map(x => (
|
||||||
|
<Button
|
||||||
|
outlined={system?.tag !== x}
|
||||||
|
severity="warning"
|
||||||
|
key={x}
|
||||||
|
value={x}
|
||||||
|
size="small"
|
||||||
|
className="p-[3px] justify-center"
|
||||||
|
onClick={() => system?.tag !== x && onSystemTag(x)}
|
||||||
|
>
|
||||||
|
{x}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
<Button
|
||||||
|
disabled={!isSelectedTag}
|
||||||
|
icon="pi pi-ban"
|
||||||
|
size="small"
|
||||||
|
className="!p-0 !w-[initial] justify-center"
|
||||||
|
outlined
|
||||||
|
severity="help"
|
||||||
|
onClick={() => onSystemTag()}
|
||||||
|
></Button>
|
||||||
|
</div>
|
||||||
|
</LayoutEventBlocker>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
return menuItem;
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { MapUserAddIcon, MapUserDeleteIcon } from '@/hooks/Mapper/icons';
|
||||||
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
|
import { useCallback, useRef } from 'react';
|
||||||
|
import { WidgetsIds } from '@/hooks/Mapper/components/mapInterface/constants.tsx';
|
||||||
|
|
||||||
|
interface UseUserRouteProps {
|
||||||
|
systemId: string | undefined;
|
||||||
|
userHubs: string[];
|
||||||
|
onUserHubToggle(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUserRoute = ({ userHubs, systemId, onUserHubToggle }: UseUserRouteProps) => {
|
||||||
|
const {
|
||||||
|
data: { isSubscriptionActive },
|
||||||
|
windowsSettings,
|
||||||
|
} = useMapRootState();
|
||||||
|
|
||||||
|
const ref = useRef({ userHubs, systemId, onUserHubToggle, isSubscriptionActive, windowsSettings });
|
||||||
|
ref.current = { userHubs, systemId, onUserHubToggle, isSubscriptionActive, windowsSettings };
|
||||||
|
|
||||||
|
return useCallback(() => {
|
||||||
|
const { userHubs, systemId, onUserHubToggle, isSubscriptionActive, windowsSettings } = ref.current;
|
||||||
|
|
||||||
|
const isVisibleUserRoutes = windowsSettings.visible.some(x => x === WidgetsIds.userRoutes);
|
||||||
|
|
||||||
|
if (!isSubscriptionActive || !isVisibleUserRoutes || !systemId) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: !userHubs.includes(systemId) ? 'Add User Route' : 'Remove User Route',
|
||||||
|
icon: !userHubs.includes(systemId) ? (
|
||||||
|
<MapUserAddIcon className="mr-1 relative left-[-2px]" />
|
||||||
|
) : (
|
||||||
|
<MapUserDeleteIcon className="mr-1 relative left-[-2px]" />
|
||||||
|
),
|
||||||
|
command: onUserHubToggle,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, [windowsSettings]);
|
||||||
|
};
|
||||||
@@ -5,22 +5,29 @@ import { SolarSystemRawType } from '@/hooks/Mapper/types';
|
|||||||
import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
||||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||||
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
|
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
|
||||||
|
// import { PingType } from '@/hooks/Mapper/types/ping.ts';
|
||||||
|
|
||||||
interface UseContextMenuSystemHandlersProps {
|
interface UseContextMenuSystemHandlersProps {
|
||||||
hubs: string[];
|
hubs: string[];
|
||||||
|
userHubs: string[];
|
||||||
systems: SolarSystemRawType[];
|
systems: SolarSystemRawType[];
|
||||||
outCommand: OutCommandHandler;
|
outCommand: OutCommandHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseContextMenuSystemHandlersProps) => {
|
export const useContextMenuSystemHandlers = ({
|
||||||
|
systems,
|
||||||
|
hubs,
|
||||||
|
userHubs,
|
||||||
|
outCommand,
|
||||||
|
}: UseContextMenuSystemHandlersProps) => {
|
||||||
const contextMenuRef = useRef<ContextMenu | null>(null);
|
const contextMenuRef = useRef<ContextMenu | null>(null);
|
||||||
|
|
||||||
const [system, setSystem] = useState<string>();
|
const [system, setSystem] = useState<string>();
|
||||||
|
|
||||||
const { deleteSystems } = useDeleteSystems();
|
const { deleteSystems } = useDeleteSystems();
|
||||||
|
|
||||||
const ref = useRef({ hubs, system, systems, outCommand, deleteSystems });
|
const ref = useRef({ hubs, userHubs, system, systems, outCommand, deleteSystems });
|
||||||
ref.current = { hubs, system, systems, outCommand, deleteSystems };
|
ref.current = { hubs, userHubs, system, systems, outCommand, deleteSystems };
|
||||||
|
|
||||||
const open = useCallback((ev: any, systemId: string) => {
|
const open = useCallback((ev: any, systemId: string) => {
|
||||||
setSystem(systemId);
|
setSystem(systemId);
|
||||||
@@ -72,6 +79,37 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
|
|||||||
setSystem(undefined);
|
setSystem(undefined);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const onUserHubToggle = useCallback(() => {
|
||||||
|
const { userHubs, system, outCommand } = ref.current;
|
||||||
|
if (!system) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
outCommand({
|
||||||
|
type: !userHubs.includes(system) ? OutCommand.addUserHub : OutCommand.deleteUserHub,
|
||||||
|
data: {
|
||||||
|
system_id: system,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
setSystem(undefined);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// const onTogglePingRally = useCallback(() => {
|
||||||
|
// const { userHubs, system, outCommand } = ref.current;
|
||||||
|
// if (!system) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// outCommand({
|
||||||
|
// type: OutCommand.openPing,
|
||||||
|
// data: {
|
||||||
|
// solar_system_id: system,
|
||||||
|
// type: PingType.Rally,
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// setSystem(undefined);
|
||||||
|
// }, []);
|
||||||
|
|
||||||
const onSystemTag = useCallback((tag?: string) => {
|
const onSystemTag = useCallback((tag?: string) => {
|
||||||
const { system, outCommand } = ref.current;
|
const { system, outCommand } = ref.current;
|
||||||
if (!system) {
|
if (!system) {
|
||||||
@@ -88,6 +126,22 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
|
|||||||
setSystem(undefined);
|
setSystem(undefined);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const onSystemTemporaryName = useCallback((temporaryName?: string) => {
|
||||||
|
const { system, outCommand } = ref.current;
|
||||||
|
if (!system) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
outCommand({
|
||||||
|
type: OutCommand.updateSystemTemporaryName,
|
||||||
|
data: {
|
||||||
|
system_id: system,
|
||||||
|
value: temporaryName ?? '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
setSystem(undefined);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const onSystemStatus = useCallback((status: number) => {
|
const onSystemStatus = useCallback((status: number) => {
|
||||||
const { system, outCommand } = ref.current;
|
const { system, outCommand } = ref.current;
|
||||||
if (!system) {
|
if (!system) {
|
||||||
@@ -160,7 +214,10 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
|
|||||||
onDeleteSystem,
|
onDeleteSystem,
|
||||||
onLockToggle,
|
onLockToggle,
|
||||||
onHubToggle,
|
onHubToggle,
|
||||||
|
onUserHubToggle,
|
||||||
|
// onTogglePingRally,
|
||||||
onSystemTag,
|
onSystemTag,
|
||||||
|
onSystemTemporaryName,
|
||||||
onSystemStatus,
|
onSystemStatus,
|
||||||
onSystemLabels,
|
onSystemLabels,
|
||||||
onOpenSettings,
|
onOpenSettings,
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { useLabelsMenu, useStatusMenu, useTagMenu } from '@/hooks/Mapper/components/contexts/ContextMenuSystem/hooks';
|
import {
|
||||||
|
useLabelsMenu,
|
||||||
|
useStatusMenu,
|
||||||
|
useTagMenu,
|
||||||
|
useUserRoute,
|
||||||
|
} from '@/hooks/Mapper/components/contexts/ContextMenuSystem/hooks';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { getSystemById } from '@/hooks/Mapper/helpers';
|
import { getSystemById } from '@/hooks/Mapper/helpers';
|
||||||
import classes from './ContextMenuSystem.module.scss';
|
import classes from './ContextMenuSystem.module.scss';
|
||||||
@@ -6,11 +11,23 @@ import { PrimeIcons } from 'primereact/api';
|
|||||||
import { ContextMenuSystemProps } from '@/hooks/Mapper/components/contexts';
|
import { ContextMenuSystemProps } from '@/hooks/Mapper/components/contexts';
|
||||||
import { useWaypointMenu } from '@/hooks/Mapper/components/contexts/hooks';
|
import { useWaypointMenu } from '@/hooks/Mapper/components/contexts/hooks';
|
||||||
import { FastSystemActions } from '@/hooks/Mapper/components/contexts/components';
|
import { FastSystemActions } from '@/hooks/Mapper/components/contexts/components';
|
||||||
|
import { useMapCheckPermissions } from '@/hooks/Mapper/mapRootProvider/hooks/api';
|
||||||
|
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
|
||||||
|
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
|
||||||
|
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
|
||||||
|
import { MapAddIcon, MapDeleteIcon } from '@/hooks/Mapper/icons';
|
||||||
|
import { PingType } from '@/hooks/Mapper/types';
|
||||||
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { MenuItem } from 'primereact/menuitem';
|
||||||
|
import { MenuItemWithInfo, WdMenuItem } from '@/hooks/Mapper/components/ui-kit';
|
||||||
|
|
||||||
export const useContextMenuSystemItems = ({
|
export const useContextMenuSystemItems = ({
|
||||||
onDeleteSystem,
|
onDeleteSystem,
|
||||||
onLockToggle,
|
onLockToggle,
|
||||||
onHubToggle,
|
onHubToggle,
|
||||||
|
onUserHubToggle,
|
||||||
|
onTogglePing,
|
||||||
onSystemTag,
|
onSystemTag,
|
||||||
onSystemStatus,
|
onSystemStatus,
|
||||||
onSystemLabels,
|
onSystemLabels,
|
||||||
@@ -19,15 +36,40 @@ export const useContextMenuSystemItems = ({
|
|||||||
onWaypointSet,
|
onWaypointSet,
|
||||||
systemId,
|
systemId,
|
||||||
hubs,
|
hubs,
|
||||||
|
userHubs,
|
||||||
systems,
|
systems,
|
||||||
}: Omit<ContextMenuSystemProps, 'contextMenuRef'>) => {
|
}: Omit<ContextMenuSystemProps, 'contextMenuRef'>) => {
|
||||||
const getTags = useTagMenu(systems, systemId, onSystemTag);
|
const getTags = useTagMenu(systems, systemId, onSystemTag);
|
||||||
const getStatus = useStatusMenu(systems, systemId, onSystemStatus);
|
const getStatus = useStatusMenu(systems, systemId, onSystemStatus);
|
||||||
const getLabels = useLabelsMenu(systems, systemId, onSystemLabels, onCustomLabelDialog);
|
const getLabels = useLabelsMenu(systems, systemId, onSystemLabels, onCustomLabelDialog);
|
||||||
const getWaypointMenu = useWaypointMenu(onWaypointSet);
|
const getWaypointMenu = useWaypointMenu(onWaypointSet);
|
||||||
|
const canLockSystem = useMapCheckPermissions([UserPermission.LOCK_SYSTEM]);
|
||||||
|
const canManageSystem = useMapCheckPermissions([UserPermission.UPDATE_SYSTEM]);
|
||||||
|
const canDeleteSystem = useMapCheckPermissions([UserPermission.DELETE_SYSTEM]);
|
||||||
|
const getUserRoutes = useUserRoute({ userHubs, systemId, onUserHubToggle });
|
||||||
|
|
||||||
return useMemo(() => {
|
const {
|
||||||
|
data: { pings, isSubscriptionActive },
|
||||||
|
} = useMapRootState();
|
||||||
|
|
||||||
|
const ping = useMemo(() => (pings.length === 1 ? pings[0] : undefined), [pings]);
|
||||||
|
const isShowPingBtn = useMemo(() => {
|
||||||
|
if (!isSubscriptionActive) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pings.length === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pings[0].solar_system_id === systemId;
|
||||||
|
}, [isSubscriptionActive, pings, systemId]);
|
||||||
|
|
||||||
|
return useMemo((): MenuItem[] => {
|
||||||
const system = systemId ? getSystemById(systems, systemId) : undefined;
|
const system = systemId ? getSystemById(systems, systemId) : undefined;
|
||||||
|
const systemStaticInfo = getSystemStaticInfo(systemId)!;
|
||||||
|
|
||||||
|
const hasPing = ping?.solar_system_id === systemId;
|
||||||
|
|
||||||
if (!system || !systemId) {
|
if (!system || !systemId) {
|
||||||
return [];
|
return [];
|
||||||
@@ -40,7 +82,9 @@ export const useContextMenuSystemItems = ({
|
|||||||
return (
|
return (
|
||||||
<FastSystemActions
|
<FastSystemActions
|
||||||
systemId={systemId}
|
systemId={systemId}
|
||||||
systemName={system.system_static_info.solar_system_name}
|
systemName={systemStaticInfo.solar_system_name}
|
||||||
|
regionName={systemStaticInfo.region_name}
|
||||||
|
isWH={isWormholeSpace(systemStaticInfo.system_class)}
|
||||||
showEdit
|
showEdit
|
||||||
onOpenSettings={onOpenSettings}
|
onOpenSettings={onOpenSettings}
|
||||||
/>
|
/>
|
||||||
@@ -51,13 +95,45 @@ export const useContextMenuSystemItems = ({
|
|||||||
getTags(),
|
getTags(),
|
||||||
getStatus(),
|
getStatus(),
|
||||||
...getLabels(),
|
...getLabels(),
|
||||||
...getWaypointMenu(systemId, system.system_static_info.system_class),
|
...getWaypointMenu(systemId, systemStaticInfo.system_class),
|
||||||
{
|
{
|
||||||
label: !hubs.includes(systemId) ? 'Add in Routes' : 'Remove from Routes',
|
label: !hubs.includes(systemId) ? 'Add Route' : 'Remove Route',
|
||||||
icon: PrimeIcons.MAP_MARKER,
|
icon: !hubs.includes(systemId) ? (
|
||||||
|
<MapAddIcon className="mr-1 relative left-[-2px]" />
|
||||||
|
) : (
|
||||||
|
<MapDeleteIcon className="mr-1 relative left-[-2px]" />
|
||||||
|
),
|
||||||
command: onHubToggle,
|
command: onHubToggle,
|
||||||
},
|
},
|
||||||
...(system.locked
|
...getUserRoutes(),
|
||||||
|
|
||||||
|
{ separator: true },
|
||||||
|
{
|
||||||
|
command: () => onTogglePing(PingType.Rally, systemId, hasPing),
|
||||||
|
disabled: !isShowPingBtn,
|
||||||
|
template: () => {
|
||||||
|
const iconClasses = clsx({
|
||||||
|
'pi text-cyan-400 hero-signal': !hasPing,
|
||||||
|
'pi text-red-400 hero-signal-slash': hasPing,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isShowPingBtn) {
|
||||||
|
return <WdMenuItem icon={iconClasses}>{!hasPing ? 'Ping: RALLY' : 'Cancel: RALLY'}</WdMenuItem>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItemWithInfo
|
||||||
|
infoTitle="Locked. Ping can be set only for one system."
|
||||||
|
infoClass="pi-lock text-stone-500 mr-[12px]"
|
||||||
|
>
|
||||||
|
<WdMenuItem disabled icon={iconClasses}>
|
||||||
|
{!hasPing ? 'Ping: RALLY' : 'Cancel: RALLY'}
|
||||||
|
</WdMenuItem>
|
||||||
|
</MenuItemWithInfo>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...(system.locked && canLockSystem
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
label: 'Unlock',
|
label: 'Unlock',
|
||||||
@@ -65,31 +141,60 @@ export const useContextMenuSystemItems = ({
|
|||||||
command: onLockToggle,
|
command: onLockToggle,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [
|
: []),
|
||||||
|
...(!system.locked && canManageSystem
|
||||||
|
? [
|
||||||
{
|
{
|
||||||
label: 'Lock',
|
label: 'Lock',
|
||||||
icon: PrimeIcons.LOCK,
|
icon: PrimeIcons.LOCK,
|
||||||
command: onLockToggle,
|
command: onLockToggle,
|
||||||
},
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
|
||||||
|
...(canDeleteSystem && !system.locked
|
||||||
|
? [
|
||||||
{ separator: true },
|
{ separator: true },
|
||||||
{
|
{
|
||||||
label: 'Delete',
|
|
||||||
icon: PrimeIcons.TRASH,
|
|
||||||
command: onDeleteSystem,
|
command: onDeleteSystem,
|
||||||
|
disabled: hasPing,
|
||||||
|
template: () => {
|
||||||
|
if (!hasPing) {
|
||||||
|
return <WdMenuItem icon="text-red-400 pi pi-trash">Delete</WdMenuItem>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItemWithInfo
|
||||||
|
infoTitle="Locked. System can not be deleted until ping set."
|
||||||
|
infoClass="pi-lock text-stone-500 mr-[12px]"
|
||||||
|
>
|
||||||
|
<WdMenuItem disabled icon="text-red-400 pi pi-trash">
|
||||||
|
Delete
|
||||||
|
</WdMenuItem>
|
||||||
|
</MenuItemWithInfo>
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
]),
|
]
|
||||||
|
: []),
|
||||||
];
|
];
|
||||||
}, [
|
}, [
|
||||||
systems,
|
|
||||||
systemId,
|
systemId,
|
||||||
|
systems,
|
||||||
getTags,
|
getTags,
|
||||||
getStatus,
|
getStatus,
|
||||||
getLabels,
|
getLabels,
|
||||||
getWaypointMenu,
|
getWaypointMenu,
|
||||||
|
getUserRoutes,
|
||||||
hubs,
|
hubs,
|
||||||
onHubToggle,
|
onHubToggle,
|
||||||
onOpenSettings,
|
canLockSystem,
|
||||||
onLockToggle,
|
onLockToggle,
|
||||||
|
canDeleteSystem,
|
||||||
onDeleteSystem,
|
onDeleteSystem,
|
||||||
|
onOpenSettings,
|
||||||
|
onTogglePing,
|
||||||
|
ping,
|
||||||
|
isShowPingBtn,
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/ty
|
|||||||
import { FastSystemActions } from '@/hooks/Mapper/components/contexts/components';
|
import { FastSystemActions } from '@/hooks/Mapper/components/contexts/components';
|
||||||
import { useJumpPlannerMenu } from '@/hooks/Mapper/components/contexts/hooks';
|
import { useJumpPlannerMenu } from '@/hooks/Mapper/components/contexts/hooks';
|
||||||
import { Route } from '@/hooks/Mapper/types/routes.ts';
|
import { Route } from '@/hooks/Mapper/types/routes.ts';
|
||||||
|
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
|
||||||
|
import { MapAddIcon, MapDeleteIcon } from '@/hooks/Mapper/icons';
|
||||||
|
|
||||||
export interface ContextMenuSystemInfoProps {
|
export interface ContextMenuSystemInfoProps {
|
||||||
systemStatics: Map<number, SolarSystemStaticInfoRaw>;
|
systemStatics: Map<number, SolarSystemStaticInfoRaw>;
|
||||||
@@ -48,7 +50,6 @@ export const ContextMenuSystemInfo: React.FC<ContextMenuSystemInfoProps> = ({
|
|||||||
if (!systemId || !system) {
|
if (!systemId || !system) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
className: classes.FastActions,
|
className: classes.FastActions,
|
||||||
@@ -57,6 +58,8 @@ export const ContextMenuSystemInfo: React.FC<ContextMenuSystemInfoProps> = ({
|
|||||||
<FastSystemActions
|
<FastSystemActions
|
||||||
systemId={systemId}
|
systemId={systemId}
|
||||||
systemName={system.solar_system_name}
|
systemName={system.solar_system_name}
|
||||||
|
regionName={system.region_name}
|
||||||
|
isWH={isWormholeSpace(system.system_class)}
|
||||||
onOpenSettings={onOpenSettings}
|
onOpenSettings={onOpenSettings}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -67,8 +70,12 @@ export const ContextMenuSystemInfo: React.FC<ContextMenuSystemInfoProps> = ({
|
|||||||
...getJumpPlannerMenu(system, routes),
|
...getJumpPlannerMenu(system, routes),
|
||||||
...getWaypointMenu(systemId, system.system_class),
|
...getWaypointMenu(systemId, system.system_class),
|
||||||
{
|
{
|
||||||
label: !hubs.includes(systemId) ? 'Add in Routes' : 'Remove from Routes',
|
label: !hubs.includes(systemId) ? 'Add Route' : 'Remove Route',
|
||||||
icon: PrimeIcons.MAP_MARKER,
|
icon: !hubs.includes(systemId) ? (
|
||||||
|
<MapAddIcon className="mr-1 relative left-[-2px]" />
|
||||||
|
) : (
|
||||||
|
<MapDeleteIcon className="mr-1 relative left-[-2px]" />
|
||||||
|
),
|
||||||
command: onHubToggle,
|
command: onHubToggle,
|
||||||
},
|
},
|
||||||
...(!systemOnMap
|
...(!systemOnMap
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
import { RefObject, useCallback, useRef, useState } from 'react';
|
import * as React from 'react';
|
||||||
|
import { useCallback, useRef, useState } from 'react';
|
||||||
import { ContextMenu } from 'primereact/contextmenu';
|
import { ContextMenu } from 'primereact/contextmenu';
|
||||||
import { Commands, MapHandlers, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
|
import { Commands, OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||||
import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
||||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||||
import * as React from 'react';
|
|
||||||
import { SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
|
import { SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
|
||||||
|
import { emitMapEvent } from '@/hooks/Mapper/events';
|
||||||
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
|
import { useRouteProvider } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesProvider.tsx';
|
||||||
|
|
||||||
interface UseContextMenuSystemHandlersProps {
|
export const useContextMenuSystemInfoHandlers = () => {
|
||||||
hubs: string[];
|
const { outCommand } = useMapRootState();
|
||||||
outCommand: OutCommandHandler;
|
const { hubs = [], toggleHubCommand } = useRouteProvider();
|
||||||
mapRef: RefObject<MapHandlers>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand, mapRef }: UseContextMenuSystemHandlersProps) => {
|
|
||||||
const contextMenuRef = useRef<ContextMenu | null>(null);
|
const contextMenuRef = useRef<ContextMenu | null>(null);
|
||||||
|
|
||||||
const [system, setSystem] = useState<string>();
|
const [system, setSystem] = useState<string>();
|
||||||
const routeRef = useRef<(SolarSystemStaticInfoRaw | undefined)[]>([]);
|
const routeRef = useRef<(SolarSystemStaticInfoRaw | undefined)[]>([]);
|
||||||
|
|
||||||
const ref = useRef({ hubs, system, outCommand, mapRef });
|
const ref = useRef({ hubs, system, outCommand, toggleHubCommand });
|
||||||
ref.current = { hubs, system, outCommand, mapRef };
|
ref.current = { hubs, system, outCommand, toggleHubCommand };
|
||||||
|
|
||||||
const open = useCallback(
|
const open = useCallback(
|
||||||
(ev: React.SyntheticEvent, systemId: string, route: (SolarSystemStaticInfoRaw | undefined)[]) => {
|
(ev: React.SyntheticEvent, systemId: string, route: (SolarSystemStaticInfoRaw | undefined)[]) => {
|
||||||
@@ -33,22 +33,17 @@ export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand, mapRef }: U
|
|||||||
);
|
);
|
||||||
|
|
||||||
const onHubToggle = useCallback(() => {
|
const onHubToggle = useCallback(() => {
|
||||||
const { hubs, system, outCommand } = ref.current;
|
const { system } = ref.current;
|
||||||
if (!system) {
|
if (!system) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
outCommand({
|
ref.current.toggleHubCommand(system);
|
||||||
type: !hubs.includes(system) ? OutCommand.addHub : OutCommand.deleteHub,
|
|
||||||
data: {
|
|
||||||
system_id: system,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
setSystem(undefined);
|
setSystem(undefined);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onAddSystem = useCallback(() => {
|
const onAddSystem = useCallback(() => {
|
||||||
const { system: solarSystemId, outCommand, mapRef } = ref.current;
|
const { system: solarSystemId, outCommand } = ref.current;
|
||||||
if (!solarSystemId) {
|
if (!solarSystemId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -59,8 +54,14 @@ export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand, mapRef }: U
|
|||||||
system_id: solarSystemId,
|
system_id: solarSystemId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO add it to some queue
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
mapRef.current?.command(Commands.centerSystem, solarSystemId);
|
emitMapEvent({
|
||||||
|
name: Commands.centerSystem,
|
||||||
|
data: solarSystemId,
|
||||||
|
});
|
||||||
|
|
||||||
setSystem(undefined);
|
setSystem(undefined);
|
||||||
}, 200);
|
}, 200);
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useCallback, useRef } from 'react';
|
import { useCallback, useRef } from 'react';
|
||||||
import { LayoutEventBlocker, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
import { LayoutEventBlocker, TooltipPosition, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
||||||
import { ANOIK_ICON, DOTLAN_ICON, ZKB_ICON } from '@/hooks/Mapper/icons.ts';
|
import { ANOIK_ICON, DOTLAN_ICON, ZKB_ICON } from '@/hooks/Mapper/icons';
|
||||||
|
|
||||||
import classes from './FastSystemActions.module.scss';
|
import classes from './FastSystemActions.module.scss';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
@@ -9,13 +9,22 @@ import { PrimeIcons } from 'primereact/api';
|
|||||||
export interface FastSystemActionsProps {
|
export interface FastSystemActionsProps {
|
||||||
systemId: string;
|
systemId: string;
|
||||||
systemName: string;
|
systemName: string;
|
||||||
|
regionName: string;
|
||||||
|
isWH: boolean;
|
||||||
showEdit?: boolean;
|
showEdit?: boolean;
|
||||||
onOpenSettings(): void;
|
onOpenSettings(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FastSystemActions = ({ systemId, systemName, onOpenSettings, showEdit }: FastSystemActionsProps) => {
|
export const FastSystemActions = ({
|
||||||
const ref = useRef({ systemId, systemName });
|
systemId,
|
||||||
ref.current = { systemId, systemName };
|
systemName,
|
||||||
|
regionName,
|
||||||
|
isWH,
|
||||||
|
onOpenSettings,
|
||||||
|
showEdit,
|
||||||
|
}: FastSystemActionsProps) => {
|
||||||
|
const ref = useRef({ systemId, systemName, regionName, isWH });
|
||||||
|
ref.current = { systemId, systemName, regionName, isWH };
|
||||||
|
|
||||||
const handleOpenZKB = useCallback(
|
const handleOpenZKB = useCallback(
|
||||||
() => window.open(`https://zkillboard.com/system/${ref.current.systemId}`, '_blank'),
|
() => window.open(`https://zkillboard.com/system/${ref.current.systemId}`, '_blank'),
|
||||||
@@ -27,10 +36,17 @@ export const FastSystemActions = ({ systemId, systemName, onOpenSettings, showEd
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleOpenDotlan = useCallback(
|
const handleOpenDotlan = useCallback(() => {
|
||||||
() => window.open(`https://evemaps.dotlan.net/system/${ref.current.systemName}`, '_blank'),
|
if (ref.current.isWH) {
|
||||||
[],
|
window.open(`https://evemaps.dotlan.net/system/${ref.current.systemName}`, '_blank');
|
||||||
);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return window.open(
|
||||||
|
`https://evemaps.dotlan.net/map/${ref.current.regionName.replace(/ /gim, '_')}/${ref.current.systemName}#jumps`,
|
||||||
|
'_blank',
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const copySystemNameToClipboard = useCallback(async () => {
|
const copySystemNameToClipboard = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -43,9 +59,21 @@ export const FastSystemActions = ({ systemId, systemName, onOpenSettings, showEd
|
|||||||
return (
|
return (
|
||||||
<LayoutEventBlocker className={clsx('flex px-2 gap-2 justify-between items-center h-full')}>
|
<LayoutEventBlocker className={clsx('flex px-2 gap-2 justify-between items-center h-full')}>
|
||||||
<div className={clsx('flex gap-2 items-center h-full', classes.Links)}>
|
<div className={clsx('flex gap-2 items-center h-full', classes.Links)}>
|
||||||
<WdImgButton source={ZKB_ICON} onClick={handleOpenZKB} />
|
<WdImgButton
|
||||||
<WdImgButton source={ANOIK_ICON} onClick={handleOpenAnoikis} />
|
tooltip={{ position: TooltipPosition.top, content: 'Open zkillboard' }}
|
||||||
<WdImgButton source={DOTLAN_ICON} onClick={handleOpenDotlan} />
|
source={ZKB_ICON}
|
||||||
|
onClick={handleOpenZKB}
|
||||||
|
/>
|
||||||
|
<WdImgButton
|
||||||
|
tooltip={{ position: TooltipPosition.top, content: 'Open Anoikis' }}
|
||||||
|
source={ANOIK_ICON}
|
||||||
|
onClick={handleOpenAnoikis}
|
||||||
|
/>
|
||||||
|
<WdImgButton
|
||||||
|
tooltip={{ position: TooltipPosition.top, content: 'Open Dotlan' }}
|
||||||
|
source={DOTLAN_ICON}
|
||||||
|
onClick={handleOpenDotlan}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2 items-center pl-1">
|
<div className="flex gap-2 items-center pl-1">
|
||||||
@@ -53,14 +81,14 @@ export const FastSystemActions = ({ systemId, systemName, onOpenSettings, showEd
|
|||||||
textSize={WdImageSize.off}
|
textSize={WdImageSize.off}
|
||||||
className={PrimeIcons.COPY}
|
className={PrimeIcons.COPY}
|
||||||
onClick={copySystemNameToClipboard}
|
onClick={copySystemNameToClipboard}
|
||||||
tooltip={{ content: 'Copy system name' }}
|
tooltip={{ position: TooltipPosition.top, content: 'Copy system name' }}
|
||||||
/>
|
/>
|
||||||
{showEdit && (
|
{showEdit && (
|
||||||
<WdImgButton
|
<WdImgButton
|
||||||
textSize={WdImageSize.off}
|
textSize={WdImageSize.off}
|
||||||
className="pi pi-pen-to-square text-base"
|
className="pi pi-pen-to-square text-base"
|
||||||
onClick={onOpenSettings}
|
onClick={onOpenSettings}
|
||||||
tooltip={{ content: 'Edit system name and description' }}
|
tooltip={{ position: TooltipPosition.top, content: 'Edit system name and description' }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { useCallback } from 'react';
|
|||||||
import { isPossibleSpace } from '@/hooks/Mapper/components/map/helpers/isKnownSpace.ts';
|
import { isPossibleSpace } from '@/hooks/Mapper/components/map/helpers/isKnownSpace.ts';
|
||||||
import { Route } from '@/hooks/Mapper/types/routes.ts';
|
import { Route } from '@/hooks/Mapper/types/routes.ts';
|
||||||
import { SolarSystemRawType, SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
|
import { SolarSystemRawType, SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
|
||||||
import { getSystemById } from '@/hooks/Mapper/helpers';
|
|
||||||
import { SOLAR_SYSTEM_CLASS_IDS } from '@/hooks/Mapper/components/map/constants.ts';
|
import { SOLAR_SYSTEM_CLASS_IDS } from '@/hooks/Mapper/components/map/constants.ts';
|
||||||
|
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
|
||||||
|
|
||||||
const imperialSpace = [SOLAR_SYSTEM_CLASS_IDS.hs, SOLAR_SYSTEM_CLASS_IDS.ls, SOLAR_SYSTEM_CLASS_IDS.ns];
|
const imperialSpace = [SOLAR_SYSTEM_CLASS_IDS.hs, SOLAR_SYSTEM_CLASS_IDS.ls, SOLAR_SYSTEM_CLASS_IDS.ns];
|
||||||
const criminalSpace = [SOLAR_SYSTEM_CLASS_IDS.ls, SOLAR_SYSTEM_CLASS_IDS.ns];
|
const criminalSpace = [SOLAR_SYSTEM_CLASS_IDS.ls, SOLAR_SYSTEM_CLASS_IDS.ns];
|
||||||
@@ -47,7 +47,7 @@ export const useJumpPlannerMenu = (
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const origin = getSystemById(systems, systemIdFrom)?.system_static_info;
|
const origin = getSystemStaticInfo(systemIdFrom);
|
||||||
|
|
||||||
if (!origin) {
|
if (!origin) {
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
.active {
|
|
||||||
background-color: rgba(98, 98, 98, 0.33);
|
|
||||||
}
|
|
||||||
1
assets/js/hooks/Mapper/components/helpers/index.ts
Normal file
1
assets/js/hooks/Mapper/components/helpers/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './parseMapUserSettings.ts';
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { MapUserSettings, SettingsWithVersion } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||||
|
|
||||||
|
const REQUIRED_KEYS = [
|
||||||
|
'widgets',
|
||||||
|
'interface',
|
||||||
|
'onTheMap',
|
||||||
|
'routes',
|
||||||
|
'localWidget',
|
||||||
|
'signaturesWidget',
|
||||||
|
'killsWidget',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
type RequiredKeys = (typeof REQUIRED_KEYS)[number];
|
||||||
|
|
||||||
|
/** Custom error for any parsing / validation issue */
|
||||||
|
export class MapUserSettingsParseError extends Error {
|
||||||
|
constructor(msg: string) {
|
||||||
|
super(`MapUserSettings parse error: ${msg}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isNumber = (v: unknown): v is number => typeof v === 'number' && !Number.isNaN(v);
|
||||||
|
|
||||||
|
/** Minimal check that an object matches SettingsWithVersion<*> */
|
||||||
|
const isSettingsWithVersion = (v: unknown): v is SettingsWithVersion<unknown> =>
|
||||||
|
typeof v === 'object' && v !== null && isNumber((v as any).version) && 'settings' in (v as any);
|
||||||
|
|
||||||
|
/** Ensure every required key is present */
|
||||||
|
const hasAllRequiredKeys = (v: unknown): v is Record<RequiredKeys, unknown> =>
|
||||||
|
typeof v === 'object' && v !== null && REQUIRED_KEYS.every(k => k in v);
|
||||||
|
|
||||||
|
/* ------------------------------ Main parser ------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses and validates a JSON string as `MapUserSettings`.
|
||||||
|
*
|
||||||
|
* @throws `MapUserSettingsParseError` – если строка не JSON или нарушена структура
|
||||||
|
*/
|
||||||
|
export const parseMapUserSettings = (json: unknown): MapUserSettings => {
|
||||||
|
if (typeof json !== 'string') throw new MapUserSettingsParseError('Input must be a JSON string');
|
||||||
|
|
||||||
|
let data: unknown;
|
||||||
|
try {
|
||||||
|
data = JSON.parse(json);
|
||||||
|
} catch (e) {
|
||||||
|
throw new MapUserSettingsParseError(`Invalid JSON: ${(e as Error).message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasAllRequiredKeys(data)) {
|
||||||
|
const missing = REQUIRED_KEYS.filter(k => !(k in (data as any)));
|
||||||
|
throw new MapUserSettingsParseError(`Missing top-level field(s): ${missing.join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key of REQUIRED_KEYS) {
|
||||||
|
if (!isSettingsWithVersion((data as any)[key])) {
|
||||||
|
throw new MapUserSettingsParseError(`"${key}" must match SettingsWithVersion<T>`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything passes, so cast is safe
|
||||||
|
return data as MapUserSettings;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ------------------------------ Usage example ----------------------------- */
|
||||||
|
|
||||||
|
// const raw = fetchFromServer(); // string
|
||||||
|
// const settings = parseMapUserSettings(raw);
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from './useSystemInfo';
|
export * from './useSystemInfo';
|
||||||
export * from './useGetOwnOnlineCharacters';
|
export * from './useGetOwnOnlineCharacters';
|
||||||
|
export * from './useElementWidth';
|
||||||
|
|||||||
43
assets/js/hooks/Mapper/components/hooks/useElementWidth.ts
Normal file
43
assets/js/hooks/Mapper/components/hooks/useElementWidth.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { useState, useLayoutEffect, RefObject } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* useElementWidth
|
||||||
|
*
|
||||||
|
* A custom hook that accepts a ref to an HTML element and returns its current width.
|
||||||
|
* It uses a ResizeObserver and window resize listener to update the width when necessary.
|
||||||
|
*
|
||||||
|
* @param ref - A RefObject pointing to an HTML element.
|
||||||
|
* @returns The current width of the element.
|
||||||
|
*/
|
||||||
|
export function useElementWidth<T extends HTMLElement>(ref: RefObject<T>): number {
|
||||||
|
const [width, setWidth] = useState<number>(0);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const updateWidth = () => {
|
||||||
|
if (ref.current) {
|
||||||
|
const newWidth = ref.current.getBoundingClientRect().width;
|
||||||
|
if (newWidth > 0) {
|
||||||
|
setWidth(newWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateWidth(); // Initial measurement
|
||||||
|
|
||||||
|
const observer = new ResizeObserver(() => {
|
||||||
|
const id = setTimeout(updateWidth, 100);
|
||||||
|
return () => clearTimeout(id);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ref.current) {
|
||||||
|
observer.observe(ref.current);
|
||||||
|
}
|
||||||
|
window.addEventListener("resize", updateWidth);
|
||||||
|
return () => {
|
||||||
|
observer.disconnect();
|
||||||
|
window.removeEventListener("resize", updateWidth);
|
||||||
|
};
|
||||||
|
}, [ref]);
|
||||||
|
|
||||||
|
return width;
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
|
import { getSystemById } from '@/hooks/Mapper/helpers';
|
||||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { getSystemById } from '@/hooks/Mapper/helpers';
|
import { getSystemStaticInfo } from '../../mapRootProvider/hooks/useLoadSystemStatic';
|
||||||
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
|
|
||||||
|
|
||||||
interface UseSystemInfoProps {
|
interface UseSystemInfoProps {
|
||||||
systemId: string;
|
systemId: string;
|
||||||
@@ -12,14 +12,12 @@ export const useSystemInfo = ({ systemId }: UseSystemInfoProps) => {
|
|||||||
data: { systems, connections },
|
data: { systems, connections },
|
||||||
} = useMapRootState();
|
} = useMapRootState();
|
||||||
|
|
||||||
const { systems: systemStatics } = useLoadSystemStatic({ systems: [systemId] });
|
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const staticInfo = systemStatics.get(parseInt(systemId));
|
const staticInfo = getSystemStaticInfo(parseInt(systemId));
|
||||||
const dynamicInfo = getSystemById(systems, systemId);
|
const dynamicInfo = getSystemById(systems, systemId);
|
||||||
|
|
||||||
if (!staticInfo || !dynamicInfo) {
|
if (!staticInfo || !dynamicInfo) {
|
||||||
throw new Error(`Error on getting system ${systemId}`);
|
return { dynamicInfo, staticInfo, leadsTo: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
const leadsTo = connections
|
const leadsTo = connections
|
||||||
@@ -29,5 +27,5 @@ export const useSystemInfo = ({ systemId }: UseSystemInfoProps) => {
|
|||||||
.filter(x => x !== systemId);
|
.filter(x => x !== systemId);
|
||||||
|
|
||||||
return { dynamicInfo, staticInfo, leadsTo };
|
return { dynamicInfo, staticInfo, leadsTo };
|
||||||
}, [systemStatics, systemId, systems, connections]);
|
}, [systemId, systems, connections]);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
.MapRoot {
|
.MapRoot {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
|
background-color: var(--rf-bg-color, #0C0A09);
|
||||||
|
|
||||||
|
&.BackgroundAlternateColor {
|
||||||
|
background-color: var(--rf-soft-bg-color, #171717);
|
||||||
|
--rf-node-bg-color: var(--rf-node-soft-bg-color, #202020);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +1,39 @@
|
|||||||
import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect, useRef } from 'react';
|
import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect, useMemo } from 'react';
|
||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
Background,
|
Background,
|
||||||
ConnectionMode,
|
|
||||||
Edge,
|
Edge,
|
||||||
MiniMap,
|
MiniMap,
|
||||||
Node,
|
Node,
|
||||||
|
NodeChange,
|
||||||
NodeDragHandler,
|
NodeDragHandler,
|
||||||
OnConnect,
|
OnConnect,
|
||||||
OnMoveEnd,
|
OnMoveEnd,
|
||||||
OnSelectionChangeFunc,
|
OnSelectionChangeFunc,
|
||||||
SelectionDragHandler,
|
SelectionDragHandler,
|
||||||
SelectionMode,
|
SelectionMode,
|
||||||
useEdgesState,
|
|
||||||
useNodesState,
|
|
||||||
NodeChange,
|
|
||||||
useReactFlow,
|
useReactFlow,
|
||||||
} from 'reactflow';
|
} from 'reactflow';
|
||||||
import 'reactflow/dist/style.css';
|
import 'reactflow/dist/style.css';
|
||||||
import classes from './Map.module.scss';
|
import classes from './Map.module.scss';
|
||||||
import './styles/neon-theme.scss';
|
|
||||||
import './styles/eve-common.scss';
|
|
||||||
import { MapProvider, useMapState } from './MapProvider';
|
import { MapProvider, useMapState } from './MapProvider';
|
||||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
import { useEdgesState, useMapHandlers, useNodesState, useUpdateNodes } from './hooks';
|
||||||
import { useMapHandlers, useUpdateNodes } from './hooks';
|
|
||||||
import { MapHandlers, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
|
import { MapHandlers, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||||
import {
|
import {
|
||||||
ContextMenuConnection,
|
ContextMenuConnection,
|
||||||
ContextMenuRoot,
|
ContextMenuRoot,
|
||||||
SolarSystemEdge,
|
SolarSystemEdge,
|
||||||
SolarSystemNode,
|
|
||||||
useContextMenuConnectionHandlers,
|
useContextMenuConnectionHandlers,
|
||||||
useContextMenuRootHandlers,
|
useContextMenuRootHandlers,
|
||||||
} from './components';
|
} from './components';
|
||||||
import { OnMapSelectionChange } from './map.types';
|
import { getBehaviorForTheme } from './helpers/getThemeBehavior';
|
||||||
|
import { OnMapAddSystemCallback, OnMapSelectionChange } from './map.types';
|
||||||
import { SESSION_KEY } from '@/hooks/Mapper/constants.ts';
|
import { SESSION_KEY } from '@/hooks/Mapper/constants.ts';
|
||||||
import { SolarSystemConnection, SolarSystemRawType } from '@/hooks/Mapper/types';
|
import { PingData, SolarSystemConnection, SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||||
import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
||||||
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
|
import clsx from 'clsx';
|
||||||
|
import { useBackgroundVars } from './hooks/useBackgroundVars';
|
||||||
|
import type { PanelPosition } from '@reactflow/core';
|
||||||
|
|
||||||
const DEFAULT_VIEW_PORT = { zoom: 1, x: 0, y: 0 };
|
const DEFAULT_VIEW_PORT = { zoom: 1, x: 0, y: 0 };
|
||||||
|
|
||||||
@@ -79,26 +75,30 @@ const initialEdges = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const nodeTypes = {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
custom: SolarSystemNode,
|
|
||||||
} as never;
|
|
||||||
|
|
||||||
const edgeTypes = {
|
const edgeTypes = {
|
||||||
floating: SolarSystemEdge,
|
floating: SolarSystemEdge,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const MAP_ROOT_ID = 'MAP_ROOT_ID';
|
||||||
|
|
||||||
interface MapCompProps {
|
interface MapCompProps {
|
||||||
refn: ForwardedRef<MapHandlers>;
|
refn: ForwardedRef<MapHandlers>;
|
||||||
onCommand: OutCommandHandler;
|
onCommand: OutCommandHandler;
|
||||||
onSelectionChange: OnMapSelectionChange;
|
onSelectionChange: OnMapSelectionChange;
|
||||||
onConnectionInfoClick?(e: SolarSystemConnection): void;
|
onConnectionInfoClick?(e: SolarSystemConnection): void;
|
||||||
|
onAddSystem?: OnMapAddSystemCallback;
|
||||||
onSelectionContextMenu?: NodeSelectionMouseHandler;
|
onSelectionContextMenu?: NodeSelectionMouseHandler;
|
||||||
minimapClasses?: string;
|
minimapClasses?: string;
|
||||||
isShowMinimap?: boolean;
|
isShowMinimap?: boolean;
|
||||||
onSystemContextMenu: (event: MouseEvent<Element>, systemId: string) => void;
|
onSystemContextMenu: (event: MouseEvent<Element>, systemId: string) => void;
|
||||||
showKSpaceBG?: boolean;
|
showKSpaceBG?: boolean;
|
||||||
|
isThickConnections?: boolean;
|
||||||
|
isShowBackgroundPattern?: boolean;
|
||||||
|
isSoftBackground?: boolean;
|
||||||
|
theme?: string;
|
||||||
|
pings: PingData[];
|
||||||
|
minimapPlacement?: PanelPosition;
|
||||||
|
localShowShipName?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MapComp = ({
|
const MapComp = ({
|
||||||
@@ -111,24 +111,32 @@ const MapComp = ({
|
|||||||
onSelectionContextMenu,
|
onSelectionContextMenu,
|
||||||
isShowMinimap,
|
isShowMinimap,
|
||||||
showKSpaceBG,
|
showKSpaceBG,
|
||||||
|
isThickConnections,
|
||||||
|
isShowBackgroundPattern,
|
||||||
|
isSoftBackground,
|
||||||
|
theme,
|
||||||
|
onAddSystem,
|
||||||
|
pings,
|
||||||
|
minimapPlacement = 'bottom-right',
|
||||||
|
localShowShipName = false,
|
||||||
}: MapCompProps) => {
|
}: MapCompProps) => {
|
||||||
const { getNode } = useReactFlow();
|
const { getNodes } = useReactFlow();
|
||||||
const [nodes, , onNodesChange] = useNodesState<SolarSystemRawType>(initialNodes);
|
const [nodes, , onNodesChange] = useNodesState<Node<SolarSystemRawType>>(initialNodes);
|
||||||
const [edges, , onEdgesChange] = useEdgesState<Edge<SolarSystemConnection>[]>(initialEdges);
|
const [edges, , onEdgesChange] = useEdgesState<Edge<SolarSystemConnection>>(initialEdges);
|
||||||
|
|
||||||
useMapHandlers(refn, onSelectionChange);
|
useMapHandlers(refn, onSelectionChange);
|
||||||
useUpdateNodes(nodes);
|
useUpdateNodes(nodes);
|
||||||
const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers();
|
const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers({ onAddSystem });
|
||||||
const { handleConnectionContext, ...connectionCtxProps } = useContextMenuConnectionHandlers();
|
const { handleConnectionContext, ...connectionCtxProps } = useContextMenuConnectionHandlers();
|
||||||
const { update } = useMapState();
|
const { update } = useMapState();
|
||||||
const {
|
const { variant, gap, size, color } = useBackgroundVars(theme);
|
||||||
data: { systems },
|
const { isPanAndDrag, nodeComponent, connectionMode } = getBehaviorForTheme(theme || 'default');
|
||||||
} = useMapRootState();
|
|
||||||
|
|
||||||
const { deleteSystems } = useDeleteSystems();
|
const nodeTypes = useMemo(() => {
|
||||||
|
return {
|
||||||
const systemsRef = useRef({ systems });
|
custom: nodeComponent,
|
||||||
systemsRef.current = { systems };
|
};
|
||||||
|
}, [nodeComponent]);
|
||||||
|
|
||||||
const onConnect: OnConnect = useCallback(
|
const onConnect: OnConnect = useCallback(
|
||||||
params => {
|
params => {
|
||||||
@@ -185,40 +193,37 @@ const MapComp = ({
|
|||||||
|
|
||||||
const handleNodesChange = useCallback(
|
const handleNodesChange = useCallback(
|
||||||
(changes: NodeChange[]) => {
|
(changes: NodeChange[]) => {
|
||||||
const systemsIdsToRemove: string[] = [];
|
// prevents single node deselection on background / same node click
|
||||||
|
// allows deseletion of all nodes if multiple are currently selected
|
||||||
|
if (changes.length === 1 && changes[0].type == 'select' && changes[0].selected === false) {
|
||||||
|
changes[0].selected = getNodes().filter(node => node.selected).length === 1;
|
||||||
|
}
|
||||||
|
|
||||||
const nextChanges = changes.reduce((acc, change) => {
|
const nextChanges = changes.reduce((acc, change) => {
|
||||||
if (change.type === 'remove') {
|
|
||||||
const node = getNode(change.id);
|
|
||||||
const { systems = [] } = systemsRef.current;
|
|
||||||
if (node?.data?.id && !systems.map(s => s.id).includes(node?.data?.id)) {
|
|
||||||
return [...acc, change];
|
|
||||||
} else if (!node?.data?.locked) {
|
|
||||||
systemsIdsToRemove.push(node?.data?.id);
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}
|
|
||||||
return [...acc, change];
|
return [...acc, change];
|
||||||
}, [] as NodeChange[]);
|
}, [] as NodeChange[]);
|
||||||
|
|
||||||
if (systemsIdsToRemove.length) {
|
|
||||||
deleteSystems(systemsIdsToRemove);
|
|
||||||
}
|
|
||||||
|
|
||||||
onNodesChange(nextChanges);
|
onNodesChange(nextChanges);
|
||||||
},
|
},
|
||||||
[deleteSystems, getNode, onNodesChange],
|
[getNodes, onNodesChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
update(x => ({
|
update(x => ({
|
||||||
...x,
|
...x,
|
||||||
showKSpaceBG: showKSpaceBG,
|
showKSpaceBG: showKSpaceBG,
|
||||||
|
isThickConnections: isThickConnections,
|
||||||
|
pings,
|
||||||
|
localShowShipName,
|
||||||
}));
|
}));
|
||||||
}, [showKSpaceBG, update]);
|
}, [showKSpaceBG, isThickConnections, pings, update, localShowShipName]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={classes.MapRoot}>
|
<div
|
||||||
|
data-window-id={MAP_ROOT_ID}
|
||||||
|
className={clsx(classes.MapRoot, { [classes.BackgroundAlternateColor]: isSoftBackground })}
|
||||||
|
>
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
edges={edges}
|
edges={edges}
|
||||||
@@ -230,7 +235,7 @@ const MapComp = ({
|
|||||||
defaultViewport={getViewPortFromStore()}
|
defaultViewport={getViewPortFromStore()}
|
||||||
edgeTypes={edgeTypes}
|
edgeTypes={edgeTypes}
|
||||||
nodeTypes={nodeTypes}
|
nodeTypes={nodeTypes}
|
||||||
connectionMode={ConnectionMode.Loose}
|
connectionMode={connectionMode}
|
||||||
snapToGrid
|
snapToGrid
|
||||||
nodeDragThreshold={10}
|
nodeDragThreshold={10}
|
||||||
onNodeDragStop={handleDragStop}
|
onNodeDragStop={handleDragStop}
|
||||||
@@ -238,6 +243,11 @@ const MapComp = ({
|
|||||||
onConnectStart={() => update({ isConnecting: true })}
|
onConnectStart={() => update({ isConnecting: true })}
|
||||||
onConnectEnd={() => update({ isConnecting: false })}
|
onConnectEnd={() => update({ isConnecting: false })}
|
||||||
onNodeMouseEnter={(_, node) => update({ hoverNodeId: node.id })}
|
onNodeMouseEnter={(_, node) => update({ hoverNodeId: node.id })}
|
||||||
|
onPaneClick={event => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}}
|
||||||
|
// onKeyUp=
|
||||||
onNodeMouseLeave={() => update({ hoverNodeId: null })}
|
onNodeMouseLeave={() => update({ hoverNodeId: null })}
|
||||||
onEdgeClick={(_, t) => {
|
onEdgeClick={(_, t) => {
|
||||||
onConnectionInfoClick?.(t.data);
|
onConnectionInfoClick?.(t.data);
|
||||||
@@ -257,14 +267,22 @@ const MapComp = ({
|
|||||||
minZoom={0.2}
|
minZoom={0.2}
|
||||||
maxZoom={1.5}
|
maxZoom={1.5}
|
||||||
elevateNodesOnSelect
|
elevateNodesOnSelect
|
||||||
deleteKeyCode={['Delete']}
|
deleteKeyCode={['']}
|
||||||
|
{...(isPanAndDrag
|
||||||
|
? {
|
||||||
|
selectionOnDrag: true,
|
||||||
|
panOnDrag: [2],
|
||||||
|
}
|
||||||
|
: {})}
|
||||||
// TODO need create clear example with problem with that flag
|
// TODO need create clear example with problem with that flag
|
||||||
// if system is not visible edge not drawing (and any render in Custom node is not happening)
|
// if system is not visible edge not drawing (and any render in Custom node is not happening)
|
||||||
// onlyRenderVisibleElements
|
// onlyRenderVisibleElements
|
||||||
selectionMode={SelectionMode.Partial}
|
selectionMode={SelectionMode.Partial}
|
||||||
>
|
>
|
||||||
{isShowMinimap && <MiniMap pannable zoomable ariaLabel="Mini map" className={minimapClasses} />}
|
{isShowMinimap && (
|
||||||
<Background />
|
<MiniMap pannable zoomable ariaLabel="Mini map" className={minimapClasses} position={minimapPlacement} />
|
||||||
|
)}
|
||||||
|
{isShowBackgroundPattern && <Background variant={variant} gap={gap} size={size} color={color} />}
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
{/* <button className="z-auto btn btn-primary absolute top-20 right-20" onClick={handleGetPassages}>
|
{/* <button className="z-auto btn btn-primary absolute top-20 right-20" onClick={handleGetPassages}>
|
||||||
Test // DON NOT REMOVE
|
Test // DON NOT REMOVE
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { createContext, useContext } from 'react';
|
import React, { createContext, useContext } from 'react';
|
||||||
import { OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
|
import { OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||||
import { MapUnionTypes } from '@/hooks/Mapper/types';
|
import { MapUnionTypes, SystemSignature } from '@/hooks/Mapper/types';
|
||||||
import { ContextStoreDataUpdate, useContextStore } from '@/hooks/Mapper/utils';
|
import { ContextStoreDataUpdate, useContextStore } from '@/hooks/Mapper/utils';
|
||||||
|
|
||||||
export type MapData = MapUnionTypes & {
|
export type MapData = MapUnionTypes & {
|
||||||
@@ -8,6 +8,9 @@ export type MapData = MapUnionTypes & {
|
|||||||
hoverNodeId: string | null;
|
hoverNodeId: string | null;
|
||||||
visibleNodes: Set<string>;
|
visibleNodes: Set<string>;
|
||||||
showKSpaceBG: boolean;
|
showKSpaceBG: boolean;
|
||||||
|
isThickConnections: boolean;
|
||||||
|
linkedSigEveId: string;
|
||||||
|
localShowShipName: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface MapProviderProps {
|
interface MapProviderProps {
|
||||||
@@ -17,6 +20,7 @@ interface MapProviderProps {
|
|||||||
|
|
||||||
const INITIAL_DATA: MapData = {
|
const INITIAL_DATA: MapData = {
|
||||||
wormholesData: {},
|
wormholesData: {},
|
||||||
|
wormholes: [],
|
||||||
effects: {},
|
effects: {},
|
||||||
characters: [],
|
characters: [],
|
||||||
userCharacters: [],
|
userCharacters: [],
|
||||||
@@ -27,8 +31,19 @@ const INITIAL_DATA: MapData = {
|
|||||||
isConnecting: false,
|
isConnecting: false,
|
||||||
connections: [],
|
connections: [],
|
||||||
hoverNodeId: null,
|
hoverNodeId: null,
|
||||||
|
linkedSigEveId: '',
|
||||||
visibleNodes: new Set(),
|
visibleNodes: new Set(),
|
||||||
showKSpaceBG: false,
|
showKSpaceBG: false,
|
||||||
|
isThickConnections: false,
|
||||||
|
userPermissions: {},
|
||||||
|
systemSignatures: {} as Record<string, SystemSignature[]>,
|
||||||
|
options: {} as Record<string, string | boolean>,
|
||||||
|
isSubscriptionActive: false,
|
||||||
|
mainCharacterEveId: null,
|
||||||
|
followingCharacterEveId: null,
|
||||||
|
userHubs: [],
|
||||||
|
pings: [],
|
||||||
|
localShowShipName: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface MapContextProps {
|
export interface MapContextProps {
|
||||||
@@ -44,7 +59,7 @@ const MapContext = createContext<MapContextProps>({
|
|||||||
outCommand: async () => void 0,
|
outCommand: async () => void 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const MapProvider: React.FC<MapProviderProps> = ({ children, onCommand }) => {
|
export const MapProvider = ({ children, onCommand }: MapProviderProps) => {
|
||||||
const { update, ref } = useContextStore<MapData>({ ...INITIAL_DATA });
|
const { update, ref } = useContextStore<MapData>({ ...INITIAL_DATA });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
|
@import '@/hooks/Mapper/components/map/styles/eve-common-variables';
|
||||||
|
|
||||||
.ConnectionTimeEOL {
|
.ConnectionTimeEOL {
|
||||||
background-image: linear-gradient(207deg, transparent, #7452c3e3);
|
background-image: linear-gradient(207deg, transparent, var(--conn-time-eol));
|
||||||
}
|
}
|
||||||
|
|
||||||
.ConnectionFrigate {
|
.ConnectionFrigate {
|
||||||
background-image: linear-gradient(207deg, transparent, #325d88);
|
background-image: linear-gradient(207deg, transparent, var(--conn-frigate));
|
||||||
}
|
}
|
||||||
|
|
||||||
.ConnectionSave {
|
.ConnectionSave {
|
||||||
background-image: linear-gradient(207deg, transparent, rgba(155, 102, 45, 0.85));
|
background-image: linear-gradient(207deg, transparent, var(--conn-save));
|
||||||
}
|
}
|
||||||
|
|
||||||
.SelectedItem {
|
.SelectedItem {
|
||||||
background-color: rgba(98, 98, 98, 0.33);
|
background-color: var(--selected-item-bg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,18 @@ import React, { RefObject, useMemo } from 'react';
|
|||||||
import { ContextMenu } from 'primereact/contextmenu';
|
import { ContextMenu } from 'primereact/contextmenu';
|
||||||
import { PrimeIcons } from 'primereact/api';
|
import { PrimeIcons } from 'primereact/api';
|
||||||
import { MenuItem } from 'primereact/menuitem';
|
import { MenuItem } from 'primereact/menuitem';
|
||||||
import { Edge } from '@reactflow/core/dist/esm/types/edges';
|
import { ConnectionType, MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
|
||||||
import { MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import classes from './ContextMenuConnection.module.scss';
|
import classes from './ContextMenuConnection.module.scss';
|
||||||
import { MASS_STATE_NAMES, MASS_STATE_NAMES_ORDER } from '@/hooks/Mapper/components/map/constants.ts';
|
import {
|
||||||
|
MASS_STATE_NAMES,
|
||||||
|
MASS_STATE_NAMES_ORDER,
|
||||||
|
SHIP_SIZES_NAMES,
|
||||||
|
SHIP_SIZES_NAMES_ORDER,
|
||||||
|
SHIP_SIZES_NAMES_SHORT,
|
||||||
|
SHIP_SIZES_SIZE,
|
||||||
|
} from '@/hooks/Mapper/components/map/constants.ts';
|
||||||
|
import { Edge } from 'reactflow';
|
||||||
|
|
||||||
export interface ContextMenuConnectionProps {
|
export interface ContextMenuConnectionProps {
|
||||||
contextMenuRef: RefObject<ContextMenu>;
|
contextMenuRef: RefObject<ContextMenu>;
|
||||||
@@ -35,46 +42,72 @@ export const ContextMenuConnection: React.FC<ContextMenuConnectionProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isFrigateSize = edge.data?.ship_size_type === ShipSizeStatus.small;
|
const isFrigateSize = edge.data?.ship_size_type === ShipSizeStatus.small;
|
||||||
|
const isWormhole = edge.data?.type !== ConnectionType.gate;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
...(isWormhole
|
||||||
label: `EOL`,
|
|
||||||
className: clsx({
|
|
||||||
[classes.ConnectionTimeEOL]: edge.data?.time_status === TimeStatus.eol,
|
|
||||||
}),
|
|
||||||
icon: PrimeIcons.CLOCK,
|
|
||||||
command: onChangeTimeState,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: `Frigate`,
|
|
||||||
className: clsx({
|
|
||||||
[classes.ConnectionFrigate]: isFrigateSize,
|
|
||||||
}),
|
|
||||||
icon: PrimeIcons.CLOUD,
|
|
||||||
command: () =>
|
|
||||||
onChangeShipSizeStatus(
|
|
||||||
edge.data?.ship_size_type === ShipSizeStatus.small ? ShipSizeStatus.normal : ShipSizeStatus.small,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: `Save mass`,
|
|
||||||
className: clsx({
|
|
||||||
[classes.ConnectionSave]: edge.data?.locked,
|
|
||||||
}),
|
|
||||||
icon: PrimeIcons.LOCK,
|
|
||||||
command: () => onToggleMassSave(!edge.data?.locked),
|
|
||||||
},
|
|
||||||
...(!isFrigateSize
|
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
label: `Mass status`,
|
label: `EOL`,
|
||||||
icon: PrimeIcons.CHART_PIE,
|
className: clsx({
|
||||||
items: MASS_STATE_NAMES_ORDER.map(x => ({
|
[classes.ConnectionTimeEOL]: edge.data?.time_status === TimeStatus.eol,
|
||||||
label: MASS_STATE_NAMES[x],
|
}),
|
||||||
|
icon: PrimeIcons.CLOCK,
|
||||||
|
command: onChangeTimeState,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: `Frigate`,
|
||||||
|
className: clsx({
|
||||||
|
[classes.ConnectionFrigate]: isFrigateSize,
|
||||||
|
}),
|
||||||
|
icon: PrimeIcons.CLOUD,
|
||||||
|
command: () =>
|
||||||
|
onChangeShipSizeStatus(
|
||||||
|
edge.data?.ship_size_type === ShipSizeStatus.small ? ShipSizeStatus.large : ShipSizeStatus.small,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: `Save mass`,
|
||||||
|
className: clsx({
|
||||||
|
[classes.ConnectionSave]: edge.data?.locked,
|
||||||
|
}),
|
||||||
|
icon: PrimeIcons.LOCK,
|
||||||
|
command: () => onToggleMassSave(!edge.data?.locked),
|
||||||
|
},
|
||||||
|
...(!isFrigateSize
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
label: `Mass status`,
|
||||||
|
icon: PrimeIcons.CHART_PIE,
|
||||||
|
items: MASS_STATE_NAMES_ORDER.map(x => ({
|
||||||
|
label: MASS_STATE_NAMES[x],
|
||||||
|
className: clsx({
|
||||||
|
[classes.SelectedItem]: edge.data?.mass_status === x,
|
||||||
|
}),
|
||||||
|
command: () => onChangeMassState(x),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
|
||||||
|
{
|
||||||
|
label: `Ship Size`,
|
||||||
|
icon: PrimeIcons.CLOUD,
|
||||||
|
items: SHIP_SIZES_NAMES_ORDER.map(x => ({
|
||||||
|
label: (
|
||||||
|
<div className="grid grid-cols-[20px_120px_1fr_40px] gap-2 items-center">
|
||||||
|
<div className="text-[12px] font-bold text-stone-400">{SHIP_SIZES_NAMES_SHORT[x]}</div>
|
||||||
|
<div>{SHIP_SIZES_NAMES[x]}</div>
|
||||||
|
<div></div>
|
||||||
|
<div className="flex justify-end whitespace-nowrap text-[12px] font-bold text-stone-500">
|
||||||
|
{SHIP_SIZES_SIZE[x]} t.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) as unknown as string, // TODO my lovely kostyl
|
||||||
className: clsx({
|
className: clsx({
|
||||||
[classes.SelectedItem]: edge.data?.mass_status === x,
|
[classes.SelectedItem]: edge.data?.ship_size_type === x,
|
||||||
}),
|
}),
|
||||||
command: () => onChangeMassState(x),
|
command: () => onChangeShipSizeStatus(x),
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -85,7 +118,7 @@ export const ContextMenuConnection: React.FC<ContextMenuConnectionProps> = ({
|
|||||||
command: onDeleteConnection,
|
command: onDeleteConnection,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}, [edge, onChangeTimeState, onDeleteConnection, onChangeMassState, onChangeShipSizeStatus]);
|
}, [edge, onChangeTimeState, onDeleteConnection, onChangeShipSizeStatus, onToggleMassSave, onChangeMassState]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { EdgeMouseHandler } from 'reactflow';
|
import { Edge, EdgeMouseHandler } from 'reactflow';
|
||||||
import { useCallback, useRef, useState } from 'react';
|
import { useCallback, useRef, useState } from 'react';
|
||||||
import { ContextMenu } from 'primereact/contextmenu';
|
import { ContextMenu } from 'primereact/contextmenu';
|
||||||
import { useMapState } from '../../MapProvider.tsx';
|
import { useMapState } from '../../MapProvider.tsx';
|
||||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||||
import { Edge } from '@reactflow/core/dist/esm/types/edges';
|
import { ConnectionType, MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
|
||||||
import { MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
|
|
||||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||||
|
|
||||||
export const useContextMenuConnectionHandlers = () => {
|
export const useContextMenuConnectionHandlers = () => {
|
||||||
@@ -47,6 +46,23 @@ export const useContextMenuConnectionHandlers = () => {
|
|||||||
setEdge(undefined);
|
setEdge(undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onChangeType = useCallback((type: ConnectionType) => {
|
||||||
|
const { edge, outCommand } = ref.current;
|
||||||
|
|
||||||
|
if (!edge) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
outCommand({
|
||||||
|
type: OutCommand.updateConnectionType,
|
||||||
|
data: {
|
||||||
|
source: edge.source,
|
||||||
|
target: edge.target,
|
||||||
|
value: type,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
const onChangeMassState = useCallback((status: MassState) => {
|
const onChangeMassState = useCallback((status: MassState) => {
|
||||||
const { edge, outCommand } = ref.current;
|
const { edge, outCommand } = ref.current;
|
||||||
|
|
||||||
@@ -80,14 +96,16 @@ export const useContextMenuConnectionHandlers = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
outCommand({
|
if (status === ShipSizeStatus.small) {
|
||||||
type: OutCommand.updateConnectionMassStatus,
|
outCommand({
|
||||||
data: {
|
type: OutCommand.updateConnectionMassStatus,
|
||||||
source: edge.source,
|
data: {
|
||||||
target: edge.target,
|
source: edge.source,
|
||||||
value: MassState.normal,
|
target: edge.target,
|
||||||
},
|
value: MassState.normal,
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onToggleMassSave = useCallback((locked: boolean) => {
|
const onToggleMassSave = useCallback((locked: boolean) => {
|
||||||
@@ -118,6 +136,7 @@ export const useContextMenuConnectionHandlers = () => {
|
|||||||
contextMenuRef,
|
contextMenuRef,
|
||||||
onDeleteConnection,
|
onDeleteConnection,
|
||||||
onChangeTimeState,
|
onChangeTimeState,
|
||||||
|
onChangeType,
|
||||||
onChangeMassState,
|
onChangeMassState,
|
||||||
onChangeShipSizeStatus,
|
onChangeShipSizeStatus,
|
||||||
onToggleMassSave,
|
onToggleMassSave,
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import { useReactFlow, XYPosition } from 'reactflow';
|
import { useReactFlow, XYPosition } from 'reactflow';
|
||||||
import React, { useRef, useState } from 'react';
|
import React, { useCallback, useRef, useState } from 'react';
|
||||||
import { ContextMenu } from 'primereact/contextmenu';
|
import { ContextMenu } from 'primereact/contextmenu';
|
||||||
import { useMapState } from '../../MapProvider.tsx';
|
|
||||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
|
||||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||||
|
import { OnMapAddSystemCallback } from '@/hooks/Mapper/components/map/map.types.ts';
|
||||||
|
|
||||||
export const useContextMenuRootHandlers = () => {
|
type UseContextMenuRootHandlers = {
|
||||||
|
onAddSystem?: OnMapAddSystemCallback;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useContextMenuRootHandlers = ({ onAddSystem }: UseContextMenuRootHandlers = {}) => {
|
||||||
const rf = useReactFlow();
|
const rf = useReactFlow();
|
||||||
const contextMenuRef = useRef<ContextMenu | null>(null);
|
const contextMenuRef = useRef<ContextMenu | null>(null);
|
||||||
const { outCommand } = useMapState();
|
|
||||||
const [position, setPosition] = useState<XYPosition | null>(null);
|
const [position, setPosition] = useState<XYPosition | null>(null);
|
||||||
|
|
||||||
const handleRootContext = (e: React.MouseEvent<HTMLDivElement>) => {
|
const handleRootContext = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
@@ -18,14 +20,17 @@ export const useContextMenuRootHandlers = () => {
|
|||||||
contextMenuRef.current?.show(e);
|
contextMenuRef.current?.show(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onAddSystem = () => {
|
const ref = useRef({ onAddSystem, position });
|
||||||
outCommand({ type: OutCommand.manualAddSystem, data: { coordinates: position } });
|
ref.current = { onAddSystem, position };
|
||||||
};
|
|
||||||
|
const onAddSystemCallback = useCallback(() => {
|
||||||
|
ref.current.onAddSystem?.({ coordinates: position });
|
||||||
|
}, [position]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleRootContext,
|
handleRootContext,
|
||||||
|
|
||||||
contextMenuRef,
|
contextMenuRef,
|
||||||
onAddSystem,
|
onAddSystem: onAddSystemCallback,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
.KillsBookmark {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 8px;
|
||||||
|
font-weight: 700;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 5px 5px 0 0;
|
||||||
|
padding: 4px 3px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.KillsBookmarkWithIcon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: -2px;
|
||||||
|
text-shadow: 0 0 3px #000;
|
||||||
|
padding-right: 2px;
|
||||||
|
height: 8px;
|
||||||
|
font-size: 8px;
|
||||||
|
line-height: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-size-adjust: 100%;
|
||||||
|
|
||||||
|
.pi {
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
font-size: 9px;
|
||||||
|
font-family: var(--rf-node-font-family, inherit) !important;
|
||||||
|
font-weight: var(--rf-node-font-weight, inherit) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.TooltipContainer {
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
color: #fff;
|
||||||
|
padding: 3px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||||
|
border-radius: 2px;
|
||||||
|
pointer-events: auto;
|
||||||
|
max-width: 500px;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useKillsCounter } from '../../hooks/useKillsCounter.ts';
|
||||||
|
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||||
|
import { WithChildren, WithClassName } from '@/hooks/Mapper/types/common.ts';
|
||||||
|
import {
|
||||||
|
KILLS_ROW_HEIGHT,
|
||||||
|
SystemKillsList,
|
||||||
|
} from '@/hooks/Mapper/components/mapInterface/widgets/WSystemKills/SystemKillsList';
|
||||||
|
import { TooltipSize } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper/utils.ts';
|
||||||
|
|
||||||
|
const MIN_TOOLTIP_HEIGHT = 40;
|
||||||
|
|
||||||
|
type KillsBookmarkTooltipProps = {
|
||||||
|
killsCount: number;
|
||||||
|
killsActivityType: string | null;
|
||||||
|
systemId: string;
|
||||||
|
className?: string;
|
||||||
|
size?: TooltipSize;
|
||||||
|
} & WithChildren &
|
||||||
|
WithClassName;
|
||||||
|
|
||||||
|
export const KillsCounter = ({
|
||||||
|
killsCount,
|
||||||
|
systemId,
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
size = TooltipSize.xs,
|
||||||
|
}: KillsBookmarkTooltipProps) => {
|
||||||
|
const { isLoading, kills: detailedKills } = useKillsCounter({
|
||||||
|
realSystemId: systemId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const limitedKills = useMemo(() => {
|
||||||
|
if (!detailedKills || detailedKills.length === 0) return [];
|
||||||
|
return detailedKills.slice(0, killsCount);
|
||||||
|
}, [detailedKills, killsCount]);
|
||||||
|
|
||||||
|
if (!killsCount || limitedKills.length === 0 || !systemId || isLoading) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate height based on number of kills, but ensure a minimum height
|
||||||
|
const killsNeededHeight = limitedKills.length * KILLS_ROW_HEIGHT;
|
||||||
|
// Add a small buffer (10px) to prevent scrollbar from appearing unnecessarily
|
||||||
|
const tooltipHeight = Math.max(MIN_TOOLTIP_HEIGHT, Math.min(killsNeededHeight + 10, 500));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<WdTooltipWrapper
|
||||||
|
content={
|
||||||
|
<div className="overflow-hidden flex w-[450px] flex-col" style={{ height: `${tooltipHeight}px` }}>
|
||||||
|
<div className="flex-1 h-full">
|
||||||
|
<SystemKillsList kills={limitedKills} onlyOneSystem timeRange={1} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
className={className}
|
||||||
|
tooltipClassName="!px-0"
|
||||||
|
size={size}
|
||||||
|
interactive={true}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</WdTooltipWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './KillsCounter.tsx';
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
.TooltipActive {
|
||||||
|
pointer-events: auto !important;
|
||||||
|
position: relative;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hoverTarget {
|
||||||
|
padding: 0.5rem;
|
||||||
|
margin: -0.5rem;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.localCounter {
|
||||||
|
mix-blend-mode: screen;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1px;
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
color: var(--rf-node-local-counter);
|
||||||
|
|
||||||
|
&.hasUserCharacters {
|
||||||
|
color: var(--rf-has-user-characters);
|
||||||
|
}
|
||||||
|
|
||||||
|
& > i {
|
||||||
|
font-size: 9px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > span {
|
||||||
|
font-size: 9px;
|
||||||
|
line-height: 9px;
|
||||||
|
font-weight: var(--rf-local-counter-font-weight, 500);
|
||||||
|
|
||||||
|
@-moz-document url-prefix() {
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Pathfinder {
|
||||||
|
.localCounter {
|
||||||
|
@-moz-document url-prefix() {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > span {
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||||
|
import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit/WdTooltip';
|
||||||
|
import { CharItemProps, LocalCharactersList } from '../../../mapInterface/widgets/LocalCharacters/components';
|
||||||
|
import { useTheme } from '@/hooks/Mapper/hooks/useTheme.ts';
|
||||||
|
import { AvailableThemes } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||||
|
import classes from './LocalCounter.module.scss';
|
||||||
|
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
||||||
|
import { useLocalCharactersItemTemplate } from '@/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/hooks/useLocalCharacters.tsx';
|
||||||
|
|
||||||
|
interface LocalCounterProps {
|
||||||
|
localCounterCharacters: Array<CharItemProps>;
|
||||||
|
hasUserCharacters: boolean;
|
||||||
|
showIcon?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LocalCounter = ({ localCounterCharacters, hasUserCharacters, showIcon = true }: LocalCounterProps) => {
|
||||||
|
const {
|
||||||
|
data: { localShowShipName },
|
||||||
|
} = useMapState();
|
||||||
|
const itemTemplate = useLocalCharactersItemTemplate(localShowShipName);
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const pilotTooltipContent = useMemo(() => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
minWidth: '300px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LocalCharactersList items={localCounterCharacters} itemTemplate={itemTemplate} itemSize={26} autoSize={true} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, [localCounterCharacters, itemTemplate]);
|
||||||
|
|
||||||
|
if (localCounterCharacters.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx(classes.TooltipActive, {
|
||||||
|
[classes.Pathfinder]: theme === AvailableThemes.pathfinder,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<WdTooltipWrapper content={pilotTooltipContent} position={TooltipPosition.right} offset={0} interactive={true}>
|
||||||
|
<div className={clsx(classes.hoverTarget)}>
|
||||||
|
<div
|
||||||
|
className={clsx(classes.localCounter, {
|
||||||
|
[classes.hasUserCharacters]: hasUserCharacters,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{showIcon && <i className="pi pi-users" />}
|
||||||
|
<span>{localCounterCharacters.length}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</WdTooltipWrapper>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './LocalCounter';
|
||||||
@@ -1,51 +1,7 @@
|
|||||||
@import "@/hooks/Mapper/components/map/styles/neon-variables";
|
@import '@/hooks/Mapper/components/map/styles/eve-common-variables';
|
||||||
|
|
||||||
.react-flow__edge.selected {
|
|
||||||
.EdgePathBack {
|
|
||||||
stroke: $pastel-yellow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.EdgePathFront {
|
|
||||||
fill: none;
|
|
||||||
|
|
||||||
stroke: #2c3844;
|
|
||||||
stroke-width: 2px;
|
|
||||||
|
|
||||||
&.MassVerge:not(&.Frigate) {
|
|
||||||
stroke: #af2900;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.MassHalf:not(&.Frigate) {
|
|
||||||
stroke: #a85f00;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.Frigate {
|
|
||||||
stroke: #4e62c9;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.Hovered {
|
|
||||||
stroke: #4e5d6c;
|
|
||||||
stroke-width: 2px;
|
|
||||||
|
|
||||||
&.MassVerge:not(&.Frigate) {
|
|
||||||
stroke: #9d4c34;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.MassHalf:not(&.Frigate) {
|
|
||||||
stroke: #ec992c;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.Frigate {
|
|
||||||
stroke: #41acd7;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.EdgePathBack {
|
.EdgePathBack {
|
||||||
fill: none;
|
fill: none;
|
||||||
|
|
||||||
stroke: #80a5c5;
|
stroke: #80a5c5;
|
||||||
stroke-width: 3px;
|
stroke-width: 3px;
|
||||||
|
|
||||||
@@ -61,6 +17,65 @@
|
|||||||
stroke: #ef7dce;
|
stroke: #ef7dce;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.Tick {
|
||||||
|
stroke-width: 5px;
|
||||||
|
|
||||||
|
&.TimeCrit {
|
||||||
|
stroke-width: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.Gate {
|
||||||
|
stroke: #9aff40;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.EdgePathFront {
|
||||||
|
fill: none;
|
||||||
|
stroke: #2c3844;
|
||||||
|
stroke-width: 2px;
|
||||||
|
|
||||||
|
&.MassVerge:not(&.Frigate) {
|
||||||
|
stroke: #af0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.MassHalf:not(&.Frigate) {
|
||||||
|
stroke: #ffd700;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.Frigate {
|
||||||
|
stroke: #d4f0ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.Gate {
|
||||||
|
stroke: #1c1e15;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.Hovered {
|
||||||
|
stroke: #4e5d6c;
|
||||||
|
stroke-width: 2px;
|
||||||
|
|
||||||
|
&.MassVerge:not(&.Frigate) {
|
||||||
|
stroke: #9d4c34;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.MassHalf:not(&.Frigate) {
|
||||||
|
stroke: #ec992c;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.Frigate {
|
||||||
|
stroke: #d4f0ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.Tick {
|
||||||
|
stroke-width: 3px;
|
||||||
|
|
||||||
|
&.Hovered {
|
||||||
|
stroke-width: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ClickPath {
|
.ClickPath {
|
||||||
@@ -69,7 +84,23 @@
|
|||||||
stroke-width: 8px;
|
stroke-width: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.LinkLabel{
|
.Handle {
|
||||||
|
border: 1px solid var(--pastel-blue);
|
||||||
|
width: 5px;
|
||||||
|
height: 5px;
|
||||||
|
z-index: 1001;
|
||||||
|
|
||||||
|
&.Tick {
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.Right {
|
||||||
|
margin-left: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.LinkLabel {
|
||||||
font-size: 9px;
|
font-size: 9px;
|
||||||
line-height: 10px;
|
line-height: 10px;
|
||||||
padding: 2px 4px;
|
padding: 2px 4px;
|
||||||
@@ -85,13 +116,3 @@
|
|||||||
height: 8px;
|
height: 8px;
|
||||||
font-size: 8px;
|
font-size: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Handle {
|
|
||||||
min-width: initial;
|
|
||||||
min-height: initial;
|
|
||||||
border: 1px solid #5a7d9a;
|
|
||||||
width: 5px;
|
|
||||||
height: 5px;
|
|
||||||
z-index: 1001;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,36 +4,70 @@ import classes from './SolarSystemEdge.module.scss';
|
|||||||
import { EdgeLabelRenderer, EdgeProps, getBezierPath, Position, useStore } from 'reactflow';
|
import { EdgeLabelRenderer, EdgeProps, getBezierPath, Position, useStore } from 'reactflow';
|
||||||
import { getEdgeParams } from '@/hooks/Mapper/components/map/utils.ts';
|
import { getEdgeParams } from '@/hooks/Mapper/components/map/utils.ts';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
|
import { ConnectionType, MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
|
||||||
import { PrimeIcons } from 'primereact/api';
|
import { PrimeIcons } from 'primereact/api';
|
||||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||||
|
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
||||||
|
import { SHIP_SIZES_DESCRIPTION, SHIP_SIZES_NAMES_SHORT } from '@/hooks/Mapper/components/map/constants.ts';
|
||||||
|
|
||||||
const MAP_TRANSLATES: Record<string, string> = {
|
const MAP_TRANSLATES: Record<string, string> = {
|
||||||
[Position.Top]: 'translate(-50%, 0%)',
|
[Position.Top]: 'translate(-48%, 0%)',
|
||||||
[Position.Bottom]: 'translate(-50%, -100%)',
|
[Position.Bottom]: 'translate(-50%, -100%)',
|
||||||
[Position.Left]: 'translate(0%, -50%)',
|
[Position.Left]: 'translate(0%, -50%)',
|
||||||
[Position.Right]: 'translate(-100%, -50%)',
|
[Position.Right]: 'translate(-100%, -50%)',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const MAP_OFFSETS_TICK: Record<string, { x: number; y: number }> = {
|
||||||
|
[Position.Top]: { x: 0, y: 3 },
|
||||||
|
[Position.Bottom]: { x: 0, y: -3 },
|
||||||
|
[Position.Left]: { x: 3, y: 0 },
|
||||||
|
[Position.Right]: { x: -3, y: 0 },
|
||||||
|
};
|
||||||
|
|
||||||
|
const MAP_OFFSETS: Record<string, { x: number; y: number }> = {
|
||||||
|
[Position.Top]: { x: 0, y: 0 },
|
||||||
|
[Position.Bottom]: { x: 0, y: 0 },
|
||||||
|
[Position.Left]: { x: 0, y: 0 },
|
||||||
|
[Position.Right]: { x: 0, y: 0 },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SHIP_SIZES_COLORS = {
|
||||||
|
[ShipSizeStatus.small]: 'bg-indigo-400',
|
||||||
|
[ShipSizeStatus.medium]: 'bg-cyan-500',
|
||||||
|
[ShipSizeStatus.large]: '',
|
||||||
|
[ShipSizeStatus.freight]: 'bg-lime-400',
|
||||||
|
[ShipSizeStatus.capital]: 'bg-red-400',
|
||||||
|
};
|
||||||
|
|
||||||
export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }: EdgeProps<SolarSystemConnection>) => {
|
export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }: EdgeProps<SolarSystemConnection>) => {
|
||||||
const sourceNode = useStore(useCallback(store => store.nodeInternals.get(source), [source]));
|
const sourceNode = useStore(useCallback(store => store.nodeInternals.get(source), [source]));
|
||||||
const targetNode = useStore(useCallback(store => store.nodeInternals.get(target), [target]));
|
const targetNode = useStore(useCallback(store => store.nodeInternals.get(target), [target]));
|
||||||
|
const isWormhole = data?.type !== ConnectionType.gate;
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: { isThickConnections },
|
||||||
|
} = useMapState();
|
||||||
|
|
||||||
const [hovered, setHovered] = useState(false);
|
const [hovered, setHovered] = useState(false);
|
||||||
|
|
||||||
const [path, labelX, labelY, sx, sy, tx, ty, sourcePos, targetPos] = useMemo(() => {
|
const [path, labelX, labelY, sx, sy, tx, ty, sourcePos, targetPos] = useMemo(() => {
|
||||||
const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(sourceNode, targetNode);
|
const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(sourceNode!, targetNode!);
|
||||||
|
|
||||||
const [edgePath, labelX, labelY] = getBezierPath({
|
const offset = isThickConnections ? MAP_OFFSETS_TICK[targetPos] : MAP_OFFSETS[targetPos];
|
||||||
sourceX: sx,
|
|
||||||
sourceY: sy,
|
const method = isWormhole ? getBezierPath : getBezierPath;
|
||||||
|
|
||||||
|
const [edgePath, labelX, labelY] = method({
|
||||||
|
sourceX: sx - offset.x,
|
||||||
|
sourceY: sy - offset.y,
|
||||||
sourcePosition: sourcePos,
|
sourcePosition: sourcePos,
|
||||||
targetPosition: targetPos,
|
targetPosition: targetPos,
|
||||||
targetX: tx,
|
targetX: tx + offset.x,
|
||||||
targetY: ty,
|
targetY: ty + offset.y,
|
||||||
});
|
});
|
||||||
|
|
||||||
return [edgePath, labelX, labelY, sx, sy, tx, ty, sourcePos, targetPos];
|
return [edgePath, labelX, labelY, sx, sy, tx, ty, sourcePos, targetPos];
|
||||||
}, [sourceNode, targetNode]);
|
}, [isThickConnections, sourceNode, targetNode, isWormhole]);
|
||||||
|
|
||||||
if (!sourceNode || !targetNode || !data) {
|
if (!sourceNode || !targetNode || !data) {
|
||||||
return null;
|
return null;
|
||||||
@@ -44,8 +78,10 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
|
|||||||
<path
|
<path
|
||||||
id={`back_${id}`}
|
id={`back_${id}`}
|
||||||
className={clsx(classes.EdgePathBack, {
|
className={clsx(classes.EdgePathBack, {
|
||||||
[classes.TimeCrit]: data.time_status === TimeStatus.eol,
|
[classes.Tick]: isThickConnections,
|
||||||
|
[classes.TimeCrit]: isWormhole && data.time_status === TimeStatus.eol,
|
||||||
[classes.Hovered]: hovered,
|
[classes.Hovered]: hovered,
|
||||||
|
[classes.Gate]: !isWormhole,
|
||||||
})}
|
})}
|
||||||
d={path}
|
d={path}
|
||||||
markerEnd={markerEnd}
|
markerEnd={markerEnd}
|
||||||
@@ -54,10 +90,12 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
|
|||||||
<path
|
<path
|
||||||
id={`front_${id}`}
|
id={`front_${id}`}
|
||||||
className={clsx(classes.EdgePathFront, {
|
className={clsx(classes.EdgePathFront, {
|
||||||
|
[classes.Tick]: isThickConnections,
|
||||||
[classes.Hovered]: hovered,
|
[classes.Hovered]: hovered,
|
||||||
[classes.MassVerge]: data.mass_status === MassState.verge,
|
[classes.MassVerge]: isWormhole && data.mass_status === MassState.verge,
|
||||||
[classes.MassHalf]: data.mass_status === MassState.half,
|
[classes.MassHalf]: isWormhole && data.mass_status === MassState.half,
|
||||||
[classes.Frigate]: data.ship_size_type === ShipSizeStatus.small,
|
[classes.Frigate]: isWormhole && data.ship_size_type === ShipSizeStatus.small,
|
||||||
|
[classes.Gate]: !isWormhole,
|
||||||
})}
|
})}
|
||||||
d={path}
|
d={path}
|
||||||
markerEnd={markerEnd}
|
markerEnd={markerEnd}
|
||||||
@@ -75,21 +113,29 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
|
|||||||
|
|
||||||
<EdgeLabelRenderer>
|
<EdgeLabelRenderer>
|
||||||
<div
|
<div
|
||||||
className={clsx(classes.Handle, 'react-flow__handle absolute nodrag pointer-events-none')}
|
className={clsx(
|
||||||
|
classes.Handle,
|
||||||
|
{ [classes.Tick]: isThickConnections, [classes.Right]: Position.Right === sourcePos },
|
||||||
|
'react-flow__handle absolute nodrag pointer-events-none',
|
||||||
|
)}
|
||||||
style={{ transform: `${MAP_TRANSLATES[sourcePos]} translate(${sx}px,${sy}px)` }}
|
style={{ transform: `${MAP_TRANSLATES[sourcePos]} translate(${sx}px,${sy}px)` }}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className={clsx(classes.Handle, 'react-flow__handle absolute nodrag pointer-events-none')}
|
className={clsx(
|
||||||
|
classes.Handle,
|
||||||
|
{ [classes.Tick]: isThickConnections },
|
||||||
|
'react-flow__handle absolute nodrag pointer-events-none',
|
||||||
|
)}
|
||||||
style={{ transform: `${MAP_TRANSLATES[targetPos]} translate(${tx}px,${ty}px)` }}
|
style={{ transform: `${MAP_TRANSLATES[targetPos]} translate(${tx}px,${ty}px)` }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="absolute flex items-center gap-1"
|
className="absolute flex items-center gap-1 pointer-events-none"
|
||||||
style={{
|
style={{
|
||||||
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{data.locked && (
|
{isWormhole && data.locked && (
|
||||||
<WdTooltipWrapper
|
<WdTooltipWrapper
|
||||||
content="Save mass"
|
content="Save mass"
|
||||||
className={clsx(
|
className={clsx(
|
||||||
@@ -100,6 +146,19 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
|
|||||||
<span className={clsx(PrimeIcons.LOCK, classes.icon)} />
|
<span className={clsx(PrimeIcons.LOCK, classes.icon)} />
|
||||||
</WdTooltipWrapper>
|
</WdTooltipWrapper>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{isWormhole && data.ship_size_type !== ShipSizeStatus.large && (
|
||||||
|
<WdTooltipWrapper
|
||||||
|
content={SHIP_SIZES_DESCRIPTION[data.ship_size_type]}
|
||||||
|
className={clsx(
|
||||||
|
classes.LinkLabel,
|
||||||
|
'pointer-events-auto rounded opacity-100 cursor-auto text-neutral-900 font-bold',
|
||||||
|
SHIP_SIZES_COLORS[data.ship_size_type],
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{SHIP_SIZES_NAMES_SHORT[data.ship_size_type]}
|
||||||
|
</WdTooltipWrapper>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</EdgeLabelRenderer>
|
</EdgeLabelRenderer>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,271 +0,0 @@
|
|||||||
import { memo, useMemo } from 'react';
|
|
||||||
import { Handle, Position, WrapNodeProps } from 'reactflow';
|
|
||||||
import { MapSolarSystemType } from '../../map.types';
|
|
||||||
import classes from './SolarSystemNode.module.scss';
|
|
||||||
import clsx from 'clsx';
|
|
||||||
import {
|
|
||||||
EFFECT_BACKGROUND_STYLES,
|
|
||||||
LABELS_INFO,
|
|
||||||
LABELS_ORDER,
|
|
||||||
MARKER_BOOKMARK_BG_STYLES,
|
|
||||||
STATUS_CLASSES,
|
|
||||||
} from '@/hooks/Mapper/components/map/constants.ts';
|
|
||||||
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
|
|
||||||
import { WormholeClassComp } from '@/hooks/Mapper/components/map/components/WormholeClassComp';
|
|
||||||
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
|
||||||
import { getSystemClassStyles } from '@/hooks/Mapper/components/map/helpers';
|
|
||||||
import { sortWHClasses } from '@/hooks/Mapper/helpers';
|
|
||||||
import { PrimeIcons } from 'primereact/api';
|
|
||||||
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager.ts';
|
|
||||||
import { OutCommand } from '@/hooks/Mapper/types';
|
|
||||||
import { useDoubleClick } from '@/hooks/Mapper/hooks/useDoubleClick.ts';
|
|
||||||
import { REGIONS_MAP, Spaces } from '@/hooks/Mapper/constants';
|
|
||||||
|
|
||||||
const SpaceToClass: Record<string, string> = {
|
|
||||||
[Spaces.Caldari]: classes.Caldaria,
|
|
||||||
[Spaces.Matar]: classes.Mataria,
|
|
||||||
[Spaces.Amarr]: classes.Amarria,
|
|
||||||
[Spaces.Gallente]: classes.Gallente,
|
|
||||||
};
|
|
||||||
|
|
||||||
const sortedLabels = (labels: string[]) => {
|
|
||||||
if (!labels) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return LABELS_ORDER.filter(x => labels.includes(x)).map(x => LABELS_INFO[x]);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getActivityType = (count: number) => {
|
|
||||||
if (count <= 5) {
|
|
||||||
return 'activityNormal';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count <= 30) {
|
|
||||||
return 'activityWarn';
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'activityDanger';
|
|
||||||
};
|
|
||||||
|
|
||||||
// eslint-disable-next-line react/display-name
|
|
||||||
export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarSystemType>) => {
|
|
||||||
const {
|
|
||||||
system_class,
|
|
||||||
security,
|
|
||||||
class_title,
|
|
||||||
solar_system_id,
|
|
||||||
statics,
|
|
||||||
effect_name,
|
|
||||||
region_name,
|
|
||||||
region_id,
|
|
||||||
is_shattered,
|
|
||||||
solar_system_name,
|
|
||||||
} = data.system_static_info;
|
|
||||||
|
|
||||||
const { locked, name, tag, status, labels, id } = data || {};
|
|
||||||
|
|
||||||
const customName = solar_system_name !== name ? name : undefined;
|
|
||||||
|
|
||||||
const {
|
|
||||||
data: {
|
|
||||||
characters,
|
|
||||||
presentCharacters,
|
|
||||||
wormholesData,
|
|
||||||
hubs,
|
|
||||||
kills,
|
|
||||||
userCharacters,
|
|
||||||
isConnecting,
|
|
||||||
hoverNodeId,
|
|
||||||
visibleNodes,
|
|
||||||
showKSpaceBG,
|
|
||||||
},
|
|
||||||
outCommand,
|
|
||||||
} = useMapState();
|
|
||||||
|
|
||||||
const visible = useMemo(() => visibleNodes.has(id), [id, visibleNodes]);
|
|
||||||
|
|
||||||
const charactersInSystem = useMemo(() => {
|
|
||||||
return characters.filter(c => c.location?.solar_system_id === solar_system_id).filter(c => c.online);
|
|
||||||
// eslint-disable-next-line
|
|
||||||
}, [characters, presentCharacters, solar_system_id]);
|
|
||||||
|
|
||||||
const isWormhole = isWormholeSpace(system_class);
|
|
||||||
const classTitleColor = useMemo(
|
|
||||||
() => getSystemClassStyles({ systemClass: system_class, security }),
|
|
||||||
[security, system_class],
|
|
||||||
);
|
|
||||||
const sortedStatics = useMemo(() => sortWHClasses(wormholesData, statics), [wormholesData, statics]);
|
|
||||||
const lebM = useMemo(() => new LabelsManager(labels ?? ''), [labels]);
|
|
||||||
const labelsInfo = useMemo(() => sortedLabels(lebM.list), [lebM]);
|
|
||||||
const labelCustom = useMemo(() => lebM.customLabel, [lebM]);
|
|
||||||
|
|
||||||
const killsCount = useMemo(() => {
|
|
||||||
const systemKills = kills[solar_system_id];
|
|
||||||
if (!systemKills) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return systemKills;
|
|
||||||
}, [kills, solar_system_id]);
|
|
||||||
|
|
||||||
const hasUserCharacters = useMemo(() => {
|
|
||||||
return charactersInSystem.some(x => userCharacters.includes(x.eve_id));
|
|
||||||
}, [charactersInSystem, userCharacters]);
|
|
||||||
|
|
||||||
const dbClick = useDoubleClick(() => {
|
|
||||||
outCommand({
|
|
||||||
type: OutCommand.openSettings,
|
|
||||||
data: {
|
|
||||||
system_id: solar_system_id.toString(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const showHandlers = isConnecting || hoverNodeId === id;
|
|
||||||
|
|
||||||
const space = showKSpaceBG ? REGIONS_MAP[region_id] : '';
|
|
||||||
const regionClass = showKSpaceBG ? SpaceToClass[space] : null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{visible && (
|
|
||||||
<div className={classes.Bookmarks}>
|
|
||||||
{labelCustom !== '' && (
|
|
||||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.custom)}>
|
|
||||||
<span className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] ">{labelCustom}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{is_shattered && (
|
|
||||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.shattered)}>
|
|
||||||
<span className={clsx('pi pi-chart-pie', classes.icon)} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{killsCount && (
|
|
||||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[getActivityType(killsCount)])}>
|
|
||||||
<div className={clsx(classes.BookmarkWithIcon)}>
|
|
||||||
<span className={clsx(PrimeIcons.BOLT, classes.icon)} />
|
|
||||||
<span className={clsx(classes.text)}>{killsCount}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{labelsInfo.map(x => (
|
|
||||||
<div key={x.id} className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[x.id])}>
|
|
||||||
{x.shortName}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div
|
|
||||||
className={clsx(classes.RootCustomNode, regionClass, classes[STATUS_CLASSES[status]], {
|
|
||||||
[classes.selected]: selected,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{visible && (
|
|
||||||
<>
|
|
||||||
<div className={classes.HeadRow}>
|
|
||||||
<div className={clsx(classes.classTitle, classTitleColor, '[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]')}>
|
|
||||||
{class_title ?? '-'}
|
|
||||||
</div>
|
|
||||||
{tag != null && tag !== '' && (
|
|
||||||
<div className={clsx(classes.TagTitle, 'text-sky-400 font-medium')}>{tag}</div>
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
className={clsx(
|
|
||||||
classes.classSystemName,
|
|
||||||
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] flex-grow overflow-hidden text-ellipsis whitespace-nowrap font-sans',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{solar_system_name}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isWormhole && (
|
|
||||||
<div className={classes.statics}>
|
|
||||||
{sortedStatics.map(x => (
|
|
||||||
<WormholeClassComp key={x} id={x} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{effect_name !== null && isWormhole && (
|
|
||||||
<div className={clsx(classes.effect, EFFECT_BACKGROUND_STYLES[effect_name])}></div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={clsx(classes.BottomRow, 'flex items-center justify-between')}>
|
|
||||||
{customName && (
|
|
||||||
<div className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] text-blue-300 whitespace-nowrap overflow-hidden text-ellipsis mr-0.5">
|
|
||||||
{customName}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isWormhole && !customName && (
|
|
||||||
<div
|
|
||||||
className={clsx(
|
|
||||||
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] text-stone-300 whitespace-nowrap overflow-hidden text-ellipsis mr-0.5',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{region_name}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isWormhole && !customName && <div />}
|
|
||||||
|
|
||||||
<div className="flex items-center justify-end">
|
|
||||||
<div className="flex gap-1 items-center">
|
|
||||||
{locked && <i className={PrimeIcons.LOCK} style={{ fontSize: '0.45rem', fontWeight: 'bold' }}></i>}
|
|
||||||
|
|
||||||
{hubs.includes(solar_system_id.toString()) && (
|
|
||||||
<i className={PrimeIcons.MAP_MARKER} style={{ fontSize: '0.45rem', fontWeight: 'bold' }}></i>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{charactersInSystem.length > 0 && (
|
|
||||||
<div className={clsx(classes.localCounter, { ['text-amber-300']: hasUserCharacters })}>
|
|
||||||
<i className="pi pi-users" style={{ fontSize: '0.50rem' }}></i>
|
|
||||||
<span className="font-sans">{charactersInSystem.length}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div onMouseDownCapture={dbClick} className={classes.Handlers}>
|
|
||||||
<Handle
|
|
||||||
type="source"
|
|
||||||
className={clsx(classes.Handle, classes.HandleTop, { [classes.selected]: selected })}
|
|
||||||
style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
|
|
||||||
position={Position.Top}
|
|
||||||
id="a"
|
|
||||||
/>
|
|
||||||
<Handle
|
|
||||||
type="source"
|
|
||||||
className={clsx(classes.Handle, classes.HandleRight, { [classes.selected]: selected })}
|
|
||||||
style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
|
|
||||||
position={Position.Right}
|
|
||||||
id="b"
|
|
||||||
/>
|
|
||||||
<Handle
|
|
||||||
type="source"
|
|
||||||
className={clsx(classes.Handle, classes.HandleBottom, { [classes.selected]: selected })}
|
|
||||||
style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
|
|
||||||
position={Position.Bottom}
|
|
||||||
id="c"
|
|
||||||
/>
|
|
||||||
<Handle
|
|
||||||
type="source"
|
|
||||||
className={clsx(classes.Handle, classes.HandleLeft, { [classes.selected]: selected })}
|
|
||||||
style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
|
|
||||||
position={Position.Left}
|
|
||||||
id="d"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@@ -1,35 +1,51 @@
|
|||||||
@import '@/hooks/Mapper/components/map/styles/eve-common-variables';
|
@import '@/hooks/Mapper/components/map/styles/eve-common-variables';
|
||||||
|
|
||||||
$pastel-blue: #5a7d9a;
|
$pastel-blue: #5a7d9a;
|
||||||
$pastel-pink: #d291bc;
|
$pastel-pink: rgb(30, 161, 255);
|
||||||
$pastel-green: #88b04b;
|
|
||||||
$pastel-yellow: #ffdd59;
|
|
||||||
$dark-bg: #2d2d2d;
|
$dark-bg: #2d2d2d;
|
||||||
$text-color: #ffffff;
|
$text-color: #ffffff;
|
||||||
$tooltip-bg: #202020; // Темный фон для подсказок
|
$tooltip-bg: #202020;
|
||||||
|
|
||||||
|
$neon-color-1: rgb(27, 132, 236);
|
||||||
|
$neon-color-3: rgba(27, 132, 236, 0.40);
|
||||||
|
|
||||||
|
@keyframes move-stripes {
|
||||||
|
from {
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
background-position: 30px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.RootCustomNode {
|
.RootCustomNode {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 130px;
|
width: 130px;
|
||||||
height: 34px;
|
height: 34px;
|
||||||
|
|
||||||
|
font-family: var(--rf-node-font-family, inherit) !important;
|
||||||
|
font-weight: var(--rf-node-font-weight, inherit) !important;
|
||||||
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 2px 6px;
|
padding: 2px 6px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
|
|
||||||
background-color: $tooltip-bg;
|
background-color: var(--rf-node-bg-color, #202020) !important;
|
||||||
|
color: var(--rf-text-color, #ffffff);
|
||||||
|
|
||||||
box-shadow: 0 0 5px rgba($dark-bg, 0.5);
|
box-shadow: 0 0 5px rgba($dark-bg, 0.5);
|
||||||
border: 1px solid darken($pastel-blue, 10%);
|
border: 1px solid darken($pastel-blue, 10%);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 3;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
&.Pochven,
|
||||||
&.Mataria,
|
&.Mataria,
|
||||||
&.Amarria,
|
&.Amarria,
|
||||||
&.Gallente,
|
&.Gallente,
|
||||||
&.Caldaria {
|
&.Caldaria {
|
||||||
&::before {
|
&::after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -45,7 +61,7 @@ $tooltip-bg: #202020; // Темный фон для подсказок
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.Mataria {
|
&.Mataria {
|
||||||
&::before {
|
&::after {
|
||||||
background-image: url('/images/mataria-180.png');
|
background-image: url('/images/mataria-180.png');
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
background-position-x: 1px;
|
background-position-x: 1px;
|
||||||
@@ -54,7 +70,7 @@ $tooltip-bg: #202020; // Темный фон для подсказок
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.Caldaria {
|
&.Caldaria {
|
||||||
&::before {
|
&::after {
|
||||||
background-image: url('/images/caldaria-180.png');
|
background-image: url('/images/caldaria-180.png');
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
background-position-x: 1px;
|
background-position-x: 1px;
|
||||||
@@ -63,7 +79,7 @@ $tooltip-bg: #202020; // Темный фон для подсказок
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.Amarria {
|
&.Amarria {
|
||||||
&::before {
|
&::after {
|
||||||
opacity: 0.45;
|
opacity: 0.45;
|
||||||
background-image: url('/images/amarr-180.png');
|
background-image: url('/images/amarr-180.png');
|
||||||
background-position-x: 0;
|
background-position-x: 0;
|
||||||
@@ -72,7 +88,7 @@ $tooltip-bg: #202020; // Темный фон для подсказок
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.Gallente {
|
&.Gallente {
|
||||||
&::before {
|
&::after {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
background-image: url('/images/gallente-180.png');
|
background-image: url('/images/gallente-180.png');
|
||||||
background-position-x: 1px;
|
background-position-x: 1px;
|
||||||
@@ -80,63 +96,84 @@ $tooltip-bg: #202020; // Темный фон для подсказок
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.Pochven {
|
||||||
|
&::after {
|
||||||
|
opacity: 0.8;
|
||||||
|
background-image: url('/images/pochven.webp');
|
||||||
|
background-position-x: 0;
|
||||||
|
background-position-y: -13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
border-color: $pastel-pink;
|
border-color: $pastel-pink;
|
||||||
box-shadow: 0 0 10px #9a1af1c2;
|
box-shadow: 0 0 10px #9a1af1c2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip {
|
&.rally {
|
||||||
background-color: $tooltip-bg;
|
&::before {
|
||||||
color: $text-color;
|
content: '';
|
||||||
padding: 5px 10px;
|
position: absolute;
|
||||||
border-radius: 3px;
|
top: 0;
|
||||||
border: 1px solid $pastel-pink;
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: -1;
|
||||||
|
|
||||||
|
border-color: $neon-color-1;
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
45deg,
|
||||||
|
$neon-color-3 0px,
|
||||||
|
$neon-color-3 8px,
|
||||||
|
transparent 8px,
|
||||||
|
transparent 21px
|
||||||
|
);
|
||||||
|
background-size: 30px 30px;
|
||||||
|
animation: move-stripes 3s linear infinite;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.eve-system-status-home {
|
&.eve-system-status-home {
|
||||||
border: 1px solid darken($eve-solar-system-status-color-home, 30%);
|
border: 1px solid var(--eve-solar-system-status-color-home-dark30);
|
||||||
background-image: linear-gradient(275deg, $eve-solar-system-status-friendly, transparent);
|
background-image: linear-gradient(45deg, var(--eve-solar-system-status-color-background), transparent);
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
border-color: $eve-solar-system-status-color-home;
|
border-color: var(--eve-solar-system-status-color-home);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.eve-system-status-friendly {
|
&.eve-system-status-friendly {
|
||||||
border: 1px solid darken($eve-solar-system-status-color-friendly, 20%);
|
border: 1px solid var(--eve-solar-system-status-color-friendly-dark20);
|
||||||
background-image: linear-gradient(275deg, darken($eve-solar-system-status-friendly, 30%), transparent);
|
background-image: linear-gradient(275deg, var(--eve-solar-system-status-friendly-dark30), transparent);
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
border-color: darken($eve-solar-system-status-color-friendly, 5%);
|
border-color: var(--eve-solar-system-status-color-friendly-dark5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.eve-system-status-lookingFor {
|
&.eve-system-status-lookingFor {
|
||||||
border: 1px solid darken($eve-solar-system-status-color-lookingFor, 15%);
|
border: 1px solid var(--eve-solar-system-status-color-lookingFor-dark15);
|
||||||
background-image: linear-gradient(275deg, #45ff8f2f, #457fff2f);
|
background-image: linear-gradient(275deg, #45ff8f2f, #457fff2f);
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
border-color: $pastel-pink;
|
border-color: $pastel-pink;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.eve-system-status-warning {
|
&.eve-system-status-warning {
|
||||||
background-image: linear-gradient(275deg, $eve-solar-system-status-warning, transparent);
|
background-image: linear-gradient(275deg, var(--eve-solar-system-status-warning), transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.eve-system-status-dangerous {
|
&.eve-system-status-dangerous {
|
||||||
background-image: linear-gradient(275deg, $eve-solar-system-status-dangerous, transparent);
|
background-image: linear-gradient(275deg, var(--eve-solar-system-status-dangerous), transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.eve-system-status-target {
|
&.eve-system-status-target {
|
||||||
background-image: linear-gradient(275deg, $eve-solar-system-status-target, transparent);
|
background-image: linear-gradient(275deg, var(--eve-solar-system-status-target), transparent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.Bookmarks {
|
.Bookmarks {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 0;
|
z-index: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
left: 4px;
|
left: 4px;
|
||||||
|
|
||||||
@@ -154,8 +191,6 @@ $tooltip-bg: #202020; // Темный фон для подсказок
|
|||||||
padding-left: 3px;
|
padding-left: 3px;
|
||||||
padding-right: 3px;
|
padding-right: 3px;
|
||||||
|
|
||||||
//background-color: #833ca4;
|
|
||||||
|
|
||||||
&:not(:first-child) {
|
&:not(:first-child) {
|
||||||
box-shadow: inset 4px -3px 4px rgba(0, 0, 0, 0.3);
|
box-shadow: inset 4px -3px 4px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
@@ -182,6 +217,42 @@ $tooltip-bg: #202020; // Темный фон для подсказок
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Unsplashed {
|
||||||
|
position: absolute;
|
||||||
|
width: calc(50% - 4px);
|
||||||
|
z-index: -1;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 2px;
|
||||||
|
left: 2px;
|
||||||
|
|
||||||
|
&--right {
|
||||||
|
left: calc(50% + 6px);
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .Signature {
|
||||||
|
width: 13px;
|
||||||
|
height: 4px;
|
||||||
|
position: relative;
|
||||||
|
top: 3px;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 8px;
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 2px;
|
||||||
|
font-weight: bolder;
|
||||||
|
padding-left: 3px;
|
||||||
|
padding-right: 3px;
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
background-color: #833ca4;
|
||||||
|
|
||||||
|
&:not(:first-child) {
|
||||||
|
box-shadow: inset 4px -3px 4px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
height: 8px;
|
height: 8px;
|
||||||
@@ -206,17 +277,16 @@ $tooltip-bg: #202020; // Темный фон для подсказок
|
|||||||
|
|
||||||
.TagTitle {
|
.TagTitle {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: bold;
|
font-weight: 500;
|
||||||
text-shadow: 0 0 2px rgba(231, 146, 52, 0.73);
|
text-shadow: 0 0 2px rgba(231, 146, 52, 0.73);
|
||||||
|
color: var(--rf-tag-color, #38bdf8);
|
||||||
color: #ffb01d;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.classSystemName {
|
/* Firefox kostyl */
|
||||||
//font-weight: bold;
|
@-moz-document url-prefix() {
|
||||||
}
|
.classSystemName {
|
||||||
|
font-weight: bold;
|
||||||
.solarSystemName {
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,22 +296,23 @@ $tooltip-bg: #202020; // Темный фон для подсказок
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
height: 19px;
|
height: 19px;
|
||||||
|
|
||||||
.localCounter {
|
.hasLocalCounter {
|
||||||
display: flex;
|
margin-right: 2px;
|
||||||
//align-items: center;
|
&.countAbove9 {
|
||||||
gap: 2px;
|
margin-right: 1.5rem;
|
||||||
|
|
||||||
& > i {
|
|
||||||
position: relative;
|
|
||||||
top: 1px;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
& > span {
|
.lockIcon {
|
||||||
font-size: 9px;
|
font-size: 0.45rem;
|
||||||
line-height: 9px;
|
font-weight: bold;
|
||||||
font-weight: 500;
|
position: relative;
|
||||||
//margin-top: 1px;
|
}
|
||||||
}
|
|
||||||
|
.mapMarker {
|
||||||
|
font-size: 0.45rem;
|
||||||
|
font-weight: bold;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,11 +333,18 @@ $tooltip-bg: #202020; // Темный фон для подсказок
|
|||||||
& > * {
|
& > * {
|
||||||
line-height: 10px;
|
line-height: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Firefox kostyl */
|
||||||
|
@-moz-document url-prefix() {
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.Handlers {
|
.Handlers {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 2;
|
z-index: 4;
|
||||||
|
pointer-events: none;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -279,6 +357,7 @@ $tooltip-bg: #202020; // Темный фон для подсказок
|
|||||||
border: 1px solid $pastel-blue;
|
border: 1px solid $pastel-blue;
|
||||||
width: 5px;
|
width: 5px;
|
||||||
height: 5px;
|
height: 5px;
|
||||||
|
pointer-events: auto;
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
border-color: $pastel-pink;
|
border-color: $pastel-pink;
|
||||||
@@ -299,4 +378,37 @@ $tooltip-bg: #202020; // Темный фон для подсказок
|
|||||||
&.HandleLeft {
|
&.HandleLeft {
|
||||||
left: -2px;
|
left: -2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.Tick {
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
|
||||||
|
&.HandleTop {
|
||||||
|
top: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.HandleRight {
|
||||||
|
right: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.HandleBottom {
|
||||||
|
bottom: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.HandleLeft {
|
||||||
|
left: -3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ShatteredIcon {
|
||||||
|
position: relative;
|
||||||
|
//top: -1px;
|
||||||
|
left: -1px;
|
||||||
|
|
||||||
|
background-size: 100%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
|
||||||
|
background-image: url(/images/chart-network-svgrepo-com.svg)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
import { memo } from 'react';
|
||||||
|
import { MapSolarSystemType } from '../../map.types';
|
||||||
|
import { Handle, NodeProps, Position } from 'reactflow';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import classes from './SolarSystemNodeDefault.module.scss';
|
||||||
|
import { PrimeIcons } from 'primereact/api';
|
||||||
|
import { useLocalCounter, useNodeKillsCount, useSolarSystemNode } from '../../hooks';
|
||||||
|
import {
|
||||||
|
EFFECT_BACKGROUND_STYLES,
|
||||||
|
MARKER_BOOKMARK_BG_STYLES,
|
||||||
|
STATUS_CLASSES,
|
||||||
|
} from '@/hooks/Mapper/components/map/constants';
|
||||||
|
import { WormholeClassComp } from '@/hooks/Mapper/components/map/components/WormholeClassComp';
|
||||||
|
import { UnsplashedSignature } from '@/hooks/Mapper/components/map/components/UnsplashedSignature';
|
||||||
|
import { TooltipSize } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper/utils.ts';
|
||||||
|
import { TooltipPosition, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
|
||||||
|
import { Tag } from 'primereact/tag';
|
||||||
|
import { LocalCounter } from '@/hooks/Mapper/components/map/components/LocalCounter';
|
||||||
|
import { KillsCounter } from '@/hooks/Mapper/components/map/components/KillsCounter';
|
||||||
|
|
||||||
|
// let render = 0;
|
||||||
|
export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>) => {
|
||||||
|
const nodeVars = useSolarSystemNode(props);
|
||||||
|
const { localCounterCharacters } = useLocalCounter(nodeVars);
|
||||||
|
const { killsCount: localKillsCount, killsActivityType: localKillsActivityType } = useNodeKillsCount(
|
||||||
|
nodeVars.solarSystemId,
|
||||||
|
);
|
||||||
|
|
||||||
|
// console.log('JOipP', `render ${nodeVars.id}`, render++);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{nodeVars.visible && (
|
||||||
|
<div className={classes.Bookmarks}>
|
||||||
|
{nodeVars.isShattered && (
|
||||||
|
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.shattered, '!pr-[2px]')}>
|
||||||
|
<WdTooltipWrapper content="Shattered" position={TooltipPosition.top}>
|
||||||
|
<span className={clsx('block w-[10px] h-[10px]', classes.ShatteredIcon)} />
|
||||||
|
</WdTooltipWrapper>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{localKillsCount != null && localKillsCount > 0 && nodeVars.solarSystemId && localKillsActivityType && (
|
||||||
|
<KillsCounter
|
||||||
|
killsCount={localKillsCount}
|
||||||
|
systemId={nodeVars.solarSystemId}
|
||||||
|
size={TooltipSize.lg}
|
||||||
|
killsActivityType={localKillsActivityType}
|
||||||
|
className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[localKillsActivityType])}
|
||||||
|
>
|
||||||
|
<div className={clsx(classes.BookmarkWithIcon)}>
|
||||||
|
<span className={clsx(PrimeIcons.BOLT, classes.icon)} />
|
||||||
|
<span className={clsx(classes.text)}>{localKillsCount}</span>
|
||||||
|
</div>
|
||||||
|
</KillsCounter>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{nodeVars.labelCustom !== '' && (
|
||||||
|
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.custom)}>
|
||||||
|
<span className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]">{nodeVars.labelCustom}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{nodeVars.labelsInfo.map(x => (
|
||||||
|
<div key={x.id} className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[x.id])}>
|
||||||
|
{x.shortName}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
classes.RootCustomNode,
|
||||||
|
nodeVars.regionClass && classes[nodeVars.regionClass],
|
||||||
|
nodeVars.status !== undefined && classes[STATUS_CLASSES[nodeVars.status]],
|
||||||
|
{
|
||||||
|
[classes.selected]: nodeVars.selected,
|
||||||
|
[classes.rally]: nodeVars.isRally,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
onMouseDownCapture={e => nodeVars.dbClick(e)}
|
||||||
|
>
|
||||||
|
{nodeVars.visible && (
|
||||||
|
<>
|
||||||
|
<div className={classes.HeadRow}>
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
classes.classTitle,
|
||||||
|
nodeVars.classTitleColor,
|
||||||
|
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{nodeVars.classTitle ?? '-'}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{nodeVars.tag != null && nodeVars.tag !== '' && (
|
||||||
|
<Tag
|
||||||
|
value={nodeVars.tag}
|
||||||
|
severity="warning"
|
||||||
|
className="py-0 px-[2px] text-[9px] [&_.p-tag-value]:leading-[1.3]"
|
||||||
|
></Tag>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
classes.classSystemName,
|
||||||
|
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] flex-grow overflow-hidden text-ellipsis whitespace-nowrap font-sans',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{nodeVars.systemName}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{nodeVars.isWormhole && (
|
||||||
|
<div className={classes.statics}>
|
||||||
|
{nodeVars.sortedStatics.map(whClass => (
|
||||||
|
<WormholeClassComp key={String(whClass)} id={String(whClass)} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{nodeVars.effectName !== null && nodeVars.isWormhole && (
|
||||||
|
<div className={clsx(classes.effect, EFFECT_BACKGROUND_STYLES[nodeVars.effectName])} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={clsx(classes.BottomRow, 'flex items-center justify-between')}>
|
||||||
|
{nodeVars.customName && (
|
||||||
|
<div className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] text-blue-300 whitespace-nowrap overflow-hidden text-ellipsis mr-0.5">
|
||||||
|
{nodeVars.customName}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!nodeVars.isWormhole && !nodeVars.customName && (
|
||||||
|
<div className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] text-stone-300 whitespace-nowrap overflow-hidden text-ellipsis mr-0.5">
|
||||||
|
{nodeVars.regionName}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{nodeVars.isWormhole && !nodeVars.customName && <div />}
|
||||||
|
|
||||||
|
<div className="flex items-center gap-1 justify-end">
|
||||||
|
<div className={clsx('flex items-center gap-1')}>
|
||||||
|
{nodeVars.locked && <i className={clsx(PrimeIcons.LOCK, classes.lockIcon)} />}
|
||||||
|
{nodeVars.hubs.includes(nodeVars.solarSystemId) && (
|
||||||
|
<i className={clsx(PrimeIcons.MAP_MARKER, classes.mapMarker)} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<LocalCounter
|
||||||
|
hasUserCharacters={nodeVars.hasUserCharacters}
|
||||||
|
localCounterCharacters={localCounterCharacters}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{nodeVars.visible && (
|
||||||
|
<>
|
||||||
|
{nodeVars.unsplashedLeft.length > 0 && (
|
||||||
|
<div className={classes.Unsplashed}>
|
||||||
|
{nodeVars.unsplashedLeft.map(sig => (
|
||||||
|
<UnsplashedSignature key={sig.eve_id} signature={sig} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{nodeVars.unsplashedRight.length > 0 && (
|
||||||
|
<div className={clsx(classes.Unsplashed, classes['Unsplashed--right'])}>
|
||||||
|
{nodeVars.unsplashedRight.map(sig => (
|
||||||
|
<UnsplashedSignature key={sig.eve_id} signature={sig} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={classes.Handlers}>
|
||||||
|
<Handle
|
||||||
|
type="source"
|
||||||
|
className={clsx(classes.Handle, classes.HandleTop, {
|
||||||
|
[classes.selected]: nodeVars.selected,
|
||||||
|
[classes.Tick]: nodeVars.isThickConnections,
|
||||||
|
})}
|
||||||
|
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
|
||||||
|
position={Position.Top}
|
||||||
|
id="a"
|
||||||
|
/>
|
||||||
|
<Handle
|
||||||
|
type="source"
|
||||||
|
className={clsx(classes.Handle, classes.HandleRight, {
|
||||||
|
[classes.selected]: nodeVars.selected,
|
||||||
|
[classes.Tick]: nodeVars.isThickConnections,
|
||||||
|
})}
|
||||||
|
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
|
||||||
|
position={Position.Right}
|
||||||
|
id="b"
|
||||||
|
/>
|
||||||
|
<Handle
|
||||||
|
type="source"
|
||||||
|
className={clsx(classes.Handle, classes.HandleBottom, {
|
||||||
|
[classes.selected]: nodeVars.selected,
|
||||||
|
[classes.Tick]: nodeVars.isThickConnections,
|
||||||
|
})}
|
||||||
|
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
|
||||||
|
position={Position.Bottom}
|
||||||
|
id="c"
|
||||||
|
/>
|
||||||
|
<Handle
|
||||||
|
type="source"
|
||||||
|
className={clsx(classes.Handle, classes.HandleLeft, {
|
||||||
|
[classes.selected]: nodeVars.selected,
|
||||||
|
[classes.Tick]: nodeVars.isThickConnections,
|
||||||
|
})}
|
||||||
|
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
|
||||||
|
position={Position.Left}
|
||||||
|
id="d"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
SolarSystemNodeDefault.displayName = 'SolarSystemNodeDefault';
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
@import './SolarSystemNodeDefault.module.scss';
|
||||||
|
|
||||||
|
/* ---------------------------------------------
|
||||||
|
Only override what's different from the base
|
||||||
|
Currently none required
|
||||||
|
---------------------------------------------- */
|
||||||
|
|
||||||
|
.RootCustomNode {
|
||||||
|
&.eve-system-status-home {
|
||||||
|
border: 1px solid var(--eve-solar-system-status-color-home-dark30);
|
||||||
|
background-image: linear-gradient(
|
||||||
|
275deg,
|
||||||
|
var(--eve-solar-system-status-home),
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
&.selected {
|
||||||
|
border-color: var(--eve-solar-system-status-color-home);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
import { memo } from 'react';
|
||||||
|
import { MapSolarSystemType } from '../../map.types';
|
||||||
|
import { Handle, NodeProps, Position } from 'reactflow';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import classes from './SolarSystemNodeTheme.module.scss';
|
||||||
|
import { PrimeIcons } from 'primereact/api';
|
||||||
|
import { useLocalCounter, useNodeKillsCount, useSolarSystemNode } from '../../hooks';
|
||||||
|
import {
|
||||||
|
EFFECT_BACKGROUND_STYLES,
|
||||||
|
MARKER_BOOKMARK_BG_STYLES,
|
||||||
|
STATUS_CLASSES,
|
||||||
|
} from '@/hooks/Mapper/components/map/constants';
|
||||||
|
import { WormholeClassComp } from '@/hooks/Mapper/components/map/components/WormholeClassComp';
|
||||||
|
import { UnsplashedSignature } from '@/hooks/Mapper/components/map/components/UnsplashedSignature';
|
||||||
|
import { TooltipPosition, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
|
||||||
|
import { TooltipSize } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper/utils.ts';
|
||||||
|
import { LocalCounter } from '@/hooks/Mapper/components/map/components/LocalCounter';
|
||||||
|
import { KillsCounter } from '@/hooks/Mapper/components/map/components/KillsCounter';
|
||||||
|
|
||||||
|
// let render = 0;
|
||||||
|
export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>) => {
|
||||||
|
const nodeVars = useSolarSystemNode(props);
|
||||||
|
const { localCounterCharacters } = useLocalCounter(nodeVars);
|
||||||
|
const { killsCount: localKillsCount, killsActivityType: localKillsActivityType } = useNodeKillsCount(nodeVars.solarSystemId);
|
||||||
|
|
||||||
|
// console.log('JOipP', `render ${nodeVars.id}`, render++);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{nodeVars.visible && (
|
||||||
|
<div className={classes.Bookmarks}>
|
||||||
|
{nodeVars.isShattered && (
|
||||||
|
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.shattered, '!pr-[2px]')}>
|
||||||
|
<WdTooltipWrapper content="Shattered" position={TooltipPosition.top}>
|
||||||
|
<span className={clsx('block w-[10px] h-[10px]', classes.ShatteredIcon)} />
|
||||||
|
</WdTooltipWrapper>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{localKillsCount && localKillsCount > 0 && nodeVars.solarSystemId && localKillsActivityType && (
|
||||||
|
<KillsCounter
|
||||||
|
killsCount={localKillsCount}
|
||||||
|
systemId={nodeVars.solarSystemId}
|
||||||
|
size={TooltipSize.lg}
|
||||||
|
killsActivityType={localKillsActivityType}
|
||||||
|
className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[localKillsActivityType])}
|
||||||
|
>
|
||||||
|
<div className={clsx(classes.BookmarkWithIcon)}>
|
||||||
|
<span className={clsx(PrimeIcons.BOLT, classes.icon)} />
|
||||||
|
<span className={clsx(classes.text)}>{localKillsCount}</span>
|
||||||
|
</div>
|
||||||
|
</KillsCounter>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{nodeVars.labelCustom !== '' && (
|
||||||
|
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.custom)}>
|
||||||
|
<span className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]">{nodeVars.labelCustom}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{nodeVars.labelsInfo.map(x => (
|
||||||
|
<div key={x.id} className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[x.id])}>
|
||||||
|
{x.shortName}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
classes.RootCustomNode,
|
||||||
|
nodeVars.regionClass && classes[nodeVars.regionClass],
|
||||||
|
nodeVars.status !== undefined ? classes[STATUS_CLASSES[nodeVars.status]] : '',
|
||||||
|
{
|
||||||
|
[classes.selected]: nodeVars.selected,
|
||||||
|
[classes.rally]: nodeVars.isRally,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
onMouseDownCapture={e => nodeVars.dbClick(e)}
|
||||||
|
>
|
||||||
|
{nodeVars.visible && (
|
||||||
|
<>
|
||||||
|
<div className={classes.HeadRow}>
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
classes.classTitle,
|
||||||
|
nodeVars.classTitleColor,
|
||||||
|
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{nodeVars.classTitle ?? '-'}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{nodeVars.tag != null && nodeVars.tag !== '' && (
|
||||||
|
<div className={clsx(classes.TagTitle)}>{nodeVars.tag}</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
classes.classSystemName,
|
||||||
|
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] flex-grow overflow-hidden text-ellipsis whitespace-nowrap',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{nodeVars.systemName}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{nodeVars.isWormhole && (
|
||||||
|
<div className={classes.statics}>
|
||||||
|
{nodeVars.sortedStatics.map(whClass => (
|
||||||
|
<WormholeClassComp key={String(whClass)} id={String(whClass)} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{nodeVars.effectName !== null && nodeVars.isWormhole && (
|
||||||
|
<div className={clsx(classes.effect, EFFECT_BACKGROUND_STYLES[nodeVars.effectName])} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={clsx(classes.BottomRow, 'flex items-center justify-between')}>
|
||||||
|
{nodeVars.customName && (
|
||||||
|
<div className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] whitespace-nowrap overflow-hidden text-ellipsis mr-0.5">
|
||||||
|
{nodeVars.customName}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!nodeVars.isWormhole && !nodeVars.customName && (
|
||||||
|
<div className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] whitespace-nowrap overflow-hidden text-ellipsis mr-0.5">
|
||||||
|
{nodeVars.regionName}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{nodeVars.isWormhole && !nodeVars.customName && <div />}
|
||||||
|
|
||||||
|
<div className="flex items-center gap-1 justify-end">
|
||||||
|
<div className={clsx('flex items-center gap-1')}>
|
||||||
|
{nodeVars.locked && <i className={clsx(PrimeIcons.LOCK, classes.lockIcon)} />}
|
||||||
|
{nodeVars.hubs.includes(nodeVars.solarSystemId) && (
|
||||||
|
<i className={clsx(PrimeIcons.MAP_MARKER, classes.mapMarker)} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<LocalCounter
|
||||||
|
hasUserCharacters={nodeVars.hasUserCharacters}
|
||||||
|
localCounterCharacters={localCounterCharacters}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{nodeVars.visible && (
|
||||||
|
<>
|
||||||
|
{nodeVars.unsplashedLeft.length > 0 && (
|
||||||
|
<div className={classes.Unsplashed}>
|
||||||
|
{nodeVars.unsplashedLeft.map(sig => (
|
||||||
|
<UnsplashedSignature key={sig.eve_id} signature={sig} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{nodeVars.unsplashedRight.length > 0 && (
|
||||||
|
<div className={clsx(classes.Unsplashed, classes['Unsplashed--right'])}>
|
||||||
|
{nodeVars.unsplashedRight.map(sig => (
|
||||||
|
<UnsplashedSignature key={sig.eve_id} signature={sig} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={classes.Handlers}>
|
||||||
|
<Handle
|
||||||
|
type="source"
|
||||||
|
className={clsx(classes.Handle, classes.HandleTop, {
|
||||||
|
[classes.selected]: nodeVars.selected,
|
||||||
|
[classes.Tick]: nodeVars.isThickConnections,
|
||||||
|
})}
|
||||||
|
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
|
||||||
|
position={Position.Top}
|
||||||
|
id="a"
|
||||||
|
/>
|
||||||
|
<Handle
|
||||||
|
type="source"
|
||||||
|
className={clsx(classes.Handle, classes.HandleRight, {
|
||||||
|
[classes.selected]: nodeVars.selected,
|
||||||
|
[classes.Tick]: nodeVars.isThickConnections,
|
||||||
|
})}
|
||||||
|
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
|
||||||
|
position={Position.Right}
|
||||||
|
id="b"
|
||||||
|
/>
|
||||||
|
<Handle
|
||||||
|
type="source"
|
||||||
|
className={clsx(classes.Handle, classes.HandleBottom, {
|
||||||
|
[classes.selected]: nodeVars.selected,
|
||||||
|
[classes.Tick]: nodeVars.isThickConnections,
|
||||||
|
})}
|
||||||
|
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
|
||||||
|
position={Position.Bottom}
|
||||||
|
id="c"
|
||||||
|
/>
|
||||||
|
<Handle
|
||||||
|
type="source"
|
||||||
|
className={clsx(classes.Handle, classes.HandleLeft, {
|
||||||
|
[classes.selected]: nodeVars.selected,
|
||||||
|
[classes.Tick]: nodeVars.isThickConnections,
|
||||||
|
})}
|
||||||
|
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
|
||||||
|
position={Position.Left}
|
||||||
|
id="d"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
SolarSystemNodeTheme.displayName = 'SolarSystemNodeTheme';
|
||||||
@@ -1 +1,2 @@
|
|||||||
export * from './SolarSystemNode';
|
export * from './SolarSystemNodeDefault';
|
||||||
|
export * from './SolarSystemNodeTheme';
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
@import '@/hooks/Mapper/components/map/styles/eve-common-variables';
|
||||||
|
|
||||||
|
.Signature {
|
||||||
|
position: relative;
|
||||||
|
top: 3px;
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
& > .Box {
|
||||||
|
width: 13px;
|
||||||
|
height: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--text-color);
|
||||||
|
font-size: 8px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bolder;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .Eol {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||||
|
import { InfoDrawer } from '@/hooks/Mapper/components/ui-kit';
|
||||||
|
|
||||||
|
import classes from './UnsplashedSignature.module.scss';
|
||||||
|
import { SystemSignature } from '@/hooks/Mapper/types/signatures';
|
||||||
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
|
import { WORMHOLE_CLASS_STYLES, WORMHOLES_ADDITIONAL_INFO } from '@/hooks/Mapper/components/map/constants.ts';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { renderInfoColumn } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
|
||||||
|
import { K162_TYPES_MAP } from '@/hooks/Mapper/constants.ts';
|
||||||
|
import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo.ts';
|
||||||
|
|
||||||
|
interface UnsplashedSignatureProps {
|
||||||
|
signature: SystemSignature;
|
||||||
|
}
|
||||||
|
export const UnsplashedSignature = ({ signature }: UnsplashedSignatureProps) => {
|
||||||
|
const {
|
||||||
|
data: { wormholesData },
|
||||||
|
} = useMapRootState();
|
||||||
|
|
||||||
|
const whData = useMemo(() => wormholesData[signature.type], [signature.type, wormholesData]);
|
||||||
|
const whClass = useMemo(() => (whData ? WORMHOLES_ADDITIONAL_INFO[whData.dest] : null), [whData]);
|
||||||
|
|
||||||
|
const customInfo = useMemo(() => {
|
||||||
|
return parseSignatureCustomInfo(signature.custom_info);
|
||||||
|
}, [signature]);
|
||||||
|
|
||||||
|
const k162TypeOption = useMemo(() => {
|
||||||
|
if (!customInfo?.k162Type) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return K162_TYPES_MAP[customInfo.k162Type];
|
||||||
|
}, [customInfo]);
|
||||||
|
|
||||||
|
const isEOL = useMemo(() => {
|
||||||
|
return customInfo?.isEOL;
|
||||||
|
}, [customInfo]);
|
||||||
|
|
||||||
|
const whClassStyle = useMemo(() => {
|
||||||
|
if (signature.type === 'K162' && k162TypeOption) {
|
||||||
|
const k162Data = wormholesData[k162TypeOption.whClassName];
|
||||||
|
const k162Class = k162Data ? WORMHOLES_ADDITIONAL_INFO[k162Data.dest] : null;
|
||||||
|
return k162Class ? WORMHOLE_CLASS_STYLES[k162Class.wormholeClassID] : '';
|
||||||
|
}
|
||||||
|
return whClass ? WORMHOLE_CLASS_STYLES[whClass.wormholeClassID] : '';
|
||||||
|
}, [signature, whClass, k162TypeOption, wormholesData]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<WdTooltipWrapper
|
||||||
|
className={clsx(classes.Signature)}
|
||||||
|
// @ts-ignore
|
||||||
|
content={
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<InfoDrawer title={<b className="text-slate-50">{signature.eve_id}</b>}>
|
||||||
|
{renderInfoColumn(signature)}
|
||||||
|
</InfoDrawer>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className={clsx(classes.Box, whClassStyle)}>
|
||||||
|
<svg width="13" height="8" viewBox="0 0 13 8" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect y="1" width="13" height="4" rx="2" className={whClassStyle} fill="currentColor" />
|
||||||
|
{isEOL && <rect x="4" width="5" height="6" rx="1" className={clsx(classes.Eol)} fill="#a153ac" />}
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</WdTooltipWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './UnsplashedSignature.tsx';
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { MassState } from '@/hooks/Mapper/types';
|
import { ConnectionType, MassState, ShipSizeStatus } from '@/hooks/Mapper/types';
|
||||||
|
|
||||||
export enum SOLAR_SYSTEM_CLASS_IDS {
|
export enum SOLAR_SYSTEM_CLASS_IDS {
|
||||||
ccp1 = -1,
|
ccp1 = -1,
|
||||||
@@ -16,7 +16,7 @@ export enum SOLAR_SYSTEM_CLASS_IDS {
|
|||||||
thera = 12,
|
thera = 12,
|
||||||
c13 = 13,
|
c13 = 13,
|
||||||
sentinel = 14,
|
sentinel = 14,
|
||||||
baribican = 15,
|
barbican = 15,
|
||||||
vidette = 16,
|
vidette = 16,
|
||||||
conflux = 17,
|
conflux = 17,
|
||||||
redoubt = 18,
|
redoubt = 18,
|
||||||
@@ -82,7 +82,7 @@ export const SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS = {
|
|||||||
thera: SOLAR_SYSTEM_CLASS_GROUPS.thera,
|
thera: SOLAR_SYSTEM_CLASS_GROUPS.thera,
|
||||||
c13: SOLAR_SYSTEM_CLASS_GROUPS.c13,
|
c13: SOLAR_SYSTEM_CLASS_GROUPS.c13,
|
||||||
sentinel: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
sentinel: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
||||||
baribican: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
barbican: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
||||||
vidette: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
vidette: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
||||||
conflux: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
conflux: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
||||||
redoubt: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
redoubt: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
||||||
@@ -217,7 +217,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
|
|||||||
wormholeClassID: 14,
|
wormholeClassID: 14,
|
||||||
effectPower: 2,
|
effectPower: 2,
|
||||||
title: 'Class 14 (Sentinel Drifter)',
|
title: 'Class 14 (Sentinel Drifter)',
|
||||||
shortTitle: 'Sentinel',
|
shortTitle: 'Sentinel MZ',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'barbican',
|
id: 'barbican',
|
||||||
@@ -225,7 +225,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
|
|||||||
wormholeClassID: 15,
|
wormholeClassID: 15,
|
||||||
effectPower: 2,
|
effectPower: 2,
|
||||||
title: 'Class 15 (Barbican Drifter)',
|
title: 'Class 15 (Barbican Drifter)',
|
||||||
shortTitle: 'Barbican',
|
shortTitle: 'Liberated Barbican',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'vidette',
|
id: 'vidette',
|
||||||
@@ -233,7 +233,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
|
|||||||
wormholeClassID: 16,
|
wormholeClassID: 16,
|
||||||
effectPower: 2,
|
effectPower: 2,
|
||||||
title: 'Class 16 (Vidette Drifter)',
|
title: 'Class 16 (Vidette Drifter)',
|
||||||
shortTitle: 'Vidette',
|
shortTitle: 'Sanctified Vidette',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'conflux',
|
id: 'conflux',
|
||||||
@@ -241,7 +241,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
|
|||||||
wormholeClassID: 17,
|
wormholeClassID: 17,
|
||||||
effectPower: 2,
|
effectPower: 2,
|
||||||
title: 'Class 17 (Conflux Drifter)',
|
title: 'Class 17 (Conflux Drifter)',
|
||||||
shortTitle: 'Conflux',
|
shortTitle: 'Conflux Eyrie',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'redoubt',
|
id: 'redoubt',
|
||||||
@@ -249,7 +249,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
|
|||||||
wormholeClassID: 18,
|
wormholeClassID: 18,
|
||||||
effectPower: 2,
|
effectPower: 2,
|
||||||
title: 'Class 18 (Redoubt Drifter)',
|
title: 'Class 18 (Redoubt Drifter)',
|
||||||
shortTitle: 'Redoubt',
|
shortTitle: 'Azdaja Redoubt',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'a1',
|
id: 'a1',
|
||||||
@@ -322,6 +322,9 @@ export const WORMHOLES_ADDITIONAL_INFO: Record<string, WormholesAdditionalInfoTy
|
|||||||
export const WORMHOLES_ADDITIONAL_INFO_BY_CLASS_ID: Record<string, WormholesAdditionalInfoType> =
|
export const WORMHOLES_ADDITIONAL_INFO_BY_CLASS_ID: Record<string, WormholesAdditionalInfoType> =
|
||||||
WORMHOLES_ADDITIONAL_INFO_RAW.reduce((acc, x) => ({ ...acc, [x.wormholeClassID]: x }), {});
|
WORMHOLES_ADDITIONAL_INFO_RAW.reduce((acc, x) => ({ ...acc, [x.wormholeClassID]: x }), {});
|
||||||
|
|
||||||
|
export const WORMHOLES_ADDITIONAL_INFO_BY_SHORT_NAME: Record<string, WormholesAdditionalInfoType> =
|
||||||
|
WORMHOLES_ADDITIONAL_INFO_RAW.reduce((acc, x) => ({ ...acc, [x.shortName]: x }), {});
|
||||||
|
|
||||||
// export const SOLAR_SYSTEM_CLASS_NAMES = {
|
// export const SOLAR_SYSTEM_CLASS_NAMES = {
|
||||||
// ccp1 = ,
|
// ccp1 = ,
|
||||||
// c1 = ,
|
// c1 = ,
|
||||||
@@ -650,6 +653,7 @@ export enum LABELS {
|
|||||||
l3 = '3',
|
l3 = '3',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export const LABELS_INFO: Record<string, any> = {
|
export const LABELS_INFO: Record<string, any> = {
|
||||||
[LABELS.clear]: { id: 'clear', name: 'Clear', shortName: '', icon: '' },
|
[LABELS.clear]: { id: 'clear', name: 'Clear', shortName: '', icon: '' },
|
||||||
[LABELS.la]: { id: 'la', name: 'Label A', shortName: 'A', icon: '' },
|
[LABELS.la]: { id: 'la', name: 'Label A', shortName: 'A', icon: '' },
|
||||||
@@ -712,6 +716,13 @@ export const STATUS_CLASSES: Record<number, string> = {
|
|||||||
[STATUSES.dangerous]: 'eve-system-status-dangerous',
|
[STATUSES.dangerous]: 'eve-system-status-dangerous',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const TYPE_NAMES_ORDER = [ConnectionType.wormhole, ConnectionType.gate];
|
||||||
|
|
||||||
|
export const TYPE_NAMES = {
|
||||||
|
[ConnectionType.wormhole]: 'Wormhole',
|
||||||
|
[ConnectionType.gate]: 'Gate',
|
||||||
|
};
|
||||||
|
|
||||||
export const MASS_STATE_NAMES_ORDER = [MassState.verge, MassState.half, MassState.normal];
|
export const MASS_STATE_NAMES_ORDER = [MassState.verge, MassState.half, MassState.normal];
|
||||||
|
|
||||||
export const MASS_STATE_NAMES = {
|
export const MASS_STATE_NAMES = {
|
||||||
@@ -720,16 +731,52 @@ export const MASS_STATE_NAMES = {
|
|||||||
[MassState.verge]: 'Verge of collapse',
|
[MassState.verge]: 'Verge of collapse',
|
||||||
};
|
};
|
||||||
|
|
||||||
// export const SHIP_SIZES_NAMES_ORDER = [
|
export const SHIP_SIZES_NAMES_ORDER = [
|
||||||
// ShipSizeStatus.small,
|
ShipSizeStatus.small,
|
||||||
// ShipSizeStatus.normal,
|
ShipSizeStatus.medium,
|
||||||
// // ShipSizeStatus.large,
|
ShipSizeStatus.large,
|
||||||
// // ShipSizeStatus.capital,
|
ShipSizeStatus.freight,
|
||||||
// ];
|
ShipSizeStatus.capital,
|
||||||
//
|
];
|
||||||
// export const SHIP_SIZES_NAMES = {
|
|
||||||
// [ShipSizeStatus.small]: 'Frigate',
|
export const SHIP_SIZES_NAMES = {
|
||||||
// [ShipSizeStatus.normal]: 'Normal',
|
[ShipSizeStatus.small]: 'Frigate',
|
||||||
// // [ShipSizeStatus.large]: 'Normal',
|
[ShipSizeStatus.medium]: 'Medium',
|
||||||
// // [ShipSizeStatus.capital]: 'Normal',
|
[ShipSizeStatus.large]: 'Normal',
|
||||||
// };
|
[ShipSizeStatus.freight]: 'Huge',
|
||||||
|
[ShipSizeStatus.capital]: 'Capital',
|
||||||
|
};
|
||||||
|
export const SHIP_SIZES_SIZE = {
|
||||||
|
[ShipSizeStatus.small]: '5K',
|
||||||
|
[ShipSizeStatus.medium]: '62K',
|
||||||
|
[ShipSizeStatus.large]: '375K',
|
||||||
|
[ShipSizeStatus.freight]: '1M',
|
||||||
|
[ShipSizeStatus.capital]: '2M',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SHIP_MASSES_SIZE: Record<number, ShipSizeStatus> = {
|
||||||
|
5_000_000: ShipSizeStatus.small,
|
||||||
|
62_000_000: ShipSizeStatus.medium,
|
||||||
|
300_000_000: ShipSizeStatus.large,
|
||||||
|
375_000_000: ShipSizeStatus.large,
|
||||||
|
1_000_000_000: ShipSizeStatus.freight,
|
||||||
|
1_350_000_000: ShipSizeStatus.capital,
|
||||||
|
1_800_000_000: ShipSizeStatus.capital,
|
||||||
|
2_000_000_000: ShipSizeStatus.capital,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SHIP_SIZES_DESCRIPTION = {
|
||||||
|
[ShipSizeStatus.small]: 'Frigate wormhole - up to Destroyer | 5K t.',
|
||||||
|
[ShipSizeStatus.medium]: 'Cruise wormhole - up to Battlecruiser | 62K t.',
|
||||||
|
[ShipSizeStatus.large]: 'Large wormhole - up to Battleship | 375K t.',
|
||||||
|
[ShipSizeStatus.freight]: 'Huge wormhole - up to Freighter | 1M t.',
|
||||||
|
[ShipSizeStatus.capital]: 'Capital wormhole - up to Capital | 2M t.',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SHIP_SIZES_NAMES_SHORT = {
|
||||||
|
[ShipSizeStatus.small]: 'S',
|
||||||
|
[ShipSizeStatus.medium]: 'M',
|
||||||
|
[ShipSizeStatus.large]: 'L',
|
||||||
|
[ShipSizeStatus.freight]: 'H',
|
||||||
|
[ShipSizeStatus.capital]: 'XL',
|
||||||
|
};
|
||||||
|
|||||||
@@ -10,5 +10,6 @@ export const convertSystem2Node = (sys: SolarSystemRawType): Node => {
|
|||||||
position: sys.position,
|
position: sys.position,
|
||||||
data: sys,
|
data: sys,
|
||||||
draggable: !sys.locked,
|
draggable: !sys.locked,
|
||||||
|
deletable: !sys.locked,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { SolarSystemNodeDefault, SolarSystemNodeTheme } from '../components/SolarSystemNode';
|
||||||
|
import type { NodeProps } from 'reactflow';
|
||||||
|
import type { ComponentType } from 'react';
|
||||||
|
import { MapSolarSystemType } from '../map.types';
|
||||||
|
import { ConnectionMode } from 'reactflow';
|
||||||
|
|
||||||
|
export type SolarSystemNodeComponent = ComponentType<NodeProps<MapSolarSystemType>>;
|
||||||
|
|
||||||
|
interface ThemeBehavior {
|
||||||
|
isPanAndDrag: boolean;
|
||||||
|
nodeComponent: SolarSystemNodeComponent;
|
||||||
|
connectionMode: ConnectionMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const THEME_BEHAVIORS: {
|
||||||
|
[key: string]: ThemeBehavior;
|
||||||
|
} = {
|
||||||
|
default: {
|
||||||
|
isPanAndDrag: false,
|
||||||
|
nodeComponent: SolarSystemNodeDefault,
|
||||||
|
connectionMode: ConnectionMode.Loose,
|
||||||
|
},
|
||||||
|
pathfinder: {
|
||||||
|
isPanAndDrag: true,
|
||||||
|
nodeComponent: SolarSystemNodeTheme,
|
||||||
|
connectionMode: ConnectionMode.Loose,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getBehaviorForTheme(themeName: string) {
|
||||||
|
return THEME_BEHAVIORS[themeName] ?? THEME_BEHAVIORS.default;
|
||||||
|
}
|
||||||
@@ -3,3 +3,4 @@ export * from './convertSystem2Node';
|
|||||||
export * from './getSystemClassStyles';
|
export * from './getSystemClassStyles';
|
||||||
export * from './getShapeClass';
|
export * from './getShapeClass';
|
||||||
export * from './getBackgroundClass';
|
export * from './getBackgroundClass';
|
||||||
|
export * from './prepareUnsplashedChunks';
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export const isWormholeSpace = (wormholeClassID: number) => {
|
|||||||
case SOLAR_SYSTEM_CLASS_IDS.c6:
|
case SOLAR_SYSTEM_CLASS_IDS.c6:
|
||||||
case SOLAR_SYSTEM_CLASS_IDS.c13:
|
case SOLAR_SYSTEM_CLASS_IDS.c13:
|
||||||
case SOLAR_SYSTEM_CLASS_IDS.thera:
|
case SOLAR_SYSTEM_CLASS_IDS.thera:
|
||||||
case SOLAR_SYSTEM_CLASS_IDS.baribican:
|
case SOLAR_SYSTEM_CLASS_IDS.barbican:
|
||||||
case SOLAR_SYSTEM_CLASS_IDS.vidette:
|
case SOLAR_SYSTEM_CLASS_IDS.vidette:
|
||||||
case SOLAR_SYSTEM_CLASS_IDS.conflux:
|
case SOLAR_SYSTEM_CLASS_IDS.conflux:
|
||||||
case SOLAR_SYSTEM_CLASS_IDS.redoubt:
|
case SOLAR_SYSTEM_CLASS_IDS.redoubt:
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// Helper function to split an array into chunks of size
|
||||||
|
const chunkArray = (array: any[], size: number) => {
|
||||||
|
const chunks = [];
|
||||||
|
for (let i = 0; i < array.length; i += size) {
|
||||||
|
chunks.push(array.slice(i, i + size));
|
||||||
|
}
|
||||||
|
return chunks;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const prepareUnsplashedChunks = (items: any[]) => {
|
||||||
|
// Split the items into chunks of 4
|
||||||
|
const chunks = chunkArray(items, 4);
|
||||||
|
|
||||||
|
// Get the column elements
|
||||||
|
const leftColumn: any[] = [];
|
||||||
|
const rightColumn: any[] = [];
|
||||||
|
|
||||||
|
chunks.forEach((chunk, index) => {
|
||||||
|
const column = index % 2 === 0 ? leftColumn : rightColumn;
|
||||||
|
|
||||||
|
chunk.forEach(item => {
|
||||||
|
column.push(item);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return [leftColumn, rightColumn];
|
||||||
|
};
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
||||||
import { useCallback, useRef } from 'react';
|
|
||||||
import {
|
import {
|
||||||
CommandCharacterAdded,
|
CommandCharacterAdded,
|
||||||
CommandCharacterRemoved,
|
CommandCharacterRemoved,
|
||||||
@@ -7,6 +6,7 @@ import {
|
|||||||
CommandCharacterUpdated,
|
CommandCharacterUpdated,
|
||||||
CommandPresentCharacters,
|
CommandPresentCharacters,
|
||||||
} from '@/hooks/Mapper/types';
|
} from '@/hooks/Mapper/types';
|
||||||
|
import { useCallback, useRef } from 'react';
|
||||||
|
|
||||||
export const useCommandsCharacters = () => {
|
export const useCommandsCharacters = () => {
|
||||||
const { update } = useMapState();
|
const { update } = useMapState();
|
||||||
|
|||||||
@@ -2,17 +2,24 @@ import { Node, useReactFlow } from 'reactflow';
|
|||||||
import { useCallback, useRef } from 'react';
|
import { useCallback, useRef } from 'react';
|
||||||
import { CommandAddSystems } from '@/hooks/Mapper/types/mapHandlers.ts';
|
import { CommandAddSystems } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||||
import { convertSystem2Node } from '../../helpers';
|
import { convertSystem2Node } from '../../helpers';
|
||||||
|
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
|
||||||
|
|
||||||
export const useMapAddSystems = () => {
|
export const useMapAddSystems = () => {
|
||||||
const rf = useReactFlow();
|
const rf = useReactFlow();
|
||||||
|
|
||||||
|
const { addSystemStatic } = useLoadSystemStatic({ systems: [] });
|
||||||
|
|
||||||
const ref = useRef({ rf });
|
const ref = useRef({ rf });
|
||||||
ref.current = { rf };
|
ref.current = { rf };
|
||||||
|
|
||||||
return useCallback((systems: CommandAddSystems) => {
|
return useCallback((systems: CommandAddSystems) => {
|
||||||
const { rf } = ref.current;
|
const { rf } = ref.current;
|
||||||
const nodes = rf.getNodes();
|
const nodes = rf.getNodes();
|
||||||
const prepared: Node[] = systems.filter(x => !nodes.some(y => x.id === y.id)).map(convertSystem2Node);
|
|
||||||
|
const newSystems = systems.filter(x => !nodes.some(y => x.id === y.id));
|
||||||
|
newSystems.forEach(x => addSystemStatic(x.system_static_info));
|
||||||
|
|
||||||
|
const prepared: Node[] = newSystems.map(convertSystem2Node);
|
||||||
rf.addNodes(prepared);
|
rf.addNodes(prepared);
|
||||||
}, []);
|
}, []);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { MapData, useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
import { MapData, useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
||||||
import { useCallback, useRef } from 'react';
|
|
||||||
import { CommandKillsUpdated, CommandMapUpdated } from '@/hooks/Mapper/types';
|
import { CommandKillsUpdated, CommandMapUpdated } from '@/hooks/Mapper/types';
|
||||||
|
import { useCallback, useRef } from 'react';
|
||||||
|
|
||||||
export const useMapCommands = () => {
|
export const useMapCommands = () => {
|
||||||
const { update } = useMapState();
|
const { update } = useMapState();
|
||||||
@@ -8,13 +8,21 @@ export const useMapCommands = () => {
|
|||||||
const ref = useRef({ update });
|
const ref = useRef({ update });
|
||||||
ref.current = { update };
|
ref.current = { update };
|
||||||
|
|
||||||
const mapUpdated = useCallback(({ hubs }: CommandMapUpdated) => {
|
const mapUpdated = useCallback(({ hubs, system_signatures, kills }: CommandMapUpdated) => {
|
||||||
const out: Partial<MapData> = {};
|
const out: Partial<MapData> = {};
|
||||||
|
|
||||||
if (hubs) {
|
if (hubs) {
|
||||||
out.hubs = hubs;
|
out.hubs = hubs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (system_signatures) {
|
||||||
|
out.systemSignatures = system_signatures;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kills) {
|
||||||
|
out.kills = kills.reduce((acc, x) => ({ ...acc, [x.solar_system_id]: x.kills }), {});
|
||||||
|
}
|
||||||
|
|
||||||
ref.current.update(out);
|
ref.current.update(out);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useReactFlow } from 'reactflow';
|
|
||||||
import { useCallback, useRef } from 'react';
|
|
||||||
import { CommandInit } from '@/hooks/Mapper/types/mapHandlers.ts';
|
|
||||||
import { convertConnection2Edge, convertSystem2Node } from '../../helpers';
|
|
||||||
import { MapData, useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
import { MapData, useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
||||||
|
import { CommandInit } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||||
|
import { useCallback, useRef } from 'react';
|
||||||
|
import { useReactFlow } from 'reactflow';
|
||||||
|
import { convertConnection2Edge, convertSystem2Node } from '../../helpers';
|
||||||
|
|
||||||
export const useMapInit = () => {
|
export const useMapInit = () => {
|
||||||
const rf = useReactFlow();
|
const rf = useReactFlow();
|
||||||
@@ -14,6 +14,7 @@ export const useMapInit = () => {
|
|||||||
return useCallback(
|
return useCallback(
|
||||||
({
|
({
|
||||||
systems,
|
systems,
|
||||||
|
system_signatures,
|
||||||
kills,
|
kills,
|
||||||
connections,
|
connections,
|
||||||
wormholes,
|
wormholes,
|
||||||
@@ -51,6 +52,10 @@ export const useMapInit = () => {
|
|||||||
updateData.systems = systems;
|
updateData.systems = systems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (system_signatures) {
|
||||||
|
updateData.systemSignatures = system_signatures;
|
||||||
|
}
|
||||||
|
|
||||||
if (kills) {
|
if (kills) {
|
||||||
updateData.kills = kills.reduce((acc, x) => ({ ...acc, [x.solar_system_id]: x.kills }), {});
|
updateData.kills = kills.reduce((acc, x) => ({ ...acc, [x.solar_system_id]: x.kills }), {});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,24 +5,21 @@ import { OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts
|
|||||||
|
|
||||||
export const useMapRemoveSystems = (onSelectionChange: OnMapSelectionChange) => {
|
export const useMapRemoveSystems = (onSelectionChange: OnMapSelectionChange) => {
|
||||||
const rf = useReactFlow();
|
const rf = useReactFlow();
|
||||||
const ref = useRef({ onSelectionChange });
|
const ref = useRef({ onSelectionChange, rf });
|
||||||
ref.current = { onSelectionChange };
|
ref.current = { onSelectionChange, rf };
|
||||||
|
|
||||||
return useCallback(
|
return useCallback((systems: CommandRemoveSystems) => {
|
||||||
(systems: CommandRemoveSystems) => {
|
ref.current.rf.deleteElements({ nodes: systems.map(x => ({ id: `${x}` })) });
|
||||||
rf.deleteElements({ nodes: systems.map(x => ({ id: `${x}` })) });
|
|
||||||
|
|
||||||
const newSelection = rf
|
const newSelection = ref.current.rf
|
||||||
.getNodes()
|
.getNodes()
|
||||||
.filter(x => !systems.includes(parseInt(x.id)))
|
.filter(x => !systems.includes(parseInt(x.id)))
|
||||||
.filter(x => x.selected)
|
.filter(x => x.selected)
|
||||||
.map(x => x.id);
|
.map(x => x.id);
|
||||||
|
|
||||||
ref.current.onSelectionChange({
|
ref.current.onSelectionChange({
|
||||||
systems: newSelection,
|
systems: newSelection,
|
||||||
connections: [],
|
connections: [],
|
||||||
});
|
});
|
||||||
},
|
}, []);
|
||||||
[rf],
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export const useMapUpdateSystems = () => {
|
|||||||
return newSystem;
|
return newSystem;
|
||||||
});
|
});
|
||||||
|
|
||||||
update({ systems: out });
|
update({ systems: out }, true);
|
||||||
},
|
},
|
||||||
[rf, update],
|
[rf, update],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,2 +1,11 @@
|
|||||||
export * from './useMapHandlers';
|
export * from './useMapHandlers';
|
||||||
export * from './useUpdateNodes';
|
export * from './useUpdateNodes';
|
||||||
|
export * from './useNodesEdgesState';
|
||||||
|
export * from './useBackgroundVars';
|
||||||
|
export * from './useKillsCounter';
|
||||||
|
export * from './useSystemName';
|
||||||
|
export * from './useNodesEdgesState';
|
||||||
|
export * from './useSolarSystemNode';
|
||||||
|
export * from './useUnsplashedSignatures';
|
||||||
|
export * from './useUpdateNodes';
|
||||||
|
export * from './useNodeKillsCount';
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { BackgroundVariant } from 'reactflow';
|
||||||
|
|
||||||
|
export function useBackgroundVars(themeName?: string) {
|
||||||
|
const [variant, setVariant] = useState<BackgroundVariant>(BackgroundVariant.Dots);
|
||||||
|
const [gap, setGap] = useState<number>(16);
|
||||||
|
const [size, setSize] = useState<number>(1);
|
||||||
|
const [color, setColor] = useState('#81818b');
|
||||||
|
const [snapSize, setSnapSize] = useState<number>(25);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// match any element whose entire `class` attribute ends with "-theme"
|
||||||
|
let themeEl = document.querySelector('[class$="-theme"]');
|
||||||
|
|
||||||
|
// If none is found, fall back to the <html> element
|
||||||
|
if (!themeEl) {
|
||||||
|
themeEl = document.documentElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
const style = getComputedStyle(themeEl as HTMLElement);
|
||||||
|
|
||||||
|
const rawVariant = style.getPropertyValue('--rf-bg-variant').replace(/['"]/g, '').trim().toLowerCase();
|
||||||
|
let finalVariant: BackgroundVariant = BackgroundVariant.Dots;
|
||||||
|
|
||||||
|
if (rawVariant === 'lines') {
|
||||||
|
finalVariant = BackgroundVariant.Lines;
|
||||||
|
} else if (rawVariant === 'cross') {
|
||||||
|
finalVariant = BackgroundVariant.Cross;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cssVarGap = style.getPropertyValue('--rf-bg-gap');
|
||||||
|
const cssVarSize = style.getPropertyValue('--rf-bg-size');
|
||||||
|
const cssVarSnapSize = style.getPropertyValue('--rf-snap-size');
|
||||||
|
const cssColor = style.getPropertyValue('--rf-bg-pattern-color');
|
||||||
|
|
||||||
|
const gapNum = parseInt(cssVarGap, 10) || 16;
|
||||||
|
const sizeNum = parseInt(cssVarSize, 10) || 1;
|
||||||
|
const snapSize = parseInt(cssVarSnapSize, 10) || 25; //react-flow default
|
||||||
|
|
||||||
|
setVariant(finalVariant);
|
||||||
|
setGap(gapNum);
|
||||||
|
setSize(sizeNum);
|
||||||
|
setColor(cssColor);
|
||||||
|
setSnapSize(snapSize);
|
||||||
|
}, [themeName]);
|
||||||
|
|
||||||
|
return { variant, gap, size, color, snapSize };
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
|
import { useSystemKills } from '@/hooks/Mapper/components/mapInterface/widgets/WSystemKills/hooks/useSystemKills.ts';
|
||||||
|
|
||||||
|
interface UseKillsCounterProps {
|
||||||
|
realSystemId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useKillsCounter({ realSystemId }: UseKillsCounterProps) {
|
||||||
|
const { data: mapData, outCommand } = useMapRootState();
|
||||||
|
const { systems } = mapData;
|
||||||
|
|
||||||
|
const systemNameMap = useMemo(() => {
|
||||||
|
const m: Record<string, string> = {};
|
||||||
|
systems.forEach(sys => {
|
||||||
|
m[sys.id] = sys.temporary_name || sys.name || '???';
|
||||||
|
});
|
||||||
|
return m;
|
||||||
|
}, [systems]);
|
||||||
|
|
||||||
|
const { kills: allKills, isLoading } = useSystemKills({
|
||||||
|
systemId: realSystemId,
|
||||||
|
outCommand,
|
||||||
|
showAllVisible: false,
|
||||||
|
sinceHours: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const filteredKills = useMemo(() => {
|
||||||
|
if (!allKills || allKills.length === 0) return [];
|
||||||
|
|
||||||
|
// Sort kills by time, most recent first, but don't limit the number of kills
|
||||||
|
return [...allKills].sort((a, b) => {
|
||||||
|
const aTime = a.kill_time ? new Date(a.kill_time).getTime() : 0;
|
||||||
|
const bTime = b.kill_time ? new Date(b.kill_time).getTime() : 0;
|
||||||
|
return bTime - aTime;
|
||||||
|
});
|
||||||
|
}, [allKills]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isLoading,
|
||||||
|
kills: filteredKills,
|
||||||
|
systemNameMap,
|
||||||
|
};
|
||||||
|
}
|
||||||
31
assets/js/hooks/Mapper/components/map/hooks/useLabelsInfo.ts
Normal file
31
assets/js/hooks/Mapper/components/map/hooks/useLabelsInfo.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager';
|
||||||
|
import { LABELS_INFO, LABELS_ORDER } from '@/hooks/Mapper/components/map/constants';
|
||||||
|
interface UseLabelsInfoParams {
|
||||||
|
labels: string | null;
|
||||||
|
linkedSigPrefix: string | null;
|
||||||
|
isShowLinkedSigId: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LabelInfo = {
|
||||||
|
id: string;
|
||||||
|
shortName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function sortedLabels(labels: string[]): LabelInfo[] {
|
||||||
|
if (!labels) return [];
|
||||||
|
return LABELS_ORDER.filter(x => labels.includes(x)).map(x => LABELS_INFO[x] as LabelInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useLabelsInfo({ labels, linkedSigPrefix, isShowLinkedSigId }: UseLabelsInfoParams) {
|
||||||
|
const labelsManager = useMemo(() => new LabelsManager(labels ?? ''), [labels]);
|
||||||
|
const labelsInfo = useMemo(() => sortedLabels(labelsManager.list), [labelsManager]);
|
||||||
|
const labelCustom = useMemo(() => {
|
||||||
|
if (isShowLinkedSigId && linkedSigPrefix) {
|
||||||
|
return labelsManager.customLabel ? `${linkedSigPrefix}・${labelsManager.customLabel}` : linkedSigPrefix;
|
||||||
|
}
|
||||||
|
return labelsManager.customLabel;
|
||||||
|
}, [linkedSigPrefix, isShowLinkedSigId, labelsManager]);
|
||||||
|
|
||||||
|
return { labelsInfo, labelCustom };
|
||||||
|
}
|
||||||
@@ -19,8 +19,6 @@ import {
|
|||||||
MapHandlers,
|
MapHandlers,
|
||||||
} from '@/hooks/Mapper/types/mapHandlers.ts';
|
} from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||||
|
|
||||||
import { useMapEventListener } from '@/hooks/Mapper/events';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useCommandsCharacters,
|
useCommandsCharacters,
|
||||||
useCommandsConnections,
|
useCommandsConnections,
|
||||||
@@ -60,16 +58,19 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
|
|||||||
mapInit(data as CommandInit);
|
mapInit(data as CommandInit);
|
||||||
break;
|
break;
|
||||||
case Commands.addSystems:
|
case Commands.addSystems:
|
||||||
|
setTimeout(() => mapAddSystems(data as CommandAddSystems), 100);
|
||||||
break;
|
break;
|
||||||
case Commands.updateSystems:
|
case Commands.updateSystems:
|
||||||
mapUpdateSystems(data as CommandUpdateSystems);
|
mapUpdateSystems(data as CommandUpdateSystems);
|
||||||
break;
|
break;
|
||||||
case Commands.removeSystems:
|
case Commands.removeSystems:
|
||||||
|
setTimeout(() => removeSystems(data as CommandRemoveSystems), 100);
|
||||||
break;
|
break;
|
||||||
case Commands.addConnections:
|
case Commands.addConnections:
|
||||||
|
setTimeout(() => addConnections(data as CommandAddConnections), 100);
|
||||||
break;
|
break;
|
||||||
case Commands.removeConnections:
|
case Commands.removeConnections:
|
||||||
removeConnections(data as CommandRemoveConnections);
|
setTimeout(() => removeConnections(data as CommandRemoveConnections), 100);
|
||||||
break;
|
break;
|
||||||
case Commands.charactersUpdated:
|
case Commands.charactersUpdated:
|
||||||
charactersUpdated(data as CommandCharactersUpdated);
|
charactersUpdated(data as CommandCharactersUpdated);
|
||||||
@@ -111,15 +112,21 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
|
|||||||
connections: [],
|
connections: [],
|
||||||
});
|
});
|
||||||
selectSystem(systemId as CommandSelectSystem);
|
selectSystem(systemId as CommandSelectSystem);
|
||||||
}, 100);
|
}, 500);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Commands.pingAdded:
|
||||||
|
case Commands.pingCancelled:
|
||||||
case Commands.routes:
|
case Commands.routes:
|
||||||
// do nothing here
|
case Commands.signaturesUpdated:
|
||||||
break;
|
|
||||||
|
|
||||||
case Commands.linkSignatureToSystem:
|
case Commands.linkSignatureToSystem:
|
||||||
// do nothing here
|
case Commands.detailedKillsUpdated:
|
||||||
|
case Commands.characterActivityData:
|
||||||
|
case Commands.trackingCharactersData:
|
||||||
|
case Commands.updateActivity:
|
||||||
|
case Commands.updateTracking:
|
||||||
|
case Commands.userSettingsUpdated:
|
||||||
|
// do nothing
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -131,20 +138,4 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
|
|||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
useMapEventListener(event => {
|
|
||||||
switch (event.name) {
|
|
||||||
case Commands.addConnections:
|
|
||||||
addConnections(event.data as CommandAddConnections);
|
|
||||||
break;
|
|
||||||
case Commands.addSystems:
|
|
||||||
mapAddSystems(event.data as CommandAddSystems);
|
|
||||||
break;
|
|
||||||
case Commands.removeSystems:
|
|
||||||
removeSystems(event.data as CommandRemoveSystems);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import { useEffect, useState, useCallback, useMemo } from 'react';
|
||||||
|
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||||
|
import { Commands } from '@/hooks/Mapper/types';
|
||||||
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
|
|
||||||
|
interface Kill {
|
||||||
|
solar_system_id: number | string;
|
||||||
|
kills: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MapEvent {
|
||||||
|
name: Commands;
|
||||||
|
data?: unknown;
|
||||||
|
payload?: Kill[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActivityType(count: number): string {
|
||||||
|
if (count <= 5) return 'activityNormal';
|
||||||
|
if (count <= 30) return 'activityWarn';
|
||||||
|
return 'activityDanger';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useNodeKillsCount(systemId: number | string, initialKillsCount: number | null = null): { killsCount: number | null; killsActivityType: string | null } {
|
||||||
|
const [killsCount, setKillsCount] = useState<number | null>(initialKillsCount);
|
||||||
|
const { data: mapData } = useMapRootState();
|
||||||
|
const { detailedKills = {} } = mapData;
|
||||||
|
|
||||||
|
// Calculate 1-hour kill count from detailed kills
|
||||||
|
const oneHourKillCount = useMemo(() => {
|
||||||
|
const systemKills = detailedKills[systemId] || [];
|
||||||
|
|
||||||
|
// If we have detailed kills data (even if empty), use it for counting
|
||||||
|
if (Object.prototype.hasOwnProperty.call(detailedKills, systemId)) {
|
||||||
|
const oneHourAgo = Date.now() - 60 * 60 * 1000; // 1 hour in milliseconds
|
||||||
|
const recentKills = systemKills.filter(kill => {
|
||||||
|
if (!kill.kill_time) return false;
|
||||||
|
const killTime = new Date(kill.kill_time).getTime();
|
||||||
|
if (isNaN(killTime)) return false;
|
||||||
|
return killTime >= oneHourAgo;
|
||||||
|
});
|
||||||
|
|
||||||
|
return recentKills.length; // Return 0 if no recent kills, not null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return null only if we don't have detailed kills data for this system
|
||||||
|
return null;
|
||||||
|
}, [detailedKills, systemId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Always prefer the calculated 1-hour count over initial count
|
||||||
|
// This ensures we properly expire old kills
|
||||||
|
if (oneHourKillCount !== null) {
|
||||||
|
setKillsCount(oneHourKillCount);
|
||||||
|
} else if (detailedKills[systemId] && detailedKills[systemId].length === 0) {
|
||||||
|
// If we have detailed kills data but it's empty, set to 0
|
||||||
|
setKillsCount(0);
|
||||||
|
} else {
|
||||||
|
// Only fall back to initial count if we have no detailed kills data at all
|
||||||
|
setKillsCount(initialKillsCount);
|
||||||
|
}
|
||||||
|
}, [oneHourKillCount, initialKillsCount, detailedKills, systemId]);
|
||||||
|
|
||||||
|
const handleEvent = useCallback(
|
||||||
|
(event: MapEvent): boolean => {
|
||||||
|
if (event.name === Commands.killsUpdated && Array.isArray(event.payload)) {
|
||||||
|
const killForSystem = event.payload.find(kill => kill.solar_system_id.toString() === systemId.toString());
|
||||||
|
if (killForSystem && typeof killForSystem.kills === 'number') {
|
||||||
|
// Only update if we don't have detailed kills data
|
||||||
|
if (!detailedKills[systemId] || detailedKills[systemId].length === 0) {
|
||||||
|
setKillsCount(killForSystem.kills);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
[systemId, detailedKills],
|
||||||
|
);
|
||||||
|
|
||||||
|
useMapEventListener(handleEvent);
|
||||||
|
|
||||||
|
const killsActivityType = useMemo(() => {
|
||||||
|
return killsCount !== null && killsCount > 0 ? getActivityType(killsCount) : null;
|
||||||
|
}, [killsCount]);
|
||||||
|
|
||||||
|
return { killsCount, killsActivityType };
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { useState, useCallback, type Dispatch, type SetStateAction } from 'react';
|
||||||
|
|
||||||
|
import { applyNodeChanges, applyEdgeChanges } from '../utils/changes';
|
||||||
|
import { OnNodesChange, Edge, OnEdgesChange, Node } from 'reactflow';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook for managing the state of nodes - should only be used for prototyping / simple use cases.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @param initialNodes
|
||||||
|
* @returns an array [nodes, setNodes, onNodesChange]
|
||||||
|
*/
|
||||||
|
export function useNodesState<NodeType extends Node>(
|
||||||
|
initialNodes: NodeType[],
|
||||||
|
): [NodeType[], Dispatch<SetStateAction<NodeType[]>>, OnNodesChange] {
|
||||||
|
const [nodes, setNodes] = useState(initialNodes);
|
||||||
|
const onNodesChange: OnNodesChange = useCallback(changes => setNodes(nds => applyNodeChanges(changes, nds)), []);
|
||||||
|
|
||||||
|
return [nodes, setNodes, onNodesChange];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook for managing the state of edges - should only be used for prototyping / simple use cases.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @param initialEdges
|
||||||
|
* @returns an array [edges, setEdges, onEdgesChange]
|
||||||
|
*/
|
||||||
|
export function useEdgesState<EdgeType extends Edge = Edge>(
|
||||||
|
initialEdges: EdgeType[],
|
||||||
|
): [EdgeType[], Dispatch<SetStateAction<EdgeType[]>>, OnEdgesChange] {
|
||||||
|
const [edges, setEdges] = useState(initialEdges);
|
||||||
|
const onEdgesChange: OnEdgesChange = useCallback(changes => setEdges(eds => applyEdgeChanges(changes, eds)), []);
|
||||||
|
|
||||||
|
return [edges, setEdges, onEdgesChange];
|
||||||
|
}
|
||||||
@@ -0,0 +1,238 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import { MapSolarSystemType } from '../map.types';
|
||||||
|
import { NodeProps } from 'reactflow';
|
||||||
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
|
import { useMapGetOption } from '@/hooks/Mapper/mapRootProvider/hooks/api';
|
||||||
|
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider';
|
||||||
|
import { useDoubleClick } from '@/hooks/Mapper/hooks/useDoubleClick';
|
||||||
|
import { Regions, REGIONS_MAP, Spaces } from '@/hooks/Mapper/constants';
|
||||||
|
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace';
|
||||||
|
import { getSystemClassStyles } from '@/hooks/Mapper/components/map/helpers';
|
||||||
|
import { sortWHClasses } from '@/hooks/Mapper/helpers';
|
||||||
|
import { CharacterTypeRaw, OutCommand, PingType, SystemSignature } from '@/hooks/Mapper/types';
|
||||||
|
import { useUnsplashedSignatures } from './useUnsplashedSignatures';
|
||||||
|
import { useSystemName } from './useSystemName';
|
||||||
|
import { LabelInfo, useLabelsInfo } from './useLabelsInfo';
|
||||||
|
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
|
||||||
|
|
||||||
|
export interface SolarSystemNodeVars {
|
||||||
|
id: string;
|
||||||
|
selected: boolean;
|
||||||
|
visible: boolean;
|
||||||
|
isWormhole: boolean;
|
||||||
|
classTitleColor: string | null;
|
||||||
|
hasUserCharacters: boolean;
|
||||||
|
showHandlers: boolean;
|
||||||
|
regionClass: string | null;
|
||||||
|
systemName: string;
|
||||||
|
customName?: string | null;
|
||||||
|
labelCustom: string | null;
|
||||||
|
isShattered: boolean;
|
||||||
|
tag?: string | null;
|
||||||
|
status?: number;
|
||||||
|
labelsInfo: LabelInfo[];
|
||||||
|
dbClick: (event: React.MouseEvent<HTMLDivElement>) => void;
|
||||||
|
sortedStatics: Array<string | number>;
|
||||||
|
effectName: string | null;
|
||||||
|
regionName: string | null;
|
||||||
|
solarSystemId: string;
|
||||||
|
solarSystemName: string | null;
|
||||||
|
locked: boolean;
|
||||||
|
hubs: string[];
|
||||||
|
name: string | null;
|
||||||
|
isConnecting: boolean;
|
||||||
|
hoverNodeId: string | null;
|
||||||
|
charactersInSystem: Array<CharacterTypeRaw>;
|
||||||
|
userCharacters: string[];
|
||||||
|
unsplashedLeft: Array<SystemSignature>;
|
||||||
|
unsplashedRight: Array<SystemSignature>;
|
||||||
|
isThickConnections: boolean;
|
||||||
|
isRally: boolean;
|
||||||
|
classTitle: string | null;
|
||||||
|
temporaryName?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SpaceToClass: Record<string, string> = {
|
||||||
|
[Spaces.Caldari]: 'Caldaria',
|
||||||
|
[Spaces.Matar]: 'Mataria',
|
||||||
|
[Spaces.Amarr]: 'Amarria',
|
||||||
|
[Spaces.Gallente]: 'Gallente',
|
||||||
|
[Spaces.Pochven]: 'Pochven',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useLocalCounter(nodeVars: SolarSystemNodeVars) {
|
||||||
|
const localCounterCharacters = useMemo(() => {
|
||||||
|
return nodeVars.charactersInSystem
|
||||||
|
.map(char => ({
|
||||||
|
...char,
|
||||||
|
compact: true,
|
||||||
|
isOwn: nodeVars.userCharacters.includes(char.eve_id),
|
||||||
|
}))
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
}, [nodeVars.charactersInSystem, nodeVars.userCharacters]);
|
||||||
|
return { localCounterCharacters };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSolarSystemNode = (props: NodeProps<MapSolarSystemType>): SolarSystemNodeVars => {
|
||||||
|
const { id, data, selected } = props;
|
||||||
|
const {
|
||||||
|
id: solar_system_id,
|
||||||
|
locked,
|
||||||
|
name,
|
||||||
|
tag,
|
||||||
|
status,
|
||||||
|
labels,
|
||||||
|
temporary_name,
|
||||||
|
linked_sig_eve_id: linkedSigEveId = '',
|
||||||
|
} = data;
|
||||||
|
|
||||||
|
const {
|
||||||
|
storedSettings: { interfaceSettings },
|
||||||
|
data: { systemSignatures: mapSystemSignatures },
|
||||||
|
} = useMapRootState();
|
||||||
|
|
||||||
|
const systemStaticInfo = useMemo(() => {
|
||||||
|
return getSystemStaticInfo(solar_system_id)!;
|
||||||
|
}, [solar_system_id]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
system_class,
|
||||||
|
security,
|
||||||
|
class_title,
|
||||||
|
statics,
|
||||||
|
effect_name,
|
||||||
|
region_name,
|
||||||
|
region_id,
|
||||||
|
is_shattered,
|
||||||
|
solar_system_name,
|
||||||
|
constellation_name,
|
||||||
|
} = systemStaticInfo;
|
||||||
|
|
||||||
|
const { isShowUnsplashedSignatures } = interfaceSettings;
|
||||||
|
const isTempSystemNameEnabled = useMapGetOption('show_temp_system_name') === 'true';
|
||||||
|
const isShowLinkedSigId = useMapGetOption('show_linked_signature_id') === 'true';
|
||||||
|
const isShowLinkedSigIdTempName = useMapGetOption('show_linked_signature_id_temp_name') === 'true';
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: {
|
||||||
|
characters,
|
||||||
|
wormholesData,
|
||||||
|
hubs,
|
||||||
|
userCharacters,
|
||||||
|
isConnecting,
|
||||||
|
hoverNodeId,
|
||||||
|
visibleNodes,
|
||||||
|
showKSpaceBG,
|
||||||
|
isThickConnections,
|
||||||
|
pings,
|
||||||
|
},
|
||||||
|
outCommand,
|
||||||
|
} = useMapState();
|
||||||
|
|
||||||
|
const visible = useMemo(() => visibleNodes.has(id), [id, visibleNodes]);
|
||||||
|
|
||||||
|
const systemSigs = useMemo(() => mapSystemSignatures[solar_system_id] || [], [solar_system_id, mapSystemSignatures]);
|
||||||
|
|
||||||
|
const charactersInSystem = useMemo(() => {
|
||||||
|
return characters.filter(c => c.location?.solar_system_id === parseInt(solar_system_id) && c.online);
|
||||||
|
}, [characters, solar_system_id]);
|
||||||
|
|
||||||
|
const isWormhole = isWormholeSpace(system_class);
|
||||||
|
|
||||||
|
const classTitleColor = useMemo(
|
||||||
|
() => getSystemClassStyles({ systemClass: system_class, security }),
|
||||||
|
[security, system_class],
|
||||||
|
);
|
||||||
|
|
||||||
|
const sortedStatics = useMemo(() => sortWHClasses(wormholesData, statics), [wormholesData, statics]);
|
||||||
|
|
||||||
|
const linkedSigPrefix = useMemo(() => (linkedSigEveId ? linkedSigEveId.split('-')[0] : null), [linkedSigEveId]);
|
||||||
|
|
||||||
|
const { labelsInfo, labelCustom } = useLabelsInfo({
|
||||||
|
labels,
|
||||||
|
linkedSigPrefix,
|
||||||
|
isShowLinkedSigId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasUserCharacters = useMemo(
|
||||||
|
() => charactersInSystem.some(x => userCharacters.includes(x.eve_id)),
|
||||||
|
[charactersInSystem, userCharacters],
|
||||||
|
);
|
||||||
|
|
||||||
|
const dbClick = useDoubleClick(() => {
|
||||||
|
outCommand({
|
||||||
|
type: OutCommand.openSettings,
|
||||||
|
data: { system_id: solar_system_id },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const showHandlers = isConnecting || hoverNodeId === id;
|
||||||
|
|
||||||
|
const space = showKSpaceBG ? REGIONS_MAP[region_id] : '';
|
||||||
|
const regionClass = showKSpaceBG ? SpaceToClass[space] || null : null;
|
||||||
|
|
||||||
|
const { systemName, computedTemporaryName, customName } = useSystemName({
|
||||||
|
isTempSystemNameEnabled,
|
||||||
|
temporary_name,
|
||||||
|
isShowLinkedSigIdTempName,
|
||||||
|
linkedSigPrefix,
|
||||||
|
name,
|
||||||
|
systemStaticInfo,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { unsplashedLeft, unsplashedRight } = useUnsplashedSignatures(systemSigs, isShowUnsplashedSignatures);
|
||||||
|
|
||||||
|
const hubsAsStrings = useMemo(() => hubs.map(item => item.toString()), [hubs]);
|
||||||
|
|
||||||
|
const isRally = useMemo(
|
||||||
|
() => !!pings.find(x => x.solar_system_id === solar_system_id && x.type === PingType.Rally),
|
||||||
|
[pings, solar_system_id],
|
||||||
|
);
|
||||||
|
|
||||||
|
const regionName = useMemo(() => {
|
||||||
|
if (region_id === Regions.Pochven) {
|
||||||
|
return constellation_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return region_name;
|
||||||
|
}, [constellation_name, region_id, region_name]);
|
||||||
|
|
||||||
|
const nodeVars: SolarSystemNodeVars = {
|
||||||
|
id,
|
||||||
|
selected,
|
||||||
|
visible,
|
||||||
|
isWormhole,
|
||||||
|
classTitleColor,
|
||||||
|
hasUserCharacters,
|
||||||
|
userCharacters,
|
||||||
|
showHandlers,
|
||||||
|
regionClass,
|
||||||
|
systemName,
|
||||||
|
customName,
|
||||||
|
labelCustom,
|
||||||
|
isShattered: is_shattered,
|
||||||
|
tag,
|
||||||
|
status,
|
||||||
|
labelsInfo,
|
||||||
|
dbClick,
|
||||||
|
sortedStatics,
|
||||||
|
effectName: effect_name,
|
||||||
|
solarSystemId: solar_system_id.toString(),
|
||||||
|
locked,
|
||||||
|
hubs: hubsAsStrings,
|
||||||
|
name,
|
||||||
|
isConnecting,
|
||||||
|
hoverNodeId,
|
||||||
|
charactersInSystem,
|
||||||
|
unsplashedLeft,
|
||||||
|
unsplashedRight,
|
||||||
|
isThickConnections,
|
||||||
|
classTitle: class_title,
|
||||||
|
temporaryName: computedTemporaryName,
|
||||||
|
regionName,
|
||||||
|
solarSystemName: solar_system_name,
|
||||||
|
isRally,
|
||||||
|
};
|
||||||
|
|
||||||
|
return nodeVars;
|
||||||
|
};
|
||||||
56
assets/js/hooks/Mapper/components/map/hooks/useSystemName.ts
Normal file
56
assets/js/hooks/Mapper/components/map/hooks/useSystemName.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
interface UseSystemNameParams {
|
||||||
|
isTempSystemNameEnabled: boolean;
|
||||||
|
temporary_name?: string | null;
|
||||||
|
isShowLinkedSigIdTempName: boolean;
|
||||||
|
linkedSigPrefix: string | null;
|
||||||
|
name?: string | null;
|
||||||
|
systemStaticInfo: SolarSystemStaticInfoRaw;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSystemName = ({
|
||||||
|
isTempSystemNameEnabled,
|
||||||
|
temporary_name,
|
||||||
|
isShowLinkedSigIdTempName,
|
||||||
|
linkedSigPrefix,
|
||||||
|
name,
|
||||||
|
systemStaticInfo,
|
||||||
|
}: UseSystemNameParams) => {
|
||||||
|
const { solar_system_name = '' } = systemStaticInfo;
|
||||||
|
|
||||||
|
const computedTemporaryName = useMemo(() => {
|
||||||
|
if (!isTempSystemNameEnabled) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isShowLinkedSigIdTempName && linkedSigPrefix) {
|
||||||
|
return temporary_name ? `${linkedSigPrefix}:${temporary_name}` : `${linkedSigPrefix}:${solar_system_name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return temporary_name ?? '';
|
||||||
|
}, [isTempSystemNameEnabled, temporary_name, solar_system_name, isShowLinkedSigIdTempName, linkedSigPrefix]);
|
||||||
|
|
||||||
|
const systemName = useMemo(() => {
|
||||||
|
if (isTempSystemNameEnabled && computedTemporaryName) {
|
||||||
|
return computedTemporaryName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return solar_system_name;
|
||||||
|
}, [isTempSystemNameEnabled, computedTemporaryName, solar_system_name]);
|
||||||
|
|
||||||
|
const customName = useMemo(() => {
|
||||||
|
if (isTempSystemNameEnabled && computedTemporaryName && name) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (solar_system_name !== name && name) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}, [isTempSystemNameEnabled, computedTemporaryName, name, solar_system_name]);
|
||||||
|
|
||||||
|
return { systemName, computedTemporaryName, customName };
|
||||||
|
};
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||||
|
import { prepareUnsplashedChunks } from '@/hooks/Mapper/components/map/helpers';
|
||||||
|
|
||||||
|
export type UnsplashedSignatureType = SystemSignature & { sig_id: string };
|
||||||
|
|
||||||
|
export function useUnsplashedSignatures(systemSigs: SystemSignature[], isShowUnsplashedSignatures: boolean) {
|
||||||
|
return useMemo(() => {
|
||||||
|
if (!isShowUnsplashedSignatures) {
|
||||||
|
return {
|
||||||
|
unsplashedLeft: [] as SystemSignature[],
|
||||||
|
unsplashedRight: [] as SystemSignature[],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const chunks = prepareUnsplashedChunks(
|
||||||
|
systemSigs
|
||||||
|
.filter(s => s.group === 'Wormhole' && !s.linked_system)
|
||||||
|
.map(s => ({
|
||||||
|
eve_id: s.eve_id,
|
||||||
|
type: s.type,
|
||||||
|
custom_info: s.custom_info,
|
||||||
|
kind: s.kind,
|
||||||
|
name: s.name,
|
||||||
|
group: s.group,
|
||||||
|
})) as UnsplashedSignatureType[],
|
||||||
|
);
|
||||||
|
const [unsplashedLeft, unsplashedRight] = chunks;
|
||||||
|
return { unsplashedLeft, unsplashedRight };
|
||||||
|
}, [isShowUnsplashedSignatures, systemSigs]);
|
||||||
|
}
|
||||||
@@ -6,9 +6,9 @@ import { SolarSystemRawType } from '@/hooks/Mapper/types';
|
|||||||
const useThrottle = () => {
|
const useThrottle = () => {
|
||||||
const throttleSeed = useRef<number | null>(null);
|
const throttleSeed = useRef<number | null>(null);
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const throttleFunction = useRef((func: any, delay = 200) => {
|
const throttleFunction = useRef((func: any, delay = 200) => {
|
||||||
if (!throttleSeed.current) {
|
if (!throttleSeed.current) {
|
||||||
// Call the callback immediately for the first time
|
|
||||||
func();
|
func();
|
||||||
throttleSeed.current = setTimeout(() => {
|
throttleSeed.current = setTimeout(() => {
|
||||||
throttleSeed.current = null;
|
throttleSeed.current = null;
|
||||||
@@ -75,7 +75,7 @@ export const useUpdateNodes = (nodes: Node<SolarSystemRawType>[]) => {
|
|||||||
|
|
||||||
const visibleNodes = new Set(nodes.filter(x => isNodeVisible(x, viewport)).map(x => x.id));
|
const visibleNodes = new Set(nodes.filter(x => isNodeVisible(x, viewport)).map(x => x.id));
|
||||||
update({ visibleNodes });
|
update({ visibleNodes });
|
||||||
}, [nodes]);
|
}, [getViewport, nodes, update]);
|
||||||
|
|
||||||
useOnViewportChange({
|
useOnViewportChange({
|
||||||
onChange: () => throttle(updateNodesVisibility.bind(this)),
|
onChange: () => throttle(updateNodesVisibility.bind(this)),
|
||||||
@@ -84,5 +84,5 @@ export const useUpdateNodes = (nodes: Node<SolarSystemRawType>[]) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateNodesVisibility();
|
updateNodesVisibility();
|
||||||
}, [nodes]);
|
}, [nodes, updateNodesVisibility]);
|
||||||
};
|
};
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user