Compare commits

...

139 Commits

Author SHA1 Message Date
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
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
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
guarzo
873946a1a6 fix: removed wormhole only logic error 2025-09-05 01:43:47 +00:00
Dmitry Popov
854524a03c feat(Core): added support for jumpgates connection type 2025-03-23 21:51:26 +01:00
162 changed files with 3554 additions and 1701 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

@@ -2,6 +2,294 @@
<!-- changelog -->
## [v1.81.13](https://github.com/wanderer-industries/wanderer/compare/v1.81.12...v1.81.13) (2025-10-15)
### Bug Fixes:
* Core: Fixed system select after tab switch
## [v1.81.12](https://github.com/wanderer-industries/wanderer/compare/v1.81.11...v1.81.12) (2025-10-15)
### Bug Fixes:
* Core: Fixed map events buffering on tab switch
## [v1.81.11](https://github.com/wanderer-industries/wanderer/compare/v1.81.10...v1.81.11) (2025-10-15)
### Bug Fixes:
* Signatures: Fixed EOL indication for un-splashed and signatures list
## [v1.81.10](https://github.com/wanderer-industries/wanderer/compare/v1.81.9...v1.81.10) (2025-10-13)
### Bug Fixes:
* Signatures: Rework for lazy signatures deletion
## [v1.81.9](https://github.com/wanderer-industries/wanderer/compare/v1.81.8...v1.81.9) (2025-10-12)
### Bug Fixes:
* Signatures: Fixed issue with wrong linked signatures deletions
## [v1.81.8](https://github.com/wanderer-industries/wanderer/compare/v1.81.7...v1.81.8) (2025-10-11)
### Bug Fixes:
* Map: Fix problem with restoring settings on widgets
## [v1.81.7](https://github.com/wanderer-industries/wanderer/compare/v1.81.6...v1.81.7) (2025-10-10)
### Bug Fixes:
* Map: Fixed problem with rendering dropdown classes in signatures
## [v1.81.6](https://github.com/wanderer-industries/wanderer/compare/v1.81.5...v1.81.6) (2025-10-10)
### Bug Fixes:
* Map: Fixed problem with a lot unnecessary loads zkb data on resize map
* Map: Added ability to see focused element
* 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
* Map: Added Search tool for systems what on the map
* Map: Added migration mechanism
* Map: Remove settings some default values if migration from very old settings system
* Map: MIGRATION: support from old store settings import
* 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.
* Map: Add front-end migrations for local store settings
## [v1.81.5](https://github.com/wanderer-industries/wanderer/compare/v1.81.4...v1.81.5) (2025-10-09)
### Bug Fixes:
* Core: Update connection ship size based on linked signature type
## [v1.81.4](https://github.com/wanderer-industries/wanderer/compare/v1.81.3...v1.81.4) (2025-10-09)
### Bug Fixes:
* Core: Fixed signature to system link issues
## [v1.81.3](https://github.com/wanderer-industries/wanderer/compare/v1.81.2...v1.81.3) (2025-10-07)
### Bug Fixes:
* Core: Fixed cancel ping errors
## [v1.81.2](https://github.com/wanderer-industries/wanderer/compare/v1.81.1...v1.81.2) (2025-10-07)
### Bug Fixes:
* api dropping custom name
## [v1.81.1](https://github.com/wanderer-industries/wanderer/compare/v1.81.0...v1.81.1) (2025-10-02)
### Bug Fixes:
* Core: Fixed characters tracking updates.
## [v1.81.0](https://github.com/wanderer-industries/wanderer/compare/v1.80.0...v1.81.0) (2025-10-02)
### Features:
* core: fix pwa icons + add screen in manifest
## [v1.80.0](https://github.com/wanderer-industries/wanderer/compare/v1.79.6...v1.80.0) (2025-10-02)
### Features:
* Core: Added PWA web manifest
## [v1.79.6](https://github.com/wanderer-industries/wanderer/compare/v1.79.5...v1.79.6) (2025-10-01)
### Bug Fixes:
* Core: Fixed modals auto-save on Enter.
## [v1.79.5](https://github.com/wanderer-industries/wanderer/compare/v1.79.4...v1.79.5) (2025-10-01)
### Bug Fixes:
* Core: Fixed system details modal auto-save on Enter.
## [v1.79.4](https://github.com/wanderer-industries/wanderer/compare/v1.79.3...v1.79.4) (2025-09-30)
### Bug Fixes:
* Core: Fixed updating connection time status based on linked signature data. Fixed FR gas sites parsing.
## [v1.79.3](https://github.com/wanderer-industries/wanderer/compare/v1.79.2...v1.79.3) (2025-09-27)
### Bug Fixes:
* Core: Fixed connection passages count
## [v1.79.2](https://github.com/wanderer-industries/wanderer/compare/v1.79.1...v1.79.2) (2025-09-26)
## [v1.79.1](https://github.com/wanderer-industries/wanderer/compare/v1.79.0...v1.79.1) (2025-09-26)
## [v1.79.0](https://github.com/wanderer-industries/wanderer/compare/v1.78.1...v1.79.0) (2025-09-26)
### Features:
* Core: Updated connections EOL logic
### Bug Fixes:
* Map: Fixed eslint problems
* Map: Update lifetime design and buttons
* Map: Update wormhole lifetime UI and removed unnecessary code
## [v1.78.1](https://github.com/wanderer-industries/wanderer/compare/v1.78.0...v1.78.1) (2025-09-24)
### Bug Fixes:
* pr feedback
* removed wormhole only logic error
## [v1.78.0](https://github.com/wanderer-industries/wanderer/compare/v1.77.19...v1.78.0) (2025-09-23)
### Features:
* Core: added support for jumpgates connection type
### Bug Fixes:
* Map: Add support for Bridge. Made all tooltips left and right paddings.
## [v1.77.19](https://github.com/wanderer-industries/wanderer/compare/v1.77.18...v1.77.19) (2025-09-14)
### Bug Fixes:
* 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
## [v1.77.18](https://github.com/wanderer-industries/wanderer/compare/v1.77.17...v1.77.18) (2025-09-13)
## [v1.77.17](https://github.com/wanderer-industries/wanderer/compare/v1.77.16...v1.77.17) (2025-09-11)
### Bug Fixes:
* Updated ACL create/update APIs
## [v1.77.16](https://github.com/wanderer-industries/wanderer/compare/v1.77.15...v1.77.16) (2025-09-11)
### Bug Fixes:
* Fixed issue with ACL add members button for managers. Added WANDERER_RESTRICT_ACLS_CREATION env support.
## [v1.77.15](https://github.com/wanderer-industries/wanderer/compare/v1.77.14...v1.77.15) (2025-09-10)
### Bug Fixes:
* Map: Fix problem with unnecessary rerenders and loads routes if move/positioning widgets.
## [v1.77.14](https://github.com/wanderer-industries/wanderer/compare/v1.77.13...v1.77.14) (2025-09-08)
### Bug Fixes:
* Fixed issue with loading connection info
## [v1.77.13](https://github.com/wanderer-industries/wanderer/compare/v1.77.12...v1.77.13) (2025-09-07)

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

@@ -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,7 +1,7 @@
.vertical-tabs-container {
display: flex;
width: 100%;
min-height: 400px;
min-height: 200px;
.p-tabview {
width: 100%;

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

@@ -1,4 +1,4 @@
import { MapUserSettings, SettingsWithVersion } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { MapUserSettings, SettingsWrapper } from '@/hooks/Mapper/mapRootProvider/types.ts';
export const REQUIRED_KEYS = [
'widgets',
@@ -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

@@ -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,11 +1,10 @@
import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts';
import { SESSION_KEY } from '@/hooks/Mapper/constants.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 } from 'react';
import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect, useMemo, useRef } from 'react';
import ReactFlow, {
Background,
Edge,
@@ -33,19 +32,9 @@ import {
import { getBehaviorForTheme } from './helpers/getThemeBehavior';
import { useEdgesState, useMapHandlers, useNodesState, useUpdateNodes } from './hooks';
import { useBackgroundVars } from './hooks/useBackgroundVars';
import { OnMapAddSystemCallback, OnMapSelectionChange } from './map.types';
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 { 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

@@ -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

@@ -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

@@ -5,6 +5,16 @@
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,14 +1,6 @@
@use "sass:color";
@use '@/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);
@import '@/hooks/Mapper/components/map/styles/solar-system-node';
@keyframes move-stripes {
from {

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

@@ -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,6 +16,7 @@ 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>) => {
@@ -172,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,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

@@ -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

@@ -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

@@ -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

@@ -118,6 +118,7 @@ $homeDark30: color.adjust($homeBase, $lightness: -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

@@ -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

@@ -4,6 +4,7 @@ import {
SystemView,
TimeAgo,
TooltipPosition,
WdButton,
WdImgButton,
WdImgButtonTooltip,
} from '@/hooks/Mapper/components/ui-kit';
@@ -13,7 +14,6 @@ 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 } from 'react';
@@ -256,7 +256,7 @@ export const PingsInterface = ({ hasLeftOffset }: PingsInterfaceProps) => {
)}
></Toast>
<Button
<WdButton
icon="pi pi-bell"
severity="warning"
aria-label="Notification"

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

@@ -33,34 +33,39 @@ import { useClipboard, useHotkey } from '@/hooks/Mapper/hooks';
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { getSignatureRowClass } from '../helpers/rowStyles';
import { useSystemSignaturesData } from '../hooks/useSystemSignaturesData';
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,9 +143,9 @@ export const SystemSignaturesContent = ({
selectable
? onSelect?.(selectableSignatures[0])
: setSelectedSignatures(selectableSignatures as ExtendedSystemSignature[]);
: onSelectSignatures?.(selectableSignatures as ExtendedSystemSignature[]);
},
[onSelect, selectable, setSelectedSignatures, deletedSignatures],
[onSelect, selectable, onSelectSignatures, deletedSignatures],
);
const {
@@ -177,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)) {
@@ -213,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);
@@ -253,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">
@@ -287,7 +257,7 @@ export const SystemSignaturesContent = ({
value={filteredSignatures}
size="small"
selectionMode="multiple"
selection={selectedSignatures}
selection={selectedSignatures || []}
metaKeySelection
onSelectionChange={handleSelectSignatures}
dataKey="eve_id"
@@ -336,6 +306,8 @@ export const SystemSignaturesContent = ({
style={{ maxWidth: nameColumnWidth }}
hidden={isCompact || isMedium}
body={renderInfoColumn}
sortable
sortField="name"
/>
{showDescriptionColumn && (
<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

@@ -62,7 +62,7 @@ export const MapSettingsComp = ({ visible, onHide }: MapSettingsProps) => {
header="Map user settings"
visible
draggable={false}
style={{ width: '600px' }}
className="w-[600px] h-[400px]"
onShow={handleShow}
onHide={handleHide}
>

View File

@@ -1,7 +1,6 @@
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Toast } from 'primereact/toast';
import { Button } from 'primereact/button';
import { callToastError, callToastSuccess, callToastWarn } from '@/hooks/Mapper/helpers';
import { OutCommand } from '@/hooks/Mapper/types';
import { ConfirmPopup } from 'primereact/confirmpopup';
@@ -10,6 +9,7 @@ import { MapUserSettings, RemoteAdminSettingsResponse } from '@/hooks/Mapper/map
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 {
@@ -92,7 +92,7 @@ export const AdminSettings = () => {
<div className="w-full h-full flex flex-col gap-5">
<div className="flex flex-col gap-1">
<div>
<Button
<WdButton
// @ts-ignore
ref={cfRef}
onClick={cfShow}

View File

@@ -7,8 +7,7 @@ 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 { Button } from 'primereact/button';
import { TooltipPosition, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
import { TooltipPosition, WdButton, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
import { ConfirmPopup } from 'primereact/confirmpopup';
import { useConfirmPopup } from '@/hooks/Mapper/hooks';
@@ -42,7 +41,7 @@ export const CommonSettings = () => {
<div className="grid grid-cols-[1fr_auto]">
<div />
<WdTooltipWrapper content="This dangerous action. And can not be undone" position={TooltipPosition.top}>
<Button
<WdButton
// @ts-ignore
ref={cfRef}
className="py-[4px]"

View File

@@ -1,10 +1,10 @@
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback, useRef, useState } from 'react';
import { Toast } from 'primereact/toast';
import { Button } from 'primereact/button';
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 };
@@ -65,7 +65,7 @@ export const DefaultSettings = () => {
<div className="flex flex-col gap-1">
<div>
<Button
<WdButton
onClick={handleSaveAsDefault}
icon="pi pi-save"
size="small"

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

@@ -1,14 +1,14 @@
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Toast } from 'primereact/toast';
import { parseMapUserSettings } from '@/hooks/Mapper/components/helpers';
import { Button } from 'primereact/button';
import { OutCommand } from '@/hooks/Mapper/types';
import { createDefaultWidgetSettings } from '@/hooks/Mapper/mapRootProvider/helpers/createDefaultWidgetSettings.ts';
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 {
@@ -29,15 +29,16 @@ export const ServerSettings = () => {
}
if (res?.default_settings == null) {
applySettings(createDefaultWidgetSettings());
applySettings(createDefaultStoredSettings());
return;
}
try {
applySettings(parseMapUserSettings(res.default_settings));
//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(createDefaultWidgetSettings());
applySettings(createDefaultStoredSettings());
}
}, [applySettings, outCommand]);
@@ -64,7 +65,7 @@ export const ServerSettings = () => {
<div className="w-full h-full flex flex-col gap-5">
<div className="flex flex-col gap-1">
<div>
<Button
<WdButton
// @ts-ignore
ref={cfRef}
onClick={cfShow}

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 {}
@@ -33,7 +32,7 @@ export const WidgetsSettings = ({}: WidgetsSettingsProps) => {
<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,27 +1,15 @@
import { DEFAULT_SIGNATURE_SETTINGS } from '@/hooks/Mapper/constants/signatures.ts';
import { useConfirmPopup } from '@/hooks/Mapper/hooks';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
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 { MapUserSettings } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { saveTextFile } from '@/hooks/Mapper/utils';
import { Button } from 'primereact/button';
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 = () => {
@@ -44,13 +32,15 @@ export const OldSettingsDialog = () => {
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) {
@@ -139,7 +129,7 @@ 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={cfRef}
onClick={cfShow}
@@ -168,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"
@@ -176,7 +166,7 @@ export const OldSettingsDialog = () => {
label="Export to Clipboard"
/>
<Button
<WdButton
onClick={handleExportAsFile}
icon="pi pi-download"
size="small"

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,
}),
};
@@ -147,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(() => {
@@ -165,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]);
@@ -185,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={() => {
@@ -220,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,7 @@ 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 = () => {
@@ -29,10 +29,10 @@ export const SignatureGroupContentWormholes = () => {
<SignatureLeadsToSelect name="linked_system" />
</label>
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
<span>EOL:</span>
<SignatureEOLCheckbox name="isEOL" />
</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>Temp. Name:</span>

View File

@@ -0,0 +1,27 @@
import { Controller, useFormContext } from 'react-hook-form';
import { SystemSignature } from '@/hooks/Mapper/types';
import { WdLifetimeSelector } from '@/hooks/Mapper/components/ui-kit/WdLifetimeSelector.tsx';
export interface SignatureEOLCheckboxProps {
name: string;
defaultValue?: boolean;
}
export const SignatureLifetimeSelect = ({ name, defaultValue = false }: SignatureEOLCheckboxProps) => {
const { control } = useFormContext<SystemSignature>();
return (
<div className="my-1">
<Controller
// @ts-ignore
name={name}
control={control}
defaultValue={defaultValue}
render={({ field }) => {
// @ts-ignore
return <WdLifetimeSelector lifetime={field.value} onChangeLifetime={e => field.onChange(e)} />;
}}
/>
</div>
);
};

View File

@@ -1,3 +1,4 @@
export * from './SignatureGroupSelect';
export * from './SignatureGroupContent';
export * from './SignatureK162TypeSelect';
export * from './SignatureLifetimeSelect';

View File

@@ -0,0 +1,170 @@
@use "sass:color";
@use '@/hooks/Mapper/components/map/styles/eve-common-variables';
@import '@/hooks/Mapper/components/map/styles/solar-system-node';
:root {
--rf-has-user-characters: #ffc75d;
}
.Sidebar {
:global {
.p-sidebar-header {
padding-left: 14px;
padding-right: 14px;
}
.p-sidebar-content {
padding-left: 0px;
padding-right: 0px;
}
}
}
.Content {
position: relative;
overflow: hidden;
&.Pochven,
&.Mataria,
&.Amarria,
&.Gallente,
&.Caldaria {
&::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-size: cover;
background-position: 50% 50%;
z-index: -1;
background-repeat: no-repeat;
border-radius: 3px;
}
}
&.Mataria {
&::after {
background-image: url('/images/mataria-180.png');
opacity: 0.6;
//background-position-x: 52px;
//background-position-y: -83px;
background-position-x: 331px;
background-position-y: -77px;
transform: scale(-1);
}
}
&.Caldaria {
&::after {
background-image: url('/images/caldaria-180.png');
opacity: 0.6;
//background-position-x: 41px;
//background-position-y: -45px;
background-position-x: 360px;
background-position-y: -16px;
background-size: 30%;
transform: rotate(180deg);
}
}
&.Amarria {
&::after {
opacity: 0.45;
background-image: url('/images/amarr-small.png');
//background-position-x: 23px;
//background-position-y: -74px;
background-position-x: -1px;
background-position-y: -75px;
background-origin: border-box;
background-size: auto;
transform: scale(1, -1);
}
}
&.Gallente {
&::after {
opacity: 0.5;
background-image: url('/images/gallente-180.png');
//background-position-x: 1px;
//background-position-y: -55px;
background-position-x: 294px;
background-position-y: -53px;
transform: scale(-1);
}
}
&.Pochven {
&::after {
opacity: 0.8;
background-image: url('/images/pochven.webp');
background-position-x: 0;
background-position-y: -88px;
}
}
&.selected {
border-color: $pastel-pink;
box-shadow: 0 0 10px #9a1af1c2;
}
&.rally {
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: -1;
border-color: $neon-color-1;
background: repeating-linear-gradient(
45deg,
$neon-color-3 0px,
$neon-color-3 8px,
transparent 8px,
transparent 21px
);
background-size: 30px 30px;
animation: move-stripes 3s linear infinite;
}
}
&.eve-system-status-home {
background-image: linear-gradient(45deg, var(--eve-solar-system-status-color-background), transparent);
&.selected {
border-color: var(--eve-solar-system-status-color-home);
}
}
&.eve-system-status-friendly {
background-image: linear-gradient(275deg, var(--eve-solar-system-status-friendly-dark30), transparent);
&.selected {
border-color: var(--eve-solar-system-status-color-friendly-dark5);
}
}
&.eve-system-status-lookingFor {
background-image: linear-gradient(275deg, #45ff8f2f, #457fff2f);
&.selected {
border-color: $pastel-pink;
}
}
&.eve-system-status-warning {
background-image: linear-gradient(275deg, var(--eve-solar-system-status-warning), transparent);
}
&.eve-system-status-dangerous {
background-image: linear-gradient(275deg, var(--eve-solar-system-status-dangerous), transparent);
}
&.eve-system-status-target {
background-image: linear-gradient(275deg, var(--eve-solar-system-status-target), transparent);
}
}

View File

@@ -0,0 +1,347 @@
import classes from './TopSearch.module.scss';
import { Sidebar } from 'primereact/sidebar';
import React, { useCallback, useMemo, useRef, useState } from 'react';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
import clsx from 'clsx';
import { Commands, SolarSystemRawType, SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
import {
SystemViewStandalone,
TooltipPosition,
WdImageSize,
WdImgButton,
WdTooltipWrapper,
WHClassView,
WHEffectView,
} from '@/hooks/Mapper/components/ui-kit';
import { InputText } from 'primereact/inputtext';
import { IconField } from 'primereact/iconfield';
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
import { sortWHClasses } from '@/hooks/Mapper/helpers';
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
import { STATUS_CLASSES } from '@/hooks/Mapper/components/map/constants.ts';
import { REGIONS_MAP, SPACE_TO_CLASS } from '@/hooks/Mapper/constants.ts';
import { emitMapEvent } from '@/hooks/Mapper/events';
import { LocalCounter } from '@/hooks/Mapper/components/map/components/LocalCounter';
import { getLocalCharacters } from '@/hooks/Mapper/components/hooks/useLocalCounter.ts';
import { PrimeIcons } from 'primereact/api';
type CompiledSystem = {
dynamic: SolarSystemRawType;
static: SolarSystemStaticInfoRaw | undefined;
};
const useItemTemplate = () => {
const {
data: { wormholesData, characters, userCharacters, hubs },
} = useMapRootState();
return useCallback(
(item: CompiledSystem, options: VirtualScrollerTemplateOptions) => {
if (!item.static) {
return null;
}
const {
security,
system_class,
class_title,
effect_power,
region_name,
solar_system_name,
solar_system_id,
effect_name,
statics,
region_id,
} = item.static;
const onlineCharactersInSystem = characters.filter(
c => c.location?.solar_system_id === solar_system_id && c.online,
);
const hasOnlineUserCharacters = onlineCharactersInSystem.some(x => userCharacters.includes(x.eve_id));
const onlineCharacters = getLocalCharacters({ charactersInSystem: onlineCharactersInSystem, userCharacters });
const offlineCharactersInSystem = characters.filter(
c => c.location?.solar_system_id === solar_system_id && !c.online,
);
const hasOfflineUserCharacters = offlineCharactersInSystem.some(x => userCharacters.includes(x.eve_id));
const offlineCharacters = getLocalCharacters({ charactersInSystem: offlineCharactersInSystem, userCharacters });
const handleSelect = () => {
emitMapEvent({
name: Commands.centerSystem,
data: solar_system_id.toString(),
});
};
const sortedStatics = sortWHClasses(wormholesData, statics);
const isWH = isWormholeSpace(system_class);
const regionClass = SPACE_TO_CLASS[REGIONS_MAP[region_id]] || null;
const showTempName = item.dynamic.temporary_name != null;
const showCustomName = item.dynamic.name != null && item.dynamic.name !== solar_system_name;
return (
<div
className={clsx(
'w-full box-border px-3.5 py-1 h-[48px] cursor-pointer',
'bg-transparent hover:bg-stone-800/30 transition-all !duration-250 ease-in-out',
{
'surface-hover': options.odd,
['border-b border-gray-600 border-opacity-20']: !options.last,
},
classes.Content,
regionClass && classes[regionClass],
item.dynamic.status !== undefined && classes[STATUS_CLASSES[item.dynamic.status]],
)}
onClick={handleSelect}
>
<div className={clsx('w-full')}>
<div className={clsx('grid grid-cols-[1fr_auto] gap-1.5 w-full')}>
<div className="flex [&>*]:!text-[13px] gap-1.5">
<SystemViewStandalone
className="!text-[13px]"
security={security}
system_class={system_class}
solar_system_id={parseInt(item.dynamic.id)}
class_title={class_title}
solar_system_name={`${solar_system_name}`}
region_name={region_name}
nameClassName="font-semibold"
/>
{(showTempName || showCustomName) && (
<div className="grid grid-cols-[auto_1fr] gap-1.5 text-stone-400 text-[12px]">
{showTempName && (
<span className="overflow-hidden text-ellipsis whitespace-nowrap">
{item.dynamic.temporary_name}
</span>
)}
{showCustomName && (
<span className="overflow-hidden text-ellipsis whitespace-nowrap">{item.dynamic.name}</span>
)}
</div>
)}
{effect_name && isWH && (
<WHEffectView
effectName={effect_name}
effectPower={effect_power}
className={classes.SearchItemEffect}
/>
)}
</div>
<div>
{isWH && (
<div className="flex gap-1 grow justify-between !text-[13px]">
<div></div>
<div className="flex gap-1">
{sortedStatics.map(x => (
<WHClassView key={x} whClassName={x} />
))}
</div>
</div>
)}
</div>
</div>
<div className="flex gap-1.5 text-[13px] pl-[2px] items-center h-[20px]">
<LocalCounter
disableInteractive
className="[&_span]:!text-[12px] [&_i]:!text-[13px]"
hasUserCharacters={hasOnlineUserCharacters}
localCounterCharacters={onlineCharacters}
/>
<LocalCounter
disableInteractive
className="[&_span]:!text-[12px] [&_i]:!text-[13px] text-stone-[400]"
contentClassName={clsx('!text-stone-500', { ['!text-yellow-600']: hasOfflineUserCharacters })}
hasUserCharacters={hasOfflineUserCharacters}
localCounterCharacters={offlineCharacters}
/>
{item.dynamic.locked && <i className={clsx(PrimeIcons.LOCK, 'text-[11px] relative top-[1px]')} />}
{hubs.includes(solar_system_id.toString()) && (
<i className={clsx(PrimeIcons.MAP_MARKER, 'text-[11px] relative top-[1px]')} />
)}
{item.dynamic.comments_count != null && item.dynamic.comments_count > 0 && (
<WdTooltipWrapper
position={TooltipPosition.top}
content={`[${item.dynamic.comments_count}] Comments in System - click to system to see it in Comments Widget`}
>
<i className={clsx(PrimeIcons.COMMENT, 'text-[11px] relative top-[1px]')} />
</WdTooltipWrapper>
)}
{item.dynamic.description != null && item.dynamic.description !== '' && (
<WdTooltipWrapper
position={TooltipPosition.top}
content={`System have description - click to system to see it in Info Widget`}
>
<i
className={clsx(
'pi hero-chat-bubble-bottom-center-text w-[14px] h-[14px]',
'text-[8px] relative top-[-1px]',
)}
/>
</WdTooltipWrapper>
)}
{/*kek*/}
</div>
</div>
</div>
);
},
[characters, hubs, userCharacters, wormholesData],
);
};
export interface TopSearchSidebarProps {
show: boolean;
onHide: () => void;
}
export const TopSearchSidebar = ({ show, onHide }: TopSearchSidebarProps) => {
const [searchVal, setSearchVal] = useState('');
// eslint-disable-next-line
const inputRef = useRef<any>();
const {
data: { systems },
} = useMapRootState();
const itemTemplate = useItemTemplate();
const systemsCompiled = useMemo<CompiledSystem[]>(() => {
return systems.map(x => ({
dynamic: x,
static: getSystemStaticInfo(x.id),
}));
}, [systems]);
const onShow = useCallback(() => {
inputRef.current?.focus();
}, []);
const filtered = useMemo(() => {
let out = systemsCompiled;
out = out.sort((a, b) => {
// 1. Status > 0 — always on top and ASC
if (a.dynamic.status > 0 && b.dynamic.status > 0) return a.dynamic.status - b.dynamic.status;
if (a.dynamic.status > 0) return -1;
if (b.dynamic.status > 0) return 1;
// 2. IF status = 0, J priority
const aStartsWithJ = a.dynamic.name?.startsWith('J') ?? false;
const bStartsWithJ = b.dynamic.name?.startsWith('J') ?? false;
if (aStartsWithJ && !bStartsWithJ) return -1;
if (!aStartsWithJ && bStartsWithJ) return 1;
// 3. IF both starts with J or not - sort by name
const nameA = a.dynamic.name ?? '';
const nameB = b.dynamic.name ?? '';
return nameA.localeCompare(nameB);
});
const normalized = searchVal.toLowerCase();
if (searchVal !== '') {
out = out.filter(x => {
if (x.static?.solar_system_name.toLowerCase().includes(normalized)) {
return true;
}
if (x.dynamic.name?.toLowerCase().includes(normalized)) {
return true;
}
if (x.dynamic.temporary_name?.toLowerCase().includes(normalized)) {
return true;
}
return false;
});
}
return out;
}, [searchVal, systemsCompiled]);
return (
<Sidebar
className={clsx(classes.Sidebar, 'bg-neutral-900 !p-[0px] w-[500px]')}
visible={show}
position="right"
onShow={onShow}
onHide={onHide}
modal={false}
header={`Search [${filtered.length}]`}
icons={<></>}
>
<div className={clsx('grid grid-rows-[auto_1fr] gap-y-[8px] h-full')}>
<div className={'flex justify-between items-center gap-2 px-2 pt-1'}>
<IconField className="w-full">
{searchVal.length > 0 && (
<WdImgButton
className="pi pi-trash"
textSize={WdImageSize.large}
tooltip={{
content: 'Clear',
className: 'pi p-input-icon',
position: TooltipPosition.top,
}}
onClick={() => setSearchVal('')}
/>
)}
<InputText
id="label"
className="w-full"
aria-describedby="label"
ref={inputRef}
autoComplete="off"
value={searchVal}
placeholder="Type To Search"
onChange={e => setSearchVal(e.target.value)}
/>
</IconField>
</div>
<VirtualScroller
items={filtered}
itemSize={48}
itemTemplate={itemTemplate}
className={clsx(
classes.VirtualScroller,
'w-full h-full overflow-x-hidden overflow-y-auto custom-scrollbar select-none',
'[&>div]:w-full',
)}
autoSize={false}
/>
</div>
</Sidebar>
);
};
interface TopSearchProps {
customBtn?: (open: () => void) => React.ReactNode;
}
export const TopSearch = ({ customBtn }: TopSearchProps) => {
const [openAddSystem, setOpenAddSystem] = useState<boolean>(false);
return (
<>
{customBtn != null && customBtn(() => setOpenAddSystem(true))}
{customBtn == null && (
<button
className="btn bg-transparent text-gray-400 hover:text-white border-transparent hover:bg-transparent px-2 relative left-1"
type="button"
onClick={() => setOpenAddSystem(true)}
>
<i className="pi pi-search text-lg"></i>
</button>
)}
<TopSearchSidebar show={openAddSystem} onHide={() => setOpenAddSystem(false)} />
</>
);
};

View File

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

View File

@@ -1,13 +1,13 @@
import { Column } from 'primereact/column';
import { CharacterCard } from '@/hooks/Mapper/components/ui-kit';
import { DataTable } from 'primereact/datatable';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { TrackingCharacter } from '@/hooks/Mapper/types';
import { useTracking } from '@/hooks/Mapper/components/mapRootContent/components/TrackingDialog/TrackingProvider.tsx';
export const TrackingCharactersList = () => {
const [selected, setSelected] = useState<TrackingCharacter[]>([]);
const { trackingCharacters, updateTracking } = useTracking();
const { trackingCharacters, main, following, updateTracking } = useTracking();
const refVars = useRef({ trackingCharacters });
refVars.current = { trackingCharacters };
@@ -20,9 +20,31 @@ export const TrackingCharactersList = () => {
[updateTracking],
);
const items = useMemo(() => {
let out = trackingCharacters;
out = out.sort((a, b) => {
const aId = a.character.eve_id;
const bId = b.character.eve_id;
// 1. main always first
if (aId === main && bId !== main) return -1;
if (bId === main && aId !== main) return 1;
// 2. following after main
if (aId === following && bId !== following) return -1;
if (bId === following && aId !== following) return 1;
// 3. sort by name
return a.character.name.localeCompare(b.character.name);
});
return out;
}, [trackingCharacters, main, following]);
return (
<DataTable
value={trackingCharacters}
value={items}
size="small"
selectionMode={null}
selection={selected}

View File

@@ -1,36 +1,36 @@
import { Map, MAP_ROOT_ID } from '@/hooks/Mapper/components/map/Map.tsx';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { CommandSelectSystems, OutCommand, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types';
import { MapRootData, useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { OnMapAddSystemCallback, OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts';
import isEqual from 'lodash.isequal';
import { ContextMenuSystem, useContextMenuSystemHandlers } from '@/hooks/Mapper/components/contexts';
import { Map, MAP_ROOT_ID } from '@/hooks/Mapper/components/map/Map.tsx';
import { OnMapAddSystemCallback, OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts';
import {
SystemCustomLabelDialog,
SystemLinkSignatureDialog,
SystemSettingsDialog,
} from '@/hooks/Mapper/components/mapInterface/components';
import { Connections } from '@/hooks/Mapper/components/mapRootContent/components/Connections';
import { ContextMenuSystemMultiple, useContextMenuSystemMultipleHandlers } from '../contexts/ContextMenuSystemMultiple';
import { getSystemById } from '@/hooks/Mapper/helpers';
import { MapRootData, useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { CommandSelectSystems, OutCommand, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types';
import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
import { Node, useReactFlow, XYPosition } from 'reactflow';
import isEqual from 'lodash.isequal';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Node, useReactFlow, Viewport, XYPosition } from 'reactflow';
import { ContextMenuSystemMultiple, useContextMenuSystemMultipleHandlers } from '../contexts/ContextMenuSystemMultiple';
import { useCommandsSystems } from '@/hooks/Mapper/mapRootProvider/hooks/api';
import { emitMapEvent, useMapEventListener } from '@/hooks/Mapper/events';
import { useCommandsSystems } from '@/hooks/Mapper/mapRootProvider/hooks/api';
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
import { useCommonMapEventProcessor } from '@/hooks/Mapper/components/mapWrapper/hooks/useCommonMapEventProcessor.ts';
import {
AddSystemDialog,
SearchOnSubmitCallback,
} from '@/hooks/Mapper/components/mapInterface/components/AddSystemDialog';
import { useHotkey } from '../../hooks/useHotkey';
import { PingType } from '@/hooks/Mapper/types/ping.ts';
import { SystemPingDialog } from '@/hooks/Mapper/components/mapInterface/components/SystemPingDialog';
import { MiniMapPlacement } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { useCommonMapEventProcessor } from '@/hooks/Mapper/components/mapWrapper/hooks/useCommonMapEventProcessor.ts';
import { MINIMAP_PLACEMENT_MAP } from '@/hooks/Mapper/constants.ts';
import { MiniMapPlacement } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { PingType } from '@/hooks/Mapper/types/ping.ts';
import type { PanelPosition } from '@reactflow/core';
import { useHotkey } from '../../hooks/useHotkey';
import { MINI_MAP_PLACEMENT_OFFSETS } from './constants.ts';
// TODO: INFO - this component needs for abstract work with Map instance
@@ -48,7 +48,7 @@ export const MapWrapper = () => {
linkSignatureToSystem,
systemSignatures,
},
storedSettings: { interfaceSettings, settingsLocal },
storedSettings: { interfaceSettings, settingsLocal, mapSettings, mapSettingsUpdate },
} = useMapRootState();
const {
@@ -83,8 +83,17 @@ export const MapWrapper = () => {
systems,
systemSignatures,
deleteSystems,
mapSettingsUpdate,
});
ref.current = { selectedConnections, selectedSystems, systemContextProps, systems, systemSignatures, deleteSystems };
ref.current = {
selectedConnections,
selectedSystems,
systemContextProps,
systems,
systemSignatures,
deleteSystems,
mapSettingsUpdate,
};
useMapEventListener(event => {
runCommand(event);
@@ -97,7 +106,7 @@ export const MapWrapper = () => {
runCommand({
name: Commands.selectSystems,
data: { systems: selectedSystems } as CommandSelectSystems,
data: { systems: selectedSystems, delay: 200 } as CommandSelectSystems,
});
}
});
@@ -121,6 +130,10 @@ export const MapWrapper = () => {
[update],
);
const handleChangeViewport = useCallback((viewport: Viewport) => {
ref.current.mapSettingsUpdate({ viewport });
}, []);
const handleCommand: OutCommandHandler = useCallback(
event => {
switch (event.type) {
@@ -259,6 +272,7 @@ export const MapWrapper = () => {
onConnectionInfoClick={handleConnectionDbClick}
onSystemContextMenu={handleSystemContextMenu}
onSelectionContextMenu={handleSystemMultipleContext}
onChangeViewport={handleChangeViewport}
minimapClasses={minimapClasses}
isShowMinimap={showMinimap}
showKSpaceBG={isShowKSpace}
@@ -270,6 +284,7 @@ export const MapWrapper = () => {
onAddSystem={onAddSystem}
minimapPlacement={minimapPosition}
localShowShipName={settingsLocal.showShipName}
defaultViewport={mapSettings.viewport}
/>
{openSettings != null && (

View File

@@ -14,6 +14,7 @@ export type SystemViewStandaloneStatic = Pick<
export type SystemViewStandaloneProps = {
hideRegion?: boolean;
customName?: string;
nameClassName?: string;
compact?: boolean;
onContextMenu?(e: MouseEvent, systemId: string): void;
} & WithClassName &
@@ -23,6 +24,7 @@ export type SystemViewStandaloneProps = {
export const SystemViewStandalone = ({
className,
nameClassName,
hideRegion,
customName,
class_title,
@@ -57,10 +59,14 @@ export const SystemViewStandalone = ({
>
<span className={clsx(classTitleColor)}>{class_title}</span>
<span
className={clsx('text-gray-200 whitespace-nowrap', {
['overflow-hidden text-ellipsis']: compact,
[classes.CompactName]: compact,
})}
className={clsx(
'text-gray-200 whitespace-nowrap',
{
['overflow-hidden text-ellipsis']: compact,
[classes.CompactName]: compact,
},
nameClassName,
)}
>
{customName ?? solar_system_name}
</span>

View File

@@ -1,10 +1,10 @@
import classes from './WHClassView.module.scss';
import { Tooltip } from 'primereact/tooltip';
import clsx from 'clsx';
import { InfoDrawer } from '@/hooks/Mapper/components/ui-kit/InfoDrawer';
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 { TooltipPosition, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
const prepareMass = (mass: number) => {
if (mass === 0) {
@@ -45,19 +45,28 @@ export const WHClassView = ({
const whClass = useMemo(() => WORMHOLES_ADDITIONAL_INFO[whData.dest], [whData.dest]);
const whClassStyle = WORMHOLE_CLASS_STYLES[whClass?.wormholeClassID] ?? '';
const uid = useMemo(() => new Date().getTime().toString(), []);
const content = (
<div
className={clsx(classes.WHClassViewContent, { [classes.NoOffset]: noOffset }, 'wh-name select-none cursor-help')}
>
{!hideWhClassName && <span className={clsx({ [whClassStyle]: highlightName })}>{whClassName}</span>}
{!hideWhClass && whClass && (
<span className={clsx(classes.WHClassName, whClassStyle, classNameWh)}>
{useShortTitle ? whClass.shortTitle : whClass.shortName}
</span>
)}
</div>
);
if (hideTooltip) {
return <div className={clsx(classes.WHClassViewRoot, className)}>{content}</div>;
}
return (
<div className={clsx(classes.WHClassViewRoot, className)}>
{!hideTooltip && (
<Tooltip
target={`.wh-name${whClassName}${uid}`}
position="right"
mouseTrack
mouseTrackLeft={20}
mouseTrackTop={30}
className="border border-green-300 rounded border-opacity-10 bg-stone-900 bg-opacity-90 "
>
<WdTooltipWrapper
position={TooltipPosition.bottom}
content={
<div className="flex gap-3">
<div className="flex flex-col gap-1">
<InfoDrawer title="Total mass">{prepareMass(whData.total_mass)}</InfoDrawer>
@@ -68,24 +77,10 @@ export const WHClassView = ({
<InfoDrawer title="Mass regen">{prepareMass(whData.mass_regen)}</InfoDrawer>
</div>
</div>
</Tooltip>
)}
<div
className={clsx(
classes.WHClassViewContent,
{ [classes.NoOffset]: noOffset },
'wh-name select-none cursor-help',
`wh-name${whClassName}${uid}`,
)}
}
>
{!hideWhClassName && <span className={clsx({ [whClassStyle]: highlightName })}>{whClassName}</span>}
{!hideWhClass && whClass && (
<span className={clsx(classes.WHClassName, whClassStyle, classNameWh)}>
{useShortTitle ? whClass.shortTitle : whClass.shortName}
</span>
)}
</div>
{content}
</WdTooltipWrapper>
</div>
);
};

View File

@@ -0,0 +1,7 @@
// eslint-disable-next-line no-restricted-imports
import { Button, ButtonProps } from 'primereact/button';
export const WdButton = ({ type = 'button', ...props }: ButtonProps) => {
// eslint-disable-next-line react/forbid-elements
return <Button {...props} type={type} />;
};

View File

@@ -0,0 +1,86 @@
import { WdButton } from '@/hooks/Mapper/components/ui-kit/WdButton.tsx';
import { TimeStatus } from '@/hooks/Mapper/types';
import clsx from 'clsx';
import { BUILT_IN_TOOLTIP_OPTIONS } from './constants.ts';
const LIFE_TIME = [
{
id: TimeStatus._1h,
label: '1H',
className: 'bg-purple-400 hover:!bg-purple-400',
inactiveClassName: 'bg-purple-400/30',
description: 'Less than one 1 hours remaining',
},
{
id: TimeStatus._4h,
label: '4H',
className: 'bg-purple-300 hover:!bg-purple-300',
inactiveClassName: 'bg-purple-300/30',
description: 'Less than one 4 hours remaining',
},
{
id: TimeStatus._4h30m,
label: '4.5H',
className: 'bg-indigo-300 hover:!bg-indigo-300',
inactiveClassName: 'bg-indigo-300/30',
description: 'Less than one 4.5 hours remaining. All small holes have such lifetime.',
},
{
id: TimeStatus._16h,
label: '16H',
className: 'bg-orange-300 hover:!bg-orange-300',
inactiveClassName: 'bg-orange-400/30',
description: 'Less than one 16 hours remaining',
},
{
id: TimeStatus._24h,
label: '24H',
className: 'bg-orange-300 hover:!bg-orange-300',
inactiveClassName: 'bg-orange-400/30',
description: 'Less than one 24 hours remaining',
},
{
id: TimeStatus._48h,
label: '48H',
className: 'bg-orange-300 hover:!bg-orange-300',
inactiveClassName: 'bg-orange-400/30',
description: 'Less than one 24 hours remaining. Related only with C6. B041, B520, U319, C391.',
},
];
export interface WdLifetimeSelectorProps {
lifetime?: TimeStatus;
onChangeLifetime(lifetime: TimeStatus): void;
className?: string;
}
export const WdLifetimeSelector = ({
lifetime = TimeStatus._24h,
onChangeLifetime,
className,
}: WdLifetimeSelectorProps) => {
return (
<form>
<div className={clsx('grid grid-cols-[1fr_1fr_1fr_1fr_1fr_1fr] gap-1', className)}>
{LIFE_TIME.map(x => (
<WdButton
key={x.id}
outlined={false}
value={x.label}
tooltip={x.description}
tooltipOptions={BUILT_IN_TOOLTIP_OPTIONS}
size="small"
className={clsx(
`py-[1px] justify-center min-w-auto w-auto border-0 text-[12px] font-bold leading-[20px]`,
{ [x.inactiveClassName]: lifetime !== x.id },
x.className,
)}
onClick={() => onChangeLifetime(x.id)}
>
{x.label}
</WdButton>
))}
</div>
</form>
);
};

View File

@@ -18,6 +18,7 @@ export interface TooltipProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
content: (() => React.ReactNode) | React.ReactNode;
targetSelector?: string;
interactive?: boolean;
smallPaddings?: boolean;
}
export interface OffsetPosition {
@@ -47,6 +48,7 @@ export const WdTooltip = forwardRef(
position: tPosition = TooltipPosition.default,
offset = 5,
interactive = false,
smallPaddings = false,
className,
...restProps
}: TooltipProps,
@@ -264,10 +266,14 @@ export const WdTooltip = forwardRef(
ref={tooltipRef}
className={clsx(
classes.tooltip,
interactive ? 'pointer-events-auto' : 'pointer-events-none',
'absolute px-1 py-1',
'absolute px-2 py-1',
'border rounded-sm border-green-300 border-opacity-10 bg-stone-900 bg-opacity-90',
pos == null && 'invisible',
{
'pointer-events-auto': interactive,
'pointer-events-none': !interactive,
invisible: pos == null,
'!px-1': smallPaddings,
},
className,
)}
style={{

View File

@@ -8,13 +8,26 @@ export type WdTooltipWrapperProps = {
content?: (() => ReactNode) | ReactNode;
size?: TooltipSize;
interactive?: boolean;
smallPaddings?: boolean;
tooltipClassName?: string;
} & Omit<HTMLProps<HTMLDivElement>, 'content' | 'size'> &
Omit<TooltipProps, 'content'>;
export const WdTooltipWrapper = forwardRef<WdTooltipHandlers, WdTooltipWrapperProps>(
(
{ className, children, content, offset, position, targetSelector, interactive, size, tooltipClassName, ...props },
{
className,
children,
content,
offset,
position,
targetSelector,
interactive,
smallPaddings,
size,
tooltipClassName,
...props
},
forwardedRef,
) => {
const suffix = useMemo(() => Math.random().toString(36).slice(2, 7), []);
@@ -31,6 +44,7 @@ export const WdTooltipWrapper = forwardRef<WdTooltipHandlers, WdTooltipWrapperPr
position={position}
content={content}
interactive={interactive}
smallPaddings={smallPaddings}
targetSelector={finalTargetSelector}
className={clsx(size && sizeClass(size), tooltipClassName)}
/>

View File

@@ -0,0 +1,6 @@
export const BUILT_IN_TOOLTIP_OPTIONS = {
mouseTrack: true,
mouseTrackLeft: 10,
className:
'rounded-[3px] bg-stone-900/90 px-1 py-1 [&_.p-tooltip-text]:!text-stone-300 text-[13px] [&_.p-tooltip-text]:!p-1',
};

View File

@@ -21,3 +21,5 @@ export * from './LoadingWrapper';
export * from './WdMenuItem';
export * from './MenuItemWithInfo';
export * from './MarkdownTextViewer.tsx';
export * from './WdButton.tsx';
export * from './constants.ts';

View File

@@ -7,6 +7,8 @@ export enum SESSION_KEY {
routes = 'routes',
}
export const SYSTEM_FOCUSED_LIFETIME = 10000;
export const GRADIENT_MENU_ACTIVE_CLASSES = 'bg-gradient-to-br from-transparent/10 to-fuchsia-300/10';
export enum Regions {
@@ -151,3 +153,11 @@ export const MINIMAP_PLACEMENT_MAP = {
[PingsPlacement.rightBottom]: 'bottom-right',
[PingsPlacement.leftBottom]: 'bottom-left',
};
export const SPACE_TO_CLASS: Record<string, string> = {
[Spaces.Caldari]: 'Caldaria',
[Spaces.Matar]: 'Mataria',
[Spaces.Amarr]: 'Amarria',
[Spaces.Gallente]: 'Gallente',
[Spaces.Pochven]: 'Pochven',
};

View File

@@ -5,3 +5,4 @@ export * from './useHotkey';
export * from './usePageVisibility';
export * from './useSkipContextMenu';
export * from './useThrottle';
export * from './useStableValue';

View File

@@ -28,14 +28,17 @@ export const useEventBuffer = <T>(handler: UseEventBufferHandler<T>) => {
eventTickRef.current = eventTick;
// @ts-ignore
const handleEvent = useCallback(event => {
if (!eventTickRef.current) {
return;
}
const handleEvent = useCallback(
event => {
if (!eventTickRef.current) {
return;
}
eventsBufferRef.current.push(event);
eventTickRef.current();
}, []);
eventsBufferRef.current.push(event);
eventTickRef.current();
},
[eventTickRef.current],
);
return { handleEvent };
};

View File

@@ -0,0 +1,8 @@
import { useRef } from 'react';
import fastDeepEuqal from 'fast-deep-equal';
export const useStableValue = <T>(value: T): T => {
const ref = useRef(value);
if (!fastDeepEuqal(ref.current, value)) ref.current = value;
return ref.current;
};

View File

@@ -23,12 +23,14 @@ import {
InterfaceStoredSettings,
KillsWidgetSettings,
LocalWidgetSettings,
MapSettings,
MapUserSettings,
OnTheMapSettingsType,
RoutesType,
} from '@/hooks/Mapper/mapRootProvider/types.ts';
import {
DEFAULT_KILLS_WIDGET_SETTINGS,
DEFAULT_MAP_SETTINGS,
DEFAULT_ON_THE_MAP_SETTINGS,
DEFAULT_ROUTES_SETTINGS,
DEFAULT_WIDGET_LOCAL_SETTINGS,
@@ -127,6 +129,8 @@ export interface MapRootContextProps {
settingsOnTheMapUpdate: Dispatch<SetStateAction<OnTheMapSettingsType>>;
settingsKills: KillsWidgetSettings;
settingsKillsUpdate: Dispatch<SetStateAction<KillsWidgetSettings>>;
mapSettings: MapSettings;
mapSettingsUpdate: Dispatch<SetStateAction<MapSettings>>;
isReady: boolean;
hasOldSettings: boolean;
getSettingsForExport(): string | undefined;
@@ -172,6 +176,8 @@ const MapRootContext = createContext<MapRootContextProps>({
settingsOnTheMapUpdate: () => null,
settingsKills: DEFAULT_KILLS_WIDGET_SETTINGS,
settingsKillsUpdate: () => null,
mapSettings: DEFAULT_MAP_SETTINGS,
mapSettingsUpdate: () => null,
isReady: false,
hasOldSettings: false,
getSettingsForExport: () => '',

View File

@@ -3,16 +3,13 @@ import {
InterfaceStoredSettings,
KillsWidgetSettings,
LocalWidgetSettings,
MapSettings,
MiniMapPlacement,
OnTheMapSettingsType,
PingsPlacement,
RoutesType,
} from '@/hooks/Mapper/mapRootProvider/types.ts';
import {
CURRENT_WINDOWS_VERSION,
DEFAULT_WIDGETS,
STORED_VISIBLE_WIDGETS_DEFAULT,
} from '@/hooks/Mapper/components/mapInterface/constants.tsx';
import { DEFAULT_WIDGETS, STORED_VISIBLE_WIDGETS_DEFAULT } from '@/hooks/Mapper/components/mapInterface/constants.tsx';
export const STORED_INTERFACE_DEFAULT_VALUES: InterfaceStoredSettings = {
isShowMenu: false,
@@ -43,25 +40,25 @@ export const DEFAULT_ROUTES_SETTINGS: RoutesType = {
export const DEFAULT_WIDGET_LOCAL_SETTINGS: LocalWidgetSettings = {
compact: true,
showOffline: false,
version: 0,
showShipName: false,
};
export const DEFAULT_ON_THE_MAP_SETTINGS: OnTheMapSettingsType = {
hideOffline: false,
version: 0,
};
export const DEFAULT_KILLS_WIDGET_SETTINGS: KillsWidgetSettings = {
showAll: false,
whOnly: true,
excludedSystems: [],
version: 2,
timeRange: 4,
};
export const DEFAULT_MAP_SETTINGS: MapSettings = {
viewport: { zoom: 1, x: 0, y: 0 },
};
export const getDefaultWidgetProps = () => ({
version: CURRENT_WINDOWS_VERSION,
visible: STORED_VISIBLE_WIDGETS_DEFAULT,
windows: DEFAULT_WIDGETS,
});

View File

@@ -0,0 +1,55 @@
import { MapUserSettings, SettingsTypes, SettingsWrapper } from '@/hooks/Mapper/mapRootProvider/types.ts';
import {
DEFAULT_KILLS_WIDGET_SETTINGS,
DEFAULT_MAP_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 { STORED_SETTINGS_VERSION } from '@/hooks/Mapper/mapRootProvider/version.ts';
// TODO - we need provide and compare version
export const createWidgetSettings = <T>(settings: T) => {
return settings;
};
export const createDefaultStoredSettings = (): MapUserSettings => {
return {
version: STORED_SETTINGS_VERSION,
migratedFromOld: true,
killsWidget: createWidgetSettings(DEFAULT_KILLS_WIDGET_SETTINGS),
localWidget: createWidgetSettings(DEFAULT_WIDGET_LOCAL_SETTINGS),
widgets: createWidgetSettings(getDefaultWidgetProps()),
routes: createWidgetSettings(DEFAULT_ROUTES_SETTINGS),
onTheMap: createWidgetSettings(DEFAULT_ON_THE_MAP_SETTINGS),
signaturesWidget: createWidgetSettings(DEFAULT_SIGNATURE_SETTINGS),
interface: createWidgetSettings(STORED_INTERFACE_DEFAULT_VALUES),
map: createWidgetSettings(DEFAULT_MAP_SETTINGS),
};
};
// INFO - in another case need to generate complex type - but looks like it unnecessary
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const getDefaultSettingsByType = (type: SettingsTypes): SettingsWrapper<any> => {
switch (type) {
case SettingsTypes.killsWidget:
return createWidgetSettings(DEFAULT_KILLS_WIDGET_SETTINGS);
case SettingsTypes.localWidget:
return createWidgetSettings(DEFAULT_WIDGET_LOCAL_SETTINGS);
case SettingsTypes.widgets:
return createWidgetSettings(getDefaultWidgetProps());
case SettingsTypes.routes:
return createWidgetSettings(DEFAULT_ROUTES_SETTINGS);
case SettingsTypes.onTheMap:
return createWidgetSettings(DEFAULT_ON_THE_MAP_SETTINGS);
case SettingsTypes.signaturesWidget:
return createWidgetSettings(DEFAULT_SIGNATURE_SETTINGS);
case SettingsTypes.interface:
return createWidgetSettings(STORED_INTERFACE_DEFAULT_VALUES);
case SettingsTypes.map:
return createWidgetSettings(DEFAULT_MAP_SETTINGS);
}
};

View File

@@ -1,30 +0,0 @@
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';
// TODO - we need provide and compare version
const createWidgetSettingsWithVersion = <T>(settings: T) => {
return {
version: 0,
settings,
};
};
export const createDefaultWidgetSettings = (): MapUserSettings => {
return {
killsWidget: createWidgetSettingsWithVersion(DEFAULT_KILLS_WIDGET_SETTINGS),
localWidget: createWidgetSettingsWithVersion(DEFAULT_WIDGET_LOCAL_SETTINGS),
widgets: createWidgetSettingsWithVersion(getDefaultWidgetProps()),
routes: createWidgetSettingsWithVersion(DEFAULT_ROUTES_SETTINGS),
onTheMap: createWidgetSettingsWithVersion(DEFAULT_ON_THE_MAP_SETTINGS),
signaturesWidget: createWidgetSettingsWithVersion(DEFAULT_SIGNATURE_SETTINGS),
interface: createWidgetSettingsWithVersion(STORED_INTERFACE_DEFAULT_VALUES),
};
};

View File

@@ -5,8 +5,8 @@ import {
MapUserSettingsStructure,
RemoteAdminSettingsResponse,
} from '@/hooks/Mapper/mapRootProvider/types.ts';
import { createDefaultWidgetSettings } from '@/hooks/Mapper/mapRootProvider/helpers/createDefaultWidgetSettings.ts';
import { parseMapUserSettings } from '@/hooks/Mapper/components/helpers';
import { createDefaultStoredSettings } from '@/hooks/Mapper/mapRootProvider/helpers/createDefaultStoredSettings.ts';
import { applyMigrations } from '@/hooks/Mapper/mapRootProvider/migrations';
interface UseActualizeRemoteMapSettingsProps {
outCommand: OutCommandHandler;
@@ -37,14 +37,14 @@ export const useActualizeRemoteMapSettings = ({
}
if (res?.default_settings == null) {
applySettings(createDefaultWidgetSettings());
applySettings(createDefaultStoredSettings());
return;
}
try {
applySettings(parseMapUserSettings(res.default_settings));
applySettings(applyMigrations(JSON.parse(res.default_settings) || createDefaultStoredSettings()));
} catch (error) {
applySettings(createDefaultWidgetSettings());
applySettings(createDefaultStoredSettings());
}
}, [outCommand]);

View File

@@ -63,123 +63,127 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
const { pingAdded, pingCancelled } = useCommandPings();
const { characterActivityData, trackingCharactersData, userSettingsUpdated } = useCommandsActivity();
useImperativeHandle(ref, () => {
return {
command(type, data) {
switch (type) {
case Commands.init: // USED
mapInit(data as CommandInit);
break;
case Commands.addSystems: // USED
addSystems(data as CommandAddSystems);
break;
case Commands.updateSystems: // USED
updateSystems(data as CommandUpdateSystems);
break;
case Commands.removeSystems: // USED
removeSystems(data as CommandRemoveSystems);
break;
case Commands.addConnections: // USED
addConnections(data as CommandAddConnections);
break;
case Commands.removeConnections: // USED
removeConnections(data as CommandRemoveConnections);
break;
case Commands.updateConnection: // USED
updateConnection(data as CommandUpdateConnection);
break;
case Commands.charactersUpdated: // USED
charactersUpdated(data as CommandCharactersUpdated);
break;
case Commands.characterAdded: // USED
characterAdded(data as CommandCharacterAdded);
break;
case Commands.characterRemoved: // USED
characterRemoved(data as CommandCharacterRemoved);
break;
case Commands.characterUpdated: // USED
characterUpdated(data as CommandCharacterUpdated);
break;
case Commands.presentCharacters: // USED
presentCharacters(data as CommandPresentCharacters);
break;
case Commands.mapUpdated: // USED
mapUpdated(data as CommandMapUpdated);
break;
case Commands.routes:
mapRoutes(data as CommandRoutes);
break;
case Commands.userRoutes:
mapUserRoutes(data as CommandRoutes);
break;
useImperativeHandle(
ref,
() => {
return {
command(type, data) {
switch (type) {
case Commands.init: // USED
mapInit(data as CommandInit);
break;
case Commands.addSystems: // USED
addSystems(data as CommandAddSystems);
break;
case Commands.updateSystems: // USED
updateSystems(data as CommandUpdateSystems);
break;
case Commands.removeSystems: // USED
removeSystems(data as CommandRemoveSystems);
break;
case Commands.addConnections: // USED
addConnections(data as CommandAddConnections);
break;
case Commands.removeConnections: // USED
removeConnections(data as CommandRemoveConnections);
break;
case Commands.updateConnection: // USED
updateConnection(data as CommandUpdateConnection);
break;
case Commands.charactersUpdated: // USED
charactersUpdated(data as CommandCharactersUpdated);
break;
case Commands.characterAdded: // USED
characterAdded(data as CommandCharacterAdded);
break;
case Commands.characterRemoved: // USED
characterRemoved(data as CommandCharacterRemoved);
break;
case Commands.characterUpdated: // USED
characterUpdated(data as CommandCharacterUpdated);
break;
case Commands.presentCharacters: // USED
presentCharacters(data as CommandPresentCharacters);
break;
case Commands.mapUpdated: // USED
mapUpdated(data as CommandMapUpdated);
break;
case Commands.routes:
mapRoutes(data as CommandRoutes);
break;
case Commands.userRoutes:
mapUserRoutes(data as CommandRoutes);
break;
case Commands.signaturesUpdated: // USED
updateSystemSignatures(data as CommandSignaturesUpdated);
break;
case Commands.signaturesUpdated: // USED
updateSystemSignatures(data as CommandSignaturesUpdated);
break;
case Commands.linkSignatureToSystem: // USED
setTimeout(() => {
updateLinkSignatureToSystem(data as CommandLinkSignatureToSystem);
}, 200);
break;
case Commands.linkSignatureToSystem: // USED
setTimeout(() => {
updateLinkSignatureToSystem(data as CommandLinkSignatureToSystem);
}, 200);
break;
case Commands.centerSystem: // USED
// do nothing here
break;
case Commands.centerSystem: // USED
// do nothing here
break;
case Commands.selectSystem: // USED
// do nothing here
break;
case Commands.selectSystem: // USED
// do nothing here
break;
case Commands.killsUpdated:
// do nothing here
break;
case Commands.killsUpdated:
// do nothing here
break;
case Commands.detailedKillsUpdated:
updateDetailedKills(data as Record<string, DetailedKill[]>);
break;
case Commands.detailedKillsUpdated:
updateDetailedKills(data as Record<string, DetailedKill[]>);
break;
case Commands.characterActivityData:
characterActivityData(data as CommandCharacterActivityData);
break;
case Commands.characterActivityData:
characterActivityData(data as CommandCharacterActivityData);
break;
case Commands.trackingCharactersData:
trackingCharactersData(data as CommandTrackingCharactersData);
break;
case Commands.trackingCharactersData:
trackingCharactersData(data as CommandTrackingCharactersData);
break;
case Commands.updateActivity:
break;
case Commands.updateActivity:
break;
case Commands.updateTracking:
break;
case Commands.updateTracking:
break;
case Commands.userSettingsUpdated:
userSettingsUpdated(data as CommandUserSettingsUpdated);
break;
case Commands.userSettingsUpdated:
userSettingsUpdated(data as CommandUserSettingsUpdated);
break;
case Commands.systemCommentAdded:
addComment(data as CommandCommentAdd);
break;
case Commands.systemCommentAdded:
addComment(data as CommandCommentAdd);
break;
case Commands.systemCommentRemoved:
removeComment(data as CommandCommentRemoved);
break;
case Commands.systemCommentRemoved:
removeComment(data as CommandCommentRemoved);
break;
case Commands.pingAdded:
pingAdded(data as CommandPingAdded);
break;
case Commands.pingAdded:
pingAdded(data as CommandPingAdded);
break;
case Commands.pingCancelled:
pingCancelled(data as CommandPingCancelled);
break;
case Commands.pingCancelled:
pingCancelled(data as CommandPingCancelled);
break;
default:
console.warn(`JOipP Interface handlers: Unknown command: ${type}`, data);
break;
}
default:
console.warn(`JOipP Interface handlers: Unknown command: ${type}`, data);
break;
}
emitMapEvent({ name: type, data });
},
};
}, []);
emitMapEvent({ name: type, data });
},
};
},
[],
);
};

View File

@@ -6,7 +6,9 @@ import { useSettingsValueAndSetter } from '@/hooks/Mapper/mapRootProvider/hooks/
import fastDeepEqual from 'fast-deep-equal';
import { OutCommandHandler } from '@/hooks/Mapper/types';
import { useActualizeRemoteMapSettings } from '@/hooks/Mapper/mapRootProvider/hooks/useActualizeRemoteMapSettings.ts';
import { createDefaultWidgetSettings } from '@/hooks/Mapper/mapRootProvider/helpers/createDefaultWidgetSettings.ts';
import { createDefaultStoredSettings } from '@/hooks/Mapper/mapRootProvider/helpers/createDefaultStoredSettings.ts';
import { applyMigrations, extractData } from '@/hooks/Mapper/mapRootProvider/migrations';
import { LS_KEY, LS_KEY_LEGASY } from '@/hooks/Mapper/mapRootProvider/version.ts';
const EMPTY_OBJ = {};
@@ -14,7 +16,7 @@ export const useMapUserSettings = ({ map_slug }: MapRootData, outCommand: OutCom
const [isReady, setIsReady] = useState(false);
const [hasOldSettings, setHasOldSettings] = useState(false);
const [mapUserSettings, setMapUserSettings] = useLocalStorageState<MapUserSettingsStructure>('map-user-settings', {
const [mapUserSettings, setMapUserSettings] = useLocalStorageState<MapUserSettingsStructure>(LS_KEY, {
defaultValue: EMPTY_OBJ,
});
@@ -83,13 +85,20 @@ export const useMapUserSettings = ({ map_slug }: MapRootData, outCommand: OutCom
'killsWidget',
);
const [windowsSettings, setWindowsSettings] = useSettingsValueAndSetter(
const [windowsSettings, windowsSettingsUpdate] = useSettingsValueAndSetter(
mapUserSettings,
setMapUserSettings,
map_slug,
'widgets',
);
const [mapSettings, mapSettingsUpdate] = useSettingsValueAndSetter(
mapUserSettings,
setMapUserSettings,
map_slug,
'map',
);
// HERE we MUST work with migrations
useEffect(() => {
if (isReady) {
@@ -100,36 +109,28 @@ export const useMapUserSettings = ({ map_slug }: MapRootData, outCommand: OutCom
return;
}
if (mapUserSettings[map_slug] == null) {
const currentMapUserSettings = mapUserSettings[map_slug];
if (currentMapUserSettings == null) {
return;
}
// TODO !!!! FROM this date 06.07.2025 - we must work only with migrations
// actualizeSettings(STORED_INTERFACE_DEFAULT_VALUES, interfaceSettings, setInterfaceSettings);
// actualizeSettings(DEFAULT_ROUTES_SETTINGS, settingsRoutes, settingsRoutesUpdate);
// actualizeSettings(DEFAULT_WIDGET_LOCAL_SETTINGS, settingsLocal, settingsLocalUpdate);
// actualizeSettings(DEFAULT_SIGNATURE_SETTINGS, settingsSignatures, settingsSignaturesUpdate);
// actualizeSettings(DEFAULT_ON_THE_MAP_SETTINGS, settingsOnTheMap, settingsOnTheMapUpdate);
// actualizeSettings(DEFAULT_KILLS_WIDGET_SETTINGS, settingsKills, settingsKillsUpdate);
try {
// INFO: after migrations migratedFromOld always will be true
const migratedResult = applyMigrations(
!currentMapUserSettings.migratedFromOld ? extractData(LS_KEY_LEGASY) : currentMapUserSettings,
);
setIsReady(true);
}, [
map_slug,
mapUserSettings,
interfaceSettings,
setInterfaceSettings,
settingsRoutes,
settingsRoutesUpdate,
settingsLocal,
settingsLocalUpdate,
settingsSignatures,
settingsSignaturesUpdate,
settingsOnTheMap,
settingsOnTheMapUpdate,
settingsKills,
settingsKillsUpdate,
isReady,
]);
if (!migratedResult) {
setIsReady(true);
return;
}
setMapUserSettings({ ...mapUserSettings, [map_slug]: migratedResult });
setIsReady(true);
} catch (error) {
setIsReady(true);
}
}, [isReady, mapUserSettings, map_slug, setMapUserSettings]);
const checkOldSettings = useCallback(() => {
const interfaceSettings = localStorage.getItem('window:interface:settings');
@@ -157,7 +158,7 @@ export const useMapUserSettings = ({ map_slug }: MapRootData, outCommand: OutCom
}, []);
const resetSettings = useCallback(() => {
applySettings(createDefaultWidgetSettings());
applySettings(createDefaultStoredSettings());
}, [applySettings]);
return {
@@ -177,7 +178,9 @@ export const useMapUserSettings = ({ map_slug }: MapRootData, outCommand: OutCom
settingsKills,
settingsKillsUpdate,
windowsSettings,
setWindowsSettings,
windowsSettingsUpdate,
mapSettings,
mapSettingsUpdate,
getSettingsForExport,
applySettings,

View File

@@ -1,15 +1,11 @@
import { Dispatch, SetStateAction, useCallback, useMemo, useRef } from 'react';
import {
MapUserSettings,
MapUserSettingsStructure,
SettingsWithVersion,
} from '@/hooks/Mapper/mapRootProvider/types.ts';
import { MapUserSettings, MapUserSettingsStructure, SettingsWrapper } from '@/hooks/Mapper/mapRootProvider/types.ts';
type ExtractSettings<S extends keyof MapUserSettings> =
MapUserSettings[S] extends SettingsWithVersion<infer U> ? U : never;
type ExtractSettings<S extends keyof MapUserSettings> = MapUserSettings[S] extends SettingsWrapper<infer U> ? U : never;
type Setter<S extends keyof MapUserSettings> = (
value: Partial<ExtractSettings<S>> | ((prev: ExtractSettings<S>) => Partial<ExtractSettings<S>>),
version?: number,
) => void;
type GenerateSettingsReturn<S extends keyof MapUserSettings> = [ExtractSettings<S>, Setter<S>];
@@ -24,7 +20,7 @@ export const useSettingsValueAndSetter = <S extends keyof MapUserSettings>(
if (!mapId) return {} as ExtractSettings<S>;
const mapSettings = settings[mapId];
return (mapSettings?.[setting]?.settings ?? ({} as ExtractSettings<S>)) as ExtractSettings<S>;
return (mapSettings?.[setting] ?? ({} as ExtractSettings<S>)) as ExtractSettings<S>;
}, [mapId, setting, settings]);
const refData = useRef({ mapId, setting, setSettings });
@@ -37,8 +33,7 @@ export const useSettingsValueAndSetter = <S extends keyof MapUserSettings>(
setSettings(all => {
const currentMap = all[mapId];
const prev = currentMap[setting].settings as ExtractSettings<S>;
const version = currentMap[setting].version;
const prev = currentMap[setting] as ExtractSettings<S>;
const patch =
typeof value === 'function' ? (value as (p: ExtractSettings<S>) => Partial<ExtractSettings<S>>)(prev) : value;
@@ -47,10 +42,7 @@ export const useSettingsValueAndSetter = <S extends keyof MapUserSettings>(
...all,
[mapId]: {
...currentMap,
[setting]: {
version,
settings: { ...(prev as any), ...patch } as ExtractSettings<S>,
},
[setting]: { ...(prev as any), ...patch } as ExtractSettings<S>,
},
};
});

View File

@@ -6,7 +6,6 @@ import { getDefaultWidgetProps } from '@/hooks/Mapper/mapRootProvider/constants.
export type StoredWindowProps = Omit<WindowProps, 'content'>;
export type WindowStoreInfo = {
version: number;
windows: StoredWindowProps[];
visible: WidgetsIds[];
viewPort?: { w: number; h: number } | undefined;
@@ -16,19 +15,18 @@ export type ToggleWidgetVisibility = (widgetId: WidgetsIds) => void;
interface UseStoreWidgetsProps {
windowsSettings: WindowStoreInfo;
setWindowsSettings: Dispatch<SetStateAction<WindowStoreInfo>>;
windowsSettingsUpdate: Dispatch<SetStateAction<WindowStoreInfo>>;
}
export const useStoreWidgets = ({ windowsSettings, setWindowsSettings }: UseStoreWidgetsProps) => {
const ref = useRef({ windowsSettings, setWindowsSettings });
ref.current = { windowsSettings, setWindowsSettings };
export const useStoreWidgets = ({ windowsSettings, windowsSettingsUpdate }: UseStoreWidgetsProps) => {
const ref = useRef({ windowsSettings, windowsSettingsUpdate });
ref.current = { windowsSettings, windowsSettingsUpdate };
const updateWidgetSettings: WindowsManagerOnChange = useCallback(({ windows, viewPort }) => {
const { setWindowsSettings } = ref.current;
const { windowsSettingsUpdate } = ref.current;
setWindowsSettings(({ version, visible /*, windows*/ }: WindowStoreInfo) => {
windowsSettingsUpdate(({ visible /*, windows*/ }: WindowStoreInfo) => {
return {
version,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
windows: DEFAULT_WIDGETS.map(({ content, ...x }) => {
const windowProp = windows.find(j => j.id === x.id);
@@ -45,9 +43,9 @@ export const useStoreWidgets = ({ windowsSettings, setWindowsSettings }: UseStor
}, []);
const toggleWidgetVisibility: ToggleWidgetVisibility = useCallback(widgetId => {
const { setWindowsSettings } = ref.current;
const { windowsSettingsUpdate } = ref.current;
setWindowsSettings(({ visible, windows, ...x }) => {
windowsSettingsUpdate(({ visible, windows, ...x }) => {
const isCheckedPrev = visible.includes(widgetId);
if (!isCheckedPrev) {
const maxZIndex = Math.max(...windows.map(w => w.zIndex));
@@ -72,7 +70,7 @@ export const useStoreWidgets = ({ windowsSettings, setWindowsSettings }: UseStor
});
}, []);
const resetWidgets = useCallback(() => ref.current.setWindowsSettings(getDefaultWidgetProps()), []);
const resetWidgets = useCallback(() => ref.current.windowsSettingsUpdate(getDefaultWidgetProps()), []);
return {
windowsSettings,

View File

@@ -0,0 +1,58 @@
import { MapUserSettingsStructure } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { STORED_SETTINGS_VERSION } from '@/hooks/Mapper/mapRootProvider/version.ts';
import { migrations } from '@/hooks/Mapper/mapRootProvider/migrations/index.ts';
import { createDefaultStoredSettings } from '@/hooks/Mapper/mapRootProvider/helpers/createDefaultStoredSettings.ts';
export const extractData = (localStoreKey = 'map-user-settings'): MapUserSettingsStructure | null => {
const val = localStorage.getItem(localStoreKey);
if (!val) {
return null;
}
return JSON.parse(val);
};
export const applyMigrations = (mapSettings: any) => {
let currentMapSettings = { ...mapSettings };
// INFO if we have NO any data in store expected that we will use default
if (!currentMapSettings) {
return;
}
const direction = STORED_SETTINGS_VERSION - (currentMapSettings.version || 0);
if (direction === 0) {
if (currentMapSettings.version == null) {
return { ...currentMapSettings, version: STORED_SETTINGS_VERSION, migratedFromOld: true };
}
return currentMapSettings;
}
const cmVersion = currentMapSettings.version || 0;
// downgrade
// INFO: when we downgrading - if diff between >= 1 it means was major version
if (direction < 0) {
// If was minor version - we do nothing
if (Math.abs(direction) < 1) {
return currentMapSettings;
}
// if was major version - we set default settings
return createDefaultStoredSettings();
}
const preparedMigrations = migrations
.sort((a, b) => a.to - b.to)
.filter(x => x.to > cmVersion && x.to <= STORED_SETTINGS_VERSION);
for (const migration of preparedMigrations) {
const { to, up } = migration;
const next = up(currentMapSettings);
currentMapSettings = { ...next, version: to, migratedFromOld: true };
}
return currentMapSettings;
};

View File

@@ -0,0 +1,4 @@
import list from './list';
export * from './applyMigrations.ts';
export const migrations = [...list];

View File

@@ -0,0 +1,5 @@
import { to_1 } from './to_1.ts';
import { to_2 } from './to_2.ts';
import { MigrationStructure } from '@/hooks/Mapper/mapRootProvider/types.ts';
export default [to_1, to_2] as MigrationStructure[];

View File

@@ -0,0 +1,10 @@
import { MigrationStructure } from '@/hooks/Mapper/mapRootProvider/types.ts';
export const to_1: MigrationStructure = {
to: 1,
up: (prev: any) => {
return Object.keys(prev).reduce((acc, k) => {
return { ...acc, [k]: prev[k].settings };
}, Object.create(null));
},
};

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