Compare commits

..

141 Commits

Author SHA1 Message Date
CI
82f90ef759 chore: release version v1.37.2
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2025-01-09 07:26:08 +00:00
Aleksei Chichenkov
167c8eea6b Merge pull request #107 from guarzo/guarzo/theme-pathfinder
fix: fix route color
2025-01-09 10:25:37 +03:00
Gustav
d76079d4c7 theme cleanup 2025-01-08 23:45:27 -05:00
Gustav
bf9c4cda02 route color fix 2025-01-08 23:44:12 -05:00
CI
af00402546 chore: release version v1.37.1
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2025-01-08 21:53:07 +00:00
Aleksei Chichenkov
a245842ca4 Merge pull request #106 from guarzo/guarzo/themes
refactor: cleaned up scss classes, and updated theme dropdown to use declarative style
2025-01-09 00:52:36 +03:00
Gustav
8ddd672f13 fix: add back pathfinder theme font 2025-01-08 16:44:23 -05:00
Gustav
92f471c0b0 refactor: declarative dropdown for theme choice 2025-01-08 16:20:34 -05:00
Gustav
9e2a2c5b44 chore: clean up theme scss class usage 2025-01-08 16:20:25 -05:00
CI
5f5d3df003 chore: release version v1.37.0 2025-01-08 20:01:55 +00:00
Aleksei Chichenkov
c66cc8868e Merge pull request #100 from guarzo/guarzo/themes
feat: add theme selection, and pathfinder theme
2025-01-08 23:01:26 +03:00
guarzo
0d6528ce4f Merge branch 'main' into guarzo/themes 2025-01-08 14:05:59 -05:00
CI
34c385ac5f chore: release version v1.36.2
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2025-01-08 17:55:41 +00:00
Aleksei Chichenkov
b6d12e73a9 Merge pull request #105 from wanderer-industries/feat-wnd-104-fix-pasting
fix(Map): Fixed pasting into Name, Custom Label and Description
2025-01-08 20:55:16 +03:00
achichenkov
1118858120 fix(Map): Fixed pasting into Name, Custom Label and Description 2025-01-08 20:35:57 +03:00
CI
ae3a34d5bf chore: release version v1.36.1 2025-01-08 17:08:14 +00:00
Aleksei Chichenkov
43df42e49b Merge pull request #102 from wanderer-industries/feat-signatures-ru
fix(Map): Add support RU signatures and fix filtering
2025-01-08 20:07:43 +03:00
achichenkov
e670f3bf03 fix(Map): Removed unnecessary comment 2025-01-08 19:39:33 +03:00
achichenkov
c26a9404c5 fix(Map): Add support RU signatures and fix filtering 2025-01-08 12:46:56 +03:00
Gustav
c0fad4ca92 unused import 2025-01-07 18:44:49 -05:00
Gustav
16dbf9378b feat: add theme selection and pathfinder theme 2025-01-07 18:44:46 -05:00
CI
4001fe5eac chore: release version v1.36.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2025-01-07 23:28:11 +00:00
guarzo
2992dd8f8b feat: added static system info to api (#101)
* feat: added static system info to api
2025-01-08 03:27:44 +04:00
CI
98a03d1e59 chore: release version v1.35.0 2025-01-07 23:05:04 +00:00
guarzo
2088393c79 feat(Map): add "temporary system names" toggle (#86)
If enabled and set, a temporary name is displayed instead of the system name. The original system name appears on a secondary row if no custom name exists. Temporary names are removed when the system is removed from the map.
2025-01-08 03:04:36 +04:00
CI
093042b88a chore: release version v1.33.1
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2025-01-07 12:47:07 +00:00
Dmitry Popov
e5ef35c186 hotfix: Fix map behaviour for 'Allow only tracked characters' map option 2025-01-07 13:46:34 +01:00
CI
1cd23d5efd chore: release version v1.33.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2025-01-07 09:17:09 +00:00
guarzo
ead5818a3f feat(Map): api to allow systematic access to visible systems and tracked characters (#89)
* feat: add limited api for system and tracked characters
* env variable to disable public api key
2025-01-07 13:16:39 +04:00
CI
a5f66ada68 chore: release version v1.32.7
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2025-01-06 22:24:26 +00:00
Dmitry Popov
0919742853 Revert "hotfix: add ARM64 compatible Docker image (#94)" (#99)
This reverts commit f85317983c.
2025-01-07 02:23:59 +04:00
CI
f3efffd259 chore: release version v1.32.6 2025-01-06 21:56:57 +00:00
Tsuro Tsero
f85317983c hotfix: add ARM64 compatible Docker image (#94) 2025-01-07 01:56:22 +04:00
CI
76f709b768 chore: release version v1.32.5
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-01-04 20:23:09 +00:00
guarzo
e3b2356302 fix(map): prevent deselect on click to map (#96)
Fixes #80

- Prevents single node deselection on background / same node click
- Allows deseletion of all nodes if multiple are currently selected
2025-01-05 00:22:41 +04:00
CI
3d810211ee chore: release version v1.32.4
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-01-02 19:56:48 +00:00
Dmitry Popov
7453795dc5 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-01-02 20:56:18 +01:00
Dmitry Popov
9de7cd99ee fix(Map): Fix 'Character Activity' modal 2025-01-02 20:56:14 +01:00
CI
51489c1aa5 chore: release version v1.32.3 2025-01-02 17:23:05 +00:00
Dmitry Popov
25dd6de770 fix(Map): Fix 'Allow only tracked characters' saving 2025-01-02 18:22:32 +01:00
CI
2a825f5a02 chore: release version v1.32.2
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2025-01-02 10:12:04 +00:00
guarzo
908d249eb9 Allow user to follow a specific tracked character (#87)
* add follow character functionality
2025-01-02 14:09:10 +04:00
CI
6cd119e8f4 chore: release version v1.32.1
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-12-25 13:34:54 +00:00
Dmitry Popov
9a59c8eb75 Merge pull request #81 from LordMrcS/main
Update Wormhole Path Colors for Reduced and VOC
2024-12-25 17:34:30 +04:00
Marcos Silva
452c022d41 Merge branch 'main' into main 2024-12-25 10:03:04 -03:00
CI
27e9bab82a chore: release version v1.32.0
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-12-24 09:02:07 +00:00
Aleksei Chichenkov
edef860530 Merge pull request #85 from wanderer-industries/search-systems
Improve Search systems for Routes and Map (Manually add system)
2024-12-24 12:01:40 +03:00
achichenkov
032cb63411 Merge branch 'refs/heads/main' into search-systems 2024-12-24 11:43:02 +03:00
achichenkov
a1791ba578 fix(Map): Added ability to add new system to routes via routes widget 2024-12-24 11:42:46 +03:00
Dmitry Popov
3a69fd7786 chore(Map): Get rid of old add system modal 2024-12-23 11:13:49 +01:00
achichenkov
8a90723c2e fix(Map): Reworked add system to map 2024-12-23 13:06:15 +03:00
Dmitry Popov
af2fc342c7 chore(Map): Add search systems 2024-12-23 10:44:46 +01:00
Dmitry Popov
05ea2fcdbe chore(Map): Add default filtering to search systems 2024-12-22 22:59:28 +01:00
Dmitry Popov
6d4321fead chore(Map): Add static info to search systems 2024-12-22 17:16:42 +01:00
CI
3f6364c9ea chore: release version v1.31.0
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-12-20 15:05:20 +00:00
Dmitry Popov
0d11b12282 feat(Core): Show tracking for new users by default. Auto link characters to account fix. Add character loading indicators. 2024-12-20 16:04:31 +01:00
Dmitry Popov
0796bcf7d0 feat(Map): Add search & update manual adding systems API 2024-12-18 11:13:20 +01:00
Dmitry Popov
0b5bec142a feat(Map): Add search & update manual adding systems API 2024-12-18 11:09:52 +01:00
CI
a5020b58f2 chore: release version v1.30.2
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-12-17 16:31:33 +00:00
Aleksei Chichenkov
f039a74a8f Merge pull request #84 from wanderer-industries/fix-ship-size
fix(Map): Fixed problem with ship size change.
2024-12-17 19:30:56 +03:00
achichenkov
0e6bb7390b fix(Map): Fixed problem with ship size change. 2024-12-17 19:10:26 +03:00
CI
b52b4eecca chore: release version v1.30.1 2024-12-17 12:26:49 +00:00
Aleksei Chichenkov
8186977d1d Merge pull request #83 from wanderer-industries/feat-set-wormhole-size
Feature (Map) - Improved connection size settings and some UI issues
2024-12-17 15:26:20 +03:00
achichenkov
86adcfe4d7 fix(Map): Little rework Signatures header: change System Signatures to Signatures, and show selected system name instead. 2024-12-17 14:32:35 +03:00
Dmitry Popov
ce2dd872c4 fix(Map): update default size of connections 2024-12-17 11:47:05 +01:00
achichenkov
aadc53c90e fix(Map): add ability set the size of wormhole and mark connection with label 2024-12-17 13:33:37 +03:00
CI
cbc1b6b5c8 chore: release version v1.30.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-12-16 18:49:37 +00:00
Aleksei Chichenkov
1aed7a9232 Merge pull request #82 from wanderer-industries/ui-issues-part-3
UI issues part 3
2024-12-16 21:49:11 +03:00
achichenkov
b549189644 fix(Map): fixed U210, K346 for C4 shattered systems 2024-12-16 17:13:33 +03:00
achichenkov
35279d17b4 fix(Map): fixed U210, K346 for shattered systems. Fixed mass of mediums chains. Fixed size of some capital chains from 3M to 3.3M. Based on https://whtype.info/ data. 2024-12-16 16:48:53 +03:00
achichenkov
bb403aa0c5 fix(Map): removed unnecessary log 2024-12-16 12:45:51 +03:00
achichenkov
04327c288b fix(Map): Uncomment what should not be commented 2024-12-16 12:43:26 +03:00
achichenkov
94d60e40d0 feat(Map): Fixed incorrect wrapping labels of checkboxes in System Signatures, Local and Routes. Also changed dotlan links for k-spacem now it leads to region map before, for wh all stay as it was. Added ability to chane to softer background and remove dots on background of map. Also some small design issues. #2 2024-12-16 12:42:24 +03:00
achichenkov
8505fcb6b7 feat(Map): Fixed incorrect wrapping labels of checkboxes in System Signatures, Local and Routes. Also changed dotlan links for k-spacem now it leads to region map before, for wh all stay as it was. Added ability to chane to softer background and remove dots on background of map. Also some small design issues. 2024-12-16 12:42:03 +03:00
Marcos Silva
e0a37f7635 Update Wormhole Path Colors for Reduced and VOC
Replaced the reduced and verge of collapse path colors to make it clearer.
2024-12-14 20:03:30 -03:00
CI
9aec57166d chore: release version v1.29.5
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-12-14 22:32:16 +00:00
Dmitry Popov
a3739f2950 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-12-14 23:31:47 +01:00
Dmitry Popov
3d3b152758 fix(Core): Fix character trackers cleanup 2024-12-14 23:31:42 +01:00
CI
0e03730543 chore: release version v1.29.4
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-12-10 11:01:09 +00:00
Dmitry Popov
97e07a6511 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-12-10 12:00:38 +01:00
Dmitry Popov
a77a51ba15 fix(Core): Small fixes 2024-12-10 12:00:34 +01:00
CI
42e706e1c2 chore: release version v1.29.3
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-12-07 18:02:05 +00:00
Dmitry Popov
025dd06053 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-12-07 19:01:36 +01:00
Dmitry Popov
bcb421d879 fix(Core): Increased eve DB data download timeout 2024-12-07 19:01:32 +01:00
CI
66056ab54b chore: release version v1.29.2
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-12-07 08:32:58 +00:00
Dmitry Popov
bb92f76ceb Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-12-07 09:32:28 +01:00
Dmitry Popov
84076b340b fix(Core): Fix unpkg CDN issues, fix Abyssals sites adding as systems on map 2024-12-07 09:32:24 +01:00
CI
48caae5c0e chore: release version v1.29.1
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-12-05 10:54:41 +00:00
Dmitry Popov
77dd23795a Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-12-05 11:54:05 +01:00
Dmitry Popov
2771d6304e chore: release version v1.28.1 2024-12-05 11:54:01 +01:00
CI
9946edffa4 chore: release version v1.29.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-12-05 08:52:42 +00:00
Dmitry Popov
50bf2fd9d3 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-12-05 09:52:13 +01:00
Dmitry Popov
bdcde168aa feat(Signatures): Show 'Unsplashed' signatures on the map (optionally) 2024-12-05 09:52:07 +01:00
CI
5807142e20 chore: release version v1.28.1
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-12-04 20:14:44 +00:00
Dmitry Popov
ec2d9565b9 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-12-04 21:14:18 +01:00
Dmitry Popov
a18a71c73d chore: release version v1.27.1 2024-12-04 21:14:14 +01:00
CI
93a6bd1156 chore: release version v1.28.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-12-04 17:02:43 +00:00
Dmitry Popov
581a410aef Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-12-04 18:02:08 +01:00
Dmitry Popov
ab02fe988c feat(Map): Added an option to show 'Offline characters' to map admins & managers only
- updated UI layout for map settings modal
2024-12-04 18:01:50 +01:00
CI
b8d20fb21b chore: release version v1.27.1
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-12-04 08:14:30 +00:00
Dmitry Popov
12fa1a0be8 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-12-04 09:13:57 +01:00
Dmitry Popov
85a84f7507 fix(Map): Fix 'On the map' visibility 2024-12-04 09:13:54 +01:00
CI
2385313013 chore: release version v1.27.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-12-03 21:04:59 +00:00
Dmitry Popov
c7ce727571 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-12-03 22:04:26 +01:00
Dmitry Popov
8b165ff478 feat(Map): Hide 'On the map' list for 'Viewer' role 2024-12-03 22:04:20 +01:00
CI
6d7d0cc72d chore: release version v1.26.1 2024-12-03 20:05:05 +00:00
Dmitry Popov
f7eba5d4fd Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-12-03 21:04:32 +01:00
Dmitry Popov
73ef6dae73 fix(Signatures): Fix error on splash wh 2024-12-03 21:04:26 +01:00
CI
7fa6df1e5e chore: release version v1.26.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-12-03 16:54:46 +00:00
Dmitry Popov
e1a2ffb151 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-12-03 17:54:18 +01:00
Dmitry Popov
6d7727a32d feat(Signatures): Keep 'Lazy delete' enabled setting 2024-12-03 17:54:13 +01:00
CI
6d7a94bd5a chore: release version v1.25.2
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-12-01 07:26:29 +00:00
Dmitry Popov
ecc3fb17e1 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-12-01 08:25:59 +01:00
Dmitry Popov
209e2bf0a5 fix(Signatures): Fix lazy delete on system switch 2024-12-01 08:25:10 +01:00
CI
b1947e57a4 chore: release version v1.25.1
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-11-28 23:20:10 +00:00
Dmitry Popov
74507501a5 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-11-29 00:19:43 +01:00
Dmitry Popov
c73481fd58 fix(Signatures): Fix colors & add 'Backspace' hotkey to delete signatures 2024-11-29 00:19:26 +01:00
CI
7795ad0b0c chore: release version v1.25.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-28 10:09:57 +00:00
Dmitry Popov
aff768f413 feat(Signatures): Automatically remove signature if linked system removed 2024-11-28 11:09:24 +01:00
CI
310b60f5b6 chore: release version v1.24.2
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-27 23:42:04 +00:00
Dmitry Popov
100f0be86a fix(Signatures): Fix paste signatures 2024-11-28 00:41:01 +01:00
CI
87e115e40d chore: release version v1.24.1
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-27 15:37:38 +00:00
Dmitry Popov
ef5f36e4c4 Signatures k162 types (#77)
* feat(Signatures): Added ability to specify K162 type (actually next types supported Hi/Low/Null/C1-C6/Thera/Pochven)
2024-11-27 19:37:10 +04:00
CI
099650420d chore: release version v1.24.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-27 13:28:03 +00:00
Dmitry Popov
8ccf7fffa5 feat(Signatures): Added "Lazy delete" option & got rid of update popup 2024-11-27 14:27:27 +01:00
CI
b97a055bf7 chore: release version v1.23.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-26 21:05:58 +00:00
Dmitry Popov
663fee6699 feat(Map): Lock systems available to manager/admin roles only (#75)
* feat(Map): Lock systems available to manager/admin roles only

* feat(Map): Fix add system & add acl member select behaviour
2024-11-27 01:05:26 +04:00
CI
33d5f3938b chore: release version v1.22.0 2024-11-26 19:03:01 +00:00
Aleksei Chichenkov
ef6b45d7a1 feat(Map): Rework design of checkboxes in Signatures settings dialog. Rework design of checkboxes in Routes settings dialog. Now signature will deleteing by Delete hotkey was Backspace. Fixed size of group column in signatures list. Instead Updated column will be Added, updated may be turn on in settings. (#76)
Co-authored-by: achichenkov <aleksei.chichenkov@telleqt.ai>
2024-11-26 23:02:30 +04:00
CI
c1ecd3690e chore: release version v1.21.0
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-11-24 15:55:38 +00:00
Aleksei Chichenkov
3250fe1ec6 Merge pull request #74 from wanderer-industries/dessign-issues-2
feat(Map): add new gate design, change EOL placement
2024-11-24 18:55:12 +03:00
achichenkov
48e8cd93b9 feat(Map): add new gate design, change EOL placement 2024-11-24 18:30:40 +03:00
CI
afacbb16b6 chore: release version v1.20.1
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-11-22 12:42:02 +00:00
Dmitry Popov
dfad127f32 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-11-22 13:41:35 +01:00
Dmitry Popov
300c1b5a18 chore: release version v1.19.3 2024-11-22 13:41:30 +01:00
CI
bb38e1710b chore: release version v1.20.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-22 12:27:53 +00:00
Dmitry Popov
0857a82de5 feat(Core): Add connection type for Gates, add new Update logic
fixes #73
2024-11-22 13:27:17 +01:00
CI
da5afcc91c chore: release version v1.19.3
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-11-20 14:09:27 +00:00
Dmitry Popov
0002979fda fix(Core): Fix adding systems on splash (#71)
* fix(Core): Fix adding systems on splash
2024-11-20 18:08:59 +04:00
CI
080af16d41 chore: release version v1.19.2
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-19 21:27:05 +00:00
Dmitry Popov
d03a0b7083 chore: release version v1.19.1 2024-11-19 22:26:22 +01:00
172 changed files with 13047 additions and 6020 deletions

View File

@@ -6,3 +6,4 @@ export EVE_CLIENT_WITH_WALLET_ID="<EVE_CLIENT_WITH_WALLET_ID>"
export EVE_CLIENT_WITH_WALLET_SECRET="<EVE_CLIENT_WITH_WALLET_SECRET>"
export GIT_SHA="1111"
export WANDERER_INVITES="false"
export WANDERER_PUBLIC_API_DISABLED="false"

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,8 @@
@import 'primereact/resources/themes/arya-blue/theme.css' layer(primereact);
/*@import 'primereact/resources/themes/bootstrap4-dark-blue/theme.css' layer(primereact);*/
@import '../js/hooks/Mapper/components/map/styles/index.scss';
@layer tailwind-base {
@tailwind base;
}
@@ -466,3 +468,467 @@ body {
transform: rotate(-360deg);
}
}
/* Map refresh */
.socket {
scale: 0.5;
width: 150px;
height: 150px;
left: 50%;
/* margin-left: -75px; */
top: 50%;
/* margin-top: -50px; */
}
.hex-brick {
background: #000;
width: 30px;
height: 17px;
position: absolute;
top: 5px;
animation-name: fade;
animation-duration: 2s;
animation-iteration-count: infinite;
-webkit-animation-name: fade;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: infinite;
}
.hex-brick--active {
animation-name: fade-active;
-webkit-animation-name: fade-active;
}
.h2 {
transform: rotate(60deg);
-webkit-transform: rotate(60deg);
}
.h3 {
transform: rotate(-60deg);
-webkit-transform: rotate(-60deg);
}
.gel {
height: 30px;
width: 30px;
transition: all 0.3s;
-webkit-transition: all 0.3s;
position: absolute;
top: 50%;
left: 50%;
}
.center-gel {
margin-left: -15px;
margin-top: -15px;
animation-name: pulse-version;
animation-duration: 2s;
animation-iteration-count: infinite;
-webkit-animation-name: pulse-version;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: infinite;
}
.c1 {
margin-left: -47px;
margin-top: -15px;
}
.c2 {
margin-left: -31px;
margin-top: -43px;
}
.c3 {
margin-left: 1px;
margin-top: -43px;
}
.c4 {
margin-left: 17px;
margin-top: -15px;
}
.c5 {
margin-left: -31px;
margin-top: 13px;
}
.c6 {
margin-left: 1px;
margin-top: 13px;
}
.c7 {
margin-left: -63px;
margin-top: -43px;
}
.c8 {
margin-left: 33px;
margin-top: -43px;
}
.c9 {
margin-left: -15px;
margin-top: 41px;
}
.c10 {
margin-left: -63px;
margin-top: 13px;
}
.c11 {
margin-left: 33px;
margin-top: 13px;
}
.c12 {
margin-left: -15px;
margin-top: -71px;
}
.c13 {
margin-left: -47px;
margin-top: -71px;
}
.c14 {
margin-left: 17px;
margin-top: -71px;
}
.c15 {
margin-left: -47px;
margin-top: 41px;
}
.c16 {
margin-left: 17px;
margin-top: 41px;
}
.c17 {
margin-left: -79px;
margin-top: -15px;
}
.c18 {
margin-left: 49px;
margin-top: -15px;
}
.c19 {
margin-left: -63px;
margin-top: -99px;
}
.c20 {
margin-left: 33px;
margin-top: -99px;
}
.c21 {
margin-left: 1px;
margin-top: -99px;
}
.c22 {
margin-left: -31px;
margin-top: -99px;
}
.c23 {
margin-left: -63px;
margin-top: 69px;
}
.c24 {
margin-left: 33px;
margin-top: 69px;
}
.c25 {
margin-left: 1px;
margin-top: 69px;
}
.c26 {
margin-left: -31px;
margin-top: 69px;
}
.c27 {
margin-left: -79px;
margin-top: -15px;
}
.c28 {
margin-left: -95px;
margin-top: -43px;
}
.c29 {
margin-left: -95px;
margin-top: 13px;
}
.c30 {
margin-left: 49px;
margin-top: 41px;
}
.c31 {
margin-left: -79px;
margin-top: -71px;
}
.c32 {
margin-left: -111px;
margin-top: -15px;
}
.c33 {
margin-left: 65px;
margin-top: -43px;
}
.c34 {
margin-left: 65px;
margin-top: 13px;
}
.c35 {
margin-left: -79px;
margin-top: 41px;
}
.c36 {
margin-left: 49px;
margin-top: -71px;
}
.c37 {
margin-left: 81px;
margin-top: -15px;
}
.r1 {
animation-name: pulse-version;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-delay: 0.2s;
-webkit-animation-name: pulse-version;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: infinite;
-webkit-animation-delay: 0.2s;
}
.r2 {
animation-name: pulse-version;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-delay: 0.4s;
-webkit-animation-name: pulse-version;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: infinite;
-webkit-animation-delay: 0.4s;
}
.r3 {
animation-name: pulse-version;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-delay: 0.6s;
-webkit-animation-name: pulse-version;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: infinite;
-webkit-animation-delay: 0.6s;
}
.r1 > .hex-brick {
animation-name: fade;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-delay: 0.2s;
-webkit-animation-name: fade;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: infinite;
-webkit-animation-delay: 0.2s;
}
.r1 > .hex-brick--active {
animation-name: fade-active;
-webkit-animation-name: fade-active;
}
.r2 > .hex-brick {
animation-name: fade;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-delay: 0.4s;
-webkit-animation-name: fade;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: infinite;
-webkit-animation-delay: 0.4s;
}
.r2 > .hex-brick--active {
animation-name: fade-active;
-webkit-animation-name: fade-active;
}
.r3 > .hex-brick {
animation-name: fade;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-delay: 0.6s;
-webkit-animation-name: fade;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: infinite;
-webkit-animation-delay: 0.6s;
}
.r3 > .hex-brick--active {
animation-name: fade-active;
-webkit-animation-name: fade-active;
}
@keyframes pulse-version {
0% {
-webkit-transform: scale(1);
transform: scale(1);
}
50% {
-webkit-transform: scale(0.01);
transform: scale(0.01);
}
100% {
-webkit-transform: scale(1);
transform: scale(1);
}
}
@keyframes fade {
0% {
background: #09d0e2;
}
50% {
background: #8ae6ee;
}
100% {
background: #09d0e2;
}
}
@keyframes fade-active {
0% {
background: #ff52d9;
}
50% {
background: #ff52d9;
}
100% {
background: #ff52d9;
}
}
@-webkit-keyframes pulse {
0% {
-webkit-transform: scale(1);
transform: scale(1);
}
50% {
-webkit-transform: scale(0.01);
transform: scale(0.01);
}
100% {
-webkit-transform: scale(1);
transform: scale(1);
}
}
@-webkit-keyframes fade {
0% {
background: #abf8ff;
}
50% {
background: #389ca6;
}
100% {
background: #abf8ff;
}
}
/* Map refresh END */
.inputContainer {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
}
.inputContainer > span:nth-child(1),
.inputContainer > label:nth-child(1) {
color: var(--gray-200);
font-size: 13px;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.inputContainer > :nth-child(2) {
border-bottom: 2px dotted #3f3f3f;
height: 1px;
margin: 0 12px;
}
.smallInputSwitch {
height: 100%;
display: flex;
align-items: center;
}
.smallInputSwitch .p-inputswitch {
height: 1rem;
width: 2rem;
}
.smallInputSwitch .p-inputswitch.p-inputswitch-checked .p-inputswitch-slider::before {
transform: translateX(1rem);
}
.smallInputSwitch .p-inputswitch.p-highlight .p-inputswitch-slider:before {
transform: translateX(1rem);
}
.smallInputSwitch .p-inputswitch .p-inputswitch-slider::before {
width: 0.8rem;
height: 0.8rem;
margin-top: -0.4rem;
margin-left: -3px;
}
.checkboxRoot.sizeXS {
width: 14px;
height: 14px;
}
.checkboxRoot.sizeXS .p-checkbox-box,
.checkboxRoot.sizeXS .p-checkbox-input {
width: 14px;
height: 14px;
}
.checkboxRoot.sizeM {
width: 16px;
height: 16px;
}
.checkboxRoot.sizeM .p-checkbox-box,
.checkboxRoot.sizeM .p-checkbox-input {
width: 16px;
height: 16px;
}

View File

@@ -108,3 +108,7 @@
.p-dropdown-empty-message {
padding: 0.25rem 0.5rem;
}
.p-autocomplete .p-autocomplete-multiple-container .p-autocomplete-token {
margin-right: 0 !important;
}

View File

@@ -88,6 +88,23 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
setSystem(undefined);
}, []);
const onSystemTemporaryName = useCallback((temporaryName?: string) => {
const { system, outCommand } = ref.current;
if (!system) {
return;
}
outCommand({
type: OutCommand.updateSystemTemporaryName,
data: {
system_id: system,
value: temporaryName ?? '',
},
});
setSystem(undefined);
}, []);
const onSystemStatus = useCallback((status: number) => {
const { system, outCommand } = ref.current;
if (!system) {
@@ -161,6 +178,7 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
onLockToggle,
onHubToggle,
onSystemTag,
onSystemTemporaryName,
onSystemStatus,
onSystemLabels,
onOpenSettings,

View File

@@ -6,6 +6,9 @@ import { PrimeIcons } from 'primereact/api';
import { ContextMenuSystemProps } from '@/hooks/Mapper/components/contexts';
import { useWaypointMenu } from '@/hooks/Mapper/components/contexts/hooks';
import { FastSystemActions } from '@/hooks/Mapper/components/contexts/components';
import { useMapCheckPermissions } from '@/hooks/Mapper/mapRootProvider/hooks/api';
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
export const useContextMenuSystemItems = ({
onDeleteSystem,
@@ -25,6 +28,7 @@ export const useContextMenuSystemItems = ({
const getStatus = useStatusMenu(systems, systemId, onSystemStatus);
const getLabels = useLabelsMenu(systems, systemId, onSystemLabels, onCustomLabelDialog);
const getWaypointMenu = useWaypointMenu(onWaypointSet);
const canLockSystem = useMapCheckPermissions([UserPermission.LOCK_SYSTEM]);
return useMemo(() => {
const system = systemId ? getSystemById(systems, systemId) : undefined;
@@ -41,6 +45,8 @@ export const useContextMenuSystemItems = ({
<FastSystemActions
systemId={systemId}
systemName={system.system_static_info.solar_system_name}
regionName={system.system_static_info.region_name}
isWH={isWormholeSpace(system.system_static_info.system_class)}
showEdit
onOpenSettings={onOpenSettings}
/>
@@ -58,19 +64,25 @@ export const useContextMenuSystemItems = ({
command: onHubToggle,
},
...(system.locked
? [
{
label: 'Unlock',
icon: PrimeIcons.LOCK_OPEN,
command: onLockToggle,
},
]
? canLockSystem
? [
{
label: 'Unlock',
icon: PrimeIcons.LOCK_OPEN,
command: onLockToggle,
},
]
: []
: [
{
label: 'Lock',
icon: PrimeIcons.LOCK,
command: onLockToggle,
},
...(canLockSystem
? [
{
label: 'Lock',
icon: PrimeIcons.LOCK,
command: onLockToggle,
},
]
: []),
{ separator: true },
{
label: 'Delete',
@@ -80,6 +92,7 @@ export const useContextMenuSystemItems = ({
]),
];
}, [
canLockSystem,
systems,
systemId,
getTags,

View File

@@ -10,6 +10,7 @@ import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/ty
import { FastSystemActions } from '@/hooks/Mapper/components/contexts/components';
import { useJumpPlannerMenu } from '@/hooks/Mapper/components/contexts/hooks';
import { Route } from '@/hooks/Mapper/types/routes.ts';
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
export interface ContextMenuSystemInfoProps {
systemStatics: Map<number, SolarSystemStaticInfoRaw>;
@@ -48,7 +49,6 @@ export const ContextMenuSystemInfo: React.FC<ContextMenuSystemInfoProps> = ({
if (!systemId || !system) {
return [];
}
return [
{
className: classes.FastActions,
@@ -57,6 +57,8 @@ export const ContextMenuSystemInfo: React.FC<ContextMenuSystemInfoProps> = ({
<FastSystemActions
systemId={systemId}
systemName={system.solar_system_name}
regionName={system.region_name}
isWH={isWormholeSpace(system.system_class)}
onOpenSettings={onOpenSettings}
/>
);

View File

@@ -9,13 +9,22 @@ import { PrimeIcons } from 'primereact/api';
export interface FastSystemActionsProps {
systemId: string;
systemName: string;
regionName: string;
isWH: boolean;
showEdit?: boolean;
onOpenSettings(): void;
}
export const FastSystemActions = ({ systemId, systemName, onOpenSettings, showEdit }: FastSystemActionsProps) => {
const ref = useRef({ systemId, systemName });
ref.current = { systemId, systemName };
export const FastSystemActions = ({
systemId,
systemName,
regionName,
isWH,
onOpenSettings,
showEdit,
}: FastSystemActionsProps) => {
const ref = useRef({ systemId, systemName, regionName, isWH });
ref.current = { systemId, systemName, regionName, isWH };
const handleOpenZKB = useCallback(
() => window.open(`https://zkillboard.com/system/${ref.current.systemId}`, '_blank'),
@@ -27,10 +36,17 @@ export const FastSystemActions = ({ systemId, systemName, onOpenSettings, showEd
[],
);
const handleOpenDotlan = useCallback(
() => window.open(`https://evemaps.dotlan.net/system/${ref.current.systemName}`, '_blank'),
[],
);
const handleOpenDotlan = useCallback(() => {
if (ref.current.isWH) {
window.open(`https://evemaps.dotlan.net/system/${ref.current.systemName}`, '_blank');
return;
}
return window.open(
`https://evemaps.dotlan.net/map/${ref.current.regionName.replace(/ /gim, '_')}/${ref.current.systemName}#jumps`,
'_blank',
);
}, []);
const copySystemNameToClipboard = useCallback(async () => {
try {
@@ -43,9 +59,9 @@ export const FastSystemActions = ({ systemId, systemName, onOpenSettings, showEd
return (
<LayoutEventBlocker className={clsx('flex px-2 gap-2 justify-between items-center h-full')}>
<div className={clsx('flex gap-2 items-center h-full', classes.Links)}>
<WdImgButton source={ZKB_ICON} onClick={handleOpenZKB} />
<WdImgButton source={ANOIK_ICON} onClick={handleOpenAnoikis} />
<WdImgButton source={DOTLAN_ICON} onClick={handleOpenDotlan} />
<WdImgButton tooltip={{ content: 'Open zkillboard' }} source={ZKB_ICON} onClick={handleOpenZKB} />
<WdImgButton tooltip={{ content: 'Open Anoikis' }} source={ANOIK_ICON} onClick={handleOpenAnoikis} />
<WdImgButton tooltip={{ content: 'Open Dotlan' }} source={DOTLAN_ICON} onClick={handleOpenDotlan} />
</div>
<div className="flex gap-2 items-center pl-1">

View File

@@ -1,4 +1,11 @@
.MapRoot {
width: 100%;
height: 100%;
background-color: var(--rf-bg-color, #000000);
&.BackgroundAlternateColor {
background-color: var(--rf-soft-bg-color, #171717);
--rf-node-bg-color: var(--rf-node-soft-bg-color, #2b2b2b);
}
}

View File

@@ -12,16 +12,12 @@ import ReactFlow, {
OnSelectionChangeFunc,
SelectionDragHandler,
SelectionMode,
useEdgesState,
useNodesState,
useReactFlow,
} from 'reactflow';
import 'reactflow/dist/style.css';
import classes from './Map.module.scss';
import './styles/neon-theme.scss';
import './styles/eve-common.scss';
import { MapProvider, useMapState } from './MapProvider';
import { useMapHandlers, useUpdateNodes } from './hooks';
import { useNodesState, useEdgesState, useMapHandlers, useUpdateNodes } from './hooks';
import { MapHandlers, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
import {
ContextMenuConnection,
@@ -31,11 +27,13 @@ import {
useContextMenuConnectionHandlers,
useContextMenuRootHandlers,
} from './components';
import { OnMapSelectionChange } from './map.types';
import { OnMapAddSystemCallback, OnMapSelectionChange } from './map.types';
import { SESSION_KEY } from '@/hooks/Mapper/constants.ts';
import { SolarSystemConnection, SolarSystemRawType } from '@/hooks/Mapper/types';
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts';
import clsx from 'clsx';
import { useBackgroundVars } from './hooks/useBackgroundVars';
const DEFAULT_VIEW_PORT = { zoom: 1, x: 0, y: 0 };
@@ -93,12 +91,16 @@ interface MapCompProps {
onSelectionChange: OnMapSelectionChange;
onManualDelete(systems: string[]): void;
onConnectionInfoClick?(e: SolarSystemConnection): void;
onAddSystem?: OnMapAddSystemCallback;
onSelectionContextMenu?: NodeSelectionMouseHandler;
minimapClasses?: string;
isShowMinimap?: boolean;
onSystemContextMenu: (event: MouseEvent<Element>, systemId: string) => void;
showKSpaceBG?: boolean;
isThickConnections?: boolean;
isShowBackgroundPattern?: boolean;
isSoftBackground?: boolean;
theme?: string;
}
const MapComp = ({
@@ -113,16 +115,21 @@ const MapComp = ({
isShowMinimap,
showKSpaceBG,
isThickConnections,
isShowBackgroundPattern,
isSoftBackground,
theme,
onAddSystem,
}: MapCompProps) => {
const { getNode } = useReactFlow();
const [nodes, , onNodesChange] = useNodesState<SolarSystemRawType>(initialNodes);
const [edges, , onEdgesChange] = useEdgesState<Edge<SolarSystemConnection>[]>(initialEdges);
const { getNode, getNodes } = useReactFlow();
const [nodes, , onNodesChange] = useNodesState<Node<SolarSystemRawType>>(initialNodes);
const [edges, , onEdgesChange] = useEdgesState<Edge<SolarSystemConnection>>(initialEdges);
useMapHandlers(refn, onSelectionChange);
useUpdateNodes(nodes);
const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers();
const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers({ onAddSystem });
const { handleConnectionContext, ...connectionCtxProps } = useContextMenuConnectionHandlers();
const { update } = useMapState();
const { variant, gap, size, color } = useBackgroundVars(theme);
const onConnect: OnConnect = useCallback(
params => {
@@ -181,6 +188,12 @@ const MapComp = ({
(changes: NodeChange[]) => {
const systemsIdsToRemove: string[] = [];
// prevents single node deselection on background / same node click
// allows deseletion of all nodes if multiple are currently selected
if (changes.length === 1 && changes[0].type == 'select' && changes[0].selected === false) {
changes[0].selected = getNodes().filter(node => node.selected).length === 1;
}
const nextChanges = changes.reduce((acc, change) => {
if (change.type !== 'remove') {
return [...acc, change];
@@ -218,7 +231,7 @@ const MapComp = ({
return (
<>
<div className={classes.MapRoot}>
<div className={clsx(classes.MapRoot, { [classes.BackgroundAlternateColor]: isSoftBackground })}>
<ReactFlow
nodes={nodes}
edges={edges}
@@ -265,7 +278,7 @@ const MapComp = ({
selectionMode={SelectionMode.Partial}
>
{isShowMinimap && <MiniMap pannable zoomable ariaLabel="Mini map" className={minimapClasses} />}
<Background />
{isShowBackgroundPattern && <Background variant={variant} gap={gap} size={size} color={color} />}
</ReactFlow>
{/* <button className="z-auto btn btn-primary absolute top-20 right-20" onClick={handleGetPassages}>
Test // DON NOT REMOVE

View File

@@ -32,6 +32,7 @@ const INITIAL_DATA: MapData = {
visibleNodes: new Set(),
showKSpaceBG: false,
isThickConnections: false,
userPermissions: {},
};
export interface MapContextProps {

View File

@@ -1,15 +1,17 @@
@import '@/hooks/Mapper/components/map/styles/eve-common-variables';
.ConnectionTimeEOL {
background-image: linear-gradient(207deg, transparent, #7452c3e3);
background-image: linear-gradient(207deg, transparent, var(--conn-time-eol));
}
.ConnectionFrigate {
background-image: linear-gradient(207deg, transparent, #325d88);
background-image: linear-gradient(207deg, transparent, var(--conn-frigate));
}
.ConnectionSave {
background-image: linear-gradient(207deg, transparent, rgba(155, 102, 45, 0.85));
background-image: linear-gradient(207deg, transparent, var(--conn-save));
}
.SelectedItem {
background-color: rgba(98, 98, 98, 0.33);
background-color: var(--selected-item-bg);
}

View File

@@ -3,10 +3,17 @@ import { ContextMenu } from 'primereact/contextmenu';
import { PrimeIcons } from 'primereact/api';
import { MenuItem } from 'primereact/menuitem';
import { Edge } from '@reactflow/core/dist/esm/types/edges';
import { MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
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 } from '@/hooks/Mapper/components/map/constants.ts';
import {
MASS_STATE_NAMES,
MASS_STATE_NAMES_ORDER,
SHIP_SIZES_NAMES,
SHIP_SIZES_NAMES_ORDER,
SHIP_SIZES_NAMES_SHORT,
SHIP_SIZES_SIZE,
} from '@/hooks/Mapper/components/map/constants.ts';
export interface ContextMenuConnectionProps {
contextMenuRef: RefObject<ContextMenu>;
@@ -35,46 +42,72 @@ export const ContextMenuConnection: React.FC<ContextMenuConnectionProps> = ({
}
const isFrigateSize = edge.data?.ship_size_type === ShipSizeStatus.small;
const isWormhole = edge.data?.type !== ConnectionType.gate;
return [
{
label: `EOL`,
className: clsx({
[classes.ConnectionTimeEOL]: edge.data?.time_status === TimeStatus.eol,
}),
icon: PrimeIcons.CLOCK,
command: onChangeTimeState,
},
{
label: `Frigate`,
className: clsx({
[classes.ConnectionFrigate]: isFrigateSize,
}),
icon: PrimeIcons.CLOUD,
command: () =>
onChangeShipSizeStatus(
edge.data?.ship_size_type === ShipSizeStatus.small ? ShipSizeStatus.normal : ShipSizeStatus.small,
),
},
{
label: `Save mass`,
className: clsx({
[classes.ConnectionSave]: edge.data?.locked,
}),
icon: PrimeIcons.LOCK,
command: () => onToggleMassSave(!edge.data?.locked),
},
...(!isFrigateSize
...(isWormhole
? [
{
label: `Mass status`,
icon: PrimeIcons.CHART_PIE,
items: MASS_STATE_NAMES_ORDER.map(x => ({
label: MASS_STATE_NAMES[x],
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
className: clsx({
[classes.SelectedItem]: edge.data?.mass_status === x,
[classes.SelectedItem]: edge.data?.ship_size_type === x,
}),
command: () => onChangeMassState(x),
command: () => onChangeShipSizeStatus(x),
})),
},
]
@@ -85,7 +118,7 @@ export const ContextMenuConnection: React.FC<ContextMenuConnectionProps> = ({
command: onDeleteConnection,
},
];
}, [edge, onChangeTimeState, onDeleteConnection, onChangeMassState, onChangeShipSizeStatus]);
}, [edge, onChangeTimeState, onDeleteConnection, onChangeShipSizeStatus, onToggleMassSave, onChangeMassState]);
return (
<>

View File

@@ -4,7 +4,7 @@ import { ContextMenu } from 'primereact/contextmenu';
import { useMapState } from '../../MapProvider.tsx';
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
import { Edge } from '@reactflow/core/dist/esm/types/edges';
import { MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
import { ConnectionType, MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
export const useContextMenuConnectionHandlers = () => {
@@ -47,6 +47,23 @@ export const useContextMenuConnectionHandlers = () => {
setEdge(undefined);
};
const onChangeType = useCallback((type: ConnectionType) => {
const { edge, outCommand } = ref.current;
if (!edge) {
return;
}
outCommand({
type: OutCommand.updateConnectionType,
data: {
source: edge.source,
target: edge.target,
value: type,
},
});
}, []);
const onChangeMassState = useCallback((status: MassState) => {
const { edge, outCommand } = ref.current;
@@ -80,14 +97,16 @@ export const useContextMenuConnectionHandlers = () => {
},
});
outCommand({
type: OutCommand.updateConnectionMassStatus,
data: {
source: edge.source,
target: edge.target,
value: MassState.normal,
},
});
if (status === ShipSizeStatus.small) {
outCommand({
type: OutCommand.updateConnectionMassStatus,
data: {
source: edge.source,
target: edge.target,
value: MassState.normal,
},
});
}
}, []);
const onToggleMassSave = useCallback((locked: boolean) => {
@@ -118,6 +137,7 @@ export const useContextMenuConnectionHandlers = () => {
contextMenuRef,
onDeleteConnection,
onChangeTimeState,
onChangeType,
onChangeMassState,
onChangeShipSizeStatus,
onToggleMassSave,

View File

@@ -1,14 +1,16 @@
import { useReactFlow, XYPosition } from 'reactflow';
import React, { useRef, useState } from 'react';
import React, { useCallback, useRef, useState } from 'react';
import { ContextMenu } from 'primereact/contextmenu';
import { useMapState } from '../../MapProvider.tsx';
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
import { OnMapAddSystemCallback } from '@/hooks/Mapper/components/map/map.types.ts';
export const useContextMenuRootHandlers = () => {
type UseContextMenuRootHandlers = {
onAddSystem?: OnMapAddSystemCallback;
};
export const useContextMenuRootHandlers = ({ onAddSystem }: UseContextMenuRootHandlers = {}) => {
const rf = useReactFlow();
const contextMenuRef = useRef<ContextMenu | null>(null);
const { outCommand } = useMapState();
const [position, setPosition] = useState<XYPosition | null>(null);
const handleRootContext = (e: React.MouseEvent<HTMLDivElement>) => {
@@ -18,14 +20,17 @@ export const useContextMenuRootHandlers = () => {
contextMenuRef.current?.show(e);
};
const onAddSystem = () => {
outCommand({ type: OutCommand.manualAddSystem, data: { coordinates: position } });
};
const ref = useRef({ onAddSystem, position });
ref.current = { onAddSystem, position };
const onAddSystemCallback = useCallback(() => {
ref.current.onAddSystem?.({ coordinates: position });
}, [position]);
return {
handleRootContext,
contextMenuRef,
onAddSystem,
onAddSystem: onAddSystemCallback,
};
};

View File

@@ -1,29 +1,57 @@
@import "@/hooks/Mapper/components/map/styles/neon-variables";
@import '@/hooks/Mapper/components/map/styles/eve-common-variables';
.react-flow__edge.selected {
.EdgePathBack {
stroke: $pastel-yellow;
.EdgePathBack {
fill: none;
stroke: #80a5c5;
stroke-width: 3px;
&.TimeCrit {
stroke: #f11ab2;
stroke-width: 4px;
}
&.Hovered {
stroke: #b5c8d9;
&.TimeCrit {
stroke: #ef7dce;
}
}
&.Tick {
stroke-width: 5px;
&.TimeCrit {
stroke-width: 6px;
}
}
&.Gate {
stroke: #9aff40;
}
}
.EdgePathFront {
fill: none;
stroke: #2c3844;
stroke-width: 2px;
&.MassVerge:not(&.Frigate) {
stroke: #af2900;
stroke: #af0000;
}
&.MassHalf:not(&.Frigate) {
stroke: #a85f00;
stroke: #ffd700;
}
&.Frigate {
stroke: #d4f0ff;
}
&.Gate {
stroke: #1c1e15;
}
&.Hovered {
stroke: #4e5d6c;
stroke-width: 2px;
@@ -50,41 +78,29 @@
}
}
.EdgePathBack {
fill: none;
stroke: #80a5c5;
stroke-width: 3px;
&.TimeCrit {
stroke: #f11ab2;
stroke-width: 4px;
}
&.Hovered {
stroke: #b5c8d9;
&.TimeCrit {
stroke: #ef7dce;
}
}
&.Tick {
stroke-width: 5px;
&.TimeCrit {
stroke-width: 6px;
}
}
}
.ClickPath {
fill: none;
stroke: none;
stroke-width: 8px;
}
.LinkLabel{
.Handle {
border: 1px solid var(--pastel-blue);
width: 5px;
height: 5px;
z-index: 1001;
&.Tick {
width: 7px;
height: 7px;
}
&.Right {
margin-left: 0px;
}
}
.LinkLabel {
font-size: 9px;
line-height: 10px;
padding: 2px 4px;
@@ -100,22 +116,3 @@
height: 8px;
font-size: 8px;
}
.Handle {
min-width: initial;
min-height: initial;
border: 1px solid #5a7d9a;
width: 5px;
height: 5px;
z-index: 1001;
&.Tick {
width: 7px;
height: 7px;
&.Right {
margin-left: 0px;
}
}
}

View File

@@ -1,13 +1,14 @@
import { useCallback, useMemo, useState } from 'react';
import classes from './SolarSystemEdge.module.scss';
import { EdgeLabelRenderer, EdgeProps, getBezierPath, Position, useStore } from 'reactflow';
import { EdgeLabelRenderer, EdgeProps, getBezierPath, getSmoothStepPath, Position, useStore } from 'reactflow';
import { getEdgeParams } from '@/hooks/Mapper/components/map/utils.ts';
import clsx from 'clsx';
import { MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
import { ConnectionType, MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
import { PrimeIcons } from 'primereact/api';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
import { SHIP_SIZES_DESCRIPTION, SHIP_SIZES_NAMES_SHORT } from '@/hooks/Mapper/components/map/constants.ts';
const MAP_TRANSLATES: Record<string, string> = {
[Position.Top]: 'translate(-48%, 0%)',
@@ -30,9 +31,18 @@ const MAP_OFFSETS: Record<string, { x: number; y: number }> = {
[Position.Right]: { x: 0, y: 0 },
};
export const SHIP_SIZES_COLORS = {
[ShipSizeStatus.small]: 'bg-indigo-400',
[ShipSizeStatus.medium]: 'bg-cyan-500',
[ShipSizeStatus.large]: '',
[ShipSizeStatus.freight]: 'bg-lime-400',
[ShipSizeStatus.capital]: 'bg-red-400',
};
export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }: EdgeProps<SolarSystemConnection>) => {
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 {
data: { isThickConnections },
@@ -45,7 +55,9 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
const offset = isThickConnections ? MAP_OFFSETS_TICK[targetPos] : MAP_OFFSETS[targetPos];
const [edgePath, labelX, labelY] = getBezierPath({
const method = isWormhole ? getBezierPath : getSmoothStepPath;
const [edgePath, labelX, labelY] = method({
sourceX: sx - offset.x,
sourceY: sy - offset.y,
sourcePosition: sourcePos,
@@ -53,8 +65,9 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
targetX: tx + offset.x,
targetY: ty + offset.y,
});
return [edgePath, labelX, labelY, sx, sy, tx, ty, sourcePos, targetPos];
}, [isThickConnections, sourceNode, targetNode]);
}, [isThickConnections, sourceNode, targetNode, isWormhole]);
if (!sourceNode || !targetNode || !data) {
return null;
@@ -66,8 +79,9 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
id={`back_${id}`}
className={clsx(classes.EdgePathBack, {
[classes.Tick]: isThickConnections,
[classes.TimeCrit]: data.time_status === TimeStatus.eol,
[classes.TimeCrit]: isWormhole && data.time_status === TimeStatus.eol,
[classes.Hovered]: hovered,
[classes.Gate]: !isWormhole,
})}
d={path}
markerEnd={markerEnd}
@@ -78,9 +92,10 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
className={clsx(classes.EdgePathFront, {
[classes.Tick]: isThickConnections,
[classes.Hovered]: hovered,
[classes.MassVerge]: data.mass_status === MassState.verge,
[classes.MassHalf]: data.mass_status === MassState.half,
[classes.Frigate]: data.ship_size_type === ShipSizeStatus.small,
[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,
})}
d={path}
markerEnd={markerEnd}
@@ -115,12 +130,12 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
/>
<div
className="absolute flex items-center gap-1"
className="absolute flex items-center gap-1 pointer-events-none"
style={{
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
}}
>
{data.locked && (
{isWormhole && data.locked && (
<WdTooltipWrapper
content="Save mass"
className={clsx(
@@ -131,6 +146,19 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
<span className={clsx(PrimeIcons.LOCK, classes.icon)} />
</WdTooltipWrapper>
)}
{isWormhole && data.ship_size_type !== ShipSizeStatus.large && (
<WdTooltipWrapper
content={SHIP_SIZES_DESCRIPTION[data.ship_size_type]}
className={clsx(
classes.LinkLabel,
'pointer-events-auto rounded opacity-100 cursor-auto text-neutral-900 font-bold',
SHIP_SIZES_COLORS[data.ship_size_type],
)}
>
{SHIP_SIZES_NAMES_SHORT[data.ship_size_type]}
</WdTooltipWrapper>
)}
</div>
</EdgeLabelRenderer>
</>

View File

@@ -1,284 +1,217 @@
@import '@/hooks/Mapper/components/map/styles/eve-common-variables';
$pastel-blue: #5a7d9a;
$pastel-pink: #d291bc;
$pastel-green: #88b04b;
$pastel-yellow: #ffdd59;
$dark-bg: #2d2d2d;
$text-color: #ffffff;
$tooltip-bg: #202020; // Темный фон для подсказок
.RootCustomNode {
display: flex;
width: 130px;
height: 34px;
flex-direction: column;
padding: 2px 6px;
font-size: 10px;
background-color: $tooltip-bg;
box-shadow: 0 0 5px rgba($dark-bg, 0.5);
border: 1px solid darken($pastel-blue, 10%);
border-radius: 5px;
background-color: var(--rf-node-bg-color, #202020) !important;
position: relative;
z-index: 1;
overflow: hidden;
font-weight: var(--rf-node-font-weight, bold);
color: var(--rf-text-color, #ffffff);
font-family: var(--rf-node-font, inherit);
}
&.Mataria,
&.Amarria,
&.Gallente,
&.Caldaria {
&::before {
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 {
&::before {
background-image: url('/images/mataria-180.png');
opacity: 0.6;
background-position-x: 1px;
background-position-y: -14px;
}
}
&.Caldaria {
&::before {
background-image: url('/images/caldaria-180.png');
opacity: 0.6;
background-position-x: 1px;
background-position-y: -10px;
}
}
&.Amarria {
&::before {
opacity: 0.45;
background-image: url('/images/amarr-180.png');
background-position-x: 0;
background-position-y: -13px;
}
}
&.Gallente {
&::before {
opacity: 0.5;
background-image: url('/images/gallente-180.png');
background-position-x: 1px;
background-position-y: 0;
}
}
&.selected {
border-color: $pastel-pink;
box-shadow: 0 0 10px #9a1af1c2;
}
.tooltip {
background-color: $tooltip-bg;
color: $text-color;
padding: 5px 10px;
/* Region backgrounds for optional tinted headers, etc. */
.Mataria,
.Amarria,
.Gallente,
.Caldaria {
&::before {
content: '';
position: absolute;
inset: 0;
background-size: cover;
background-position: 50% 50%;
background-repeat: no-repeat;
z-index: -1;
border-radius: 3px;
border: 1px solid $pastel-pink;
}
}
.Mataria::before {
background-image: url('/images/mataria-180.png');
opacity: 0.6;
background-position-x: 1px;
background-position-y: -14px;
}
.Caldaria::before {
background-image: url('/images/caldaria-180.png');
opacity: 0.6;
background-position-x: 1px;
background-position-y: -10px;
}
.Amarria::before {
opacity: 0.45;
background-image: url('/images/amarr-180.png');
background-position-x: 0;
background-position-y: -13px;
}
.Gallente::before {
opacity: 0.5;
background-image: url('/images/gallente-180.png');
background-position-x: 1px;
background-position-y: 0;
}
&.eve-system-status-home {
border: 1px solid darken($eve-solar-system-status-color-home, 30%);
background-image: linear-gradient(275deg, $eve-solar-system-status-friendly, transparent);
.selected {
border-color: var(--pastel-pink, #d291bc);
box-shadow: 0 0 10px #9a1af1c2;
}
&.selected {
border-color: $eve-solar-system-status-color-home;
}
.eve-system-status-home {
border: 1px solid var(--eve-solar-system-status-color-home-dark30);
background-image: linear-gradient(
275deg,
var(--eve-solar-system-status-friendly),
transparent
);
&.selected {
border-color: var(--eve-solar-system-status-color-home);
}
&.eve-system-status-friendly {
border: 1px solid darken($eve-solar-system-status-color-friendly, 20%);
background-image: linear-gradient(275deg, darken($eve-solar-system-status-friendly, 30%), transparent);
&.selected {
border-color: darken($eve-solar-system-status-color-friendly, 5%);
}
}
.eve-system-status-friendly {
border: 1px solid var(--eve-solar-system-status-color-friendly-dark20);
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 {
border: 1px solid darken($eve-solar-system-status-color-lookingFor, 15%);
background-image: linear-gradient(275deg, #45ff8f2f, #457fff2f);
&.selected {
border-color: $pastel-pink;
}
}
&.eve-system-status-warning {
background-image: linear-gradient(275deg, $eve-solar-system-status-warning, transparent);
}
&.eve-system-status-dangerous {
background-image: linear-gradient(275deg, $eve-solar-system-status-dangerous, transparent);
}
&.eve-system-status-target {
background-image: linear-gradient(275deg, $eve-solar-system-status-target, transparent);
}
.eve-system-status-lookingFor {
border: 1px solid var(--eve-solar-system-status-color-lookingFor-dark15);
background-image: linear-gradient(275deg, #45ff8f2f, #457fff2f);
&.selected {
border-color: var(--pastel-pink, #d291bc);
}
}
.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
);
}
.Bookmarks {
position: absolute;
width: 100%;
z-index: 0;
z-index: 1;
display: flex;
left: 4px;
& > .Bookmark {
min-width: 13px;
height: 22px;
position: relative;
top: -13px;
border-radius: 5px;
color: #ffffff;
font-size: 8px;
text-align: center;
padding-top: 2px;
font-weight: bolder;
padding-left: 3px;
padding-right: 3px;
//background-color: #833ca4;
&:not(:first-child) {
box-shadow: inset 4px -3px 4px rgba(0, 0, 0, 0.3);
}
}
.BookmarkWithIcon {
display: flex;
justify-content: center;
align-items: center;
margin-top: -2px;
text-shadow: 0 0 3px rgba(0, 0, 0, 1);
padding-right: 2px;
& > .icon {
width: 8px;
height: 8px;
font-size: 8px;
}
& > .text {
margin-top: 1px;
font-size: 9px;
}
}
}
.icon {
width: 8px;
height: 8px;
.Bookmark {
min-width: 13px;
height: 22px;
position: relative;
top: -13px;
border-radius: 5px;
color: #ffffff;
font-size: 8px;
text-align: center;
padding-top: 2px;
font-weight: bolder;
padding-left: 3px;
padding-right: 3px;
&:not(:first-child) {
box-shadow: inset 4px -3px 4px rgba(0, 0, 0, 0.3);
}
}
.BookmarkWithIcon {
display: flex;
justify-content: center;
align-items: center;
margin-top: -2px;
text-shadow: 0 0 3px rgba(0, 0, 0, 1);
padding-right: 2px;
.icon {
width: 8px;
height: 8px;
font-size: 8px;
}
.text {
margin-top: 1px;
font-size: 9px;
}
}
.HeadRow {
display: flex;
align-items: center;
gap: 3px;
.TagTitle {
font-size: 11px;
line-height: 14px;
font-weight: 500;
font-weight: bold;
text-shadow: 0 0 2px rgba(231, 146, 52, 0.73);
color: var(--rf-tag-color, #ffb01d);
}
/* The top row (HeadRow). */
.HeadRow {
position: relative;
top: 1px;
.classTitle {
font-size: 11px;
font-weight: bold;
text-shadow: 0 0 2px rgb(0 0 0 / 73%);
text-shadow: 0 0 2px rgba(0, 0, 0, 0.73);
font-size: 11px;
}
.TagTitle {
font-size: 11px;
font-weight: bold;
text-shadow: 0 0 2px rgba(231, 146, 52, 0.73);
color: #ffb01d;
}
/* Firefox kostyl */
@-moz-document url-prefix() {
.classSystemName {
font-family: inherit !important;
font-family: var(--rf-node-font, inherit);
font-weight: bold;
}
}
.classSystemName {
//font-weight: bold;
}
.solarSystemName {
}
}
/* Usually ~19px tall for bottom row. */
.BottomRow {
display: flex;
justify-content: space-between;
align-items: center;
height: 19px;
.localCounter {
display: flex;
//align-items: center;
gap: 2px;
& > i {
position: relative;
top: 1px;
}
& > span {
font-size: 9px;
line-height: 9px;
font-weight: 500;
//margin-top: 1px;
}
}
.hasUserCharacters {
color: var(--rf-has-user-characters, #fbbf24);
}
}
/* The systemName overflow + effect icon, etc. */
.systemNameOverflow {
flex-grow: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-family: sans-serif;
}
.effect {
width: 8px;
height: 8px;
margin-top: -2px;
box-sizing: border-box;
border-radius: 2px;
margin-left: 1px;
}
.statics {
display: flex;
gap: 2px;
font-size: 8px;
& > * {
line-height: 10px;
}
/* Firefox kostyl */
@-moz-document url-prefix() {
position: relative;
top: -1px;
}
}
/* The node's invisible handles/double-click overlay. */
.Handlers {
position: absolute;
z-index: 2;
@@ -288,29 +221,25 @@ $tooltip-bg: #202020; // Темный фон для подсказок
height: 100%;
}
/* The little React Flow handles on each edge. */
.Handle {
min-width: initial;
min-height: initial;
border: 1px solid $pastel-blue;
border: 1px solid var(--pastel-blue, #5a7d9a);
width: 5px;
height: 5px;
&.selected {
border-color: $pastel-pink;
border-color: var(--pastel-pink, #d291bc);
}
&.HandleTop {
top: -2px;
}
&.HandleRight {
right: -2px;
}
&.HandleBottom {
bottom: -2px;
}
&.HandleLeft {
left: -2px;
}
@@ -322,17 +251,28 @@ $tooltip-bg: #202020; // Темный фон для подсказок
&.HandleTop {
top: -3px;
}
&.HandleRight {
right: -3px;
}
&.HandleBottom {
bottom: -3px;
}
&.HandleLeft {
left: -3px;
}
}
}
/* Unsplashed signature containers. */
.Unsplashed {
position: absolute;
width: calc(50% - 4px);
z-index: -1;
display: flex;
flex-wrap: wrap;
gap: 2px;
left: 2px;
&--right {
left: calc(50% + 6px);
}
}

View File

@@ -3,6 +3,10 @@ import { Handle, Position, WrapNodeProps } from 'reactflow';
import { MapSolarSystemType } from '../../map.types';
import classes from './SolarSystemNode.module.scss';
import clsx from 'clsx';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useMapGetOption } from '@/hooks/Mapper/mapRootProvider/hooks/api';
import {
EFFECT_BACKGROUND_STYLES,
LABELS_INFO,
@@ -12,8 +16,9 @@ import {
} from '@/hooks/Mapper/components/map/constants.ts';
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
import { WormholeClassComp } from '@/hooks/Mapper/components/map/components/WormholeClassComp';
import { UnsplashedSignature } from '@/hooks/Mapper/components/map/components/UnsplashedSignature';
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
import { getSystemClassStyles } from '@/hooks/Mapper/components/map/helpers';
import { getSystemClassStyles, prepareUnsplashedChunks } from '@/hooks/Mapper/components/map/helpers';
import { sortWHClasses } from '@/hooks/Mapper/helpers';
import { PrimeIcons } from 'primereact/api';
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager.ts';
@@ -50,6 +55,11 @@ export const getActivityType = (count: number) => {
// eslint-disable-next-line react/display-name
export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarSystemType>) => {
const { interfaceSettings } = useMapRootState();
const { isShowUnsplashedSignatures } = interfaceSettings;
const isTempSystemNameEnabled = useMapGetOption('show_temp_system_name') === 'true';
const {
system_class,
security,
@@ -63,9 +73,10 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
solar_system_name,
} = data.system_static_info;
const { locked, name, tag, status, labels, id } = data || {};
const signatures = data.system_signatures;
const { locked, name, tag, status, labels, id, temporary_name: temporaryName } = data || {};
const customName = solar_system_name !== name ? name : undefined;
const {
data: {
@@ -87,8 +98,9 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
const visible = useMemo(() => visibleNodes.has(id), [id, visibleNodes]);
const charactersInSystem = useMemo(() => {
return characters.filter(c => c.location?.solar_system_id === solar_system_id).filter(c => c.online);
// eslint-disable-next-line
return characters
.filter(c => c.location?.solar_system_id === solar_system_id)
.filter(c => c.online);
}, [characters, presentCharacters, solar_system_id]);
const isWormhole = isWormholeSpace(system_class);
@@ -106,7 +118,6 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
if (!systemKills) {
return null;
}
return systemKills;
}, [kills, solar_system_id]);
@@ -128,26 +139,44 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
const space = showKSpaceBG ? REGIONS_MAP[region_id] : '';
const regionClass = showKSpaceBG ? SpaceToClass[space] : null;
const systemName = (isTempSystemNameEnabled && temporaryName) || solar_system_name;
const customName = (isTempSystemNameEnabled && temporaryName && name) || (solar_system_name !== name && name);
const [unsplashedLeft, unsplashedRight] = useMemo(() => {
if (!isShowUnsplashedSignatures) {
return [[], []];
}
return prepareUnsplashedChunks(
signatures
.filter(s => s.group === 'Wormhole' && !s.linked_system)
.map(s => ({
eve_id: s.eve_id,
type: s.type,
custom_info: s.custom_info,
})),
);
}, [isShowUnsplashedSignatures, signatures]);
return (
<>
{visible && (
<div className={classes.Bookmarks}>
{labelCustom !== '' && (
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.custom)}>
<span className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] ">{labelCustom}</span>
<span className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]">{labelCustom}</span>
</div>
)}
{is_shattered && (
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.shattered)}>
<span className={clsx('pi pi-chart-pie', classes.icon)} />
<span className={clsx('pi pi-chart-pie text-[0.55rem]')} />
</div>
)}
{killsCount && (
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[getActivityType(killsCount)])}>
<div className={clsx(classes.BookmarkWithIcon)}>
<span className={clsx(PrimeIcons.BOLT, classes.icon)} />
<span className={clsx(PrimeIcons.BOLT, 'text-[0.65rem]')} />
<span className={clsx(classes.text)}>{killsCount}</span>
</div>
</div>
@@ -160,32 +189,44 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
))}
</div>
)}
<div
className={clsx(classes.RootCustomNode, regionClass, classes[STATUS_CLASSES[status]], {
[classes.selected]: selected,
})}
onMouseDownCapture={dbClick}
className={clsx(
classes.RootCustomNode,
regionClass,
classes[STATUS_CLASSES[status]],
{ [classes.selected]: selected },
'flex flex-col w-[130px] h-[34px]',
'px-[6px] pt-[2px] pb-[3px] text-[10px]',
'leading-[1] space-y-[1px]',
'shadow-[0_0_5px_rgba(45,45,45,0.5)]',
'border border-[var(--pastel-blue-darken10)] rounded-[5px]'
)}
>
{visible && (
<>
<div className={classes.HeadRow}>
<div className={clsx(classes.HeadRow, 'flex items-center gap-[3px]')}>
<div className={clsx(classes.classTitle, classTitleColor, '[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]')}>
{class_title ?? '-'}
</div>
{tag != null && tag !== '' && (
<div className={clsx(classes.TagTitle, 'text-sky-400 font-medium')}>{tag}</div>
<div className={clsx(classes.TagTitle, "color: #38bdf8; font-weight: 500;")}>
{tag}
</div>
)}
<div
className={clsx(
classes.classSystemName,
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] flex-grow overflow-hidden text-ellipsis whitespace-nowrap font-sans',
'flex-grow overflow-hidden text-ellipsis whitespace-nowrap font-sans',
)}
>
{solar_system_name}
{systemName}
</div>
{isWormhole && (
<div className={classes.statics}>
<div className={clsx(classes.statics, 'flex gap-[2px] text-[8px]')}>
{sortedStatics.map(x => (
<WormholeClassComp key={x} id={x} />
))}
@@ -197,47 +238,56 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
)}
</div>
<div className={clsx(classes.BottomRow, 'flex items-center justify-between')}>
<div className={clsx(classes.BottomRow, 'flex items-center gap-[3px]')}>
{customName && (
<div className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] text-blue-300 whitespace-nowrap overflow-hidden text-ellipsis mr-0.5">
<div className={clsx('font-bold', classes.customName)}>
{customName}
</div>
)}
{!isWormhole && !customName && (
<div
className={clsx(
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] text-stone-300 whitespace-nowrap overflow-hidden text-ellipsis mr-0.5',
)}
>
{region_name}
</div>
)}
{!isWormhole && !customName && <div className={clsx(classes.regionName)}>{region_name}</div>}
{isWormhole && !customName && <div />}
<div className="flex items-center justify-end">
<div className="flex gap-1 items-center">
{locked && <i className={PrimeIcons.LOCK} style={{ fontSize: '0.45rem', fontWeight: 'bold' }}></i>}
{hubs.includes(solar_system_id.toString()) && (
<i className={PrimeIcons.MAP_MARKER} style={{ fontSize: '0.45rem', fontWeight: 'bold' }}></i>
)}
{charactersInSystem.length > 0 && (
<div className={clsx(classes.localCounter, { ['text-amber-300']: hasUserCharacters })}>
<i className="pi pi-users" style={{ fontSize: '0.50rem' }}></i>
<span className="font-sans">{charactersInSystem.length}</span>
</div>
)}
</div>
<div className="flex items-center ml-auto gap-[2px]">
{locked && (
<i className={clsx(PrimeIcons.LOCK, 'text-[0.45rem] font-bold')} />
)}
{hubs.includes(solar_system_id.toString()) && (
<i className={clsx(PrimeIcons.MAP_MARKER, 'text-[0.45rem] font-bold')} />
)}
{charactersInSystem.length > 0 && (
<div
className={clsx(
classes.localCounter,
{ [classes.hasUserCharacters]: hasUserCharacters },
'flex gap-[2px]'
)}
>
<i className="pi pi-users text-[0.50rem]" />
<span className="text-[0.65rem]">{charactersInSystem.length}</span>
</div>
)}
</div>
</div>
</>
)}
</div>
<div onMouseDownCapture={dbClick} className={classes.Handlers}>
{visible && isShowUnsplashedSignatures && (
<div className={classes.Unsplashed}>
{unsplashedLeft.map(x => (
<UnsplashedSignature key={x.sig_id} signature={x} />
))}
</div>
)}
{visible && isShowUnsplashedSignatures && (
<div className={clsx(classes.Unsplashed, classes['Unsplashed--right'])}>
{unsplashedRight.map(x => (
<UnsplashedSignature key={x.sig_id} signature={x} />
))}
</div>
)}
<div className={classes.Handlers}>
<Handle
type="source"
className={clsx(classes.Handle, classes.HandleTop, {

View File

@@ -0,0 +1,18 @@
@import '@/hooks/Mapper/components/map/styles/eve-common-variables';
.Signature {
position: relative;
top: 3px;
display: block;
& > .Box {
width: 13px;
height: 4px;
border-radius: 4px;
color: var(--text-color);
font-size: 8px;
text-align: center;
font-weight: bolder;
display: block;
}
}

View File

@@ -0,0 +1,65 @@
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
import { InfoDrawer } from '@/hooks/Mapper/components/ui-kit';
import classes from './UnsplashedSignature.module.scss';
import { SystemSignature } from '@/hooks/Mapper/types/signatures';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { WORMHOLE_CLASS_STYLES, WORMHOLES_ADDITIONAL_INFO } from '@/hooks/Mapper/components/map/constants.ts';
import { useMemo } from 'react';
import clsx from 'clsx';
import { renderInfoColumn } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
import { k162Types } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureK162TypeSelect';
interface UnsplashedSignatureProps {
signature: SystemSignature;
}
export const UnsplashedSignature = ({ signature }: UnsplashedSignatureProps) => {
const {
data: { wormholesData },
} = useMapRootState();
const whData = useMemo(() => wormholesData[signature.type], [signature.type, wormholesData]);
const whClass = useMemo(() => (whData ? WORMHOLES_ADDITIONAL_INFO[whData.dest] : null), [whData]);
const k162TypeOption = useMemo(() => {
if (!signature.custom_info) {
return null;
}
const customInfo = JSON.parse(signature.custom_info);
if (!customInfo.k162Type) {
return null;
}
return k162Types.find(x => x.value === customInfo.k162Type);
}, [signature]);
const whClassStyle = useMemo(() => {
if (signature.type === 'K162' && k162TypeOption) {
const k162Data = wormholesData[k162TypeOption.whClassName];
const k162Class = k162Data ? WORMHOLES_ADDITIONAL_INFO[k162Data.dest] : null;
return k162Class ? WORMHOLE_CLASS_STYLES[k162Class.wormholeClassID] : '';
}
return whClass ? WORMHOLE_CLASS_STYLES[whClass.wormholeClassID] : '';
}, [signature, whClass, k162TypeOption, wormholesData]);
return (
<WdTooltipWrapper
className={clsx(classes.Signature)}
content={
(
<div className="flex flex-col gap-1">
<InfoDrawer title={<b className="text-slate-50">{signature.eve_id}</b>}>
{renderInfoColumn(signature)}
</InfoDrawer>
</div>
) as React.ReactNode
}
>
<div className={clsx(classes.Box, whClassStyle)}>
<svg width="13" height="4" viewBox="0 0 13 4" xmlns="http://www.w3.org/2000/svg">
<rect width="13" height="4" rx="2" className={whClassStyle} fill="currentColor" />
</svg>
</div>
</WdTooltipWrapper>
);
};

View File

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

View File

@@ -1,4 +1,4 @@
import { MassState } from '@/hooks/Mapper/types';
import { ConnectionType, MassState, ShipSizeStatus } from '@/hooks/Mapper/types';
export enum SOLAR_SYSTEM_CLASS_IDS {
ccp1 = -1,
@@ -712,6 +712,13 @@ 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 = {
[ConnectionType.wormhole]: 'Wormhole',
[ConnectionType.gate]: 'Gate',
};
export const MASS_STATE_NAMES_ORDER = [MassState.verge, MassState.half, MassState.normal];
export const MASS_STATE_NAMES = {
@@ -720,16 +727,41 @@ export const MASS_STATE_NAMES = {
[MassState.verge]: 'Verge of collapse',
};
// export const SHIP_SIZES_NAMES_ORDER = [
// ShipSizeStatus.small,
// ShipSizeStatus.normal,
// // ShipSizeStatus.large,
// // ShipSizeStatus.capital,
// ];
//
// export const SHIP_SIZES_NAMES = {
// [ShipSizeStatus.small]: 'Frigate',
// [ShipSizeStatus.normal]: 'Normal',
// // [ShipSizeStatus.large]: 'Normal',
// // [ShipSizeStatus.capital]: 'Normal',
// };
export const SHIP_SIZES_NAMES_ORDER = [
ShipSizeStatus.small,
ShipSizeStatus.medium,
ShipSizeStatus.large,
ShipSizeStatus.freight,
ShipSizeStatus.capital,
];
export const SHIP_SIZES_NAMES = {
[ShipSizeStatus.small]: 'Frigate',
[ShipSizeStatus.medium]: 'Medium',
[ShipSizeStatus.large]: 'Normal',
[ShipSizeStatus.freight]: 'Huge',
[ShipSizeStatus.capital]: 'Capital',
};
export const SHIP_SIZES_SIZE = {
[ShipSizeStatus.small]: '5K',
[ShipSizeStatus.medium]: '62K',
[ShipSizeStatus.large]: '375K',
[ShipSizeStatus.freight]: '1M',
[ShipSizeStatus.capital]: '2M',
};
export const SHIP_SIZES_DESCRIPTION = {
[ShipSizeStatus.small]: 'Frigate wormhole - up to Destroyer | 5K t.',
[ShipSizeStatus.medium]: 'Cruise wormhole - up to Battlecruiser | 62K t.',
[ShipSizeStatus.large]: 'Large wormhole - up to Battleship | 375K t.',
[ShipSizeStatus.freight]: 'Huge wormhole - up to Freighter | 1M t.',
[ShipSizeStatus.capital]: 'Capital wormhole - up to Capital | 2M t.',
};
export const SHIP_SIZES_NAMES_SHORT = {
[ShipSizeStatus.small]: 'S',
[ShipSizeStatus.medium]: 'M',
[ShipSizeStatus.large]: 'L',
[ShipSizeStatus.freight]: 'H',
[ShipSizeStatus.capital]: 'XL',
};

View File

@@ -3,3 +3,4 @@ export * from './convertSystem2Node';
export * from './getSystemClassStyles';
export * from './getShapeClass';
export * from './getBackgroundClass';
export * from './prepareUnsplashedChunks';

View File

@@ -0,0 +1,27 @@
// Helper function to split an array into chunks of size
const chunkArray = (array: any[], size: number) => {
const chunks = [];
for (let i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size));
}
return chunks;
};
export const prepareUnsplashedChunks = (items: any[]) => {
// Split the items into chunks of 4
const chunks = chunkArray(items, 4);
// Get the column elements
const leftColumn: any[] = [];
const rightColumn: any[] = [];
chunks.forEach((chunk, index) => {
const column = index % 2 === 0 ? leftColumn : rightColumn;
chunk.forEach(item => {
column.push(item);
});
});
return [leftColumn, rightColumn];
};

View File

@@ -12,6 +12,7 @@ export const useMapAddSystems = () => {
return useCallback((systems: CommandAddSystems) => {
const { rf } = ref.current;
const nodes = rf.getNodes();
const prepared: Node[] = systems.filter(x => !nodes.some(y => x.id === y.id)).map(convertSystem2Node);
rf.addNodes(prepared);
}, []);

View File

@@ -1,2 +1,3 @@
export * from './useMapHandlers';
export * from './useUpdateNodes';
export * from './useNodesEdgesState';

View File

@@ -0,0 +1,42 @@
import { useEffect, useState } from 'react';
import { BackgroundVariant } from 'reactflow';
export function useBackgroundVars(themeName?: string) {
const [variant, setVariant] = useState<BackgroundVariant>(BackgroundVariant.Dots);
const [gap, setGap] = useState<number>(16);
const [size, setSize] = useState<number>(1);
const [color, setColor] = useState('#81818b')
useEffect(() => {
let themeEl = document.querySelector('.pathfinder-theme, .neon-theme');
if (!themeEl) {
themeEl = document.documentElement;
}
const style = getComputedStyle(themeEl as HTMLElement);
const rawVariant = style.getPropertyValue('--rf-bg-variant').replace(/['"]/g, '').trim().toLowerCase();
let finalVariant: BackgroundVariant = BackgroundVariant.Dots;
if (rawVariant === 'lines') {
finalVariant = BackgroundVariant.Lines;
} else if (rawVariant === 'cross') {
finalVariant = BackgroundVariant.Cross;
}
const cssVarGap = style.getPropertyValue('--rf-bg-gap');
const cssVarSize = style.getPropertyValue('--rf-bg-size');
const cssColor = style.getPropertyValue('--rf-bg-pattern-color');
const gapNum = parseInt(cssVarGap, 10) || 16;
const sizeNum = parseInt(cssVarSize, 10) || 1;
setVariant(finalVariant);
setGap(gapNum);
setSize(sizeNum);
setColor(cssColor);
}, [themeName]);
return { variant, gap, size, color };
}

View File

@@ -112,13 +112,17 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
connections: [],
});
selectSystem(systemId as CommandSelectSystem);
}, 100);
}, 500);
break;
case Commands.routes:
// do nothing here
break;
case Commands.signaturesUpdated:
// do nothing here
break;
case Commands.linkSignatureToSystem:
// do nothing here
break;

View File

@@ -0,0 +1,36 @@
import { useState, useCallback, type Dispatch, type SetStateAction } from 'react';
import { applyNodeChanges, applyEdgeChanges } from '../utils/changes';
import { OnNodesChange, Edge, OnEdgesChange, Node } from 'reactflow';
/**
* Hook for managing the state of nodes - should only be used for prototyping / simple use cases.
*
* @public
* @param initialNodes
* @returns an array [nodes, setNodes, onNodesChange]
*/
export function useNodesState<NodeType extends Node>(
initialNodes: NodeType[],
): [NodeType[], Dispatch<SetStateAction<NodeType[]>>, OnNodesChange] {
const [nodes, setNodes] = useState(initialNodes);
const onNodesChange: OnNodesChange = useCallback(changes => setNodes(nds => applyNodeChanges(changes, nds)), []);
return [nodes, setNodes, onNodesChange];
}
/**
* Hook for managing the state of edges - should only be used for prototyping / simple use cases.
*
* @public
* @param initialEdges
* @returns an array [edges, setEdges, onEdgesChange]
*/
export function useEdgesState<EdgeType extends Edge = Edge>(
initialEdges: EdgeType[],
): [EdgeType[], Dispatch<SetStateAction<EdgeType[]>>, OnEdgesChange] {
const [edges, setEdges] = useState(initialEdges);
const onEdgesChange: OnEdgesChange = useCallback(changes => setEdges(eds => applyEdgeChanges(changes, eds)), []);
return [edges, setEdges, onEdgesChange];
}

View File

@@ -1,5 +1,6 @@
import { SolarSystemRawType } from '@/hooks/Mapper/types/system';
import { SolarSystemConnection } from '@/hooks/Mapper/types';
import { XYPosition } from 'reactflow';
export type MapSolarSystemType = Omit<SolarSystemRawType, 'position'>;
@@ -7,3 +8,5 @@ export type OnMapSelectionChange = (event: {
systems: string[];
connections: Pick<SolarSystemConnection, 'source' | 'target'>[];
}) => void;
export type OnMapAddSystemCallback = (props: { coordinates: XYPosition | null }) => void;

View File

@@ -1,78 +1,114 @@
$eve-link-color-default: #333;
$eve-link-color-top-mass-0: #333;
$eve-link-color-top-mass-1: #5a4520;
$eve-link-color-top-mass-2: #672c2c;
$eve-link-color-middle-mass-0: #333;
$eve-link-color-middle-mass-1: #333;
$eve-link-color-middle-mass-2: #333;
$eve-link-color-middle-time-0: #5c5c5c;
$eve-link-color-middle-time-1: #ff00cd;
$eve-link-color-middle-time-1-border: #99f3ff;
:root {
--pastel-blue: #5a7d9a;
--pastel-pink: #d291bc;
--pastel-green: #88b04b;
--pastel-yellow: #ffdd59;
--dark-bg: #2d2d2d;
--text-color: #ffffff;
--tooltip-bg: #202020;
$eve-link-color-top-mass-1-time-1: #796300;
$eve-link-color-top-mass-2-time-1: #8c1717;
$eve-link-color-temp: orange;
--pastel-blue-darken10: #4f6b86;
--pastel-blue-lighten10: #6da3af;
--pastel-pink-darken10: #bb7ca9;
--pastel-pink-lighten10: #e0a6cb;
$eve-effect-pulsar: #40aef5;
$eve-effect-magnetar: #f058f8;
$eve-effect-wolfRayet: #ef7843;
$eve-effect-blackHole: #1b1b1b;
$eve-effect-cataclysmicVariable: #ffea90;
$eve-effect-redGiant: #fd3c3c;
$eve-effect-dazhLiminalityLocus: #ff6464;
$eve-effect-imperialStellarObservatory: #6991ce;
$eve-effect-stateStellarObservatory: #6991ce;
$eve-effect-republicStellarObservatory: #6991ce;
$eve-effect-federalStellarObservatory: #6991ce;
--pastel-green-darken10: #79a244;
--pastel-green-lighten10: #99cf52;
$eve-wh-type-color-high: #5dffd2;
$eve-wh-type-color-low: #f79400;
$eve-wh-type-color-null: #fc3c3c;
$eve-wh-type-color-c1: #69bfce;
$eve-wh-type-color-c2: #6991ce;
$eve-wh-type-color-c3: #a8cb70;
$eve-wh-type-color-c4: #e39c68;
$eve-wh-type-color-c5: #de8686;
$eve-wh-type-color-c6: #e76363;
$eve-wh-type-color-c13: #988cb5;
$eve-wh-type-color-drifter: #ff44f6;
$eve-wh-type-color-thera: #ffffff;
$eve-wh-type-color-zarzakh: #212121;
--pastel-yellow-darken10: #e6c44f;
--pastel-yellow-lighten10: #ffe874;
$eve-security-color-10: #2c74df;
$eve-security-color-09: #3998e8;
$eve-security-color-08: #4dcbf5;
$eve-security-color-07: #60d8a2;
$eve-security-color-06: #71e454;
$eve-security-color-05: #f2fc81;
$eve-security-color-04: #d96c07;
$eve-security-color-03: #cb440f;
$eve-security-color-02: #b91117;
$eve-security-color-01: #732020;
$eve-security-color-00: #8b3263;
$eve-security-color-m-01: #8b3263;
$eve-security-color-m-02: #8b3263;
$eve-security-color-m-03: #8b3263;
$eve-security-color-m-04: #8b3263;
$eve-security-color-m-05: #8b3263;
$eve-security-color-m-06: #8b3263;
$eve-security-color-m-07: #8b3263;
$eve-security-color-m-08: #8b3263;
$eve-security-color-m-09: #8b3263;
$eve-security-color-m-10: #8b3263;
/* Eve Link Colors */
--eve-link-color-default: #333;
--eve-link-color-top-mass-0: #333;
--eve-link-color-top-mass-1: #5a4520;
--eve-link-color-top-mass-2: #672c2c;
--eve-link-color-middle-mass-0: #333;
--eve-link-color-middle-mass-1: #333;
--eve-link-color-middle-mass-2: #333;
--eve-link-color-middle-time-0: #5c5c5c;
--eve-link-color-middle-time-1: #ff00cd;
--eve-link-color-middle-time-1-border: #99f3ff;
--eve-link-color-top-mass-1-time-1: #796300;
--eve-link-color-top-mass-2-time-1: #8c1717;
--eve-link-color-temp: orange;
$eve-solar-system-status-unknown: transparent;
$eve-solar-system-status-friendly: #3bbd3952;
$eve-solar-system-status-warning: #906518a6;
$eve-solar-system-status-target: #b439ff6b;
$eve-solar-system-status-dangerous: #d54040;
$eve-solar-system-status-lookingFor: rgba(67, 176, 253, 0.48);
$eve-solar-system-status-home: rgb(197, 253, 67);
/* Wormhole Effects */
--eve-effect-pulsar: #40aef5;
--eve-effect-magnetar: #f058f8;
--eve-effect-wolfRayet: #ef7843;
--eve-effect-blackHole: #1b1b1b;
--eve-effect-cataclysmicVariable: #ffea90;
--eve-effect-redGiant: #fd3c3c;
--eve-effect-dazhLiminalityLocus: #ff6464;
--eve-effect-imperialStellarObservatory: #6991ce;
--eve-effect-stateStellarObservatory: #6991ce;
--eve-effect-republicStellarObservatory: #6991ce;
--eve-effect-federalStellarObservatory: #6991ce;
$eve-solar-system-status-color-unknown: transparent;
$eve-solar-system-status-color-friendly: #3bbd39;
$eve-solar-system-status-color-warning: #ffb93b;
$eve-solar-system-status-color-target: #b439ff;
$eve-solar-system-status-color-dangerous: #d54040;
$eve-solar-system-status-color-lookingFor: #43c2fd;
$eve-solar-system-status-color-home: rgb(197, 253, 67);
/* WH Types */
--eve-wh-type-color-high: #5dffd2;
--eve-wh-type-color-low: #f79400;
--eve-wh-type-color-null: #fc3c3c;
--eve-wh-type-color-c1: #69bfce;
--eve-wh-type-color-c2: #6991ce;
--eve-wh-type-color-c3: #a8cb70;
--eve-wh-type-color-c4: #e39c68;
--eve-wh-type-color-c5: #de8686;
--eve-wh-type-color-c6: #e76363;
--eve-wh-type-color-c13: #988cb5;
--eve-wh-type-color-drifter: #ff44f6;
--eve-wh-type-color-thera: #ffffff;
--eve-wh-type-color-zarzakh: #212121;
/* Security Colors */
--eve-security-color-10: #2c74df;
--eve-security-color-09: #3998e8;
--eve-security-color-08: #4dcbf5;
--eve-security-color-07: #60d8a2;
--eve-security-color-06: #71e454;
--eve-security-color-05: #f2fc81;
--eve-security-color-04: #d96c07;
--eve-security-color-03: #cb440f;
--eve-security-color-02: #b91117;
--eve-security-color-01: #732020;
--eve-security-color-00: #8b3263;
--eve-security-color-m-01: #8b3263;
--eve-security-color-m-02: #8b3263;
--eve-security-color-m-03: #8b3263;
--eve-security-color-m-04: #8b3263;
--eve-security-color-m-05: #8b3263;
--eve-security-color-m-06: #8b3263;
--eve-security-color-m-07: #8b3263;
--eve-security-color-m-08: #8b3263;
--eve-security-color-m-09: #8b3263;
--eve-security-color-m-10: #8b3263;
/* Solar System Status */
--eve-solar-system-status-unknown: transparent;
--eve-solar-system-status-friendly: #3bbd3952;
--eve-solar-system-status-warning: #906518a6;
--eve-solar-system-status-target: #b439ff6b;
--eve-solar-system-status-dangerous: #d54040;
--eve-solar-system-status-lookingFor: rgba(67, 176, 253, 0.48);
--eve-solar-system-status-home: rgb(197, 253, 67);
--eve-solar-system-status-color-unknown: transparent;
--eve-solar-system-status-color-friendly: #3bbd39;
--eve-solar-system-status-color-warning: #ffb93b;
--eve-solar-system-status-color-target: #b439ff;
--eve-solar-system-status-color-dangerous: #d54040;
--eve-solar-system-status-color-lookingFor: #43c2fd;
--eve-solar-system-status-color-home: rgb(197, 253, 67);
--eve-solar-system-status-color-friendly-dark20: #2d9b2e;
--eve-solar-system-status-friendly-dark30: #28892a;
--eve-solar-system-status-color-friendly-dark5: #38b538;
--eve-solar-system-status-color-lookingFor-dark15: #32aadf;
/* Context Menu */
--conn-time-eol: #7452c3e3;
--conn-frigate: #325d88;
--conn-save: rgba(155, 102, 45, 0.85);
--selected-item-bg: rgba(98, 98, 98, 0.33);
}

View File

@@ -1,535 +1,448 @@
@import "eve-common-variables";
@import './eve-common-variables';
.eve-wh-effect-color-pulsar {
fill: $eve-effect-pulsar;
background-color: $eve-effect-pulsar;
fill: var(--eve-effect-pulsar);
background-color: var(--eve-effect-pulsar);
}
.eve-wh-effect-color-magnetar {
fill: $eve-effect-magnetar;
background-color: $eve-effect-magnetar;
fill: var(--eve-effect-magnetar);
background-color: var(--eve-effect-magnetar);
}
.eve-wh-effect-color-wolfRayet {
fill: $eve-effect-wolfRayet;
background-color: $eve-effect-wolfRayet;
fill: var(--eve-effect-wolfRayet);
background-color: var(--eve-effect-wolfRayet);
}
.eve-wh-effect-color-blackHole {
fill: $eve-effect-blackHole;
background-color: $eve-effect-blackHole;
box-shadow: 0 0 8px rgba(255 255 255 / 33);
fill: var(--eve-effect-blackHole);
background-color: var(--eve-effect-blackHole);
box-shadow: 0 0 8px rgba(255, 255, 255, 0.33);
}
.eve-wh-effect-color-cataclysmicVariable {
fill: $eve-effect-cataclysmicVariable;
background-color: $eve-effect-cataclysmicVariable;
fill: var(--eve-effect-cataclysmicVariable);
background-color: var(--eve-effect-cataclysmicVariable);
}
.eve-wh-effect-color-redGiant {
fill: $eve-effect-redGiant;
background-color: $eve-effect-redGiant;
fill: var(--eve-effect-redGiant);
background-color: var(--eve-effect-redGiant);
}
.text-eve-wh-effect-color-pulsar {
color: $eve-effect-pulsar;
color: var(--eve-effect-pulsar);
}
.text-eve-wh-effect-color-magnetar {
color: $eve-effect-magnetar;
color: var(--eve-effect-magnetar);
}
.text-eve-wh-effect-color-wolfRayet {
color: $eve-effect-wolfRayet;
color: var(--eve-effect-wolfRayet);
}
.text-eve-wh-effect-color-blackHole {
color: #fff;
}
.text-eve-wh-effect-color-cataclysmicVariable {
color: $eve-effect-cataclysmicVariable;
color: var(--eve-effect-cataclysmicVariable);
}
.text-eve-wh-effect-color-redGiant {
color: $eve-effect-redGiant;
color: var(--eve-effect-redGiant);
}
.text-eve-wh-effect-color-dazhLiminalityLocus {
color: $eve-effect-dazhLiminalityLocus;
color: var(--eve-effect-dazhLiminalityLocus);
}
.text-eve-wh-effect-color-imperialStellarObservatory {
color: $eve-effect-imperialStellarObservatory;
color: var(--eve-effect-imperialStellarObservatory);
}
.text-eve-wh-effect-color-stateStellarObservatory {
color: $eve-effect-stateStellarObservatory;
color: var(--eve-effect-stateStellarObservatory);
}
.text-eve-wh-effect-color-republicStellarObservatory {
color: $eve-effect-republicStellarObservatory;
color: var(--eve-effect-republicStellarObservatory);
}
.text-eve-wh-effect-color-federalStellarObservatory {
color: $eve-effect-federalStellarObservatory;
color: var(--eve-effect-federalStellarObservatory);
}
/* Security color classes */
.eve-security-color-10 {
color: $eve-security-color-10 !important;
fill: $eve-security-color-10;
color: var(--eve-security-color-10) !important;
fill: var(--eve-security-color-10);
}
.eve-security-color-09 {
color: $eve-security-color-09 !important;
fill: $eve-security-color-09;
color: var(--eve-security-color-09) !important;
fill: var(--eve-security-color-09);
}
.eve-security-color-08 {
color: $eve-security-color-08 !important;
fill: $eve-security-color-08;
color: var(--eve-security-color-08) !important;
fill: var(--eve-security-color-08);
}
.eve-security-color-07 {
color: $eve-security-color-07 !important;
fill: $eve-security-color-07;
color: var(--eve-security-color-07) !important;
fill: var(--eve-security-color-07);
}
.eve-security-color-06 {
color: $eve-security-color-06 !important;
fill: $eve-security-color-06;
color: var(--eve-security-color-06) !important;
fill: var(--eve-security-color-06);
}
.eve-security-color-05 {
color: $eve-security-color-05 !important;
fill: $eve-security-color-05;
color: var(--eve-security-color-05) !important;
fill: var(--eve-security-color-05);
}
.eve-security-color-04 {
color: $eve-security-color-04 !important;
fill: $eve-security-color-04;
color: var(--eve-security-color-04) !important;
fill: var(--eve-security-color-04);
}
.eve-security-color-03 {
color: $eve-security-color-03 !important;
fill: $eve-security-color-03;
color: var(--eve-security-color-03) !important;
fill: var(--eve-security-color-03);
}
.eve-security-color-02 {
color: $eve-security-color-02 !important;
fill: $eve-security-color-02;
color: var(--eve-security-color-02) !important;
fill: var(--eve-security-color-02);
}
.eve-security-color-01 {
color: $eve-security-color-01 !important;
fill: $eve-security-color-01;
color: var(--eve-security-color-01) !important;
fill: var(--eve-security-color-01);
}
.eve-security-color-00 {
color: $eve-security-color-00 !important;
fill: $eve-security-color-00;
color: var(--eve-security-color-00) !important;
fill: var(--eve-security-color-00);
}
.eve-security-color-m-01 {
color: $eve-security-color-m-01 !important;
fill: $eve-security-color-m-01;
color: var(--eve-security-color-m-01) !important;
fill: var(--eve-security-color-m-01);
}
.eve-security-color-m-02 {
color: $eve-security-color-m-02 !important;
fill: $eve-security-color-m-02;
color: var(--eve-security-color-m-02) !important;
fill: var(--eve-security-color-m-02);
}
.eve-security-color-m-03 {
color: $eve-security-color-m-03 !important;
fill: $eve-security-color-m-03;
color: var(--eve-security-color-m-03) !important;
fill: var(--eve-security-color-m-03);
}
.eve-security-color-m-04 {
color: $eve-security-color-m-04 !important;
fill: $eve-security-color-m-04;
color: var(--eve-security-color-m-04) !important;
fill: var(--eve-security-color-m-04);
}
.eve-security-color-m-05 {
color: $eve-security-color-m-05 !important;
fill: $eve-security-color-m-05;
color: var(--eve-security-color-m-05) !important;
fill: var(--eve-security-color-m-05);
}
.eve-security-color-m-06 {
color: $eve-security-color-m-06 !important;
fill: $eve-security-color-m-06;
color: var(--eve-security-color-m-06) !important;
fill: var(--eve-security-color-m-06);
}
.eve-security-color-m-07 {
color: $eve-security-color-m-07 !important;
fill: $eve-security-color-m-07;
color: var(--eve-security-color-m-07) !important;
fill: var(--eve-security-color-m-07);
}
.eve-security-color-m-08 {
color: $eve-security-color-m-08 !important;
fill: $eve-security-color-m-08;
color: var(--eve-security-color-m-08) !important;
fill: var(--eve-security-color-m-08);
}
.eve-security-color-m-09 {
color: $eve-security-color-m-09 !important;
fill: $eve-security-color-m-09;
color: var(--eve-security-color-m-09) !important;
fill: var(--eve-security-color-m-09);
}
.eve-security-color-m-10 {
color: $eve-security-color-m-10 !important;
fill: $eve-security-color-m-10;
color: var(--eve-security-color-m-10) !important;
fill: var(--eve-security-color-m-10);
}
/* Security backgrounds */
.eve-security-background-10 {
background-color: $eve-security-color-10;
fill: $eve-security-color-10;
background-color: var(--eve-security-color-10);
fill: var(--eve-security-color-10);
}
.eve-security-background-09 {
background-color: $eve-security-color-09;
fill: $eve-security-color-09;
background-color: var(--eve-security-color-09);
fill: var(--eve-security-color-09);
}
.eve-security-background-08 {
background-color: $eve-security-color-08;
fill: $eve-security-color-08;
background-color: var(--eve-security-color-08);
fill: var(--eve-security-color-08);
}
.eve-security-background-07 {
background-color: $eve-security-color-07;
fill: $eve-security-color-07;
background-color: var(--eve-security-color-07);
fill: var(--eve-security-color-07);
}
.eve-security-background-06 {
background-color: $eve-security-color-06;
fill: $eve-security-color-06;
background-color: var(--eve-security-color-06);
fill: var(--eve-security-color-06);
}
.eve-security-background-05 {
background-color: $eve-security-color-05;
fill: $eve-security-color-05;
background-color: var(--eve-security-color-05);
fill: var(--eve-security-color-05);
}
.eve-security-background-04 {
background-color: $eve-security-color-04;
fill: $eve-security-color-04;
background-color: var(--eve-security-color-04);
fill: var(--eve-security-color-04);
}
.eve-security-background-03 {
background-color: $eve-security-color-03;
fill: $eve-security-color-03;
background-color: var(--eve-security-color-03);
fill: var(--eve-security-color-03);
}
.eve-security-background-02 {
background-color: $eve-security-color-02;
fill: $eve-security-color-02;
background-color: var(--eve-security-color-02);
fill: var(--eve-security-color-02);
}
.eve-security-background-01 {
background-color: $eve-security-color-01;
fill: $eve-security-color-01;
background-color: var(--eve-security-color-01);
fill: var(--eve-security-color-01);
}
.eve-security-background-00 {
background-color: $eve-security-color-00;
fill: $eve-security-color-00;
background-color: var(--eve-security-color-00);
fill: var(--eve-security-color-00);
}
.eve-security-background-m-01 {
background-color: $eve-security-color-m-01;
fill: $eve-security-color-m-01;
background-color: var(--eve-security-color-m-01);
fill: var(--eve-security-color-m-01);
}
.eve-security-background-m-02 {
background-color: $eve-security-color-m-02;
fill: $eve-security-color-m-02;
background-color: var(--eve-security-color-m-02);
fill: var(--eve-security-color-m-02);
}
.eve-security-background-m-03 {
background-color: $eve-security-color-m-03;
fill: $eve-security-color-m-03;
background-color: var(--eve-security-color-m-03);
fill: var(--eve-security-color-m-03);
}
.eve-security-background-m-04 {
background-color: $eve-security-color-m-04;
fill: $eve-security-color-m-04;
background-color: var(--eve-security-color-m-04);
fill: var(--eve-security-color-m-04);
}
.eve-security-background-m-05 {
background-color: $eve-security-color-m-05;
fill: $eve-security-color-m-05;
background-color: var(--eve-security-color-m-05);
fill: var(--eve-security-color-m-05);
}
.eve-security-background-m-06 {
background-color: $eve-security-color-m-06;
fill: $eve-security-color-m-06;
background-color: var(--eve-security-color-m-06);
fill: var(--eve-security-color-m-06);
}
.eve-security-background-m-07 {
background-color: $eve-security-color-m-07;
fill: $eve-security-color-m-07;
background-color: var(--eve-security-color-m-07);
fill: var(--eve-security-color-m-07);
}
.eve-security-background-m-08 {
background-color: $eve-security-color-m-08;
fill: $eve-security-color-m-08;
background-color: var(--eve-security-color-m-08);
fill: var(--eve-security-color-m-08);
}
.eve-security-background-m-09 {
background-color: $eve-security-color-m-09;
fill: $eve-security-color-m-09;
background-color: var(--eve-security-color-m-09);
fill: var(--eve-security-color-m-09);
}
.eve-security-background-m-10 {
background-color: $eve-security-color-m-10;
fill: $eve-security-color-m-10;
background-color: var(--eve-security-color-m-10);
fill: var(--eve-security-color-m-10);
}
/* WH Type color classes */
.eve-wh-type-color-high {
color: $eve-wh-type-color-high;
fill: $eve-wh-type-color-high;
color: var(--eve-wh-type-color-high) !important;
fill: var(--eve-wh-type-color-high);
font-weight: bold !important;
}
.eve-wh-type-color-low {
color: $eve-wh-type-color-low;
fill: $eve-wh-type-color-low;
color: var(--eve-wh-type-color-low) !important;
fill: var(--eve-wh-type-color-low);
font-weight: bold !important;
}
.eve-wh-type-color-null {
color: $eve-wh-type-color-null;
fill: $eve-wh-type-color-null;
color: var(--eve-wh-type-color-null) !important;
fill: var(--eve-wh-type-color-null);
font-weight: bold !important;
}
.eve-wh-type-color-c1 {
color: $eve-wh-type-color-c1 !important;
fill: $eve-wh-type-color-c1;
color: var(--eve-wh-type-color-c1) !important;
fill: var(--eve-wh-type-color-c1);
font-weight: bold !important;
}
.eve-wh-type-color-c2 {
color: $eve-wh-type-color-c2 !important;
fill: $eve-wh-type-color-c2;
color: var(--eve-wh-type-color-c2) !important;
fill: var(--eve-wh-type-color-c2);
font-weight: bold !important;
}
.eve-wh-type-color-c3 {
color: $eve-wh-type-color-c3 !important;
fill: $eve-wh-type-color-c3;
color: var(--eve-wh-type-color-c3) !important;
fill: var(--eve-wh-type-color-c3);
font-weight: bold !important;
}
.eve-wh-type-color-c4 {
color: $eve-wh-type-color-c4 !important;
fill: $eve-wh-type-color-c4;
color: var(--eve-wh-type-color-c4) !important;
fill: var(--eve-wh-type-color-c4);
font-weight: bold !important;
}
.eve-wh-type-color-c5 {
color: $eve-wh-type-color-c5 !important;
fill: $eve-wh-type-color-c5;
color: var(--eve-wh-type-color-c5) !important;
fill: var(--eve-wh-type-color-c5);
font-weight: bold !important;
}
.eve-wh-type-color-c6 {
color: $eve-wh-type-color-c6 !important;
fill: $eve-wh-type-color-c6;
color: var(--eve-wh-type-color-c6) !important;
fill: var(--eve-wh-type-color-c6);
font-weight: bold !important;
}
.eve-wh-type-color-c13 {
color: $eve-wh-type-color-c13 !important;
fill: $eve-wh-type-color-c13;
color: var(--eve-wh-type-color-c13) !important;
fill: var(--eve-wh-type-color-c13);
}
.eve-wh-type-color-drifter {
color: $eve-wh-type-color-drifter !important;
fill: $eve-wh-type-color-drifter;
color: var(--eve-wh-type-color-drifter) !important;
fill: var(--eve-wh-type-color-drifter);
}
.eve-wh-type-color-thera {
color: $eve-wh-type-color-thera !important;
fill: $eve-wh-type-color-thera;
color: var(--eve-wh-type-color-thera) !important;
fill: var(--eve-wh-type-color-thera);
}
/* WH Type backgrounds */
.eve-wh-type-background-high {
background-color: $eve-wh-type-color-high;
background-color: var(--eve-wh-type-color-high);
}
.eve-wh-type-background-low {
background-color: $eve-wh-type-color-low;
background-color: var(--eve-wh-type-color-low);
}
.eve-wh-type-background-null {
background-color: $eve-wh-type-color-null;
background-color: var(--eve-wh-type-color-null);
}
.eve-wh-type-background-c1 {
background-color: $eve-wh-type-color-c1;
background-color: var(--eve-wh-type-color-c1);
}
.eve-wh-type-background-c2 {
background-color: $eve-wh-type-color-c2;
background-color: var(--eve-wh-type-color-c2);
}
.eve-wh-type-background-c3 {
background-color: $eve-wh-type-color-c3;
background-color: var(--eve-wh-type-color-c3);
}
.eve-wh-type-background-c4 {
background-color: $eve-wh-type-color-c4;
background-color: var(--eve-wh-type-color-c4);
}
.eve-wh-type-background-c5 {
background-color: $eve-wh-type-color-c5;
background-color: var(--eve-wh-type-color-c5);
}
.eve-wh-type-background-c6 {
background-color: $eve-wh-type-color-c6;
background-color: var(--eve-wh-type-color-c6);
}
.eve-wh-type-background-c13 {
background-color: $eve-wh-type-color-c13;
background-color: var(--eve-wh-type-color-c13);
}
.eve-wh-type-background-drifter {
background-color: $eve-wh-type-color-drifter;
background-color: var(--eve-wh-type-color-drifter);
}
.eve-wh-type-background-thera {
background-color: $eve-wh-type-color-thera;
background-color: var(--eve-wh-type-color-thera);
}
.eve-wh-type-background-zarzakh {
background-color: $eve-wh-type-color-zarzakh;
background-color: var(--eve-wh-type-color-zarzakh);
}
/* Kind color classes */
.eve-kind-color-high {
color: $eve-wh-type-color-high;
fill: $eve-wh-type-color-high;
color: var(--eve-wh-type-color-high);
fill: var(--eve-wh-type-color-high);
}
.eve-kind-color-low {
color: $eve-wh-type-color-low;
fill: $eve-wh-type-color-low;
color: var(--eve-wh-type-color-low);
fill: var(--eve-wh-type-color-low);
font-weight: bold;
}
.eve-kind-color-null {
color: $eve-wh-type-color-null;
fill: $eve-wh-type-color-null;
color: var(--eve-wh-type-color-null);
fill: var(--eve-wh-type-color-null);
}
.eve-kind-color-wh {
color: $eve-wh-type-color-c6;
fill: $eve-wh-type-color-c6;
color: var(--eve-wh-type-color-c6);
fill: var(--eve-wh-type-color-c6);
}
.eve-kind-color-thera {
color: $eve-wh-type-color-thera;
fill: $eve-wh-type-color-thera;
color: var(--eve-wh-type-color-thera);
fill: var(--eve-wh-type-color-thera);
}
.eve-kind-color-abyss {
color: $eve-wh-type-color-c6;
fill: $eve-wh-type-color-c6;
color: var(--eve-wh-type-color-c6);
fill: var(--eve-wh-type-color-c6);
}
.eve-kind-color-penalty {
color: $eve-wh-type-color-c6;
fill: $eve-wh-type-color-c6;
color: var(--eve-wh-type-color-c6);
fill: var(--eve-wh-type-color-c6);
}
.eve-kind-color-pochven {
color: $eve-wh-type-color-c6;
fill: $eve-wh-type-color-c6;
color: var(--eve-wh-type-color-c6);
fill: var(--eve-wh-type-color-c6);
}
.eve-kind-color-zarzakh {
color: $eve-wh-type-color-zarzakh;
fill: $eve-wh-type-color-zarzakh;
color: var(--eve-wh-type-color-zarzakh);
fill: var(--eve-wh-type-color-zarzakh);
}
/* Kind backgrounds */
.eve-kind-background-high {
background-color: $eve-wh-type-color-high;
background-color: var(--eve-wh-type-color-high);
}
.eve-kind-background-low {
background-color: $eve-wh-type-color-low;
background-color: var(--eve-wh-type-color-low);
}
.eve-kind-background-null {
background-color: $eve-wh-type-color-null;
background-color: var(--eve-wh-type-color-null);
}
.eve-kind-background-wh {
background-color: $eve-wh-type-color-c6;
background-color: var(--eve-wh-type-color-c6);
}
.eve-kind-background-thera {
background-color: $eve-wh-type-color-thera;
background-color: var(--eve-wh-type-color-thera);
}
.eve-kind-background-abyss {
background-color: $eve-wh-type-color-c6;
background-color: var(--eve-wh-type-color-c6);
}
.eve-kind-background-penalty {
background-color: $eve-wh-type-color-c6;
background-color: var(--eve-wh-type-color-c6);
}
.eve-kind-background-pochven {
background-color: $eve-wh-type-color-c6;
background-color: var(--eve-wh-type-color-c6);
}
.eve-kind-background-zarzakh {
background-color: $eve-wh-type-color-zarzakh;
background-color: var(--eve-wh-type-color-zarzakh);
}
/* System status color classes */
.eve-system-status-color-clear {
color: $eve-solar-system-status-color-unknown;
color: var(--eve-solar-system-status-color-unknown);
}
.eve-system-status-color-home {
color: $eve-solar-system-status-color-home;
color: var(--eve-solar-system-status-color-home);
}
.eve-system-status-color-friendly {
color: $eve-solar-system-status-color-friendly;
color: var(--eve-solar-system-status-color-friendly);
}
.eve-system-status-color-lookingFor {
color: $eve-solar-system-status-color-lookingFor;
color: var(--eve-solar-system-status-color-lookingFor);
}
.eve-system-status-color-warning {
color: $eve-solar-system-status-color-warning;
color: var(--eve-solar-system-status-color-warning);
}
.eve-system-status-color-target {
color: $eve-solar-system-status-color-target;
color: var(--eve-solar-system-status-color-target);
}
.eve-system-status-color-dangerous {
color: $eve-solar-system-status-color-dangerous;
color: var(--eve-solar-system-status-color-dangerous);
}
/* Shapes */
.wd-route-system-shape-triangle {
clip-path: polygon(50% 0, 0 100%, 100% 100%);
}
.wd-route-system-shape-circle {
border-radius: 40%;
}
/* Some additional background classes */
.wd-marker-bookmark-color-shattered {
background-color: #833ca4;
margin-top: 1px;
}
.wd-marker-bookmark-color-custom {
background-color: #282828;
border: 1px solid #4c4c4c;

View File

@@ -0,0 +1,2 @@
@import './neon-theme.scss';
@import './pathfinder-theme.scss';

View File

@@ -1,74 +1,72 @@
$pastel-blue: #5a7d9a;
$pastel-pink: #d291bc;
$pastel-green: #88b04b;
$pastel-yellow: #ffdd59;
$dark-bg: #2d2d2d;
$text-color: #ffffff;
$tooltip-bg: #202020;
@import './eve-common-variables';
@import './eve-common';
.react-flow {
// background-color: $dark-bg;
color: $text-color;
.neon-theme {
--rf-bg-color: #000000;
--rf-soft-bg-color: #171717;
&__node {
//cursor: auto;
--rf-node-bg-color: #202020;
--rf-node-soft-bg-color: #2b2b2b;
--rf-text-color: #ffffff;
--rf-bg-variant: "dots";
--rf-bg-gap: 16;
--rf-bg-size: 1;
--rf-bg-pattern-color: #81818a;
--pastel-blue: #5a7d9a;
--pastel-pink: #d291bc;
--pastel-green: #88b04b;
--pastel-yellow: #ffdd59;
--dark-bg: #2d2d2d;
--text-color: #ffffff;
--tooltip-bg: #202020;
.react-flow {
color: var(--text-color);
&__pane {
cursor: auto;
}
&__minimap {
background-color: rgba(66, 66, 66, 1);
opacity: 0.7;
border: 1px solid #2f2f2f;
border-radius: 4px;
overflow: hidden;
}
&__minimap-mask {
fill: rgba(28, 28, 28, 0.75);
}
&__controls {
filter: brightness(1.5);
}
&__minimap-node {
fill: #ffb03a;
}
}
&__pane {
cursor: auto;
}
//&__edge {
// stroke: $pastel-pink;
// stroke-width: 2px;
//
// &.selected {
// stroke: $pastel-yellow;
// }
//}
&__handle {
//background-color: $pastel-green;
//box-shadow: 0 0 5px rgba($pastel-green, 0.5);
.context-menu-active {
background-color: rgba(131, 131, 131, 0.33);
}
&__minimap {
background-color: rgba(66, 66, 66, 1);
opacity: 0.7;
//backdrop-filter: blur(5px);
border: 1px solid #2f2f2f;
border-radius: 4px;
overflow: hidden;
}
&__minimap-mask {
fill: rgba(28, 28, 28, 0.75);
}
&__controls {
filter: brightness(1.5);
}
&__minimap-node {
fill: #ffb03a;
}
}
.context-menu-active {
background-color: rgba(131, 131, 131, 0.33);
}
.p-dialog {
.p-dialog-header {
height: 40px;
padding: 1rem;
padding-right: 10px !important;
}
.p-dialog-title {
font-size: 1rem !important;
}
.p-dialog-header-icons {
align-self: initial !important;
.p-dialog {
.p-dialog-header {
height: 40px;
padding: 1rem;
padding-right: 10px !important;
}
.p-dialog-title {
font-size: 1rem !important;
}
.p-dialog-header-icons {
align-self: initial !important;
}
}
}

View File

@@ -1,7 +0,0 @@
$pastel-blue: #5a7d9a;
$pastel-pink: #d291bc;
$pastel-green: #88b04b;
$pastel-yellow: #ffdd59;
$dark-bg: #2d2d2d;
$text-color: #ffffff;
$tooltip-bg: #202020;

View File

@@ -0,0 +1,49 @@
@import './eve-common-variables';
@import './eve-common';
@import url('https://fonts.googleapis.com/css2?family=Oxygen:wght@300;400;700&display=swap');
.pathfinder-theme {
--rf-bg-color: #000000;
--rf-soft-bg-color: #282828;
--rf-node-bg-color: #202020;
--rf-node-soft-bg-color: #313335;
--rf-node-font-weight: bold;
--rf-text-color: #adadad;
--tooltip-bg: #202020;
--rf-bg-variant: "lines";
--rf-bg-gap: 32;
--rf-bg-size: 1;
--rf-bg-pattern-color: #313131;
--eve-effect-pulsar: #428bca;
--eve-effect-magnetar: #e06fdf;
--eve-effect-wolfRayet: #e28a0d;
--eve-effect-blackHole: #000000;
--eve-effect-cataclysmicVariable: #ffffbb;
--eve-effect-redGiant: #d9534f;
--eve-wh-type-color-high: #5cb85c;
--eve-wh-type-color-low: #e28a0d;
--eve-wh-type-color-null: #d9534f;
--eve-wh-type-color-c1: #428bca;
--eve-wh-type-color-c2: #428bca;
--eve-wh-type-color-c3: #e28a0d;
--eve-wh-type-color-c4: #e28a0d;
--eve-wh-type-color-c5: #d9534f;
--eve-wh-type-color-c6: #d9534f;
--eve-wh-type-color-c13: #7986cb;
--eve-wh-type-color-drifter: #44aa82;
--rf-node-font-weight: bold;
--rf-node-line-height: normal;
--rf-node-font-family: 'Oxygen', sans-serif;
--rf-node-text-color: var(--pf-text-color);
--rf-tag-color: #fbbf24;
--rf-has-user-characters: #5cb85c;
}

View File

@@ -0,0 +1,174 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { EdgeChange, NodeChange, Node, Edge } from 'reactflow';
// This function applies changes to nodes or edges that are triggered by React Flow internally.
// When you drag a node for example, React Flow will send a position change update.
// This function then applies the changes and returns the updated elements.
function applyChanges(changes: any[], elements: any[]): any[] {
// we need this hack to handle the setNodes and setEdges function of the useReactFlow hook for controlled flows
if (changes.some(c => c.type === 'reset')) {
return changes.filter(c => c.type === 'reset').map(c => c.item);
}
const updatedElements: any[] = [];
// By storing a map of changes for each element, we can a quick lookup as we
// iterate over the elements array!
const changesMap = new Map<any, any[]>();
const addItemChanges: any[] = [];
for (const change of changes) {
if (change.type === 'add') {
addItemChanges.push(change);
continue;
} else if (change.type === 'remove' || change.type === 'replace') {
// For a 'remove' change we can safely ignore any other changes queued for
// the same element, it's going to be removed anyway!
changesMap.set(change.id, [change]);
} else {
const elementChanges = changesMap.get(change.id);
if (elementChanges) {
// If we have some changes queued already, we can do a mutable update of
// that array and save ourselves some copying.
elementChanges.push(change);
} else {
changesMap.set(change.id, [change]);
}
}
}
for (const element of elements) {
const changes = changesMap.get(element.id);
// When there are no changes for an element we can just push it unmodified,
// no need to copy it.
if (!changes) {
updatedElements.push(element);
continue;
}
// If we have a 'remove' change queued, it'll be the only change in the array
if (changes[0].type === 'remove') {
continue;
}
if (changes[0].type === 'replace') {
updatedElements.push({ ...changes[0].item });
continue;
}
// For other types of changes, we want to start with a shallow copy of the
// object so React knows this element has changed. Sequential changes will
/// each _mutate_ this object, so there's only ever one copy.
const updatedElement = { ...element };
for (const change of changes) {
applyChange(change, updatedElement);
}
updatedElements.push(updatedElement);
}
// we need to wait for all changes to be applied before adding new items
// to be able to add them at the correct index
if (addItemChanges.length) {
addItemChanges.forEach(change => {
if (change.index !== undefined) {
updatedElements.splice(change.index, 0, { ...change.item });
} else {
updatedElements.push({ ...change.item });
}
});
}
return updatedElements;
}
// Applies a single change to an element. This is a *mutable* update.
function applyChange(change: any, element: any): any {
switch (change.type) {
case 'select': {
element.selected = change.selected;
break;
}
case 'position': {
if (typeof change.position !== 'undefined') {
element.position = change.position;
}
if (typeof change.dragging !== 'undefined') {
element.dragging = change.dragging;
}
break;
}
case 'dimensions': {
if (typeof change.dimensions !== 'undefined') {
element.measured ??= {};
element.measured.width = change.dimensions.width;
element.measured.height = change.dimensions.height;
if (change.setAttributes) {
element.width = change.dimensions.width;
element.height = change.dimensions.height;
}
}
if (typeof change.resizing === 'boolean') {
element.resizing = change.resizing;
}
break;
}
}
}
/**
* Drop in function that applies node changes to an array of nodes.
* @public
* @remarks Various events on the <ReactFlow /> component can produce an {@link NodeChange} that describes how to update the edges of your flow in some way.
If you don't need any custom behaviour, this util can be used to take an array of these changes and apply them to your edges.
* @param changes - Array of changes to apply
* @param nodes - Array of nodes to apply the changes to
* @returns Array of updated nodes
* @example
* const onNodesChange = useCallback(
(changes) => {
setNodes((oldNodes) => applyNodeChanges(changes, oldNodes));
},
[setNodes],
);
return (
<ReactFLow nodes={nodes} edges={edges} onNodesChange={onNodesChange} />
);
*/
export function applyNodeChanges<NodeType extends Node = Node>(changes: NodeChange[], nodes: NodeType[]): NodeType[] {
return applyChanges(changes, nodes) as NodeType[];
}
/**
* Drop in function that applies edge changes to an array of edges.
* @public
* @remarks Various events on the <ReactFlow /> component can produce an {@link EdgeChange} that describes how to update the edges of your flow in some way.
If you don't need any custom behaviour, this util can be used to take an array of these changes and apply them to your edges.
* @param changes - Array of changes to apply
* @param edges - Array of edge to apply the changes to
* @returns Array of updated edges
* @example
* const onEdgesChange = useCallback(
(changes) => {
setEdges((oldEdges) => applyEdgeChanges(changes, oldEdges));
},
[setEdges],
);
return (
<ReactFlow nodes={nodes} edges={edges} onEdgesChange={onEdgesChange} />
);
*/
export function applyEdgeChanges<EdgeType extends Edge = Edge>(changes: EdgeChange[], edges: EdgeType[]): EdgeType[] {
return applyChanges(changes, edges) as EdgeType[];
}

View File

@@ -48,6 +48,7 @@ const restoreWindowsFromLS = (): WidgetGridItem[] => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const raw = localStorage.getItem(SESSION_KEY.windows);
if (!raw) {
console.warn('No windows found in local storage!!');
return DEFAULT_WINDOWS;
}
@@ -63,7 +64,7 @@ const restoreWindowsFromLS = (): WidgetGridItem[] => {
};
export const MapInterface = () => {
const [items, setItems] = useState<WidgetGridItem[]>(restoreWindowsFromLS());
const [items, setItems] = useState<WidgetGridItem[]>(restoreWindowsFromLS);
return (
<WidgetsGrid

View File

@@ -0,0 +1,10 @@
.SearchItem {
& > * {
font-size: 13px !important;
}
}
.SearchItemEffect {
font-weight: initial !important;
}

View File

@@ -0,0 +1,203 @@
import { Dialog } from 'primereact/dialog';
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 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';
export type SearchOnSubmitCallback = (item: SearchSystemItem) => void;
interface AddSystemDialogProps {
title?: string;
visible: boolean;
setVisible: (visible: boolean) => void;
onSubmit?: SearchOnSubmitCallback;
excludedSystems?: number[];
}
export const AddSystemDialog = ({
title = 'Add system',
visible,
setVisible,
onSubmit,
excludedSystems = [],
}: AddSystemDialogProps) => {
const {
outCommand,
data: { wormholesData },
} = useMapRootState();
const inputRef = useRef<any>();
const onShow = useCallback(() => {
inputRef.current?.focus();
}, []);
const [filteredItems, setFilteredItems] = useState<SearchSystemItem[]>([]);
const [selectedItem, setSelectedItem] = useState<SearchSystemItem[] | null>(null);
const searchItems = useCallback(
async (event: { query: string }) => {
if (event.query.length < 2) {
setFilteredItems([]);
return;
}
const query = event.query;
if (query.length === 0) {
setFilteredItems([]);
} else {
try {
const result = await outCommand({
type: OutCommand.searchSystems,
data: {
text: query,
},
});
let prepared = (result.systems as SearchSystemItem[]).sort((a, b) => {
const amatch = a.label.indexOf(query);
const bmatch = b.label.indexOf(query);
return amatch - bmatch;
});
if (excludedSystems) {
prepared = prepared.filter(x => !excludedSystems.includes(x.system_static_info.solar_system_id));
}
setFilteredItems(prepared);
} catch (error) {
console.error('Error fetching data:', error);
setFilteredItems([]);
}
}
},
[excludedSystems, outCommand],
);
const ref = useRef({ onSubmit, selectedItem });
ref.current = { onSubmit, selectedItem };
const handleSubmit = useCallback(() => {
const { onSubmit, selectedItem } = ref.current;
setFilteredItems([]);
setSelectedItem([]);
if (!selectedItem) {
setVisible(false);
return;
}
onSubmit?.(selectedItem[0]);
setVisible(false);
}, [setVisible]);
return (
<Dialog
header={title}
visible={visible}
draggable={false}
style={{ width: '520px' }}
onShow={onShow}
onHide={() => {
if (!visible) {
return;
}
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);
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}
/>
)}
{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>
);
}}
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>
</div>
</div>
<div className="flex gap-2 justify-end">
<Button
onClick={handleSubmit}
outlined
disabled={!selectedItem || selectedItem.length !== 1}
size="small"
label="Submit"
/>
</div>
</div>
</Dialog>
);
};

View File

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

View File

@@ -6,6 +6,7 @@ import { SystemSignature } from '@/hooks/Mapper/types';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { CommandLinkSignatureToSystem } from '@/hooks/Mapper/types';
import { SystemSignaturesContent } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent';
import { SHOW_DESCRIPTION_COLUMN_SETTING } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatures';
import {
Setting,
COSMIC_SIGNATURE,
@@ -20,6 +21,7 @@ interface SystemLinkSignatureDialogProps {
const signatureSettings: Setting[] = [
{ key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true },
{ key: SignatureGroup.Wormhole, name: 'Wormhole', value: true },
{ key: SHOW_DESCRIPTION_COLUMN_SETTING, name: 'Show Description Column', value: true, isFilter: false },
];
export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignatureDialogProps) => {

View File

@@ -3,6 +3,7 @@ import { InputTextarea } from 'primereact/inputtextarea';
import { Dialog } from 'primereact/dialog';
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';
@@ -22,30 +23,21 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
outCommand,
} = useMapRootState();
const isTempSystemNameEnabled = useMapGetOption('show_temp_system_name') === 'true';
const system = getSystemById(systems, systemId);
const [name, setName] = useState('');
const [label, setLabel] = useState('');
const [temporaryName, setTemporaryName] = useState('');
const [description, setDescription] = useState('');
const inputRef = useRef<HTMLInputElement>();
useEffect(() => {
if (!system) {
return;
}
const labels = new LabelsManager(system.labels || '');
setName(system.name || '');
setLabel(labels.customLabel);
setDescription(system.description || '');
}, [system]);
const ref = useRef({ name, description, label, outCommand, systemId, system });
ref.current = { name, description, label, outCommand, systemId, system };
const ref = useRef({ name, description, temporaryName, label, outCommand, systemId, system });
ref.current = { name, description, label, temporaryName, outCommand, systemId, system };
const handleSave = useCallback(() => {
const { name, description, label, outCommand, systemId, system } = ref.current;
const { name, description, label, temporaryName, outCommand, systemId, system } = ref.current;
const outLabel = new LabelsManager(system?.labels ?? '');
outLabel.updateCustomLabel(label);
@@ -58,6 +50,14 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
},
});
outCommand({
type: OutCommand.updateSystemTemporaryName,
data: {
system_id: systemId,
value: temporaryName,
},
});
outCommand({
type: OutCommand.updateSystemName,
data: {
@@ -93,6 +93,21 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
e.target.value = e.target.value.toUpperCase().replace(/[^A-Z0-9\-[\](){}]/g, '');
}, []);
// Attention: this effect should be call only on mount.
useEffect(() => {
const { system } = ref.current;
if (!system) {
return;
}
const labels = new LabelsManager(system.labels || '');
setName(system.name || '');
setLabel(labels.customLabel);
setTemporaryName(system.temporary_name || '');
setDescription(system.description || '');
}, []);
return (
<Dialog
header="System settings"
@@ -167,6 +182,35 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
</IconField>
</div>
{isTempSystemNameEnabled && (
<div className="flex flex-col gap-1">
<label htmlFor="username">Temporary Name</label>
<IconField>
{temporaryName !== '' && (
<WdImgButton
className="pi pi-trash text-red-400"
textSize={WdImageSize.large}
tooltip={{
content: 'Remove temporary name',
className: 'pi p-input-icon',
position: TooltipPosition.top,
}}
onClick={() => setTemporaryName('')}
/>
)}
<InputText
id="temporaryName"
aria-describedby="temporaryName"
autoComplete="off"
value={temporaryName}
maxLength={10}
onChange={e => setTemporaryName(e.target.value)}
/>
</IconField>
</div>
)}
<div className="flex flex-col gap-1">
<label htmlFor="username">Description</label>
<InputTextarea

View File

@@ -1,4 +1,4 @@
import { useCallback, useMemo } from 'react';
import { useCallback, useMemo, useRef } from 'react';
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
@@ -8,6 +8,10 @@ import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types';
import { CharacterCard, LayoutEventBlocker, WdCheckbox } from '@/hooks/Mapper/components/ui-kit';
import { sortCharacters } from '@/hooks/Mapper/components/mapInterface/helpers/sortCharacters.ts';
import useLocalStorageState from 'use-local-storage-state';
import { useMapCheckPermissions, useMapGetOption } from '@/hooks/Mapper/mapRootProvider/hooks/api';
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth.ts';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
type CharItemProps = {
compact: boolean;
@@ -62,6 +66,14 @@ export const LocalCharacters = () => {
const [systemId] = selectedSystems;
const restrictOfflineShowing = useMapGetOption('restrict_offline_showing');
const isAdminOrManager = useMapCheckPermissions([UserPermission.MANAGE_MAP]);
const showOffline = useMemo(
() => !restrictOfflineShowing || isAdminOrManager,
[isAdminOrManager, restrictOfflineShowing],
);
const itemTemplate = useItemTemplate();
const sorted = useMemo(() => {
@@ -70,32 +82,39 @@ export const LocalCharacters = () => {
.map(x => ({ ...x, isOwn: userCharacters.includes(x.eve_id), compact: settings.compact }))
.sort(sortCharacters);
if (!settings.showOffline) {
if (!showOffline || !settings.showOffline) {
return sorted.filter(c => c.online);
}
return sorted;
// eslint-disable-next-line
}, [characters, settings.showOffline, settings.compact, systemId, userCharacters, presentCharacters]);
}, [showOffline, characters, settings.showOffline, settings.compact, systemId, userCharacters, presentCharacters]);
const isNobodyHere = sorted.length === 0;
const isNotSelectedSystem = selectedSystems.length !== 1;
const showList = sorted.length > 0 && selectedSystems.length === 1;
const ref = useRef<HTMLDivElement>(null);
const compact = useMaxWidth(ref, 145);
return (
<Widget
label={
<div className="flex justify-between items-center text-xs w-full">
<div className="flex justify-between items-center text-xs w-full" ref={ref}>
<span className="select-none">Local{showList ? ` [${sorted.length}]` : ''}</span>
<LayoutEventBlocker className="flex items-center gap-2">
<WdCheckbox
size="xs"
labelSide="left"
label={'Show offline'}
value={settings.showOffline}
classNameLabel="text-stone-400 hover:text-stone-200 transition duration-300"
onChange={() => setSettings(() => ({ ...settings, showOffline: !settings.showOffline }))}
/>
{showOffline && (
<WdTooltipWrapper content="Show offline characters in system">
<WdCheckbox
size="xs"
labelSide="left"
label={compact ? '' : 'Show offline'}
value={settings.showOffline}
classNameLabel="text-stone-400 hover:text-stone-200 transition duration-300"
onChange={() => setSettings(() => ({ ...settings, showOffline: !settings.showOffline }))}
/>
</WdTooltipWrapper>
)}
<span
className={clsx('w-4 h-4 cursor-pointer', {
@@ -115,7 +134,9 @@ export const LocalCharacters = () => {
)}
{isNobodyHere && !isNotSelectedSystem && (
<div className="w-full h-full flex justify-center items-center select-none text-stone-400/80 text-sm">Nobody here</div>
<div className="w-full h-full flex justify-center items-center select-none text-stone-400/80 text-sm">
Nobody here
</div>
)}
{showList && (

View File

@@ -8,7 +8,6 @@
.RouteSystem {
width: 8px;
height: 8px;
background: #ffffff;
cursor: pointer;
transition: opacity 200ms;

View File

@@ -1,12 +1,11 @@
import { Dialog } from 'primereact/dialog';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Button } from 'primereact/button';
import { WdCheckbox } from '@/hooks/Mapper/components/ui-kit';
import {
RoutesType,
useRouteProvider,
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesProvider.tsx';
import { CheckboxChangeEvent } from 'primereact/checkbox';
import { PrettySwitchbox } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components';
interface RoutesSettingsDialog {
visible: boolean;
@@ -38,8 +37,8 @@ export const RoutesSettingsDialog = ({ visible, setVisible }: RoutesSettingsDial
currentData.current = data;
const handleChangeEvent = useCallback(
(propName: keyof RoutesType) => (event: CheckboxChangeEvent) => {
optionsRef.current = { ...optionsRef.current, [propName]: event.checked };
(propName: keyof RoutesType) => (event: boolean) => {
optionsRef.current = { ...optionsRef.current, [propName]: event };
updateKey(x => x + 1);
},
[],
@@ -71,14 +70,14 @@ export const RoutesSettingsDialog = ({ visible, setVisible }: RoutesSettingsDial
setVisible(false);
}}
>
<div className="flex flex-col gap-3">
<div className="flex flex-col gap-2">
<div className="flex flex-col gap-3 p-2.5">
<div className="flex flex-col gap-2 mb-2">
{checkboxes.map(({ label, propName }) => (
<WdCheckbox
<PrettySwitchbox
key={propName}
label={label}
value={optionsRef.current[propName]}
onChange={handleChangeEvent(propName)}
checked={optionsRef.current[propName]}
setChecked={handleChangeEvent(propName)}
/>
))}
</div>

View File

@@ -19,6 +19,13 @@ import { PrimeIcons } from 'primereact/api';
import { RoutesSettingsDialog } from './RoutesSettingsDialog';
import { RoutesProvider, useRouteProvider } from './RoutesProvider.tsx';
import { ContextMenuSystemInfo, useContextMenuSystemInfoHandlers } from '@/hooks/Mapper/components/contexts';
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth.ts';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
import {
AddSystemDialog,
SearchOnSubmitCallback,
} from '@/hooks/Mapper/components/mapInterface/components/AddSystemDialog';
import { OutCommand } from '@/hooks/Mapper/types';
const sortByDist = (a: Route, b: Route) => {
const distA = a.has_connection ? a.systems?.length || 0 : Infinity;
@@ -161,6 +168,12 @@ export const RoutesWidgetContent = () => {
export const RoutesWidgetComp = () => {
const [routeSettingsVisible, setRouteSettingsVisible] = useState(false);
const { data, update } = useRouteProvider();
const {
data: { hubs = [] },
outCommand,
} = useMapRootState();
const preparedHubs = useMemo(() => hubs.map(x => parseInt(x)), [hubs]);
const isSecure = data.path_type === 'secure';
const handleSecureChange = useCallback(() => {
@@ -170,27 +183,70 @@ export const RoutesWidgetComp = () => {
});
}, [data, update]);
const ref = useRef<HTMLDivElement>(null);
const compact = useMaxWidth(ref, 155);
const [openAddSystem, setOpenAddSystem] = useState<boolean>(false);
const onAddSystem = useCallback(() => setOpenAddSystem(true), []);
const handleSubmitAddSystem: SearchOnSubmitCallback = useCallback(
async item => {
if (preparedHubs.includes(item.value)) {
return;
}
await outCommand({
type: OutCommand.addHub,
data: { system_id: item.value },
});
},
[hubs, outCommand],
);
return (
<Widget
label={
<div className="flex justify-between items-center text-xs w-full">
<div className="flex justify-between items-center text-xs w-full" ref={ref}>
<span className="select-none">Routes</span>
<LayoutEventBlocker className="flex items-center gap-2">
<WdCheckbox
size="xs"
labelSide="left"
label={'Show shortest'}
value={!isSecure}
onChange={handleSecureChange}
classNameLabel={clsx('text-red-400')}
<WdImgButton
className={PrimeIcons.PLUS_CIRCLE}
onClick={onAddSystem}
tooltip={{
content: 'Click here to add new system to routes',
}}
/>
<WdTooltipWrapper content="Show shortest route">
<WdCheckbox
size="xs"
labelSide="left"
label={compact ? '' : 'Show shortest'}
value={!isSecure}
onChange={handleSecureChange}
classNameLabel={clsx('text-red-400')}
/>
</WdTooltipWrapper>
<WdImgButton
className={PrimeIcons.SLIDERS_H}
onClick={() => setRouteSettingsVisible(true)}
tooltip={{
content: 'Click here to open Routes settings',
}}
/>
<WdImgButton className={PrimeIcons.SLIDERS_H} onClick={() => setRouteSettingsVisible(true)} />
</LayoutEventBlocker>
</div>
}
>
<RoutesWidgetContent />
<RoutesSettingsDialog visible={routeSettingsVisible} setVisible={setRouteSettingsVisible} />
<AddSystemDialog
title="Add system to routes"
visible={openAddSystem}
setVisible={() => setOpenAddSystem(false)}
onSubmit={handleSubmitAddSystem}
/>
</Widget>
);
};

View File

@@ -1,9 +1,9 @@
import { Dialog } from 'primereact/dialog';
import { useCallback, useState } from 'react';
import { Button } from 'primereact/button';
import { Checkbox } from 'primereact/checkbox';
import { TabPanel, TabView } from 'primereact/tabview';
import styles from './SystemSignatureSettingsDialog.module.scss';
import { PrettySwitchbox } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components';
export type Setting = { key: string; name: string; value: boolean; isFilter?: boolean };
@@ -41,8 +41,8 @@ export const SystemSignatureSettingsDialog = ({
}, [onSave, settings]);
return (
<Dialog header="System Signatures Settings" visible={true} onHide={onCancel} className="w-full max-w-lg">
<div className="flex flex-col gap-3">
<Dialog header="System Signatures Settings" visible={true} onHide={onCancel} className="w-full max-w-lg h-[500px]">
<div className="flex flex-col gap-3 justify-between h-full">
<div className="flex flex-col gap-2">
<div className={styles.verticalTabsContainer}>
<TabView
@@ -54,16 +54,12 @@ export const SystemSignatureSettingsDialog = ({
<div className="w-full h-full flex flex-col gap-1">
{filterSettings.map(setting => {
return (
<div key={setting.key} className="flex items-center">
<Checkbox
inputId={setting.key}
checked={setting.value}
onChange={() => handleSettingsChange(setting.key)}
/>
<label htmlFor={setting.key} className="ml-2">
{setting.name}
</label>
</div>
<PrettySwitchbox
key={setting.key}
label={setting.name}
checked={setting.value}
setChecked={() => handleSettingsChange(setting.key)}
/>
);
})}
</div>
@@ -72,16 +68,12 @@ export const SystemSignatureSettingsDialog = ({
<div className="w-full h-full flex flex-col gap-1">
{userSettings.map(setting => {
return (
<div key={setting.key} className="flex items-center">
<Checkbox
inputId={setting.key}
checked={setting.value}
onChange={() => handleSettingsChange(setting.key)}
/>
<label htmlFor={setting.key} className="ml-2">
{setting.name}
</label>
</div>
<PrettySwitchbox
key={setting.key}
label={setting.name}
checked={setting.value}
setChecked={() => handleSettingsChange(setting.key)}
/>
);
})}
</div>

View File

@@ -1,32 +1,46 @@
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
import { InfoDrawer, LayoutEventBlocker, TooltipPosition, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
import {
InfoDrawer,
LayoutEventBlocker,
SystemView,
TooltipPosition,
WdCheckbox,
WdImgButton,
} from '@/hooks/Mapper/components/ui-kit';
import { SystemSignaturesContent } from './SystemSignaturesContent';
import {
Setting,
SystemSignatureSettingsDialog,
COSMIC_SIGNATURE,
COSMIC_ANOMALY,
COSMIC_SIGNATURE,
DEPLOYABLE,
STRUCTURE,
STARBASE,
SHIP,
DRONE,
Setting,
SHIP,
STARBASE,
STRUCTURE,
SystemSignatureSettingsDialog,
} from './SystemSignatureSettingsDialog';
import { SignatureGroup } from '@/hooks/Mapper/types';
import React, { useCallback, useEffect, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { PrimeIcons } from 'primereact/api';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { CheckboxChangeEvent } from 'primereact/checkbox';
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth.ts';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
const SIGNATURE_SETTINGS_KEY = 'wanderer_system_signature_settings_v4_1';
const SIGNATURE_SETTINGS_KEY = 'wanderer_system_signature_settings_v5_2';
export const SHOW_DESCRIPTION_COLUMN_SETTING = 'show_description_column_setting';
export const SHOW_INSERTED_COLUMN_SETTING = 'show_inserted_column_setting';
export const SHOW_UPDATED_COLUMN_SETTING = 'SHOW_UPDATED_COLUMN_SETTING';
export const LAZY_DELETE_SIGNATURES_SETTING = 'LAZY_DELETE_SIGNATURES_SETTING';
export const KEEP_LAZY_DELETE_SETTING = 'KEEP_LAZY_DELETE_ENABLED_SETTING';
const settings: Setting[] = [
{ key: SHOW_INSERTED_COLUMN_SETTING, name: 'Show Inserted Column', value: false, isFilter: false },
{ key: SHOW_UPDATED_COLUMN_SETTING, name: 'Show Updated Column', value: false, isFilter: false },
{ key: SHOW_DESCRIPTION_COLUMN_SETTING, name: 'Show Description Column', value: false, isFilter: false },
{ key: LAZY_DELETE_SIGNATURES_SETTING, name: 'Lazy Delete Signatures', value: false, isFilter: false },
{ key: KEEP_LAZY_DELETE_SETTING, name: 'Keep "Lazy Delete" Enabled', value: false, isFilter: false },
{ key: COSMIC_ANOMALY, name: 'Show Anomalies', value: true, isFilter: true },
{ key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true, isFilter: true },
{ key: DEPLOYABLE, name: 'Show Deployables', value: true, isFilter: true },
@@ -58,12 +72,25 @@ export const SystemSignatures = () => {
const isNotSelectedSystem = selectedSystems.length !== 1;
const lazyDeleteValue = useMemo(() => {
return settings.find(setting => setting.key === LAZY_DELETE_SIGNATURES_SETTING)!.value;
}, [settings]);
const handleSettingsChange = useCallback((settings: Setting[]) => {
setSettings(settings);
localStorage.setItem(SIGNATURE_SETTINGS_KEY, JSON.stringify(settings));
setVisible(false);
}, []);
const handleLazyDeleteChange = useCallback((value: boolean) => {
setSettings(settings => {
const lazyDelete = settings.find(setting => setting.key === LAZY_DELETE_SIGNATURES_SETTING)!;
lazyDelete.value = value;
localStorage.setItem(SIGNATURE_SETTINGS_KEY, JSON.stringify(settings));
return [...settings];
});
}, []);
useEffect(() => {
const restoredSettings = localStorage.getItem(SIGNATURE_SETTINGS_KEY);
@@ -72,13 +99,34 @@ export const SystemSignatures = () => {
}
}, []);
const ref = useRef<HTMLDivElement>(null);
const compact = useMaxWidth(ref, 260);
return (
<Widget
label={
<div className="flex justify-between items-center text-xs w-full h-full">
<div className="flex gap-1">System Signatures</div>
<div className="flex justify-between items-center text-xs w-full h-full" ref={ref}>
<div className="flex justify-between items-center gap-1">
{!compact && (
<div className="flex whitespace-nowrap text-ellipsis overflow-hidden text-stone-400">
Signatures {isNotSelectedSystem ? '' : 'in'}
</div>
)}
{!isNotSelectedSystem && <SystemView systemId={systemId} className="select-none text-center" hideRegion />}
</div>
<LayoutEventBlocker className="flex gap-2.5">
<WdTooltipWrapper content="Enable Lazy delete">
<WdCheckbox
size="xs"
labelSide="left"
label={compact ? '' : 'Lazy delete'}
value={lazyDeleteValue}
classNameLabel="text-stone-400 hover:text-stone-200 transition duration-300 whitespace-nowrap text-ellipsis overflow-hidden"
onChange={(event: CheckboxChangeEvent) => handleLazyDeleteChange(!!event.checked)}
/>
</WdTooltipWrapper>
<WdImgButton
className={PrimeIcons.QUESTION_CIRCLE}
tooltip={{
@@ -102,7 +150,7 @@ export const SystemSignatures = () => {
</InfoDrawer>
<InfoDrawer title={<b className="text-slate-50">How to delete?</b>}>
For delete any signature first of all you need select before
<br /> and then use <b className="text-sky-500">Backspace</b>
<br /> and then use <b className="text-sky-500">Del</b>
</InfoDrawer>
</div>
) as React.ReactNode,
@@ -118,7 +166,7 @@ export const SystemSignatures = () => {
System is not selected
</div>
) : (
<SystemSignaturesContent systemId={systemId} settings={settings} />
<SystemSignaturesContent systemId={systemId} settings={settings} onLazyDeleteChange={handleLazyDeleteChange} />
)}
{visible && (
<SystemSignatureSettingsDialog

View File

@@ -1,9 +1,11 @@
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useClipboard } from '@/hooks/Mapper/hooks/useClipboard';
import { parseSignatures } from '@/hooks/Mapper/helpers';
import { Commands, OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
import { WdTooltip, WdTooltipHandlers } from '@/hooks/Mapper/components/ui-kit';
import { GROUPS_LIST } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
import {
getGroupIdByRawGroup,
GROUPS_LIST,
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
import { DataTable, DataTableRowClickEvent, DataTableRowMouseEvent, SortOrder } from 'primereact/datatable';
import { Column } from 'primereact/column';
@@ -12,6 +14,7 @@ import useRefState from 'react-usestateref';
import { Setting } from '../SystemSignatureSettingsDialog';
import { useHotkey } from '@/hooks/Mapper/hooks';
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth.ts';
import { useClipboard } from '@/hooks/Mapper/hooks/useClipboard';
import classes from './SystemSignaturesContent.module.scss';
import clsx from 'clsx';
@@ -22,10 +25,10 @@ import {
getRowColorByTimeLeft,
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers';
import {
renderAddedTimeLeft,
renderDescription,
renderIcon,
renderInfoColumn,
renderInsertedTimeLeft,
renderUpdatedTimeLeft,
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
import useLocalStorageState from 'use-local-storage-state';
@@ -36,7 +39,9 @@ import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrap
import { COSMIC_SIGNATURE } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureSettingsDialog';
import {
SHOW_DESCRIPTION_COLUMN_SETTING,
SHOW_INSERTED_COLUMN_SETTING,
SHOW_UPDATED_COLUMN_SETTING,
LAZY_DELETE_SIGNATURES_SETTING,
KEEP_LAZY_DELETE_SETTING,
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures';
type SystemSignaturesSortSettings = {
sortField: string;
@@ -44,7 +49,7 @@ type SystemSignaturesSortSettings = {
};
const SORT_DEFAULT_VALUES: SystemSignaturesSortSettings = {
sortField: 'updated_at',
sortField: 'inserted_at',
sortOrder: -1,
};
@@ -54,6 +59,7 @@ interface SystemSignaturesContentProps {
hideLinkedSignatures?: boolean;
selectable?: boolean;
onSelect?: (signature: SystemSignature) => void;
onLazyDeleteChange?: (value: boolean) => void;
}
export const SystemSignaturesContent = ({
systemId,
@@ -61,14 +67,13 @@ export const SystemSignaturesContent = ({
hideLinkedSignatures,
selectable,
onSelect,
onLazyDeleteChange,
}: SystemSignaturesContentProps) => {
const { outCommand } = useMapRootState();
const [signatures, setSignatures, signaturesRef] = useRefState<SystemSignature[]>([]);
const [selectedSignatures, setSelectedSignatures] = useState<SystemSignature[]>([]);
const [nameColumnWidth, setNameColumnWidth] = useState('auto');
const [parsedSignatures, setParsedSignatures] = useState<SystemSignature[]>([]);
const [askUser, setAskUser] = useState(false);
const [selectedSignature, setSelectedSignature] = useState<SystemSignature | null>(null);
const [hoveredSig, setHoveredSig] = useState<SystemSignature | null>(null);
@@ -80,10 +85,20 @@ export const SystemSignaturesContent = ({
const tableRef = useRef<HTMLDivElement>(null);
const compact = useMaxWidth(tableRef, 260);
const medium = useMaxWidth(tableRef, 380);
const refData = useRef({ selectable });
refData.current = { selectable };
const tooltipRef = useRef<WdTooltipHandlers>(null);
const { clipboardContent } = useClipboard();
const { clipboardContent, setClipboardContent } = useClipboard();
const lazyDeleteValue = useMemo(() => {
return settings.find(setting => setting.key === LAZY_DELETE_SIGNATURES_SETTING)?.value ?? false;
}, [settings]);
const keepLazyDeleteValue = useMemo(() => {
return settings.find(setting => setting.key === KEEP_LAZY_DELETE_SETTING)?.value ?? false;
}, [settings]);
const handleResize = useCallback(() => {
if (tableRef.current) {
@@ -100,10 +115,7 @@ export const SystemSignaturesContent = ({
[settings],
);
const showInsertedColumn = useMemo(
() => settings.find(s => s.key === SHOW_INSERTED_COLUMN_SETTING)?.value,
[settings],
);
const showUpdatedColumn = useMemo(() => settings.find(s => s.key === SHOW_UPDATED_COLUMN_SETTING)?.value, [settings]);
const filteredSignatures = useMemo(() => {
return signatures
@@ -113,13 +125,14 @@ export const SystemSignaturesContent = ({
}
const isCosmicSignature = x.kind === COSMIC_SIGNATURE;
const preparedGroup = getGroupIdByRawGroup(x.group);
if (isCosmicSignature) {
const showCosmicSignatures = settings.find(y => y.key === COSMIC_SIGNATURE)?.value;
if (showCosmicSignatures) {
return !x.group || groupSettings.find(y => y.key === x.group)?.value;
return !x.group || groupSettings.find(y => y.key === preparedGroup)?.value;
} else {
return !!x.group && groupSettings.find(y => y.key === x.group)?.value;
return !!x.group && groupSettings.find(y => y.key === preparedGroup)?.value;
}
}
@@ -136,13 +149,17 @@ export const SystemSignaturesContent = ({
data: { system_id: systemId },
});
setAskUser(false);
setSignatures(signatures);
}, [outCommand, systemId]);
const handleUpdateSignatures = useCallback(
async (newSignatures: SystemSignature[], updateOnly: boolean) => {
const { added, updated, removed } = getActualSigs(signaturesRef.current, newSignatures, updateOnly);
async (newSignatures: SystemSignature[], updateOnly: boolean, skipUpdateUntouched?: boolean) => {
const { added, updated, removed } = getActualSigs(
signaturesRef.current,
newSignatures,
updateOnly,
skipUpdateUntouched,
);
const { signatures: updatedSignatures } = await outCommand({
type: OutCommand.updateSignatures,
@@ -160,34 +177,32 @@ export const SystemSignaturesContent = ({
[outCommand, systemId],
);
const handleDeleteSelected = useCallback(async () => {
if (selectable) {
return;
}
if (selectedSignatures.length === 0) {
return;
}
const selectedSignaturesEveIds = selectedSignatures.map(x => x.eve_id);
await handleUpdateSignatures(
signatures.filter(x => !selectedSignaturesEveIds.includes(x.eve_id)),
false,
);
}, [handleUpdateSignatures, selectable, signatures, selectedSignatures]);
const handleDeleteSelected = useCallback(
async (e: KeyboardEvent) => {
if (selectable) {
return;
}
if (selectedSignatures.length === 0) {
return;
}
e.preventDefault();
e.stopPropagation();
const selectedSignaturesEveIds = selectedSignatures.map(x => x.eve_id);
await handleUpdateSignatures(
signatures.filter(x => !selectedSignaturesEveIds.includes(x.eve_id)),
false,
true,
);
},
[handleUpdateSignatures, selectable, signatures, selectedSignatures],
);
const handleSelectAll = useCallback(() => {
setSelectedSignatures(signatures);
}, [signatures]);
const handleReplaceAll = useCallback(() => {
handleUpdateSignatures(parsedSignatures, false);
setAskUser(false);
}, [parsedSignatures, handleUpdateSignatures]);
const handleUpdateOnly = useCallback(() => {
handleUpdateSignatures(parsedSignatures, true);
setAskUser(false);
}, [parsedSignatures, handleUpdateSignatures]);
const handleSelectSignatures = useCallback(
// TODO still will be good to define types if we use typescript
// @ts-ignore
@@ -201,38 +216,51 @@ export const SystemSignaturesContent = ({
[onSelect, selectable],
);
useHotkey(true, ['a'], handleSelectAll);
useHotkey(false, ['Backspace'], handleDeleteSelected);
useEffect(() => {
if (selectable) {
return;
}
if (!clipboardContent) {
return;
}
const handlePaste = async (clipboardContent: string) => {
const newSignatures = parseSignatures(
clipboardContent,
settings.map(x => x.key),
);
const { removed } = getActualSigs(signaturesRef.current, newSignatures, false);
handleUpdateSignatures(newSignatures, !lazyDeleteValue);
if (!signaturesRef.current || !signaturesRef.current.length || !removed.length) {
handleUpdateSignatures(newSignatures, false);
} else {
setParsedSignatures(newSignatures);
setAskUser(true);
if (lazyDeleteValue && !keepLazyDeleteValue) {
onLazyDeleteChange?.(false);
}
}, [clipboardContent, selectable]);
};
const handleEnterRow = useCallback(
(e: DataTableRowMouseEvent) => {
setHoveredSig(filteredSignatures[e.index]);
tooltipRef.current?.show(e.originalEvent);
},
[filteredSignatures],
);
const handleLeaveRow = useCallback((e: DataTableRowMouseEvent) => {
tooltipRef.current?.hide(e.originalEvent);
setHoveredSig(null);
}, []);
useEffect(() => {
if (refData.current.selectable) {
return;
}
if (!clipboardContent?.text) {
return;
}
handlePaste(clipboardContent.text);
setClipboardContent(null);
}, [clipboardContent, selectable, lazyDeleteValue, keepLazyDeleteValue]);
useHotkey(true, ['a'], handleSelectAll);
useHotkey(false, ['Backspace', 'Delete'], handleDeleteSelected);
useEffect(() => {
if (!systemId) {
setSignatures([]);
setAskUser(false);
return;
}
@@ -266,19 +294,6 @@ export const SystemSignaturesContent = ({
};
}, []);
const handleEnterRow = useCallback(
(e: DataTableRowMouseEvent) => {
setHoveredSig(filteredSignatures[e.index]);
tooltipRef.current?.show(e.originalEvent);
},
[filteredSignatures],
);
const handleLeaveRow = useCallback((e: DataTableRowMouseEvent) => {
tooltipRef.current?.hide(e.originalEvent);
setHoveredSig(null);
}, []);
const renderToolbar = (/*row: SystemSignature*/) => {
return (
<div className="flex justify-end items-center gap-2 mr-[4px]">
@@ -330,7 +345,7 @@ export const SystemSignaturesContent = ({
return clsx(classes.TableRowCompact, 'bg-amber-500/50 hover:bg-amber-500/70 transition duration-200');
}
const dateClass = getRowColorByTimeLeft(row.updated_at ? new Date(row.updated_at) : undefined);
const dateClass = getRowColorByTimeLeft(row.inserted_at ? new Date(row.inserted_at) : undefined);
if (!dateClass) {
return clsx(classes.TableRowCompact, 'hover:bg-purple-400/20 transition duration-200');
}
@@ -357,11 +372,11 @@ export const SystemSignaturesContent = ({
header="Group"
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
hidden={compact}
style={{ maxWidth: 110, minWidth: 110, width: 110 }}
sortable
></Column>
<Column
field="info"
// header="Info"
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
body={renderInfoColumn}
style={{ maxWidth: nameColumnWidth }}
@@ -378,26 +393,26 @@ export const SystemSignaturesContent = ({
></Column>
)}
{showInsertedColumn && (
<Column
field="inserted_at"
header="Added"
dataType="date"
bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
body={renderAddedTimeLeft}
sortable
></Column>
{showUpdatedColumn && (
<Column
field="inserted_at"
header="Inserted"
field="updated_at"
header="Updated"
dataType="date"
bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
body={renderInsertedTimeLeft}
body={renderUpdatedTimeLeft}
sortable
></Column>
)}
<Column
field="updated_at"
header="Updated"
dataType="date"
bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
body={renderUpdatedTimeLeft}
sortable
></Column>
{!selectable && (
<Column
bodyClassName="p-0 pl-1 pr-2"
@@ -424,27 +439,6 @@ export const SystemSignaturesContent = ({
signatureData={selectedSignature}
/>
)}
{askUser && (
<div className="absolute left-[1px] top-[29px] h-[calc(100%-30px)] w-[calc(100%-3px)] bg-stone-900/10 backdrop-blur-sm">
<div className="absolute top-0 left-0 w-full h-full flex flex-col items-center justify-center">
<div className="text-stone-400/80 text-sm">
<div className="flex flex-col text-center gap-2">
<button className="p-button p-component p-button-outlined p-button-sm btn-wide">
<span className="p-button-label p-c" onClick={handleUpdateOnly}>
Update
</span>
</button>
<button className="p-button p-component p-button-outlined p-button-sm btn-wide">
<span className="p-button-label p-c" onClick={handleReplaceAll}>
Update & Delete missing
</span>
</button>
</div>
</div>
</div>
</div>
)}
</div>
</>
);

View File

@@ -1,4 +1,12 @@
import { GroupType, SignatureGroup } from '@/hooks/Mapper/types';
import {
GroupType,
SignatureGroup,
SignatureGroupENG,
SignatureGroupRU,
SignatureKind,
SignatureKindENG,
SignatureKindRU,
} from '@/hooks/Mapper/types';
export const TIME_ONE_MINUTE = 1000 * 60;
export const TIME_TEN_MINUTES = 1000 * 60 * 10;
@@ -24,3 +32,43 @@ export const GROUPS: Record<SignatureGroup, GroupType> = {
[SignatureGroup.Wormhole]: { id: SignatureGroup.Wormhole, icon: '/icons/brackets/wormhole.png', ...wh },
[SignatureGroup.CosmicSignature]: { id: SignatureGroup.CosmicSignature, icon: '/icons/x_close14.png', w: 9, h: 9 },
};
export const MAPPING_GROUP_TO_ENG = {
// ENGLISH
[SignatureGroupENG.GasSite]: SignatureGroup.GasSite,
[SignatureGroupENG.RelicSite]: SignatureGroup.RelicSite,
[SignatureGroupENG.DataSite]: SignatureGroup.DataSite,
[SignatureGroupENG.OreSite]: SignatureGroup.OreSite,
[SignatureGroupENG.CombatSite]: SignatureGroup.CombatSite,
[SignatureGroupENG.Wormhole]: SignatureGroup.Wormhole,
[SignatureGroupENG.CosmicSignature]: SignatureGroup.CosmicSignature,
// RUSSIAN
[SignatureGroupRU.GasSite]: SignatureGroup.GasSite,
[SignatureGroupRU.RelicSite]: SignatureGroup.RelicSite,
[SignatureGroupRU.DataSite]: SignatureGroup.DataSite,
[SignatureGroupRU.OreSite]: SignatureGroup.OreSite,
[SignatureGroupRU.CombatSite]: SignatureGroup.CombatSite,
[SignatureGroupRU.Wormhole]: SignatureGroup.Wormhole,
[SignatureGroupRU.CosmicSignature]: SignatureGroup.CosmicSignature,
};
export const MAPPING_TYPE_TO_ENG = {
// ENGLISH
[SignatureKindENG.CosmicSignature]: SignatureKind.CosmicSignature,
[SignatureKindENG.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
[SignatureKindENG.Structure]: SignatureKind.Structure,
[SignatureKindENG.Ship]: SignatureKind.Ship,
[SignatureKindENG.Deployable]: SignatureKind.Deployable,
[SignatureKindENG.Drone]: SignatureKind.Drone,
// RUSSIAN
[SignatureKindRU.CosmicSignature]: SignatureKind.CosmicSignature,
[SignatureKindRU.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
[SignatureKindRU.Structure]: SignatureKind.Structure,
[SignatureKindRU.Ship]: SignatureKind.Ship,
[SignatureKindRU.Deployable]: SignatureKind.Deployable,
[SignatureKindRU.Drone]: SignatureKind.Drone,
};
export const getGroupIdByRawGroup = (val: string) => MAPPING_GROUP_TO_ENG[val as SignatureGroup];

View File

@@ -6,6 +6,7 @@ export const getActualSigs = (
oldSignatures: SystemSignature[],
newSignatures: SystemSignature[],
updateOnly: boolean,
skipUpdateUntouched?: boolean,
): { added: SystemSignature[]; updated: SystemSignature[]; removed: SystemSignature[] } => {
const updated: SystemSignature[] = [];
const removed: SystemSignature[] = [];
@@ -19,7 +20,7 @@ export const getActualSigs = (
const isNeedUpgrade = getState(GROUPS_LIST, newSig) > getState(GROUPS_LIST, oldSig);
if (isNeedUpgrade) {
updated.push({ ...oldSig, group: newSig.group, name: newSig.name });
} else {
} else if (!skipUpdateUntouched) {
updated.push({ ...oldSig });
}
} else {

View File

@@ -1,7 +1,7 @@
export * from './renderIcon';
export * from './renderDescription';
export * from './renderName';
export * from './renderInsertedTimeLeft';
export * from './renderAddedTimeLeft';
export * from './renderUpdatedTimeLeft';
export * from './renderLinkedSystem';
export * from './renderInfoColumn';

View File

@@ -1,7 +1,7 @@
import { SystemSignature } from '@/hooks/Mapper/types';
import { TimeLeft } from '@/hooks/Mapper/components/ui-kit';
export const renderInsertedTimeLeft = (row: SystemSignature) => {
export const renderAddedTimeLeft = (row: SystemSignature) => {
return (
<div className="flex w-full items-center">
<TimeLeft cDate={row.inserted_at ? new Date(row.inserted_at) : undefined} />

View File

@@ -1,3 +0,0 @@
.whFontSize {
font-size: 11px !important;
}

View File

@@ -2,21 +2,31 @@ import { PrimeIcons } from 'primereact/api';
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
import { SystemViewStandalone, WHClassView } from '@/hooks/Mapper/components/ui-kit';
import {
k162Types,
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 classes from './renderInfoColumn.module.scss';
export const renderInfoColumn = (row: SystemSignature) => {
if (!row.group || row.group === SignatureGroup.Wormhole) {
let k162TypeOption = null;
if (row.custom_info) {
const customInfo = JSON.parse(row.custom_info);
if (customInfo.k162Type) {
k162TypeOption = k162Types.find(x => x.value === customInfo.k162Type);
}
}
return (
<div className="flex justify-start items-center gap-[6px]">
<div className="flex justify-start items-center gap-[4px]">
{row.type && (
<WHClassView
className="text-[11px]"
classNameWh={classes.whFontSize}
highlightName
classNameWh="!text-[11px] !font-bold"
hideWhClass={!!row.linked_system}
whClassName={row.type}
noOffset
@@ -24,9 +34,10 @@ export const renderInfoColumn = (row: SystemSignature) => {
/>
)}
{!row.linked_system && row.type === 'K162' && !!k162TypeOption && <>{renderK162Type(k162TypeOption)}</>}
{row.linked_system && (
<>
{/*<span className="w-4 h-4 hero-arrow-long-right"></span>*/}
<span title={row.linked_system?.solar_system_name}>
<SystemViewStandalone
className={clsx('select-none text-center cursor-context-menu')}

View File

@@ -16,6 +16,8 @@ export const MapRootContent = ({}: MapRootContentProps) => {
const { interfaceSettings } = useMapRootState();
const { isShowMenu } = interfaceSettings;
const themeClass = `${interfaceSettings.theme ?? 'neon'}-theme`;
const [showOnTheMap, setShowOnTheMap] = useState(false);
const [showMapSettings, setShowMapSettings] = useState(false);
const mapInterface = <MapInterface />;
@@ -26,27 +28,29 @@ export const MapRootContent = ({}: MapRootContentProps) => {
useSkipContextMenu();
return (
<Layout map={<MapWrapper />}>
{!isShowMenu ? (
<div className="absolute top-0 left-14 w-[calc(100%-3.5rem)] h-[calc(100%-3.5rem)] pointer-events-none">
<div className="absolute top-0 left-0 w-[calc(100%-3.5rem)] h-full pointer-events-none">
<Topbar />
<div className={themeClass}>
<Layout map={<MapWrapper />}>
{!isShowMenu ? (
<div className="absolute top-0 left-14 w-[calc(100%-3.5rem)] h-[calc(100%-3.5rem)] pointer-events-none">
<div className="absolute top-0 left-0 w-[calc(100%-3.5rem)] h-full pointer-events-none">
<Topbar />
{mapInterface}
</div>
<div className="absolute top-0 right-0 w-14 h-[calc(100%+3.5rem)] pointer-events-auto">
<RightBar onShowOnTheMap={handleShowOnTheMap} onShowMapSettings={handleShowMapSettings} />
</div>
</div>
) : (
<div className="absolute top-0 left-14 w-[calc(100%-3.5rem)] h-[calc(100%-3.5rem)] pointer-events-none">
<Topbar>
<MapContextMenu onShowOnTheMap={handleShowOnTheMap} onShowMapSettings={handleShowMapSettings} />
</Topbar>
{mapInterface}
</div>
<div className="absolute top-0 right-0 w-14 h-[calc(100%+3.5rem)] pointer-events-auto">
<RightBar onShowOnTheMap={handleShowOnTheMap} onShowMapSettings={handleShowMapSettings} />
</div>
</div>
) : (
<div className="absolute top-0 left-14 w-[calc(100%-3.5rem)] h-[calc(100%-3.5rem)] pointer-events-none">
<Topbar>
<MapContextMenu onShowOnTheMap={handleShowOnTheMap} onShowMapSettings={handleShowMapSettings} />
</Topbar>
{mapInterface}
</div>
)}
<OnTheMap show={showOnTheMap} onHide={() => setShowOnTheMap(false)} />
<MapSettings show={showMapSettings} onHide={() => setShowMapSettings(false)} />
</Layout>
)}
<OnTheMap show={showOnTheMap} onHide={() => setShowOnTheMap(false)} />
<MapSettings show={showMapSettings} onHide={() => setShowMapSettings(false)} />
</Layout>
</div>
);
};

View File

@@ -5,12 +5,14 @@ import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
import clsx from 'clsx';
import {
ConnectionType,
ConnectionOutput,
ConnectionInfoOutput,
OutCommand,
Passage,
SolarSystemConnection,
} from '@/hooks/Mapper/types';
import { PassageCard } from './PassageCard';
import { InfoDrawer, SystemView } from '@/hooks/Mapper/components/ui-kit';
import { kgToTons } from '@/hooks/Mapper/utils/kgToTons.ts';
@@ -75,8 +77,12 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => {
return connections.find(x => x.source === selectedConnection.source && x.target === selectedConnection.target);
}, [connections, selectedConnection]);
const isWormhole = useMemo(() => {
return cnInfo?.type !== ConnectionType.gate;
}, [cnInfo]);
const [passages, setPassages] = useState<Passage[]>([]);
const [info, setInfo] = useState<ConnectionInfoOutput>(null);
const [info, setInfo] = useState<ConnectionInfoOutput | null>(null);
const loadInfo = useCallback(
async (connection: SolarSystemConnection) => {
@@ -135,7 +141,7 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => {
>
<div className={clsx(classes.SidebarContent, '')}>
{/* Connection Info */}
<div className="px-2 pb-3 flex flex-col gap-2">
<div className="px-2 flex flex-col gap-2">
{/* Connection Info Row */}
<InfoDrawer title="Connection" rightSide>
<div className="flex justify-end gap-2 items-center">
@@ -153,14 +159,25 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => {
</div>
</InfoDrawer>
{/* Connection Info Row */}
<InfoDrawer title="Approximate mass of passages" rightSide>
{kgToTons(approximateMass)}
</InfoDrawer>
<div className="flex justify-between gap-2">
{/*Left column*/}
<div>
{isWormhole && info?.marl_eol_time && (
<InfoDrawer title="Mark EOL Time">
<TimeAgo timestamp={info.marl_eol_time} />
</InfoDrawer>
)}
</div>
<InfoDrawer title="Mark EOL Time" rightSide>
{info?.marl_eol_time ? <TimeAgo timestamp={info.marl_eol_time} /> : ' unknown '}
</InfoDrawer>
{/*Right column*/}
<div>
{isWormhole && (
<InfoDrawer title="Approximate mass of passages" rightSide>
{kgToTons(approximateMass)}
</InfoDrawer>
)}
</div>
</div>
<div className="flex gap-2"></div>
</div>

View File

@@ -5,6 +5,8 @@ import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrap
import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
import { OutCommand } from '@/hooks/Mapper/types';
import { MenuItem } from 'primereact/menuitem';
import { useMapCheckPermissions } from '@/hooks/Mapper/mapRootProvider/hooks/api';
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
export interface MapContextMenuProps {
onShowOnTheMap?: () => void;
@@ -14,6 +16,8 @@ export interface MapContextMenuProps {
export const MapContextMenu = ({ onShowOnTheMap, onShowMapSettings }: MapContextMenuProps) => {
const { outCommand, setInterfaceSettings } = useMapRootState();
const canTrackCharacters = useMapCheckPermissions([UserPermission.TRACK_CHARACTER]);
const menuRight = useRef<Menu>(null);
const handleAddCharacter = useCallback(() => {
@@ -24,34 +28,40 @@ export const MapContextMenu = ({ onShowOnTheMap, onShowMapSettings }: MapContext
}, [outCommand]);
const items = useMemo(() => {
return [
{
label: 'Tracking',
icon: 'pi pi-user-plus',
command: handleAddCharacter,
},
{
label: 'On the map',
icon: 'pi pi-hashtag',
command: onShowOnTheMap,
},
{ separator: true },
{
label: 'Settings',
icon: `pi pi-cog`,
command: onShowMapSettings,
},
{
label: 'Dock menu',
icon: 'pi pi-window-maximize',
command: () =>
setInterfaceSettings(x => ({
...x,
isShowMenu: !x.isShowMenu,
})),
},
] as MenuItem[];
}, [handleAddCharacter, onShowMapSettings, onShowOnTheMap, setInterfaceSettings]);
return (
[
{
label: 'Tracking',
icon: 'pi pi-user-plus',
command: handleAddCharacter,
visible: true,
},
{
label: 'On the map',
icon: 'pi pi-hashtag',
command: onShowOnTheMap,
visible: canTrackCharacters,
},
{ separator: true, visible: true },
{
label: 'Settings',
icon: `pi pi-cog`,
command: onShowMapSettings,
visible: true,
},
{
label: 'Dock menu',
icon: 'pi pi-window-maximize',
command: () =>
setInterfaceSettings(x => ({
...x,
isShowMenu: !x.isShowMenu,
})),
visible: true,
},
] as MenuItem[]
).filter(item => item.visible);
}, [canTrackCharacters, handleAddCharacter, onShowMapSettings, onShowOnTheMap, setInterfaceSettings]);
return (
<div className="ml-1">

View File

@@ -3,8 +3,13 @@ import { Dialog } from 'primereact/dialog';
import { useCallback, useMemo, useState } from 'react';
import { TabPanel, TabView } from 'primereact/tabview';
import { PrettySwitchbox } from './components';
import { InterfaceStoredSettings, InterfaceStoredSettingsProps, useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import {
InterfaceStoredSettingsProps,
useMapRootState,
InterfaceStoredSettings,
} from '@/hooks/Mapper/mapRootProvider';
import { OutCommand } from '@/hooks/Mapper/types';
import { Dropdown } from 'primereact/dropdown';
export enum UserSettingsRemoteProps {
link_signature_on_splash = 'link_signature_on_splash',
@@ -37,107 +42,182 @@ export interface MapSettingsProps {
onHide: () => void;
}
type CheckboxesList = {
type SettingsListItem = {
prop: keyof UserSettings;
label: string;
}[];
type: 'checkbox' | 'dropdown';
options?: { label: string; value: string }[];
};
const COMMON_CHECKBOXES_PROPS: CheckboxesList = [
{ prop: InterfaceStoredSettingsProps.isShowMinimap, label: 'Show Minimap' },
const COMMON_CHECKBOXES_PROPS: SettingsListItem[] = [
{
prop: InterfaceStoredSettingsProps.isShowMinimap,
label: 'Show Minimap',
type: 'checkbox',
},
];
const SYSTEMS_CHECKBOXES_PROPS: CheckboxesList = [
{ prop: InterfaceStoredSettingsProps.isShowKSpace, label: 'Highlight Low/High-security systems' },
{ prop: UserSettingsRemoteProps.select_on_spash, label: 'Auto-select splashed' },
const SYSTEMS_CHECKBOXES_PROPS: SettingsListItem[] = [
{
prop: InterfaceStoredSettingsProps.isShowKSpace,
label: 'Highlight Low/High-security systems',
type: 'checkbox',
},
{
prop: UserSettingsRemoteProps.select_on_spash,
label: 'Auto-select splashed',
type: 'checkbox',
},
];
const SIGNATURES_CHECKBOXES_PROPS: CheckboxesList = [
{ prop: UserSettingsRemoteProps.link_signature_on_splash, label: 'Link signature on splash' },
const SIGNATURES_CHECKBOXES_PROPS: SettingsListItem[] = [
{
prop: UserSettingsRemoteProps.link_signature_on_splash,
label: 'Link signature on splash',
type: 'checkbox',
},
{
prop: InterfaceStoredSettingsProps.isShowUnsplashedSignatures,
label: 'Show unsplashed signatures',
type: 'checkbox',
},
];
const CONNECTIONS_CHECKBOXES_PROPS: CheckboxesList = [
{ prop: UserSettingsRemoteProps.delete_connection_with_sigs, label: 'Delete connections to linked signatures' },
const CONNECTIONS_CHECKBOXES_PROPS: SettingsListItem[] = [
{
prop: UserSettingsRemoteProps.delete_connection_with_sigs,
label: 'Delete connections to linked signatures',
type: 'checkbox',
},
{
prop: InterfaceStoredSettingsProps.isThickConnections,
label: 'Thicker connections',
type: 'checkbox',
},
];
const UI_CHECKBOXES_PROPS: CheckboxesList = [
{ prop: InterfaceStoredSettingsProps.isShowMenu, label: 'Enable compact map menu bar' },
{ prop: InterfaceStoredSettingsProps.isThickConnections, label: 'Thicker connections' },
const UI_CHECKBOXES_PROPS: SettingsListItem[] = [
{
prop: InterfaceStoredSettingsProps.isShowMenu,
label: 'Enable compact map menu bar',
type: 'checkbox',
},
{
prop: InterfaceStoredSettingsProps.isShowBackgroundPattern,
label: 'Show background pattern',
type: 'checkbox',
},
{
prop: InterfaceStoredSettingsProps.isSoftBackground,
label: 'Enable soft background',
type: 'checkbox',
},
];
const THEME_OPTIONS = [
{ label: 'Default', value: 'neon' },
{ label: 'Pathfinder', value: 'pathfinder' },
];
const THEME_SETTING: SettingsListItem = {
prop: 'theme',
label: 'Theme',
type: 'dropdown',
options: THEME_OPTIONS,
};
export const MapSettings = ({ show, onHide }: MapSettingsProps) => {
const [activeIndex, setActiveIndex] = useState(0);
const { outCommand, interfaceSettings, setInterfaceSettings } = useMapRootState();
const [userRemoteSettings, setUserRemoteSettings] = useState<UserSettingsRemote>({ ...DEFAULT_REMOTE_SETTINGS });
const [userRemoteSettings, setUserRemoteSettings] = useState<UserSettingsRemote>({
...DEFAULT_REMOTE_SETTINGS,
});
const mergedSettings = useMemo(() => {
return {
...interfaceSettings,
...userRemoteSettings,
...interfaceSettings,
};
}, [userRemoteSettings, interfaceSettings]);
const handleShow = async () => {
const { user_settings } = await outCommand({
type: OutCommand.getUserSettings,
data: null,
});
setUserRemoteSettings({
...user_settings,
});
};
const handleChangeChecked = useCallback(
(prop: keyof UserSettings) => async (checked: boolean) => {
// @ts-ignore
if (UserSettingsRemoteList.includes(prop)) {
const handleSettingChange = useCallback(
async (prop: keyof UserSettings, value: boolean | string) => {
if (UserSettingsRemoteList.includes(prop as any)) {
const newRemoteSettings = {
...userRemoteSettings,
[prop]: checked,
[prop]: value,
};
await outCommand({
type: OutCommand.updateUserSettings,
data: newRemoteSettings,
});
setUserRemoteSettings(newRemoteSettings);
return;
} else {
setInterfaceSettings({
...interfaceSettings,
[prop]: value,
});
}
setInterfaceSettings({
...interfaceSettings,
[prop]: checked,
});
},
[interfaceSettings, outCommand, setInterfaceSettings, userRemoteSettings],
[userRemoteSettings, interfaceSettings, outCommand, setInterfaceSettings],
);
const renderCheckboxesList = (list: CheckboxesList) => {
return list.map(x => {
const renderSettingItem = (item: SettingsListItem) => {
const currentValue = mergedSettings[item.prop];
if (item.type === 'checkbox') {
return (
<PrettySwitchbox
key={x.prop}
label={x.label}
checked={mergedSettings[x.prop]}
setChecked={handleChangeChecked(x.prop)}
key={item.prop}
label={item.label}
checked={!!currentValue}
setChecked={(checked) => handleSettingChange(item.prop, checked)}
/>
);
});
}
if (item.type === 'dropdown' && item.options) {
return (
<div key={item.prop} className="flex items-center gap-2 mt-2">
<label className="text-sm">{item.label}:</label>
<Dropdown
className="text-sm"
value={currentValue}
options={item.options}
onChange={(e) => handleSettingChange(item.prop, e.value)}
placeholder="Select a theme"
/>
</div>
);
}
return null;
};
const renderSettingsList = (list: SettingsListItem[]) => {
return list.map(renderSettingItem);
};
return (
<Dialog
header="Map settings"
header="Map user settings"
visible={show}
draggable={false}
style={{ width: '550px' }}
onShow={handleShow}
onHide={() => {
if (!show) {
return;
}
if (!show) return;
setActiveIndex(0);
onHide();
}}
@@ -147,25 +227,35 @@ export const MapSettings = ({ show, onHide }: MapSettingsProps) => {
<div className={styles.verticalTabsContainer}>
<TabView
activeIndex={activeIndex}
onTabChange={e => setActiveIndex(e.index)}
onTabChange={(e) => setActiveIndex(e.index)}
className={styles.verticalTabView}
>
<TabPanel header="Common" headerClassName={styles.verticalTabHeader}>
<div className="w-full h-full flex flex-col gap-1">{renderCheckboxesList(COMMON_CHECKBOXES_PROPS)}</div>
</TabPanel>
<TabPanel header="Systems" headerClassName={styles.verticalTabHeader}>
<div className="w-full h-full flex flex-col gap-1">
{renderCheckboxesList(SYSTEMS_CHECKBOXES_PROPS)}
{renderSettingsList(COMMON_CHECKBOXES_PROPS)}
</div>
</TabPanel>
<TabPanel header="Systems" headerClassName={styles.verticalTabHeader}>
<div className="w-full h-full flex flex-col gap-1">
{renderSettingsList(SYSTEMS_CHECKBOXES_PROPS)}
</div>
</TabPanel>
<TabPanel header="Connections" headerClassName={styles.verticalTabHeader}>
{renderCheckboxesList(CONNECTIONS_CHECKBOXES_PROPS)}
{renderSettingsList(CONNECTIONS_CHECKBOXES_PROPS)}
</TabPanel>
<TabPanel header="Signatures" headerClassName={styles.verticalTabHeader}>
{renderCheckboxesList(SIGNATURES_CHECKBOXES_PROPS)}
{renderSettingsList(SIGNATURES_CHECKBOXES_PROPS)}
</TabPanel>
<TabPanel header="User Interface" headerClassName={styles.verticalTabHeader}>
{renderCheckboxesList(UI_CHECKBOXES_PROPS)}
{renderSettingsList(UI_CHECKBOXES_PROPS)}
</TabPanel>
<TabPanel header="Theme" headerClassName={styles.verticalTabHeader}>
{renderSettingItem(THEME_SETTING)}
</TabPanel>
</TabView>
</div>

View File

@@ -8,6 +8,8 @@ import clsx from 'clsx';
import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types';
import { CharacterCard, WdCheckbox } from '@/hooks/Mapper/components/ui-kit';
import useLocalStorageState from 'use-local-storage-state';
import { useMapCheckPermissions, useMapGetOption } from '@/hooks/Mapper/mapRootProvider/hooks/api';
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
type WindowLocalSettingsType = {
compact: boolean;
@@ -50,14 +52,22 @@ export const OnTheMap = ({ show, onHide }: OnTheMapProps) => {
defaultValue: STORED_DEFAULT_VALUES,
});
const restrictOfflineShowing = useMapGetOption('restrict_offline_showing');
const isAdminOrManager = useMapCheckPermissions([UserPermission.MANAGE_MAP]);
const showOffline = useMemo(
() => !restrictOfflineShowing || isAdminOrManager,
[isAdminOrManager, restrictOfflineShowing],
);
const sorted = useMemo(() => {
const out = characters.map(x => ({ ...x, isOwn: userCharacters.includes(x.eve_id) })).sort(sortCharacters);
if (!settings.hideOffline) {
if (showOffline && !settings.hideOffline) {
return out;
}
return out.filter(x => x.online);
}, [characters, settings.hideOffline, userCharacters]);
}, [showOffline, characters, settings.hideOffline, userCharacters]);
return (
<Sidebar
@@ -70,14 +80,16 @@ export const OnTheMap = ({ show, onHide }: OnTheMapProps) => {
>
<div className={clsx(classes.SidebarContent, '')}>
<div className={'flex justify-end items-center gap-2 px-3'}>
<WdCheckbox
size="m"
labelSide="left"
label={'Hide offline'}
value={settings.hideOffline}
classNameLabel="text-stone-400 hover:text-stone-200 transition duration-300"
onChange={() => setSettings(() => ({ ...settings, hideOffline: !settings.hideOffline }))}
/>
{showOffline && (
<WdCheckbox
size="m"
labelSide="left"
label={'Hide offline'}
value={settings.hideOffline}
classNameLabel="text-stone-400 hover:text-stone-200 transition duration-300"
onChange={() => setSettings(() => ({ ...settings, hideOffline: !settings.hideOffline }))}
/>
)}
</div>
<VirtualScroller

View File

@@ -6,6 +6,9 @@ import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
import { useMapCheckPermissions } from '@/hooks/Mapper/mapRootProvider/hooks/api';
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
interface RightBarProps {
onShowOnTheMap?: () => void;
onShowMapSettings?: () => void;
@@ -14,6 +17,8 @@ interface RightBarProps {
export const RightBar = ({ onShowOnTheMap, onShowMapSettings }: RightBarProps) => {
const { outCommand, interfaceSettings, setInterfaceSettings } = useMapRootState();
const canTrackCharacters = useMapCheckPermissions([UserPermission.TRACK_CHARACTER]);
const isShowMinimap = interfaceSettings.isShowMinimap === undefined ? true : interfaceSettings.isShowMinimap;
const handleAddCharacter = useCallback(() => {
@@ -64,19 +69,21 @@ export const RightBar = ({ onShowOnTheMap, onShowMapSettings }: RightBarProps) =
</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>
{canTrackCharacters && (
<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>
<div className="flex flex-col items-center mb-2 gap-1">
<WdTooltipWrapper content="User settings" position={TooltipPosition.left}>
<WdTooltipWrapper content="Map user settings" 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"

View File

@@ -1,6 +1,5 @@
import { Dialog } from 'primereact/dialog';
import { useCallback, useEffect } from 'react';
// import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { OutCommand, SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
import { Controller, FormProvider, useForm } from 'react-hook-form';
import {
@@ -25,102 +24,120 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
const { outCommand } = useMapRootState();
const handleShow = async () => {};
const form = useForm<Partial<SystemSignaturePrepared>>({});
const signatureForm = useForm<Partial<SystemSignaturePrepared>>({});
const handleSave = useCallback(async () => {
if (!signatureData) {
return;
}
const handleSave = useCallback(
async (e: any) => {
e?.preventDefault();
if (!signatureData) {
return;
}
const { group, ...values } = form.getValues();
let out = { ...signatureData };
const { group, ...values } = signatureForm.getValues();
let out = { ...signatureData };
switch (group) {
case SignatureGroup.Wormhole:
if (values.linked_system) {
await outCommand({
type: OutCommand.linkSignatureToSystem,
data: {
signature_eve_id: signatureData.eve_id,
solar_system_source: systemId,
solar_system_target: values.linked_system,
},
});
}
switch (group) {
case SignatureGroup.Wormhole:
if (values.linked_system) {
await outCommand({
type: OutCommand.linkSignatureToSystem,
data: {
signature_eve_id: signatureData.eve_id,
solar_system_source: systemId,
solar_system_target: values.linked_system,
},
});
}
if (values.type != null) {
out = { ...out, type: values.type };
}
out = {
...out,
custom_info: JSON.stringify({
k162Type: values.k162Type,
}),
};
if (signatureData.group !== SignatureGroup.Wormhole) {
out = { ...out, name: '' };
}
if (values.type != null) {
out = { ...out, type: values.type };
}
break;
case SignatureGroup.CosmicSignature:
out = { ...out, type: '', name: '' };
break;
default:
if (values.name != null) {
out = { ...out, name: values.name ?? '' };
}
}
if (signatureData.group !== SignatureGroup.Wormhole) {
out = { ...out, name: '' };
}
if (values.description != null) {
out = { ...out, description: values.description };
}
break;
case SignatureGroup.CosmicSignature:
out = { ...out, type: '', name: '' };
break;
default:
if (values.name != null) {
out = { ...out, name: values.name ?? '' };
}
}
if (values.description != null) {
out = { ...out, description: values.description };
}
// Note: when type of signature changed from WH to other type - we should drop name
if (
group !== SignatureGroup.Wormhole && // new
signatureData.group === SignatureGroup.Wormhole && // prev
signatureData.linked_system
) {
await outCommand({
type: OutCommand.unlinkSignature,
data: { signature_eve_id: signatureData.eve_id, solar_system_source: systemId },
});
out = { ...out, type: '' };
}
if (group === SignatureGroup.Wormhole && signatureData.linked_system != null && values.linked_system === null) {
await outCommand({
type: OutCommand.unlinkSignature,
data: { signature_eve_id: signatureData.eve_id, solar_system_source: systemId },
});
}
// Note: despite groups have optional type - this will always set
out = { ...out, group: group! };
// Note: when type of signature changed from WH to other type - we should drop name
if (
group !== SignatureGroup.Wormhole && // new
signatureData.group === SignatureGroup.Wormhole && // prev
signatureData.linked_system
) {
await outCommand({
type: OutCommand.unlinkSignature,
data: { signature_eve_id: signatureData.eve_id, solar_system_source: systemId },
type: OutCommand.updateSignatures,
data: {
system_id: systemId,
added: [],
updated: [out],
removed: [],
},
});
out = { ...out, type: '' };
}
if (group === SignatureGroup.Wormhole && signatureData.linked_system != null && values.linked_system === null) {
await outCommand({
type: OutCommand.unlinkSignature,
data: { signature_eve_id: signatureData.eve_id, solar_system_source: systemId },
});
}
// Note: despite groups have optional type - this will always set
out = { ...out, group: group! };
await outCommand({
type: OutCommand.updateSignatures,
data: {
system_id: systemId,
added: [],
updated: [out],
removed: [],
},
});
form.reset();
onHide();
}, [form, onHide, outCommand, signatureData, systemId]);
signatureForm.reset();
onHide();
},
[signatureForm, onHide, outCommand, signatureData, systemId],
);
useEffect(() => {
if (!signatureData) {
form.reset();
signatureForm.reset();
return;
}
const { linked_system, ...rest } = signatureData;
const { linked_system, custom_info, ...rest } = signatureData;
form.reset({
let k162Type = null;
if (custom_info) {
const customInfo = JSON.parse(custom_info);
k162Type = customInfo.k162Type;
}
signatureForm.reset({
linked_system: linked_system?.solar_system_id.toString() ?? undefined,
k162Type: k162Type,
...rest,
});
}, [form, signatureData]);
}, [signatureForm, signatureData]);
return (
<Dialog
@@ -138,32 +155,34 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
}}
>
<SystemsSettingsProvider initialValue={{ systemId }}>
<FormProvider {...form}>
<div className="flex flex-col gap-2 justify-between">
<div className="w-full flex flex-col gap-1 p-1 min-h-[150px]">
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
<span>Group:</span>
<SignatureGroupSelect name="group" />
</label>
<FormProvider {...signatureForm}>
<form onSubmit={handleSave}>
<div className="flex flex-col gap-2 justify-between">
<div className="w-full flex flex-col gap-1 p-1 min-h-[150px]">
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
<span>Group:</span>
<SignatureGroupSelect name="group" />
</label>
<SignatureGroupContent />
<SignatureGroupContent />
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
<span>Description:</span>
<Controller
name="description"
control={form.control}
render={({ field }) => (
<InputText placeholder="Type description" value={field.value} onChange={field.onChange} />
)}
/>
</label>
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
<span>Description:</span>
<Controller
name="description"
control={signatureForm.control}
render={({ field }) => (
<InputText placeholder="Type description" value={field.value} onChange={field.onChange} />
)}
/>
</label>
</div>
<div className="flex gap-2 justify-end">
<Button onClick={handleSave} outlined size="small" label="Save"></Button>
</div>
</div>
<div className="flex gap-2 justify-end">
<Button onClick={handleSave} outlined size="small" label="Save"></Button>
</div>
</div>
</form>
</FormProvider>
</SystemsSettingsProvider>
</Dialog>

View File

@@ -1,7 +1,13 @@
import { useFormContext } from 'react-hook-form';
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';
export const SignatureGroupContentWormholes = () => {
const { watch } = useFormContext<SystemSignature>();
const type = watch('type');
return (
<>
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
@@ -9,6 +15,13 @@ export const SignatureGroupContentWormholes = () => {
<SignatureWormholeTypeSelect name="type" />
</label>
{type === 'K162' && (
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
<span>K162 Type:</span>
<SignatureK162TypeSelect name="k162Type" />
</label>
)}
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
<span>Leads To:</span>
<SignatureLeadsToSelect name="linked_system" />

View File

@@ -0,0 +1,136 @@
import { Dropdown } from 'primereact/dropdown';
import clsx from 'clsx';
import { Controller, useFormContext } from 'react-hook-form';
import { useMemo } from 'react';
import { SystemSignature } from '@/hooks/Mapper/types';
import { WHClassView } from '@/hooks/Mapper/components/ui-kit';
export const k162Types = [
{
label: 'Hi-Sec',
value: 'hs',
whClassName: 'A641',
},
{
label: 'Low-Sec',
value: 'ls',
whClassName: 'J377',
},
{
label: 'Null-Sec',
value: 'ns',
whClassName: 'C248',
},
{
label: 'C1',
value: 'c1',
whClassName: 'E004',
},
{
label: 'C2',
value: 'c2',
whClassName: 'D382',
},
{
label: 'C3',
value: 'c3',
whClassName: 'L477',
},
{
label: 'C4',
value: 'c4',
whClassName: 'M001',
},
{
label: 'C5',
value: 'c5',
whClassName: 'L614',
},
{
label: 'C6',
value: 'c6',
whClassName: 'G008',
},
{
label: 'C13',
value: 'c13',
whClassName: 'A009',
},
{
label: 'Thera',
value: 'thera',
whClassName: 'F353',
},
{
label: 'Pochven',
value: 'pochven',
whClassName: 'F216',
},
];
const renderNoValue = () => <div className="flex gap-2 items-center">-Unknown-</div>;
// @ts-ignore
export const renderK162Type = (option: {
label?: string;
value: string;
security?: string;
system_class?: number;
whClassName?: string;
}) => {
if (!option) {
return renderNoValue();
}
const { value, whClassName = '' } = option;
if (value == null) {
return renderNoValue();
}
return (
<WHClassView
classNameWh="!text-[11px] !font-bold"
hideWhClassName
hideTooltip
whClassName={whClassName}
noOffset
useShortTitle
/>
);
};
export interface SignatureK162TypeSelectProps {
name: string;
defaultValue?: string;
}
export const SignatureK162TypeSelect = ({ name, defaultValue = '' }: SignatureK162TypeSelectProps) => {
const { control } = useFormContext<SystemSignature>();
const options = useMemo(() => {
return [{ value: null }, ...k162Types];
}, []);
return (
<Controller
// @ts-ignore
name={name}
control={control}
defaultValue={defaultValue}
render={({ field }) => {
return (
<Dropdown
value={field.value}
onChange={field.onChange}
options={options}
optionValue="value"
placeholder="Select K162 type"
className={clsx('w-full')}
scrollHeight="240px"
itemTemplate={renderK162Type}
valueTemplate={renderK162Type}
/>
);
}}
/>
);
};

View File

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

View File

@@ -13,13 +13,14 @@ import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
// @ts-ignore
const renderLinkedSystemItem = (option: { value: string }) => {
if (option.value == null) {
return <div className="flex gap-2 items-center">No linked system</div>;
const { value } = option;
if (value == null) {
return <div className="flex gap-2 items-center">- Unknown -</div>;
}
return (
<div className="flex gap-2 items-center">
<SystemView systemId={option.value} className={classes.SystemView} />
<SystemView systemId={value} className={classes.SystemView} />
</div>
);
};
@@ -65,6 +66,7 @@ export const SignatureLeadsToSelect = ({ name, defaultValue = '' }: SignatureLea
const leadsToOptions = useMemo(() => {
return [
{ value: null },
...leadsTo
.filter(systemId => {
const systemStatic = systemStatics.get(parseInt(systemId));

View File

@@ -17,7 +17,17 @@ const getPossibleWormholes = (systemStatic: SolarSystemStaticInfoRaw, wormholes:
// @ts-ignore
const spawnClassGroup = SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS[whType];
const possibleWHTypes = wormholes.filter(x => x.src.includes(spawnClassGroup));
const possibleWHTypes = wormholes.filter(x => {
return x.src.some(x => {
const [group, type] = x.split('-');
if (type === 'shattered') {
return systemStatic.is_shattered && group === spawnClassGroup;
}
return group === spawnClassGroup;
});
});
return {
statics: possibleWHTypes

View File

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

View File

@@ -2,7 +2,7 @@ import { Map } from '@/hooks/Mapper/components/map/Map.tsx';
import { useCallback, useRef, useState } from 'react';
import { OutCommand, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types';
import { MapRootData, useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts';
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 {
@@ -14,14 +14,18 @@ import classes from './MapWrapper.module.scss';
import { Connections } from '@/hooks/Mapper/components/mapRootContent/components/Connections';
import { ContextMenuSystemMultiple, useContextMenuSystemMultipleHandlers } from '../contexts/ContextMenuSystemMultiple';
import { getSystemById } from '@/hooks/Mapper/helpers';
import { Node } from 'reactflow';
import { Node, XYPosition } from 'reactflow';
import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
import { useMapEventListener } from '@/hooks/Mapper/events';
import { emitMapEvent, useMapEventListener } from '@/hooks/Mapper/events';
import { STORED_INTERFACE_DEFAULT_VALUES } from '@/hooks/Mapper/mapRootProvider/MapRootProvider';
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
import { useCommonMapEventProcessor } from '@/hooks/Mapper/components/mapWrapper/hooks/useCommonMapEventProcessor.ts';
import {
AddSystemDialog,
SearchOnSubmitCallback,
} from '@/hooks/Mapper/components/mapInterface/components/AddSystemDialog';
// TODO: INFO - this component needs for abstract work with Map instance
export const MapWrapper = () => {
@@ -34,6 +38,9 @@ export const MapWrapper = () => {
isShowMinimap = STORED_INTERFACE_DEFAULT_VALUES.isShowMinimap,
isShowKSpace,
isThickConnections,
isShowBackgroundPattern,
isSoftBackground,
theme,
},
} = useMapRootState();
const { deleteSystems } = useDeleteSystems();
@@ -45,6 +52,7 @@ export const MapWrapper = () => {
const [openSettings, setOpenSettings] = useState<string | null>(null);
const [openLinkSignatures, setOpenLinkSignatures] = useState<any | null>(null);
const [openCustomLabel, setOpenCustomLabel] = useState<string | null>(null);
const [openAddSystem, setOpenAddSystem] = useState<XYPosition | null>(null);
const [selectedConnection, setSelectedConnection] = useState<SolarSystemConnection | null>(null);
const ref = useRef({ selectedConnections, selectedSystems, systemContextProps, systems, deleteSystems });
@@ -121,6 +129,28 @@ export const MapWrapper = () => {
}
}, []);
const onAddSystem: OnMapAddSystemCallback = useCallback(({ coordinates }) => {
setOpenAddSystem(coordinates);
}, []);
const handleSubmitAddSystem: SearchOnSubmitCallback = useCallback(
async item => {
if (ref.current.systems.some(x => x.system_static_info.solar_system_id === item.value)) {
emitMapEvent({
name: Commands.centerSystem,
data: item.value.toString(),
});
return;
}
await outCommand({
type: OutCommand.manualAddSystem,
data: { coordinates: openAddSystem, solar_system_id: item.value },
});
},
[openAddSystem, outCommand],
);
return (
<>
<Map
@@ -135,6 +165,10 @@ export const MapWrapper = () => {
showKSpaceBG={isShowKSpace}
onManualDelete={handleManualDelete}
isThickConnections={isThickConnections}
isShowBackgroundPattern={isShowBackgroundPattern}
isSoftBackground={isSoftBackground}
theme={theme}
onAddSystem={onAddSystem}
/>
{openSettings != null && (
@@ -149,6 +183,12 @@ export const MapWrapper = () => {
<SystemLinkSignatureDialog data={openLinkSignatures} setVisible={() => setOpenLinkSignatures(null)} />
)}
<AddSystemDialog
visible={!!openAddSystem}
setVisible={() => setOpenAddSystem(null)}
onSubmit={handleSubmitAddSystem}
/>
<Connections selectedConnection={selectedConnection} onHide={() => setSelectedConnection(null)} />
<ContextMenuSystem

View File

@@ -18,7 +18,7 @@ const Topbar = ({ children }: WithChildren) => {
<nav
className={clsx(
'px-2 flex items-center justify-center min-w-0 h-12 pointer-events-auto',
'border-b border-gray-900 bg-gray-800 bg-opacity-5',
'border-b border-stone-800 bg-gray-800 bg-opacity-5',
'bg-opacity-70 bg-neutral-900',
)}
>

View File

@@ -3,15 +3,23 @@ import { SystemViewStandalone } from '@/hooks/Mapper/components/ui-kit';
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
import { useMemo } from 'react';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
export type SystemViewProps = {
systemId: string;
systemInfo?: SolarSystemStaticInfoRaw;
hideRegion?: boolean;
useSystemsCache?: boolean;
showCustomName?: boolean;
} & WithClassName;
export const SystemView = ({ systemId, hideRegion, className, showCustomName }: SystemViewProps) => {
export const SystemView = ({
systemId,
systemInfo: customSystemInfo,
hideRegion,
className,
showCustomName,
}: SystemViewProps) => {
const memSystems = useMemo(() => [systemId], [systemId]);
const { systems, loading } = useLoadSystemStatic({ systems: memSystems });
@@ -20,9 +28,12 @@ export const SystemView = ({ systemId, hideRegion, className, showCustomName }:
} = useMapRootState();
const systemInfo = useMemo(() => {
if (!systemId) {
return customSystemInfo;
}
return systems.get(parseInt(systemId));
// eslint-disable-next-line
}, [systemId, systems, loading]);
}, [customSystemInfo, systemId, systems, loading]);
const mapSystemInfo = useMemo(() => {
if (!showCustomName) {

View File

@@ -25,7 +25,6 @@ export const SystemViewStandalone = ({
className,
hideRegion,
customName,
class_title,
system_class,
solar_system_name,
@@ -38,6 +37,7 @@ export const SystemViewStandalone = ({
...props
}: SystemViewStandaloneProps) => {
const classTitleColor = getSystemClassStyles({ systemClass: system_class, security });
const isWH = isWormholeSpace(system_class);
const handleClick = useCallback(

View File

@@ -1,5 +1,4 @@
.WHClassViewRoot {
}
.WHClassViewContent {

View File

@@ -18,7 +18,9 @@ export interface WHClassViewProps {
whClassName: string;
noOffset?: boolean;
useShortTitle?: boolean;
hideTooltip?: boolean;
hideWhClass?: boolean;
hideWhClassName?: boolean;
highlightName?: boolean;
className?: string;
classNameWh?: string;
@@ -28,7 +30,9 @@ export const WHClassView = ({
whClassName,
noOffset,
useShortTitle,
hideTooltip,
hideWhClass,
hideWhClassName,
highlightName,
className,
classNameWh,
@@ -45,25 +49,27 @@ export const WHClassView = ({
return (
<div className={clsx(classes.WHClassViewRoot, className)}>
<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 "
>
<div className="flex gap-3">
<div className="flex flex-col gap-1">
<InfoDrawer title="Total mass">{prepareMass(whData.total_mass)}</InfoDrawer>
<InfoDrawer title="Jump mass">{prepareMass(whData.max_mass_per_jump)}</InfoDrawer>
{!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 "
>
<div className="flex gap-3">
<div className="flex flex-col gap-1">
<InfoDrawer title="Total mass">{prepareMass(whData.total_mass)}</InfoDrawer>
<InfoDrawer title="Jump mass">{prepareMass(whData.max_mass_per_jump)}</InfoDrawer>
</div>
<div className="flex flex-col gap-1">
<InfoDrawer title="Lifetime">{whData.lifetime}h</InfoDrawer>
<InfoDrawer title="Mass regen">{prepareMass(whData.mass_regen)}</InfoDrawer>
</div>
</div>
<div className="flex flex-col gap-1">
<InfoDrawer title="Lifetime">{whData.lifetime}h</InfoDrawer>
<InfoDrawer title="Mass regen">{prepareMass(whData.mass_regen)}</InfoDrawer>
</div>
</div>
</Tooltip>
</Tooltip>
)}
<div
className={clsx(
@@ -73,7 +79,7 @@ export const WHClassView = ({
`wh-name${whClassName}${uid}`,
)}
>
<span className={clsx({ [whClassStyle]: highlightName })}>{whClassName}</span>
{!hideWhClassName && <span className={clsx({ [whClassStyle]: highlightName })}>{whClassName}</span>}
{!hideWhClass && whClass && (
<span className={clsx(classes.WHClassName, whClassStyle, classNameWh)}>
{useShortTitle ? whClass.shortTitle : whClass.shortName}

View File

@@ -31,12 +31,15 @@ const prepareEffects = (effects: Record<string, EffectRaw>, effectName: string,
return out;
};
let counter = 0;
export interface WHEffectViewProps {
effectName: string;
effectPower: number;
className?: string;
}
export const WHEffectView = ({ effectName, effectPower }: WHEffectViewProps) => {
export const WHEffectView = ({ effectName, effectPower, className }: WHEffectViewProps) => {
const {
data: { effects },
} = useMapRootState();
@@ -49,7 +52,7 @@ export const WHEffectView = ({ effectName, effectPower }: WHEffectViewProps) =>
[effectName, effectPower, effects],
);
const targetClass = `wh-effect-name${effectInfo.id}`;
const targetClass = useMemo(() => `wh-effect-name${effectInfo.id}-${counter++}`, []);
return (
<div className={classes.WHEffectViewRoot}>
@@ -84,7 +87,7 @@ export const WHEffectView = ({ effectName, effectPower }: WHEffectViewProps) =>
</div>
</FixedTooltip>
<div className={clsx('font-bold select-none cursor-help w-min-content', effectClass, targetClass)}>
<div className={clsx('font-bold select-none cursor-help w-min-content', effectClass, targetClass, className)}>
{effectName}
</div>
</div>

View File

@@ -1,5 +1,5 @@
import { COSMIC_SIGNATURE } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureSettingsDialog';
import { SystemSignature } from '@/hooks/Mapper/types';
import { SignatureGroup, SignatureKind, SystemSignature } from '@/hooks/Mapper/types';
import { MAPPING_TYPE_TO_ENG } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
export const parseSignatures = (value: string, availableKeys: string[]): SystemSignature[] => {
const outArr: SystemSignature[] = [];
@@ -14,10 +14,12 @@ export const parseSignatures = (value: string, availableKeys: string[]): SystemS
continue;
}
const kind = MAPPING_TYPE_TO_ENG[sigArrInfo[1] as SignatureKind];
outArr.push({
eve_id: sigArrInfo[0],
kind: availableKeys.includes(sigArrInfo[1]) ? sigArrInfo[1] : COSMIC_SIGNATURE,
group: sigArrInfo[2],
kind: availableKeys.includes(kind) ? kind : SignatureKind.CosmicSignature,
group: sigArrInfo[2] as SignatureGroup,
name: sigArrInfo[3],
type: '',
});

View File

@@ -1,13 +1,13 @@
import { useState, useEffect, useCallback } from 'react';
export const useClipboard = () => {
const [clipboardContent, setClipboardContent] = useState<string | null>(null);
const [clipboardContent, setClipboardContent] = useState<{ text: string } | null>(null);
const [error, setError] = useState<string | null>(null);
const getClipboardContent = useCallback(async () => {
try {
const text = await navigator.clipboard.readText();
setClipboardContent(text);
setClipboardContent({ text });
setError(null);
} catch (err) {
setError('Failed to read clipboard content.');
@@ -18,7 +18,7 @@ export const useClipboard = () => {
const handlePaste = (event: ClipboardEvent) => {
const text = event.clipboardData?.getData('text');
if (text) {
setClipboardContent(text);
setClipboardContent({ text });
setError(null);
}
};
@@ -30,5 +30,5 @@ export const useClipboard = () => {
};
}, []);
return { clipboardContent, error, getClipboardContent };
return { clipboardContent, error, getClipboardContent, setClipboardContent };
};

View File

@@ -1,6 +1,6 @@
import { useEffect } from 'react';
export const useHotkey = (isMetaKey: boolean, hotkeys: string[], callback: () => void) => {
export const useHotkey = (isMetaKey: boolean, hotkeys: string[], callback: (e: KeyboardEvent) => void) => {
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if ((!isMetaKey || event.ctrlKey || event.metaKey) && hotkeys.includes(event.key)) {
@@ -8,14 +8,14 @@ export const useHotkey = (isMetaKey: boolean, hotkeys: string[], callback: () =>
return;
}
event.preventDefault();
callback();
callback(event);
}
};
window.addEventListener('keydown', handleKeyDown);
window.addEventListener('keydown', handleKeyDown, { capture: true });
return () => {
window.removeEventListener('keydown', handleKeyDown);
window.removeEventListener('keydown', handleKeyDown, { capture: true });
};
}, [isMetaKey, hotkeys, callback]);
};

View File

@@ -1,6 +1,6 @@
import { ContextStoreDataUpdate, useContextStore } from '@/hooks/Mapper/utils';
import { createContext, Dispatch, ForwardedRef, forwardRef, RefObject, SetStateAction, useContext } from 'react';
import { MapHandlers, MapUnionTypes, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types';
import { createContext, Dispatch, ForwardedRef, forwardRef, SetStateAction, useContext } from 'react';
import { MapUnionTypes, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types';
import { useMapRootHandlers } from '@/hooks/Mapper/mapRootProvider/hooks';
import { WithChildren } from '@/hooks/Mapper/types/common.ts';
import useLocalStorageState from 'use-local-storage-state';
@@ -25,6 +25,8 @@ const INITIAL_DATA: MapRootData = {
selectedSystems: [],
selectedConnections: [],
userPermissions: {},
options: {},
};
export enum InterfaceStoredSettingsProps {
@@ -32,6 +34,10 @@ export enum InterfaceStoredSettingsProps {
isShowMinimap = 'isShowMinimap',
isShowKSpace = 'isShowKSpace',
isThickConnections = 'isThickConnections',
isShowUnsplashedSignatures = 'isShowUnsplashedSignatures',
isShowBackgroundPattern = 'isShowBackgroundPattern',
isSoftBackground = 'isSoftBackground',
theme = 'theme',
}
export type InterfaceStoredSettings = {
@@ -39,6 +45,10 @@ export type InterfaceStoredSettings = {
isShowMinimap: boolean;
isShowKSpace: boolean;
isThickConnections: boolean;
isShowUnsplashedSignatures: boolean;
isShowBackgroundPattern: boolean;
isSoftBackground: boolean;
theme: string;
};
export const STORED_INTERFACE_DEFAULT_VALUES: InterfaceStoredSettings = {
@@ -46,7 +56,11 @@ export const STORED_INTERFACE_DEFAULT_VALUES: InterfaceStoredSettings = {
isShowMinimap: true,
isShowKSpace: false,
isThickConnections: false,
};
isShowUnsplashedSignatures: false,
isShowBackgroundPattern: true,
isSoftBackground: false,
theme: 'neon',
}
export interface MapRootContextProps {
update: ContextStoreDataUpdate<MapRootData>;

View File

@@ -1,5 +1,7 @@
export * from './useMapInit';
export * from './useMapUpdated';
export * from './useMapCheckPermissions';
export * from './useMapGetOption';
export * from './useRoutes';
export * from './useCommandsConnections';
export * from './useCommandsSystems';

View File

@@ -2,53 +2,86 @@ import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback, useRef } from 'react';
import { CommandAddSystems, CommandRemoveSystems, CommandUpdateSystems } from '@/hooks/Mapper/types';
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
import { emitMapEvent } from '@/hooks/Mapper/events';
import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
export const useCommandsSystems = () => {
const {
update,
data: { systems },
outCommand,
} = useMapRootState();
const { addSystemStatic } = useLoadSystemStatic({ systems: [] });
const ref = useRef({ systems, update });
ref.current = { systems, update };
const ref = useRef({ systems, update, addSystemStatic });
ref.current = { systems, update, addSystemStatic };
const addSystems = useCallback(
(addSystems: CommandAddSystems) => {
addSystems.forEach(sys => {
const addSystems = useCallback((systemsToAdd: CommandAddSystems) => {
const { update, addSystemStatic, systems } = ref.current;
systemsToAdd.forEach(sys => {
if (sys.system_static_info) {
addSystemStatic(sys.system_static_info);
});
}
});
update({
systems: [...ref.current.systems.filter(sys => !addSystems.some(x => sys.id === x.id)), ...addSystems],
});
},
[addSystemStatic, update],
);
update(
{
systems: [...systems.filter(sys => !systemsToAdd.some(x => sys.id === x.id)), ...systemsToAdd],
},
true,
);
}, []);
const removeSystems = useCallback((toRemove: CommandRemoveSystems) => {
const { update, systems } = ref.current;
update({
systems: systems.filter(x => !toRemove.includes(parseInt(x.id))),
});
update(
{
systems: systems.filter(x => !toRemove.includes(parseInt(x.id))),
},
true,
);
}, []);
const updateSystems = useCallback(
(systems: CommandUpdateSystems) => {
const out = ref.current.systems.map(current => {
const newSystem = systems.find(x => current.id === x.id);
if (!newSystem) {
return current;
}
const updateSystems = useCallback((updatedSystems: CommandUpdateSystems) => {
const { update, systems } = ref.current;
return newSystem;
const out = systems.map(current => {
const newSystem = updatedSystems.find(x => current.id === x.id);
if (!newSystem) {
return current;
}
return newSystem;
});
update({ systems: out }, true);
}, []);
const updateSystemSignatures = useCallback(
async (systemId: string) => {
const { update, systems } = ref.current;
const { signatures } = await outCommand({
type: OutCommand.getSignatures,
data: { system_id: `${systemId}` },
});
update({ systems: out });
const out = systems.map(current => {
if (current.id === `${systemId}`) {
return { ...current, system_signatures: signatures };
}
return current;
});
update({ systems: out }, true);
emitMapEvent({ name: Commands.updateSystems, data: out });
},
[update],
[outCommand],
);
return { addSystems, removeSystems, updateSystems };
return { addSystems, removeSystems, updateSystems, updateSystemSignatures };
};

View File

@@ -0,0 +1,11 @@
import { useMemo } from 'react';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
export const useMapCheckPermissions = (permissions: UserPermission[]) => {
const {
data: { userPermissions },
} = useMapRootState();
return useMemo(() => permissions.every(x => userPermissions[x]), [permissions, userPermissions]);
};

View File

@@ -0,0 +1,10 @@
import { useMemo } from 'react';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
export const useMapGetOption = (option: string) => {
const {
data: { options },
} = useMapRootState();
return useMemo(() => options[option], [option, options]);
};

View File

@@ -19,6 +19,8 @@ export const useMapInit = () => {
user_characters,
present_characters,
hubs,
user_permissions,
options,
}: CommandInit) => {
const updateData: Partial<MapRootData> = {};
@@ -51,10 +53,18 @@ export const useMapInit = () => {
updateData.connections = connections;
}
if (user_permissions) {
updateData.userPermissions = user_permissions;
}
if (hubs) {
updateData.hubs = hubs;
}
if (options) {
updateData.options = options;
}
if (system_static_infos) {
system_static_infos.forEach(static_info => {
addSystemStatic(static_info);

View File

@@ -13,6 +13,7 @@ import {
CommandRemoveSystems,
CommandRoutes,
Commands,
CommandSignaturesUpdated,
CommandUpdateConnection,
CommandUpdateSystems,
MapHandlers,
@@ -31,7 +32,7 @@ import { emitMapEvent } from '@/hooks/Mapper/events';
export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
const mapInit = useMapInit();
const { addSystems, removeSystems, updateSystems } = useCommandsSystems();
const { addSystems, removeSystems, updateSystems, updateSystemSignatures } = useCommandsSystems();
const { addConnections, removeConnections, updateConnection } = useCommandsConnections();
const { charactersUpdated, characterAdded, characterRemoved, characterUpdated, presentCharacters } =
useCommandsCharacters();
@@ -87,6 +88,14 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
mapRoutes(data as CommandRoutes);
break;
case Commands.signaturesUpdated: // USED
updateSystemSignatures(data as CommandSignaturesUpdated);
break;
case Commands.linkSignatureToSystem: // USED
// do nothing here
break;
case Commands.centerSystem: // USED
// do nothing here
break;
@@ -95,22 +104,10 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
// do nothing here
break;
// case Commands.linkSignatureToSystem:
// // TODO command data type lost
// // @ts-ignore
// emitMapEvent({ name: Commands.linkSignatureToSystem, data });
// break;
case Commands.killsUpdated:
// do nothing here
break;
// case Commands.signaturesUpdated:
// // TODO command data type lost
// // @ts-ignore
// emitMapEvent({ name: Commands.signaturesUpdated, data });
// break;
default:
console.warn(`JOipP Interface handlers: Unknown command: ${type}`, data);
break;

View File

@@ -1,3 +1,8 @@
export enum ConnectionType {
wormhole,
gate,
}
export enum MassState {
normal,
half,
@@ -9,16 +14,12 @@ export enum TimeStatus {
eol,
}
// export enum ShipSizeStatus {
// small, // frigates, destroyers - less than 5K t
// medium, // less than 20K t
// large, // less than 375K t
// capital, // less than 1.8M t
// }
export enum ShipSizeStatus {
small, // frigates, destroyers - less than 5K t
normal,
small = 0, // frigates, destroyers - less than 5K t
medium = 1, // less than 62K t
large = 2, // less than 375K t
freight = 3, // less than 1M t
capital = 4, // less than 1.8M t
}
export type SolarSystemConnection = {
@@ -32,4 +33,6 @@ export type SolarSystemConnection = {
source: string;
target: string;
type?: ConnectionType;
};

View File

@@ -6,3 +6,4 @@ export * from './system';
export * from './mapUnionTypes';
export * from './signatures';
export * from './connectionPassages';
export * from './permissions';

View File

@@ -4,6 +4,7 @@ import { WormholeDataRaw } from '@/hooks/Mapper/types/wormholes.ts';
import { CharacterTypeRaw } from '@/hooks/Mapper/types/character.ts';
import { RoutesList } from '@/hooks/Mapper/types/routes.ts';
import { Kill } from '@/hooks/Mapper/types/kills.ts';
import { UserPermissions } from '@/hooks/Mapper/types';
export enum Commands {
init = 'init',
@@ -58,9 +59,10 @@ export type CommandInit = {
characters: CharacterTypeRaw[];
present_characters: string[];
user_characters: string[];
user_permissions: any;
user_permissions: UserPermissions;
hubs: string[];
routes: RoutesList;
options: Record<string, string | boolean>;
reset?: boolean;
};
export type CommandAddSystems = SolarSystemRawType[];
@@ -120,12 +122,14 @@ export enum OutCommand {
getSystemStaticInfos = 'get_system_static_infos',
getConnectionInfo = 'get_connection_info',
updateConnectionTimeStatus = 'update_connection_time_status',
updateConnectionType = 'update_connection_type',
updateConnectionMassStatus = 'update_connection_mass_status',
updateConnectionShipSizeType = 'update_connection_ship_size_type',
updateConnectionLocked = 'update_connection_locked',
updateConnectionCustomInfo = 'update_connection_custom_info',
updateSignatures = 'update_signatures',
updateSystemName = 'update_system_name',
updateSystemTemporaryName = 'update_system_temporary_name',
updateSystemDescription = 'update_system_description',
updateSystemLabels = 'update_system_labels',
updateSystemLocked = 'update_system_locked',
@@ -150,6 +154,7 @@ export enum OutCommand {
getUserSettings = 'get_user_settings',
updateUserSettings = 'update_user_settings',
unlinkSignature = 'unlink_signature',
searchSystems = 'search_systems',
}
export type OutCommandHandler = <T = any>(event: { type: OutCommand; data: any }) => Promise<T>;

View File

@@ -4,6 +4,7 @@ import { CharacterTypeRaw } from '@/hooks/Mapper/types/character.ts';
import { SolarSystemRawType } from '@/hooks/Mapper/types/system.ts';
import { RoutesList } from '@/hooks/Mapper/types/routes.ts';
import { SolarSystemConnection } from '@/hooks/Mapper/types/connection.ts';
import { UserPermissions } from '@/hooks/Mapper/types';
export type MapUnionTypes = {
wormholesData: Record<string, WormholeDataRaw>;
@@ -17,4 +18,6 @@ export type MapUnionTypes = {
routes?: RoutesList;
kills: Record<number, number>;
connections: SolarSystemConnection[];
userPermissions: Partial<UserPermissions>;
options: Record<string, string | boolean>;
};

View File

@@ -0,0 +1,19 @@
export enum UserPermission {
ADMIN_MAP = 'admin_map',
MANAGE_MAP = 'manage_map',
VIEW_SYSTEM = 'view_system',
VIEW_CHARACTER = 'view_character',
VIEW_CONNECTION = 'view_connection',
ADD_SYSTEM = 'add_system',
ADD_CONNECTION = 'add_connection',
UPDATE_SYSTEM = 'update_system',
TRACK_CHARACTER = 'track_character',
DELETE_CONNECTION = 'delete_connection',
DELETE_SYSTEM = 'delete_system',
LOCK_SYSTEM = 'lock_system',
ADD_ACL = 'add_acl',
DELETE_ACL = 'delete_acl',
DELETE_MAP = 'delete_map',
}
export type UserPermissions = Record<UserPermission, boolean>;

View File

@@ -10,6 +10,15 @@ export enum SignatureGroup {
CombatSite = 'Combat Site',
}
export enum SignatureKind {
CosmicSignature = 'Cosmic Signature',
CosmicAnomaly = 'Cosmic Anomaly',
Structure = 'Structure',
Ship = 'Ship',
Deployable = 'Deployable',
Drone = 'Drone',
}
export type GroupType = {
id: string;
icon: string;
@@ -19,12 +28,52 @@ export type GroupType = {
export type SystemSignature = {
eve_id: string;
kind: string;
kind: SignatureKind;
name: string;
custom_info?: string;
description?: string;
group: SignatureGroup;
type: string;
k162Type?: string;
linked_system?: SolarSystemStaticInfoRaw;
inserted_at?: string;
updated_at?: string;
};
export enum SignatureKindENG {
CosmicSignature = 'Cosmic Signature',
CosmicAnomaly = 'Cosmic Anomaly',
Structure = 'Structure',
Ship = 'Ship',
Deployable = 'Deployable',
Drone = 'Drone',
}
export enum SignatureKindRU {
CosmicSignature = 'Скрытый сигнал',
CosmicAnomaly = 'Космическая аномалия',
Structure = 'Сооружение',
Ship = 'Корабль',
Deployable = 'Полевые блоки',
Drone = 'Дрон',
}
export enum SignatureGroupENG {
CosmicSignature = 'Cosmic Signature',
Wormhole = 'Wormhole',
GasSite = 'Gas Site',
RelicSite = 'Relic Site',
DataSite = 'Data Site',
OreSite = 'Ore Site',
CombatSite = 'Combat Site',
}
export enum SignatureGroupRU {
CosmicSignature = 'Скрытый сигнал',
Wormhole = 'Червоточина',
GasSite = 'Газовый район',
RelicSite = 'Археологический район',
DataSite = 'Информационный район',
OreSite = 'Астероидный район',
CombatSite = 'Боевой район',
}

View File

@@ -1,5 +1,7 @@
import { XYPosition } from 'reactflow';
import { SystemSignature } from '@/hooks/Mapper/types/signatures';
export enum SolarSystemStaticInfoRawNames {
regionId = 'region_id',
constellationId = 'constellation_id',
@@ -114,6 +116,18 @@ export type SolarSystemRawType = {
tag: string | null;
status: number;
name: string | null;
temporary_name: string | null;
system_static_info: SolarSystemStaticInfoRaw;
system_signatures: SystemSignature[];
};
export type SearchSystemItem = {
class_title: string;
constellation_name: string;
label: string;
region_name: string;
system_static_info: SolarSystemStaticInfoRaw;
value: number;
};

View File

@@ -1,17 +1,14 @@
export default {
mounted() {
const hook = this;
const url = hook.el.dataset.url;
const button = hook.el;
const button = this.el;
button.addEventListener('click', function () {
// Get the URL from the data attribute
button.classList.remove('copied');
// Copy the URL to the clipboard
navigator.clipboard
.writeText(url)
.writeText(button.dataset.url)
.then(() => {
button.classList.add('copied');
})

View File

@@ -8,6 +8,7 @@ import CopyToClipboard from './copyToClipboard';
import DownloadJson from './downloadJson';
import NewVersionUpdate from './newVersionUpdate';
import MapAction from './maps/mapAction';
import ShowCharactersAddAlert from './showCharactersAddAlert';
export default {
DownloadJson,
@@ -20,4 +21,5 @@ export default {
Ping,
CopyToClipboard,
NewVersionUpdate,
ShowCharactersAddAlert,
};

View File

@@ -4,13 +4,26 @@ export default {
mounted() {
const hook = this;
const button = hook.el.querySelector('.update-button');
const refreshZone = hook.el.querySelector('#refresh-area');
button.addEventListener('click', function () {
const lastVersion = hook.el.dataset.version;
localStorage.setItem(LAST_VERSION_KEY, lastVersion);
window.location.reload();
});
const handleUpdate = function (e: Event) {
const hexBricks = hook.el.querySelectorAll('.hex-brick');
// Add a new class to each element
hexBricks.forEach(el => {
el.classList.add('hex-brick--active');
});
setTimeout(() => {
const lastVersion = hook.el.dataset.version;
localStorage.setItem(LAST_VERSION_KEY, lastVersion);
window.location.reload();
}, 2000);
};
refreshZone.addEventListener('click', handleUpdate);
refreshZone.addEventListener('mouseover', handleUpdate);
this.updated();
},

View File

@@ -0,0 +1,11 @@
export default {
mounted() {
this.pushEvent('restore_show_characters_add_alert', {
value: localStorage.getItem('wanderer:hide_characters_add_alert') !== 'true',
});
document.getElementById('characters-add-alert-hide')?.addEventListener('click', e => {
localStorage.setItem('wanderer:hide_characters_add_alert', 'true');
});
},
};

View File

@@ -13,8 +13,6 @@
},
"dependencies": {
"@formkit/auto-animate": "0.7.0",
"@react-rxjs/core": "^0.10.7",
"@react-rxjs/utils": "^0.9.7",
"@shopify/draggable": "^1.1.3",
"clsx": "^2.1.1",
"daisyui": "^4.11.1",
@@ -34,7 +32,6 @@
"react-hook-form": "^7.53.1",
"react-usestateref": "^1.0.9",
"reactflow": "^11.11.4",
"rxjs": "^7.8.1",
"tailwindcss": "^3.3.6",
"topbar": "^3.0.0",
"use-local-storage-state": "^19.3.1"

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

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