Compare commits

...

329 Commits

Author SHA1 Message Date
Dmitry Popov
02b450325e fix(Core): Added Eve data downloaded files cleanup logic 2025-10-19 12:37:31 +02:00
Dmitry Popov
136bc4cbb9 feat(Core): Added map roles settings for copy/paste 2025-10-19 12:03:16 +02:00
Dmitry Popov
dab49df9aa Merge branch 'main' into copy-past-roles 2025-10-16 16:01:41 +02:00
Dmitry Popov
6286087f3e feat(Core): Added map roles settings for copy/paste 2025-10-16 16:01:12 +02:00
CI
4ce7160f79 chore: [skip ci] 2025-10-15 19:59:55 +00:00
CI
2913bf19b0 chore: release version v1.82.0 2025-10-15 19:59:55 +00:00
Dmitry Popov
7bd6be6fd0 Merge pull request #528 from wanderer-industries/copy-past-systems-with-connections
Copy past systems with connections
2025-10-15 23:56:31 +04:00
Dmitry Popov
705daa286b Merge branch 'main' into copy-past-systems-with-connections 2025-10-15 21:56:09 +02:00
Dmitry Popov
614d06be66 feat(Core): Added an ability to copy/paste selected map area between maps 2025-10-15 21:55:56 +02:00
CI
dec3e9a7ce chore: [skip ci] 2025-10-15 19:08:54 +00:00
CI
0017ac3373 chore: release version v1.81.15 2025-10-15 19:08:54 +00:00
DanSylvest
ae34744578 Merge remote-tracking branch 'origin/main' 2025-10-15 22:06:53 +03:00
DanSylvest
76885058ef fix(Map): Fixed problem with commit - for correct restore deprecated data - change config key 2025-10-15 22:06:31 +03:00
CI
fccb007036 chore: [skip ci] 2025-10-15 18:56:52 +00:00
CI
a9f8901bd5 chore: release version v1.81.14 2025-10-15 18:56:52 +00:00
DanSylvest
8ae968b5be fix(Map): Fixed problem with commit - for correct restore deprecated data 2025-10-15 21:54:57 +03:00
Dmitry Popov
beffd45e4f Merge branch 'main' into copy-past-systems-with-connections 2025-10-15 16:30:57 +02:00
CI
4488d81e8d chore: [skip ci] 2025-10-15 12:30:56 +00:00
CI
618cc8c5f1 chore: release version v1.81.13 2025-10-15 12:30:56 +00:00
Dmitry Popov
3fb22a877e fix(Core): Fixed system select after tab switch 2025-10-15 14:30:19 +02:00
Dmitry Popov
8759409b82 Merge branch 'main' into copy-past-systems-with-connections 2025-10-15 12:51:25 +02:00
CI
245647ae6a chore: [skip ci] 2025-10-15 10:33:07 +00:00
CI
eb7d33ea07 chore: release version v1.81.12 2025-10-15 10:33:07 +00:00
Dmitry Popov
3575b16def fix(Core): Fixed map events buffering on tab switch 2025-10-15 12:32:29 +02:00
CI
a6fb680be8 chore: [skip ci] 2025-10-15 09:59:48 +00:00
CI
9e17df5544 chore: release version v1.81.11 2025-10-15 09:59:48 +00:00
Dmitry Popov
683fde7be4 fix(Signatures): Fixed EOL indication for un-splashed and signatures list 2025-10-15 11:59:14 +02:00
DanSylvest
ee68ce92a2 fix(Map): Add ability to copy and past systems (UI part) 2025-10-14 14:34:47 +03:00
CI
8b4e38d795 chore: [skip ci] 2025-10-13 22:46:48 +00:00
CI
4995202627 chore: release version v1.81.10 2025-10-13 22:46:48 +00:00
Dmitry Popov
986b997a6a fix(Signatures): Rework for lazy signatures deletion
- if lazy delete enabled, linked connection deleted after signature only
now
- added sort by signature name for info column
- show signature temporary name if set on link signature to system &
signatures widget
2025-10-14 00:46:04 +02:00
CI
9a957af759 chore: [skip ci] 2025-10-12 21:37:58 +00:00
CI
c5a0a96016 chore: release version v1.81.9 2025-10-12 21:37:58 +00:00
Dmitry Popov
8715a6c0ac fix(Signatures): Fixed issue with wrong linked signatures deletions 2025-10-12 23:37:22 +02:00
CI
c9810095aa chore: [skip ci] 2025-10-11 16:12:20 +00:00
CI
69eb888469 chore: release version v1.81.8 2025-10-11 16:12:20 +00:00
DanSylvest
748347df9a fix(Map): Fix problem with restoring settings on widgets 2025-10-11 19:10:27 +03:00
CI
aa4d49027c chore: [skip ci] 2025-10-10 19:12:44 +00:00
CI
a9d7387e40 chore: release version v1.81.7 2025-10-10 19:12:44 +00:00
DanSylvest
dc4d260c9b fix(Map): Fixed problem with rendering dropdown classes in signatures 2025-10-10 22:10:54 +03:00
CI
dc430491bf chore: [skip ci] 2025-10-10 06:47:34 +00:00
CI
42cd261ea7 chore: release version v1.81.6 2025-10-10 06:47:34 +00:00
Aleksei Chichenkov
35af4fdc09 Merge pull request #520 from wanderer-industries/migrations
Migrations
2025-10-10 09:47:08 +03:00
DanSylvest
8bb4998e59 Merge branch 'main' into migrations 2025-10-10 09:44:49 +03:00
DanSylvest
c825a3f4c4 fix(Map): Fixed problem with a lot unnecessary loads zkb data on resize map 2025-10-10 09:34:25 +03:00
CI
5343c34488 chore: [skip ci] 2025-10-09 19:57:07 +00:00
CI
4878be1a53 chore: release version v1.81.5 2025-10-09 19:57:07 +00:00
Dmitry Popov
1ff689c26c fix(Core): Update connection ship size based on linked signature type 2025-10-09 21:56:23 +02:00
CI
79b660e899 chore: [skip ci] 2025-10-09 18:45:16 +00:00
CI
665a679bd5 chore: release version v1.81.4 2025-10-09 18:45:16 +00:00
Dmitry Popov
7bd634eb95 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-10-09 20:44:42 +02:00
Dmitry Popov
c3b5a77a86 fix(Core): Fixed signature to system link issues 2025-10-09 20:44:33 +02:00
DanSylvest
8498846d9c fix(Map): Added ability to see focused element 2025-10-09 15:52:48 +03:00
CI
12f39a0133 chore: [skip ci] 2025-10-07 20:59:47 +00:00
CI
ffc2a86e95 chore: release version v1.81.3 2025-10-07 20:59:47 +00:00
Dmitry Popov
82babf41a2 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-10-07 22:59:10 +02:00
Dmitry Popov
81055b4fbd fix(Core): Fixed cancel ping errors 2025-10-07 22:59:01 +02:00
CI
5070a59f88 chore: [skip ci] 2025-10-07 20:49:34 +00:00
CI
65d5bf960d chore: release version v1.81.2 2025-10-07 20:49:34 +00:00
Dmitry Popov
8fc4cb190e Merge pull request #526 from guarzo/guarzo/apicustom
fix: api dropping custom name
2025-10-08 00:49:00 +04:00
DanSylvest
c6c065dbb9 fix(Map): Removed unnecessary vertical scroller in Character Tracking dialog. Main always first in list of tracking characters, following next after main, another characters sorting by name 2025-10-07 15:06:29 +03:00
DanSylvest
8ba34533d7 fix(Map): Added Search tool for systems what on the map 2025-10-07 14:07:13 +03:00
Guarzo
095a4b2362 fix: api dropping custom name 2025-10-06 15:52:35 -04:00
CI
fafc631e49 chore: [skip ci] 2025-10-02 21:37:58 +00:00
CI
e56383c8b1 chore: release version v1.81.1 2025-10-02 21:37:58 +00:00
Dmitry Popov
b9c26bdb04 fix(Core): Fixed characters tracking updates. 2025-10-02 23:37:23 +02:00
CI
8aeaa81752 chore: [skip ci] 2025-10-02 16:09:27 +00:00
CI
b16ec0490f chore: release version v1.81.0 2025-10-02 16:09:27 +00:00
Dmitry Popov
eceaf1d73b Merge pull request #523 from dedo1911/feat/pwa
feat(core): fix pwa icons + add screen in manifest
2025-10-02 20:08:57 +04:00
dedo1911
34cf668a33 feat(core): fix pwa icons + add screen in manifest 2025-10-02 17:57:52 +02:00
CI
c22d410c9f chore: [skip ci] 2025-10-02 11:39:21 +00:00
CI
fc6af867f2 chore: release version v1.80.0 2025-10-02 11:39:21 +00:00
Dmitry Popov
2d96114984 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-10-02 13:38:50 +02:00
Dmitry Popov
fd7e19e490 feat(Core): Added PWA web manifest 2025-10-02 13:38:46 +02:00
CI
f7d996f5b2 chore: [skip ci] 2025-10-01 14:32:20 +00:00
CI
f8ab1383ab chore: release version v1.79.6 2025-10-01 14:32:20 +00:00
Dmitry Popov
e1559aac94 fix(Core): Fixed modals auto-save on Enter. 2025-10-01 16:31:48 +02:00
CI
2e17cce5cd chore: [skip ci] 2025-10-01 13:57:58 +00:00
CI
8fb831f171 chore: release version v1.79.5 2025-10-01 13:57:58 +00:00
Dmitry Popov
cb84f34515 fix(Core): Fixed system details modal auto-save on Enter. 2025-10-01 15:57:26 +02:00
CI
272cce1a77 chore: [skip ci] 2025-09-30 13:00:53 +00:00
CI
e0e3ed1580 chore: release version v1.79.4 2025-09-30 13:00:53 +00:00
Dmitry Popov
c4c848cf37 fix(Core): Fixed updating connection time status based on linked signature data. Fixed FR gas sites parsing.
Some checks failed
Build Test / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build Test / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
🧪 Test Suite / Test Suite (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-09-30 15:00:14 +02:00
DanSylvest
d33a2e3a5b Merge branch 'refs/heads/main' into migrations
# Conflicts:
#	assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/components/ServerSettings.tsx
2025-09-28 21:23:28 +03:00
DanSylvest
5c8753fb96 fix(Map): Added migration mechanism 2025-09-28 09:29:42 +03:00
CI
32d25d86eb chore: [skip ci] 2025-09-27 15:37:16 +00:00
CI
863adccac1 chore: release version v1.79.3 2025-09-27 15:37:16 +00:00
Dmitry Popov
2d527e1d16 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-09-27 17:36:40 +02:00
Dmitry Popov
9a64ad6fa7 fix(Core): Fixed connection passages count 2025-09-27 17:36:35 +02:00
CI
5ce472ebff chore: [skip ci] 2025-09-26 18:28:30 +00:00
CI
76588af12f chore: release version v1.79.2 2025-09-26 18:28:30 +00:00
Dmitry Popov
134f169eb9 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-09-26 20:28:01 +02:00
Dmitry Popov
7c2d731c4c chore: fix 2025-09-26 20:27:54 +02:00
CI
c7e2a290cf chore: [skip ci] 2025-09-26 18:27:38 +00:00
CI
5ea966892a chore: release version v1.79.1 2025-09-26 18:27:38 +00:00
Dmitry Popov
b879db76b7 chore: fix 2025-09-26 20:27:06 +02:00
CI
d13a628029 chore: [skip ci] 2025-09-26 18:18:51 +00:00
CI
7c1e2595e3 chore: release version v1.79.0 2025-09-26 18:18:51 +00:00
Dmitry Popov
a99e8a915e Merge pull request #522 from wanderer-industries/update-lifetime
Update lifetime
2025-09-26 22:18:20 +04:00
Dmitry Popov
36f424da0b Merge branch 'main' into update-lifetime 2025-09-26 15:45:19 +02:00
Dmitry Popov
c0a65d5a23 Merge branch 'main' into update-lifetime 2025-09-26 00:57:26 +02:00
Dmitry Popov
02e31333d2 chore: fix 2025-09-26 00:54:55 +02:00
Dmitry Popov
d69616119d feat(Core): Updated connections EOL logic 2025-09-26 00:54:14 +02:00
DanSylvest
f89cd5f44f fix(Map): Remove settings some default values if migration from very old settings system 2025-09-25 21:21:24 +03:00
DanSylvest
abe4951251 Merge branch 'main' into migrations 2025-09-25 21:20:49 +03:00
Dmitry Popov
dbc770d40b Merge branch 'update-lifetime' of github.com:wanderer-industries/wanderer into update-lifetime 2025-09-24 18:44:10 +02:00
CI
e69a8fece5 chore: [skip ci] 2025-09-24 16:38:54 +00:00
CI
cf20be8a77 chore: release version v1.78.1 2025-09-24 16:38:54 +00:00
Dmitry Popov
450bcb649c Merge pull request #521 from wanderer-industries/kills-fix
Kills fix
2025-09-24 20:38:29 +04:00
Dmitry Popov
a00395351e Merge pull request #513 from guarzo/guarzo/killfilter
fix: removed wormhole only logic error
2025-09-24 20:33:00 +04:00
DanSylvest
3b24c760ff fix(Map): Fixed eslint problems 2025-09-24 13:12:32 +03:00
DanSylvest
3801f0be18 Merge branch 'main' into update-lifetime
# Conflicts:
#	assets/js/hooks/Mapper/components/map/components/ContextMenuConnection/ContextMenuConnection.tsx
#	assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx
2025-09-24 13:11:02 +03:00
DanSylvest
5508fbee2f fix(Map): MIGRATION: support from old store settings import 2025-09-24 11:07:52 +03:00
DanSylvest
51ff4e7f36 fix(Map): Add common migration mechanism. ATTENTION! This is a non-reversible stored map settings commit — it means we do not guarantee that settings will work if you check out back. We’ve tried to migrate old settings, but it may not work well or may NOT work at all. 2025-09-24 11:03:17 +03:00
Dmitry Popov
f3104db2e4 Merge branch 'update-lifetime' of github.com:wanderer-industries/wanderer into update-lifetime 2025-09-23 20:19:53 +02:00
CI
602b1028c3 chore: [skip ci] 2025-09-23 16:27:20 +00:00
CI
4f98e979a2 chore: release version v1.78.0 2025-09-23 16:27:20 +00:00
Dmitry Popov
e0f46c4af7 Merge pull request #519 from wanderer-industries/jumpgates
Jumpgates
2025-09-23 20:23:25 +04:00
Dmitry Popov
bc8a9a2b85 Merge branch 'main' into jumpgates 2025-09-23 18:23:11 +02:00
Dmitry Popov
c2b03f925d Merge pull request #518 from leesolway/drag-dialog
Drag signature dialog
2025-09-23 20:22:47 +04:00
Dmitry Popov
efa2e52054 Merge branch 'main' into jumpgates 2025-09-23 18:18:32 +02:00
Dmitry Popov
cedf5761f8 Merge branch 'main' into jumpgates 2025-09-23 17:51:45 +02:00
Dmitry Popov
f601bb8751 Merge branch 'main' into jumpgates 2025-09-23 17:48:59 +02:00
Dmitry Popov
c39d2a56d2 Merge branch 'main' into jumpgates 2025-09-23 17:19:05 +02:00
DanSylvest
34d3d92afd fix(Map): Add front-end migrations for local store settings 2025-09-22 12:24:34 +03:00
Lee Solway
805722bbe8 Merge branch 'wanderer-industries:main' into drag-dialog 2025-09-20 16:05:34 +01:00
Lee Solway
fe3e38343b SystemSettingsDialog & SignatureSettings draggable 2025-09-19 17:35:28 +01:00
DanSylvest
616e82c497 fix(Map): Add support for Bridge. Made all tooltips left and right paddings. 2025-09-18 11:52:16 +03:00
guarzo
ab7e47b91f pr feedback 2025-09-18 06:18:23 +00:00
guarzo
cf1c103a46 fix: pr feedback 2025-09-17 15:50:26 +00:00
CI
71202a4a29 chore: [skip ci] 2025-09-14 15:00:45 +00:00
CI
a7e0ceac4c chore: release version v1.77.19 2025-09-14 15:00:45 +00:00
Aleksei Chichenkov
6bce701aab Merge pull request #515 from wanderer-industries/wh-db-fixed
fix(Map): Fixed for all Large wormholes jump mass from 300 to 375. Fi…
2025-09-14 18:00:20 +03:00
CI
f8b9e206a5 chore: [skip ci] 2025-09-13 21:53:27 +00:00
CI
4c1ec2004b chore: release version v1.77.18 2025-09-13 21:53:27 +00:00
Dmitry Popov
ebed74d239 Revert "fix: Updated ACL create/update APIs"
This reverts commit b6c680e802.
2025-09-13 23:52:02 +02:00
DanSylvest
c789b69b54 fix(Map): Update lifetime design and buttons 2025-09-13 19:17:00 +03:00
Dmitry Popov
24c32511d8 Merge branch 'main' into jumpgates 2025-09-13 11:30:40 +02:00
Dmitry Popov
302fb0642d chore: update connection time values 2025-09-13 11:24:11 +02:00
DanSylvest
06e7b6e3eb fix(Map): Fixed for all Large wormholes jump mass from 300 to 375. Fixed jump mass and total mass for N290, K329. Fixed static for J005663 was H296 now Y790. Added J492 wormhole. Change lifetime for E587 from 16 to 48 2025-09-12 19:37:59 +03:00
DanSylvest
33acd55eaa fix(Map): Update wormhole lifetime UI and removed unnecessary code 2025-09-12 11:05:57 +03:00
CI
dec82e89c2 chore: [skip ci] 2025-09-11 19:14:13 +00:00
CI
f5ac5bc4ec chore: release version v1.77.17 2025-09-11 19:14:13 +00:00
Dmitry Popov
b6c680e802 fix: Updated ACL create/update APIs 2025-09-11 21:13:41 +02:00
CI
5fa57c13b4 chore: [skip ci] 2025-09-11 17:56:11 +00:00
CI
acc81fda44 chore: release version v1.77.16 2025-09-11 17:56:11 +00:00
Dmitry Popov
7ab5acf45f Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-09-11 19:55:37 +02:00
Dmitry Popov
0d4ffbcc22 fix: Fixed issue with ACL add members button for managers. Added WANDERER_RESTRICT_ACLS_CREATION env support. 2025-09-11 19:55:32 +02:00
CI
a9253ac2df chore: [skip ci] 2025-09-10 07:29:33 +00:00
CI
d00b4843a7 chore: release version v1.77.15 2025-09-10 07:29:33 +00:00
Aleksei Chichenkov
6068de2c71 Merge pull request #514 from wanderer-industries/unnecessary-rerenders
fix(Map): Fix problem with unnecessary rerenders and loads routes if …
2025-09-10 10:29:03 +03:00
DanSylvest
73da427c6b fix(Map): Fix problem with unnecessary rerenders and loads routes if move/positioning widgets. 2025-09-10 10:10:17 +03:00
CI
9b7ec0ddfe chore: [skip ci] 2025-09-08 22:07:20 +00:00
CI
c2f5f14c44 chore: release version v1.77.14 2025-09-08 22:07:20 +00:00
Dmitry Popov
0b7c3588d5 fix: Fixed issue with loading connection info 2025-09-09 00:06:49 +02:00
CI
a51fac5736 chore: [skip ci] 2025-09-07 22:27:12 +00:00
CI
726c3d0704 chore: release version v1.77.13 2025-09-07 22:27:12 +00:00
Dmitry Popov
8dd564dbd0 fix: Updated character tracking, added an extra check for offline characters to reduce errors 2025-09-08 00:26:40 +02:00
CI
e33c65cddc chore: [skip ci] 2025-09-07 19:28:25 +00:00
CI
f2fbd2ead0 chore: release version v1.77.12 2025-09-07 19:28:25 +00:00
Dmitry Popov
123a2e45eb Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-09-07 21:27:56 +02:00
Dmitry Popov
f8d2d9c680 fix: Decreased character tracking grace period 2025-09-07 21:27:53 +02:00
CI
9dcbef9a79 chore: [skip ci] 2025-09-07 19:16:08 +00:00
CI
0b14857a12 chore: release version v1.77.11 2025-09-07 19:16:08 +00:00
Dmitry Popov
bd3d516f60 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-09-07 21:15:39 +02:00
Dmitry Popov
40d0bd8cea fix: Fixed CSP errors 2025-09-07 21:15:35 +02:00
guarzo
873946a1a6 fix: removed wormhole only logic error 2025-09-05 01:43:47 +00:00
CI
968deeb254 chore: [skip ci] 2025-09-04 09:17:39 +00:00
CI
959041be52 chore: release version v1.77.10 2025-09-04 09:17:39 +00:00
Dmitry Popov
3319520179 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-09-04 11:17:08 +02:00
Dmitry Popov
580fcf3657 fix: Removed invalid invite options 2025-09-04 11:17:04 +02:00
CI
53dae7c520 chore: [skip ci] 2025-09-04 09:11:38 +00:00
CI
6d59d709f1 chore: release version v1.77.9 2025-09-04 09:11:38 +00:00
Dmitry Popov
4343e9070c fix: Auto select following char system on start 2025-09-04 11:10:59 +02:00
CI
b62373fb5f chore: [skip ci] 2025-09-03 14:38:52 +00:00
CI
3da98f8e56 chore: release version v1.77.8 2025-09-03 14:38:52 +00:00
Dmitry Popov
494d24952e Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-09-03 16:38:26 +02:00
Dmitry Popov
8a6b17bd7b fix: Updated character tracking 2025-09-03 16:38:23 +02:00
CI
d2e859a74e chore: [skip ci] 2025-09-03 13:03:26 +00:00
CI
4a78d55d22 chore: release version v1.77.7 2025-09-03 13:03:26 +00:00
Dmitry Popov
dc252b8c4b Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-09-03 15:02:57 +02:00
Dmitry Popov
c433205e89 fix: Updated character tracking 2025-09-03 15:02:53 +02:00
CI
d6bc5b57b1 chore: [skip ci] 2025-09-02 17:34:32 +00:00
CI
280a286266 chore: release version v1.77.6 2025-09-02 17:34:32 +00:00
Dmitry Popov
d5c18b5de3 fix: Updated character tracking, added grace period to reduce false-positive cases 2025-09-02 19:33:57 +02:00
CI
7452e5d011 chore: [skip ci] 2025-09-02 10:37:50 +00:00
CI
71674b0d52 chore: release version v1.77.5 2025-09-02 10:37:50 +00:00
Dmitry Popov
5b4824bd5d Merge pull request #510 from guarzo/guarzo/newtracking
fix: resolve tracking issues
2025-09-02 14:37:22 +04:00
CI
deda16a7da chore: [skip ci] 2025-09-02 10:26:47 +00:00
CI
0b7c067de7 chore: release version v1.77.4 2025-09-02 10:26:47 +00:00
Dmitry Popov
0d0db8c129 Merge pull request #509 from guarzo/guarzo/aclapi
fix: ensure pub/sub occurs after acl api change
2025-09-02 14:26:20 +04:00
guarzo
9f1b7994a3 fix: resolve tracking issues 2025-09-02 07:11:25 +00:00
guarzo
378df0ac70 fix: pr feedback 2025-09-02 00:27:40 +00:00
guarzo
0e4a132f69 refactor: dry 2025-09-01 22:38:12 +00:00
guarzo
631746375d fix: ensure pub/sub occurs after acl api change 2025-09-01 22:11:58 +00:00
CI
7dc01dad54 chore: [skip ci] 2025-08-29 00:33:30 +00:00
CI
8a9807d3e5 chore: release version v1.77.3 2025-08-29 00:33:30 +00:00
Dmitry Popov
39df3c97ce Merge pull request #505 from wanderer-industries/tracking-fix
Tracking fix
2025-08-29 04:33:00 +04:00
Dmitry Popov
46c1ccdfcc fix: Fixed character tracking settings 2025-08-29 02:31:00 +02:00
Dmitry Popov
8817536038 fix: Fixed character tracking settings 2025-08-29 02:30:19 +02:00
Dmitry Popov
c3bb23a6ee fix: Fixed character tracking settings 2025-08-29 01:41:08 +02:00
Dmitry Popov
7e9c4c575e fix: Fixed character tracking settings 2025-08-28 22:18:18 +02:00
CI
5a70eee91e chore: [skip ci] 2025-08-28 10:24:51 +00:00
CI
228f6990a1 chore: release version v1.77.2 2025-08-28 10:24:51 +00:00
Dmitry Popov
d80ed0e70e Merge pull request #504 from guarzo/guarzo/sigapi
fix: update system signature api to return correct system id
2025-08-28 14:24:26 +04:00
CI
4576c75737 chore: [skip ci] 2025-08-28 10:03:36 +00:00
CI
67764faaa7 chore: release version v1.77.1 2025-08-28 10:03:36 +00:00
Dmitry Popov
91dd0b27ae chore: Added support for limited telemetry (base only). 2025-08-28 12:03:01 +02:00
guarzo
99dcf49fbc Merge branch 'main' into guarzo/sigapi 2025-08-27 21:30:59 -04:00
guarzo
6fb3edbfd6 fix: update system signature api to return correct system id 2025-08-28 01:30:37 +00:00
CI
26f13ce857 chore: [skip ci] 2025-08-27 21:18:31 +00:00
CI
e9b475c0a8 chore: release version v1.77.0 2025-08-27 21:18:31 +00:00
Dmitry Popov
7752010092 feat(Core): Reduced DB calls to check existing system jumps 2025-08-27 23:17:58 +02:00
CI
d3705b3ed7 chore: [skip ci] 2025-08-27 20:46:18 +00:00
CI
1394e2897e chore: release version v1.76.13 2025-08-27 20:46:18 +00:00
Dmitry Popov
5117a1c5af fix(Core): Fixed maps start timeout 2025-08-27 22:42:29 +02:00
CI
3c62403f33 chore: [skip ci] 2025-08-20 14:37:18 +00:00
CI
a4760f5162 chore: release version v1.76.12 2025-08-20 14:37:18 +00:00
Dmitry Popov
b071070431 fix(Core): Reduced ESI api calls to update character corp/ally info 2025-08-20 16:36:46 +02:00
CI
3bcb9628e7 chore: [skip ci] 2025-08-20 07:53:27 +00:00
CI
e62c4cf5bf chore: release version v1.76.11 2025-08-20 07:53:27 +00:00
Dmitry Popov
af46962ce4 Merge pull request #503 from wanderer-industries/revert-501-guarzo/sigsfix
Revert "fix: default signature types not being shown"
2025-08-20 11:53:00 +04:00
Dmitry Popov
0b0967830b Revert "fix: default signature types not being shown" 2025-08-20 11:52:34 +04:00
CI
172251a208 chore: [skip ci] 2025-08-18 23:28:33 +00:00
CI
8a6fb63d55 chore: release version v1.76.10 2025-08-18 23:28:33 +00:00
Dmitry Popov
9652959e5e fix(Core): Added character trackers start queue 2025-08-19 01:27:58 +02:00
CI
825ef46d41 chore: [skip ci] 2025-08-18 11:42:47 +00:00
CI
ad9f7c6b95 chore: release version v1.76.9 2025-08-18 11:42:47 +00:00
Dmitry Popov
b960b5c149 Merge pull request #501 from guarzo/guarzo/sigsfix
fix: default signature types not being shown
2025-08-18 15:42:14 +04:00
CI
0f092d21f9 chore: [skip ci] 2025-08-17 21:28:20 +00:00
CI
031576caa6 chore: release version v1.76.8 2025-08-17 21:28:20 +00:00
Dmitry Popov
7a97a96c42 fix(Core): added DB connection default timeouts 2025-08-17 23:27:21 +02:00
CI
2efb2daba0 chore: [skip ci] 2025-08-16 22:17:44 +00:00
CI
4374c39924 chore: release version v1.76.7 2025-08-16 22:17:44 +00:00
Dmitry Popov
15711495c7 fix(Core): Fixed auth redirect URL 2025-08-17 00:17:17 +02:00
guarzo
236f803427 fix: default signature types not being shown 2025-08-15 23:03:22 +00:00
CI
6772130f2a chore: [skip ci] 2025-08-15 15:27:07 +00:00
CI
ddd72f3fac chore: release version v1.76.6 2025-08-15 15:27:07 +00:00
Dmitry Popov
6e262835ef Merge pull request #500 from guarzo/guarzo/moressefix
fix: empty subscriptions for sse
2025-08-15 19:26:34 +04:00
guarzo
2f3b8ddc5f fix: empty subscriptions for sse 2025-08-15 11:08:40 -04:00
CI
cea3a74b34 chore: [skip ci] 2025-08-15 10:29:11 +00:00
CI
867941a233 chore: release version v1.76.5 2025-08-15 10:29:11 +00:00
Dmitry Popov
3ff388a16d fix(Core): fixed tracking paused issues, fixed user activity data 2025-08-15 12:28:36 +02:00
CI
f4248e9ab9 chore: [skip ci] 2025-08-14 23:40:20 +00:00
CI
507b3289c7 chore: release version v1.76.4 2025-08-14 23:40:20 +00:00
Dmitry Popov
9e1dfc48d5 Merge pull request #499 from guarzo/guarzo/relfixes 2025-08-15 03:39:50 +04:00
guarzo
518cbc7b5d fix: timestamp errors for sse and tracking 2025-08-14 19:22:30 -04:00
CI
ccc8db0620 chore: [skip ci] 2025-08-14 21:41:56 +00:00
CI
7cfb663efd chore: release version v1.76.3 2025-08-14 21:41:56 +00:00
Dmitry Popov
e5103cc925 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-08-14 23:41:30 +02:00
Dmitry Popov
26458f5a19 chore: Get rid of tracking pauses 2025-08-14 23:41:26 +02:00
CI
79d5ec6caf chore: [skip ci] 2025-08-14 20:56:39 +00:00
CI
034d461ab6 chore: release version v1.76.2 2025-08-14 20:56:39 +00:00
Dmitry Popov
2e9c1c170c chore: Get rid of tracking pauses 2025-08-14 22:56:08 +02:00
Dmitry Popov
24ad3b2c61 chore: [skip ci] 2025-08-14 11:28:56 +02:00
CI
288f55dc2f chore: [skip ci] 2025-08-13 16:15:29 +00:00
CI
78dbea6267 chore: release version v1.76.1 2025-08-13 16:15:29 +00:00
Dmitry Popov
6a9e53141d Merge pull request #498 from wanderer-industries/reselect-systems-after-init
fix(Map): Fix problem when systems was deselected after change tab
2025-08-13 20:15:05 +04:00
DanSylvest
05e6994520 fix(Map): Fix problem when systems was deselected after change tab 2025-08-13 18:58:24 +03:00
CI
1a4dc67eb9 chore: [skip ci] 2025-08-12 11:39:44 +00:00
CI
31d87a116b chore: release version v1.76.0 2025-08-12 11:39:44 +00:00
Dmitry Popov
c47796d590 Merge pull request #497 from wanderer-industries/sig-temp-names
Sig temp names
2025-08-12 15:39:07 +04:00
Dmitry Popov
c7138a41ee feat(Signatures): Sync signature temporary name with system on link signature to system 2025-08-12 13:20:03 +02:00
Dmitry Popov
96f04c70a9 Merge branch 'main' into sig-temp-names 2025-08-11 19:20:46 +02:00
Dmitry Popov
87a8bc09ab chore: [skip ci] 2025-08-11 19:20:37 +02:00
Dmitry Popov
5f5661d559 chore: [skip ci] 2025-08-11 19:20:13 +02:00
CI
35ca87790e chore: [skip ci] 2025-08-11 15:57:51 +00:00
CI
ae43e4a57c chore: release version v1.75.23 2025-08-11 15:57:51 +00:00
Dmitry Popov
b91712a01a chore: updated deps 2025-08-11 17:54:23 +02:00
CI
b20007b341 chore: [skip ci] 2025-08-11 14:47:44 +00:00
CI
6a24e1188b chore: release version v1.75.22 2025-08-11 14:47:44 +00:00
Dmitry Popov
5894efc1aa chore: release version v1.75.21 2025-08-11 16:47:11 +02:00
CI
a05612d243 chore: [skip ci] 2025-08-11 12:02:06 +00:00
CI
48de874d6b chore: release version v1.75.21 2025-08-11 12:02:06 +00:00
Dmitry Popov
91e6da316f Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-08-11 14:01:41 +02:00
Dmitry Popov
fa60bd81a1 chore: release version v1.75.19 2025-08-11 14:01:33 +02:00
CI
a08a69c5be chore: [skip ci] 2025-08-11 11:55:22 +00:00
CI
18d450a41a chore: release version v1.75.20 2025-08-11 11:55:22 +00:00
Dmitry Popov
36cdee61c0 chore: release version v1.75.19 2025-08-11 13:54:51 +02:00
Dmitry Popov
797e188259 fix: Fixed docs 2025-08-11 13:49:22 +02:00
Dmitry Popov
91b581668a Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-08-11 13:44:17 +02:00
Dmitry Popov
ad01fec28f Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-08-11 13:44:13 +02:00
CI
357d3a0df6 chore: release version v1.75.19 2025-08-11 11:25:52 +00:00
CI
5ce6022761 chore: release version v1.75.18 2025-08-11 11:25:17 +00:00
CI
235a0c5aea chore: release version v1.75.17 2025-08-11 11:24:44 +00:00
CI
9b81fa6ebb chore: release version v1.75.16 2025-08-11 11:24:16 +00:00
Dmitry Popov
8792d5ab0e Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-08-11 13:23:40 +02:00
Dmitry Popov
d46ed0c078 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-08-11 13:23:36 +02:00
CI
73c433fcd2 chore: release version v1.75.15 2025-08-11 11:23:28 +00:00
CI
02b5239220 chore: release version v1.75.14 2025-08-11 11:22:57 +00:00
CI
0ed3bdfcb0 chore: release version v1.75.13 2025-08-11 11:22:29 +00:00
CI
bdeb89011f chore: release version v1.75.12 2025-08-11 11:22:00 +00:00
CI
1523b625bc chore: release version v1.75.11 2025-08-11 11:21:27 +00:00
CI
fb91eeb692 chore: release version v1.75.10 2025-08-11 11:20:57 +00:00
CI
601d2e02cb chore: release version v1.75.9 2025-08-11 11:20:24 +00:00
Dmitry Popov
0a662d34eb Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-08-11 13:19:52 +02:00
Dmitry Popov
5cd4693e9d Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-08-11 13:19:49 +02:00
CI
f3f0f860e3 chore: release version v1.75.8 2025-08-11 11:04:18 +00:00
Dmitry Popov
93a5cf8a79 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-08-11 13:03:42 +02:00
Dmitry Popov
7cf15cbc21 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-08-11 13:03:39 +02:00
CI
30bc6d20b2 chore: release version v1.75.7 2025-08-11 10:55:27 +00:00
Dmitry Popov
b39f99fde4 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-08-11 12:55:02 +02:00
Dmitry Popov
0e8aa9efa4 chore: release version v1.75.5 2025-08-11 12:54:58 +02:00
CI
e1fcde36e3 chore: release version v1.75.6 2025-08-11 10:51:27 +00:00
Dmitry Popov
7aafe077d3 chore: release version v1.75.5 2025-08-11 12:50:59 +02:00
CI
5b8cab5e76 chore: release version v1.75.5 2025-08-11 10:48:40 +00:00
Dmitry Popov
4ab56af40a chore: release version v1.75.4 2025-08-11 12:48:09 +02:00
CI
e8cea86a76 chore: release version v1.75.4 2025-08-11 07:52:21 +00:00
Dmitry Popov
d0a6e0b358 Merge pull request #496 from guarzo/guarzo/secaudit 2025-08-11 11:51:26 +04:00
guarzo
8831b3e970 fix: restore security audit 2025-08-11 03:37:33 +00:00
CI
f6db6f0914 chore: release version v1.75.3
Some checks failed
Flaky Test Detection / 🔍 Detect Flaky Tests (push) Has been cancelled
Flaky Test Detection / 📊 Analyze Test History (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
🧪 Test Suite / Test Suite (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-08-10 22:17:25 +00:00
Dmitry Popov
ab8baeedd1 fix(core): Fixed character tracking issues 2025-08-11 00:16:52 +02:00
CI
eccee5e72e chore: release version v1.75.2 2025-08-10 16:00:40 +00:00
Dmitry Popov
4d93055bda Merge pull request #480 from wanderer-industries/default-settings
Default settings
2025-08-10 19:56:58 +04:00
Dmitry Popov
c60c16e56a chore: Fix issues with ash resources 2025-08-10 17:45:29 +02:00
Dmitry Popov
99b1de5647 chore: Fix issues with ash resources 2025-08-10 17:44:56 +02:00
Dmitry Popov
7efe11a421 chore: Fix issues with ash resources 2025-08-10 17:21:36 +02:00
Dmitry Popov
954108856a chore: Fix issues with ash resources 2025-08-10 16:55:45 +02:00
Dmitry Popov
cbca745ec4 Merge branch 'main' into default-settings 2025-08-10 16:19:59 +02:00
DanSylvest
e15e7c8f8d fix(Map): Fix indents for ally logos in list "On the map" 2025-08-10 12:51:15 +03:00
DanSylvest
65e8a520e5 fix(Map): Fix cancelling ping from system context menu 2025-08-10 12:00:05 +03:00
DanSylvest
3926af5a6d fix(Map): Hide admin settings tab 2025-08-10 10:02:29 +03:00
DanSylvest
556fb33223 fix(Map): Remote map setting refactoring 2025-08-10 09:57:50 +03:00
Dmitry Popov
82295adeab Merge pull request #477 from guarzo/guarzo/settings
feature: provide default settings interface
2025-07-31 12:21:06 +04:00
guarzo
9d7d4fad2e feature: provide default settings interface 2025-07-27 14:27:45 -04:00
Dmitry Popov
74f7ad155d Merge branch 'develop' into sig-temp-names 2025-07-18 13:52:07 +02:00
DanSylvest
f58ebad0ec fix(Map): Add Temp name field 2025-07-08 18:43:49 +03:00
Dmitry Popov
7ca4eb3b8f feat(Signatures): add support for signature temp names 2025-07-08 14:03:22 +02:00
Dmitry Popov
854524a03c feat(Core): added support for jumpgates connection type 2025-03-23 21:51:26 +01:00
349 changed files with 13233 additions and 147125 deletions

View File

@@ -13,4 +13,4 @@ export WANDERER_KILLS_BASE_URL="ws://host.docker.internal:4004"
export WANDERER_SSE_ENABLED="true"
export WANDERER_WEBHOOKS_ENABLED="true"
export WANDERER_SSE_MAX_CONNECTIONS="1000"
export WANDERER_WEBHOOK_TIMEOUT_MS="15000"
export WANDERER_WEBHOOK_TIMEOUT_MS="15000"

View File

@@ -5,7 +5,7 @@ on:
branches:
- main
- develop
- "releases/*"
env:
MIX_ENV: prod
GH_TOKEN: ${{ github.token }}
@@ -53,6 +53,7 @@ jobs:
- name: ⬇️ Checkout repo
uses: actions/checkout@v3
with:
ssh-key: "${{ secrets.COMMIT_KEY }}"
fetch-depth: 0
- name: 😅 Cache deps
id: cache-deps
@@ -95,9 +96,10 @@ jobs:
git config --global user.name 'CI'
git config --global user.email 'ci@users.noreply.github.com'
mix git_ops.release --force-patch --yes
git commit --allow-empty -m 'chore: [skip ci]'
git push --follow-tags
echo "commit_hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
- name: Set commit hash for develop
id: set-commit-develop
if: github.ref == 'refs/heads/develop'
@@ -106,11 +108,9 @@ jobs:
docker:
name: 🛠 Build Docker Images
if: github.ref == 'refs/heads/develop'
needs: build
runs-on: ubuntu-22.04
outputs:
release-tag: ${{ steps.get-latest-tag.outputs.tag }}
release-notes: ${{ steps.get-content.outputs.string }}
permissions:
checks: write
contents: write
@@ -137,19 +137,6 @@ jobs:
ref: ${{ needs.build.outputs.commit_hash }}
fetch-depth: 0
- name: Prepare Changelog
if: github.ref == 'refs/heads/main'
run: |
yes | cp -rf CHANGELOG.md priv/changelog/CHANGELOG.md
sed -i '1i%{title: "Change Log"}\n\n---\n' priv/changelog/CHANGELOG.md
- name: Get Release Tag
id: get-latest-tag
if: github.ref == 'refs/heads/main'
uses: "WyriHaximus/github-action-get-previous-tag@v1"
with:
fallback: 1.0.0
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
@@ -198,26 +185,6 @@ jobs:
if-no-files-found: error
retention-days: 1
- uses: markpatterson27/markdown-to-output@v1
id: extract-changelog
if: github.ref == 'refs/heads/main'
with:
filepath: CHANGELOG.md
- name: Get content
uses: 2428392/gh-truncate-string-action@v1.3.0
id: get-content
if: github.ref == 'refs/heads/main'
with:
stringToTruncate: |
📣 Wanderer new release available 🎉
**Version**: ${{ steps.get-latest-tag.outputs.tag }}
${{ steps.extract-changelog.outputs.body }}
maxLength: 500
truncationSymbol: "…"
merge:
runs-on: ubuntu-latest
needs:
@@ -248,9 +215,6 @@ jobs:
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}},enable=${{ github.ref == 'refs/heads/main' }}
type=semver,pattern={{major}}.{{minor}},enable=${{ github.ref == 'refs/heads/main' }}
type=semver,pattern={{version}},value=${{ needs.docker.outputs.release-tag }},enable=${{ github.ref == 'refs/heads/main' }}
type=raw,value=develop,enable=${{ github.ref == 'refs/heads/develop' }}
type=raw,value=develop-{{sha}},enable=${{ github.ref == 'refs/heads/develop' }}
@@ -267,19 +231,25 @@ jobs:
create-release:
name: 🏷 Create Release
runs-on: ubuntu-22.04
needs: [docker, merge]
if: ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }}
needs: build
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Get Release Tag
id: get-latest-tag
uses: "WyriHaximus/github-action-get-previous-tag@v1"
with:
fallback: 1.0.0
- name: 🏷 Create Draft Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ needs.docker.outputs.release-tag }}
name: Release ${{ needs.docker.outputs.release-tag }}
tag_name: ${{ steps.get-latest-tag.outputs.tag }}
name: Release ${{ steps.get-latest-tag.outputs.tag }}
body: |
## Info
Commit ${{ github.sha }} was deployed to `staging`. [See code diff](${{ github.event.compare }}).
@@ -289,10 +259,3 @@ jobs:
## How to Promote?
In order to promote this to prod, edit the draft and press **"Publish release"**.
draft: true
- name: Discord Webhook Action
uses: tsickert/discord-webhook@v5.3.0
if: github.ref == 'refs/heads/main'
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
content: ${{ needs.docker.outputs.release-notes }}

187
.github/workflows/docker-arm.yml vendored Normal file
View File

@@ -0,0 +1,187 @@
name: Build Docker ARM Image
on:
push:
tags:
- '**'
env:
MIX_ENV: prod
GH_TOKEN: ${{ github.token }}
REGISTRY_IMAGE: wandererltd/community-edition-arm
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: write
jobs:
docker:
name: 🛠 Build Docker Images
runs-on: ubuntu-22.04
outputs:
release-tag: ${{ steps.get-latest-tag.outputs.tag }}
release-notes: ${{ steps.get-content.outputs.string }}
permissions:
checks: write
contents: write
packages: write
attestations: write
id-token: write
pull-requests: write
repository-projects: write
strategy:
fail-fast: false
matrix:
platform:
- linux/arm64
steps:
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: ⬇️ Checkout repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Get Release Tag
id: get-latest-tag
uses: "WyriHaximus/github-action-get-previous-tag@v1"
with:
fallback: 1.0.0
- name: ⬇️ Checkout repo
uses: actions/checkout@v3
with:
ref: ${{ steps.get-latest-tag.outputs.tag }}
fetch-depth: 0
- name: Prepare Changelog
run: |
yes | cp -rf CHANGELOG.md priv/changelog/CHANGELOG.md
sed -i '1i%{title: "Change Log"}\n\n---\n' priv/changelog/CHANGELOG.md
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY_IMAGE }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.WANDERER_DOCKER_USER }}
password: ${{ secrets.WANDERER_DOCKER_PASSWORD }}
- name: Build and push
id: build
uses: docker/build-push-action@v6
with:
push: true
context: .
file: ./Dockerfile
cache-from: type=gha
cache-to: type=gha,mode=max
labels: ${{ steps.meta.outputs.labels }}
platforms: ${{ matrix.platform }}
outputs: type=image,"name=${{ env.REGISTRY_IMAGE }}",push-by-digest=true,name-canonical=true,push=true
build-args: |
MIX_ENV=prod
BUILD_METADATA=${{ steps.meta.outputs.json }}
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
- uses: markpatterson27/markdown-to-output@v1
id: extract-changelog
with:
filepath: CHANGELOG.md
- name: Get content
uses: 2428392/gh-truncate-string-action@v1.3.0
id: get-content
with:
stringToTruncate: |
📣 Wanderer **ARM** release available 🎉
**Version**: :${{ steps.get-latest-tag.outputs.tag }}
${{ steps.extract-changelog.outputs.body }}
maxLength: 500
truncationSymbol: "…"
merge:
runs-on: ubuntu-latest
needs:
- docker
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.WANDERER_DOCKER_USER }}
password: ${{ secrets.WANDERER_DOCKER_PASSWORD }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.REGISTRY_IMAGE }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{version}},value=${{ needs.docker.outputs.release-tag }}
- name: Create manifest list and push
working-directory: /tmp/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
- name: Inspect image
run: |
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }}
notify:
name: 🏷 Notify about release
runs-on: ubuntu-22.04
needs: [docker, merge]
steps:
- name: Discord Webhook Action
uses: tsickert/discord-webhook@v5.3.0
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
content: ${{ needs.docker.outputs.release-notes }}

187
.github/workflows/docker.yml vendored Normal file
View File

@@ -0,0 +1,187 @@
name: Build Docker Image
on:
push:
tags:
- '**'
env:
MIX_ENV: prod
GH_TOKEN: ${{ github.token }}
REGISTRY_IMAGE: wandererltd/community-edition
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: write
jobs:
docker:
name: 🛠 Build Docker Images
runs-on: ubuntu-22.04
outputs:
release-tag: ${{ steps.get-latest-tag.outputs.tag }}
release-notes: ${{ steps.get-content.outputs.string }}
permissions:
checks: write
contents: write
packages: write
attestations: write
id-token: write
pull-requests: write
repository-projects: write
strategy:
fail-fast: false
matrix:
platform:
- linux/amd64
steps:
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: ⬇️ Checkout repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Get Release Tag
id: get-latest-tag
uses: "WyriHaximus/github-action-get-previous-tag@v1"
with:
fallback: 1.0.0
- name: ⬇️ Checkout repo
uses: actions/checkout@v3
with:
ref: ${{ steps.get-latest-tag.outputs.tag }}
fetch-depth: 0
- name: Prepare Changelog
run: |
yes | cp -rf CHANGELOG.md priv/changelog/CHANGELOG.md
sed -i '1i%{title: "Change Log"}\n\n---\n' priv/changelog/CHANGELOG.md
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY_IMAGE }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.WANDERER_DOCKER_USER }}
password: ${{ secrets.WANDERER_DOCKER_PASSWORD }}
- name: Build and push
id: build
uses: docker/build-push-action@v6
with:
push: true
context: .
file: ./Dockerfile
cache-from: type=gha
cache-to: type=gha,mode=max
labels: ${{ steps.meta.outputs.labels }}
platforms: ${{ matrix.platform }}
outputs: type=image,"name=${{ env.REGISTRY_IMAGE }}",push-by-digest=true,name-canonical=true,push=true
build-args: |
MIX_ENV=prod
BUILD_METADATA=${{ steps.meta.outputs.json }}
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
- uses: markpatterson27/markdown-to-output@v1
id: extract-changelog
with:
filepath: CHANGELOG.md
- name: Get content
uses: 2428392/gh-truncate-string-action@v1.3.0
id: get-content
with:
stringToTruncate: |
📣 Wanderer new release available 🎉
**Version**: ${{ steps.get-latest-tag.outputs.tag }}
${{ steps.extract-changelog.outputs.body }}
maxLength: 500
truncationSymbol: "…"
merge:
runs-on: ubuntu-latest
needs:
- docker
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.WANDERER_DOCKER_USER }}
password: ${{ secrets.WANDERER_DOCKER_PASSWORD }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.REGISTRY_IMAGE }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{version}},value=${{ needs.docker.outputs.release-tag }}
- name: Create manifest list and push
working-directory: /tmp/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
- name: Inspect image
run: |
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }}
notify:
name: 🏷 Notify about release
runs-on: ubuntu-22.04
needs: [docker, merge]
steps:
- name: Discord Webhook Action
uses: tsickert/discord-webhook@v5.3.0
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
content: ${{ needs.docker.outputs.release-notes }}

File diff suppressed because it is too large Load Diff

View File

@@ -21,21 +21,17 @@ RUN mkdir config
# to ensure any relevant config change will trigger the dependencies
# to be re-compiled.
COPY config/config.exs config/${MIX_ENV}.exs config/
COPY priv priv
COPY lib lib
COPY assets assets
RUN mix compile
RUN mix assets.deploy
RUN mix compile
# Changes to config/runtime.exs don't require recompiling the code
COPY config/runtime.exs config/
COPY rel rel
RUN mix release
# start a new build stage so that the final image will only contain

View File

@@ -18,5 +18,28 @@ module.exports = {
'react/react-in-jsx-scope': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
"linebreak-style": "off",
"no-restricted-imports": [
"error",
{
"paths": [
{
"name": "primereact/button",
"importNames": ["Button"],
"message": "Use WdButton instead Button"
}
]
}
],
"react/forbid-elements": [
"error",
{
"forbid": [
{
"element": "Button",
"message": "Use WdButton instead Button"
}
]
}
]
},
};

View File

@@ -9,6 +9,7 @@ import { useMapperHandlers } from './useMapperHandlers';
import { MapRootContent } from '@/hooks/Mapper/components/mapRootContent/MapRootContent.tsx';
import { MapRootProvider } from '@/hooks/Mapper/mapRootProvider';
import './common-styles/main.scss';
import { ToastProvider } from '@/hooks/Mapper/ToastProvider.tsx';
const ErrorFallback = () => {
return <div className="!z-100 absolute w-screen h-screen bg-transparent"></div>;
@@ -39,13 +40,15 @@ export default function MapRoot({ hooks }) {
return (
<PrimeReactProvider>
<MapRootProvider fwdRef={providerRef} outCommand={handleCommand}>
<ErrorBoundary FallbackComponent={ErrorFallback} onError={logError}>
<ReactFlowProvider>
<MapRootContent />
</ReactFlowProvider>
</ErrorBoundary>
</MapRootProvider>
<ToastProvider>
<MapRootProvider fwdRef={providerRef} outCommand={handleCommand}>
<ErrorBoundary FallbackComponent={ErrorFallback} onError={logError}>
<ReactFlowProvider>
<MapRootContent />
</ReactFlowProvider>
</ErrorBoundary>
</MapRootProvider>
</ToastProvider>
</PrimeReactProvider>
);
}

View File

@@ -0,0 +1,31 @@
import React, { createContext, useContext, useRef } from 'react';
import { Toast } from 'primereact/toast';
import type { ToastMessage } from 'primereact/toast';
interface ToastContextValue {
toastRef: React.RefObject<Toast>;
show: (message: ToastMessage | ToastMessage[]) => void;
}
const ToastContext = createContext<ToastContextValue | null>(null);
export const ToastProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const toastRef = useRef<Toast>(null);
const show = (message: ToastMessage | ToastMessage[]) => {
toastRef.current?.show(message);
};
return (
<ToastContext.Provider value={{ toastRef, show }}>
<Toast ref={toastRef} position="top-right" />
{children}
</ToastContext.Provider>
);
};
export const useToast = (): ToastContextValue => {
const context = useContext(ToastContext);
if (!context) throw new Error('useToast must be used within a ToastProvider');
return context;
};

View File

@@ -284,3 +284,7 @@
border-left-color: #e67e22;
}
.p-dialog-header-icon.p-dialog-header-close.p-link {
position: relative;
left: 6px;
}

View File

@@ -1,18 +1,13 @@
// import './tailwind.css';
//@import 'primereact/resources/themes/bootstrap4-dark-blue/theme.css';
//@import 'primereact/resources/themes/lara-dark-purple/theme.css';
//@import "prime-fixes";
@import 'primereact/resources/primereact.min.css';
//@import 'primeflex/primeflex.css';
@import 'primeicons/primeicons.css';
//@import 'primereact/resources/primereact.css';
@use 'primereact/resources/primereact.min.css';
@use 'primeicons/primeicons.css';
@import "fixes";
@import "prime-fixes";
@import "custom-scrollbar";
@import "tooltip";
@import "context-menu";
@use "fixes";
@use "prime-fixes";
@use "custom-scrollbar";
@use "tooltip";
@use "context-menu";
.fixedImportant {

View File

@@ -1,7 +1,7 @@
.vertical-tabs-container {
display: flex;
width: 100%;
min-height: 300px;
min-height: 200px;
.p-tabview {
width: 100%;
@@ -68,6 +68,28 @@
}
}
&.color-warn {
@apply bg-yellow-600/5 border-r-yellow-600/20;
&:hover {
@apply bg-yellow-600/10 border-r-yellow-600/40;
}
&.p-tabview-selected {
@apply bg-yellow-600/10 border-r-yellow-600;
.p-tabview-nav-link {
@apply text-yellow-600;
}
&:hover {
@apply bg-yellow-600/10 border-r-yellow-600;
}
}
}
}
}

View File

@@ -1,6 +1,3 @@
@import "fix-dialog";
@import "fix-popup";
@import "fix-tabs";
//@import "fix-input";
//@import "theme";
@use "fix-dialog";
@use "fix-popup";
@use "fix-tabs";

View File

@@ -19,7 +19,7 @@ export interface ContextMenuSystemProps {
onSystemStatus(val: number): void;
onSystemLabels(val: string): void;
onCustomLabelDialog(): void;
onTogglePing(type: PingType, solar_system_id: string, hasPing: boolean): void;
onTogglePing(type: PingType, solar_system_id: string, ping_id: string | undefined, hasPing: boolean): void;
onWaypointSet: WaypointSetContextHandler;
}

View File

@@ -5,8 +5,7 @@ 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';
import { LayoutEventBlocker, WdButton } from '@/hooks/Mapper/components/ui-kit';
const AVAILABLE_TAGS = [
'A',
@@ -61,7 +60,7 @@ export const useTagMenu = (
<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
<WdButton
outlined={system?.tag !== x}
severity="warning"
key={x}
@@ -71,9 +70,9 @@ export const useTagMenu = (
onClick={() => system?.tag !== x && onSystemTag(x)}
>
{x}
</Button>
</WdButton>
))}
<Button
<WdButton
disabled={!isSelectedTag}
icon="pi pi-ban"
size="small"
@@ -81,7 +80,7 @@ export const useTagMenu = (
outlined
severity="help"
onClick={() => onSystemTag()}
></Button>
></WdButton>
</div>
</LayoutEventBlocker>
);

View File

@@ -109,7 +109,7 @@ export const useContextMenuSystemItems = ({
{ separator: true },
{
command: () => onTogglePing(PingType.Rally, systemId, hasPing),
command: () => onTogglePing(PingType.Rally, systemId, ping?.id, hasPing),
disabled: !isShowPingBtn,
template: () => {
const iconClasses = clsx({

View File

@@ -6,21 +6,28 @@ import { MenuItem } from 'primereact/menuitem';
export interface ContextMenuSystemMultipleProps {
contextMenuRef: RefObject<ContextMenu>;
onDeleteSystems(): void;
onCopySystems(): void;
}
export const ContextMenuSystemMultiple: React.FC<ContextMenuSystemMultipleProps> = ({
contextMenuRef,
onDeleteSystems,
onCopySystems,
}) => {
const items: MenuItem[] = useMemo(() => {
return [
{
label: 'Copy',
icon: PrimeIcons.COPY,
command: onCopySystems,
},
{
label: 'Delete',
icon: PrimeIcons.TRASH,
command: onDeleteSystems,
},
];
}, [onDeleteSystems]);
}, [onCopySystems, onDeleteSystems]);
return (
<>

View File

@@ -1,40 +1,81 @@
import { Node } from 'reactflow';
import { useCallback, useRef, useState } from 'react';
import { useCallback, useMemo, useRef, useState } from 'react';
import { ContextMenu } from 'primereact/contextmenu';
import { SolarSystemRawType } from '@/hooks/Mapper/types';
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts';
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { encodeJsonToUriBase64 } from '@/hooks/Mapper/utils';
import { useToast } from '@/hooks/Mapper/ToastProvider.tsx';
export const useContextMenuSystemMultipleHandlers = () => {
const {
data: { pings, connections },
} = useMapRootState();
const { show } = useToast();
const contextMenuRef = useRef<ContextMenu | null>(null);
const [systems, setSystems] = useState<Node<SolarSystemRawType>[]>();
const { deleteSystems } = useDeleteSystems();
const ping = useMemo(() => (pings.length === 1 ? pings[0] : undefined), [pings]);
const refVars = useRef({ systems, ping, connections, deleteSystems });
refVars.current = { systems, ping, connections, deleteSystems };
const handleSystemMultipleContext: NodeSelectionMouseHandler = (ev, systems_) => {
const handleSystemMultipleContext = useCallback<NodeSelectionMouseHandler>((ev, systems_) => {
setSystems(systems_);
ev.preventDefault();
ctxManager.next('ctxSysMult', contextMenuRef.current);
contextMenuRef.current?.show(ev);
};
}, []);
const onDeleteSystems = useCallback(() => {
const { systems, ping, deleteSystems } = refVars.current;
if (!systems) {
return;
}
const sysToDel = systems.filter(x => !x.data.locked).map(x => x.id);
const sysToDel = systems
.filter(x => !x.data.locked)
.filter(x => x.id !== ping?.solar_system_id)
.map(x => x.id);
if (sysToDel.length === 0) {
return;
}
deleteSystems(sysToDel);
}, [deleteSystems, systems]);
}, []);
const onCopySystems = useCallback(async () => {
const { systems, connections } = refVars.current;
if (!systems) {
return;
}
const connectionToCopy = connections.filter(
c => systems.filter(s => [c.target, c.source].includes(s.id)).length == 2,
);
await navigator.clipboard.writeText(
encodeJsonToUriBase64({ systems: systems.map(x => x.data), connections: connectionToCopy }),
);
show({
severity: 'success',
summary: 'Copied to clipboard',
detail: `Successfully copied to clipboard - [${systems.length}] systems and [${connectionToCopy.length}] connections`,
life: 3000,
});
}, [show]);
return {
handleSystemMultipleContext,
contextMenuRef,
onDeleteSystems,
onCopySystems,
};
};

View File

@@ -8,6 +8,4 @@ export type WaypointSetContextHandlerProps = {
destination: string;
};
export type WaypointSetContextHandler = (props: WaypointSetContextHandlerProps) => void;
export type NodeSelectionMouseHandler =
| ((event: React.MouseEvent<Element, MouseEvent>, nodes: Node[]) => void)
| undefined;
export type NodeSelectionMouseHandler = (event: React.MouseEvent<Element, MouseEvent>, nodes: Node[]) => void;

View File

@@ -1,6 +1,6 @@
import { MapUserSettings, SettingsWithVersion } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { MapUserSettings, SettingsWrapper } from '@/hooks/Mapper/mapRootProvider/types.ts';
const REQUIRED_KEYS = [
export const REQUIRED_KEYS = [
'widgets',
'interface',
'onTheMap',
@@ -19,11 +19,8 @@ export class MapUserSettingsParseError extends Error {
}
}
const isNumber = (v: unknown): v is number => typeof v === 'number' && !Number.isNaN(v);
/** Minimal check that an object matches SettingsWithVersion<*> */
const isSettingsWithVersion = (v: unknown): v is SettingsWithVersion<unknown> =>
typeof v === 'object' && v !== null && isNumber((v as any).version) && 'settings' in (v as any);
/** Minimal check that an object matches SettingsWrapper<*> */
const isSettings = (v: unknown): v is SettingsWrapper<unknown> => typeof v === 'object' && v !== null;
/** Ensure every required key is present */
const hasAllRequiredKeys = (v: unknown): v is Record<RequiredKeys, unknown> =>
@@ -52,8 +49,8 @@ export const parseMapUserSettings = (json: unknown): MapUserSettings => {
}
for (const key of REQUIRED_KEYS) {
if (!isSettingsWithVersion((data as any)[key])) {
throw new MapUserSettingsParseError(`"${key}" must match SettingsWithVersion<T>`);
if (!isSettings((data as any)[key])) {
throw new MapUserSettingsParseError(`"${key}" must match SettingsWrapper<T>`);
}
}

View File

@@ -1,3 +1,4 @@
export * from './useSystemInfo';
export * from './useGetOwnOnlineCharacters';
export * from './useElementWidth';
export * from './useDetectSettingsChanged';

View File

@@ -0,0 +1,23 @@
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useEffect, useState } from 'react';
export const useDetectSettingsChanged = () => {
const {
storedSettings: {
interfaceSettings,
settingsRoutes,
settingsLocal,
settingsSignatures,
settingsOnTheMap,
settingsKills,
},
} = useMapRootState();
const [counter, setCounter] = useState(0);
useEffect(
() => setCounter(x => x + 1),
[interfaceSettings, settingsRoutes, settingsLocal, settingsSignatures, settingsOnTheMap, settingsKills],
);
return counter;
};

View File

@@ -0,0 +1,26 @@
import { useMemo } from 'react';
import { CharacterTypeRaw } from '@/hooks/Mapper/types';
export type UseLocalCounterProps = {
charactersInSystem: Array<CharacterTypeRaw>;
userCharacters: string[];
};
export const getLocalCharacters = ({ charactersInSystem, userCharacters }: UseLocalCounterProps) => {
return charactersInSystem
.map(char => ({
...char,
compact: true,
isOwn: userCharacters.includes(char.eve_id),
}))
.sort((a, b) => a.name.localeCompare(b.name));
};
export const useLocalCounter = ({ charactersInSystem, userCharacters }: UseLocalCounterProps) => {
const localCounterCharacters = useMemo(
() => getLocalCharacters({ charactersInSystem, userCharacters }),
[charactersInSystem, userCharacters],
);
return { localCounterCharacters };
};

View File

@@ -1,4 +1,10 @@
import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect, useMemo } from 'react';
import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts';
import { PingData, SolarSystemConnection, SolarSystemRawType } from '@/hooks/Mapper/types';
import { MapHandlers, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
import type { PanelPosition } from '@reactflow/core';
import clsx from 'clsx';
import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect, useMemo, useRef } from 'react';
import ReactFlow, {
Background,
Edge,
@@ -16,8 +22,6 @@ import ReactFlow, {
import 'reactflow/dist/style.css';
import classes from './Map.module.scss';
import { MapProvider, useMapState } from './MapProvider';
import { useEdgesState, useMapHandlers, useNodesState, useUpdateNodes } from './hooks';
import { MapHandlers, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
import {
ContextMenuConnection,
ContextMenuRoot,
@@ -26,26 +30,11 @@ import {
useContextMenuRootHandlers,
} from './components';
import { getBehaviorForTheme } from './helpers/getThemeBehavior';
import { OnMapAddSystemCallback, OnMapSelectionChange } from './map.types';
import { SESSION_KEY } from '@/hooks/Mapper/constants.ts';
import { PingData, SolarSystemConnection, SolarSystemRawType } from '@/hooks/Mapper/types';
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts';
import clsx from 'clsx';
import { useEdgesState, useMapHandlers, useNodesState, useUpdateNodes } from './hooks';
import { useBackgroundVars } from './hooks/useBackgroundVars';
import type { PanelPosition } from '@reactflow/core';
const DEFAULT_VIEW_PORT = { zoom: 1, x: 0, y: 0 };
const getViewPortFromStore = () => {
const restored = localStorage.getItem(SESSION_KEY.viewPort);
if (!restored) {
return { ...DEFAULT_VIEW_PORT };
}
return JSON.parse(restored);
};
import { MapViewport, OnMapAddSystemCallback, OnMapSelectionChange } from './map.types';
import type { Viewport } from '@reactflow/core/dist/esm/types';
import { usePrevious } from 'primereact/hooks';
const initialNodes: Node<SolarSystemRawType>[] = [
// {
@@ -88,6 +77,7 @@ interface MapCompProps {
onConnectionInfoClick?(e: SolarSystemConnection): void;
onAddSystem?: OnMapAddSystemCallback;
onSelectionContextMenu?: NodeSelectionMouseHandler;
onChangeViewport?: (viewport: MapViewport) => void;
minimapClasses?: string;
isShowMinimap?: boolean;
onSystemContextMenu: (event: MouseEvent<Element>, systemId: string) => void;
@@ -99,6 +89,7 @@ interface MapCompProps {
pings: PingData[];
minimapPlacement?: PanelPosition;
localShowShipName?: boolean;
defaultViewport?: Viewport;
}
const MapComp = ({
@@ -119,19 +110,25 @@ const MapComp = ({
pings,
minimapPlacement = 'bottom-right',
localShowShipName = false,
onChangeViewport,
defaultViewport,
}: MapCompProps) => {
const { getNodes } = useReactFlow();
const { getNodes, setViewport } = useReactFlow();
const [nodes, , onNodesChange] = useNodesState<Node<SolarSystemRawType>>(initialNodes);
const [edges, , onEdgesChange] = useEdgesState<Edge<SolarSystemConnection>>(initialEdges);
useMapHandlers(refn, onSelectionChange);
useUpdateNodes(nodes);
const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers({ onAddSystem });
const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers({ onAddSystem, onCommand });
const { handleConnectionContext, ...connectionCtxProps } = useContextMenuConnectionHandlers();
const { update } = useMapState();
const { variant, gap, size, color } = useBackgroundVars(theme);
const { isPanAndDrag, nodeComponent, connectionMode } = getBehaviorForTheme(theme || 'default');
const refVars = useRef({ onChangeViewport });
refVars.current = { onChangeViewport };
const nodeTypes = useMemo(() => {
return {
custom: nodeComponent,
@@ -187,9 +184,10 @@ const MapComp = ({
[onSelectionChange],
);
const handleMoveEnd: OnMoveEnd = (_, viewport) => {
localStorage.setItem(SESSION_KEY.viewPort, JSON.stringify(viewport));
};
const handleMoveEnd: OnMoveEnd = useCallback((_, viewport) => {
// @ts-ignore
refVars.current.onChangeViewport?.(viewport);
}, []);
const handleNodesChange = useCallback(
(changes: NodeChange[]) => {
@@ -218,6 +216,19 @@ const MapComp = ({
}));
}, [showKSpaceBG, isThickConnections, pings, update, localShowShipName]);
const prevViewport = usePrevious(defaultViewport);
useEffect(() => {
if (defaultViewport == null) {
return;
}
if (prevViewport == null) {
return;
}
setViewport(defaultViewport);
}, [defaultViewport, prevViewport, setViewport]);
return (
<>
<div
@@ -232,7 +243,7 @@ const MapComp = ({
onConnect={onConnect}
// TODO we need save into session all of this
// and on any action do either
defaultViewport={getViewPortFromStore()}
defaultViewport={defaultViewport}
edgeTypes={edgeTypes}
nodeTypes={nodeTypes}
connectionMode={connectionMode}

View File

@@ -11,6 +11,7 @@ export type MapData = MapUnionTypes & {
isThickConnections: boolean;
linkedSigEveId: string;
localShowShipName: boolean;
systemHighlighted: string | undefined;
};
interface MapProviderProps {
@@ -44,6 +45,7 @@ const INITIAL_DATA: MapData = {
userHubs: [],
pings: [],
localShowShipName: false,
systemHighlighted: undefined,
};
export interface MapContextProps {

View File

@@ -1,4 +1,4 @@
@import '@/hooks/Mapper/components/map/styles/eve-common-variables';
@use '@/hooks/Mapper/components/map/styles/eve-common-variables';
.ConnectionTimeEOL {
background-image: linear-gradient(207deg, transparent, var(--conn-time-eol));
@@ -8,6 +8,10 @@
background-image: linear-gradient(207deg, transparent, var(--conn-frigate));
}
.ConnectionBridge {
background-image: linear-gradient(207deg, transparent, var(--conn-bridge));
}
.ConnectionSave {
background-image: linear-gradient(207deg, transparent, var(--conn-save));
}
@@ -15,3 +19,14 @@
.SelectedItem {
background-color: var(--selected-item-bg);
}
.FastActions {
:global {
.p-menuitem-content {
background-color: initial !important;
}
.p-menuitem-content:hover {
background-color: initial !important;
}
}
}

View File

@@ -1,10 +1,3 @@
import React, { RefObject, useMemo } from 'react';
import { ContextMenu } from 'primereact/contextmenu';
import { PrimeIcons } from 'primereact/api';
import { MenuItem } from 'primereact/menuitem';
import { ConnectionType, MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
import clsx from 'clsx';
import classes from './ContextMenuConnection.module.scss';
import {
MASS_STATE_NAMES,
MASS_STATE_NAMES_ORDER,
@@ -13,14 +6,25 @@ import {
SHIP_SIZES_NAMES_SHORT,
SHIP_SIZES_SIZE,
} from '@/hooks/Mapper/components/map/constants.ts';
import { ConnectionType, MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
import clsx from 'clsx';
import { PrimeIcons } from 'primereact/api';
import { ContextMenu } from 'primereact/contextmenu';
import { MenuItem } from 'primereact/menuitem';
import React, { RefObject, useMemo } from 'react';
import { Edge } from 'reactflow';
import { LifetimeActionsWrapper } from '@/hooks/Mapper/components/map/components/ContextMenuConnection/LifetimeActionsWrapper.tsx';
import classes from './ContextMenuConnection.module.scss';
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
import { isNullsecSpace } from '@/hooks/Mapper/components/map/helpers/isKnownSpace.ts';
export interface ContextMenuConnectionProps {
contextMenuRef: RefObject<ContextMenu>;
onDeleteConnection(): void;
onChangeTimeState(): void;
onChangeTimeState(lifetime: TimeStatus): void;
onChangeMassState(state: MassState): void;
onChangeShipSizeStatus(state: ShipSizeStatus): void;
onChangeType(type: ConnectionType): void;
onToggleMassSave(isLocked: boolean): void;
onHide(): void;
edge?: Edge<SolarSystemConnection>;
@@ -32,6 +36,7 @@ export const ContextMenuConnection: React.FC<ContextMenuConnectionProps> = ({
onChangeTimeState,
onChangeMassState,
onChangeShipSizeStatus,
onChangeType,
onToggleMassSave,
onHide,
edge,
@@ -41,88 +46,128 @@ export const ContextMenuConnection: React.FC<ContextMenuConnectionProps> = ({
return [];
}
const sourceInfo = getSystemStaticInfo(edge.data?.source);
const targetInfo = getSystemStaticInfo(edge.data?.target);
const bothNullsec =
sourceInfo && targetInfo && isNullsecSpace(sourceInfo.system_class) && isNullsecSpace(targetInfo.system_class);
const isFrigateSize = edge.data?.ship_size_type === ShipSizeStatus.small;
const isWormhole = edge.data?.type !== ConnectionType.gate;
if (edge.data?.type === ConnectionType.bridge) {
return [
{
label: `Set as Wormhole`,
icon: 'pi hero-arrow-uturn-left',
command: () => onChangeType(ConnectionType.wormhole),
},
{
label: 'Disconnect',
icon: PrimeIcons.TRASH,
command: onDeleteConnection,
},
];
}
if (edge.data?.type === ConnectionType.gate) {
return [
{
label: 'Disconnect',
icon: PrimeIcons.TRASH,
command: onDeleteConnection,
},
];
}
return [
...(isWormhole
{
className: clsx(classes.FastActions, '!h-[54px]'),
template: () => {
return <LifetimeActionsWrapper lifetime={edge.data?.time_status} onChangeLifetime={onChangeTimeState} />;
},
},
{
label: `Frigate`,
className: clsx({
[classes.ConnectionFrigate]: isFrigateSize,
}),
icon: PrimeIcons.CLOUD,
command: () =>
onChangeShipSizeStatus(
edge.data?.ship_size_type === ShipSizeStatus.small ? ShipSizeStatus.large : ShipSizeStatus.small,
),
},
{
label: `Save mass`,
className: clsx({
[classes.ConnectionSave]: edge.data?.locked,
}),
icon: PrimeIcons.LOCK,
command: () => onToggleMassSave(!edge.data?.locked),
},
...(!isFrigateSize
? [
{
label: `EOL`,
className: clsx({
[classes.ConnectionTimeEOL]: edge.data?.time_status === TimeStatus.eol,
}),
icon: PrimeIcons.CLOCK,
command: onChangeTimeState,
},
{
label: `Frigate`,
className: clsx({
[classes.ConnectionFrigate]: isFrigateSize,
}),
icon: PrimeIcons.CLOUD,
command: () =>
onChangeShipSizeStatus(
edge.data?.ship_size_type === ShipSizeStatus.small ? ShipSizeStatus.large : ShipSizeStatus.small,
),
},
{
label: `Save mass`,
className: clsx({
[classes.ConnectionSave]: edge.data?.locked,
}),
icon: PrimeIcons.LOCK,
command: () => onToggleMassSave(!edge.data?.locked),
},
...(!isFrigateSize
? [
{
label: `Mass status`,
icon: PrimeIcons.CHART_PIE,
items: MASS_STATE_NAMES_ORDER.map(x => ({
label: MASS_STATE_NAMES[x],
className: clsx({
[classes.SelectedItem]: edge.data?.mass_status === x,
}),
command: () => onChangeMassState(x),
})),
},
]
: []),
{
label: `Ship Size`,
icon: PrimeIcons.CLOUD,
items: SHIP_SIZES_NAMES_ORDER.map(x => ({
label: (
<div className="grid grid-cols-[20px_120px_1fr_40px] gap-2 items-center">
<div className="text-[12px] font-bold text-stone-400">{SHIP_SIZES_NAMES_SHORT[x]}</div>
<div>{SHIP_SIZES_NAMES[x]}</div>
<div></div>
<div className="flex justify-end whitespace-nowrap text-[12px] font-bold text-stone-500">
{SHIP_SIZES_SIZE[x]} t.
</div>
</div>
) as unknown as string, // TODO my lovely kostyl
label: `Mass status`,
icon: PrimeIcons.CHART_PIE,
items: MASS_STATE_NAMES_ORDER.map(x => ({
label: MASS_STATE_NAMES[x],
className: clsx({
[classes.SelectedItem]: edge.data?.ship_size_type === x,
[classes.SelectedItem]: edge.data?.mass_status === x,
}),
command: () => onChangeShipSizeStatus(x),
command: () => onChangeMassState(x),
})),
},
]
: []),
{
label: `Ship Size`,
icon: PrimeIcons.CLOUD,
items: SHIP_SIZES_NAMES_ORDER.map(x => ({
label: (
<div className="grid grid-cols-[20px_120px_1fr_40px] gap-2 items-center">
<div className="text-[12px] font-bold text-stone-400">{SHIP_SIZES_NAMES_SHORT[x]}</div>
<div>{SHIP_SIZES_NAMES[x]}</div>
<div></div>
<div className="flex justify-end whitespace-nowrap text-[12px] font-bold text-stone-500">
{SHIP_SIZES_SIZE[x]} t.
</div>
</div>
) as unknown as string, // TODO my lovely kostyl
className: clsx({
[classes.SelectedItem]: edge.data?.ship_size_type === x,
}),
command: () => onChangeShipSizeStatus(x),
})),
},
...(bothNullsec
? [
{
label: `Set as Bridge`,
icon: 'pi hero-forward',
command: () => onChangeType(ConnectionType.bridge),
},
]
: []),
{
label: 'Disconnect',
icon: PrimeIcons.TRASH,
command: onDeleteConnection,
},
];
}, [edge, onChangeTimeState, onDeleteConnection, onChangeShipSizeStatus, onToggleMassSave, onChangeMassState]);
}, [
edge,
onChangeTimeState,
onDeleteConnection,
onChangeType,
onChangeShipSizeStatus,
onToggleMassSave,
onChangeMassState,
]);
return (
<>
<ContextMenu model={items} ref={contextMenuRef} onHide={onHide} breakpoint="767px" />
<ContextMenu model={items} ref={contextMenuRef} onHide={onHide} breakpoint="767px" className="!w-[250px]" />
</>
);
};

View File

@@ -0,0 +1,12 @@
import { LayoutEventBlocker } from '@/hooks/Mapper/components/ui-kit';
import { WdLifetimeSelector, WdLifetimeSelectorProps } from '@/hooks/Mapper/components/ui-kit/WdLifetimeSelector.tsx';
export const LifetimeActionsWrapper = (props: WdLifetimeSelectorProps) => {
return (
<LayoutEventBlocker className="flex flex-col gap-1 w-[100%] h-full px-2 pt-[4px]">
<div className="text-[12px] text-stone-500 font-semibold">Life time:</div>
<WdLifetimeSelector {...props} />
</LayoutEventBlocker>
);
};

View File

@@ -30,7 +30,7 @@ export const useContextMenuConnectionHandlers = () => {
setEdge(undefined);
};
const onChangeTimeState = () => {
const onChangeTimeState = (lifetime: TimeStatus) => {
if (!edge || !edge.data) {
return;
}
@@ -40,7 +40,7 @@ export const useContextMenuConnectionHandlers = () => {
data: {
source: edge.source,
target: edge.target,
value: edge.data.time_status === TimeStatus.default ? TimeStatus.eol : TimeStatus.default,
value: lifetime,
},
});
setEdge(undefined);

View File

@@ -2,13 +2,21 @@ import React, { RefObject, useMemo } from 'react';
import { ContextMenu } from 'primereact/contextmenu';
import { PrimeIcons } from 'primereact/api';
import { MenuItem } from 'primereact/menuitem';
import { PasteSystemsAndConnections } from '@/hooks/Mapper/components/map/components';
export interface ContextMenuRootProps {
contextMenuRef: RefObject<ContextMenu>;
pasteSystemsAndConnections: PasteSystemsAndConnections | undefined;
onAddSystem(): void;
onPasteSystemsAnsConnections(): void;
}
export const ContextMenuRoot: React.FC<ContextMenuRootProps> = ({ contextMenuRef, onAddSystem }) => {
export const ContextMenuRoot: React.FC<ContextMenuRootProps> = ({
contextMenuRef,
onAddSystem,
onPasteSystemsAnsConnections,
pasteSystemsAndConnections,
}) => {
const items: MenuItem[] = useMemo(() => {
return [
{
@@ -16,8 +24,17 @@ export const ContextMenuRoot: React.FC<ContextMenuRootProps> = ({ contextMenuRef
icon: PrimeIcons.PLUS,
command: onAddSystem,
},
...(pasteSystemsAndConnections != null
? [
{
label: 'Paste',
icon: 'pi pi-clipboard',
command: onPasteSystemsAnsConnections,
},
]
: []),
];
}, [onAddSystem]);
}, [onAddSystem, onPasteSystemsAnsConnections, pasteSystemsAndConnections]);
return (
<>

View File

@@ -1,36 +1,76 @@
import { useReactFlow, XYPosition } from 'reactflow';
import React, { useCallback, useRef, useState } from 'react';
import { ContextMenu } from 'primereact/contextmenu';
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
import { OnMapAddSystemCallback } from '@/hooks/Mapper/components/map/map.types.ts';
import { recenterSystemsByBounds } from '@/hooks/Mapper/helpers/recenterSystems.ts';
import { OutCommand, OutCommandHandler, SolarSystemConnection, SolarSystemRawType } from '@/hooks/Mapper/types';
import { decodeUriBase64ToJson } from '@/hooks/Mapper/utils';
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
import { ContextMenu } from 'primereact/contextmenu';
import React, { useCallback, useRef, useState } from 'react';
import { useReactFlow, XYPosition } from 'reactflow';
export type PasteSystemsAndConnections = {
systems: SolarSystemRawType[];
connections: SolarSystemConnection[];
};
type UseContextMenuRootHandlers = {
onAddSystem?: OnMapAddSystemCallback;
onCommand?: OutCommandHandler;
};
export const useContextMenuRootHandlers = ({ onAddSystem }: UseContextMenuRootHandlers = {}) => {
export const useContextMenuRootHandlers = ({ onAddSystem, onCommand }: UseContextMenuRootHandlers = {}) => {
const rf = useReactFlow();
const contextMenuRef = useRef<ContextMenu | null>(null);
const [position, setPosition] = useState<XYPosition | null>(null);
const [pasteSystemsAndConnections, setPasteSystemsAndConnections] = useState<PasteSystemsAndConnections>();
const handleRootContext = (e: React.MouseEvent<HTMLDivElement>) => {
const handleRootContext = async (e: React.MouseEvent<HTMLDivElement>) => {
setPosition(rf.project({ x: e.clientX, y: e.clientY }));
e.preventDefault();
ctxManager.next('ctxRoot', contextMenuRef.current);
contextMenuRef.current?.show(e);
try {
const text = await navigator.clipboard.readText();
const result = decodeUriBase64ToJson(text);
setPasteSystemsAndConnections(result as PasteSystemsAndConnections);
} catch (err) {
setPasteSystemsAndConnections(undefined);
// do nothing
}
};
const ref = useRef({ onAddSystem, position });
ref.current = { onAddSystem, position };
const ref = useRef({ onAddSystem, position, pasteSystemsAndConnections, onCommand });
ref.current = { onAddSystem, position, pasteSystemsAndConnections, onCommand };
const onAddSystemCallback = useCallback(() => {
ref.current.onAddSystem?.({ coordinates: position });
}, [position]);
const onPasteSystemsAnsConnections = useCallback(async () => {
const { pasteSystemsAndConnections, onCommand, position } = ref.current;
if (!position || !onCommand || !pasteSystemsAndConnections) {
return;
}
const { systems } = recenterSystemsByBounds(pasteSystemsAndConnections.systems);
await onCommand({
type: OutCommand.manualPasteSystemsAndConnections,
data: {
systems: systems.map(({ position: srcPos, ...rest }) => ({
position: { x: Math.round(srcPos.x + position.x), y: Math.round(srcPos.y + position.y) },
...rest,
})),
connections: pasteSystemsAndConnections.connections,
},
});
}, []);
return {
handleRootContext,
pasteSystemsAndConnections,
contextMenuRef,
onAddSystem: onAddSystemCallback,
onPasteSystemsAnsConnections,
};
};

View File

@@ -56,7 +56,8 @@ export const KillsCounter = ({
className={className}
tooltipClassName="!px-0"
size={size}
interactive={true}
interactive
smallPaddings
>
{children}
</WdTooltipWrapper>

View File

@@ -5,8 +5,8 @@
}
.hoverTarget {
padding: 0.5rem;
margin: -0.5rem;
padding: 2px;
margin: -2px;
display: inline-block;
}

View File

@@ -13,9 +13,19 @@ interface LocalCounterProps {
localCounterCharacters: Array<CharItemProps>;
hasUserCharacters: boolean;
showIcon?: boolean;
disableInteractive?: boolean;
className?: string;
contentClassName?: string;
}
export const LocalCounter = ({ localCounterCharacters, hasUserCharacters, showIcon = true }: LocalCounterProps) => {
export const LocalCounter = ({
className,
contentClassName,
localCounterCharacters,
hasUserCharacters,
showIcon = true,
disableInteractive,
}: LocalCounterProps) => {
const {
data: { localShowShipName },
} = useMapState();
@@ -42,16 +52,30 @@ export const LocalCounter = ({ localCounterCharacters, hasUserCharacters, showIc
return (
<div
className={clsx(classes.TooltipActive, {
[classes.Pathfinder]: theme === AvailableThemes.pathfinder,
})}
className={clsx(
classes.TooltipActive,
{
[classes.Pathfinder]: theme === AvailableThemes.pathfinder,
},
className,
)}
>
<WdTooltipWrapper content={pilotTooltipContent} position={TooltipPosition.right} offset={0} interactive={true}>
<WdTooltipWrapper
content={pilotTooltipContent}
position={TooltipPosition.right}
offset={0}
interactive={!disableInteractive}
smallPaddings
>
<div className={clsx(classes.hoverTarget)}>
<div
className={clsx(classes.localCounter, {
[classes.hasUserCharacters]: hasUserCharacters,
})}
className={clsx(
classes.localCounter,
{
[classes.hasUserCharacters]: hasUserCharacters,
},
contentClassName,
)}
>
{showIcon && <i className="pi pi-users" />}
<span>{localCounterCharacters.length}</span>

View File

@@ -1,10 +1,20 @@
@import '@/hooks/Mapper/components/map/styles/eve-common-variables';
@use '@/hooks/Mapper/components/map/styles/eve-common-variables';
.EdgePathBack {
fill: none;
stroke: #80a5c5;
stroke-width: 3px;
&.time1 {
stroke: #f11ab2;
stroke-width: 4px;
}
&.time4 {
stroke: #a654e3;
stroke-width: 4px;
}
&.TimeCrit {
stroke: #f11ab2;
stroke-width: 4px;
@@ -29,6 +39,13 @@
&.Gate {
stroke: #9aff40;
}
&.Bridge {
stroke: #9aff40;
stroke-dasharray: 10 5;
stroke-linecap: round;
}
}
.EdgePathFront {

View File

@@ -9,6 +9,7 @@ import { PrimeIcons } from 'primereact/api';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
import { SHIP_SIZES_DESCRIPTION, SHIP_SIZES_NAMES_SHORT } from '@/hooks/Mapper/components/map/constants.ts';
import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
const MAP_TRANSLATES: Record<string, string> = {
[Position.Top]: 'translate(-48%, 0%)',
@@ -42,7 +43,9 @@ export const SHIP_SIZES_COLORS = {
export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }: EdgeProps<SolarSystemConnection>) => {
const sourceNode = useStore(useCallback(store => store.nodeInternals.get(source), [source]));
const targetNode = useStore(useCallback(store => store.nodeInternals.get(target), [target]));
const isWormhole = data?.type !== ConnectionType.gate;
const isWormhole = data?.type === ConnectionType.wormhole;
const isGate = data?.type === ConnectionType.gate;
const isBridge = data?.type === ConnectionType.bridge;
const {
data: { isThickConnections },
@@ -55,9 +58,7 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
const offset = isThickConnections ? MAP_OFFSETS_TICK[targetPos] : MAP_OFFSETS[targetPos];
const method = isWormhole ? getBezierPath : getBezierPath;
const [edgePath, labelX, labelY] = method({
const [edgePath, labelX, labelY] = getBezierPath({
sourceX: sx - offset.x,
sourceY: sy - offset.y,
sourcePosition: sourcePos,
@@ -67,7 +68,7 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
});
return [edgePath, labelX, labelY, sx, sy, tx, ty, sourcePos, targetPos];
}, [isThickConnections, sourceNode, targetNode, isWormhole]);
}, [isThickConnections, sourceNode, targetNode]);
if (!sourceNode || !targetNode || !data) {
return null;
@@ -79,9 +80,11 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
id={`back_${id}`}
className={clsx(classes.EdgePathBack, {
[classes.Tick]: isThickConnections,
[classes.TimeCrit]: isWormhole && data.time_status === TimeStatus.eol,
[classes.time1]: isWormhole && data.time_status === TimeStatus._1h,
[classes.time4]: isWormhole && data.time_status === TimeStatus._4h,
[classes.Hovered]: hovered,
[classes.Gate]: !isWormhole,
[classes.Gate]: isGate,
[classes.Bridge]: isBridge,
})}
d={path}
markerEnd={markerEnd}
@@ -95,7 +98,8 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
[classes.MassVerge]: isWormhole && data.mass_status === MassState.verge,
[classes.MassHalf]: isWormhole && data.mass_status === MassState.half,
[classes.Frigate]: isWormhole && data.ship_size_type === ShipSizeStatus.small,
[classes.Gate]: !isWormhole,
[classes.Gate]: isGate,
[classes.Bridge]: isBridge,
})}
d={path}
markerEnd={markerEnd}
@@ -147,6 +151,19 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
</WdTooltipWrapper>
)}
{isBridge && (
<WdTooltipWrapper
content="Ansiblex Jump Bridge"
position={TooltipPosition.top}
className={clsx(
classes.LinkLabel,
'pointer-events-auto bg-lime-300 rounded opacity-100 cursor-auto text-neutral-900',
)}
>
B
</WdTooltipWrapper>
)}
{isWormhole && data.ship_size_type !== ShipSizeStatus.large && (
<WdTooltipWrapper
content={SHIP_SIZES_DESCRIPTION[data.ship_size_type]}

View File

@@ -1,13 +1,6 @@
@import '@/hooks/Mapper/components/map/styles/eve-common-variables';
$pastel-blue: #5a7d9a;
$pastel-pink: rgb(30, 161, 255);
$dark-bg: #2d2d2d;
$text-color: #ffffff;
$tooltip-bg: #202020;
$neon-color-1: rgb(27, 132, 236);
$neon-color-3: rgba(27, 132, 236, 0.40);
@use "sass:color";
@use '@/hooks/Mapper/components/map/styles/eve-common-variables';
@import '@/hooks/Mapper/components/map/styles/solar-system-node';
@keyframes move-stripes {
from {
@@ -34,7 +27,7 @@ $neon-color-3: rgba(27, 132, 236, 0.40);
color: var(--rf-text-color, #ffffff);
box-shadow: 0 0 5px rgba($dark-bg, 0.5);
border: 1px solid darken($pastel-blue, 10%);
border: 1px solid color.adjust($pastel-blue, $lightness: -10%);
border-radius: 5px;
position: relative;
z-index: 3;

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, useNodeKillsCount, useSolarSystemNode } from '../../hooks';
import { useNodeKillsCount, useSolarSystemNode } from '../../hooks';
import {
EFFECT_BACKGROUND_STYLES,
MARKER_BOOKMARK_BG_STYLES,
@@ -17,10 +17,12 @@ import { TooltipPosition, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-
import { Tag } from 'primereact/tag';
import { LocalCounter } from '@/hooks/Mapper/components/map/components/LocalCounter';
import { KillsCounter } from '@/hooks/Mapper/components/map/components/KillsCounter';
import { useLocalCounter } from '@/hooks/Mapper/components/hooks/useLocalCounter.ts';
// let render = 0;
export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>) => {
const nodeVars = useSolarSystemNode(props);
const { localCounterCharacters } = useLocalCounter(nodeVars);
const { killsCount: localKillsCount, killsActivityType: localKillsActivityType } = useNodeKillsCount(
nodeVars.solarSystemId,
@@ -139,12 +141,26 @@ export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>
{nodeVars.isWormhole && !nodeVars.customName && <div />}
<div className="flex items-center gap-1 justify-end">
<div className={clsx('flex items-center gap-1')}>
<div className="flex items-center gap-0.5 justify-end">
<div className={clsx('flex items-center gap-0.5')}>
{nodeVars.locked && <i className={clsx(PrimeIcons.LOCK, classes.lockIcon)} />}
{nodeVars.hubs.includes(nodeVars.solarSystemId) && (
<i className={clsx(PrimeIcons.MAP_MARKER, classes.mapMarker)} />
)}
{nodeVars.description != null && nodeVars.description !== '' && (
<WdTooltipWrapper
className="h-[15px] transform -translate-y-[6%]"
position={TooltipPosition.top}
content={`System have description`}
>
<i
className={clsx(
'pi hero-chat-bubble-bottom-center-text w-[10px] h-[10px]',
'text-[8px] relative top-[1px]',
)}
/>
</WdTooltipWrapper>
)}
</div>
<LocalCounter
@@ -177,6 +193,17 @@ export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>
</>
)}
{nodeVars.systemHighlighted === nodeVars.solarSystemId && (
<div
className={clsx('absolute top-[-4px] left-[-4px]', 'w-[calc(100%+8px)] h-[calc(100%+8px)]', 'animate-pulse')}
>
<div className="absolute left-0 top-0 w-3 h-2 border-t-2 border-l-2 border-sky-300"></div>
<div className="absolute right-0 top-0 w-3 h-2 border-t-2 border-r-2 border-sky-300"></div>
<div className="absolute left-0 bottom-0 w-3 h-2 border-b-2 border-l-2 border-sky-300"></div>
<div className="absolute right-0 bottom-0 w-3 h-2 border-b-2 border-r-2 border-sky-300"></div>
</div>
)}
<div className={classes.Handlers}>
<Handle
type="source"

View File

@@ -1,4 +1,4 @@
@import './SolarSystemNodeDefault.module.scss';
@use './SolarSystemNodeDefault.module.scss';
/* ---------------------------------------------
Only override what's different from the base

View File

@@ -4,7 +4,7 @@ import { Handle, NodeProps, Position } from 'reactflow';
import clsx from 'clsx';
import classes from './SolarSystemNodeTheme.module.scss';
import { PrimeIcons } from 'primereact/api';
import { useLocalCounter, useNodeKillsCount, useSolarSystemNode } from '../../hooks';
import { useNodeKillsCount, useSolarSystemNode } from '../../hooks';
import {
EFFECT_BACKGROUND_STYLES,
MARKER_BOOKMARK_BG_STYLES,
@@ -16,12 +16,15 @@ import { TooltipPosition, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-
import { TooltipSize } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper/utils.ts';
import { LocalCounter } from '@/hooks/Mapper/components/map/components/LocalCounter';
import { KillsCounter } from '@/hooks/Mapper/components/map/components/KillsCounter';
import { useLocalCounter } from '@/hooks/Mapper/components/hooks/useLocalCounter.ts';
// let render = 0;
export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>) => {
const nodeVars = useSolarSystemNode(props);
const { localCounterCharacters } = useLocalCounter(nodeVars);
const { killsCount: localKillsCount, killsActivityType: localKillsActivityType } = useNodeKillsCount(nodeVars.solarSystemId);
const { killsCount: localKillsCount, killsActivityType: localKillsActivityType } = useNodeKillsCount(
nodeVars.solarSystemId,
);
// console.log('JOipP', `render ${nodeVars.id}`, render++);
@@ -170,6 +173,17 @@ export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>)
</>
)}
{nodeVars.systemHighlighted === nodeVars.solarSystemId && (
<div
className={clsx('absolute top-[-4px] left-[-4px]', 'w-[calc(100%+8px)] h-[calc(100%+8px)]', 'animate-pulse')}
>
<div className="absolute left-0 top-0 w-3 h-2 border-t-2 border-l-2 border-sky-300"></div>
<div className="absolute right-0 top-0 w-3 h-2 border-t-2 border-r-2 border-sky-300"></div>
<div className="absolute left-0 bottom-0 w-3 h-2 border-b-2 border-l-2 border-sky-300"></div>
<div className="absolute right-0 bottom-0 w-3 h-2 border-b-2 border-r-2 border-sky-300"></div>
</div>
)}
<div className={classes.Handlers}>
<Handle
type="source"

View File

@@ -1,4 +1,4 @@
@import '@/hooks/Mapper/components/map/styles/eve-common-variables';
@use '@/hooks/Mapper/components/map/styles/eve-common-variables';
.Signature {
position: relative;

View File

@@ -1,15 +1,16 @@
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
import { InfoDrawer } from '@/hooks/Mapper/components/ui-kit';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
import classes from './UnsplashedSignature.module.scss';
import { SystemSignature } from '@/hooks/Mapper/types/signatures';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { WORMHOLE_CLASS_STYLES, WORMHOLES_ADDITIONAL_INFO } from '@/hooks/Mapper/components/map/constants.ts';
import { useMemo } from 'react';
import clsx from 'clsx';
import { renderInfoColumn } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
import { K162_TYPES_MAP } from '@/hooks/Mapper/constants.ts';
import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo.ts';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { TimeStatus } from '@/hooks/Mapper/types';
import { SystemSignature } from '@/hooks/Mapper/types/signatures';
import clsx from 'clsx';
import { useMemo } from 'react';
import classes from './UnsplashedSignature.module.scss';
interface UnsplashedSignatureProps {
signature: SystemSignature;
@@ -35,7 +36,7 @@ export const UnsplashedSignature = ({ signature }: UnsplashedSignatureProps) =>
}, [customInfo]);
const isEOL = useMemo(() => {
return customInfo?.isEOL;
return customInfo?.time_status === TimeStatus._1h;
}, [customInfo]);
const whClassStyle = useMemo(() => {
@@ -58,6 +59,7 @@ export const UnsplashedSignature = ({ signature }: UnsplashedSignatureProps) =>
</InfoDrawer>
</div>
}
smallPaddings
>
<div className={clsx(classes.Box, whClassStyle)}>
<svg width="13" height="8" viewBox="0 0 13 8" xmlns="http://www.w3.org/2000/svg">

View File

@@ -716,11 +716,12 @@ export const STATUS_CLASSES: Record<number, string> = {
[STATUSES.dangerous]: 'eve-system-status-dangerous',
};
export const TYPE_NAMES_ORDER = [ConnectionType.wormhole, ConnectionType.gate];
export const TYPE_NAMES_ORDER = [ConnectionType.wormhole, ConnectionType.gate, ConnectionType.bridge];
export const TYPE_NAMES = {
[ConnectionType.wormhole]: 'Wormhole',
[ConnectionType.gate]: 'Gate',
[ConnectionType.bridge]: 'Jumpgate',
};
export const MASS_STATE_NAMES_ORDER = [MassState.verge, MassState.half, MassState.normal];

View File

@@ -15,3 +15,12 @@ export const isKnownSpace = (wormholeClassID: number) => {
export const isPossibleSpace = (spaces: number[], wormholeClassID: number) => {
return spaces.includes(wormholeClassID);
};
export const isNullsecSpace = (wormholeClassID: number) => {
switch (wormholeClassID) {
case SOLAR_SYSTEM_CLASS_IDS.ns:
return true;
}
return false;
};

View File

@@ -6,5 +6,5 @@ export * from './useCommandsCharacters';
export * from './useCommandsConnections';
export * from './useCommandsConnections';
export * from './useCenterSystem';
export * from './useSelectSystem';
export * from './useSelectSystems';
export * from './useMapCommands';

View File

@@ -1,12 +1,18 @@
import { useReactFlow } from 'reactflow';
import { useCallback, useRef } from 'react';
import { CommandCenterSystem } from '@/hooks/Mapper/types';
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
import { SYSTEM_FOCUSED_LIFETIME } from '@/hooks/Mapper/constants.ts';
export const useCenterSystem = () => {
const rf = useReactFlow();
const ref = useRef({ rf });
ref.current = { rf };
const { update } = useMapState();
const ref = useRef({ rf, update });
ref.current = { rf, update };
const highlightTimeout = useRef<number>();
return useCallback((systemId: CommandCenterSystem) => {
const systemNode = ref.current.rf.getNodes().find(x => x.data.id === systemId);
@@ -14,5 +20,16 @@ export const useCenterSystem = () => {
return;
}
ref.current.rf.setCenter(systemNode.position.x, systemNode.position.y, { duration: 1000 });
ref.current.update({ systemHighlighted: systemId });
if (highlightTimeout.current !== undefined) {
clearTimeout(highlightTimeout.current);
}
highlightTimeout.current = setTimeout(() => {
highlightTimeout.current = undefined;
ref.current.update({ systemHighlighted: undefined });
}, SYSTEM_FOCUSED_LIFETIME);
}, []);
};

View File

@@ -1,4 +1,6 @@
import { MapData, useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
import { useEventBuffer } from '@/hooks/Mapper/hooks';
import { SolarSystemConnection, SolarSystemRawType } from '@/hooks/Mapper/types';
import { CommandInit } from '@/hooks/Mapper/types/mapHandlers.ts';
import { useCallback, useRef } from 'react';
import { useReactFlow } from 'reactflow';
@@ -11,6 +13,20 @@ export const useMapInit = () => {
const ref = useRef({ rf, data, update });
ref.current = { update, data, rf };
const updateSystems = useCallback((systems: SolarSystemRawType[]) => {
const { rf } = ref.current;
rf.setNodes(systems.map(convertSystem2Node));
}, []);
const { handleEvent: handleUpdateSystems } = useEventBuffer<any>(updateSystems);
const updateEdges = useCallback((connections: SolarSystemConnection[]) => {
const { rf } = ref.current;
rf.setEdges(connections.map(convertConnection2Edge));
}, []);
const { handleEvent: handleUpdateConnections } = useEventBuffer<any>(updateEdges);
return useCallback(
({
systems,
@@ -24,7 +40,6 @@ export const useMapInit = () => {
hubs,
}: CommandInit) => {
const { update } = ref.current;
const { rf } = ref.current;
const updateData: Partial<MapData> = {};
@@ -63,11 +78,13 @@ export const useMapInit = () => {
update(updateData);
if (systems) {
rf.setNodes(systems.map(convertSystem2Node));
handleUpdateSystems(systems);
// rf.setNodes(systems.map(convertSystem2Node));
}
if (connections) {
rf.setEdges(connections.map(convertConnection2Edge));
handleUpdateConnections(connections);
// rf.setEdges(connections.map(convertConnection2Edge));
}
},
[],

View File

@@ -1,21 +0,0 @@
import { useReactFlow } from 'reactflow';
import { useCallback, useRef } from 'react';
import { CommandSelectSystem } from '@/hooks/Mapper/types';
export const useSelectSystem = () => {
const rf = useReactFlow();
const ref = useRef({ rf });
ref.current = { rf };
return useCallback((systemId: CommandSelectSystem) => {
ref.current.rf.setNodes(nds =>
nds.map(node => {
return {
...node,
selected: node.id === systemId,
};
}),
);
}, []);
};

View File

@@ -0,0 +1,31 @@
import { OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts';
import { CommandSelectSystems } from '@/hooks/Mapper/types';
import { useCallback, useRef } from 'react';
import { useReactFlow } from 'reactflow';
export const useSelectSystems = (onSelectionChange: OnMapSelectionChange) => {
const rf = useReactFlow();
const ref = useRef({ rf, onSelectionChange });
ref.current = { rf, onSelectionChange };
return useCallback(({ systems, delay }: CommandSelectSystems) => {
const run = () => {
ref.current.rf.setNodes(nds =>
nds.map(node => {
return {
...node,
selected: systems.includes(node.id),
};
}),
);
};
if (delay == null || delay === 0) {
run();
return;
}
setTimeout(run, delay);
}, []);
};

View File

@@ -1,4 +1,3 @@
import { ForwardedRef, useImperativeHandle, useRef } from 'react';
import {
CommandAddConnections,
CommandAddSystems,
@@ -14,12 +13,16 @@ import {
CommandRemoveSystems,
Commands,
CommandSelectSystem,
CommandSelectSystems,
CommandUpdateConnection,
CommandUpdateSystems,
MapHandlers,
} from '@/hooks/Mapper/types/mapHandlers.ts';
import { ForwardedRef, useImperativeHandle, useRef } from 'react';
import { OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts';
import {
useCenterSystem,
useCommandsCharacters,
useCommandsConnections,
useMapAddSystems,
@@ -27,10 +30,8 @@ import {
useMapInit,
useMapRemoveSystems,
useMapUpdateSystems,
useCenterSystem,
useSelectSystem,
useSelectSystems,
} from './api';
import { OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts';
export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange: OnMapSelectionChange) => {
const mapInit = useMapInit();
@@ -38,7 +39,7 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
const mapUpdateSystems = useMapUpdateSystems();
const removeSystems = useMapRemoveSystems(onSelectionChange);
const centerSystem = useCenterSystem();
const selectSystem = useSelectSystem();
const selectSystems = useSelectSystems(onSelectionChange);
const selectRef = useRef({ onSelectionChange });
selectRef.current = { onSelectionChange };
@@ -48,94 +49,87 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
const { charactersUpdated, presentCharacters, characterAdded, characterRemoved, characterUpdated } =
useCommandsCharacters();
useImperativeHandle(
ref,
() => {
return {
command(type, data) {
switch (type) {
case Commands.init:
mapInit(data as CommandInit);
break;
case Commands.addSystems:
setTimeout(() => mapAddSystems(data as CommandAddSystems), 100);
break;
case Commands.updateSystems:
mapUpdateSystems(data as CommandUpdateSystems);
break;
case Commands.removeSystems:
setTimeout(() => removeSystems(data as CommandRemoveSystems), 100);
break;
case Commands.addConnections:
setTimeout(() => addConnections(data as CommandAddConnections), 100);
break;
case Commands.removeConnections:
setTimeout(() => removeConnections(data as CommandRemoveConnections), 100);
break;
case Commands.charactersUpdated:
charactersUpdated(data as CommandCharactersUpdated);
break;
case Commands.characterAdded:
characterAdded(data as CommandCharacterAdded);
break;
case Commands.characterRemoved:
characterRemoved(data as CommandCharacterRemoved);
break;
case Commands.characterUpdated:
characterUpdated(data as CommandCharacterUpdated);
break;
case Commands.presentCharacters:
presentCharacters(data as CommandPresentCharacters);
break;
case Commands.updateConnection:
updateConnection(data as CommandUpdateConnection);
break;
case Commands.mapUpdated:
mapUpdated(data as CommandMapUpdated);
break;
case Commands.killsUpdated:
killsUpdated(data as CommandKillsUpdated);
break;
useImperativeHandle(ref, () => {
return {
command(type, data) {
switch (type) {
case Commands.init:
mapInit(data as CommandInit);
break;
case Commands.addSystems:
setTimeout(() => mapAddSystems(data as CommandAddSystems), 100);
break;
case Commands.updateSystems:
mapUpdateSystems(data as CommandUpdateSystems);
break;
case Commands.removeSystems:
setTimeout(() => removeSystems(data as CommandRemoveSystems), 100);
break;
case Commands.addConnections:
setTimeout(() => addConnections(data as CommandAddConnections), 100);
break;
case Commands.removeConnections:
setTimeout(() => removeConnections(data as CommandRemoveConnections), 100);
break;
case Commands.charactersUpdated:
charactersUpdated(data as CommandCharactersUpdated);
break;
case Commands.characterAdded:
characterAdded(data as CommandCharacterAdded);
break;
case Commands.characterRemoved:
characterRemoved(data as CommandCharacterRemoved);
break;
case Commands.characterUpdated:
characterUpdated(data as CommandCharacterUpdated);
break;
case Commands.presentCharacters:
presentCharacters(data as CommandPresentCharacters);
break;
case Commands.updateConnection:
updateConnection(data as CommandUpdateConnection);
break;
case Commands.mapUpdated:
mapUpdated(data as CommandMapUpdated);
break;
case Commands.killsUpdated:
killsUpdated(data as CommandKillsUpdated);
break;
case Commands.centerSystem:
setTimeout(() => {
const systemId = `${data}`;
centerSystem(systemId as CommandSelectSystem);
}, 100);
break;
case Commands.centerSystem:
setTimeout(() => {
const systemId = `${data}`;
centerSystem(systemId as CommandSelectSystem);
}, 100);
break;
case Commands.selectSystem:
setTimeout(() => {
const systemId = `${data}`;
selectRef.current.onSelectionChange({
systems: [systemId],
connections: [],
});
selectSystem(systemId as CommandSelectSystem);
}, 500);
break;
case Commands.selectSystem:
selectSystems({ systems: [data as string], delay: 500 });
break;
case Commands.pingAdded:
case Commands.pingCancelled:
case Commands.routes:
case Commands.signaturesUpdated:
case Commands.linkSignatureToSystem:
case Commands.detailedKillsUpdated:
case Commands.characterActivityData:
case Commands.trackingCharactersData:
case Commands.updateActivity:
case Commands.updateTracking:
case Commands.userSettingsUpdated:
// do nothing
break;
case Commands.selectSystems:
selectSystems(data as CommandSelectSystems);
break;
default:
console.warn(`Map handlers: Unknown command: ${type}`, data);
break;
}
},
};
},
[],
);
case Commands.pingAdded:
case Commands.pingCancelled:
case Commands.routes:
case Commands.signaturesUpdated:
case Commands.linkSignatureToSystem:
case Commands.detailedKillsUpdated:
case Commands.characterActivityData:
case Commands.trackingCharactersData:
case Commands.updateActivity:
case Commands.updateTracking:
case Commands.userSettingsUpdated:
// do nothing
break;
default:
console.warn(`Map handlers: Unknown command: ${type}`, data);
break;
}
},
};
}, []);
};

View File

@@ -5,7 +5,7 @@ import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useMapGetOption } from '@/hooks/Mapper/mapRootProvider/hooks/api';
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider';
import { useDoubleClick } from '@/hooks/Mapper/hooks/useDoubleClick';
import { Regions, REGIONS_MAP, Spaces } from '@/hooks/Mapper/constants';
import { Regions, REGIONS_MAP, SPACE_TO_CLASS } from '@/hooks/Mapper/constants';
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace';
import { getSystemClassStyles } from '@/hooks/Mapper/components/map/helpers';
import { sortWHClasses } from '@/hooks/Mapper/helpers';
@@ -50,27 +50,9 @@ export interface SolarSystemNodeVars {
isRally: boolean;
classTitle: string | null;
temporaryName?: string | null;
}
const SpaceToClass: Record<string, string> = {
[Spaces.Caldari]: 'Caldaria',
[Spaces.Matar]: 'Mataria',
[Spaces.Amarr]: 'Amarria',
[Spaces.Gallente]: 'Gallente',
[Spaces.Pochven]: 'Pochven',
};
export function useLocalCounter(nodeVars: SolarSystemNodeVars) {
const localCounterCharacters = useMemo(() => {
return nodeVars.charactersInSystem
.map(char => ({
...char,
compact: true,
isOwn: nodeVars.userCharacters.includes(char.eve_id),
}))
.sort((a, b) => a.name.localeCompare(b.name));
}, [nodeVars.charactersInSystem, nodeVars.userCharacters]);
return { localCounterCharacters };
description: string | null;
comments_count: number | null;
systemHighlighted: string | undefined;
}
export const useSolarSystemNode = (props: NodeProps<MapSolarSystemType>): SolarSystemNodeVars => {
@@ -84,6 +66,8 @@ export const useSolarSystemNode = (props: NodeProps<MapSolarSystemType>): SolarS
labels,
temporary_name,
linked_sig_eve_id: linkedSigEveId = '',
description,
comments_count,
} = data;
const {
@@ -125,6 +109,7 @@ export const useSolarSystemNode = (props: NodeProps<MapSolarSystemType>): SolarS
showKSpaceBG,
isThickConnections,
pings,
systemHighlighted,
},
outCommand,
} = useMapState();
@@ -169,7 +154,7 @@ export const useSolarSystemNode = (props: NodeProps<MapSolarSystemType>): SolarS
const showHandlers = isConnecting || hoverNodeId === id;
const space = showKSpaceBG ? REGIONS_MAP[region_id] : '';
const regionClass = showKSpaceBG ? SpaceToClass[space] || null : null;
const regionClass = showKSpaceBG ? SPACE_TO_CLASS[space] || null : null;
const { systemName, computedTemporaryName, customName } = useSystemName({
isTempSystemNameEnabled,
@@ -232,6 +217,9 @@ export const useSolarSystemNode = (props: NodeProps<MapSolarSystemType>): SolarS
regionName,
solarSystemName: solar_system_name,
isRally,
description,
comments_count,
systemHighlighted,
};
return nodeVars;

View File

@@ -1,7 +1,7 @@
import { useCallback, useEffect, useRef } from 'react';
import { Node, useOnViewportChange, useReactFlow } from 'reactflow';
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
import { SolarSystemRawType } from '@/hooks/Mapper/types';
import { useCallback, useEffect, useRef } from 'react';
import { Node, useOnViewportChange, useReactFlow } from 'reactflow';
const useThrottle = () => {
const throttleSeed = useRef<number | null>(null);

View File

@@ -10,3 +10,5 @@ export type OnMapSelectionChange = (event: {
}) => void;
export type OnMapAddSystemCallback = (props: { coordinates: XYPosition | null }) => void;
export type MapViewport = { zoom: 1; x: 0; y: 0 };

View File

@@ -1,5 +1,5 @@
@import './eve-common-variables';
@import './eve-common';
@use './eve-common-variables';
@use './eve-common';
.default-theme {
--rf-bg-color: #0C0A09;

View File

@@ -1,18 +1,19 @@
@use "sass:color";
$friendlyBase: #3bbd39;
$friendlyAlpha: #3bbd3952;
$friendlyDark20: darken($friendlyBase, 20%);
$friendlyDark30: darken($friendlyBase, 30%);
$friendlyDark5: darken($friendlyBase, 5%);
$friendlyDark20: color.adjust($friendlyBase, $lightness: -20%);
$friendlyDark30: color.adjust($friendlyBase, $lightness: -30%);
$friendlyDark5: color.adjust($friendlyBase, $lightness: -5%);
$lookingForBase: #43c2fd;
$lookingForAlpha: rgba(67, 176, 253, 0.48);
$lookingForDark15: darken($lookingForBase, 15%);
$lookingForDark15: color.adjust($lookingForBase, $lightness: -15%);
$homeBase: rgb(179, 253, 67);
$homeAlpha: rgba(186, 248, 48, 0.32);
$homeBackground: #a0fa5636;
$homeDark30: darken($homeBase, 30%);
$homeDark30: color.adjust($homeBase, $lightness: -30%);
:root {
--pastel-blue: #5a7d9a;
@@ -117,6 +118,7 @@ $homeDark30: darken($homeBase, 30%);
--conn-time-eol: #7452c3e3;
--conn-frigate: #325d88;
--conn-bridge: rgba(135, 185, 93, 0.85);
--conn-save: rgba(155, 102, 45, 0.85);
--selected-item-bg: rgba(98, 98, 98, 0.33);
}

View File

@@ -1,4 +1,4 @@
@import './eve-common-variables';
@use './eve-common-variables';
.eve-wh-effect-color-pulsar {

View File

@@ -1,2 +1,2 @@
@import './default-theme.scss';
@import './pathfinder-theme.scss';
@use './default-theme.scss';
@use './pathfinder-theme.scss';

View File

@@ -1,10 +1,11 @@
@import './eve-common-variables';
@import './eve-common';
@use "sass:color";
@use './eve-common-variables';
@use './eve-common';
@import url('https://fonts.googleapis.com/css2?family=Oxygen:wght@300;400;700&display=swap');
$homeBase: rgb(197, 253, 67);
$homeAlpha: rgba(197, 253, 67, 0.32);
$homeDark30: darken($homeBase, 30%);
$homeDark30: color.adjust($homeBase, $lightness: -30%);
.pathfinder-theme {
/* -- Override values from the default theme -- */

View File

@@ -0,0 +1,8 @@
$pastel-blue: #5a7d9a;
$pastel-pink: rgb(30, 161, 255);
$dark-bg: #2d2d2d;
$text-color: #ffffff;
$tooltip-bg: #202020;
$neon-color-1: rgb(27, 132, 236);
$neon-color-3: rgba(27, 132, 236, 0.40);

View File

@@ -1,16 +1,15 @@
import { Dialog } from 'primereact/dialog';
import { SystemViewStandalone, WdButton, WHClassView, WHEffectView } from '@/hooks/Mapper/components/ui-kit';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback, useRef, useState } from 'react';
import { Button } from 'primereact/button';
import { IconField } from 'primereact/iconfield';
import { AutoComplete } from 'primereact/autocomplete';
import { OutCommand, SearchSystemItem } from '@/hooks/Mapper/types';
import { SystemViewStandalone, WHClassView, WHEffectView } from '@/hooks/Mapper/components/ui-kit';
import { AutoComplete } from 'primereact/autocomplete';
import { Dialog } from 'primereact/dialog';
import { IconField } from 'primereact/iconfield';
import { useCallback, useRef, useState } from 'react';
import classes from './AddSystemDialog.module.scss';
import clsx from 'clsx';
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
import { sortWHClasses } from '@/hooks/Mapper/helpers';
import clsx from 'clsx';
export type SearchOnSubmitCallback = (item: SearchSystemItem) => void;
@@ -34,6 +33,7 @@ export const AddSystemDialog = ({
data: { wormholesData },
} = useMapRootState();
// TODO fix it
const inputRef = useRef<any>();
const onShow = useCallback(() => {
inputRef.current?.focus();
@@ -62,6 +62,7 @@ export const AddSystemDialog = ({
},
});
// TODO fix it
let prepared = (result.systems as SearchSystemItem[]).sort((a, b) => {
const amatch = a.label.indexOf(query);
const bmatch = b.label.indexOf(query);
@@ -114,90 +115,93 @@ export const AddSystemDialog = ({
setVisible(false);
}}
>
<div className="flex flex-col gap-3 px-1.5">
<div className="flex flex-col gap-2 py-3.5">
<div className="flex flex-col gap-1">
<IconField>
<AutoComplete
ref={inputRef}
multiple
showEmptyMessage
scrollHeight="300px"
value={selectedItem}
suggestions={filteredItems}
completeMethod={searchItems}
onChange={e => {
setSelectedItem(e.value.length < 2 ? e.value : [e.value[e.value.length - 1]]);
}}
emptyMessage="Not found any system..."
placeholder="Type here..."
field="label"
id="value"
className="w-full"
itemTemplate={(item: SearchSystemItem) => {
const { security, system_class, effect_power, effect_name, statics } = item.system_static_info;
const sortedStatics = sortWHClasses(wormholesData, statics);
const isWH = isWormholeSpace(system_class);
<form onSubmit={handleSubmit}>
<div className="flex flex-col gap-3 px-1.5">
<div className="flex flex-col gap-2 py-3.5">
<div className="flex flex-col gap-1">
<IconField>
<AutoComplete
ref={inputRef}
multiple
showEmptyMessage
scrollHeight="300px"
value={selectedItem}
suggestions={filteredItems}
completeMethod={searchItems}
onChange={e => {
setSelectedItem(e.value.length < 2 ? e.value : [e.value[e.value.length - 1]]);
}}
emptyMessage="Not found any system..."
placeholder="Type here..."
field="label"
id="value"
className="w-full"
itemTemplate={(item: SearchSystemItem) => {
const { security, system_class, effect_power, effect_name, statics } = item.system_static_info;
const sortedStatics = sortWHClasses(wormholesData, statics);
const isWH = isWormholeSpace(system_class);
return (
<div className={clsx('flex gap-1.5', classes.SearchItem)}>
<SystemViewStandalone
security={security}
system_class={system_class}
solar_system_id={item.value}
class_title={item.class_title}
solar_system_name={item.label}
region_name={item.region_name}
/>
{effect_name && isWH && (
<WHEffectView
effectName={effect_name}
effectPower={effect_power}
className={classes.SearchItemEffect}
return (
<div className={clsx('flex gap-1.5', classes.SearchItem)}>
<SystemViewStandalone
security={security}
system_class={system_class}
solar_system_id={item.value}
class_title={item.class_title}
solar_system_name={item.label}
region_name={item.region_name}
/>
)}
{isWH && (
<div className="flex gap-1 grow justify-between">
<div></div>
<div className="flex gap-1">
{sortedStatics.map(x => (
<WHClassView key={x} whClassName={x} />
))}
{effect_name && isWH && (
<WHEffectView
effectName={effect_name}
effectPower={effect_power}
className={classes.SearchItemEffect}
/>
)}
{isWH && (
<div className="flex gap-1 grow justify-between">
<div></div>
<div className="flex gap-1">
{sortedStatics.map(x => (
<WHClassView key={x} whClassName={x} />
))}
</div>
</div>
</div>
)}
</div>
);
}}
selectedItemTemplate={(item: SearchSystemItem) => (
<SystemViewStandalone
security={item.system_static_info.security}
system_class={item.system_static_info.system_class}
solar_system_id={item.value}
class_title={item.class_title}
solar_system_name={item.label}
region_name={item.region_name}
/>
)}
/>
</IconField>
)}
</div>
);
}}
selectedItemTemplate={(item: SearchSystemItem) => (
<SystemViewStandalone
security={item.system_static_info.security}
system_class={item.system_static_info.system_class}
solar_system_id={item.value}
class_title={item.class_title}
solar_system_name={item.label}
region_name={item.region_name}
/>
)}
/>
</IconField>
<span className="text-[12px] text-stone-400 ml-1">*to search type at least 2 symbols.</span>
<span className="text-[12px] text-stone-400 ml-1">*to search type at least 2 symbols.</span>
</div>
</div>
<div className="flex gap-2 justify-end">
<WdButton
type="submit"
onClick={handleSubmit}
outlined
disabled={!selectedItem || selectedItem.length !== 1}
size="small"
label="Submit"
/>
</div>
</div>
<div className="flex gap-2 justify-end">
<Button
onClick={handleSubmit}
outlined
disabled={!selectedItem || selectedItem.length !== 1}
size="small"
label="Submit"
/>
</div>
</div>
</form>
</Dialog>
);
};

View File

@@ -14,6 +14,7 @@ import { PrimeIcons } from 'primereact/api';
import { ConfirmPopup } from 'primereact/confirmpopup';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { OutCommand } from '@/hooks/Mapper/types';
import { useConfirmPopup } from '@/hooks/Mapper/hooks';
const TOOLTIP_PROPS = { content: 'Remove comment', position: TooltipPosition.top };
@@ -28,8 +29,7 @@ export const MarkdownComment = ({ text, time, characterEveId, id }: MarkdownComm
const char = useGetCacheCharacter(characterEveId);
const [hovered, setHovered] = useState(false);
const cpRemoveBtnRef = useRef<HTMLElement>();
const [cpRemoveVisible, setCpRemoveVisible] = useState(false);
const { cfShow, cfHide, cfVisible, cfRef } = useConfirmPopup();
const { outCommand } = useMapRootState();
const ref = useRef({ outCommand, id });
@@ -45,9 +45,6 @@ export const MarkdownComment = ({ text, time, characterEveId, id }: MarkdownComm
const handleMouseEnter = useCallback(() => setHovered(true), []);
const handleMouseLeave = useCallback(() => setHovered(false), []);
const handleShowCP = useCallback(() => setCpRemoveVisible(true), []);
const handleHideCP = useCallback(() => setCpRemoveVisible(false), []);
return (
<>
<InfoDrawer
@@ -68,11 +65,11 @@ export const MarkdownComment = ({ text, time, characterEveId, id }: MarkdownComm
{!hovered && <TimeAgo timestamp={time} />}
{hovered && (
// @ts-ignore
<div ref={cpRemoveBtnRef}>
<div ref={cfRef}>
<WdImgButton
className={clsx(PrimeIcons.TRASH, 'hover:text-red-400')}
tooltip={TOOLTIP_PROPS}
onClick={handleShowCP}
onClick={cfShow}
/>
</div>
)}
@@ -85,9 +82,9 @@ export const MarkdownComment = ({ text, time, characterEveId, id }: MarkdownComm
</InfoDrawer>
<ConfirmPopup
target={cpRemoveBtnRef.current}
visible={cpRemoveVisible}
onHide={handleHideCP}
target={cfRef.current}
visible={cfVisible}
onHide={cfHide}
message="Are you sure you want to delete?"
icon="pi pi-exclamation-triangle"
accept={handleDelete}

View File

@@ -4,6 +4,7 @@ import {
SystemView,
TimeAgo,
TooltipPosition,
WdButton,
WdImgButton,
WdImgButtonTooltip,
} from '@/hooks/Mapper/components/ui-kit';
@@ -13,11 +14,11 @@ import { PingsPlacement } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { Commands, OutCommand, PingType } from '@/hooks/Mapper/types';
import clsx from 'clsx';
import { PrimeIcons } from 'primereact/api';
import { Button } from 'primereact/button';
import { ConfirmPopup } from 'primereact/confirmpopup';
import { Toast } from 'primereact/toast';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useCallback, useEffect, useMemo, useRef } from 'react';
import useRefState from 'react-usestateref';
import { useConfirmPopup } from '@/hooks/Mapper/hooks';
const PING_PLACEMENT_MAP = {
[PingsPlacement.rightTop]: 'top-right',
@@ -78,9 +79,7 @@ export interface PingsInterfaceProps {
export const PingsInterface = ({ hasLeftOffset }: PingsInterfaceProps) => {
const toast = useRef<Toast>(null);
const [isShow, setIsShow, isShowRef] = useRefState(false);
const cpRemoveBtnRef = useRef<HTMLElement>();
const [cpRemoveVisible, setCpRemoveVisible] = useState(false);
const { cfShow, cfHide, cfVisible, cfRef } = useConfirmPopup();
const {
storedSettings: { interfaceSettings },
@@ -98,9 +97,6 @@ export const PingsInterface = ({ hasLeftOffset }: PingsInterfaceProps) => {
const ping = useMemo(() => (pings.length === 1 ? pings[0] : null), [pings]);
const handleShowCP = useCallback(() => setCpRemoveVisible(true), []);
const handleHideCP = useCallback(() => setCpRemoveVisible(false), []);
const navigateTo = useCallback(() => {
if (!ping) {
return;
@@ -242,11 +238,11 @@ export const PingsInterface = ({ hasLeftOffset }: PingsInterfaceProps) => {
/>
{/*@ts-ignore*/}
<div ref={cpRemoveBtnRef}>
<div ref={cfRef}>
<WdImgButton
className={clsx('pi-trash', 'text-red-400 hover:text-red-300')}
tooltip={DELETE_TOOLTIP_PROPS}
onClick={handleShowCP}
onClick={cfShow}
/>
</div>
{/* TODO ADD solar system menu*/}
@@ -260,7 +256,7 @@ export const PingsInterface = ({ hasLeftOffset }: PingsInterfaceProps) => {
)}
></Toast>
<Button
<WdButton
icon="pi pi-bell"
severity="warning"
aria-label="Notification"
@@ -272,9 +268,9 @@ export const PingsInterface = ({ hasLeftOffset }: PingsInterfaceProps) => {
/>
<ConfirmPopup
target={cpRemoveBtnRef.current}
visible={cpRemoveVisible}
onHide={handleHideCP}
target={cfRef.current}
visible={cfVisible}
onHide={cfHide}
message="Are you sure you want to delete ping?"
icon="pi pi-exclamation-triangle text-orange-400"
accept={removePing}

View File

@@ -1,13 +1,12 @@
import { InputText } from 'primereact/inputtext';
import { Dialog } from 'primereact/dialog';
import { TooltipPosition, WdButton, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
import { getSystemById } from '@/hooks/Mapper/helpers';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Button } from 'primereact/button';
import { OutCommand } from '@/hooks/Mapper/types';
import { IconField } from 'primereact/iconfield';
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager.ts';
import { WdImageSize, WdImgButton, TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
import { Dialog } from 'primereact/dialog';
import { IconField } from 'primereact/iconfield';
import { InputText } from 'primereact/inputtext';
import { useCallback, useEffect, useRef, useState } from 'react';
interface SystemCustomLabelDialog {
systemId: string;
@@ -126,7 +125,7 @@ export const SystemCustomLabelDialog = ({ systemId, visible, setVisible }: Syste
</div>
<div className="flex gap-2 justify-end">
<Button onClick={handleSave} outlined size="small" label="Save"></Button>
<WdButton type="submit" onClick={handleSave} outlined size="small" label="Save"></WdButton>
</div>
</div>
</form>

View File

@@ -9,12 +9,12 @@ import {
} from '@/hooks/Mapper/components/map/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 { SETTINGS_KEYS, SignatureSettingsType } from '@/hooks/Mapper/constants/signatures';
import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { CommandLinkSignatureToSystem, SignatureGroup, SystemSignature, TimeStatus } from '@/hooks/Mapper/types';
import { CommandLinkSignatureToSystem, SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
import { SETTINGS_KEYS, SignatureSettingsType } from '@/hooks/Mapper/constants/signatures';
import { useSystemSignaturesData } from '../../widgets/SystemSignatures/hooks/useSystemSignaturesData';
const K162_SIGNATURE_TYPE = WORMHOLES_ADDITIONAL_INFO_BY_SHORT_NAME['K162'].shortName;
@@ -116,14 +116,14 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
);
const handleSelect = useCallback(
async (signature: SystemSignature) => {
(signature: SystemSignature) => {
if (!signature) {
return;
}
const { outCommand } = ref.current;
await outCommand({
outCommand({
type: OutCommand.linkSignatureToSystem,
data: {
...data,
@@ -131,34 +131,16 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
},
});
if (parseSignatureCustomInfo(signature.custom_info).isEOL === true) {
await outCommand({
type: OutCommand.updateConnectionTimeStatus,
data: {
source: data.solar_system_source,
target: data.solar_system_target,
value: TimeStatus.eol,
},
});
}
const whShipSize = getWhSize(wormholes, signature.type);
if (whShipSize !== undefined && whShipSize !== null) {
await outCommand({
type: OutCommand.updateConnectionShipSizeType,
data: {
source: data.solar_system_source,
target: data.solar_system_target,
value: whShipSize,
},
});
}
setVisible(false);
},
[data, setVisible, wormholes],
[data, setVisible],
);
const { signatures } = useSystemSignaturesData({
systemId: `${data.solar_system_source}`,
settings: LINK_SIGNTATURE_SETTINGS,
});
useEffect(() => {
if (!targetSystemDynamicInfo) {
handleHide();
@@ -176,10 +158,12 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
>
<SystemSignaturesContent
systemId={`${data.solar_system_source}`}
hideLinkedSignatures
signatures={signatures}
hasUnsupportedLanguage={false}
settings={LINK_SIGNTATURE_SETTINGS}
hideLinkedSignatures
selectable
onSelect={handleSelect}
selectable={true}
filterSignature={filterSignature}
/>
</Dialog>

View File

@@ -1,12 +1,11 @@
import { InputTextarea } from 'primereact/inputtextarea';
import { Dialog } from 'primereact/dialog';
import { SystemView, WdButton } from '@/hooks/Mapper/components/ui-kit';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback, useRef, useState } from 'react';
import { Button } from 'primereact/button';
import { OutCommand } from '@/hooks/Mapper/types';
import { PingType } from '@/hooks/Mapper/types/ping.ts';
import { SystemView } from '@/hooks/Mapper/components/ui-kit';
import clsx from 'clsx';
import { Dialog } from 'primereact/dialog';
import { InputTextarea } from 'primereact/inputtextarea';
import { useCallback, useRef, useState } from 'react';
const PING_TITLES = {
[PingType.Rally]: 'RALLY',
@@ -63,7 +62,7 @@ export const SystemPingDialog = ({ systemId, type, visible, setVisible }: System
</div>
}
visible={visible}
draggable={false}
draggable={true}
style={{ width: '450px' }}
onShow={onShow}
onHide={() => {
@@ -92,7 +91,7 @@ export const SystemPingDialog = ({ systemId, type, visible, setVisible }: System
</div>
<div className="flex gap-2 justify-end">
<Button onClick={handleSave} size="small" severity="danger" label="Ping!"></Button>
<WdButton type="submit" onClick={handleSave} size="small" severity="danger" label="Ping!" />
</div>
</div>
</form>

View File

@@ -1,16 +1,15 @@
import { InputText } from 'primereact/inputtext';
import { InputTextarea } from 'primereact/inputtextarea';
import { Dialog } from 'primereact/dialog';
import { TooltipPosition, WdButton, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
import { getSystemById } from '@/hooks/Mapper/helpers';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useMapGetOption } from '@/hooks/Mapper/mapRootProvider/hooks/api';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Button } from 'primereact/button';
import { OutCommand } from '@/hooks/Mapper/types';
import { IconField } from 'primereact/iconfield';
import { TooltipPosition, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager.ts';
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
import { OutCommand } from '@/hooks/Mapper/types';
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager.ts';
import { Dialog } from 'primereact/dialog';
import { IconField } from 'primereact/iconfield';
import { InputText } from 'primereact/inputtext';
import { InputTextarea } from 'primereact/inputtextarea';
import { useCallback, useEffect, useRef, useState } from 'react';
interface SystemSettingsDialog {
systemId: string;
@@ -114,7 +113,7 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
<Dialog
header="System settings"
visible={visible}
draggable={false}
draggable={true}
style={{ width: '450px' }}
onShow={onShow}
onHide={() => {
@@ -126,7 +125,7 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
}}
>
<form onSubmit={handleSave}>
<div className="flex flex-col gap-3">
<div className="flex flex-col gap-3 px-2">
<div className="flex flex-col gap-2">
<div className="flex flex-col gap-1">
<label htmlFor="username">Custom name</label>
@@ -226,7 +225,7 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
</div>
<div className="flex gap-2 justify-end">
<Button onClick={handleSave} outlined size="small" label="Save"></Button>
<WdButton onClick={handleSave} outlined size="small" label="Save" type="submit" />
</div>
</div>
</form>

View File

@@ -1,11 +1,9 @@
import { useRouteProvider } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesProvider.tsx';
import { PrettySwitchbox } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
import { RoutesType } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { Dialog } from 'primereact/dialog';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Button } from 'primereact/button';
import {
RoutesType,
useRouteProvider,
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesProvider.tsx';
import { PrettySwitchbox } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components';
interface RoutesSettingsDialog {
visible: boolean;
@@ -83,7 +81,7 @@ export const RoutesSettingsDialog = ({ visible, setVisible }: RoutesSettingsDial
</div>
<div className="flex gap-2 justify-end">
<Button onClick={handleSave} outlined size="small" label="Apply"></Button>
<WdButton onClick={handleSave} outlined size="small" label="Apply"></WdButton>
</div>
</div>
</Dialog>

View File

@@ -3,6 +3,7 @@ import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { RoutesType } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { LoadRoutesCommand } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/types.ts';
import { RoutesList } from '@/hooks/Mapper/types/routes.ts';
import { flattenValues } from '@/hooks/Mapper/utils/flattenValues.ts';
function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T>();
@@ -64,12 +65,8 @@ export const useLoadRoutes = ({
systems?.length,
connections,
hubs,
routesSettings,
...Object.keys(routesSettings)
.sort()
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
.map(x => routesSettings[x]),
// we need make it flat recursively
...flattenValues(routesSettings),
...deps,
]);

View File

@@ -1,6 +1,5 @@
import { Dialog } from 'primereact/dialog';
import { useCallback, useState } from 'react';
import { Button } from 'primereact/button';
import { TabPanel, TabView } from 'primereact/tabview';
import { PrettySwitchbox } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components';
import { Dropdown } from 'primereact/dropdown';
@@ -10,6 +9,7 @@ import {
SIGNATURE_SETTINGS,
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
import { SignatureSettingsType } from '@/hooks/Mapper/constants/signatures.ts';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
interface SystemSignatureSettingsDialogProps {
settings: SignatureSettingsType;
@@ -92,7 +92,7 @@ export const SystemSignatureSettingsDialog = ({
</div>
<div className="flex gap-2 justify-end">
<Button onClick={handleSave} outlined size="small" label="Save"></Button>
<WdButton onClick={handleSave} outlined size="small" label="Save" />
</div>
</div>
</Dialog>

View File

@@ -1,123 +1,16 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
import { SETTINGS_KEYS, SIGNATURE_WINDOW_ID, SignatureSettingsType } from '@/hooks/Mapper/constants/signatures';
import { useHotkey } from '@/hooks/Mapper/hooks/useHotkey';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback, useMemo, useState } from 'react';
import { useSignatureUndo } from './hooks/useSignatureUndo';
import { useSystemSignaturesData } from './hooks/useSystemSignaturesData';
import { SystemSignaturesHeader } from './SystemSignatureHeader';
import { SystemSignaturesContent } from './SystemSignaturesContent';
import { SystemSignatureSettingsDialog } from './SystemSignatureSettingsDialog';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { SystemSignaturesHeader } from './SystemSignatureHeader';
import { useHotkey } from '@/hooks/Mapper/hooks/useHotkey';
import { getDeletionTimeoutMs } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
import { OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers';
import { ExtendedSystemSignature } from '@/hooks/Mapper/types';
import { SETTINGS_KEYS, SIGNATURE_WINDOW_ID, SignatureSettingsType } from '@/hooks/Mapper/constants/signatures';
/**
* Custom hook for managing pending signature deletions and undo countdown.
*/
function useSignatureUndo(
systemId: string | undefined,
settings: SignatureSettingsType,
outCommand: OutCommandHandler,
) {
const [countdown, setCountdown] = useState<number>(0);
const [pendingIds, setPendingIds] = useState<Set<string>>(new Set());
const [deletedSignatures, setDeletedSignatures] = useState<ExtendedSystemSignature[]>([]);
const intervalRef = useRef<number | null>(null);
const addDeleted = useCallback((signatures: ExtendedSystemSignature[]) => {
const newIds = signatures.map(sig => sig.eve_id);
setPendingIds(prev => {
const next = new Set(prev);
newIds.forEach(id => next.add(id));
return next;
});
setDeletedSignatures(prev => [...prev, ...signatures]);
}, []);
// Clear deleted signatures when system changes
useEffect(() => {
if (systemId) {
setDeletedSignatures([]);
setPendingIds(new Set());
setCountdown(0);
if (intervalRef.current != null) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
}
}, [systemId]);
// kick off or clear countdown whenever pendingIds changes
useEffect(() => {
// clear any existing timer
if (intervalRef.current != null) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
if (pendingIds.size === 0) {
setCountdown(0);
setDeletedSignatures([]);
return;
}
// determine timeout from settings
const timeoutMs = getDeletionTimeoutMs(settings);
// Ensure a minimum of 1 second for immediate deletion so the UI shows
const effectiveTimeoutMs = timeoutMs === 0 ? 1000 : timeoutMs;
setCountdown(Math.ceil(effectiveTimeoutMs / 1000));
// start new interval
intervalRef.current = window.setInterval(() => {
setCountdown(prev => {
if (prev <= 1) {
clearInterval(intervalRef.current!);
intervalRef.current = null;
setPendingIds(new Set());
setDeletedSignatures([]);
return 0;
}
return prev - 1;
});
}, 1000);
return () => {
if (intervalRef.current != null) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};
}, [pendingIds, settings]);
// undo handler
const handleUndo = useCallback(async () => {
if (!systemId || pendingIds.size === 0) return;
await outCommand({
type: OutCommand.undoDeleteSignatures,
data: { system_id: systemId, eve_ids: Array.from(pendingIds) },
});
setPendingIds(new Set());
setDeletedSignatures([]);
setCountdown(0);
if (intervalRef.current != null) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
}, [systemId, pendingIds, outCommand]);
return {
pendingIds,
countdown,
deletedSignatures,
addDeleted,
handleUndo,
};
}
export const SystemSignatures = () => {
const [visible, setVisible] = useState(false);
const [sigCount, setSigCount] = useState(0);
const [showSettings, setShowSettings] = useState(false);
const {
data: { selectedSystems },
@@ -127,31 +20,6 @@ export const SystemSignatures = () => {
const [systemId] = selectedSystems;
const isSystemSelected = useMemo(() => selectedSystems.length === 1, [selectedSystems.length]);
const { pendingIds, countdown, deletedSignatures, addDeleted, handleUndo } = useSignatureUndo(
systemId,
settingsSignatures,
outCommand,
);
useHotkey(true, ['z', 'Z'], (event: KeyboardEvent) => {
if (pendingIds.size > 0 && countdown > 0) {
event.preventDefault();
event.stopPropagation();
handleUndo();
}
});
const handleCountChange = useCallback((count: number) => {
setSigCount(count);
}, []);
const handleSettingsSave = useCallback(
(newSettings: SignatureSettingsType) => {
settingsSignaturesUpdate(newSettings);
setVisible(false);
},
[settingsSignaturesUpdate],
);
const handleLazyDeleteToggle = useCallback(
(value: boolean) => {
@@ -163,7 +31,42 @@ export const SystemSignatures = () => {
[settingsSignaturesUpdate],
);
const openSettings = useCallback(() => setVisible(true), []);
const {
signatures,
selectedSignatures,
setSelectedSignatures,
handleDeleteSelected,
handleSelectAll,
handlePaste,
hasUnsupportedLanguage,
} = useSystemSignaturesData({
systemId,
settings: settingsSignatures,
onLazyDeleteChange: handleLazyDeleteToggle,
});
const sigCount = useMemo(() => signatures.length, [signatures]);
const deletedSignatures = useMemo(() => signatures.filter(s => s.deleted), [signatures]);
const { countdown, handleUndo } = useSignatureUndo(systemId, settingsSignatures, deletedSignatures, outCommand);
useHotkey(true, ['z', 'Z'], (event: KeyboardEvent) => {
if (deletedSignatures.length > 0 && countdown > 0) {
event.preventDefault();
event.stopPropagation();
handleUndo();
}
});
const handleSettingsSave = useCallback(
(newSettings: SignatureSettingsType) => {
settingsSignaturesUpdate(newSettings);
setShowSettings(false);
},
[settingsSignaturesUpdate],
);
const openSettings = useCallback(() => setShowSettings(true), []);
return (
<Widget
@@ -171,7 +74,7 @@ export const SystemSignatures = () => {
<SystemSignaturesHeader
sigCount={sigCount}
lazyDeleteValue={settingsSignatures[SETTINGS_KEYS.LAZY_DELETE_SIGNATURES] as boolean}
pendingCount={pendingIds.size}
pendingCount={deletedSignatures.length}
undoCountdown={countdown}
onLazyDeleteChange={handleLazyDeleteToggle}
onUndoClick={handleUndo}
@@ -187,18 +90,21 @@ export const SystemSignatures = () => {
) : (
<SystemSignaturesContent
systemId={systemId}
signatures={signatures}
selectedSignatures={selectedSignatures}
onSelectSignatures={setSelectedSignatures}
onDeleteSelected={handleDeleteSelected}
onSelectAll={handleSelectAll}
onPaste={handlePaste}
hasUnsupportedLanguage={hasUnsupportedLanguage}
settings={settingsSignatures}
deletedSignatures={deletedSignatures}
onLazyDeleteChange={handleLazyDeleteToggle}
onCountChange={handleCountChange}
onSignatureDeleted={addDeleted}
/>
)}
{visible && (
{showSettings && (
<SystemSignatureSettingsDialog
settings={settingsSignatures}
onCancel={() => setVisible(false)}
onCancel={() => setShowSettings(false)}
onSave={handleSettingsSave}
/>
)}

View File

@@ -28,39 +28,44 @@ import {
renderInfoColumn,
renderUpdatedTimeLeft,
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
import { SETTINGS_KEYS, SIGNATURE_WINDOW_ID, SignatureSettingsType } from '@/hooks/Mapper/constants/signatures.ts';
import { useClipboard, useHotkey } from '@/hooks/Mapper/hooks';
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth';
import { getSignatureRowClass } from '../helpers/rowStyles';
import { useSystemSignaturesData } from '../hooks/useSystemSignaturesData';
import { SETTINGS_KEYS, SIGNATURE_WINDOW_ID, SignatureSettingsType } from '@/hooks/Mapper/constants/signatures.ts';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { getSignatureRowClass } from '../helpers/rowStyles';
const renderColIcon = (sig: SystemSignature) => renderIcon(sig);
interface SystemSignaturesContentProps {
systemId: string;
signatures: ExtendedSystemSignature[];
selectedSignatures?: ExtendedSystemSignature[];
onSelectSignatures?: (s: ExtendedSystemSignature[]) => void;
onDeleteSelected?: () => Promise<void>;
onSelectAll?: () => void;
onPaste?: (clipboardString: string) => void;
settings: SignatureSettingsType;
hideLinkedSignatures?: boolean;
hasUnsupportedLanguage?: boolean;
selectable?: boolean;
onSelect?: (signature: SystemSignature) => void;
onLazyDeleteChange?: (value: boolean) => void;
onCountChange?: (count: number) => void;
filterSignature?: (signature: SystemSignature) => boolean;
onSignatureDeleted?: (deletedSignatures: ExtendedSystemSignature[]) => void;
deletedSignatures?: ExtendedSystemSignature[];
}
export const SystemSignaturesContent = ({
systemId,
signatures,
selectedSignatures,
onSelectSignatures,
onDeleteSelected,
onSelectAll,
onPaste,
settings,
hideLinkedSignatures,
hasUnsupportedLanguage,
selectable,
onSelect,
onLazyDeleteChange,
onCountChange,
filterSignature,
onSignatureDeleted,
deletedSignatures = [],
}: SystemSignaturesContentProps) => {
const [selectedSignatureForDialog, setSelectedSignatureForDialog] = useState<SystemSignature | null>(null);
const [showSignatureSettings, setShowSignatureSettings] = useState(false);
@@ -79,32 +84,18 @@ export const SystemSignaturesContent = ({
const { clipboardContent, setClipboardContent } = useClipboard();
const {
signatures,
selectedSignatures,
setSelectedSignatures,
handleDeleteSelected,
handleSelectAll,
handlePaste,
hasUnsupportedLanguage,
} = useSystemSignaturesData({
systemId,
settings,
onCountChange,
onLazyDeleteChange,
onSignatureDeleted,
});
const deletedSignatures = useMemo(() => signatures.filter(s => s.deleted), [signatures]);
useEffect(() => {
if (selectable) return;
if (!clipboardContent?.text) return;
handlePaste(clipboardContent.text);
onPaste?.(clipboardContent.text);
setClipboardContent(null);
}, [selectable, clipboardContent, handlePaste, setClipboardContent]);
}, [selectable, clipboardContent, onPaste, setClipboardContent]);
useHotkey(true, ['a'], handleSelectAll);
useHotkey(true, ['a'], () => onSelectAll?.());
useHotkey(false, ['Backspace', 'Delete'], (event: KeyboardEvent) => {
const targetWindow = (event.target as HTMLHtmlElement)?.closest(`[data-window-id="${SIGNATURE_WINDOW_ID}"]`);
@@ -117,7 +108,7 @@ export const SystemSignaturesContent = ({
event.stopPropagation();
// Delete key should always immediately delete, never show pending deletions
handleDeleteSelected();
onDeleteSelected?.();
});
const handleResize = useCallback(() => {
@@ -152,14 +143,23 @@ export const SystemSignaturesContent = ({
selectable
? onSelect?.(selectableSignatures[0])
: setSelectedSignatures(selectableSignatures as ExtendedSystemSignature[]);
: onSelectSignatures?.(selectableSignatures as ExtendedSystemSignature[]);
},
[onSelect, selectable, setSelectedSignatures, deletedSignatures],
[onSelect, selectable, onSelectSignatures, deletedSignatures],
);
const { showDescriptionColumn, showUpdatedColumn, showCharacterColumn, showCharacterPortrait } = useMemo(
const {
showGroupColumn,
showDescriptionColumn,
showAddedColumn,
showUpdatedColumn,
showCharacterColumn,
showCharacterPortrait,
} = useMemo(
() => ({
showGroupColumn: settings[SETTINGS_KEYS.SHOW_GROUP_COLUMN] as boolean,
showDescriptionColumn: settings[SETTINGS_KEYS.SHOW_DESCRIPTION_COLUMN] as boolean,
showAddedColumn: settings[SETTINGS_KEYS.SHOW_ADDED_COLUMN] as boolean,
showUpdatedColumn: settings[SETTINGS_KEYS.SHOW_UPDATED_COLUMN] as boolean,
showCharacterColumn: settings[SETTINGS_KEYS.SHOW_CHARACTER_COLUMN] as boolean,
showCharacterPortrait: settings[SETTINGS_KEYS.SHOW_CHARACTER_PORTRAIT] as boolean,
@@ -168,9 +168,6 @@ export const SystemSignaturesContent = ({
);
const filteredSignatures = useMemo<ExtendedSystemSignature[]>(() => {
// Get the set of deleted signature IDs for quick lookup
const deletedIds = new Set(deletedSignatures.map(sig => sig.eve_id));
// Common filter function
const shouldShowSignature = (sig: ExtendedSystemSignature): boolean => {
if (filterSignature && !filterSignature(sig)) {
@@ -204,24 +201,8 @@ export const SystemSignaturesContent = ({
return settings[sig.kind] as boolean;
};
// Filter active signatures, excluding any that are in the deleted list
const activeSignatures = signatures.filter(sig => {
// Skip if this signature is in the deleted list
if (deletedIds.has(sig.eve_id)) {
return false;
}
return shouldShowSignature(sig);
});
// Add deleted signatures with pending deletion flag, applying the same filters
const deletedWithPendingFlag = deletedSignatures.filter(shouldShowSignature).map(sig => ({
...sig,
pendingDeletion: true,
}));
return [...activeSignatures, ...deletedWithPendingFlag];
}, [signatures, hideLinkedSignatures, settings, filterSignature, deletedSignatures]);
return signatures.filter(sig => shouldShowSignature(sig));
}, [signatures, hideLinkedSignatures, settings, filterSignature]);
const onRowMouseEnter = useCallback((e: DataTableRowMouseEvent) => {
setHoveredSignature(e.data as SystemSignature);
@@ -244,20 +225,18 @@ export const SystemSignaturesContent = ({
return getSignatureRowClass(
rowData as ExtendedSystemSignature,
refVars.current.selectedSignatures,
refVars.current.selectedSignatures || [],
refVars.current.settings[SETTINGS_KEYS.COLOR_BY_TYPE] as boolean,
);
}, []);
const handleSortSettings = useCallback(
(e: DataTableStateEvent) =>
refVars.current.settingsSignaturesUpdate({
...refVars.current.settingsSignatures,
[SETTINGS_KEYS.SORT_FIELD]: e.sortField,
[SETTINGS_KEYS.SORT_ORDER]: e.sortOrder,
}),
[],
);
const handleSortSettings = useCallback((e: DataTableStateEvent) => {
refVars.current.settingsSignaturesUpdate({
...refVars.current.settingsSignatures,
[SETTINGS_KEYS.SORT_FIELD]: e.sortField,
[SETTINGS_KEYS.SORT_ORDER]: e.sortOrder,
});
}, []);
return (
<div ref={tableRef} className="h-full">
@@ -278,7 +257,7 @@ export const SystemSignaturesContent = ({
value={filteredSignatures}
size="small"
selectionMode="multiple"
selection={selectedSignatures}
selection={selectedSignatures || []}
metaKeySelection
onSelectionChange={handleSelectSignatures}
dataKey="eve_id"
@@ -309,15 +288,17 @@ export const SystemSignaturesContent = ({
style={{ maxWidth: 72, minWidth: 72, width: 72 }}
sortable
/>
<Column
field="group"
header="Group"
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
style={{ maxWidth: 110, minWidth: 110, width: 110 }}
body={sig => sig.group ?? ''}
hidden={isCompact}
sortable
/>
{showGroupColumn && (
<Column
field="group"
header="Group"
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
style={{ maxWidth: 110, minWidth: 110, width: 110 }}
body={sig => sig.group ?? ''}
hidden={isCompact}
sortable
/>
)}
<Column
field="info"
header="Info"
@@ -325,6 +306,8 @@ export const SystemSignaturesContent = ({
style={{ maxWidth: nameColumnWidth }}
hidden={isCompact || isMedium}
body={renderInfoColumn}
sortable
sortField="name"
/>
{showDescriptionColumn && (
<Column
@@ -336,15 +319,17 @@ export const SystemSignaturesContent = ({
sortable
/>
)}
<Column
field="inserted_at"
header="Added"
dataType="date"
body={renderAddedTimeLeft}
style={{ minWidth: 70, maxWidth: 80 }}
bodyClassName="ssc-header text-ellipsis overflow-hidden whitespace-nowrap"
sortable
/>
{showAddedColumn && (
<Column
field="inserted_at"
header="Added"
dataType="date"
body={renderAddedTimeLeft}
style={{ minWidth: 70, maxWidth: 80 }}
bodyClassName="ssc-header text-ellipsis overflow-hidden whitespace-nowrap"
sortable
/>
)}
{showUpdatedColumn && (
<Column
field="updated_at"

View File

@@ -1,3 +1,4 @@
import { SETTINGS_KEYS, SIGNATURES_DELETION_TIMING, SignatureSettingsType } from '@/hooks/Mapper/constants/signatures';
import {
GroupType,
SignatureGroup,
@@ -11,7 +12,6 @@ import {
SignatureKindFR,
SignatureKindRU,
} from '@/hooks/Mapper/types';
import { SETTINGS_KEYS, SIGNATURES_DELETION_TIMING, SignatureSettingsType } from '@/hooks/Mapper/constants/signatures';
export const TIME_ONE_MINUTE = 1000 * 60;
export const TIME_TEN_MINUTES = TIME_ONE_MINUTE * 10;
@@ -130,6 +130,8 @@ export const SIGNATURE_SETTINGS = {
{ type: SettingsTypes.flag, key: SETTINGS_KEYS.COMBAT_SITE, name: 'Show Combat Sites' },
],
uiFlags: [
{ type: SettingsTypes.flag, key: SETTINGS_KEYS.SHOW_GROUP_COLUMN, name: 'Show Group Column' },
{ type: SettingsTypes.flag, key: SETTINGS_KEYS.SHOW_ADDED_COLUMN, name: 'Show Added Column' },
{ type: SettingsTypes.flag, key: SETTINGS_KEYS.SHOW_UPDATED_COLUMN, name: 'Show Updated Column' },
{ type: SettingsTypes.flag, key: SETTINGS_KEYS.SHOW_DESCRIPTION_COLUMN, name: 'Show Description Column' },
{ type: SettingsTypes.flag, key: SETTINGS_KEYS.SHOW_CHARACTER_COLUMN, name: 'Show Character Column' },

View File

@@ -1,5 +1,5 @@
import clsx from 'clsx';
import { ExtendedSystemSignature, SignatureGroup } from '@/hooks/Mapper/types';
import clsx from 'clsx';
import { getRowBackgroundColor } from './getRowBackgroundColor';
import classes from './rowStyles.module.scss';
@@ -20,7 +20,7 @@ export function getSignatureRowClass(
return clsx([...baseCls, 'bg-violet-400/40 hover:bg-violet-300/40']);
}
if (row.pendingDeletion) {
if (row.deleted) {
return clsx([...baseCls, 'bg-red-400/40 hover:bg-red-400/50']);
}

View File

@@ -1,24 +1,20 @@
import { ExtendedSystemSignature } from '@/hooks/Mapper/types';
import { SignatureSettingsType } from '@/hooks/Mapper/constants/signatures.ts';
import { ExtendedSystemSignature } from '@/hooks/Mapper/types';
export interface UseSystemSignaturesDataProps {
systemId: string;
settings: SignatureSettingsType;
hideLinkedSignatures?: boolean;
onCountChange?: (count: number) => void;
onPendingChange?: (
pending: React.MutableRefObject<Record<string, ExtendedSystemSignature>>,
undo: () => void,
) => void;
onLazyDeleteChange?: (value: boolean) => void;
deletionTiming?: number;
}
export interface UseFetchingParams {
systemId: string;
settings: SignatureSettingsType;
signaturesRef: React.MutableRefObject<ExtendedSystemSignature[]>;
setSignatures: React.Dispatch<React.SetStateAction<ExtendedSystemSignature[]>>;
pendingDeletionMapRef: React.MutableRefObject<Record<string, ExtendedSystemSignature>>;
}
export interface UsePendingDeletionParams {

View File

@@ -1,42 +0,0 @@
import { useCallback, useRef } from 'react';
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
import { prepareUpdatePayload } from '../helpers';
import { UsePendingDeletionParams } from './types';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { ExtendedSystemSignature } from '@/hooks/Mapper/types';
export function usePendingDeletions({
systemId,
setSignatures,
onPendingChange,
}: Omit<UsePendingDeletionParams, 'deletionTiming'>) {
const { outCommand } = useMapRootState();
const pendingDeletionMapRef = useRef<Record<string, ExtendedSystemSignature>>({});
const processRemovedSignatures = useCallback(
async (
removed: ExtendedSystemSignature[],
added: ExtendedSystemSignature[],
updated: ExtendedSystemSignature[],
) => {
if (!removed.length) return;
await outCommand({
type: OutCommand.updateSignatures,
data: prepareUpdatePayload(systemId, added, updated, removed),
});
},
[systemId, outCommand],
);
const clearPendingDeletions = useCallback(() => {
pendingDeletionMapRef.current = {};
setSignatures(prev => prev.map(x => (x.pendingDeletion ? { ...x, pendingDeletion: false } : x)));
onPendingChange?.(pendingDeletionMapRef, clearPendingDeletions);
}, []);
return {
pendingDeletionMapRef,
processRemovedSignatures,
clearPendingDeletions,
};
}

View File

@@ -1,21 +1,27 @@
import { useCallback } from 'react';
import { SETTINGS_KEYS } from '@/hooks/Mapper/constants/signatures';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { ExtendedSystemSignature, SystemSignature } from '@/hooks/Mapper/types';
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
import { prepareUpdatePayload, getActualSigs, mergeLocalPending } from '../helpers';
import { useCallback, useMemo } from 'react';
import { getDeletionTimeoutMs } from '../constants';
import { getActualSigs, prepareUpdatePayload } from '../helpers';
import { UseFetchingParams } from './types';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
export const useSignatureFetching = ({
systemId,
signaturesRef,
setSignatures,
pendingDeletionMapRef,
}: UseFetchingParams) => {
export const useSignatureFetching = ({ systemId, settings, signaturesRef, setSignatures }: UseFetchingParams) => {
const {
data: { characters },
outCommand,
} = useMapRootState();
const deleteTimeout = useMemo(() => {
const lazyDelete = settings[SETTINGS_KEYS.LAZY_DELETE_SIGNATURES] as boolean;
if (!lazyDelete) {
return 0;
}
return getDeletionTimeoutMs(settings);
}, [settings]);
const handleGetSignatures = useCallback(async () => {
if (!systemId) {
setSignatures([]);
@@ -32,24 +38,23 @@ export const useSignatureFetching = ({
character_name: characters.find(c => c.eve_id === s.character_eve_id)?.name,
})) as ExtendedSystemSignature[];
setSignatures(() => mergeLocalPending(pendingDeletionMapRef, extended));
setSignatures(() => extended);
}, [characters, systemId, outCommand]);
const handleUpdateSignatures = useCallback(
async (newList: ExtendedSystemSignature[], updateOnly: boolean, skipUpdateUntouched?: boolean) => {
const { added, updated, removed } = getActualSigs(
signaturesRef.current,
newList,
updateOnly,
skipUpdateUntouched,
);
const actualSigs = getActualSigs(signaturesRef.current, newList, updateOnly, skipUpdateUntouched);
await outCommand({
type: OutCommand.updateSignatures,
data: prepareUpdatePayload(systemId, added, updated, removed),
});
const { added, updated, removed } = actualSigs;
if (updated.length !== 0 || added.length !== 0 || removed.length !== 0) {
await outCommand({
type: OutCommand.updateSignatures,
data: { ...prepareUpdatePayload(systemId, added, updated, removed), deleteTimeout },
});
}
},
[systemId, outCommand, signaturesRef],
[systemId, deleteTimeout, outCommand, signaturesRef],
);
return {

View File

@@ -0,0 +1,89 @@
import { SignatureSettingsType } from '@/hooks/Mapper/constants/signatures';
import { ExtendedSystemSignature, OutCommandHandler } from '@/hooks/Mapper/types';
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
import { useCallback, useEffect, useRef, useState } from 'react';
import { getDeletionTimeoutMs } from '../constants';
/**
* Custom hook for managing pending signature deletions and undo countdown.
*/
export function useSignatureUndo(
systemId: string | undefined,
settings: SignatureSettingsType,
deletedSignatures: ExtendedSystemSignature[],
outCommand: OutCommandHandler,
) {
const [countdown, setCountdown] = useState<number>(0);
const intervalRef = useRef<number | null>(null);
// Clear deleted signatures when system changes
useEffect(() => {
if (systemId) {
setCountdown(0);
if (intervalRef.current != null) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
}
}, [systemId]);
// kick off or clear countdown whenever pendingIds changes
useEffect(() => {
// clear any existing timer
if (intervalRef.current != null) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
if (deletedSignatures.length === 0) {
setCountdown(0);
return;
}
// determine timeout from settings
const timeoutMs = getDeletionTimeoutMs(settings);
// Ensure a minimum of 1 second for immediate deletion so the UI shows
const effectiveTimeoutMs = timeoutMs === 0 ? 1000 : timeoutMs;
setCountdown(Math.ceil(effectiveTimeoutMs / 1000));
// start new interval
intervalRef.current = window.setInterval(() => {
setCountdown(prev => {
if (prev <= 1) {
clearInterval(intervalRef.current!);
intervalRef.current = null;
return 0;
}
return prev - 1;
});
}, 1000);
return () => {
if (intervalRef.current != null) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};
}, [deletedSignatures, settings]);
// undo handler
const handleUndo = useCallback(async () => {
if (!systemId || deletedSignatures.length === 0) return;
await outCommand({
type: OutCommand.undoDeleteSignatures,
data: { system_id: systemId, eve_ids: deletedSignatures.map(s => s.eve_id) },
});
setCountdown(0);
if (intervalRef.current != null) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
}, [systemId, deletedSignatures, outCommand]);
return {
countdown,
handleUndo,
};
}

View File

@@ -1,44 +1,29 @@
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 { useCallback, useEffect, useState } from 'react';
import useRefState from 'react-usestateref';
import { getDeletionTimeoutMs } 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';
import { SETTINGS_KEYS } from '@/hooks/Mapper/constants/signatures.ts';
import { UseSystemSignaturesDataProps } from './types';
import { useSignatureFetching } from './useSignatureFetching';
export const useSystemSignaturesData = ({
systemId,
settings,
onCountChange,
onPendingChange,
onLazyDeleteChange,
onSignatureDeleted,
}: Omit<UseSystemSignaturesDataProps, 'deletionTiming'> & {
onSignatureDeleted?: (deletedSignatures: ExtendedSystemSignature[]) => void;
}) => {
const { outCommand } = useMapRootState();
const [signatures, setSignatures, signaturesRef] = useRefState<ExtendedSystemSignature[]>([]);
const [selectedSignatures, setSelectedSignatures] = useState<ExtendedSystemSignature[]>([]);
const [hasUnsupportedLanguage, setHasUnsupportedLanguage] = useState<boolean>(false);
const { pendingDeletionMapRef, processRemovedSignatures, clearPendingDeletions } = usePendingDeletions({
systemId,
setSignatures,
onPendingChange,
});
const { handleGetSignatures, handleUpdateSignatures } = useSignatureFetching({
systemId,
settings,
signaturesRef,
setSignatures,
pendingDeletionMapRef,
});
const handlePaste = useCallback(
@@ -67,40 +52,14 @@ export const useSystemSignaturesData = ({
setHasUnsupportedLanguage(false);
}
const currentNonPending = lazyDeleteValue
? signaturesRef.current.filter(sig => !sig.pendingDeletion)
: signaturesRef.current.filter(sig => !sig.pendingDeletion || !sig.pendingAddition);
const { added, updated, removed } = getActualSigs(currentNonPending, incomingSignatures, !lazyDeleteValue, false);
if (removed.length > 0) {
await processRemovedSignatures(removed, added, updated);
// Show pending deletions if lazy deletion is enabled
// The deletion timing controls how long the countdown lasts, not whether lazy delete is active
if (onSignatureDeleted && lazyDeleteValue) {
onSignatureDeleted(removed);
}
}
if (updated.length !== 0 || added.length !== 0) {
await outCommand({
type: OutCommand.updateSignatures,
data: {
system_id: systemId,
added,
updated,
removed: [],
},
});
}
await handleUpdateSignatures(incomingSignatures, !lazyDeleteValue, false);
const keepLazy = settings[SETTINGS_KEYS.KEEP_LAZY_DELETE] as boolean;
if (lazyDeleteValue && !keepLazy) {
onLazyDeleteChange?.(false);
}
},
[settings, signaturesRef, processRemovedSignatures, outCommand, systemId, onLazyDeleteChange, onSignatureDeleted],
[settings, handleUpdateSignatures, onLazyDeleteChange],
);
const handleDeleteSelected = useCallback(async () => {
@@ -109,23 +68,15 @@ export const useSystemSignaturesData = ({
const selectedIds = selectedSignatures.map(s => s.eve_id);
const finalList = signatures.filter(s => !selectedIds.includes(s.eve_id));
// IMPORTANT: Send deletion to server BEFORE updating local state
// Otherwise signaturesRef.current will be updated and getActualSigs won't detect removals
await handleUpdateSignatures(finalList, false, true);
// Update local state after server call
setSignatures(finalList);
setSelectedSignatures([]);
}, [handleUpdateSignatures, selectedSignatures, signatures, setSignatures]);
await handleUpdateSignatures(finalList, false, true);
}, [handleUpdateSignatures, selectedSignatures, signatures]);
const handleSelectAll = useCallback(() => {
setSelectedSignatures(signatures);
}, [signatures]);
const undoPending = useCallback(() => {
clearPendingDeletions();
}, [clearPendingDeletions]);
useMapEventListener(event => {
if (event.name === Commands.signaturesUpdated && String(event.data) === String(systemId)) {
handleGetSignatures();
@@ -136,18 +87,13 @@ export const useSystemSignaturesData = ({
useEffect(() => {
if (!systemId) {
setSignatures([]);
undoPending();
return;
}
handleGetSignatures();
}, [systemId]);
useEffect(() => {
onCountChange?.(signatures.length);
}, [signatures]);
return {
signatures: signatures.filter(sig => !sig.deleted),
signatures,
selectedSignatures,
setSelectedSignatures,
handleDeleteSelected,

View File

@@ -1,14 +1,14 @@
import { PrimeIcons } from 'primereact/api';
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
import { SystemViewStandalone, TooltipPosition, WHClassView } from '@/hooks/Mapper/components/ui-kit';
import { SignatureGroup, SystemSignature, TimeStatus } from '@/hooks/Mapper/types';
import { PrimeIcons } from 'primereact/api';
import { renderK162Type } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureK162TypeSelect';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
import clsx from 'clsx';
import { renderName } from './renderName.tsx';
import { K162_TYPES_MAP } from '@/hooks/Mapper/constants.ts';
import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo.ts';
import clsx from 'clsx';
import { renderName } from './renderName.tsx';
export const renderInfoColumn = (row: SystemSignature) => {
if (!row.group || row.group === SignatureGroup.Wormhole) {
@@ -18,7 +18,9 @@ export const renderInfoColumn = (row: SystemSignature) => {
return (
<div className="flex justify-start items-center gap-[4px]">
{customInfo.isEOL && (
{row.temporary_name && <span className={clsx('text-[12px]')}>{row.temporary_name}</span>}
{customInfo.time_status === TimeStatus._1h && (
<WdTooltipWrapper offset={5} position={TooltipPosition.top} content="Signature marked as EOL">
<div className="pi pi-clock text-fuchsia-400 text-[11px] mr-[2px]"></div>
</WdTooltipWrapper>

View File

@@ -1,13 +1,13 @@
import React, { useEffect, useState, useCallback } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { Dialog } from 'primereact/dialog';
import { Button } from 'primereact/button';
import { AutoComplete } from 'primereact/autocomplete';
import { Calendar } from 'primereact/calendar';
import clsx from 'clsx';
import { StructureItem, StructureStatus, statusesRequiringTimer, formatToISO } from '../helpers';
import { formatToISO, statusesRequiringTimer, StructureItem, StructureStatus } from '../helpers';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { OutCommand } from '@/hooks/Mapper/types';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
interface StructuresEditDialogProps {
visible: boolean;
@@ -54,14 +54,13 @@ export const SystemStructuresDialog: React.FC<StructuresEditDialogProps> = ({
// If user typed more text but we have partial match in prevResults
if (newQuery.startsWith(prevQuery) && prevResults.length > 0) {
const filtered = prevResults.filter(item =>
item.label.toLowerCase().includes(newQuery.toLowerCase()),
);
const filtered = prevResults.filter(item => item.label.toLowerCase().includes(newQuery.toLowerCase()));
setOwnerSuggestions(filtered);
return;
}
try {
// TODO fix it
const { results = [] } = await outCommand({
type: OutCommand.getCorporationNames,
data: { search: newQuery },
@@ -96,9 +95,7 @@ export const SystemStructuresDialog: React.FC<StructuresEditDialogProps> = ({
// when user picks a corp from auto-complete
const handleSelectOwner = (selected: { label: string; value: string }) => {
setOwnerInput(selected.label);
setEditData(prev =>
prev ? { ...prev, ownerName: selected.label, ownerId: selected.value } : null,
);
setEditData(prev => (prev ? { ...prev, ownerName: selected.label, ownerId: selected.value } : null));
};
const handleStatusChange = (val: string) => {
@@ -125,6 +122,7 @@ export const SystemStructuresDialog: React.FC<StructuresEditDialogProps> = ({
// fetch corporation ticker if we have an ownerId
if (editData.ownerId) {
try {
// TODO fix it
const { ticker } = await outCommand({
type: OutCommand.getCorporationTicker,
data: { corp_id: editData.ownerId },
@@ -157,11 +155,7 @@ export const SystemStructuresDialog: React.FC<StructuresEditDialogProps> = ({
<div className="flex flex-col gap-2 text-[14px]">
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center">
<span>Type:</span>
<input
readOnly
className="p-inputtext p-component cursor-not-allowed"
value={editData.structureType ?? ''}
/>
<input readOnly className="p-inputtext p-component cursor-not-allowed" value={editData.structureType ?? ''} />
</label>
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center">
<span>Name:</span>
@@ -204,10 +198,12 @@ export const SystemStructuresDialog: React.FC<StructuresEditDialogProps> = ({
{statusesRequiringTimer.includes(editData.status) && (
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center">
<span>Timer <br /> (Eve Time):</span>
<span>
Timer <br /> (Eve Time):
</span>
<Calendar
value={editData.endTime ? new Date(editData.endTime) : undefined}
onChange={(e) => handleChange('endTime', e.value ?? '')}
onChange={e => handleChange('endTime', e.value ?? '')}
showTime
hourFormat="24"
dateFormat="yy-mm-dd"
@@ -227,8 +223,8 @@ export const SystemStructuresDialog: React.FC<StructuresEditDialogProps> = ({
</div>
<div className="flex justify-end items-center gap-2 mt-4">
<Button label="Delete" severity="danger" className="p-button-sm" onClick={handleDeleteClick} />
<Button label="Save" className="p-button-sm" onClick={handleSaveClick} />
<WdButton label="Delete" severity="danger" className="p-button-sm" onClick={handleDeleteClick} />
<WdButton label="Save" className="p-button-sm" onClick={handleSaveClick} />
</div>
</Dialog>
);

View File

@@ -16,8 +16,21 @@ const SystemKillsContent = () => {
} = useMapRootState();
const [systemId] = selectedSystems || [];
const whCacheRef = useMemo(() => new Map<number, boolean>(), []);
const systemStaticInfo = getSystemStaticInfo(systemId)!;
const isWormholeSystem = useCallback(
(systemId: number): boolean => {
const cached = whCacheRef.get(systemId);
if (cached !== undefined) return cached;
const info = getSystemStaticInfo(systemId);
const isWH = info?.system_class != null ? isWormholeSpace(Number(info.system_class)) : false;
whCacheRef.set(systemId, isWH);
return isWH;
},
[whCacheRef],
);
const { kills, isLoading, error } = useSystemKills({
systemId,
@@ -30,15 +43,9 @@ const SystemKillsContent = () => {
const showLoading = isLoading && kills.length === 0;
const filteredKills = useMemo(() => {
if (!settingsKills.whOnly || !settingsKills.showAll) return kills;
return kills.filter(kill => {
if (!systemStaticInfo) {
console.warn(`System with id ${kill.solar_system_id} not found.`);
return false;
}
return isWormholeSpace(systemStaticInfo.system_class);
});
}, [kills, settingsKills.whOnly, systemStaticInfo, settingsKills.showAll]);
if (!settingsKills.whOnly) return kills;
return kills.filter(kill => isWormholeSystem(Number(kill.solar_system_id)));
}, [kills, settingsKills.whOnly, isWormholeSystem]);
if (!isSubscriptionActive) {
return (

View File

@@ -1,13 +1,11 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { Dialog } from 'primereact/dialog';
import { Button } from 'primereact/button';
import { WdImgButton } from '@/hooks/Mapper/components/ui-kit';
import { SystemView, TooltipPosition, WdButton, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
import { PrimeIcons } from 'primereact/api';
import {
AddSystemDialog,
SearchOnSubmitCallback,
} from '@/hooks/Mapper/components/mapInterface/components/AddSystemDialog';
import { SystemView, TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
interface KillsSettingsDialogProps {
@@ -158,7 +156,7 @@ export const KillsSettingsDialog: React.FC<KillsSettingsDialogProps> = ({ visibl
</div>
<div className="flex gap-2 justify-end mt-4">
<Button onClick={handleApply} label="Apply" outlined size="small" />
<WdButton onClick={handleApply} label="Apply" outlined size="small" />
</div>
</div>

View File

@@ -1,8 +1,8 @@
import { useCallback, useMemo, useState, useEffect, useRef } from 'react';
import debounce from 'lodash.debounce';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
import { DetailedKill } from '@/hooks/Mapper/types/kills';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useStableValue } from '@/hooks/Mapper/hooks';
interface UseSystemKillsProps {
systemId?: string;
@@ -30,9 +30,8 @@ export function useSystemKills({ systemId, outCommand, showAllVisible = false, s
update,
storedSettings: { settingsKills },
} = useMapRootState();
const { excludedSystems } = settingsKills;
const effectiveSinceHours = sinceHours;
const excludedSystems = useStableValue(settingsKills.excludedSystems);
const effectiveSystemIds = useMemo(() => {
if (showAllVisible) {
@@ -76,13 +75,13 @@ export function useSystemKills({ systemId, outCommand, showAllVisible = false, s
eventType = OutCommand.getSystemsKills;
requestData = {
system_ids: effectiveSystemIds,
since_hours: effectiveSinceHours,
since_hours: sinceHours,
};
} else if (systemId) {
eventType = OutCommand.getSystemKills;
requestData = {
system_id: systemId,
since_hours: effectiveSinceHours,
since_hours: sinceHours,
};
} else {
setIsLoading(false);
@@ -110,16 +109,7 @@ export function useSystemKills({ systemId, outCommand, showAllVisible = false, s
setIsLoading(false);
}
},
[showAllVisible, systemId, outCommand, effectiveSystemIds, effectiveSinceHours, mergeKillsIntoGlobal],
);
const debouncedFetchKills = useMemo(
() =>
debounce(fetchKills, 500, {
leading: true,
trailing: false,
}),
[fetchKills],
[showAllVisible, systemId, outCommand, effectiveSystemIds, sinceHours, mergeKillsIntoGlobal],
);
const finalKills = useMemo(() => {
@@ -141,27 +131,22 @@ export function useSystemKills({ systemId, outCommand, showAllVisible = false, s
useEffect(() => {
if (!systemId && !showAllVisible && !didFallbackFetch.current) {
didFallbackFetch.current = true;
debouncedFetchKills.cancel();
fetchKills(true);
}
}, [systemId, showAllVisible, debouncedFetchKills, fetchKills]);
}, [systemId, showAllVisible, fetchKills]);
useEffect(() => {
if (effectiveSystemIds.length === 0) return;
if (showAllVisible || systemId) {
// Cancel any pending debounced fetch
debouncedFetchKills.cancel();
// Fetch kills immediately
fetchKills();
return () => debouncedFetchKills.cancel();
return;
}
}, [showAllVisible, systemId, effectiveSystemIds, debouncedFetchKills, fetchKills]);
}, [showAllVisible, systemId, effectiveSystemIds, fetchKills]);
const refetch = useCallback(() => {
debouncedFetchKills.cancel();
fetchKills();
}, [debouncedFetchKills, fetchKills]);
}, [fetchKills]);
return {
kills: finalKills,

View File

@@ -15,6 +15,7 @@ import { useMapEventListener } from '@/hooks/Mapper/events';
import { Commands } from '@/hooks/Mapper/types';
import { PingsInterface } from '@/hooks/Mapper/components/mapInterface/components';
import { OldSettingsDialog } from '@/hooks/Mapper/components/mapRootContent/components/OldSettingsDialog.tsx';
import { TopSearch } from '@/hooks/Mapper/components/mapRootContent/components/TopSearch';
export interface MapRootContentProps {}
@@ -72,6 +73,7 @@ export const MapRootContent = ({}: MapRootContentProps) => {
<div className="absolute top-0 left-14 w-[calc(100%-3.5rem)] h-[calc(100%-3.5rem)] pointer-events-none">
<Topbar>
<div className="flex items-center ml-1">
<TopSearch />
<PingsInterface />
<MapContextMenu
onShowOnTheMap={handleShowOnTheMap}

View File

@@ -1,22 +1,21 @@
import classes from './Connections.module.scss';
import { Sidebar } from 'primereact/sidebar';
import { useEffect, useMemo, useState, useCallback } from 'react';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
import clsx from 'clsx';
import {
ConnectionType,
ConnectionOutput,
ConnectionInfoOutput,
ConnectionOutput,
ConnectionType,
OutCommand,
Passage,
SolarSystemConnection,
} from '@/hooks/Mapper/types';
import clsx from 'clsx';
import { Sidebar } from 'primereact/sidebar';
import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
import { useCallback, useEffect, useMemo, useState } from 'react';
import classes from './Connections.module.scss';
import { PassageCard } from './PassageCard';
import { InfoDrawer, SystemView } from '@/hooks/Mapper/components/ui-kit';
import { InfoDrawer, SystemView, TimeAgo } from '@/hooks/Mapper/components/ui-kit';
import { kgToTons } from '@/hooks/Mapper/utils/kgToTons.ts';
import { TimeAgo } from '@/hooks/Mapper/components/ui-kit';
import { PassageCard } from './PassageCard';
const sortByDate = (a: string, b: string) => new Date(a).getTime() - new Date(b).getTime();
@@ -78,7 +77,7 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => {
}, [connections, selectedConnection]);
const isWormhole = useMemo(() => {
return cnInfo?.type !== ConnectionType.gate;
return cnInfo?.type === ConnectionType.wormhole;
}, [cnInfo]);
const [passages, setPassages] = useState<Passage[]>([]);

View File

@@ -3,7 +3,7 @@ import { Dialog } from 'primereact/dialog';
import { useCallback, useRef, useState } from 'react';
import { TabPanel, TabView } from 'primereact/tabview';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { OutCommand } from '@/hooks/Mapper/types';
import { OutCommand, UserPermission } from '@/hooks/Mapper/types';
import { CONNECTIONS_CHECKBOXES_PROPS, SIGNATURES_CHECKBOXES_PROPS, SYSTEMS_CHECKBOXES_PROPS } from './constants.ts';
import {
MapSettingsProvider,
@@ -12,7 +12,10 @@ import {
import { WidgetsSettings } from './components/WidgetsSettings';
import { CommonSettings } from './components/CommonSettings';
import { SettingsListItem } from './types.ts';
import { ImportExport } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components/ImportExport.tsx';
import { ImportExport } from './components/ImportExport.tsx';
import { ServerSettings } from './components/ServerSettings.tsx';
import { AdminSettings } from './components/AdminSettings.tsx';
import { useMapCheckPermissions } from '@/hooks/Mapper/mapRootProvider/hooks/api';
export interface MapSettingsProps {
visible: boolean;
@@ -24,6 +27,7 @@ export const MapSettingsComp = ({ visible, onHide }: MapSettingsProps) => {
const { outCommand } = useMapRootState();
const { renderSettingItem, setUserRemoteSettings } = useMapSettings();
const isAdmin = useMapCheckPermissions([UserPermission.ADMIN_MAP]);
const refVars = useRef({ outCommand, onHide, visible });
refVars.current = { outCommand, onHide, visible };
@@ -58,7 +62,7 @@ export const MapSettingsComp = ({ visible, onHide }: MapSettingsProps) => {
header="Map user settings"
visible
draggable={false}
style={{ width: '550px' }}
className="w-[600px] h-[400px]"
onShow={handleShow}
onHide={handleHide}
>
@@ -92,6 +96,16 @@ export const MapSettingsComp = ({ visible, onHide }: MapSettingsProps) => {
<TabPanel header="Import/Export" className="h-full" headerClassName={styles.verticalTabHeader}>
<ImportExport />
</TabPanel>
<TabPanel header="Server Settings" className="h-full" headerClassName="color-warn">
<ServerSettings />
</TabPanel>
{isAdmin && (
<TabPanel header="Admin Settings" className="h-full" headerClassName="color-warn">
<AdminSettings />
</TabPanel>
)}
</TabView>
</div>
</div>

View File

@@ -0,0 +1,128 @@
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Toast } from 'primereact/toast';
import { callToastError, callToastSuccess, callToastWarn } from '@/hooks/Mapper/helpers';
import { OutCommand } from '@/hooks/Mapper/types';
import { ConfirmPopup } from 'primereact/confirmpopup';
import { useConfirmPopup } from '@/hooks/Mapper/hooks';
import { MapUserSettings, RemoteAdminSettingsResponse } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { parseMapUserSettings } from '@/hooks/Mapper/components/helpers';
import fastDeepEqual from 'fast-deep-equal';
import { useDetectSettingsChanged } from '@/hooks/Mapper/components/hooks';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
export const AdminSettings = () => {
const {
storedSettings: { getSettingsForExport },
outCommand,
} = useMapRootState();
const settingsChanged = useDetectSettingsChanged();
const [currentRemoteSettings, setCurrentRemoteSettings] = useState<MapUserSettings | null>(null);
const { cfShow, cfHide, cfVisible, cfRef } = useConfirmPopup();
const toast = useRef<Toast | null>(null);
const hasSettingsForExport = useMemo(() => !!getSettingsForExport(), [getSettingsForExport]);
const refVars = useRef({ currentRemoteSettings, getSettingsForExport });
refVars.current = { currentRemoteSettings, getSettingsForExport };
useEffect(() => {
const load = async () => {
let res: RemoteAdminSettingsResponse | undefined;
try {
res = await outCommand({ type: OutCommand.getDefaultSettings, data: null });
} catch (error) {
// do nothing
}
if (!res || res.default_settings == null) {
return;
}
setCurrentRemoteSettings(parseMapUserSettings(res.default_settings));
};
load();
}, [outCommand]);
const isDirty = useMemo(() => {
const { currentRemoteSettings, getSettingsForExport } = refVars.current;
const localCurrent = parseMapUserSettings(getSettingsForExport());
return !fastDeepEqual(currentRemoteSettings, localCurrent);
// eslint-disable-next-line
}, [settingsChanged, currentRemoteSettings]);
const handleSync = useCallback(async () => {
const settings = getSettingsForExport();
if (!settings) {
callToastWarn(toast.current, 'No settings to save');
return;
}
let response: { success: boolean } | undefined;
try {
response = await outCommand({
type: OutCommand.saveDefaultSettings,
data: { settings },
});
} catch (err) {
callToastError(toast.current, 'Something went wrong while saving settings');
console.error('ERROR: ', err);
return;
}
if (!response || !response.success) {
callToastError(toast.current, 'Settings not saved - dont not why it');
return;
}
setCurrentRemoteSettings(parseMapUserSettings(settings));
callToastSuccess(toast.current, 'Settings saved successfully');
}, [getSettingsForExport, outCommand]);
return (
<div className="w-full h-full flex flex-col gap-5">
<div className="flex flex-col gap-1">
<div>
<WdButton
// @ts-ignore
ref={cfRef}
onClick={cfShow}
icon="pi pi-save"
size="small"
severity="danger"
label="Save as Map Default"
className="py-[4px]"
disabled={!hasSettingsForExport || !isDirty}
/>
</div>
{!isDirty && <span className="text-red-500/70 text-[12px]">*Local and remote are identical.</span>}
<span className="text-stone-500 text-[12px]">
*Will save your current settings as the default for all new users of this map. This action will overwrite any
existing default settings.
</span>
</div>
<Toast ref={toast} />
<ConfirmPopup
target={cfRef.current}
visible={cfVisible}
onHide={cfHide}
message="Your settings will overwrite default. Sure?."
icon="pi pi-exclamation-triangle"
accept={handleSync}
/>
</div>
);
};

View File

@@ -7,9 +7,13 @@ import {
import { useMapSettings } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx';
import { SettingsListItem } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/types.ts';
import { useCallback } from 'react';
import { TooltipPosition, WdButton, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
import { ConfirmPopup } from 'primereact/confirmpopup';
import { useConfirmPopup } from '@/hooks/Mapper/hooks';
export const CommonSettings = () => {
const { renderSettingItem } = useMapSettings();
const { cfShow, cfHide, cfVisible, cfRef } = useConfirmPopup();
const renderSettingsList = useCallback(
(list: SettingsListItem[]) => {
@@ -18,6 +22,8 @@ export const CommonSettings = () => {
[renderSettingItem],
);
const handleResetSettings = () => {};
return (
<div className="flex flex-col h-full gap-1">
<div>
@@ -29,6 +35,33 @@ export const CommonSettings = () => {
<div className="grid grid-cols-[1fr_auto]">{renderSettingItem(MINI_MAP_PLACEMENT)}</div>
<div className="grid grid-cols-[1fr_auto]">{renderSettingItem(PINGS_PLACEMENT)}</div>
<div className="grid grid-cols-[1fr_auto]">{renderSettingItem(THEME_SETTING)}</div>
<div className="border-b-2 border-dotted border-stone-700/50 h-px my-3" />
<div className="grid grid-cols-[1fr_auto]">
<div />
<WdTooltipWrapper content="This dangerous action. And can not be undone" position={TooltipPosition.top}>
<WdButton
// @ts-ignore
ref={cfRef}
className="py-[4px]"
onClick={cfShow}
outlined
size="small"
severity="danger"
label="Reset Settings"
/>
</WdTooltipWrapper>
</div>
<ConfirmPopup
target={cfRef.current}
visible={cfVisible}
onHide={cfHide}
message="All settings for this map will be reset to default."
icon="pi pi-exclamation-triangle"
accept={handleResetSettings}
/>
</div>
);
};

View File

@@ -0,0 +1,90 @@
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback, useRef, useState } from 'react';
import { Toast } from 'primereact/toast';
import { OutCommand } from '@/hooks/Mapper/types';
import { Divider } from 'primereact/divider';
import { callToastError, callToastSuccess, callToastWarn } from '@/hooks/Mapper/helpers';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
type SaveDefaultSettingsReturn = { success: boolean; error: string };
export const DefaultSettings = () => {
const {
outCommand,
storedSettings: { getSettingsForExport },
data: { userPermissions },
} = useMapRootState();
const [loading, setLoading] = useState(false);
const toast = useRef<Toast | null>(null);
const refVars = useRef({ getSettingsForExport, outCommand });
refVars.current = { getSettingsForExport, outCommand };
const handleSaveAsDefault = useCallback(async () => {
const settings = refVars.current.getSettingsForExport();
if (!settings) {
callToastWarn(toast.current, 'No settings to save');
return;
}
setLoading(true);
let response: SaveDefaultSettingsReturn;
try {
response = await refVars.current.outCommand({
type: OutCommand.saveDefaultSettings,
data: { settings },
});
} catch (error) {
console.error('Save default settings error:', error);
callToastError(toast.current, 'Failed to save default settings');
setLoading(false);
return;
}
if (response.success) {
callToastSuccess(toast.current, 'Default settings saved successfully');
setLoading(false);
return;
}
callToastError(toast.current, response.error || 'Failed to save default settings');
setLoading(false);
}, []);
if (!userPermissions?.admin_map) {
return null;
}
return (
<>
<Divider />
<div className="w-full h-full flex flex-col gap-5">
<h3 className="text-lg font-semibold">Default Settings (Admin Only)</h3>
<div className="flex flex-col gap-1">
<div>
<WdButton
onClick={handleSaveAsDefault}
icon="pi pi-save"
size="small"
severity="danger"
label="Save as Map Default"
className="py-[4px]"
loading={loading}
disabled={loading}
/>
</div>
<span className="text-stone-500 text-[12px]">
*Will save your current settings as the default for all new users of this map. This action will overwrite
any existing default settings.
</span>
</div>
<Toast ref={toast} />
</div>
</>
);
};

View File

@@ -5,6 +5,8 @@ import { parseMapUserSettings } from '@/hooks/Mapper/components/helpers';
import { saveTextFile } from '@/hooks/Mapper/utils/saveToFile.ts';
import { SplitButton } from 'primereact/splitbutton';
import { loadTextFile } from '@/hooks/Mapper/utils';
import { applyMigrations } from '@/hooks/Mapper/mapRootProvider/migrations';
import { createDefaultStoredSettings } from '@/hooks/Mapper/mapRootProvider/helpers/createDefaultStoredSettings.ts';
export const ImportExport = () => {
const {
@@ -22,8 +24,9 @@ export const ImportExport = () => {
}
try {
// INFO: WE NOT SUPPORT MIGRATIONS FOR OLD FILES AND Clipboard
const parsed = parseMapUserSettings(text);
if (applySettings(parsed)) {
if (applySettings(applyMigrations(parsed) || createDefaultStoredSettings())) {
toast.current?.show({
severity: 'success',
summary: 'Import',
@@ -59,8 +62,9 @@ export const ImportExport = () => {
try {
const text = await loadTextFile();
// INFO: WE NOT SUPPORT MIGRATIONS FOR OLD FILES AND Clipboard
const parsed = parseMapUserSettings(text);
if (applySettings(parsed)) {
if (applySettings(applyMigrations(parsed) || createDefaultStoredSettings())) {
toast.current?.show({
severity: 'success',
summary: 'Import',

View File

@@ -0,0 +1,98 @@
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Toast } from 'primereact/toast';
import { OutCommand } from '@/hooks/Mapper/types';
import { createDefaultStoredSettings } from '@/hooks/Mapper/mapRootProvider/helpers/createDefaultStoredSettings.ts';
import { callToastSuccess } from '@/hooks/Mapper/helpers';
import { ConfirmPopup } from 'primereact/confirmpopup';
import { useConfirmPopup } from '@/hooks/Mapper/hooks';
import { RemoteAdminSettingsResponse } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { applyMigrations } from '@/hooks/Mapper/mapRootProvider/migrations';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
export const ServerSettings = () => {
const {
storedSettings: { applySettings },
outCommand,
} = useMapRootState();
const [hasSettings, setHasSettings] = useState(false);
const { cfShow, cfHide, cfVisible, cfRef } = useConfirmPopup();
const toast = useRef<Toast | null>(null);
const handleSync = useCallback(async () => {
let res: RemoteAdminSettingsResponse | undefined;
try {
res = await outCommand({ type: OutCommand.getDefaultSettings, data: null });
} catch (error) {
// do nothing
}
if (res?.default_settings == null) {
applySettings(createDefaultStoredSettings());
return;
}
try {
//INFO: INSTEAD CHECK WE WILL TRY TO APPLY MIGRATION
applySettings(applyMigrations(JSON.parse(res.default_settings)) || createDefaultStoredSettings());
callToastSuccess(toast.current, 'Settings synchronized successfully');
} catch (error) {
applySettings(createDefaultStoredSettings());
}
}, [applySettings, outCommand]);
useEffect(() => {
const load = async () => {
let res: RemoteAdminSettingsResponse | undefined;
try {
res = await outCommand({ type: OutCommand.getDefaultSettings, data: null });
} catch (error) {
// do nothing
}
if (res?.default_settings == null) {
return;
}
setHasSettings(true);
};
load();
}, [outCommand]);
return (
<div className="w-full h-full flex flex-col gap-5">
<div className="flex flex-col gap-1">
<div>
<WdButton
// @ts-ignore
ref={cfRef}
onClick={cfShow}
icon="pi pi-file-import"
size="small"
severity="warning"
label="Sync with Default Settings"
className="py-[4px]"
disabled={!hasSettings}
/>
</div>
{!hasSettings && (
<span className="text-red-500/70 text-[12px]">*Default settings was not set by map administrator.</span>
)}
<span className="text-stone-500 text-[12px]">*Will apply admin settings which set as Default for map.</span>
</div>
<Toast ref={toast} />
<ConfirmPopup
target={cfRef.current}
visible={cfVisible}
onHide={cfHide}
message="You lost your current settings. Sure?."
icon="pi pi-exclamation-triangle"
accept={handleSync}
/>
</div>
);
};

View File

@@ -2,8 +2,7 @@ import { PrettySwitchbox } from '@/hooks/Mapper/components/mapRootContent/compon
import { WIDGETS_CHECKBOXES_PROPS, WidgetsIds } from '@/hooks/Mapper/components/mapInterface/constants.tsx';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback } from 'react';
import { Button } from 'primereact/button';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
export interface WidgetsSettingsProps {}
@@ -28,9 +27,12 @@ export const WidgetsSettings = ({}: WidgetsSettingsProps) => {
/>
))}
</div>
<div className="border-b-2 border-dotted border-stone-700/50 h-px my-3" />
<div className="grid grid-cols-[1fr_auto]">
<div />
<Button className="py-[4px]" onClick={resetWidgets} outlined size="small" label="Reset Widgets"></Button>
<WdButton className="py-[4px]" onClick={resetWidgets} outlined size="small" label="Reset Widgets" />
</div>
</div>
);

View File

@@ -1,6 +1,6 @@
import { SettingsListItem, UserSettingsRemoteProps } from './types.ts';
import { InterfaceStoredSettingsProps } from '@/hooks/Mapper/mapRootProvider';
import { AvailableThemes, MiniMapPlacement, PingsPlacement } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { SettingsListItem, UserSettingsRemoteProps } from './types.ts';
export const DEFAULT_REMOTE_SETTINGS = {
[UserSettingsRemoteProps.link_signature_on_splash]: false,
@@ -51,7 +51,7 @@ export const SIGNATURES_CHECKBOXES_PROPS: SettingsListItem[] = [
export const CONNECTIONS_CHECKBOXES_PROPS: SettingsListItem[] = [
{
prop: UserSettingsRemoteProps.delete_connection_with_sigs,
label: 'Delete connections to linked signatures',
label: 'Delete connections with linked signatures',
type: 'checkbox',
},
{

View File

@@ -1,33 +1,19 @@
import { Dialog } from 'primereact/dialog';
import { Button } from 'primereact/button';
import { ConfirmPopup } from 'primereact/confirmpopup';
import { useCallback, useRef, useState } from 'react';
import { MapUserSettings } from '@/hooks/Mapper/mapRootProvider/types.ts';
import {
DEFAULT_KILLS_WIDGET_SETTINGS,
DEFAULT_ON_THE_MAP_SETTINGS,
DEFAULT_ROUTES_SETTINGS,
DEFAULT_WIDGET_LOCAL_SETTINGS,
getDefaultWidgetProps,
STORED_INTERFACE_DEFAULT_VALUES,
} from '@/hooks/Mapper/mapRootProvider/constants.ts';
import { DEFAULT_SIGNATURE_SETTINGS } from '@/hooks/Mapper/constants/signatures.ts';
import { Toast } from 'primereact/toast';
import { useConfirmPopup } from '@/hooks/Mapper/hooks';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { MapUserSettings } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { saveTextFile } from '@/hooks/Mapper/utils';
import { ConfirmPopup } from 'primereact/confirmpopup';
import { Dialog } from 'primereact/dialog';
import { Toast } from 'primereact/toast';
import { useCallback, useRef } from 'react';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
const createSettings = function <T>(lsSettings: string | null, defaultValues: T) {
return {
version: -1,
settings: lsSettings ? JSON.parse(lsSettings) : defaultValues,
};
return lsSettings ? JSON.parse(lsSettings) : defaultValues;
};
export const OldSettingsDialog = () => {
const cpRemoveBtnRef = useRef<HTMLElement>();
const [cpRemoveVisible, setCpRemoveVisible] = useState(false);
const handleShowCP = useCallback(() => setCpRemoveVisible(true), []);
const handleHideCP = useCallback(() => setCpRemoveVisible(false), []);
const { cfShow, cfHide, cfVisible, cfRef } = useConfirmPopup();
const toast = useRef<Toast | null>(null);
const {
@@ -43,16 +29,18 @@ export const OldSettingsDialog = () => {
const widgetKills = localStorage.getItem('kills:widget:settings');
const onTheMapOld = localStorage.getItem('window:onTheMap:settings');
const widgetsOld = localStorage.getItem('windows:settings:v2');
const signatures = localStorage.getItem('wanderer_system_signature_settings_v6_5');
const signatures = localStorage.getItem('wanderer_system_signature_settings_v6_6');
const out: MapUserSettings = {
killsWidget: createSettings(widgetKills, DEFAULT_KILLS_WIDGET_SETTINGS),
localWidget: createSettings(widgetLocal, DEFAULT_WIDGET_LOCAL_SETTINGS),
widgets: createSettings(widgetsOld, getDefaultWidgetProps()),
routes: createSettings(widgetRoutes, DEFAULT_ROUTES_SETTINGS),
onTheMap: createSettings(onTheMapOld, DEFAULT_ON_THE_MAP_SETTINGS),
signaturesWidget: createSettings(signatures, DEFAULT_SIGNATURE_SETTINGS),
interface: createSettings(interfaceSettings, STORED_INTERFACE_DEFAULT_VALUES),
version: 0,
migratedFromOld: false,
killsWidget: createSettings(widgetKills, {}),
localWidget: createSettings(widgetLocal, {}),
widgets: createSettings(widgetsOld, {}),
routes: createSettings(widgetRoutes, {}),
onTheMap: createSettings(onTheMapOld, {}),
signaturesWidget: createSettings(signatures, {}),
interface: createSettings(interfaceSettings, {}),
};
if (asFile) {
@@ -120,7 +108,7 @@ export const OldSettingsDialog = () => {
localStorage.removeItem('kills:widget:settings');
localStorage.removeItem('window:onTheMap:settings');
localStorage.removeItem('windows:settings:v2');
localStorage.removeItem('wanderer_system_signature_settings_v6_5');
localStorage.removeItem('wanderer_system_signature_settings_v6_6');
checkOldSettings();
}, [checkOldSettings]);
@@ -141,10 +129,10 @@ export const OldSettingsDialog = () => {
className="w-[640px] h-[400px] text-text-color min-h-0"
footer={
<div className="flex items-center justify-end">
<Button
<WdButton
// @ts-ignore
ref={cpRemoveBtnRef}
onClick={handleShowCP}
ref={cfRef}
onClick={cfShow}
icon="pi pi-exclamation-triangle"
size="small"
severity="warning"
@@ -170,7 +158,7 @@ export const OldSettingsDialog = () => {
<div className="h-[30px]"></div>
<div className="flex items-center gap-3">
<Button
<WdButton
onClick={handleExportClipboard}
icon="pi pi-copy"
size="small"
@@ -178,7 +166,7 @@ export const OldSettingsDialog = () => {
label="Export to Clipboard"
/>
<Button
<WdButton
onClick={handleExportAsFile}
icon="pi pi-download"
size="small"
@@ -192,9 +180,9 @@ export const OldSettingsDialog = () => {
</Dialog>
<ConfirmPopup
target={cpRemoveBtnRef.current}
visible={cpRemoveVisible}
onHide={handleHideCP}
target={cfRef.current}
visible={cfVisible}
onHide={cfHide}
message="After click dialog will disappear. Ready?"
icon="pi pi-exclamation-triangle"
accept={handleProceed}

View File

@@ -13,6 +13,8 @@ import { InputText } from 'primereact/inputtext';
import { IconField } from 'primereact/iconfield';
const itemTemplate = (item: CharacterTypeRaw & WithIsOwnCharacter, options: VirtualScrollerTemplateOptions) => {
const showAllyLogoPlaceholder = options.props.items?.some(x => x.alliance_id != null);
return (
<div
className={clsx(classes.CharacterRow, 'w-full box-border px-2 py-1', {
@@ -22,7 +24,15 @@ const itemTemplate = (item: CharacterTypeRaw & WithIsOwnCharacter, options: Virt
})}
style={{ height: options.props.itemSize + 'px' }}
>
<CharacterCard showCorporationLogo showAllyLogo showSystem showTicker showShip {...item} />
<CharacterCard
showCorporationLogo
showAllyLogo
showAllyLogoPlaceholder={showAllyLogoPlaceholder}
showSystem
showTicker
showShip
{...item}
/>
</div>
);
};

View File

@@ -7,6 +7,7 @@ import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
import { useMapCheckPermissions } from '@/hooks/Mapper/mapRootProvider/hooks/api';
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
import { TopSearch } from '@/hooks/Mapper/components/mapRootContent/components/TopSearch';
// import { DebugComponent } from '@/hooks/Mapper/components/mapRootContent/components/RightBar/DebugComponent.tsx';
interface RightBarProps {
@@ -48,7 +49,7 @@ export const RightBar = ({
classes.RightBarRoot,
'w-full h-full',
'text-gray-200 shadow-lg border-l border-zinc-800 border-opacity-70 bg-opacity-70 bg-neutral-900',
'flex flex-col items-center justify-between',
'flex flex-col items-center justify-between pt-1',
)}
>
<div className="flex flex-col gap-2 items-center mt-1">
@@ -65,15 +66,31 @@ export const RightBar = ({
</button>
</WdTooltipWrapper>
<WdTooltipWrapper content="Show on the map" position={TooltipPosition.left}>
<button
className="btn bg-transparent text-gray-400 hover:text-white border-transparent hover:bg-transparent py-2 h-auto min-h-auto"
type="button"
onClick={onShowOnTheMap}
>
<i className="pi pi-hashtag"></i>
</button>
</WdTooltipWrapper>
<div className="flex flex-col gap-1">
<TopSearch
customBtn={open => (
<WdTooltipWrapper content="Show on the map" position={TooltipPosition.left}>
<button
className="btn bg-transparent text-gray-400 hover:text-white border-transparent hover:bg-transparent py-2 h-auto min-h-auto"
type="button"
onClick={open}
>
<i className="pi pi-search"></i>
</button>
</WdTooltipWrapper>
)}
/>
<WdTooltipWrapper content="Show on the map" position={TooltipPosition.left}>
<button
className="btn bg-transparent text-gray-400 hover:text-white border-transparent hover:bg-transparent py-2 h-auto min-h-auto"
type="button"
onClick={onShowOnTheMap}
>
<i className="pi pi-hashtag"></i>
</button>
</WdTooltipWrapper>
</div>
</>
)}
{additionalContent}

View File

@@ -1,18 +1,21 @@
import { Dialog } from 'primereact/dialog';
import { useCallback, useEffect } from 'react';
import { OutCommand, SignatureGroup, SystemSignature, TimeStatus } from '@/hooks/Mapper/types';
import { Controller, FormProvider, useForm } from 'react-hook-form';
import {
SignatureGroupContent,
SignatureGroupSelect,
} from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components';
import { InputText } from 'primereact/inputtext';
import { SystemsSettingsProvider } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/Provider.tsx';
import { Button } from 'primereact/button';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { getWhSize } from '@/hooks/Mapper/helpers/getWhSize';
import { OutCommand, SignatureGroup, SystemSignature, TimeStatus } from '@/hooks/Mapper/types';
import { Dialog } from 'primereact/dialog';
import { InputText } from 'primereact/inputtext';
import { useCallback, useEffect } from 'react';
import { Controller, FormProvider, useForm } from 'react-hook-form';
type SystemSignaturePrepared = Omit<SystemSignature, 'linked_system'> & { linked_system: string };
type SystemSignaturePrepared = Omit<SystemSignature, 'linked_system'> & {
linked_system: string;
k162Type: string;
time_status: TimeStatus;
};
export interface MapSettingsProps {
systemId: string;
@@ -22,10 +25,7 @@ export interface MapSettingsProps {
}
export const SignatureSettings = ({ systemId, show, onHide, signatureData }: MapSettingsProps) => {
const {
outCommand,
data: { wormholes },
} = useMapRootState();
const { outCommand } = useMapRootState();
const handleShow = async () => {};
const signatureForm = useForm<Partial<SystemSignaturePrepared>>({});
@@ -52,41 +52,13 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
solar_system_target: values.linked_system,
},
});
// TODO: need fix
if (values.isEOL) {
await outCommand({
type: OutCommand.updateConnectionTimeStatus,
data: {
source: systemId,
target: values.linked_system,
value: TimeStatus.eol,
},
});
}
if (values.type) {
const whShipSize = getWhSize(wormholes, values.type);
if (whShipSize !== undefined && whShipSize !== null) {
await outCommand({
type: OutCommand.updateConnectionShipSizeType,
data: {
source: systemId,
target: values.linked_system,
value: whShipSize,
},
});
}
}
}
out = {
...out,
custom_info: JSON.stringify({
// TODO: need fix
k162Type: values.k162Type,
// TODO: need fix
isEOL: values.isEOL,
time_status: values.time_status,
}),
};
@@ -94,6 +66,10 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
out = { ...out, type: values.type };
}
if (values.temporary_name != null) {
out = { ...out, temporary_name: values.temporary_name };
}
if (signatureData.group !== SignatureGroup.Wormhole) {
out = { ...out, name: '' };
}
@@ -143,13 +119,14 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
added: [],
updated: [out],
removed: [],
deleteTimeout: 0,
},
});
signatureForm.reset();
onHide();
},
[signatureData, signatureForm, outCommand, systemId, onHide, wormholes],
[signatureData, signatureForm, outCommand, systemId, onHide],
);
useEffect(() => {
@@ -161,18 +138,17 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
const { linked_system, custom_info, ...rest } = signatureData;
let k162Type = null;
let isEOL = false;
let time_status = TimeStatus._24h;
if (custom_info) {
const customInfo = JSON.parse(custom_info);
k162Type = customInfo.k162Type;
isEOL = customInfo.isEOL;
time_status = customInfo.time_status;
}
signatureForm.reset({
linked_system: linked_system?.solar_system_id.toString() ?? undefined,
// TODO: need fix
k162Type: k162Type,
isEOL: isEOL,
time_status: time_status,
...rest,
});
}, [signatureForm, signatureData]);
@@ -181,7 +157,8 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
<Dialog
header={`Signature Edit [${signatureData?.eve_id}]`}
visible={show}
draggable={false}
draggable
resizable={false}
style={{ width: '390px' }}
onShow={handleShow}
onHide={() => {
@@ -216,8 +193,8 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
</label>
</div>
<div className="flex gap-2 justify-end">
<Button onClick={handleSave} outlined size="small" label="Save"></Button>
<div className="flex gap-2 justify-end px-[0.75rem] pb-[0.5rem]">
<WdButton type="submit" outlined size="small" label="Save" />
</div>
</div>
</form>

View File

@@ -1,24 +0,0 @@
import { InputSwitch } from 'primereact/inputswitch';
import { Controller, useFormContext } from 'react-hook-form';
import { SystemSignature } from '@/hooks/Mapper/types';
export interface SignatureEOLCheckboxProps {
name: string;
defaultValue?: boolean;
}
export const SignatureEOLCheckbox = ({ name, defaultValue = false }: SignatureEOLCheckboxProps) => {
const { control } = useFormContext<SystemSignature>();
return (
<Controller
// @ts-ignore
name={name}
control={control}
defaultValue={defaultValue}
render={({ field }) => {
return <InputSwitch className="my-1" checked={!!field.value} onChange={e => field.onChange(e.value)} />;
}}
/>
);
};

View File

@@ -3,7 +3,8 @@ import { SystemSignature } from '@/hooks/Mapper/types';
import { SignatureWormholeTypeSelect } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureWormholeTypeSelect';
import { SignatureK162TypeSelect } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureK162TypeSelect';
import { SignatureLeadsToSelect } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureLeadsToSelect';
import { SignatureEOLCheckbox } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureEOLCheckbox';
import { SignatureLifetimeSelect } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureLifetimeSelect.tsx';
import { SignatureTempName } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureTempName.tsx';
export const SignatureGroupContentWormholes = () => {
const { watch } = useFormContext<SystemSignature>();
@@ -28,9 +29,14 @@ export const SignatureGroupContentWormholes = () => {
<SignatureLeadsToSelect name="linked_system" />
</label>
<div className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
<span>Lifetime:</span>
<SignatureLifetimeSelect name="time_status" />
</div>
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
<span>EOL:</span>
<SignatureEOLCheckbox name="isEOL" />
<span>Temp. Name:</span>
<SignatureTempName />
</label>
</>
);

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