Compare commits

...

108 Commits

Author SHA1 Message Date
CI
e5aa726899 chore: release version v1.64.6
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-05-15 10:23:04 +00:00
Dmitry Popov
93d1c28ccd Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-05-15 12:02:02 +02:00
Dmitry Popov
b5ba9200bc fix(Core): Added map hubs limits checking & a proper warning message shown 2025-05-15 12:01:59 +02:00
CI
699d866670 chore: release version v1.64.5
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-05-14 22:13:32 +00:00
Dmitry Popov
c3071344cb chore: Added link to YT channel 2025-05-14 23:59:06 +02:00
Dmitry Popov
9e998dd2b6 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-05-14 23:24:57 +02:00
Dmitry Popov
c9accf6079 fix(Core): Added character name update on re-auth 2025-05-14 23:24:49 +02:00
CI
1b41a51004 chore: release version v1.64.4 2025-05-14 20:49:45 +00:00
Dmitry Popov
b49d3423fc fix(Core): Added 1 min timeout for ship and location updates on ESI API errors
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-05-14 16:35:21 +02:00
CI
cccab2a985 chore: release version v1.64.3 2025-05-14 10:14:49 +00:00
Dmitry Popov
1abaa90a7d Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-05-14 11:54:41 +02:00
Dmitry Popov
6e1993ca8a fix(Core): Fixed character tracking initialization logic & removed search caching 2025-05-14 11:54:37 +02:00
CI
171c821ac4 chore: release version v1.64.2
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-05-13 21:51:53 +00:00
Dmitry Popov
7ebf9186bf Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-05-13 23:42:32 +02:00
Dmitry Popov
57d2f2baef fix(Core): Fixed tracking of ship & location for offline characters 2025-05-13 23:42:30 +02:00
CI
0aee13878a chore: release version v1.64.1 2025-05-13 19:40:55 +00:00
Dmitry Popov
f93ef0ca76 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-05-13 21:31:06 +02:00
Dmitry Popov
4ec03d8338 fix(Core): Fixed tracking stopped due to server errors 2025-05-13 21:31:02 +02:00
CI
733482cd5c chore: release version v1.64.0
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-05-13 10:42:59 +00:00
Dmitry Popov
3969d1287d fix(Core): Fixed EOL connections cleanup 2025-05-13 12:26:58 +02:00
Dmitry Popov
1aa7854b0d chore: Added ESI API cached responces based on expire headers 2025-05-13 12:08:06 +02:00
Dmitry Popov
7b27d4a1a7 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-05-13 11:09:39 +02:00
Dmitry Popov
24ddb8771f fix(Core): Avoid Zarzakh system in routes widget 2025-05-13 11:09:35 +02:00
Dmitry Popov
7134714245 Merge pull request #376 from wanderer-industries/develop
Develop
2025-05-13 13:02:47 +04:00
guarzo
96b320ac26 fix: remove repeat errors for token refresh (#375) 2025-05-13 13:01:05 +04:00
guarzo
b88e121b30 fix: updated openapi spec for character activity (#374) 2025-05-12 19:09:18 +04:00
guarzo
4ba4119c2b fix: removed error from characters endpoint, and updated routes (#372) 2025-05-12 11:15:20 +04:00
Dmitry Popov
91d1ca201c Merge branch 'main' into develop 2025-05-11 14:35:53 +02:00
guarzo
8bf063a228 fix: cleanup examples for system and connections (#370) 2025-05-11 16:20:23 +04:00
CI
4f53de39b1 chore: release version v1.63.0
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-05-11 12:17:06 +00:00
Dmitry Popov
8c3804f107 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-05-11 14:03:57 +02:00
Dmitry Popov
1be4ec2b90 feat(Core): Updated map active characters page 2025-05-11 14:03:53 +02:00
Dmitry Popov
8f0ed44b11 chore: release version v1.62.3 2025-05-10 20:46:01 +02:00
CI
cbadfc4ac4 chore: release version v1.62.4
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-05-10 12:08:58 +00:00
Dmitry Popov
3d88ae4452 fix(Core): Fixed map characters got untracked 2025-05-10 13:46:49 +02:00
Dmitry Popov
07e2196eb4 Merge branch 'main' into develop 2025-05-09 14:02:39 +02:00
CI
6d99c54af7 chore: release version v1.62.3
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-05-08 12:14:20 +00:00
Dmitry Popov
2b7901e9a8 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-05-08 12:20:14 +02:00
Dmitry Popov
fb06dd1dbc fix(Core): Fixed map characters got untracked 2025-05-08 12:20:09 +02:00
guarzo
d3b825529e fix: remove error on websocket reconnect (#367) 2025-05-07 12:24:17 +04:00
guarzo
ccf9c0db22 feat (api): add additional structure/signature methods (#365) 2025-05-06 20:42:47 +04:00
CI
f8ba36b8be chore: release version v1.62.2
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-05-05 18:51:41 +00:00
Dmitry Popov
5bf9d99b3d Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-05-05 20:32:17 +02:00
Dmitry Popov
7cad05342a fix(Core): Fixed audit export API 2025-05-05 20:32:14 +02:00
CI
867780e525 chore: release version v1.62.1
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-05-05 09:14:15 +00:00
Dmitry Popov
ff4f9a79c9 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-05-05 10:18:44 +02:00
Dmitry Popov
6699c36fb3 chore: load user roads for map with active subscription only 2025-05-05 10:18:41 +02:00
CI
abd4556994 chore: release version v1.62.0 2025-05-05 08:05:56 +00:00
Dmitry Popov
ccf0d17371 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-05-05 09:46:42 +02:00
Dmitry Popov
898584bbb6 fix(Map): Fixed link signature modal crash afrer destination system removed 2025-05-05 09:46:39 +02:00
Aleksei Chichenkov
6d7a267e39 fix(Map): Change design for tags (#358)
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
Co-authored-by: achichenkov <aleksei.chichenkov@telleqt.ai>
2025-05-04 14:43:59 +04:00
Dmitry Popov
9f656ca3cb chore: send hubs limit as part of init event 2025-05-04 11:25:38 +02:00
Aleksei Chichenkov
fede6451e2 Merge pull request #352 from wanderer-industries/custom-hubs
Custom hubs
2025-05-04 11:39:33 +03:00
achichenkov
9797ad380c Merge remote-tracking branch 'origin/custom-hubs' into custom-hubs 2025-05-04 11:32:14 +03:00
achichenkov
33bc4a4d22 fix(Map): Removed paywall restriction from public routes 2025-05-04 11:31:56 +03:00
guarzo
6378754c57 feat (api): add additional system/connections methods (#351) 2025-05-03 19:41:21 +04:00
Dmitry Popov
30fc972d78 Merge branch 'main' into custom-hubs 2025-05-03 10:29:30 +02:00
Dmitry Popov
c022b31c79 Merge branch 'main' of github.com:wanderer-industries/wanderer
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-05-03 10:26:20 +02:00
Dmitry Popov
049b06bb39 fix(Core): Fixed issues with structures loading 2025-05-03 10:26:17 +02:00
achichenkov
e17d5213c0 fix(Map): Removed unnecessary logs 2025-04-30 13:18:17 +03:00
achichenkov
dcf681941e fix(Map): Add support user routes 2025-04-30 13:17:31 +03:00
CI
1cd7d40405 chore: release version v1.61.2
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-04-29 10:40:50 +00:00
Dmitry Popov
fbd80ba2c7 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-04-29 12:28:01 +02:00
Dmitry Popov
88ab85bd04 fix(Core): Fixed main character checking & manual systems delete logic 2025-04-29 12:27:55 +02:00
Dmitry Popov
78f98744fd chore(Map): Fixed user routes api 2025-04-28 00:21:55 +02:00
achichenkov
9c9634a927 fix(Map): Add support for User Routes on FE side. 2025-04-27 15:09:28 +03:00
CI
be47be626c chore: release version v1.61.1
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-04-26 21:48:21 +00:00
Dmitry Popov
2fbd3d8e19 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-04-26 23:19:31 +02:00
Dmitry Popov
d5c3d4c051 fix(Core): Fixed additional price calc for map sub updates
Apply discounts and recalc period based on subscription
2025-04-26 23:19:27 +02:00
achichenkov
fac60f7ddd fix(Map): Refactor Local - show ship name, change placement of ship name. Refactor On the Map - show corp and ally logo. Fixed problem with ellipsis at long character and ship names. 2025-04-26 16:22:24 +03:00
CI
c371478c61 chore: release version v1.61.0
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-04-24 12:52:40 +00:00
Dmitry Popov
5911e29f34 feat(Core): force checking main character set for all map activity 2025-04-24 13:45:01 +02:00
achichenkov
99d68dfc0e fix(Map): Refactored routes widget. Add loader for routes. Prepared for custom hubs 2025-04-24 12:46:08 +03:00
achichenkov
c9b366f3e2 fix(Map): Refactor init and update of mapper 2025-04-23 08:57:55 +03:00
Dmitry Popov
4e732e9491 chore: refactor map_init/map_update events 2025-04-22 22:35:53 +02:00
Dmitry Popov
dd5b12aa38 chore: refactor map_init/map_update events 2025-04-22 11:57:11 +02:00
Dmitry Popov
7bd960fba9 Merge branch 'main' into custom-hubs 2025-04-22 11:15:47 +02:00
CI
c338c33902 chore: release version v1.60.1
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-04-22 08:14:35 +00:00
achichenkov
df6b7ae635 Merge branch 'refs/heads/main' into custom-hubs 2025-04-22 11:07:34 +03:00
Aleksei Chichenkov
a3346f8223 Merge pull request #341 from wanderer-industries/whdb-types-fixes-2
Whdb types fixes 2
2025-04-22 11:06:05 +03:00
achichenkov
196f2c2c3b fix(Map): Removed unnecessary code onFE part 2025-04-20 18:34:59 +03:00
Dmitry Popov
77d549ac1b chore: fixed drifter system names 2025-04-19 12:07:17 +02:00
achichenkov
5c3cce66c1 fix(Map): Removed unnecessary debugger 2025-04-19 10:57:56 +03:00
achichenkov
cc2f09601e fix(Map): Changed name for drifters systems. Fixed static info for Barbican. 2025-04-19 10:57:05 +03:00
CI
14af04cc73 chore: release version v1.60.0
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-04-17 09:26:43 +00:00
guarzo
37c9e68c21 feat (api): api showing character by user and main character (#334) 2025-04-17 13:06:34 +04:00
Dmitry Popov
2bd343b2da feat(Core): force map page reload after 30 mins of user inactivity (switched browser/tab)
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-04-17 00:38:20 +02:00
Dmitry Popov
f5d502c5ad chore: introduced tracker pool servers (100 trackers per server) 2025-04-16 23:58:41 +02:00
guarzo
35cf460ccf feat: update character activity to use main character (#333) 2025-04-16 23:10:07 +04:00
guarzo
28c7b90c3f scroll activity (#331)
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-04-16 21:28:48 +04:00
CI
4fbdaf42e1 chore: release version v1.59.11
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-04-16 06:38:44 +00:00
Aleksei Chichenkov
90910620d9 fix(Map): Fixed lifetime for A009 from 16h to 4.5h. Fixed problem with no appearing icon of shattered for Drifter wormholes. Fixed wanderings for Drifter wormholes. For system J011355 added static K346. For system J011824 added static K346. (#329) 2025-04-16 10:28:15 +04:00
CI
eb4336fef7 chore: release version v1.59.10
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-04-15 22:25:50 +00:00
Dmitry Popov
69264cc8ec chore: release version v1.59.9 2025-04-16 00:18:16 +02:00
CI
ab0cb74ca9 chore: release version v1.59.9 2025-04-15 22:04:46 +00:00
Dmitry Popov
42101ab6fd chore: release version v1.59.8 2025-04-15 23:56:58 +02:00
Dmitry Popov
8d69c70076 fix(Core): Fixed issues with map server manager 2025-04-15 23:52:59 +02:00
CI
beb3077159 chore: release version v1.59.8
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-04-15 10:32:20 +00:00
Dmitry Popov
ecb3ca2b4e fix(Core): Fixed issues with main character & tracking
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-04-15 12:15:08 +02:00
Dmitry Popov
2ba42e0c25 chore: release version v1.59.7 2025-04-14 20:36:09 +02:00
Dmitry Popov
3ef5590e18 Merge branch 'main' into custom-hubs 2025-04-14 20:30:39 +02:00
CI
8412e3867d chore: release version v1.59.7
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-04-14 18:06:11 +00:00
Dmitry Popov
90c40100d1 fix(Core): Fixed auto-select splashed systems
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-04-14 19:56:23 +02:00
CI
92cb49da90 chore: release version v1.59.6
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-04-13 19:31:05 +00:00
Aleksei Chichenkov
abc09c067f fix(Map): Fix icons of main, follow and shattered (#321) 2025-04-13 22:59:56 +04:00
CI
edbd1e4bbc chore: release version v1.59.5
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-04-12 09:36:06 +00:00
Dmitry Popov
75edb91825 fix(Signatures): avoid signatures delete on wrong buffer 2025-04-12 11:23:46 +02:00
Dmitry Popov
4b29060c96 feat(Core): added user routes support 2025-03-28 09:22:08 +01:00
191 changed files with 12796 additions and 3404 deletions

View File

@@ -2,6 +2,271 @@
<!-- changelog -->
## [v1.64.6](https://github.com/wanderer-industries/wanderer/compare/v1.64.5...v1.64.6) (2025-05-15)
### Bug Fixes:
* Core: Added map hubs limits checking & a proper warning message shown
## [v1.64.5](https://github.com/wanderer-industries/wanderer/compare/v1.64.4...v1.64.5) (2025-05-14)
### Bug Fixes:
* Core: Added character name update on re-auth
## [v1.64.4](https://github.com/wanderer-industries/wanderer/compare/v1.64.3...v1.64.4) (2025-05-14)
### Bug Fixes:
* Core: Added 1 min timeout for ship and location updates on ESI API errors
## [v1.64.3](https://github.com/wanderer-industries/wanderer/compare/v1.64.2...v1.64.3) (2025-05-14)
### Bug Fixes:
* Core: Fixed character tracking initialization logic & removed search caching
## [v1.64.2](https://github.com/wanderer-industries/wanderer/compare/v1.64.1...v1.64.2) (2025-05-13)
### Bug Fixes:
* Core: Fixed tracking of ship & location for offline characters
## [v1.64.1](https://github.com/wanderer-industries/wanderer/compare/v1.64.0...v1.64.1) (2025-05-13)
### Bug Fixes:
* Core: Fixed tracking stopped due to server errors
## [v1.64.0](https://github.com/wanderer-industries/wanderer/compare/v1.63.0...v1.64.0) (2025-05-13)
### Features:
* api: add additional structure/signature methods (#365)
* api: add additional system/connections methods (#351)
### Bug Fixes:
* Core: Fixed EOL connections cleanup
* Core: Avoid Zarzakh system in routes widget
* remove repeat errors for token refresh (#375)
* updated openapi spec for character activity (#374)
* removed error from characters endpoint, and updated routes (#372)
* cleanup examples for system and connections (#370)
* remove error on websocket reconnect (#367)
## [v1.63.0](https://github.com/wanderer-industries/wanderer/compare/v1.62.4...v1.63.0) (2025-05-11)
### Features:
* Core: Updated map active characters page
## [v1.62.4](https://github.com/wanderer-industries/wanderer/compare/v1.62.3...v1.62.4) (2025-05-10)
### Bug Fixes:
* Core: Fixed map characters got untracked
## [v1.62.3](https://github.com/wanderer-industries/wanderer/compare/v1.62.2...v1.62.3) (2025-05-08)
### Bug Fixes:
* Core: Fixed map characters got untracked
## [v1.62.2](https://github.com/wanderer-industries/wanderer/compare/v1.62.1...v1.62.2) (2025-05-05)
### Bug Fixes:
* Core: Fixed audit export API
## [v1.62.1](https://github.com/wanderer-industries/wanderer/compare/v1.62.0...v1.62.1) (2025-05-05)
## [v1.62.0](https://github.com/wanderer-industries/wanderer/compare/v1.61.2...v1.62.0) (2025-05-05)
### Features:
* Core: added user routes support
### Bug Fixes:
* Map: Fixed link signature modal crash afrer destination system removed
* Map: Change design for tags (#358)
* Map: Removed paywall restriction from public routes
* Core: Fixed issues with structures loading
* Map: Removed unnecessary logs
* Map: Add support user routes
* Map: Add support for User Routes on FE side.
* Map: Refactor Local - show ship name, change placement of ship name. Refactor On the Map - show corp and ally logo. Fixed problem with ellipsis at long character and ship names.
* Map: Refactored routes widget. Add loader for routes. Prepared for custom hubs
* Map: Refactor init and update of mapper
## [v1.61.2](https://github.com/wanderer-industries/wanderer/compare/v1.61.1...v1.61.2) (2025-04-29)
### Bug Fixes:
* Core: Fixed main character checking & manual systems delete logic
## [v1.61.1](https://github.com/wanderer-industries/wanderer/compare/v1.61.0...v1.61.1) (2025-04-26)
### Bug Fixes:
* Core: Fixed additional price calc for map sub updates
## [v1.61.0](https://github.com/wanderer-industries/wanderer/compare/v1.60.1...v1.61.0) (2025-04-24)
### Features:
* Core: force checking main character set for all map activity
## [v1.60.1](https://github.com/wanderer-industries/wanderer/compare/v1.60.0...v1.60.1) (2025-04-22)
### Bug Fixes:
* Map: Removed unnecessary code onFE part
* Map: Removed unnecessary debugger
* Map: Changed name for drifters systems. Fixed static info for Barbican.
## [v1.60.0](https://github.com/wanderer-industries/wanderer/compare/v1.59.11...v1.60.0) (2025-04-17)
### Features:
* api: api showing character by user and main character (#334)
* Core: force map page reload after 30 mins of user inactivity (switched browser/tab)
* update character activity to use main character (#333)
## [v1.59.11](https://github.com/wanderer-industries/wanderer/compare/v1.59.10...v1.59.11) (2025-04-16)
### Bug Fixes:
* Map: Fixed lifetime for A009 from 16h to 4.5h. Fixed problem with no appearing icon of shattered for Drifter wormholes. Fixed wanderings for Drifter wormholes. For system J011355 added static K346. For system J011824 added static K346. (#329)
## [v1.59.10](https://github.com/wanderer-industries/wanderer/compare/v1.59.9...v1.59.10) (2025-04-15)
## [v1.59.9](https://github.com/wanderer-industries/wanderer/compare/v1.59.8...v1.59.9) (2025-04-15)
### Bug Fixes:
* Core: Fixed issues with map server manager
## [v1.59.8](https://github.com/wanderer-industries/wanderer/compare/v1.59.7...v1.59.8) (2025-04-15)
### Bug Fixes:
* Core: Fixed issues with main character & tracking
## [v1.59.7](https://github.com/wanderer-industries/wanderer/compare/v1.59.6...v1.59.7) (2025-04-14)
### Bug Fixes:
* Core: Fixed auto-select splashed systems
## [v1.59.6](https://github.com/wanderer-industries/wanderer/compare/v1.59.5...v1.59.6) (2025-04-13)
### Bug Fixes:
* Map: Fix icons of main, follow and shattered (#321)
## [v1.59.5](https://github.com/wanderer-industries/wanderer/compare/v1.59.4...v1.59.5) (2025-04-12)
### Bug Fixes:
* Signatures: avoid signatures delete on wrong buffer
## [v1.59.4](https://github.com/wanderer-industries/wanderer/compare/v1.59.3...v1.59.4) (2025-04-10)

View File

@@ -1,14 +1,14 @@
import { ErrorBoundary } from 'react-error-boundary';
import { PrimeReactProvider } from 'primereact/api';
import { ErrorBoundary } from 'react-error-boundary';
import { ReactFlowProvider } from 'reactflow';
import { MapHandlers } from '@/hooks/Mapper/types/mapHandlers.ts';
import { ErrorInfo, useCallback, useEffect, useRef } from 'react';
import { ReactFlowProvider } from 'reactflow';
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 { MapRootProvider } from '@/hooks/Mapper/mapRootProvider';
import './common-styles/main.scss';
const ErrorFallback = () => {
return <div className="!z-100 absolute w-screen h-screen bg-transparent"></div>;
@@ -20,7 +20,7 @@ export default function MapRoot({ hooks }) {
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) => {
if (!hooksRef.current) {
@@ -35,7 +35,6 @@ export default function MapRoot({ hooks }) {
}
hooksRef.current.handleEvent('map_event', handleMapEvent);
hooksRef.current.handleEvent('map_events', handleMapEvents);
}, []);
return (

View File

@@ -99,6 +99,11 @@
.p-dropdown-item {
padding: 0.25rem 0.5rem;
font-size: 14px;
width: 100%;
.p-dropdown-item-label {
width: 100%;
}
}
.p-dropdown-item-group {
@@ -180,3 +185,16 @@
.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;
}
}

View File

@@ -1,38 +1,3 @@
.MainCharacter,
.FollowingCharacter {
&::before, &::after {
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;
}
}
.MainCharacter {
&::before {
background-image: url(/images/73_16_247.png);
left: 22px;
top: 0px;
transform: rotateZ(90deg);
}
}
.FollowingCharacter {
&::after {
background-image: url(/images/73_16_241.png);
left: 22px;
top: 22px;
transform: rotateZ(180deg);
}
}
.Docked {
content: " ";
display: inline-block;

View File

@@ -7,6 +7,7 @@ import { emitMapEvent } from '@/hooks/Mapper/events';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import classes from './Characters.module.scss';
import { isDocked } from '@/hooks/Mapper/helpers/isDocked.ts';
import { PrimeIcons } from 'primereact/api';
interface CharactersProps {
data: CharacterTypeRaw[];
@@ -34,18 +35,35 @@ export const Characters = ({ data }: CharactersProps) => {
>
<div
className={clsx(
'overflow-hidden relative',
'flex w-[35px] h-[35px] rounded-[4px] border-[1px] border-solid bg-transparent cursor-pointer',
'transition-colors duration-250',
{
['overflow-hidden relative']: true,
['border-stone-800/90']: !character.online,
['border-lime-600/70']: character.online,
[classes.MainCharacter]: mainCharacterEveId === character.eve_id,
[classes.FollowingCharacter]: followingCharacterEveId === character.eve_id,
},
)}
title={character.name}
>
{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(

View File

@@ -6,6 +6,7 @@ import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/ty
export interface ContextMenuSystemProps {
hubs: string[];
userHubs: string[];
contextMenuRef: RefObject<ContextMenu>;
systemId: string | undefined;
systems: SolarSystemRawType[];
@@ -13,6 +14,7 @@ export interface ContextMenuSystemProps {
onLockToggle(): void;
onOpenSettings(): void;
onHubToggle(): void;
onUserHubToggle(): void;
onSystemTag(val?: string): void;
onSystemStatus(val: number): void;
onSystemLabels(val: string): void;
@@ -25,7 +27,7 @@ export const ContextMenuSystem: React.FC<ContextMenuSystemProps> = ({ contextMen
return (
<>
<ContextMenu model={items} ref={contextMenuRef} breakpoint="767px" />
<ContextMenu className="min-w-[200px]" model={items} ref={contextMenuRef} breakpoint="767px" />
</>
);
};

View File

@@ -1 +1 @@
export * from './useTagMenu.ts';
export * from './useTagMenu.tsx';

View File

@@ -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;
}, []);
};

View File

@@ -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;
}, []);
};

View File

@@ -8,19 +8,25 @@ import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
interface UseContextMenuSystemHandlersProps {
hubs: string[];
userHubs: string[];
systems: SolarSystemRawType[];
outCommand: OutCommandHandler;
}
export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseContextMenuSystemHandlersProps) => {
export const useContextMenuSystemHandlers = ({
systems,
hubs,
userHubs,
outCommand,
}: UseContextMenuSystemHandlersProps) => {
const contextMenuRef = useRef<ContextMenu | null>(null);
const [system, setSystem] = useState<string>();
const { deleteSystems } = useDeleteSystems();
const ref = useRef({ hubs, system, systems, outCommand, deleteSystems });
ref.current = { hubs, system, systems, outCommand, deleteSystems };
const ref = useRef({ hubs, userHubs, system, systems, outCommand, deleteSystems });
ref.current = { hubs, userHubs, system, systems, outCommand, deleteSystems };
const open = useCallback((ev: any, systemId: string) => {
setSystem(systemId);
@@ -72,6 +78,21 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
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 onSystemTag = useCallback((tag?: string) => {
const { system, outCommand } = ref.current;
if (!system) {
@@ -104,7 +125,6 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
setSystem(undefined);
}, []);
const onSystemStatus = useCallback((status: number) => {
const { system, outCommand } = ref.current;
if (!system) {
@@ -177,6 +197,7 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
onDeleteSystem,
onLockToggle,
onHubToggle,
onUserHubToggle,
onSystemTag,
onSystemTemporaryName,
onSystemStatus,

View File

@@ -10,11 +10,13 @@ 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, MapUserAddIcon, MapUserDeleteIcon } from '@/hooks/Mapper/icons';
export const useContextMenuSystemItems = ({
onDeleteSystem,
onLockToggle,
onHubToggle,
onUserHubToggle,
onSystemTag,
onSystemStatus,
onSystemLabels,
@@ -23,6 +25,7 @@ export const useContextMenuSystemItems = ({
onWaypointSet,
systemId,
hubs,
userHubs,
systems,
}: Omit<ContextMenuSystemProps, 'contextMenuRef'>) => {
const getTags = useTagMenu(systems, systemId, onSystemTag);
@@ -61,10 +64,23 @@ export const useContextMenuSystemItems = ({
...getLabels(),
...getWaypointMenu(systemId, systemStaticInfo.system_class),
{
label: !hubs.includes(systemId) ? 'Add in Routes' : 'Remove from Routes',
icon: PrimeIcons.MAP_MARKER,
label: !hubs.includes(systemId) ? 'Add Route' : 'Remove Route',
icon: !hubs.includes(systemId) ? (
<MapAddIcon className="mr-1 relative left-[-2px]" />
) : (
<MapDeleteIcon className="mr-1 relative left-[-2px]" />
),
command: onHubToggle,
},
{
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,
},
...(system.locked
? canLockSystem
? [

View File

@@ -11,6 +11,7 @@ import { FastSystemActions } from '@/hooks/Mapper/components/contexts/components
import { useJumpPlannerMenu } from '@/hooks/Mapper/components/contexts/hooks';
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 {
systemStatics: Map<number, SolarSystemStaticInfoRaw>;
@@ -69,8 +70,12 @@ export const ContextMenuSystemInfo: React.FC<ContextMenuSystemInfoProps> = ({
...getJumpPlannerMenu(system, routes),
...getWaypointMenu(systemId, system.system_class),
{
label: !hubs.includes(systemId) ? 'Add in Routes' : 'Remove from Routes',
icon: PrimeIcons.MAP_MARKER,
label: !hubs.includes(systemId) ? 'Add Route' : 'Remove Route',
icon: !hubs.includes(systemId) ? (
<MapAddIcon className="mr-1 relative left-[-2px]" />
) : (
<MapDeleteIcon className="mr-1 relative left-[-2px]" />
),
command: onHubToggle,
},
...(!systemOnMap

View File

@@ -1,25 +1,25 @@
import * as React from 'react';
import { useCallback, useRef, useState } from 'react';
import { ContextMenu } from 'primereact/contextmenu';
import { Commands, 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 { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
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 {
hubs: string[];
outCommand: OutCommandHandler;
}
export const useContextMenuSystemInfoHandlers = () => {
const { outCommand } = useMapRootState();
const { hubs = [], toggleHubCommand } = useRouteProvider();
export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand }: UseContextMenuSystemHandlersProps) => {
const contextMenuRef = useRef<ContextMenu | null>(null);
const [system, setSystem] = useState<string>();
const routeRef = useRef<(SolarSystemStaticInfoRaw | undefined)[]>([]);
const ref = useRef({ hubs, system, outCommand });
ref.current = { hubs, system, outCommand };
const ref = useRef({ hubs, system, outCommand, toggleHubCommand });
ref.current = { hubs, system, outCommand, toggleHubCommand };
const open = useCallback(
(ev: React.SyntheticEvent, systemId: string, route: (SolarSystemStaticInfoRaw | undefined)[]) => {
@@ -33,17 +33,12 @@ export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand }: UseContex
);
const onHubToggle = useCallback(() => {
const { hubs, system, outCommand } = ref.current;
const { system } = ref.current;
if (!system) {
return;
}
outCommand({
type: !hubs.includes(system) ? OutCommand.addHub : OutCommand.deleteHub,
data: {
system_id: system,
},
});
ref.current.toggleHubCommand(system);
setSystem(undefined);
}, []);
@@ -59,6 +54,8 @@ export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand }: UseContex
system_id: solarSystemId,
},
});
// TODO add it to some queue
setTimeout(() => {
emitMapEvent({
name: Commands.centerSystem,

View File

@@ -1,6 +1,6 @@
import { useCallback, useRef } from 'react';
import { LayoutEventBlocker, 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 clsx from 'clsx';

View File

@@ -1,6 +1,6 @@
import { getSystemById } from '@/hooks/Mapper/helpers';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useMemo } from 'react';
import { getSystemById } from '@/hooks/Mapper/helpers';
import { getSystemStaticInfo } from '../../mapRootProvider/hooks/useLoadSystemStatic';
interface UseSystemInfoProps {
@@ -17,7 +17,7 @@ export const useSystemInfo = ({ systemId }: UseSystemInfoProps) => {
const dynamicInfo = getSystemById(systems, systemId);
if (!staticInfo || !dynamicInfo) {
throw new Error(`Error on getting system ${systemId}`);
return { dynamicInfo, staticInfo, leadsTo: [] };
}
const leadsTo = connections

View File

@@ -38,6 +38,8 @@ const INITIAL_DATA: MapData = {
systemSignatures: {} as Record<string, SystemSignature[]>,
options: {} as Record<string, string | boolean>,
isSubscriptionActive: false,
mainCharacterEveId: null,
followingCharacterEveId: null,
};
export interface MapContextProps {

View File

@@ -26,11 +26,7 @@ export const KillsCounter = ({
children,
size = TooltipSize.xs,
}: KillsBookmarkTooltipProps) => {
const {
isLoading,
kills: detailedKills,
systemNameMap,
} = useKillsCounter({
const { isLoading, kills: detailedKills } = useKillsCounter({
realSystemId: systemId,
});

View File

@@ -6,8 +6,8 @@ import { CharItemProps, LocalCharactersList } from '../../../mapInterface/widget
import { useLocalCharactersItemTemplate } from '../../../mapInterface/widgets/LocalCharacters/hooks/useLocalCharacters';
import { useLocalCharacterWidgetSettings } from '../../../mapInterface/widgets/LocalCharacters/hooks/useLocalWidgetSettings';
import classes from './SolarSystemLocalCounter.module.scss';
import { AvailableThemes } from '@/hooks/Mapper/mapRootProvider';
import { useTheme } from '@/hooks/Mapper/hooks/useTheme.ts';
import { AvailableThemes } from '@/hooks/Mapper/mapRootProvider/types.ts';
interface LocalCounterProps {
localCounterCharacters: Array<CharItemProps>;

View File

@@ -355,3 +355,15 @@ $tooltip-bg: #202020;
}
}
}
.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)
}

View File

@@ -4,7 +4,7 @@ import { Handle, NodeProps, Position } from 'reactflow';
import clsx from 'clsx';
import classes from './SolarSystemNodeDefault.module.scss';
import { PrimeIcons } from 'primereact/api';
import { useLocalCounter, useSolarSystemNode, useNodeKillsCount } from '../../hooks';
import { useLocalCounter, useNodeKillsCount, useSolarSystemNode } from '../../hooks';
import {
EFFECT_BACKGROUND_STYLES,
MARKER_BOOKMARK_BG_STYLES,
@@ -14,25 +14,27 @@ import { WormholeClassComp } from '@/hooks/Mapper/components/map/components/Worm
import { UnsplashedSignature } from '@/hooks/Mapper/components/map/components/UnsplashedSignature';
import { LocalCounter } from './SolarSystemLocalCounter';
import { KillsCounter } from './SolarSystemKillsCounter';
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';
// let render = 0;
export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>) => {
const nodeVars = useSolarSystemNode(props);
const { localCounterCharacters } = useLocalCounter(nodeVars);
const localKillsCount = useNodeKillsCount(nodeVars.solarSystemId, nodeVars.killsCount);
// console.log('JOipP', `render ${nodeVars.id}`, render++);
return (
<>
{nodeVars.visible && (
<div className={classes.Bookmarks}>
{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.isShattered && (
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.shattered)}>
<span className={clsx('pi pi-chart-pie', classes.icon)} />
<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>
)}
@@ -40,7 +42,7 @@ export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>
<KillsCounter
killsCount={localKillsCount}
systemId={nodeVars.solarSystemId}
size="lg"
size={TooltipSize.lg}
killsActivityType={nodeVars.killsActivityType}
className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[nodeVars.killsActivityType!])}
>
@@ -51,6 +53,12 @@ export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>
</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}
@@ -82,7 +90,11 @@ export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>
</div>
{nodeVars.tag != null && nodeVars.tag !== '' && (
<div className={clsx(classes.TagTitle, 'text-sky-400 font-medium')}>{nodeVars.tag}</div>
<Tag
value={nodeVars.tag}
severity="warning"
className="py-0 px-[2px] text-[9px] [&_.p-tag-value]:leading-[1.3]"
></Tag>
)}
<div

View File

@@ -14,25 +14,26 @@ import { WormholeClassComp } from '@/hooks/Mapper/components/map/components/Worm
import { UnsplashedSignature } from '@/hooks/Mapper/components/map/components/UnsplashedSignature';
import { LocalCounter } from './SolarSystemLocalCounter';
import { KillsCounter } from './SolarSystemKillsCounter';
import { TooltipPosition, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
import { TooltipSize } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper/utils.ts';
// let render = 0;
export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>) => {
const nodeVars = useSolarSystemNode(props);
const { localCounterCharacters } = useLocalCounter(nodeVars);
const localKillsCount = useNodeKillsCount(nodeVars.solarSystemId, nodeVars.killsCount);
// console.log('JOipP', `render ${nodeVars.id}`, render++);
return (
<>
{nodeVars.visible && (
<div className={classes.Bookmarks}>
{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.isShattered && (
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.shattered)}>
<span className={clsx('pi pi-chart-pie', classes.icon)} />
<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>
)}
@@ -40,7 +41,7 @@ export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>)
<KillsCounter
killsCount={localKillsCount}
systemId={nodeVars.solarSystemId}
size="lg"
size={TooltipSize.lg}
killsActivityType={nodeVars.killsActivityType}
className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[nodeVars.killsActivityType!])}
>
@@ -51,6 +52,12 @@ export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>)
</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}

View File

@@ -16,7 +16,7 @@ export enum SOLAR_SYSTEM_CLASS_IDS {
thera = 12,
c13 = 13,
sentinel = 14,
baribican = 15,
barbican = 15,
vidette = 16,
conflux = 17,
redoubt = 18,
@@ -82,7 +82,7 @@ export const SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS = {
thera: SOLAR_SYSTEM_CLASS_GROUPS.thera,
c13: SOLAR_SYSTEM_CLASS_GROUPS.c13,
sentinel: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
baribican: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
barbican: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
vidette: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
conflux: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
redoubt: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
@@ -217,7 +217,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
wormholeClassID: 14,
effectPower: 2,
title: 'Class 14 (Sentinel Drifter)',
shortTitle: 'Sentinel',
shortTitle: 'Sentinel MZ',
},
{
id: 'barbican',
@@ -225,7 +225,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
wormholeClassID: 15,
effectPower: 2,
title: 'Class 15 (Barbican Drifter)',
shortTitle: 'Barbican',
shortTitle: 'Liberated Barbican',
},
{
id: 'vidette',
@@ -233,7 +233,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
wormholeClassID: 16,
effectPower: 2,
title: 'Class 16 (Vidette Drifter)',
shortTitle: 'Vidette',
shortTitle: 'Sanctified Vidette',
},
{
id: 'conflux',
@@ -241,7 +241,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
wormholeClassID: 17,
effectPower: 2,
title: 'Class 17 (Conflux Drifter)',
shortTitle: 'Conflux',
shortTitle: 'Conflux Eyrie',
},
{
id: 'redoubt',
@@ -249,7 +249,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
wormholeClassID: 18,
effectPower: 2,
title: 'Class 18 (Redoubt Drifter)',
shortTitle: 'Redoubt',
shortTitle: 'Azdaja Redoubt',
},
{
id: 'a1',

View File

@@ -10,7 +10,7 @@ export const isWormholeSpace = (wormholeClassID: number) => {
case SOLAR_SYSTEM_CLASS_IDS.c6:
case SOLAR_SYSTEM_CLASS_IDS.c13:
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.conflux:
case SOLAR_SYSTEM_CLASS_IDS.redoubt:

View File

@@ -1,6 +1,6 @@
import { MapData, useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
import { useCallback, useRef } from 'react';
import { CommandKillsUpdated, CommandMapUpdated } from '@/hooks/Mapper/types';
import { useCallback, useRef } from 'react';
export const useMapCommands = () => {
const { update } = useMapState();
@@ -8,13 +8,21 @@ export const useMapCommands = () => {
const ref = useRef({ update });
ref.current = { update };
const mapUpdated = useCallback(({ hubs }: CommandMapUpdated) => {
const mapUpdated = useCallback(({ hubs, system_signatures, kills }: CommandMapUpdated) => {
const out: Partial<MapData> = {};
if (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);
}, []);

View File

@@ -13,28 +13,26 @@ interface MapEvent {
payload?: Kill[];
}
export function useNodeKillsCount(
systemId: number | string,
initialKillsCount: number | null
): number | null {
export function useNodeKillsCount(systemId: number | string, initialKillsCount: number | null): number | null {
const [killsCount, setKillsCount] = useState<number | null>(initialKillsCount);
useEffect(() => {
setKillsCount(initialKillsCount);
}, [initialKillsCount]);
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') {
setKillsCount(killForSystem.kills);
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') {
setKillsCount(killForSystem.kills);
}
return true;
}
return true;
}
return false;
}, [systemId]);
return false;
},
[systemId],
);
useMapEventListener(handleEvent);

View File

@@ -55,10 +55,14 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
} = data;
const {
interfaceSettings,
storedSettings: { interfaceSettings },
data: { systemSignatures: mapSystemSignatures },
} = useMapRootState();
const systemStaticInfo = useMemo(() => {
return getSystemStaticInfo(solar_system_id)!;
}, [solar_system_id]);
const {
system_class,
security,
@@ -69,9 +73,7 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
region_id,
is_shattered,
solar_system_name,
} = useMemo(() => {
return getSystemStaticInfo(parseInt(solar_system_id))!;
}, [solar_system_id]);
} = systemStaticInfo;
const { isShowUnsplashedSignatures } = interfaceSettings;
const isTempSystemNameEnabled = useMapGetOption('show_temp_system_name') === 'true';
@@ -130,7 +132,7 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
const dbClick = useDoubleClick(() => {
outCommand({
type: OutCommand.openSettings,
data: { system_id: solar_system_id.toString() },
data: { system_id: solar_system_id },
});
});
@@ -142,10 +144,10 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
const { systemName, computedTemporaryName, customName } = useSystemName({
isTempSystemNameEnabled,
temporary_name,
solar_system_name: solar_system_name || '',
isShowLinkedSigIdTempName,
linkedSigPrefix,
name,
systemStaticInfo,
});
const { unsplashedLeft, unsplashedRight } = useUnsplashedSignatures(systemSigs, isShowUnsplashedSignatures);

View File

@@ -1,30 +1,34 @@
// useSystemName.ts
import { useMemo } from 'react';
import { SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
interface UseSystemNameParams {
isTempSystemNameEnabled: boolean;
temporary_name?: string | null;
solar_system_name: string;
isShowLinkedSigIdTempName: boolean;
linkedSigPrefix: string | null;
name?: string | null;
systemStaticInfo: SolarSystemStaticInfoRaw;
}
export function useSystemName({
export const useSystemName = ({
isTempSystemNameEnabled,
temporary_name,
solar_system_name,
isShowLinkedSigIdTempName,
linkedSigPrefix,
name,
}: UseSystemNameParams) {
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]);
@@ -32,6 +36,7 @@ export function useSystemName({
if (isTempSystemNameEnabled && computedTemporaryName) {
return computedTemporaryName;
}
return solar_system_name;
}, [isTempSystemNameEnabled, computedTemporaryName, solar_system_name]);
@@ -39,11 +44,13 @@ export function useSystemName({
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 };
}
};

View File

@@ -1,23 +1,23 @@
import { useCallback, useMemo, useRef } from 'react';
import { Dialog } from 'primereact/dialog';
import { useCallback, useEffect, useMemo, useRef } from 'react';
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
import { CommandLinkSignatureToSystem, SignatureGroup, SystemSignature, TimeStatus } from '@/hooks/Mapper/types';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { SystemSignaturesContent } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent';
import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo';
import { getWhSize } from '@/hooks/Mapper/helpers/getWhSize';
import { useSystemInfo } from '@/hooks/Mapper/components/hooks';
import {
SOLAR_SYSTEM_CLASS_IDS,
SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS,
WORMHOLES_ADDITIONAL_INFO_BY_SHORT_NAME,
} from '@/hooks/Mapper/components/map/constants.ts';
import { K162_TYPES_MAP } from '@/hooks/Mapper/constants.ts';
import {
SETTINGS_KEYS,
SignatureSettingsType,
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
import { SystemSignaturesContent } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent';
import { K162_TYPES_MAP } from '@/hooks/Mapper/constants.ts';
import { getWhSize } from '@/hooks/Mapper/helpers/getWhSize';
import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { CommandLinkSignatureToSystem, SignatureGroup, SystemSignature, TimeStatus } from '@/hooks/Mapper/types';
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
const K162_SIGNATURE_TYPE = WORMHOLES_ADDITIONAL_INFO_BY_SHORT_NAME['K162'].shortName;
@@ -49,7 +49,9 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
ref.current = { outCommand };
// Get system info for the target system
const { staticInfo: targetSystemInfo } = useSystemInfo({ systemId: `${data.solar_system_target}` });
const { staticInfo: targetSystemInfo, dynamicInfo: targetSystemDynamicInfo } = useSystemInfo({
systemId: `${data.solar_system_target}`,
});
// Get the system class group for the target system
const targetSystemClassGroup = useMemo(() => {
@@ -160,6 +162,12 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
[data, setVisible, wormholes],
);
useEffect(() => {
if (!targetSystemDynamicInfo) {
handleHide();
}
}, [targetSystemDynamicInfo]);
return (
<Dialog
header="Select signature to link"

View File

@@ -1,13 +1,14 @@
import { WindowProps } from '@/hooks/Mapper/components/ui-kit/WindowManager/types.ts';
import {
CommentsWidget,
LocalCharacters,
RoutesWidget,
SystemInfo,
SystemSignatures,
SystemStructures,
WRoutesPublic,
WRoutesUser,
WSystemKills,
} from '@/hooks/Mapper/components/mapInterface/widgets';
import { CommentsWidget } from '@/hooks/Mapper/components/mapInterface/widgets/CommentsWidget';
export const CURRENT_WINDOWS_VERSION = 9;
export const WINDOWS_LOCAL_STORE_KEY = 'windows:settings:v2';
@@ -20,6 +21,7 @@ export enum WidgetsIds {
structures = 'structures',
kills = 'kills',
comments = 'comments',
userRoutes = 'userRoutes',
}
export const STORED_VISIBLE_WIDGETS_DEFAULT = [
@@ -56,7 +58,14 @@ export const DEFAULT_WIDGETS: WindowProps[] = [
position: { x: 10, y: 530 },
size: { width: 510, height: 200 },
zIndex: 0,
content: () => <RoutesWidget />,
content: () => <WRoutesPublic />,
},
{
id: WidgetsIds.userRoutes,
position: { x: 10, y: 530 },
size: { width: 510, height: 200 },
zIndex: 0,
content: () => <WRoutesUser />,
},
{
id: WidgetsIds.structures,
@@ -103,6 +112,10 @@ export const WIDGETS_CHECKBOXES_PROPS: WidgetsCheckboxesType = [
id: WidgetsIds.routes,
label: 'Routes',
},
{
id: WidgetsIds.userRoutes,
label: 'User Routes',
},
{
id: WidgetsIds.structures,
label: 'Structures',

View File

@@ -22,7 +22,7 @@ export const LocalCharactersItemTemplate = ({ showShipName, ...options }: LocalC
)}
style={{ height: `${options.props.itemSize}px` }}
>
<CharacterCard showShipName={showShipName} {...options} />
<CharacterCard showShipName={showShipName} showTicker {...options} />
</div>
);
};

View File

@@ -1,79 +1,34 @@
import React, { createContext, useContext, useEffect } from 'react';
import { ContextStoreDataUpdate, useContextStore } from '@/hooks/Mapper/utils';
import { SESSION_KEY } from '@/hooks/Mapper/constants.ts';
import React, { createContext, forwardRef, useContext, useImperativeHandle, useState } from 'react';
import {
RoutesImperativeHandle,
RoutesProviderInnerProps,
RoutesWidgetProps,
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/types.ts';
export type RoutesType = {
path_type: 'shortest' | 'secure' | 'insecure';
include_mass_crit: boolean;
include_eol: boolean;
include_frig: boolean;
include_cruise: boolean;
include_thera: boolean;
avoid_wormholes: boolean;
avoid_pochven: boolean;
avoid_edencom: boolean;
avoid_triglavian: boolean;
avoid: number[];
};
interface MapProviderProps {
type MapProviderProps = {
children: React.ReactNode;
}
} & RoutesWidgetProps;
export const DEFAULT_SETTINGS: RoutesType = {
path_type: 'shortest',
include_mass_crit: true,
include_eol: true,
include_frig: true,
include_cruise: true,
include_thera: true,
avoid_wormholes: false,
avoid_pochven: false,
avoid_edencom: false,
avoid_triglavian: false,
avoid: [],
};
export interface MapContextProps {
update: ContextStoreDataUpdate<RoutesType>;
data: RoutesType;
}
const RoutesContext = createContext<MapContextProps>({
const RoutesContext = createContext<RoutesProviderInnerProps>({
update: () => {},
data: { ...DEFAULT_SETTINGS },
// @ts-ignore
data: {},
});
export const RoutesProvider: React.FC<MapProviderProps> = ({ children }) => {
const { update, ref } = useContextStore<RoutesType>(
{ ...DEFAULT_SETTINGS },
{
onAfterAUpdate: values => {
localStorage.setItem(SESSION_KEY.routes, JSON.stringify(values));
},
export const RoutesProvider = forwardRef<RoutesImperativeHandle, MapProviderProps>(({ children, ...props }, ref) => {
const [loading, setLoading] = useState(false);
useImperativeHandle(ref, () => ({
stopLoading() {
setLoading(false);
},
);
}));
useEffect(() => {
const items = localStorage.getItem(SESSION_KEY.routes);
if (items) {
update(JSON.parse(items));
}
}, [update]);
return (
<RoutesContext.Provider
value={{
update,
data: ref,
}}
>
{children}
</RoutesContext.Provider>
);
};
return <RoutesContext.Provider value={{ ...props, loading, setLoading }}>{children}</RoutesContext.Provider>;
});
RoutesProvider.displayName = 'RoutesProvider';
export const useRouteProvider = () => {
const context = useContext<MapContextProps>(RoutesContext);
const context = useContext<RoutesProviderInnerProps>(RoutesContext);
return context;
};

View File

@@ -2,13 +2,14 @@ import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import {
LayoutEventBlocker,
LoadingWrapper,
SystemViewStandalone,
TooltipPosition,
WdCheckbox,
WdImgButton,
} from '@/hooks/Mapper/components/ui-kit';
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
import { MouseEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { forwardRef, MouseEvent, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { getSystemById } from '@/hooks/Mapper/helpers/getSystemById.ts';
import classes from './RoutesWidget.module.scss';
import { useLoadRoutes } from './hooks';
@@ -25,7 +26,10 @@ import {
AddSystemDialog,
SearchOnSubmitCallback,
} from '@/hooks/Mapper/components/mapInterface/components/AddSystemDialog';
import { OutCommand } from '@/hooks/Mapper/types';
import {
RoutesImperativeHandle,
RoutesWidgetProps,
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/types.ts';
const sortByDist = (a: Route, b: Route) => {
const distA = a.has_connection ? a.systems?.length || 0 : Infinity;
@@ -36,19 +40,16 @@ const sortByDist = (a: Route, b: Route) => {
export const RoutesWidgetContent = () => {
const {
data: { selectedSystems, hubs = [], systems, routes },
outCommand,
data: { selectedSystems, systems, isSubscriptionActive },
} = useMapRootState();
const { hubs = [], routesList, isRestricted } = useRouteProvider();
const [systemId] = selectedSystems;
const { loading } = useLoadRoutes();
const { systems: systemStatics, loadSystems, lastUpdateKey } = useLoadSystemStatic({ systems: hubs ?? [] });
const { open, ...systemCtxProps } = useContextMenuSystemInfoHandlers({
outCommand,
hubs,
});
const { open, ...systemCtxProps } = useContextMenuSystemInfoHandlers();
const preparedHubs = useMemo(() => {
return hubs.map(x => {
@@ -61,20 +62,20 @@ export const RoutesWidgetContent = () => {
const preparedRoutes: Route[] = useMemo(() => {
return (
routes?.routes
routesList?.routes
.sort(sortByDist)
.filter(x => x.destination.toString() !== systemId)
// .filter(x => x.destination.toString() !== systemId)
.map(route => ({
...route,
mapped_systems:
route.systems?.map(solar_system_id =>
routes?.systems_static_data.find(
routesList?.systems_static_data.find(
system_static_data => system_static_data.solar_system_id === solar_system_id,
),
) ?? [],
})) ?? []
);
}, [routes?.routes, routes?.systems_static_data, systemId]);
}, [routesList?.routes, routesList?.systems_static_data, systemId]);
const refData = useRef({ open, loadSystems, preparedRoutes });
refData.current = { open, loadSystems, preparedRoutes };
@@ -97,9 +98,13 @@ export const RoutesWidgetContent = () => {
[handleClick],
);
if (loading) {
if (isRestricted && !isSubscriptionActive) {
return (
<div className="w-full h-full flex justify-center items-center select-none text-center">Loading routes...</div>
<div className="w-full h-full flex items-center justify-center">
<span className="select-none text-center text-stone-400/80 text-sm">
User Routes available with &#39;Active&#39; map subscription only (contact map administrators)
</span>
</div>
);
}
@@ -117,7 +122,7 @@ export const RoutesWidgetContent = () => {
return (
<>
{systemId !== undefined && routes && (
<LoadingWrapper loading={loading}>
<div className={clsx(classes.RoutesGrid, 'px-2 py-2')}>
{preparedRoutes.map(route => {
const sys = preparedHubs.find(x => x.solar_system_id === route.destination)!;
@@ -132,7 +137,11 @@ export const RoutesWidgetContent = () => {
<WdImgButton
className={clsx(PrimeIcons.BARS, classes.RemoveBtn)}
onClick={e => handleClick(e, route.destination.toString())}
tooltip={{ content: 'Click here to open system menu', position: TooltipPosition.top, offset: 10 }}
tooltip={{
content: 'Click here to open system menu',
position: TooltipPosition.top,
offset: 10,
}}
/>
<SystemViewStandalone
@@ -151,7 +160,7 @@ export const RoutesWidgetContent = () => {
);
})}
</div>
)}
</LoadingWrapper>
<ContextMenuSystemInfo
hubs={hubs}
@@ -165,15 +174,13 @@ export const RoutesWidgetContent = () => {
);
};
export const RoutesWidgetComp = () => {
const [routeSettingsVisible, setRouteSettingsVisible] = useState(false);
const { data, update } = useRouteProvider();
const {
data: { hubs = [] },
outCommand,
} = useMapRootState();
type RoutesWidgetCompProps = {
title: ReactNode | string;
};
const preparedHubs = useMemo(() => hubs.map(x => parseInt(x)), [hubs]);
export const RoutesWidgetComp = ({ title }: RoutesWidgetCompProps) => {
const [routeSettingsVisible, setRouteSettingsVisible] = useState(false);
const { data, update, addHubCommand } = useRouteProvider();
const isSecure = data.path_type === 'secure';
const handleSecureChange = useCallback(() => {
@@ -190,24 +197,15 @@ export const RoutesWidgetComp = () => {
const onAddSystem = useCallback(() => setOpenAddSystem(true), []);
const handleSubmitAddSystem: SearchOnSubmitCallback = useCallback(
async item => {
if (preparedHubs.includes(item.value)) {
return;
}
await outCommand({
type: OutCommand.addHub,
data: { system_id: item.value },
});
},
[hubs, outCommand],
async item => addHubCommand(item.value.toString()),
[addHubCommand],
);
return (
<Widget
label={
<div className="flex justify-between items-center text-xs w-full" ref={ref}>
<span className="select-none">Routes</span>
<span className="select-none">{title}</span>
<LayoutEventBlocker className="flex items-center gap-2">
<WdImgButton
className={PrimeIcons.PLUS_CIRCLE}
@@ -231,6 +229,7 @@ export const RoutesWidgetComp = () => {
className={PrimeIcons.SLIDERS_H}
onClick={() => setRouteSettingsVisible(true)}
tooltip={{
position: TooltipPosition.top,
content: 'Click here to open Routes settings',
}}
/>
@@ -251,10 +250,13 @@ export const RoutesWidgetComp = () => {
);
};
export const RoutesWidget = () => {
return (
<RoutesProvider>
<RoutesWidgetComp />
</RoutesProvider>
);
};
export const RoutesWidget = forwardRef<RoutesImperativeHandle, RoutesWidgetProps & RoutesWidgetCompProps>(
({ title, ...props }, ref) => {
return (
<RoutesProvider {...props} ref={ref}>
<RoutesWidgetComp title={title} />
</RoutesProvider>
);
},
);
RoutesWidget.displayName = 'RoutesWidget';

View File

@@ -1,10 +1,7 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { OutCommand } from '@/hooks/Mapper/types';
import { useCallback, useEffect, useRef } from 'react';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import {
RoutesType,
useRouteProvider,
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesProvider.tsx';
import { useRouteProvider } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesProvider.tsx';
import { RoutesType } from '@/hooks/Mapper/mapRootProvider/types.ts';
function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T>();
@@ -17,12 +14,10 @@ function usePrevious<T>(value: T): T | undefined {
}
export const useLoadRoutes = () => {
const [loading, setLoading] = useState(false);
const { data: routesSettings } = useRouteProvider();
const { data: routesSettings, loadRoutesCommand, hubs, routesList, loading, setLoading } = useRouteProvider();
const {
outCommand,
data: { selectedSystems, hubs, systems, connections },
data: { selectedSystems, systems, connections },
} = useMapRootState();
const prevSys = usePrevious(systems);
@@ -31,17 +26,16 @@ export const useLoadRoutes = () => {
const loadRoutes = useCallback(
(systemId: string, routesSettings: RoutesType) => {
outCommand({
type: OutCommand.getRoutes,
data: {
system_id: systemId,
routes_settings: routesSettings,
},
});
loadRoutesCommand(systemId, routesSettings);
setLoading(true);
},
[outCommand],
[loadRoutesCommand],
);
useEffect(() => {
setLoading(false);
}, [routesList]);
useEffect(() => {
if (selectedSystems.length !== 1) {
return;

View File

@@ -0,0 +1,27 @@
import { RoutesType } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { RoutesList } from '@/hooks/Mapper/types/routes.ts';
export type LoadRoutesCommand = (systemId: string, routesSettings: RoutesType) => Promise<void>;
export type AddHubCommand = (systemId: string) => Promise<void>;
export type ToggleHubCommand = (systemId: string) => Promise<void>;
export type RoutesWidgetProps = {
data: RoutesType;
update: (d: RoutesType) => void;
hubs: string[];
routesList: RoutesList | undefined;
loadRoutesCommand: LoadRoutesCommand;
addHubCommand: AddHubCommand;
toggleHubCommand: ToggleHubCommand;
isRestricted?: boolean;
};
export type RoutesProviderInnerProps = RoutesWidgetProps & {
loading: boolean;
setLoading(loading: boolean): void;
};
export type RoutesImperativeHandle = {
stopLoading: () => void;
};

View File

@@ -1,9 +1,9 @@
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { LayoutEventBlocker, SystemView, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
import { LayoutEventBlocker, SystemView, TooltipPosition, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
import { SystemInfoContent } from './SystemInfoContent';
import { PrimeIcons } from 'primereact/api';
import { useState, useCallback } from 'react';
import { useCallback, useState } from 'react';
import { SystemSettingsDialog } from '@/hooks/Mapper/components/mapInterface/components/SystemSettingsDialog/SystemSettingsDialog.tsx';
import { ANOIK_ICON, DOTLAN_ICON, ZKB_ICON } from '@/hooks/Mapper/icons';
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
@@ -42,7 +42,7 @@ export const SystemInfo = () => {
<WdImgButton
className="pi pi-pen-to-square"
onClick={() => setVisible(true)}
tooltip={{ content: 'Edit system name and description' }}
tooltip={{ position: TooltipPosition.top, content: 'Edit system name and description' }}
/>
</LayoutEventBlocker>
</div>

View File

@@ -1,16 +1,16 @@
import { useCallback, useEffect, useState } from 'react';
import useRefState from 'react-usestateref';
import { useMapEventListener } from '@/hooks/Mapper/events';
import { parseSignatures } from '@/hooks/Mapper/helpers';
import { Commands, ExtendedSystemSignature, SignatureKind } from '@/hooks/Mapper/types';
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
import { parseSignatures } from '@/hooks/Mapper/helpers';
import { useCallback, useEffect, useState } from 'react';
import useRefState from 'react-usestateref';
import { getActualSigs } from '../helpers';
import { useSignatureFetching } from './useSignatureFetching';
import { usePendingDeletions } from './usePendingDeletions';
import { UseSystemSignaturesDataProps } from './types';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { SETTINGS_KEYS } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { getActualSigs } from '../helpers';
import { UseSystemSignaturesDataProps } from './types';
import { usePendingDeletions } from './usePendingDeletions';
import { useSignatureFetching } from './useSignatureFetching';
export const useSystemSignaturesData = ({
systemId,
@@ -47,6 +47,10 @@ export const useSystemSignaturesData = ({
Object.keys(settings).filter(skey => skey in SignatureKind),
) as ExtendedSystemSignature[];
if (incomingSignatures.length === 0) {
return;
}
const currentNonPending = lazyDeleteValue
? signaturesRef.current.filter(sig => !sig.pendingDeletion)
: signaturesRef.current.filter(sig => !sig.pendingDeletion || !sig.pendingAddition);

View File

@@ -0,0 +1,83 @@
import { Commands, OutCommand } from '@/hooks/Mapper/types';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import {
AddHubCommand,
LoadRoutesCommand,
RoutesImperativeHandle,
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/types.ts';
import { useCallback, useRef } from 'react';
import { RoutesWidget } from '@/hooks/Mapper/components/mapInterface/widgets';
import { useMapEventListener } from '@/hooks/Mapper/events';
export const WRoutesPublic = () => {
const {
outCommand,
storedSettings: { settingsRoutes, settingsRoutesUpdate },
data: { hubs, routes },
} = useMapRootState();
const ref = useRef<RoutesImperativeHandle>(null);
const loadRoutesCommand: LoadRoutesCommand = useCallback(
async (systemId, routesSettings) => {
outCommand({
type: OutCommand.getRoutes,
data: {
system_id: systemId,
routes_settings: routesSettings,
},
});
},
[outCommand],
);
const addHubCommand: AddHubCommand = useCallback(
async systemId => {
if (hubs.includes(systemId)) {
return;
}
await outCommand({
type: OutCommand.addHub,
data: { system_id: systemId },
});
},
[hubs, outCommand],
);
const toggleHubCommand: AddHubCommand = useCallback(
async (systemId: string | undefined) => {
if (!systemId) {
return;
}
outCommand({
type: !hubs.includes(systemId) ? OutCommand.addHub : OutCommand.deleteHub,
data: {
system_id: systemId,
},
});
},
[hubs, outCommand],
);
useMapEventListener(event => {
if (event.name === Commands.routes) {
ref.current?.stopLoading();
}
});
return (
<RoutesWidget
ref={ref}
title="Routes"
data={settingsRoutes}
update={settingsRoutesUpdate}
hubs={hubs}
routesList={routes}
loadRoutesCommand={loadRoutesCommand}
addHubCommand={addHubCommand}
toggleHubCommand={toggleHubCommand}
/>
);
};

View File

@@ -0,0 +1 @@
export * from './WRoutesPublic';

View File

@@ -0,0 +1,85 @@
import { Commands, OutCommand } from '@/hooks/Mapper/types';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import {
AddHubCommand,
LoadRoutesCommand,
RoutesImperativeHandle,
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/types.ts';
import { useCallback, useRef } from 'react';
import { RoutesWidget } from '@/hooks/Mapper/components/mapInterface/widgets';
import { useMapEventListener } from '@/hooks/Mapper/events';
export const WRoutesUser = () => {
const {
outCommand,
storedSettings: { settingsRoutes, settingsRoutesUpdate },
data: { userHubs, userRoutes },
} = useMapRootState();
const ref = useRef<RoutesImperativeHandle>(null);
const loadRoutesCommand: LoadRoutesCommand = useCallback(
async (systemId, routesSettings) => {
outCommand({
type: OutCommand.getUserRoutes,
data: {
system_id: systemId,
routes_settings: routesSettings,
},
});
},
[outCommand],
);
const addHubCommand: AddHubCommand = useCallback(
async systemId => {
if (userHubs.includes(systemId)) {
return;
}
await outCommand({
type: OutCommand.addUserHub,
data: { system_id: systemId },
});
},
[userHubs, outCommand],
);
const toggleHubCommand: AddHubCommand = useCallback(
async (systemId: string | undefined) => {
if (!systemId) {
return;
}
outCommand({
type: !userHubs.includes(systemId) ? OutCommand.addUserHub : OutCommand.deleteUserHub,
data: {
system_id: systemId,
},
});
},
[userHubs, outCommand],
);
useMapEventListener(event => {
if (event.name === Commands.userRoutes) {
ref.current?.stopLoading();
}
return true;
});
return (
<RoutesWidget
ref={ref}
title="User Routes"
data={settingsRoutes}
update={settingsRoutesUpdate}
hubs={userHubs}
routesList={userRoutes}
loadRoutesCommand={loadRoutesCommand}
addHubCommand={addHubCommand}
toggleHubCommand={toggleHubCommand}
isRestricted
/>
);
};

View File

@@ -0,0 +1 @@
export * from './WRoutesUser';

View File

@@ -4,3 +4,6 @@ export * from './RoutesWidget';
export * from './SystemSignatures';
export * from './SystemStructures';
export * from './WSystemKills';
export * from './WRoutesUser';
export * from './WRoutesPublic';
export * from './CommentsWidget';

View File

@@ -18,7 +18,10 @@ export interface MapRootContentProps {}
// eslint-disable-next-line no-empty-pattern
export const MapRootContent = ({}: MapRootContentProps) => {
const { interfaceSettings, data } = useMapRootState();
const {
storedSettings: { interfaceSettings },
data,
} = useMapRootState();
const { isShowMenu } = interfaceSettings;
const { showCharacterActivity } = data;
const { handleHideCharacterActivity } = useCharacterActivityHandlers();

View File

@@ -8,10 +8,15 @@ interface CharacterActivityProps {
export const CharacterActivity = ({ visible, onHide }: CharacterActivityProps) => {
return (
<Dialog header="Character Activity" visible={visible} className="w-[550px]" onHide={onHide} dismissableMask>
<div className="w-full h-[500px] flex flex-col overflow-hidden p-0 m-0">
<CharacterActivityContent />
</div>
<Dialog
header="Character Activity"
visible={visible}
className="w-[550px] max-h-[90vh]"
onHide={onHide}
dismissableMask
contentClassName="p-0 h-full flex flex-col"
>
<CharacterActivityContent />
</Dialog>
);
};

View File

@@ -31,41 +31,49 @@ export const CharacterActivityContent = () => {
}
return (
<DataTable
value={activity}
scrollable
className="w-full"
tableClassName="w-full border-0"
emptyMessage="No character activity data available"
sortField="passages"
sortOrder={-1}
size="small"
rowClassName={getRowClassName}
rowHover
>
<Column field="character_name" header="Character" body={renderCharacterTemplate} sortable className="!py-[6px]" />
<div className="w-full h-full overflow-auto custom-scrollbar">
<DataTable
value={activity}
scrollable
className="w-full"
tableClassName="w-full border-0"
emptyMessage="No character activity data available"
sortField="passages"
sortOrder={-1}
size="small"
rowClassName={getRowClassName}
rowHover
>
<Column
field="character_name"
header="Character"
body={renderCharacterTemplate}
sortable
className="!py-[6px]"
/>
<Column
field="passages"
header="Passages"
headerClassName="[&_.p-column-header-content]:justify-center"
body={rowData => renderValueTemplate(rowData, 'passages')}
sortable
/>
<Column
field="connections"
header="Connections"
headerClassName="[&_.p-column-header-content]:justify-center"
body={rowData => renderValueTemplate(rowData, 'connections')}
sortable
/>
<Column
field="signatures"
header="Signatures"
headerClassName="[&_.p-column-header-content]:justify-center"
body={rowData => renderValueTemplate(rowData, 'signatures')}
sortable
/>
</DataTable>
<Column
field="passages"
header="Passages"
headerClassName="[&_.p-column-header-content]:justify-center"
body={rowData => renderValueTemplate(rowData, 'passages')}
sortable
/>
<Column
field="connections"
header="Connections"
headerClassName="[&_.p-column-header-content]:justify-center"
body={rowData => renderValueTemplate(rowData, 'connections')}
sortable
/>
<Column
field="signatures"
header="Signatures"
headerClassName="[&_.p-column-header-content]:justify-center"
body={rowData => renderValueTemplate(rowData, 'signatures')}
sortable
/>
</DataTable>
</div>
);
};

View File

@@ -31,13 +31,16 @@ type MapSettingsContextType = {
const MapSettingsContext = createContext<MapSettingsContextType | undefined>(undefined);
export const MapSettingsProvider = ({ children }: { children: ReactNode }) => {
const { outCommand, interfaceSettings, setInterfaceSettings } = useMapRootState();
const {
outCommand,
storedSettings: { interfaceSettings, setInterfaceSettings },
} = useMapRootState();
const [userRemoteSettings, setUserRemoteSettings] = useState<UserSettingsRemote>({
...DEFAULT_REMOTE_SETTINGS,
});
const mergedSettings = useMemo(() => {
const mergedSettings: UserSettings = useMemo(() => {
return {
...userRemoteSettings,
...interfaceSettings,
@@ -75,7 +78,7 @@ export const MapSettingsProvider = ({ children }: { children: ReactNode }) => {
if (item.type === 'checkbox') {
return (
<PrettySwitchbox
key={item.prop}
key={item.prop.toString()}
label={item.label}
checked={!!currentValue}
setChecked={checked => handleSettingChange(item.prop, checked)}
@@ -85,7 +88,7 @@ export const MapSettingsProvider = ({ children }: { children: ReactNode }) => {
if (item.type === 'dropdown' && item.options) {
return (
<div key={item.prop} className="flex items-center gap-2 mt-2">
<div key={item.prop.toString()} className="flex items-center gap-2 mt-2">
<label className="text-sm">{item.label}:</label>
<Dropdown
className="text-sm"

View File

@@ -1,5 +1,6 @@
import { SettingsListItem, UserSettingsRemoteProps } from './types.ts';
import { AvailableThemes, InterfaceStoredSettingsProps } from '@/hooks/Mapper/mapRootProvider';
import { InterfaceStoredSettingsProps } from '@/hooks/Mapper/mapRootProvider';
import { AvailableThemes } from '@/hooks/Mapper/mapRootProvider/types.ts';
export const DEFAULT_REMOTE_SETTINGS = {
[UserSettingsRemoteProps.link_signature_on_splash]: false,

View File

@@ -3,7 +3,7 @@
}
.SidebarOnTheMap {
width: 400px;
width: 500px;
padding: 0 !important;
:global {

View File

@@ -1,15 +1,17 @@
import classes from './OnTheMap.module.scss';
import { Sidebar } from 'primereact/sidebar';
import { useMemo } from 'react';
import { useMemo, useState } from 'react';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { sortCharacters } from '@/hooks/Mapper/components/mapInterface/helpers/sortCharacters.ts';
import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
import clsx from 'clsx';
import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types';
import { CharacterCard, WdCheckbox } from '@/hooks/Mapper/components/ui-kit';
import { CharacterCard, TooltipPosition, WdCheckbox, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
import useLocalStorageState from 'use-local-storage-state';
import { useMapCheckPermissions, useMapGetOption } from '@/hooks/Mapper/mapRootProvider/hooks/api';
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
import { InputText } from 'primereact/inputtext';
import { IconField } from 'primereact/iconfield';
type WindowLocalSettingsType = {
compact: boolean;
@@ -33,7 +35,7 @@ const itemTemplate = (item: CharacterTypeRaw & WithIsOwnCharacter, options: Virt
})}
style={{ height: options.props.itemSize + 'px' }}
>
<CharacterCard showSystem {...item} />
<CharacterCard showCorporationLogo showAllyLogo showSystem showTicker {...item} />
</div>
);
};
@@ -48,6 +50,8 @@ export const OnTheMap = ({ show, onHide }: OnTheMapProps) => {
data: { characters, userCharacters },
} = useMapRootState();
const [searchVal, setSearchVal] = useState('');
const [settings, setSettings] = useLocalStorageState<WindowLocalSettingsType>('window:onTheMap:settings', {
defaultValue: STORED_DEFAULT_VALUES,
});
@@ -61,13 +65,54 @@ export const OnTheMap = ({ show, onHide }: OnTheMapProps) => {
);
const sorted = useMemo(() => {
const out = characters.map(x => ({ ...x, isOwn: userCharacters.includes(x.eve_id) })).sort(sortCharacters);
let out = characters.map(x => ({ ...x, isOwn: userCharacters.includes(x.eve_id) })).sort(sortCharacters);
if (searchVal !== '') {
out = out.filter(x => {
const normalized = searchVal.toLowerCase();
if (x.name.toLowerCase().includes(normalized)) {
return true;
}
if (x.corporation_name.toLowerCase().includes(normalized)) {
return true;
}
if (x.alliance_name?.toLowerCase().includes(normalized)) {
return true;
}
if (x.corporation_ticker.toLowerCase().includes(normalized)) {
return true;
}
if (x.alliance_ticker?.toLowerCase().includes(normalized)) {
return true;
}
if (x.ship?.ship_name?.toLowerCase().includes(normalized)) {
return true;
}
if (x.ship?.ship_type_info.name?.toLowerCase().includes(normalized)) {
return true;
}
if (x.ship?.ship_type_info.group_name?.toLowerCase().includes(normalized)) {
return true;
}
return false;
});
}
if (showOffline && !settings.hideOffline) {
return out;
}
return out.filter(x => x.online);
}, [showOffline, characters, settings.hideOffline, userCharacters]);
}, [showOffline, searchVal, characters, settings.hideOffline, userCharacters]);
return (
<Sidebar
@@ -79,7 +124,30 @@ export const OnTheMap = ({ show, onHide }: OnTheMapProps) => {
icons={<></>}
>
<div className={clsx(classes.SidebarContent, '')}>
<div className={'flex justify-end items-center gap-2 px-3'}>
<div className={'flex justify-between items-center gap-2 px-2 pt-1'}>
<IconField>
{searchVal.length > 0 && (
<WdImgButton
className="pi pi-trash"
textSize={WdImageSize.large}
tooltip={{
content: 'Clear',
className: 'pi p-input-icon',
position: TooltipPosition.top,
}}
onClick={() => setSearchVal('')}
/>
)}
<InputText
id="label"
aria-describedby="label"
autoComplete="off"
value={searchVal}
placeholder="Type to search"
onChange={e => setSearchVal(e.target.value)}
/>
</IconField>
{showOffline && (
<WdCheckbox
size="m"

View File

@@ -15,7 +15,9 @@ interface RightBarProps {
}
export const RightBar = ({ onShowOnTheMap, onShowMapSettings, onShowTrackingDialog }: RightBarProps) => {
const { interfaceSettings, setInterfaceSettings } = useMapRootState();
const {
storedSettings: { interfaceSettings, setInterfaceSettings },
} = useMapRootState();
const canTrackCharacters = useMapCheckPermissions([UserPermission.TRACK_CHARACTER]);

View File

@@ -31,6 +31,7 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
const signatureForm = useForm<Partial<SystemSignaturePrepared>>({});
const handleSave = useCallback(
// TODO: need fix
async (e: any) => {
e?.preventDefault();
if (!signatureData) {
@@ -52,6 +53,7 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
},
});
// TODO: need fix
if (values.isEOL) {
await outCommand({
type: OutCommand.updateConnectionTimeStatus,
@@ -81,7 +83,9 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
out = {
...out,
custom_info: JSON.stringify({
// TODO: need fix
k162Type: values.k162Type,
// TODO: need fix
isEOL: values.isEOL,
}),
};
@@ -145,7 +149,7 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
signatureForm.reset();
onHide();
},
[signatureForm, onHide, outCommand, signatureData, systemId],
[signatureData, signatureForm, outCommand, systemId, onHide, wormholes],
);
useEffect(() => {
@@ -166,6 +170,7 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
signatureForm.reset({
linked_system: linked_system?.solar_system_id.toString() ?? undefined,
// TODO: need fix
k162Type: k162Type,
isEOL: isEOL,
...rest,

View File

@@ -44,7 +44,7 @@ export const TrackingCharactersList = () => {
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
headerClassName="[&_div]:ml-2"
body={row => {
return <CharacterCard showShipName={false} showSystem={false} isOwn {...row.character} />;
return <CharacterCard showCorporationLogo showTicker isOwn {...row.character} />;
}}
/>
</DataTable>

View File

@@ -10,8 +10,8 @@ const renderValCharacterTemplate = (row: TrackingCharacter | undefined) => {
}
return (
<div className="py-1">
<CharacterCard compact showShipName={false} showSystem={false} isOwn {...row.character} />
<div className="py-1 w-full">
<CharacterCard compact isOwn {...row.character} />
</div>
);
};
@@ -21,7 +21,11 @@ const renderCharacterTemplate = (row: TrackingCharacter | undefined) => {
return <div className="h-[33px] flex items-center">Character is not selected</div>;
}
return <CharacterCard showShipName={false} showSystem={false} isOwn {...row.character} />;
return (
<div className="w-full">
<CharacterCard isOwn {...row.character} />
</div>
);
};
export const TrackingSettings = () => {

View File

@@ -1,7 +1,7 @@
import { useCallback } from 'react';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
import type { ActivitySummary } from '@/hooks/Mapper/components/mapRootContent/components/CharacterActivity/CharacterActivity';
import { ActivitySummary } from '@/hooks/Mapper/types';
/**
* Hook for character activity related handlers

View File

@@ -20,7 +20,6 @@ import { Node, useReactFlow, XYPosition } from 'reactflow';
import { useCommandsSystems } from '@/hooks/Mapper/mapRootProvider/hooks/api';
import { emitMapEvent, useMapEventListener } from '@/hooks/Mapper/events';
import { STORED_INTERFACE_DEFAULT_VALUES } from '@/hooks/Mapper/mapRootProvider/MapRootProvider';
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
import { useCommonMapEventProcessor } from '@/hooks/Mapper/components/mapWrapper/hooks/useCommonMapEventProcessor.ts';
import {
@@ -28,30 +27,34 @@ import {
SearchOnSubmitCallback,
} from '@/hooks/Mapper/components/mapInterface/components/AddSystemDialog';
import { useHotkey } from '../../hooks/useHotkey';
import { STORED_INTERFACE_DEFAULT_VALUES } from '@/hooks/Mapper/mapRootProvider/constants.ts';
// TODO: INFO - this component needs for abstract work with Map instance
export const MapWrapper = () => {
const {
update,
outCommand,
data: { selectedConnections, selectedSystems, hubs, systems, linkSignatureToSystem, systemSignatures },
interfaceSettings: {
isShowMenu,
isShowMinimap = STORED_INTERFACE_DEFAULT_VALUES.isShowMinimap,
isShowKSpace,
isThickConnections,
isShowBackgroundPattern,
isShowUnsplashedSignatures,
isSoftBackground,
theme,
},
data: { selectedConnections, selectedSystems, hubs, userHubs, systems, linkSignatureToSystem, systemSignatures },
storedSettings: { interfaceSettings },
} = useMapRootState();
const {
isShowMenu,
isShowMinimap = STORED_INTERFACE_DEFAULT_VALUES.isShowMinimap,
isShowKSpace,
isThickConnections,
isShowBackgroundPattern,
isShowUnsplashedSignatures,
isSoftBackground,
theme,
} = interfaceSettings;
const { deleteSystems } = useDeleteSystems();
const { mapRef, runCommand } = useCommonMapEventProcessor();
const { getNodes } = useReactFlow();
const { updateLinkSignatureToSystem } = useCommandsSystems();
const { open, ...systemContextProps } = useContextMenuSystemHandlers({ systems, hubs, outCommand });
const { open, ...systemContextProps } = useContextMenuSystemHandlers({ systems, hubs, userHubs, outCommand });
const { handleSystemMultipleContext, ...systemMultipleCtxProps } = useContextMenuSystemMultipleHandlers();
const [openSettings, setOpenSettings] = useState<string | null>(null);
@@ -107,17 +110,20 @@ export const MapWrapper = () => {
[outCommand],
);
const handleSystemContextMenu = useCallback((ev: any, systemId: string) => {
const { selectedSystems, systems } = ref.current;
if (selectedSystems.length > 1) {
const systemsInfo: Node[] = selectedSystems.map(x => ({ data: getSystemById(systems, x), id: x }) as Node);
const handleSystemContextMenu = useCallback(
(ev: any, systemId: string) => {
const { selectedSystems, systems } = ref.current;
if (selectedSystems.length > 1) {
const systemsInfo: Node[] = selectedSystems.map(x => ({ data: getSystemById(systems, x), id: x }) as Node);
handleSystemMultipleContext(ev, systemsInfo);
return;
}
handleSystemMultipleContext(ev, systemsInfo);
return;
}
open(ev, systemId);
}, []);
open(ev, systemId);
},
[handleSystemMultipleContext, open],
);
const handleConnectionDbClick = useCallback((e: SolarSystemConnection) => setSelectedConnection(e), []);
@@ -215,6 +221,7 @@ export const MapWrapper = () => {
<ContextMenuSystem
systems={systems}
hubs={hubs}
userHubs={userHubs}
{...systemContextProps}
onOpenSettings={() => {
systemContextProps.systemId && setOpenSettings(systemContextProps.systemId);

View File

@@ -4,15 +4,24 @@ import { SystemView } from '@/hooks/Mapper/components/ui-kit/SystemView';
import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types';
import { Commands } from '@/hooks/Mapper/types/mapHandlers';
import { emitMapEvent } from '@/hooks/Mapper/events';
import { CharacterPortrait, CharacterPortraitSize } from '@/hooks/Mapper/components/ui-kit';
import {
TooltipPosition,
WdEveEntityPortrait,
WdEveEntityPortraitSize,
WdEveEntityPortraitType,
WdTooltipWrapper,
} from '@/hooks/Mapper/components/ui-kit';
import { isDocked } from '@/hooks/Mapper/helpers/isDocked.ts';
import classes from './CharacterCard.module.scss';
type CharacterCardProps = {
compact?: boolean;
showSystem?: boolean;
showTicker?: boolean;
showShipName?: boolean;
useSystemsCache?: boolean;
showCorporationLogo?: boolean;
showAllyLogo?: boolean;
} & CharacterTypeRaw &
WithIsOwnCharacter;
@@ -29,6 +38,9 @@ export const CharacterCard = ({
isOwn,
showSystem,
showShipName,
showCorporationLogo,
showAllyLogo,
showTicker,
useSystemsCache,
...char
}: CharacterCardProps) => {
@@ -46,26 +58,80 @@ export const CharacterCard = ({
if (compact) {
return (
<div className={clsx('w-full text-xs box-border')} onClick={handleSelect}>
<div className="text-xs box-border w-full" onClick={handleSelect}>
<div className="w-full flex items-center gap-1 relative">
<CharacterPortrait characterEveId={char.eve_id} size={CharacterPortraitSize.w18} />
<WdEveEntityPortrait eveId={char.eve_id} size={WdEveEntityPortraitSize.w18} />
{showCorporationLogo && (
<WdTooltipWrapper position={TooltipPosition.top} content={char.corporation_name}>
<WdEveEntityPortrait
type={WdEveEntityPortraitType.corporation}
eveId={char.corporation_id.toString()}
size={WdEveEntityPortraitSize.w18}
/>
</WdTooltipWrapper>
)}
{showAllyLogo && char.alliance_id && (
<WdTooltipWrapper position={TooltipPosition.top} content={char.alliance_name}>
<WdEveEntityPortrait
type={WdEveEntityPortraitType.alliance}
eveId={char.alliance_id.toString()}
size={WdEveEntityPortraitSize.w18}
/>
</WdTooltipWrapper>
)}
{isDocked(char.location) && <span className={classes.Docked} />}
<div className="flex flex-grow overflow-hidden text-left">
<div className="overflow-hidden text-ellipsis whitespace-nowrap">
<span className={clsx(isOwn ? 'text-orange-400' : 'text-gray-200')}>{char.name}</span>{' '}
<span className="text-gray-400">
{!locationShown && showShipName && shipNameText ? `- ${shipNameText}` : `[${tickerText}]`}
<div className="flex flex-grow-[2] overflow-hidden text-left w-[50px]">
<div className="flex min-w-0">
<span
className={clsx(
'overflow-hidden text-ellipsis whitespace-nowrap',
isOwn ? 'text-orange-400' : 'text-gray-200',
)}
title={char.name}
>
{char.name}
</span>
{showTicker && <span className="flex-shrink-0 text-gray-400 ml-1">[{tickerText}]</span>}
</div>
</div>
{shipType && (
<div
className="text-gray-300 overflow-hidden text-ellipsis whitespace-nowrap flex-shrink-0"
style={{ maxWidth: '120px' }}
title={shipType}
>
{shipType}
</div>
<>
{!showShipName && (
<div
className="text-gray-300 overflow-hidden text-ellipsis whitespace-nowrap flex-shrink-0"
style={{ maxWidth: '120px' }}
title={shipType}
>
{shipType}
</div>
)}
{showShipName && (
<div className="flex flex-grow-[1] justify-end w-[50px]">
<div className="min-w-0">
<div
className="text-gray-300 overflow-hidden text-ellipsis whitespace-nowrap flex-shrink-0"
style={{ maxWidth: '120px' }}
title={shipNameText}
>
{shipNameText}
</div>
</div>
</div>
)}
{char.ship && (
<WdTooltipWrapper position={TooltipPosition.top} content={char.ship.ship_type_info.name}>
<WdEveEntityPortrait
type={WdEveEntityPortraitType.ship}
eveId={char.ship.ship_type_id.toString()}
size={WdEveEntityPortraitSize.w18}
/>
</WdTooltipWrapper>
)}
</>
)}
</div>
</div>
@@ -75,11 +141,41 @@ export const CharacterCard = ({
return (
<div className={clsx('w-full text-xs box-border')} onClick={handleSelect}>
<div className="w-full flex items-center gap-2">
<CharacterPortrait characterEveId={char.eve_id} size={CharacterPortraitSize.w33} />
<div className="flex flex-col flex-grow overflow-hidden">
<div className="overflow-hidden text-ellipsis whitespace-nowrap">
<span className={clsx(isOwn ? 'text-orange-400' : 'text-gray-200')}>{char.name}</span>{' '}
<span className="text-gray-400">[{tickerText}]</span>
<div className="flex items-center gap-1">
<WdEveEntityPortrait eveId={char.eve_id} size={WdEveEntityPortraitSize.w33} />
{showCorporationLogo && (
<WdTooltipWrapper position={TooltipPosition.top} content={char.corporation_name}>
<WdEveEntityPortrait
type={WdEveEntityPortraitType.corporation}
eveId={char.corporation_id.toString()}
size={WdEveEntityPortraitSize.w33}
/>
</WdTooltipWrapper>
)}
{showAllyLogo && char.alliance_id && (
<WdTooltipWrapper position={TooltipPosition.top} content={char.alliance_name}>
<WdEveEntityPortrait
type={WdEveEntityPortraitType.alliance}
eveId={char.alliance_id.toString()}
size={WdEveEntityPortraitSize.w33}
/>
</WdTooltipWrapper>
)}
</div>
<div className="flex flex-col flex-grow overflow-hidden w-[50px]">
<div className="flex min-w-0">
<span
className={clsx(
'overflow-hidden text-ellipsis whitespace-nowrap',
isOwn ? 'text-orange-400' : 'text-gray-200',
)}
>
{char.name}
</span>
{showTicker && <span className="flex-shrink-0 text-gray-400 ml-1">[{tickerText}]</span>}
</div>
{locationShown ? (
<div className="text-gray-300 text-xs overflow-hidden text-ellipsis whitespace-nowrap">
@@ -97,15 +193,30 @@ export const CharacterCard = ({
)}
</div>
{shipType && (
<div className="flex-shrink-0 self-start">
<div
className="text-gray-300 overflow-hidden text-ellipsis whitespace-nowrap"
style={{ maxWidth: '200px' }}
title={shipType}
>
{shipType}
<>
<div className="flex flex-col flex-shrink-0 items-end self-start">
<div
className="text-gray-300 overflow-hidden text-ellipsis whitespace-nowrap max-w-[200px]"
title={shipType}
>
{shipType}
</div>
<div
className="flex justify-end text-stone-500 overflow-hidden text-ellipsis whitespace-nowrap max-w-[200px]"
title={shipNameText}
>
{shipNameText}
</div>
</div>
</div>
{char.ship && (
<WdEveEntityPortrait
type={WdEveEntityPortraitType.ship}
eveId={char.ship.ship_type_id.toString()}
size={WdEveEntityPortraitSize.w33}
/>
)}
</>
)}
</div>
</div>

View File

@@ -1,47 +0,0 @@
import clsx from 'clsx';
import { WithClassName } from '@/hooks/Mapper/types/common.ts';
export enum CharacterPortraitSize {
default,
w18,
w33,
}
// TODO IF YOU NEED ANOTHER ONE SIZE PLEASE ADD IT HERE and IN CharacterPortraitSize
const getSize = (size: CharacterPortraitSize) => {
switch (size) {
case CharacterPortraitSize.w18:
return 'min-w-[18px] min-h-[18px] w-[18px] h-[18px]';
case CharacterPortraitSize.w33:
return 'min-w-[33px] min-h-[33px] w-[33px] h-[33px]';
default:
return '';
}
};
export type CharacterPortraitProps = {
characterEveId: string | undefined;
size?: CharacterPortraitSize;
} & WithClassName;
export const CharacterPortrait = ({
characterEveId,
size = CharacterPortraitSize.default,
className,
}: CharacterPortraitProps) => {
if (characterEveId == null) {
return null;
}
return (
<span
className={clsx(
getSize(size),
'flex transition-[border-color,opacity] duration-250 border border-gray-800 bg-transparent rounded-none',
'wd-bg-default',
className,
)}
style={{ backgroundImage: `url(https://images.evetech.net/characters/${characterEveId}/portrait)` }}
/>
);
};

View File

@@ -1 +0,0 @@
export * from './CharacterPortrait';

View File

@@ -0,0 +1,25 @@
import React from 'react';
import { ProgressSpinner } from 'primereact/progressspinner';
type LoadingWrapperProps = {
loading?: boolean;
children: React.ReactNode;
};
export const LoadingWrapper: React.FC<LoadingWrapperProps> = ({ loading, children }) => {
return (
<div className="relative w-full h-full">
{children}
{loading && (
<div className="absolute inset-0 bg-stone-950/50 flex items-center justify-center z-10">
<ProgressSpinner
style={{ width: '50px', height: '50px' }}
strokeWidth="2"
fill="transparent"
animationDuration="2s"
/>
</div>
)}
</div>
);
};

View File

@@ -0,0 +1,29 @@
import React from 'react';
import clsx from 'clsx';
export type SvgIconProps = React.SVGAttributes<SVGElement> & {
width?: number;
height?: number;
className?: string;
};
export const SvgIconWrapper = ({
width = 24,
height = 24,
children,
className,
...props
}: SvgIconProps & { children: React.ReactNode }) => {
return (
<svg
width={width}
height={height}
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={clsx('w-[19px] h-[19px]', className)}
{...props}
>
{children}
</svg>
);
};

View File

@@ -37,8 +37,8 @@ export const SystemViewStandalone = ({
...props
}: SystemViewStandaloneProps) => {
const classTitleColor = getSystemClassStyles({ systemClass: system_class, security });
const isWH = isWormholeSpace(system_class);
1;
const handleClick = useCallback(
(e: MouseEvent) => {

View File

@@ -0,0 +1,71 @@
import clsx from 'clsx';
import { WithClassName } from '@/hooks/Mapper/types/common.ts';
export enum WdEveEntityPortraitType {
character,
corporation,
alliance,
ship,
}
export enum WdEveEntityPortraitSize {
default,
w18,
w33,
}
export const getLogo = (type: WdEveEntityPortraitType, eveId: string | number) => {
switch (type) {
case WdEveEntityPortraitType.alliance:
return `url(https://images.evetech.net/alliances/${eveId}/logo?size=64)`;
case WdEveEntityPortraitType.corporation:
return `url(https://images.evetech.net/corporations/${eveId}/logo?size=64)`;
case WdEveEntityPortraitType.character:
return `url(https://images.evetech.net/characters/${eveId}/portrait)`;
case WdEveEntityPortraitType.ship:
return `url(https://images.evetech.net/types/${eveId}/icon)`;
}
return '';
};
// TODO IF YOU NEED ANOTHER ONE SIZE PLEASE ADD IT HERE and IN WdEveEntityPortraitSize
const getSize = (size: WdEveEntityPortraitSize) => {
switch (size) {
case WdEveEntityPortraitSize.w18:
return 'min-w-[18px] min-h-[18px] w-[18px] h-[18px]';
case WdEveEntityPortraitSize.w33:
return 'min-w-[33px] min-h-[33px] w-[33px] h-[33px]';
default:
return '';
}
};
export type WdEveEntityPortraitProps = {
eveId: string | undefined;
type?: WdEveEntityPortraitType;
size?: WdEveEntityPortraitSize;
} & WithClassName;
export const WdEveEntityPortrait = ({
eveId,
size = WdEveEntityPortraitSize.default,
type = WdEveEntityPortraitType.character,
className,
}: WdEveEntityPortraitProps) => {
if (eveId == null) {
return null;
}
return (
<span
className={clsx(
getSize(size),
'flex transition-[border-color,opacity] duration-250 border border-gray-800 bg-transparent rounded-none',
'wd-bg-default',
className,
)}
style={{ backgroundImage: getLogo(type, eveId) }}
/>
);
};

View File

@@ -0,0 +1 @@
export * from './WdEveEntityPortrait.tsx';

View File

@@ -14,5 +14,6 @@ export * from './TimeAgo';
export * from './WdTooltipWrapper';
export * from './WdResponsiveCheckBox';
export * from './WdRadioButton';
export * from './CharacterPortrait';
export * from './WdEveEntityPortrait';
export * from './WdTransition';
export * from './LoadingWrapper';

View File

@@ -2,3 +2,4 @@ export * from './usePageVisibility';
export * from './useClipboard';
export * from './useHotkey';
export * from './useSkipContextMenu';
export * from './useActualizeSettings';

View File

@@ -0,0 +1,23 @@
import { useEffect } from 'react';
type Settings = Record<string, unknown>;
export const useActualizeSettings = <T extends Settings>(defaultVals: T, vals: T, setVals: (newVals: T) => void) => {
useEffect(() => {
let foundNew = false;
const newVals = Object.keys(defaultVals).reduce((acc, x) => {
if (Object.keys(acc).includes(x)) {
return acc;
}
foundNew = true;
// @ts-ignore
return { ...acc, [x]: defaultVals[x] };
}, vals);
if (foundNew) {
setVals(newVals);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
};

View File

@@ -1,7 +1,8 @@
import { AvailableThemes, useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { AvailableThemes } from '@/hooks/Mapper/mapRootProvider/types.ts';
export const useTheme = (): AvailableThemes => {
const { interfaceSettings } = useMapRootState();
const { storedSettings } = useMapRootState();
return interfaceSettings.theme;
return storedSettings.interfaceSettings.theme;
};

View File

@@ -0,0 +1,18 @@
import { SvgIconProps, SvgIconWrapper } from '@/hooks/Mapper/components/ui-kit/SvgIconWrapper.tsx';
export const MapAddIcon = (props: SvgIconProps) => (
<SvgIconWrapper width={800} height={800} viewBox="0 0 800 800" {...props}>
<path
d="M416.667 234.716C411.247 233.807 405.68 233.333 400 233.333C344.77 233.333 300 278.105 300 333.333C300 388.563 344.77 433.333 400 433.333C455.23 433.333 500 388.563 500 333.333C500 327.655 499.527 322.087 498.617 316.667"
stroke="currentColor"
strokeWidth="50"
strokeLinecap="round"
/>
<path
d="M166.667 507.203C145.085 452.073 133.333 393.377 133.333 338.11C133.333 188.196 252.724 66.6667 400 66.6667C547.277 66.6667 666.667 188.196 666.667 338.11C666.667 486.85 581.557 660.413 448.763 722.48C417.81 736.95 382.19 736.95 351.237 722.48C308.825 702.657 271.277 671.463 239.813 633.333"
stroke="currentColor"
strokeWidth="50"
strokeLinecap="round"
/>
</SvgIconWrapper>
);

View File

@@ -0,0 +1,19 @@
import { SvgIconProps, SvgIconWrapper } from '@/hooks/Mapper/components/ui-kit/SvgIconWrapper.tsx';
export const MapDeleteIcon = (props: SvgIconProps) => (
<SvgIconWrapper width={800} height={800} viewBox="0 0 800 800" {...props}>
<path
d="M416.667 234.716C411.247 233.807 405.68 233.333 400 233.333C344.77 233.333 300 278.105 300 333.333C300 388.563 344.77 433.333 400 433.333C455.23 433.333 500 388.563 500 333.333C500 327.655 499.527 322.087 498.617 316.667"
stroke="currentColor"
strokeWidth="50"
strokeLinecap="round"
/>
<path
d="M166.667 507.203C145.085 452.073 133.333 393.377 133.333 338.11C133.333 188.196 252.724 66.6666 400 66.6666C547.277 66.6666 666.667 188.196 666.667 338.11C666.667 486.85 581.557 660.413 448.763 722.48C417.81 736.95 382.19 736.95 351.237 722.48C308.825 702.657 271.277 671.463 239.813 633.333"
stroke="currentColor"
strokeWidth="50"
strokeLinecap="round"
/>
<path d="M747.901 90L105 732.9" stroke="currentColor" strokeWidth="63" strokeLinecap="round" />
</SvgIconWrapper>
);

View File

@@ -0,0 +1,14 @@
import { SvgIconProps, SvgIconWrapper } from '@/hooks/Mapper/components/ui-kit/SvgIconWrapper.tsx';
export const MapUserAddIcon = (props: SvgIconProps) => (
<SvgIconWrapper width={800} height={800} viewBox="0 0 800 800" {...props}>
<path
d="M259.095 617.422C288.199 652.691 322.293 680.967 360.022 698.982L361.822 699.832L361.823 699.833L362.963 700.355C371.571 704.236 380.618 706.7 389.783 707.749L392.258 758.116C374.601 757.137 357.082 752.809 340.65 745.128V745.129C294.272 723.452 253.892 689.676 220.529 649.245L239.812 633.334L259.095 617.422ZM223.9 614.051C234.55 605.263 250.307 606.773 259.095 617.422L220.529 649.245C211.742 638.596 213.251 622.839 223.9 614.051ZM400 41.667C515.7 41.6671 615.317 110.002 662.514 208.804C653.511 207.614 644.327 207 635 207C625.128 207 615.417 207.686 605.911 209.017C563.301 138.509 486.858 91.6671 400 91.667C266.948 91.6672 158.333 201.583 158.333 338.11C158.333 389.156 169.046 443.842 188.989 495.627L189.946 498.09L190.174 498.694C194.779 511.398 188.435 525.529 175.779 530.483C163.123 535.437 148.873 529.369 143.63 516.915L143.387 516.317L142.334 513.607C120.393 456.639 108.333 395.871 108.333 338.11C108.333 174.81 238.5 41.6672 400 41.667ZM400 208.334C406.63 208.334 413.153 208.852 419.529 209.854L420.803 210.061L421.438 210.176C434.704 212.748 443.57 225.448 441.321 238.853C439.072 252.257 426.546 261.369 413.167 259.471L412.529 259.372L411.768 259.248C407.948 258.648 404.02 258.334 400 258.334C358.578 258.334 325 291.913 325 333.334C325 374.756 358.577 408.334 400 408.334C409.643 408.334 418.861 406.513 427.329 403.198C427.111 407.104 427 411.039 427 415C427 428.513 428.289 441.725 430.75 454.521C420.913 457.009 410.611 458.334 400 458.334C330.963 458.334 275 402.371 275 333.334C275 264.299 330.963 208.334 400 208.334Z"
fill="currentColor"
/>
<path
d="M661.071 406.777C661.071 381.914 640.868 361.666 615.833 361.666C590.798 361.666 570.595 381.914 570.595 406.777C570.595 431.641 590.798 451.889 615.833 451.889C640.868 451.889 661.071 431.641 661.071 406.777ZM727.737 406.777C727.737 457.286 694.219 499.913 648.248 513.79C727.063 528.912 786.666 598.136 786.666 681.333C786.666 699.742 771.742 714.666 753.333 714.666C734.924 714.666 720 699.742 720 681.333C720 623.977 673.414 577.389 615.833 577.389C558.253 577.389 511.666 623.977 511.666 681.333C511.666 699.742 496.742 714.666 478.333 714.666C459.924 714.666 445 699.742 445 681.333C445 598.136 504.603 528.912 583.417 513.79C537.446 499.913 503.929 457.286 503.929 406.777C503.929 344.993 554.081 295 615.833 295C677.584 295 727.737 344.993 727.737 406.777Z"
fill="currentColor"
/>
</SvgIconWrapper>
);

View File

@@ -0,0 +1,15 @@
import { SvgIconProps, SvgIconWrapper } from '@/hooks/Mapper/components/ui-kit/SvgIconWrapper.tsx';
export const MapUserDeleteIcon = (props: SvgIconProps) => (
<SvgIconWrapper width={800} height={800} viewBox="0 0 800 800" {...props}>
<path
d="M259.095 617.422C288.199 652.691 322.293 680.967 360.022 698.982L361.822 699.832L361.823 699.833L362.963 700.355C371.571 704.236 380.618 706.7 389.783 707.749L392.258 758.116C374.601 757.137 357.082 752.809 340.65 745.128V745.129C294.272 723.452 253.892 689.676 220.529 649.245L239.812 633.334L259.095 617.422ZM223.9 614.051C234.55 605.263 250.307 606.773 259.095 617.422L220.529 649.245C211.742 638.596 213.251 622.839 223.9 614.051ZM400 41.667C515.7 41.6671 615.317 110.002 662.514 208.804C653.511 207.614 644.327 207 635 207C625.128 207 615.417 207.686 605.911 209.017C563.301 138.509 486.858 91.6671 400 91.667C266.948 91.6672 158.333 201.583 158.333 338.11C158.333 389.156 169.046 443.842 188.989 495.627L189.946 498.09L190.174 498.694C194.779 511.398 188.435 525.529 175.779 530.483C163.123 535.437 148.873 529.369 143.63 516.915L143.387 516.317L142.334 513.607C120.393 456.639 108.333 395.871 108.333 338.11C108.333 174.81 238.5 41.6672 400 41.667ZM400 208.334C406.63 208.334 413.153 208.852 419.529 209.854L420.803 210.061L421.438 210.176C434.704 212.748 443.57 225.448 441.321 238.853C439.072 252.257 426.546 261.369 413.167 259.471L412.529 259.372L411.768 259.248C407.948 258.648 404.02 258.334 400 258.334C358.578 258.334 325 291.913 325 333.334C325 374.756 358.577 408.334 400 408.334C409.643 408.334 418.861 406.513 427.329 403.198C427.111 407.104 427 411.039 427 415C427 428.513 428.289 441.725 430.75 454.521C420.913 457.009 410.611 458.334 400 458.334C330.963 458.334 275 402.371 275 333.334C275 264.299 330.963 208.334 400 208.334Z"
fill="currentColor"
/>
<path
d="M661.071 406.777C661.071 381.914 640.868 361.666 615.833 361.666C590.798 361.666 570.595 381.914 570.595 406.777C570.595 431.641 590.798 451.889 615.833 451.889C640.868 451.889 661.071 431.641 661.071 406.777ZM727.737 406.777C727.737 457.286 694.219 499.913 648.248 513.79C727.063 528.912 786.666 598.136 786.666 681.333C786.666 699.742 771.742 714.666 753.333 714.666C734.924 714.666 720 699.742 720 681.333C720 623.977 673.414 577.389 615.833 577.389C558.253 577.389 511.666 623.977 511.666 681.333C511.666 699.742 496.742 714.666 478.333 714.666C459.924 714.666 445 699.742 445 681.333C445 598.136 504.603 528.912 583.417 513.79C537.446 499.913 503.929 457.286 503.929 406.777C503.929 344.993 554.081 295 615.833 295C677.584 295 727.737 344.993 727.737 406.777Z"
fill="currentColor"
/>
<path d="M750.901 72L108 714.9" stroke="currentColor" strokeWidth="63" strokeLinecap="round" />
</SvgIconWrapper>
);

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
import { ContextStoreDataUpdate, useContextStore } from '@/hooks/Mapper/utils';
import { createContext, Dispatch, ForwardedRef, forwardRef, SetStateAction, useContext, useEffect } from 'react';
import { createContext, Dispatch, ForwardedRef, forwardRef, SetStateAction, useContext } from 'react';
import {
ActivitySummary,
CommandLinkSignatureToSystem,
@@ -12,7 +12,6 @@ import {
} from '@/hooks/Mapper/types';
import { useCharactersCache, useComments, useMapRootHandlers } from '@/hooks/Mapper/mapRootProvider/hooks';
import { WithChildren } from '@/hooks/Mapper/types/common.ts';
import useLocalStorageState from 'use-local-storage-state';
import {
ToggleWidgetVisibility,
useStoreWidgets,
@@ -20,6 +19,9 @@ import {
} from '@/hooks/Mapper/mapRootProvider/hooks/useStoreWidgets.ts';
import { WindowsManagerOnChange } from '@/hooks/Mapper/components/ui-kit/WindowManager';
import { DetailedKill } from '../types/kills';
import { InterfaceStoredSettings, RoutesType } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { DEFAULT_ROUTES_SETTINGS, STORED_INTERFACE_DEFAULT_VALUES } from '@/hooks/Mapper/mapRootProvider/constants.ts';
import { useMapUserSettings } from '@/hooks/Mapper/mapRootProvider/hooks/useMapUserSettings.ts';
export type MapRootData = MapUnionTypes & {
selectedSystems: string[];
@@ -50,7 +52,9 @@ const INITIAL_DATA: MapRootData = {
systems: [],
systemSignatures: {},
hubs: [],
userHubs: [],
routes: undefined,
userRoutes: undefined,
kills: [],
connections: [],
detailedKills: {},
@@ -64,11 +68,6 @@ const INITIAL_DATA: MapRootData = {
followingCharacterEveId: null,
};
export enum AvailableThemes {
default = 'default',
pathfinder = 'pathfinder',
}
export enum InterfaceStoredSettingsProps {
isShowMenu = 'isShowMenu',
isShowMinimap = 'isShowMinimap',
@@ -80,40 +79,28 @@ export enum InterfaceStoredSettingsProps {
theme = 'theme',
}
export type InterfaceStoredSettings = {
isShowMenu: boolean;
isShowMinimap: boolean;
isShowKSpace: boolean;
isThickConnections: boolean;
isShowUnsplashedSignatures: boolean;
isShowBackgroundPattern: boolean;
isSoftBackground: boolean;
theme: AvailableThemes;
};
export const STORED_INTERFACE_DEFAULT_VALUES: InterfaceStoredSettings = {
isShowMenu: false,
isShowMinimap: true,
isShowKSpace: false,
isThickConnections: false,
isShowUnsplashedSignatures: false,
isShowBackgroundPattern: true,
isSoftBackground: false,
theme: AvailableThemes.default,
};
export interface MapRootContextProps {
update: ContextStoreDataUpdate<MapRootData>;
data: MapRootData;
outCommand: OutCommandHandler;
interfaceSettings: InterfaceStoredSettings;
setInterfaceSettings: Dispatch<SetStateAction<InterfaceStoredSettings>>;
windowsSettings: WindowStoreInfo;
toggleWidgetVisibility: ToggleWidgetVisibility;
updateWidgetSettings: WindowsManagerOnChange;
resetWidgets: () => void;
comments: UseCommentsData;
charactersCache: UseCharactersCacheData;
/**
* !!!
* DO NOT PASS THIS PROP INTO COMPONENT
* !!!
* */
storedSettings: {
interfaceSettings: InterfaceStoredSettings;
setInterfaceSettings: Dispatch<SetStateAction<InterfaceStoredSettings>>;
settingsRoutes: RoutesType;
settingsRoutesUpdate: Dispatch<SetStateAction<RoutesType>>;
};
}
const MapRootContext = createContext<MapRootContextProps>({
@@ -121,8 +108,6 @@ const MapRootContext = createContext<MapRootContextProps>({
data: { ...INITIAL_DATA },
// @ts-ignore
outCommand: async () => void 0,
interfaceSettings: STORED_INTERFACE_DEFAULT_VALUES,
setInterfaceSettings: () => null,
comments: {
loadComments: async () => {},
comments: new Map(),
@@ -141,6 +126,12 @@ const MapRootContext = createContext<MapRootContextProps>({
characters: new Map(),
lastUpdateKey: 0,
},
storedSettings: {
interfaceSettings: STORED_INTERFACE_DEFAULT_VALUES,
setInterfaceSettings: () => null,
settingsRoutes: DEFAULT_ROUTES_SETTINGS,
settingsRoutesUpdate: () => null,
},
});
type MapRootProviderProps = {
@@ -159,49 +150,25 @@ const MapRootHandlers = forwardRef(({ children }: WithChildren, fwdRef: Forwarde
export const MapRootProvider = ({ children, fwdRef, outCommand }: MapRootProviderProps) => {
const { update, ref } = useContextStore<MapRootData>({ ...INITIAL_DATA });
const [interfaceSettings, setInterfaceSettings] = useLocalStorageState<InterfaceStoredSettings>(
'window:interface:settings',
{
defaultValue: STORED_INTERFACE_DEFAULT_VALUES,
},
);
const storedSettings = useMapUserSettings();
const { windowsSettings, toggleWidgetVisibility, updateWidgetSettings, resetWidgets } = useStoreWidgets();
const comments = useComments({ outCommand });
const charactersCache = useCharactersCache({ outCommand });
useEffect(() => {
let foundNew = false;
const newVals = Object.keys(STORED_INTERFACE_DEFAULT_VALUES).reduce((acc, x) => {
if (Object.keys(acc).includes(x)) {
return acc;
}
foundNew = true;
// @ts-ignore
return { ...acc, [x]: STORED_INTERFACE_DEFAULT_VALUES[x] };
}, interfaceSettings);
if (foundNew) {
setInterfaceSettings(newVals);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<MapRootContext.Provider
value={{
update,
data: ref,
outCommand,
setInterfaceSettings,
interfaceSettings,
windowsSettings,
updateWidgetSettings,
toggleWidgetVisibility,
resetWidgets,
comments,
charactersCache,
storedSettings,
}}
>
<MapRootHandlers ref={fwdRef}>{children}</MapRootHandlers>

View File

@@ -0,0 +1,26 @@
import { AvailableThemes, InterfaceStoredSettings, RoutesType } from '@/hooks/Mapper/mapRootProvider/types.ts';
export const STORED_INTERFACE_DEFAULT_VALUES: InterfaceStoredSettings = {
isShowMenu: false,
isShowMinimap: true,
isShowKSpace: false,
isThickConnections: false,
isShowUnsplashedSignatures: false,
isShowBackgroundPattern: true,
isSoftBackground: false,
theme: AvailableThemes.default,
};
export const DEFAULT_ROUTES_SETTINGS: RoutesType = {
path_type: 'shortest',
include_mass_crit: true,
include_eol: true,
include_frig: true,
include_cruise: true,
include_thera: true,
avoid_wormholes: false,
avoid_pochven: false,
avoid_edencom: false,
avoid_triglavian: false,
avoid: [],
};

View File

@@ -9,23 +9,26 @@ export const useMapInit = () => {
const { addSystemStatic } = useLoadSystemStatic({ systems: [] });
return useCallback(
({
systems,
system_signatures,
connections,
effects,
wormholes,
system_static_infos,
characters,
user_characters,
present_characters,
hubs,
user_permissions,
options,
is_subscription_active,
main_character_eve_id,
following_character_eve_id,
}: CommandInit) => {
(props: CommandInit) => {
const {
systems,
system_signatures,
connections,
effects,
wormholes,
system_static_infos,
characters,
user_characters,
present_characters,
hubs,
user_permissions,
options,
is_subscription_active,
main_character_eve_id,
following_character_eve_id,
user_hubs,
} = props;
const updateData: Partial<MapRootData> = {};
if (wormholes) {
@@ -69,6 +72,10 @@ export const useMapInit = () => {
updateData.hubs = hubs;
}
if (user_hubs) {
updateData.userHubs = user_hubs;
}
if (options) {
updateData.options = options;
}
@@ -87,7 +94,7 @@ export const useMapInit = () => {
updateData.mainCharacterEveId = main_character_eve_id;
}
if (following_character_eve_id) {
if ('following_character_eve_id' in props) {
updateData.followingCharacterEveId = following_character_eve_id;
}

View File

@@ -8,13 +8,33 @@ export const useMapUpdated = () => {
const ref = useRef({ update });
ref.current = { update };
return useCallback(({ hubs }: CommandMapUpdated) => {
return useCallback((props: CommandMapUpdated) => {
const { update } = ref.current;
const out: Partial<MapRootData> = {};
if (hubs) {
out.hubs = hubs;
if ('hubs' in props) {
out.hubs = props.hubs;
}
if ('user_hubs' in props) {
out.userHubs = props.user_hubs;
}
if ('system_signatures' in props) {
out.systemSignatures = props.system_signatures;
}
if ('following_character_eve_id' in props) {
out.userCharacters = props.user_characters;
}
if ('following_character_eve_id' in props) {
out.followingCharacterEveId = props.following_character_eve_id;
}
if ('main_character_eve_id' in props) {
out.mainCharacterEveId = props.main_character_eve_id;
}
update(out);

View File

@@ -92,3 +92,23 @@ export const useRoutes = () => {
update({ routes: value });
}, []);
};
export const useUserRoutes = () => {
const {
update,
data: { userRoutes },
} = useMapRootState();
const ref = useRef({ update, userRoutes });
ref.current = { update, userRoutes };
return useCallback((value: CommandRoutes) => {
const { update, userRoutes } = ref.current;
if (areRoutesListsEqual(userRoutes, value)) {
return;
}
update({ userRoutes: value });
}, []);
};

View File

@@ -33,6 +33,7 @@ import {
useMapInit,
useMapUpdated,
useRoutes,
useUserRoutes,
} from './api';
import { useCommandsActivity } from './api/useCommandsActivity';
@@ -54,6 +55,7 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
useCommandsCharacters();
const mapUpdated = useMapUpdated();
const mapRoutes = useRoutes();
const mapUserRoutes = useUserRoutes();
const { addComment, removeComment } = useCommandComments();
const { characterActivityData, trackingCharactersData, userSettingsUpdated } = useCommandsActivity();
@@ -105,6 +107,9 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
case Commands.routes:
mapRoutes(data as CommandRoutes);
break;
case Commands.userRoutes:
mapUserRoutes(data as CommandRoutes);
break;
case Commands.signaturesUpdated: // USED
updateSystemSignatures(data as CommandSignaturesUpdated);

View File

@@ -0,0 +1,39 @@
import useLocalStorageState from 'use-local-storage-state';
import { InterfaceStoredSettings, RoutesType } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { DEFAULT_ROUTES_SETTINGS, STORED_INTERFACE_DEFAULT_VALUES } from '@/hooks/Mapper/mapRootProvider/constants.ts';
import { useActualizeSettings } from '@/hooks/Mapper/hooks';
import { useEffect } from 'react';
import { SESSION_KEY } from '@/hooks/Mapper/constants.ts';
export const useMigrationRoutesSettingsV1 = (update: (upd: RoutesType) => void) => {
//TODO if current Date is more than 01.01.2026 - remove this hook.
useEffect(() => {
const items = localStorage.getItem(SESSION_KEY.routes);
if (items) {
update(JSON.parse(items));
localStorage.removeItem(SESSION_KEY.routes);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
};
export const useMapUserSettings = () => {
const [interfaceSettings, setInterfaceSettings] = useLocalStorageState<InterfaceStoredSettings>(
'window:interface:settings',
{
defaultValue: STORED_INTERFACE_DEFAULT_VALUES,
},
);
const [settingsRoutes, settingsRoutesUpdate] = useLocalStorageState<RoutesType>('window:interface:routes', {
defaultValue: DEFAULT_ROUTES_SETTINGS,
});
useActualizeSettings(STORED_INTERFACE_DEFAULT_VALUES, interfaceSettings, setInterfaceSettings);
useActualizeSettings(DEFAULT_ROUTES_SETTINGS, settingsRoutes, settingsRoutesUpdate);
useMigrationRoutesSettingsV1(settingsRoutesUpdate);
return { interfaceSettings, setInterfaceSettings, settingsRoutes, settingsRoutesUpdate };
};

View File

@@ -0,0 +1,29 @@
export enum AvailableThemes {
default = 'default',
pathfinder = 'pathfinder',
}
export type InterfaceStoredSettings = {
isShowMenu: boolean;
isShowMinimap: boolean;
isShowKSpace: boolean;
isThickConnections: boolean;
isShowUnsplashedSignatures: boolean;
isShowBackgroundPattern: boolean;
isSoftBackground: boolean;
theme: AvailableThemes;
};
export type RoutesType = {
path_type: 'shortest' | 'secure' | 'insecure';
include_mass_crit: boolean;
include_eol: boolean;
include_frig: boolean;
include_cruise: boolean;
include_thera: boolean;
avoid_wormholes: boolean;
avoid_pochven: boolean;
avoid_edencom: boolean;
avoid_triglavian: boolean;
avoid: number[];
};

View File

@@ -28,8 +28,8 @@ export type CharacterTypeRaw = {
ship: ShipTypeRaw | null;
alliance_id: number | null;
alliance_name: number | null;
alliance_ticker: number | null;
alliance_name: string | null;
alliance_ticker: string | null;
corporation_id: number;
corporation_name: string;
corporation_ticker: string;

View File

@@ -24,6 +24,7 @@ export enum Commands {
killsUpdated = 'kills_updated',
detailedKillsUpdated = 'detailed_kills_updated',
routes = 'routes',
userRoutes = 'user_routes',
centerSystem = 'center_system',
selectSystem = 'select_system',
linkSignatureToSystem = 'link_signature_to_system',
@@ -55,6 +56,7 @@ export type Command =
| Commands.killsUpdated
| Commands.detailedKillsUpdated
| Commands.routes
| Commands.userRoutes
| Commands.selectSystem
| Commands.centerSystem
| Commands.linkSignatureToSystem
@@ -82,6 +84,7 @@ export type CommandInit = {
user_characters: string[];
user_permissions: UserPermissions;
hubs: string[];
user_hubs: string[];
routes: RoutesList;
options: Record<string, string | boolean>;
reset?: boolean;
@@ -104,6 +107,7 @@ export type CommandUpdateConnection = SolarSystemConnection;
export type CommandSignaturesUpdated = string;
export type CommandMapUpdated = Partial<CommandInit>;
export type CommandRoutes = RoutesList;
export type CommandUserRoutes = RoutesList;
export type CommandKillsUpdated = Kill[];
export type CommandDetailedKillsUpdated = Record<string, DetailedKill[]>;
export type CommandSelectSystem = string | undefined;
@@ -170,6 +174,7 @@ export interface CommandData {
[Commands.updateConnection]: CommandUpdateConnection;
[Commands.mapUpdated]: CommandMapUpdated;
[Commands.routes]: CommandRoutes;
[Commands.userRoutes]: CommandUserRoutes;
[Commands.killsUpdated]: CommandKillsUpdated;
[Commands.detailedKillsUpdated]: CommandDetailedKillsUpdated;
[Commands.selectSystem]: CommandSelectSystem;
@@ -194,7 +199,10 @@ export interface MapHandlers {
export enum OutCommand {
addHub = 'add_hub',
deleteHub = 'delete_hub',
addUserHub = 'add_user_hub',
deleteUserHub = 'delete_user_hub',
getRoutes = 'get_routes',
getUserRoutes = 'get_user_routes',
getCharacterJumps = 'get_character_jumps',
getStructures = 'get_structures',
getSignatures = 'get_signatures',

View File

@@ -15,9 +15,11 @@ export type MapUnionTypes = {
userCharacters: string[];
presentCharacters: string[];
hubs: string[];
userHubs: string[];
systems: SolarSystemRawType[];
systemSignatures: Record<string, SystemSignature[]>;
routes?: RoutesList;
userRoutes?: RoutesList;
kills: Record<number, number>;
connections: SolarSystemConnection[];
userPermissions: Partial<UserPermissions>;

View File

@@ -1,5 +1,8 @@
import { RefObject, useCallback } from 'react';
import { MapHandlers } from '@/hooks/Mapper/types/mapHandlers.ts';
import { RefObject, useCallback } from 'react';
// Force reload the page after 5 minutes of inactivity
const FORCE_PAGE_RELOAD_TIMEOUT = 1000 * 60 * 5;
export const useMapperHandlers = (handlerRefs: RefObject<MapHandlers>[], hooksRef: RefObject<any>) => {
const handleCommand = useCallback(
@@ -13,7 +16,13 @@ export const useMapperHandlers = (handlerRefs: RefObject<MapHandlers>[], hooksRe
[hooksRef.current],
);
const handleMapEvent = useCallback(({ type, body }) => {
const handleMapEvent = useCallback(({ type, body, timestamp }) => {
const timeDiff = Date.now() - Date.parse(timestamp);
// If the event is older than the timeout, force reload the page
if (timeDiff > FORCE_PAGE_RELOAD_TIMEOUT) {
window.location.reload();
return;
}
handlerRefs.forEach(ref => {
if (!ref.current) {
return;
@@ -23,14 +32,5 @@ export const useMapperHandlers = (handlerRefs: RefObject<MapHandlers>[], hooksRe
});
}, []);
const handleMapEvents = useCallback(
events => {
events.forEach(event => {
handleMapEvent(event);
});
},
[handleMapEvent],
);
return { handleCommand, handleMapEvent, handleMapEvents };
return { handleCommand, handleMapEvent };
};

View File

@@ -0,0 +1,7 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="#ffffff">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

View File

@@ -85,7 +85,7 @@ defmodule WandererApp.Api.Character do
update :update do
require_atomic? false
accept([:access_token, :refresh_token, :expires_at, :scopes])
accept([:name, :access_token, :refresh_token, :expires_at, :scopes])
change(set_attribute(:deleted, false))
end

View File

@@ -5,6 +5,16 @@ defmodule WandererApp.Api.MapCharacterSettings do
domain: WandererApp.Api,
data_layer: AshPostgres.DataLayer
@derive {Jason.Encoder, only: [
:id,
:map_id,
:character_id,
:tracked,
:followed,
:inserted_at,
:updated_at
]}
postgres do
repo(WandererApp.Repo)
table("map_character_settings_v1")
@@ -17,6 +27,7 @@ defmodule WandererApp.Api.MapCharacterSettings do
define(:read_by_map, action: :read_by_map)
define(:by_map_filtered, action: :by_map_filtered)
define(:tracked_by_map_filtered, action: :tracked_by_map_filtered)
define(:tracked_by_character, action: :tracked_by_character)
define(:tracked_by_map_all, action: :tracked_by_map_all)
define(:track, action: :track)
@@ -61,6 +72,11 @@ defmodule WandererApp.Api.MapCharacterSettings do
filter(expr(map_id == ^arg(:map_id) and tracked == true))
end
read :tracked_by_character do
argument(:character_id, :uuid, allow_nil?: false)
filter(expr(character_id == ^arg(:character_id) and tracked == true))
end
update :track do
accept [:map_id, :character_id]
argument :map_id, :string, allow_nil?: false

View File

@@ -164,4 +164,23 @@ defmodule WandererApp.Api.MapSystemSignature do
identities do
identity :uniq_system_eve_id, [:system_id, :eve_id]
end
@derive {Jason.Encoder,
only: [
:id,
:system_id,
:eve_id,
:character_eve_id,
:name,
:description,
:type,
:linked_system_id,
:kind,
:group,
:custom_info,
:updated,
:inserted_at,
:updated_at
]
}
end

View File

@@ -4,6 +4,27 @@ defmodule WandererApp.Api.MapSystemStructure do
"""
@derive {Jason.Encoder,
only: [
:id,
:system_id,
:solar_system_id,
:solar_system_name,
:structure_type_id,
:structure_type,
:character_eve_id,
:name,
:notes,
:owner_name,
:owner_ticker,
:owner_id,
:status,
:end_time,
:inserted_at,
:updated_at
]
}
use Ash.Resource,
domain: WandererApp.Api,
data_layer: AshPostgres.DataLayer

View File

@@ -21,6 +21,8 @@ defmodule WandererApp.Api.MapUserSettings do
define(:update_settings, action: :update_settings)
define(:update_main_character, action: :update_main_character)
define(:update_following_character, action: :update_following_character)
define(:update_hubs, action: :update_hubs)
end
actions do
@@ -43,6 +45,10 @@ defmodule WandererApp.Api.MapUserSettings do
update :update_following_character do
accept [:following_character_eve_id]
end
update :update_hubs do
accept [:hubs]
end
end
attributes do
@@ -59,6 +65,12 @@ defmodule WandererApp.Api.MapUserSettings do
attribute :following_character_eve_id, :string do
allow_nil? true
end
attribute :hubs, {:array, :string} do
allow_nil?(true)
default([])
end
end
relationships do

View File

@@ -13,47 +13,51 @@ defmodule WandererApp.Application do
WandererAppWeb.Telemetry,
WandererApp.Vault,
WandererApp.Repo,
{Phoenix.PubSub, name: WandererApp.PubSub, adapter_name: Phoenix.PubSub.PG2},
{
Finch,
name: WandererApp.Finch,
pools: %{
default: [
size: 25, # number of connections per pool
count: 2, # number of pools (so total 50 connections)
# number of connections per pool
size: 25,
# number of pools (so total 50 connections)
count: 2
]
}
},
WandererApp.Cache,
Supervisor.child_spec({Cachex, name: :system_static_info_cache}, id: :system_static_info_cache_worker),
Supervisor.child_spec({Cachex, name: :ship_types_cache}, id: :ship_types_cache_worker),
Supervisor.child_spec({Cachex, name: :character_cache}, id: :character_cache_worker),
Supervisor.child_spec({Cachex, name: :map_cache}, id: :map_cache_worker),
Supervisor.child_spec({Cachex, name: :character_state_cache}, id: :character_state_cache_worker),
Supervisor.child_spec({Cachex, name: :api_cache}, id: :api_cache_worker),
Supervisor.child_spec({Cachex, name: :system_static_info_cache},
id: :system_static_info_cache_worker
),
Supervisor.child_spec({Cachex, name: :ship_types_cache}, id: :ship_types_cache_worker),
Supervisor.child_spec({Cachex, name: :character_cache}, id: :character_cache_worker),
Supervisor.child_spec({Cachex, name: :map_cache}, id: :map_cache_worker),
Supervisor.child_spec({Cachex, name: :character_state_cache},
id: :character_state_cache_worker
),
Supervisor.child_spec({Cachex, name: :tracked_characters},
id: :tracked_characters_cache_worker
),
WandererApp.Scheduler,
{Registry, keys: :unique, name: WandererApp.MapRegistry},
{Registry, keys: :unique, name: WandererApp.Character.TrackerRegistry},
{PartitionSupervisor, child_spec: DynamicSupervisor, name: WandererApp.Map.DynamicSupervisors},
{PartitionSupervisor, child_spec: DynamicSupervisor, name: WandererApp.Character.DynamicSupervisors},
{PartitionSupervisor,
child_spec: DynamicSupervisor, name: WandererApp.Map.DynamicSupervisors},
{PartitionSupervisor,
child_spec: DynamicSupervisor, name: WandererApp.Character.DynamicSupervisors},
WandererApp.Zkb.Supervisor,
WandererApp.Server.ServerStatusTracker,
WandererApp.Server.TheraDataFetcher,
{WandererApp.Character.TrackerPoolSupervisor, []},
WandererApp.Character.TrackerManager,
WandererApp.Map.Manager,
WandererApp.Map.ZkbDataFetcher,
WandererAppWeb.Presence,
WandererAppWeb.Endpoint
]
++ maybe_start_corp_wallet_tracker(WandererApp.Env.map_subscriptions_enabled?())
] ++
maybe_start_corp_wallet_tracker(WandererApp.Env.map_subscriptions_enabled?())
opts = [strategy: :one_for_one, name: WandererApp.Supervisor]

View File

@@ -4,35 +4,6 @@ defmodule WandererApp.Character.Activity do
"""
require Logger
@doc """
Finds a followed character ID from a list of character settings and activities.
## Parameters
- `character_settings`: List of character settings with `followed` and `character_id` fields
- `activities_by_character`: Map of activities grouped by character_id
- `is_current_user`: Boolean indicating if this is for the current user
## Returns
- Character ID of the followed character if found, nil otherwise
"""
def find_followed_character(character_settings, activities_by_character, is_current_user) do
if is_current_user do
followed_chars =
character_settings
|> Enum.filter(& &1.followed)
|> Enum.map(& &1.character_id)
# Find if any of user's characters is followed
user_char_ids = Map.keys(activities_by_character)
Enum.find(followed_chars, fn followed_id ->
followed_id in user_char_ids
end)
else
nil
end
end
@doc """
Finds the character with the most activity from a map of activities grouped by character_id.
@@ -71,20 +42,39 @@ defmodule WandererApp.Character.Activity do
## Parameters
- `map_id`: ID of the map
- `current_user`: Current user struct
- `current_user`: Current user struct (used only to get user settings)
## Returns
- List of processed activity data
"""
def process_character_activity(map_id, current_user) do
with {:ok, character_settings} <- get_map_character_settings(map_id),
with {:ok, map_user_settings} <- get_map_user_settings(map_id, current_user.id),
raw_activity <- WandererApp.Map.get_character_activity(map_id),
{:ok, user_characters} <-
WandererApp.Api.Character.active_by_user(%{user_id: current_user.id}) do
process_activity_data(raw_activity, character_settings, user_characters, current_user)
{:ok, user_characters} <- WandererApp.Api.Character.active_by_user(%{user_id: current_user.id}) do
result = process_activity_data(raw_activity, map_user_settings, user_characters)
result
end
end
def get_map_user_settings(map_id, user_id) do
case WandererApp.MapUserSettingsRepo.get(map_id, user_id) do
{:ok, settings} when not is_nil(settings) ->
{:ok, settings}
_ ->
{:ok, %{main_character_eve_id: nil}}
end
end
@doc """
Gets character settings for a map.
## Parameters
- `map_id`: ID of the map
## Returns
- `{:ok, settings}` with list of settings or empty list
"""
def get_map_character_settings(map_id) do
case WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id) do
{:ok, settings} -> {:ok, settings}
@@ -92,74 +82,60 @@ defmodule WandererApp.Character.Activity do
end
end
defp process_activity_data([], _character_settings, _user_characters, _current_user), do: []
# Handle empty activity list
defp process_activity_data([], _map_user_settings, _all_characters), do: []
# Simplify the pre-processed data handling - just pass it through
defp process_activity_data([%{character: _} | _] = activity_data, _, _, _), do: activity_data
# Process activity data
defp process_activity_data(all_activity, map_user_settings, all_characters) do
# Group activities by user ID
activities_by_user = Enum.group_by(all_activity, &Map.get(&1, :user_id, "unknown"))
defp process_activity_data(all_activity, character_settings, user_characters, current_user) do
all_activity
|> group_by_user_id()
|> process_users_activity(character_settings, user_characters, current_user)
# Process each user's activities
activities_by_user
|> Enum.flat_map(fn {user_id, user_activities} ->
process_user_activity(user_id, user_activities, map_user_settings, all_characters)
end)
|> sort_by_timestamp()
end
defp group_by_user_id(activities) do
Enum.group_by(activities, &Map.get(&1, :user_id, "unknown"))
end
defp process_users_activity(
activity_by_user_id,
character_settings,
user_characters,
current_user
) do
Enum.flat_map(activity_by_user_id, fn {user_id, user_activities} ->
process_single_user_activity(
user_id,
user_activities,
character_settings,
user_characters,
current_user
)
end)
end
defp process_single_user_activity(
user_id,
user_activities,
character_settings,
user_characters,
current_user
) do
# Determine if this is the current user's activity
is_current_user = user_id == current_user.id
defp process_user_activity(user_id, user_activities, %{user_id: user_id, main_character_eve_id: main_id} = _map_user_settings, all_characters)
when not is_nil(main_id) do
# Group activities by character
activities_by_character = group_activities_by_character(user_activities)
# Find the character to show (followed or most active)
char_id_to_show =
select_character_to_show(activities_by_character, character_settings, is_current_user)
main_id_str = to_string(main_id)
# Create activity entry for the selected character
case char_id_to_show do
nil ->
[]
id ->
create_character_activity_entry(
id,
activities_by_character,
user_characters,
is_current_user
)
display_character = case Enum.find(all_characters, &(to_string(&1.eve_id) == main_id_str)) do
nil -> find_most_active_character_details(activities_by_character) # Fall back to most active
main_char -> main_char
end
build_activity_entry_if_valid(display_character, activities_by_character, user_id)
end
defp process_user_activity(user_id, user_activities, _map_user_settings, _all_characters) do
# Group activities by character
activities_by_character = group_activities_by_character(user_activities)
# Find the most active character
display_character = find_most_active_character_details(activities_by_character)
build_activity_entry_if_valid(display_character, activities_by_character, user_id)
end
# Helper function to build activity entry only if display character is valid
defp build_activity_entry_if_valid(nil, _activities_by_character, user_id) do
Logger.warning("No suitable character found for user #{user_id}")
[]
end
defp build_activity_entry_if_valid(display_character, activities_by_character, _user_id) do
build_activity_entry(display_character, activities_by_character)
end
# Group activities by character ID
defp group_activities_by_character(activities) do
Enum.group_by(activities, fn activity ->
# Character info is now in a nested 'character' field
cond do
character = Map.get(activity, :character) -> Map.get(character, :id)
id = Map.get(activity, :character_id) -> id
@@ -169,57 +145,54 @@ defmodule WandererApp.Character.Activity do
end)
end
defp select_character_to_show(activities_by_character, character_settings, is_current_user) do
followed_char_id =
find_followed_character(character_settings, activities_by_character, is_current_user)
followed_char_id || find_most_active_character(activities_by_character)
end
defp create_character_activity_entry(
char_id,
activities_by_character,
user_characters,
is_current_user
) do
char_activities = Map.get(activities_by_character, char_id, [])
case get_character_details(char_id, char_activities, user_characters, is_current_user) do
nil -> []
char_details -> [build_activity_entry(char_details, char_activities)]
# Find the details of the most active character
defp find_most_active_character_details(activities_by_character) do
with most_active_id when not is_nil(most_active_id) <- find_most_active_character(activities_by_character),
most_active_activities <- Map.get(activities_by_character, most_active_id, []),
[first_activity | _] <- most_active_activities,
character when not is_nil(character) <- Map.get(first_activity, :character) do
character
else
_ ->
Logger.warning("Could not find most active character")
nil
end
end
defp get_character_details(_char_id, [activity | _], _user_characters, false) do
# Return the raw character data without mapping
Map.get(activity, :character)
# Build activity entry with the provided character and sum all activities
defp build_activity_entry(character, activities_by_character) do
# Sum up all activities
all_passages = sum_all_activities(activities_by_character, :passages)
all_connections = sum_all_activities(activities_by_character, :connections)
all_signatures = sum_all_activities(activities_by_character, :signatures)
# Only create entry if there's at least some activity
if all_passages + all_connections + all_signatures > 0 do
[%{
character: character,
passages: all_passages,
connections: all_connections,
signatures: all_signatures,
timestamp: get_latest_timestamp(activities_by_character)
}]
else
Logger.warning("Character has no activity, not creating entry")
[]
end
end
defp get_character_details(char_id, _char_activities, user_characters, true) do
# Find the character in user_characters and return it without mapping
Enum.find(user_characters, fn char ->
char.id == char_id || to_string(char.eve_id) == char_id
end)
# Sum up activities of a specific type across all characters
defp sum_all_activities(activities_by_character, key) do
activities_by_character
|> Enum.flat_map(fn {_, char_activities} -> char_activities end)
|> Enum.map(&Map.get(&1, key, 0))
|> Enum.sum()
end
defp build_activity_entry(
char_details,
char_activities
) do
%{
character: char_details,
passages: sum_activity(char_activities, :passages),
connections: sum_activity(char_activities, :connections),
signatures: sum_activity(char_activities, :signatures),
timestamp: get_most_recent_timestamp(char_activities)
}
end
defp sum_activity(activities, key),
do: activities |> Enum.map(&Map.get(&1, key, 0)) |> Enum.sum()
defp get_most_recent_timestamp(activities) do
activities
# Get the most recent timestamp across all characters
defp get_latest_timestamp(activities_by_character) do
activities_by_character
|> Enum.flat_map(fn {_, char_activities} -> char_activities end)
|> Enum.map(&Map.get(&1, :timestamp, DateTime.utc_now()))
|> Enum.sort_by(& &1, {:desc, DateTime})
|> List.first() || DateTime.utc_now()

View File

@@ -33,7 +33,7 @@ defmodule WandererApp.Character.Tracker do
status: binary()
}
@online_error_timeout :timer.minutes(2)
@online_error_timeout :timer.minutes(3)
@forbidden_ttl :timer.minutes(1)
@pubsub_client Application.compile_env(:wanderer_app, :pubsub_client)
@@ -49,7 +49,7 @@ defmodule WandererApp.Character.Tracker do
|> new()
end
def update_track_settings(character_id, track_settings) do
def update_settings(character_id, track_settings) do
{:ok, character_state} = WandererApp.Character.get_character_state(character_id)
{:ok,
@@ -103,7 +103,9 @@ defmodule WandererApp.Character.Tracker do
|> update_ship()
end
def update_ship(%{character_id: character_id, track_ship: true} = character_state) do
def update_ship(
%{character_id: character_id, track_ship: true, is_online: true} = character_state
) do
character_id
|> WandererApp.Character.get_character()
|> case do
@@ -137,6 +139,13 @@ defmodule WandererApp.Character.Tracker do
{:error, error} ->
Logger.error("#{__MODULE__} failed to update_ship: #{inspect(error)}")
WandererApp.Cache.put(
"character:#{character_id}:ship_forbidden",
true,
ttl: @forbidden_ttl
)
{:error, error}
end
end
@@ -154,7 +163,9 @@ defmodule WandererApp.Character.Tracker do
|> update_location()
end
def update_location(%{track_location: true, character_id: character_id} = character_state) do
def update_location(
%{track_location: true, is_online: true, character_id: character_id} = character_state
) do
case WandererApp.Character.get_character(character_id) do
{:ok, %{eve_id: eve_id, access_token: access_token}} when not is_nil(access_token) ->
WandererApp.Cache.has_key?("character:#{character_id}:location_forbidden")
@@ -187,6 +198,13 @@ defmodule WandererApp.Character.Tracker do
{:error, error} ->
Logger.error("#{__MODULE__} failed to update_location: #{inspect(error)}")
WandererApp.Cache.put(
"character:#{character_id}:location_forbidden",
true,
ttl: @forbidden_ttl
)
{:error, error}
end
@@ -305,14 +323,9 @@ defmodule WandererApp.Character.Tracker do
WandererApp.Cache.delete("character:#{character_id}:online_forbidden")
WandererApp.Cache.delete("character:#{character_id}:online_error_time")
WandererApp.Character.update_character(character_id, %{online: false})
WandererApp.Cache.delete("character:#{character_id}:location_started")
WandererApp.Cache.delete("character:#{character_id}:start_solar_system_id")
WandererApp.Character.update_character_state(character_id, %{
character_state
| is_online: false,
track_ship: false,
track_location: false
is_online: false
})
:ok
@@ -494,7 +507,7 @@ defmodule WandererApp.Character.Tracker do
state,
location
) do
location = get_location(location)
location = get_location(location)
if not is_location_started?(character_id) do
WandererApp.Cache.lookup!("character:#{character_id}:start_solar_system_id", nil)
@@ -544,25 +557,20 @@ defmodule WandererApp.Character.Tracker do
)
defp is_location_updated?(
%{solar_system_id: new_solar_system_id, station_id: new_station_id, structure_id: new_structure_id} = _location,
%{
solar_system_id: new_solar_system_id,
station_id: new_station_id,
structure_id: new_structure_id
} = _location,
solar_system_id,
structure_id,
station_id
)
do
IO.inspect("is_location_updated")
IO.inspect(solar_system_id)
IO.inspect(new_solar_system_id)
IO.inspect(structure_id)
IO.inspect(new_structure_id)
IO.inspect(station_id)
IO.inspect(new_station_id)
solar_system_id != new_solar_system_id ||
),
do:
solar_system_id != new_solar_system_id ||
solar_system_id != new_solar_system_id ||
structure_id != new_structure_id ||
station_id != new_station_id
end
defp maybe_update_corporation(
state,
@@ -733,14 +741,7 @@ defmodule WandererApp.Character.Tracker do
)
end
WandererApp.Character.update_character(character_id, %{online: false})
%{
state
| track_ship: false,
track_online: false,
track_location: false
}
state
end
defp maybe_stop_tracking(

View File

@@ -31,9 +31,7 @@ defmodule WandererApp.Character.TrackerManager do
def init(args) do
Logger.info("#{__MODULE__} started")
{:ok, tracked_characters} = WandererApp.Cache.lookup("tracked_characters", [])
{:ok, Impl.init(args |> Keyword.merge(characters: tracked_characters)), {:continue, :start}}
{:ok, Impl.init(args), {:continue, :start}}
end
@impl true

View File

@@ -4,22 +4,14 @@ defmodule WandererApp.Character.TrackerManager.Impl do
defstruct [
:characters,
:opts,
server_online: true
:opts
]
@type t :: %__MODULE__{
characters: [integer],
opts: map,
server_online: boolean
opts: map
}
@update_location_interval :timer.seconds(2)
@update_online_interval :timer.seconds(5)
@check_online_errors_interval :timer.seconds(30)
@update_ship_interval :timer.seconds(5)
@update_info_interval :timer.minutes(1)
@update_wallet_interval :timer.minutes(5)
@garbage_collection_interval :timer.minutes(15)
@untrack_characters_interval :timer.minutes(5)
@inactive_character_timeout :timer.minutes(5)
@@ -40,71 +32,72 @@ defmodule WandererApp.Character.TrackerManager.Impl do
|> new()
end
def start(%{opts: opts} = state) do
Phoenix.PubSub.subscribe(
WandererApp.PubSub,
"server_status"
)
def start(state) do
{:ok, tracked_characters} = WandererApp.Cache.lookup("tracked_characters", [])
WandererApp.Cache.insert("tracked_characters", [])
Process.send_after(self(), :update_online, 100)
Process.send_after(self(), :check_online_errors, @check_online_errors_interval)
Process.send_after(self(), :update_location, 300)
Process.send_after(self(), :update_ship, 500)
Process.send_after(self(), :update_info, 1500)
if WandererApp.Env.wallet_tracking_enabled?() do
Process.send_after(self(), :update_wallet, 1000)
end
opts[:characters]
|> Enum.reduce(state, fn character_id, acc ->
start_tracking(acc, character_id, %{})
tracked_characters
|> Enum.each(fn character_id ->
start_tracking(state, character_id, %{})
end)
state
end
def start_tracking(%__MODULE__{characters: characters} = state, character_id, opts) do
case Enum.member?(characters, character_id) do
true ->
state
def start_tracking(state, character_id, opts) do
with {:ok, characters} <- WandererApp.Cache.lookup("tracked_characters", []),
false <- Enum.member?(characters, character_id) do
Logger.debug(fn -> "Start character tracker: #{inspect(character_id)}" end)
false ->
Logger.debug(fn -> "Start character tracker: #{inspect(character_id)}" end)
tracked_characters = [character_id | characters] |> Enum.uniq()
WandererApp.Cache.insert("tracked_characters", tracked_characters)
WandererApp.TaskWrapper.start_link(WandererApp.Character, :update_character_state, [
character_id,
%{opts: opts}
])
WandererApp.Character.update_character(character_id, %{online: false})
tracked_characters = [character_id | state.characters] |> Enum.uniq()
WandererApp.Cache.insert("tracked_characters", tracked_characters)
WandererApp.Character.update_character_state(character_id, %{
is_online: false
})
%{state | characters: tracked_characters}
WandererApp.Character.TrackerPoolDynamicSupervisor.start_tracking(character_id)
WandererApp.TaskWrapper.start_link(WandererApp.Character, :update_character_state, [
character_id,
%{opts: opts}
])
end
state
end
def stop_tracking(%__MODULE__{} = state, character_id) do
{:ok, character_state} = WandererApp.Character.get_character_state(character_id, false)
def stop_tracking(state, character_id) do
with {:ok, characters} <- WandererApp.Cache.lookup("tracked_characters", []),
true <- Enum.member?(characters, character_id),
{:ok, %{start_time: start_time}} <-
WandererApp.Character.get_character_state(character_id, false) do
Logger.debug(fn -> "Shutting down character tracker: #{inspect(character_id)}" end)
case character_state do
nil ->
state
WandererApp.Cache.delete("character:#{character_id}:last_active_time")
WandererApp.Cache.delete("character:#{character_id}:location_started")
WandererApp.Cache.delete("character:#{character_id}:start_solar_system_id")
WandererApp.Character.delete_character_state(character_id)
%{start_time: start_time} ->
duration = DateTime.diff(DateTime.utc_now(), start_time, :second)
:telemetry.execute([:wanderer_app, :character, :tracker, :running], %{duration: duration})
:telemetry.execute([:wanderer_app, :character, :tracker, :stopped], %{count: 1})
Logger.debug(fn -> "Shutting down character tracker: #{inspect(character_id)}" end)
tracked_characters =
characters |> Enum.reject(fn c_id -> c_id == character_id end)
WandererApp.Cache.delete("character:#{character_id}:location_started")
WandererApp.Cache.delete("character:#{character_id}:start_solar_system_id")
WandererApp.Character.delete_character_state(character_id)
WandererApp.Cache.insert("tracked_characters", tracked_characters)
tracked_characters = state.characters |> Enum.reject(fn c_id -> c_id == character_id end)
WandererApp.Character.TrackerPoolDynamicSupervisor.stop_tracking(character_id)
WandererApp.Cache.insert("tracked_characters", tracked_characters)
duration = DateTime.diff(DateTime.utc_now(), start_time, :second)
%{state | characters: tracked_characters}
:telemetry.execute([:wanderer_app, :character, :tracker, :running], %{
duration: duration
})
:telemetry.execute([:wanderer_app, :character, :tracker, :stopped], %{count: 1})
end
state
end
def update_track_settings(
@@ -126,7 +119,7 @@ defmodule WandererApp.Character.TrackerManager.Impl do
)
{:ok, character_state} =
WandererApp.Character.Tracker.update_track_settings(character_id, track_settings)
WandererApp.Character.Tracker.update_settings(character_id, track_settings)
WandererApp.Character.update_character_state(character_id, character_state)
else
@@ -143,12 +136,12 @@ defmodule WandererApp.Character.TrackerManager.Impl do
end
def get_characters(
%{
characters: characters
} = state,
state,
_opts \\ []
),
do: {characters, state}
) do
{:ok, characters} = WandererApp.Cache.lookup("tracked_characters", [])
{characters, state}
end
def handle_event({ref, result}, state) do
Process.demonitor(ref, [:flush])
@@ -169,209 +162,14 @@ defmodule WandererApp.Character.TrackerManager.Impl do
end
end
def handle_info({:server_status, status}, state),
do: %{state | server_online: not status.vip}
def handle_info(
:update_online,
%{
characters: characters,
server_online: true
} =
state
) do
Process.send_after(self(), :update_online, @update_online_interval)
characters
|> Enum.map(fn character_id ->
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_online, [
character_id
])
end)
state
end
def handle_info(
:update_online,
state
) do
Process.send_after(self(), :update_online, @update_online_interval)
state
end
def handle_info(
:check_online_errors,
%{
characters: characters
} =
state
) do
Process.send_after(self(), :check_online_errors, @check_online_errors_interval)
characters
|> Task.async_stream(
fn character_id ->
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :check_online_errors, [
character_id
])
end,
timeout: :timer.seconds(15),
max_concurrency: System.schedulers_online(),
on_timeout: :kill_task
)
|> Enum.each(fn
{:ok, _result} -> :ok
{:error, reason} -> @logger.error("Error in check_online_errors: #{inspect(reason)}")
end)
state
end
def handle_info(
:update_location,
%{
characters: characters,
server_online: true
} =
state
) do
Process.send_after(self(), :update_location, @update_location_interval)
characters
|> Enum.map(fn character_id ->
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_location, [
character_id
])
end)
state
end
def handle_info(
:update_location,
state
) do
Process.send_after(self(), :update_location, @update_location_interval)
state
end
def handle_info(
:update_ship,
%{
characters: characters,
server_online: true
} =
state
) do
Process.send_after(self(), :update_ship, @update_ship_interval)
characters
|> Enum.map(fn character_id ->
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_ship, [
character_id
])
end)
state
end
def handle_info(
:update_ship,
state
) do
Process.send_after(self(), :update_ship, @update_ship_interval)
state
end
def handle_info(
:update_info,
%{
characters: characters,
server_online: true
} =
state
) do
Process.send_after(self(), :update_info, @update_info_interval)
characters
|> Task.async_stream(
fn character_id ->
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_info, [
character_id
])
end,
timeout: :timer.seconds(15),
max_concurrency: System.schedulers_online(),
on_timeout: :kill_task
)
|> Enum.each(fn
{:ok, _result} -> :ok
{:error, reason} -> @logger.error("Error in update_info: #{inspect(reason)}")
end)
state
end
def handle_info(
:update_info,
state
) do
Process.send_after(self(), :update_info, @update_info_interval)
state
end
def handle_info(
:update_wallet,
%{
characters: characters,
server_online: true
} =
state
) do
Process.send_after(self(), :update_wallet, @update_wallet_interval)
characters
|> Task.async_stream(
fn character_id ->
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_wallet, [
character_id
])
end,
timeout: :timer.seconds(15),
max_concurrency: System.schedulers_online(),
on_timeout: :kill_task
)
|> Enum.each(fn
{:ok, _result} -> :ok
{:error, reason} -> @logger.error("Error in update_wallet: #{inspect(reason)}")
end)
state
end
def handle_info(
:update_wallet,
state
) do
Process.send_after(self(), :update_wallet, @update_wallet_interval)
state
end
def handle_info(
:garbage_collect,
%{
characters: characters
} =
state
state
) do
Process.send_after(self(), :garbage_collect, @garbage_collection_interval)
{:ok, characters} = WandererApp.Cache.lookup("tracked_characters", [])
characters
|> Task.async_stream(
fn character_id ->
@@ -415,15 +213,15 @@ defmodule WandererApp.Character.TrackerManager.Impl do
WandererApp.Cache.get_and_remove!("character_untrack_queue", [])
|> Task.async_stream(
fn {map_id, character_id} ->
WandererApp.Cache.delete("map_#{map_id}:character_#{character_id}:tracked")
if not character_is_present(map_id, character_id) do
{:ok, character_state} =
WandererApp.Character.Tracker.update_settings(character_id, %{
map_id: map_id,
track: false
})
{:ok, character_state} =
WandererApp.Character.Tracker.update_track_settings(character_id, %{
map_id: map_id,
track: false
})
WandererApp.Character.update_character_state(character_id, character_state)
WandererApp.Character.update_character_state(character_id, character_state)
end
end,
max_concurrency: System.schedulers_online(),
on_timeout: :kill_task,
@@ -435,21 +233,23 @@ defmodule WandererApp.Character.TrackerManager.Impl do
end
def handle_info({:stop_track, character_id}, state) do
WandererApp.Cache.has_key?("character:#{character_id}:is_stop_tracking")
|> case do
false ->
WandererApp.Cache.insert("character:#{character_id}:is_stop_tracking", true)
Logger.debug(fn -> "Stopping character tracker: #{inspect(character_id)}" end)
state = state |> stop_tracking(character_id)
WandererApp.Cache.delete("character:#{character_id}:is_stop_tracking")
state
_ ->
state
if not WandererApp.Cache.has_key?("character:#{character_id}:is_stop_tracking") do
WandererApp.Cache.insert("character:#{character_id}:is_stop_tracking", true)
Logger.debug(fn -> "Stopping character tracker: #{inspect(character_id)}" end)
stop_tracking(state, character_id)
WandererApp.Cache.delete("character:#{character_id}:is_stop_tracking")
end
state
end
def handle_info(_event, state),
do: state
defp character_is_present(map_id, character_id) do
{:ok, presence_character_ids} =
WandererApp.Cache.lookup("map_#{map_id}:presence_character_ids", [])
Enum.member?(presence_character_ids, character_id)
end
end

View File

@@ -0,0 +1,354 @@
defmodule WandererApp.Character.TrackerPool do
@moduledoc false
use GenServer, restart: :transient
require Logger
defstruct [
:tracked_ids,
:uuid,
:characters,
server_online: true
]
@name __MODULE__
@cache :tracked_characters
@registry :tracker_pool_registry
@unique_registry :unique_tracker_pool_registry
@update_location_interval :timer.seconds(2)
@update_online_interval :timer.seconds(5)
@check_online_errors_interval :timer.seconds(30)
@update_ship_interval :timer.seconds(2)
@update_info_interval :timer.minutes(1)
@update_wallet_interval :timer.minutes(1)
@inactive_character_timeout :timer.minutes(5)
@logger Application.compile_env(:wanderer_app, :logger)
def new(), do: __struct__()
def new(args), do: __struct__(args)
def start_link(tracked_ids) do
uuid = UUID.uuid1()
GenServer.start_link(
@name,
{uuid, tracked_ids},
name: Module.concat(__MODULE__, uuid)
)
end
@impl true
def init({uuid, tracked_ids}) do
{:ok, _} = Registry.register(@unique_registry, Module.concat(__MODULE__, uuid), tracked_ids)
{:ok, _} = Registry.register(@registry, __MODULE__, uuid)
# Cachex.get_and_update(@cache, :tracked_characters, fn ids ->
# {:commit, ids ++ tracked_ids}
# end)
tracked_ids
|> Enum.each(fn id -> Cachex.put(@cache, id, uuid) end)
state =
%{
uuid: uuid,
characters: tracked_ids
}
|> new()
{:ok, state, {:continue, :start}}
end
@impl true
def terminate(_reason, _state) do
:ok
end
@impl true
def handle_cast(:stop, state), do: {:stop, :normal, state}
@impl true
def handle_cast({:add_tracked_id, tracked_id}, %{characters: characters, uuid: uuid} = state) do
Registry.update_value(@unique_registry, Module.concat(__MODULE__, uuid), fn r_tracked_ids ->
[tracked_id | r_tracked_ids]
end)
# Cachex.get_and_update(@cache, :tracked_characters, fn ids ->
# {:commit, ids ++ [tracked_id]}
# end)
Cachex.put(@cache, tracked_id, uuid)
{:noreply, %{state | characters: [tracked_id | characters]}}
end
@impl true
def handle_cast(
{:remove_tracked_id, tracked_id},
%{characters: characters, uuid: uuid} = state
) do
Registry.update_value(@unique_registry, Module.concat(__MODULE__, uuid), fn r_tracked_ids ->
r_tracked_ids |> Enum.reject(fn id -> id == tracked_id end)
end)
# Cachex.get_and_update(@cache, :tracked_characters, fn ids ->
# {:commit, ids |> Enum.reject(fn id -> id == tracked_id end)}
# end)
Cachex.del(@cache, tracked_id)
{:noreply, %{state | characters: characters |> Enum.reject(fn id -> id == tracked_id end)}}
end
@impl true
def handle_call(:error, _, state), do: {:stop, :error, :ok, state}
@impl true
def handle_continue(:start, state) do
Logger.info("#{@name} started")
Phoenix.PubSub.subscribe(
WandererApp.PubSub,
"server_status"
)
Process.send_after(self(), :update_online, 100)
Process.send_after(self(), :check_online_errors, @check_online_errors_interval)
Process.send_after(self(), :update_location, 300)
Process.send_after(self(), :update_ship, 500)
Process.send_after(self(), :update_info, 1500)
if WandererApp.Env.wallet_tracking_enabled?() do
Process.send_after(self(), :update_wallet, 1000)
end
{:noreply, state}
end
def handle_info({ref, result}, state) when is_reference(ref) do
Process.demonitor(ref, [:flush])
case result do
{:error, error} ->
@logger.error("#{__MODULE__} failed to process: #{inspect(error)}")
:ok
_ ->
:ok
end
{:noreply, state}
end
def handle_info({:server_status, status}, state),
do: {:noreply, %{state | server_online: not status.vip}}
def handle_info(
:update_online,
%{
characters: characters,
server_online: true
} =
state
) do
Process.send_after(self(), :update_online, @update_online_interval)
characters
|> Enum.map(fn character_id ->
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_online, [
character_id
])
end)
{:noreply, state}
end
def handle_info(
:update_online,
%{
characters: characters
} =
state
) do
Process.send_after(self(), :update_online, @update_online_interval)
characters
|> Enum.each(fn character_id ->
WandererApp.Character.update_character(character_id, %{online: false})
WandererApp.Character.update_character_state(character_id, %{
is_online: false
})
end)
{:noreply, state}
end
def handle_info(
:check_online_errors,
%{
characters: characters
} =
state
) do
Process.send_after(self(), :check_online_errors, @check_online_errors_interval)
characters
|> Task.async_stream(
fn character_id ->
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :check_online_errors, [
character_id
])
end,
timeout: :timer.seconds(15),
max_concurrency: System.schedulers_online(),
on_timeout: :kill_task
)
|> Enum.each(fn
{:ok, _result} -> :ok
{:error, reason} -> @logger.error("Error in check_online_errors: #{inspect(reason)}")
end)
{:noreply, state}
end
def handle_info(
:update_location,
%{
characters: characters,
server_online: true
} =
state
) do
Process.send_after(self(), :update_location, @update_location_interval)
characters
|> Enum.map(fn character_id ->
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_location, [
character_id
])
end)
{:noreply, state}
end
def handle_info(
:update_location,
state
) do
Process.send_after(self(), :update_location, @update_location_interval)
{:noreply, state}
end
def handle_info(
:update_ship,
%{
characters: characters,
server_online: true
} =
state
) do
Process.send_after(self(), :update_ship, @update_ship_interval)
characters
|> Enum.map(fn character_id ->
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_ship, [
character_id
])
end)
{:noreply, state}
end
def handle_info(
:update_ship,
state
) do
Process.send_after(self(), :update_ship, @update_ship_interval)
{:noreply, state}
end
def handle_info(
:update_info,
%{
characters: characters,
server_online: true
} =
state
) do
Process.send_after(self(), :update_info, @update_info_interval)
characters
|> Task.async_stream(
fn character_id ->
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_info, [
character_id
])
end,
timeout: :timer.seconds(15),
max_concurrency: System.schedulers_online(),
on_timeout: :kill_task
)
|> Enum.each(fn
{:ok, _result} -> :ok
{:error, reason} -> @logger.error("Error in update_info: #{inspect(reason)}")
end)
{:noreply, state}
end
def handle_info(
:update_info,
state
) do
Process.send_after(self(), :update_info, @update_info_interval)
{:noreply, state}
end
def handle_info(
:update_wallet,
%{
characters: characters,
server_online: true
} =
state
) do
Process.send_after(self(), :update_wallet, @update_wallet_interval)
characters
|> Task.async_stream(
fn character_id ->
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_wallet, [
character_id
])
end,
timeout: :timer.seconds(15),
max_concurrency: System.schedulers_online(),
on_timeout: :kill_task
)
|> Enum.each(fn
{:ok, _result} -> :ok
{:error, reason} -> @logger.error("Error in update_wallet: #{inspect(reason)}")
end)
{:noreply, state}
end
def handle_info(
:update_wallet,
state
) do
Process.send_after(self(), :update_wallet, @update_wallet_interval)
{:noreply, state}
end
defp via_tuple(uuid) do
{:via, Registry, {@unique_registry, Module.concat(__MODULE__, uuid)}}
end
end

View File

@@ -0,0 +1,107 @@
defmodule WandererApp.Character.TrackerPoolDynamicSupervisor do
@moduledoc false
use DynamicSupervisor
require Logger
@cache :tracked_characters
@registry :tracker_pool_registry
@unique_registry :unique_tracker_pool_registry
@tracker_pool_limit 100
@name __MODULE__
def start_link(_arg) do
DynamicSupervisor.start_link(@name, [], name: @name, max_restarts: 10)
end
def init(_arg) do
DynamicSupervisor.init(strategy: :one_for_one)
end
def start_tracking(tracked_id) do
case Registry.lookup(@registry, WandererApp.Character.TrackerPool) do
[] ->
start_child([tracked_id], 0)
pools ->
case get_available_pool(pools) do
nil ->
start_child([tracked_id], pools |> Enum.count())
pid ->
GenServer.cast(pid, {:add_tracked_id, tracked_id})
end
end
end
def stop_tracking(tracked_id) do
{:ok, uuid} = Cachex.get(@cache, tracked_id)
case Registry.lookup(
@unique_registry,
Module.concat(WandererApp.Character.TrackerPool, uuid)
) do
[] ->
:ok
[{pool_pid, _}] ->
GenServer.cast(pool_pid, {:remove_tracked_id, tracked_id})
end
end
def is_not_tracked?(tracked_id) do
{:ok, tracked_ids} = Cachex.get(@cache, :tracked_characters)
tracked_ids |> Enum.member?(tracked_id) |> Kernel.not()
end
defp get_available_pool([]), do: nil
defp get_available_pool([{pid, uuid} | pools]) do
case Registry.lookup(@unique_registry, Module.concat(WandererApp.Character.TrackerPool, uuid)) do
[] ->
nil
uuid_pools ->
case get_available_pool_pid(uuid_pools) do
nil ->
get_available_pool(pools)
pid ->
pid
end
end
end
defp get_available_pool_pid([]), do: nil
defp get_available_pool_pid([{pid, tracked_ids} | pools]) do
if Enum.count(tracked_ids) < @tracker_pool_limit do
pid
else
get_available_pool_pid(pools)
end
end
defp start_child(tracked_ids, pools_count) do
case DynamicSupervisor.start_child(@name, {WandererApp.Character.TrackerPool, tracked_ids}) do
{:ok, pid} ->
Logger.info("Starting tracking pool, total pools: #{pools_count + 1}")
{:ok, pid}
{:error, {:already_started, pid}} ->
{:ok, pid}
end
end
defp stop_child(uuid) do
case Registry.lookup(@registry, uuid) do
[{pid, _}] ->
GenServer.cast(pid, :stop)
_ ->
Logger.warn("Unable to locate pool assigned to #{inspect(uuid)}")
:ok
end
end
end

View File

@@ -0,0 +1,22 @@
defmodule WandererApp.Character.TrackerPoolSupervisor do
@moduledoc false
use Supervisor
@name __MODULE__
@registry :tracker_pool_registry
@unique_registry :unique_tracker_pool_registry
def start_link(_args) do
Supervisor.start_link(@name, [], name: @name)
end
def init(_args) do
children = [
{Registry, [keys: :unique, name: @unique_registry]},
{Registry, [keys: :duplicate, name: @registry]},
{WandererApp.Character.TrackerPoolDynamicSupervisor, []}
]
Supervisor.init(children, strategy: :rest_for_one, max_restarts: 10)
end
end

Some files were not shown because too many files have changed in this diff Show More