Compare commits

...

95 Commits

Author SHA1 Message Date
Dmitry Popov
e2b56c019e Revert "hotfix: add ARM64 compatible Docker image (#94)"
This reverts commit f85317983c.
2025-01-07 02:23:33 +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
113 changed files with 7368 additions and 2892 deletions

View File

@@ -2,6 +2,267 @@
<!-- changelog -->
## [v1.32.6](https://github.com/wanderer-industries/wanderer/compare/v1.32.5...v1.32.6) (2025-01-06)
## [v1.32.5](https://github.com/wanderer-industries/wanderer/compare/v1.32.4...v1.32.5) (2025-01-04)
### Bug Fixes:
* map: prevent deselect on click to map (#96)
## [v1.32.4](https://github.com/wanderer-industries/wanderer/compare/v1.32.3...v1.32.4) (2025-01-02)
### Bug Fixes:
* Map: Fix 'Character Activity' modal
## [v1.32.3](https://github.com/wanderer-industries/wanderer/compare/v1.32.2...v1.32.3) (2025-01-02)
### Bug Fixes:
* Map: Fix 'Allow only tracked characters' saving
## [v1.32.2](https://github.com/wanderer-industries/wanderer/compare/v1.32.1...v1.32.2) (2025-01-02)
## [v1.32.1](https://github.com/wanderer-industries/wanderer/compare/v1.32.0...v1.32.1) (2024-12-25)
## [v1.32.0](https://github.com/wanderer-industries/wanderer/compare/v1.31.0...v1.32.0) (2024-12-24)
### Features:
* Map: Add search & update manual adding systems API
* Map: Add search & update manual adding systems API
### Bug Fixes:
* Map: Added ability to add new system to routes via routes widget
* Map: Reworked add system to map
## [v1.31.0](https://github.com/wanderer-industries/wanderer/compare/v1.30.2...v1.31.0) (2024-12-20)
### Features:
* Core: Show tracking for new users by default. Auto link characters to account fix. Add character loading indicators.
## [v1.30.2](https://github.com/wanderer-industries/wanderer/compare/v1.30.1...v1.30.2) (2024-12-17)
### Bug Fixes:
* Map: Fixed problem with ship size change.
## [v1.30.1](https://github.com/wanderer-industries/wanderer/compare/v1.30.0...v1.30.1) (2024-12-17)
### Bug Fixes:
* Map: Little rework Signatures header: change System Signatures to Signatures, and show selected system name instead.
* Map: update default size of connections
* Map: add ability set the size of wormhole and mark connection with label
## [v1.30.0](https://github.com/wanderer-industries/wanderer/compare/v1.29.5...v1.30.0) (2024-12-16)
### Features:
* 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
* 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.
### Bug Fixes:
* Map: fixed U210, K346 for C4 shattered systems
* 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.
* Map: removed unnecessary log
* Map: Uncomment what should not be commented
## [v1.29.5](https://github.com/wanderer-industries/wanderer/compare/v1.29.4...v1.29.5) (2024-12-14)
### Bug Fixes:
* Core: Fix character trackers cleanup
## [v1.29.4](https://github.com/wanderer-industries/wanderer/compare/v1.29.3...v1.29.4) (2024-12-10)
### Bug Fixes:
* Core: Small fixes
## [v1.29.3](https://github.com/wanderer-industries/wanderer/compare/v1.29.2...v1.29.3) (2024-12-07)
### Bug Fixes:
* Core: Increased eve DB data download timeout
## [v1.29.2](https://github.com/wanderer-industries/wanderer/compare/v1.29.1...v1.29.2) (2024-12-07)
### Bug Fixes:
* Core: Fix unpkg CDN issues, fix Abyssals sites adding as systems on map
## [v1.29.1](https://github.com/wanderer-industries/wanderer/compare/v1.29.0...v1.29.1) (2024-12-05)
## [v1.29.0](https://github.com/wanderer-industries/wanderer/compare/v1.28.1...v1.29.0) (2024-12-05)
### Features:
* Signatures: Show 'Unsplashed' signatures on the map (optionally)
## [v1.28.1](https://github.com/wanderer-industries/wanderer/compare/v1.28.0...v1.28.1) (2024-12-04)
## [v1.28.0](https://github.com/wanderer-industries/wanderer/compare/v1.27.1...v1.28.0) (2024-12-04)
### Features:
* Map: Added an option to show 'Offline characters' to map admins & managers only
## [v1.27.1](https://github.com/wanderer-industries/wanderer/compare/v1.27.0...v1.27.1) (2024-12-04)
### Bug Fixes:
* Map: Fix 'On the map' visibility
## [v1.27.0](https://github.com/wanderer-industries/wanderer/compare/v1.26.1...v1.27.0) (2024-12-03)
### Features:
* Map: Hide 'On the map' list for 'Viewer' role
## [v1.26.1](https://github.com/wanderer-industries/wanderer/compare/v1.26.0...v1.26.1) (2024-12-03)
### Bug Fixes:
* Signatures: Fix error on splash wh
## [v1.26.0](https://github.com/wanderer-industries/wanderer/compare/v1.25.2...v1.26.0) (2024-12-03)
### Features:
* Signatures: Keep 'Lazy delete' enabled setting
## [v1.25.2](https://github.com/wanderer-industries/wanderer/compare/v1.25.1...v1.25.2) (2024-12-01)
### Bug Fixes:
* Signatures: Fix lazy delete on system switch
## [v1.25.1](https://github.com/wanderer-industries/wanderer/compare/v1.25.0...v1.25.1) (2024-11-28)
### Bug Fixes:
* Signatures: Fix colors & add 'Backspace' hotkey to delete signatures
## [v1.25.0](https://github.com/wanderer-industries/wanderer/compare/v1.24.2...v1.25.0) (2024-11-28)
### Features:
* Signatures: Automatically remove signature if linked system removed
## [v1.24.2](https://github.com/wanderer-industries/wanderer/compare/v1.24.1...v1.24.2) (2024-11-27)
### Bug Fixes:
* Signatures: Fix paste signatures
## [v1.24.1](https://github.com/wanderer-industries/wanderer/compare/v1.24.0...v1.24.1) (2024-11-27)
## [v1.24.0](https://github.com/wanderer-industries/wanderer/compare/v1.23.0...v1.24.0) (2024-11-27)
### Features:
* Signatures: Added "Lazy delete" option & got rid of update popup
## [v1.23.0](https://github.com/wanderer-industries/wanderer/compare/v1.22.0...v1.23.0) (2024-11-26)

View File

@@ -870,3 +870,63 @@ body {
}
}
/* 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

@@ -8,6 +8,7 @@ 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,
@@ -44,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}
/>

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

@@ -2,3 +2,7 @@
width: 100%;
height: 100%;
}
.BackgroundAlternateColor {
}

View File

@@ -29,11 +29,12 @@ 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';
const DEFAULT_VIEW_PORT = { zoom: 1, x: 0, y: 0 };
@@ -91,12 +92,15 @@ 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;
}
const MapComp = ({
@@ -111,14 +115,17 @@ const MapComp = ({
isShowMinimap,
showKSpaceBG,
isThickConnections,
isShowBackgroundPattern,
isSoftBackground,
onAddSystem,
}: MapCompProps) => {
const { getNode } = useReactFlow();
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();
@@ -179,6 +186,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];
@@ -216,7 +229,7 @@ const MapComp = ({
return (
<>
<div className={classes.MapRoot}>
<div className={clsx(classes.MapRoot, { ['bg-neutral-900']: isSoftBackground })}>
<ReactFlow
nodes={nodes}
edges={edges}
@@ -263,7 +276,7 @@ const MapComp = ({
selectionMode={SelectionMode.Partial}
>
{isShowMinimap && <MiniMap pannable zoomable ariaLabel="Mini map" className={minimapClasses} />}
<Background />
{isShowBackgroundPattern && <Background />}
</ReactFlow>
{/* <button className="z-auto btn btn-primary absolute top-20 right-20" onClick={handleGetPassages}>
Test // DON NOT REMOVE

View File

@@ -6,7 +6,14 @@ import { Edge } from '@reactflow/core/dist/esm/types/edges';
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>;
@@ -48,10 +55,6 @@ export const ContextMenuConnection: React.FC<ContextMenuConnectionProps> = ({
icon: PrimeIcons.CLOCK,
command: onChangeTimeState,
},
]
: []),
...(isWormhole
? [
{
label: `Frigate`,
className: clsx({
@@ -60,13 +63,9 @@ export const ContextMenuConnection: React.FC<ContextMenuConnectionProps> = ({
icon: PrimeIcons.CLOUD,
command: () =>
onChangeShipSizeStatus(
edge.data?.ship_size_type === ShipSizeStatus.small ? ShipSizeStatus.normal : ShipSizeStatus.small,
edge.data?.ship_size_type === ShipSizeStatus.small ? ShipSizeStatus.large : ShipSizeStatus.small,
),
},
]
: []),
...(isWormhole
? [
{
label: `Save mass`,
className: clsx({
@@ -75,19 +74,40 @@ export const ContextMenuConnection: React.FC<ContextMenuConnectionProps> = ({
icon: PrimeIcons.LOCK,
command: () => onToggleMassSave(!edge.data?.locked),
},
]
: []),
...(isWormhole && !isFrigateSize
? [
...(!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: `Mass status`,
icon: PrimeIcons.CHART_PIE,
items: MASS_STATE_NAMES_ORDER.map(x => ({
label: MASS_STATE_NAMES[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),
})),
},
]
@@ -98,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

@@ -97,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) => {

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

@@ -13,11 +13,11 @@
stroke-width: 2px;
&.MassVerge:not(&.Frigate) {
stroke: #af2900;
stroke: #af0000;
}
&.MassHalf:not(&.Frigate) {
stroke: #a85f00;
stroke: #ffd700;
}
&.Frigate {

View File

@@ -8,6 +8,7 @@ import { ConnectionType, MassState, ShipSizeStatus, SolarSystemConnection, TimeS
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,6 +31,14 @@ 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]));
@@ -137,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

@@ -6,7 +6,7 @@ $pastel-green: #88b04b;
$pastel-yellow: #ffdd59;
$dark-bg: #2d2d2d;
$text-color: #ffffff;
$tooltip-bg: #202020; // Темный фон для подсказок
$tooltip-bg: #202020; // Dark background for tooltips
.RootCustomNode {
display: flex;
@@ -136,7 +136,7 @@ $tooltip-bg: #202020; // Темный фон для подсказок
.Bookmarks {
position: absolute;
width: 100%;
z-index: 0;
z-index: 1;
display: flex;
left: 4px;
@@ -182,6 +182,42 @@ $tooltip-bg: #202020; // Темный фон для подсказок
}
}
.Unsplashed {
position: absolute;
width: calc(50% - 4px);
z-index: -1;
display: flex;
flex-wrap: wrap;
gap: 2px;
left: 2px;
&--right {
left: calc(50% + 6px);
}
& > .Signature {
width: 13px;
height: 4px;
position: relative;
top: 3px;
border-radius: 5px;
color: #ffffff;
font-size: 8px;
text-align: center;
padding-top: 2px;
font-weight: bolder;
padding-left: 3px;
padding-right: 3px;
display: block;
background-color: #833ca4;
&:not(:first-child) {
box-shadow: inset 4px -3px 4px rgba(0, 0, 0, 0.3);
}
}
}
.icon {
width: 8px;
height: 8px;
@@ -276,7 +312,6 @@ $tooltip-bg: #202020; // Темный фон для подсказок
position: relative;
top: -1px;
}
}
.Handlers {

View File

@@ -3,6 +3,8 @@ 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 {
EFFECT_BACKGROUND_STYLES,
LABELS_INFO,
@@ -12,8 +14,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 +53,9 @@ 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 {
system_class,
security,
@@ -63,6 +69,8 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
solar_system_name,
} = data.system_static_info;
const signatures = data.system_signatures;
const { locked, name, tag, status, labels, id } = data || {};
const customName = solar_system_name !== name ? name : undefined;
@@ -128,6 +136,22 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
const space = showKSpaceBG ? REGIONS_MAP[region_id] : '';
const regionClass = showKSpaceBG ? SpaceToClass[space] : null;
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 && (
@@ -237,6 +261,22 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
)}
</div>
{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 onMouseDownCapture={dbClick} className={classes.Handlers}>
<Handle
type="source"

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: #ffffff;
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 { ConnectionType, MassState } from '@/hooks/Mapper/types';
import { ConnectionType, MassState, ShipSizeStatus } from '@/hooks/Mapper/types';
export enum SOLAR_SYSTEM_CLASS_IDS {
ccp1 = -1,
@@ -727,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

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

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

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

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

@@ -11,6 +11,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';
@@ -36,6 +37,8 @@ import { COSMIC_SIGNATURE } from '@/hooks/Mapper/components/mapInterface/widgets
import {
SHOW_DESCRIPTION_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;
@@ -53,6 +56,7 @@ interface SystemSignaturesContentProps {
hideLinkedSignatures?: boolean;
selectable?: boolean;
onSelect?: (signature: SystemSignature) => void;
onLazyDeleteChange?: (value: boolean) => void;
}
export const SystemSignaturesContent = ({
systemId,
@@ -60,14 +64,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);
@@ -84,6 +87,16 @@ export const SystemSignaturesContent = ({
const tooltipRef = useRef<WdTooltipHandlers>(null);
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) {
const tableWidth = tableRef.current.offsetWidth;
@@ -132,13 +145,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,
@@ -172,6 +189,7 @@ export const SystemSignaturesContent = ({
await handleUpdateSignatures(
signatures.filter(x => !selectedSignaturesEveIds.includes(x.eve_id)),
false,
true,
);
},
[handleUpdateSignatures, selectable, signatures, selectedSignatures],
@@ -181,16 +199,6 @@ export const SystemSignaturesContent = ({
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
@@ -204,40 +212,51 @@ export const SystemSignaturesContent = ({
[onSelect, selectable],
);
const handlePaste = async () => {
const clipboardContent = await navigator.clipboard.readText();
if (refData.current.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);
}
};
useHotkey(true, ['a'], handleSelectAll);
useHotkey(true, ['v'], handlePaste);
const handleEnterRow = useCallback(
(e: DataTableRowMouseEvent) => {
setHoveredSig(filteredSignatures[e.index]);
tooltipRef.current?.show(e.originalEvent);
},
[filteredSignatures],
);
useHotkey(false, ['Delete'], handleDeleteSelected);
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;
}
@@ -271,19 +290,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]">
@@ -367,7 +373,6 @@ export const SystemSignaturesContent = ({
></Column>
<Column
field="info"
// header="Info"
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
body={renderInfoColumn}
style={{ maxWidth: nameColumnWidth }}
@@ -430,27 +435,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

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

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

@@ -53,15 +53,18 @@ const SYSTEMS_CHECKBOXES_PROPS: CheckboxesList = [
const SIGNATURES_CHECKBOXES_PROPS: CheckboxesList = [
{ prop: UserSettingsRemoteProps.link_signature_on_splash, label: 'Link signature on splash' },
{ prop: InterfaceStoredSettingsProps.isShowUnsplashedSignatures, label: 'Show unsplashed signatures' },
];
const CONNECTIONS_CHECKBOXES_PROPS: CheckboxesList = [
{ prop: UserSettingsRemoteProps.delete_connection_with_sigs, label: 'Delete connections to linked signatures' },
{ prop: InterfaceStoredSettingsProps.isThickConnections, label: 'Thicker connections' },
];
const UI_CHECKBOXES_PROPS: CheckboxesList = [
{ prop: InterfaceStoredSettingsProps.isShowMenu, label: 'Enable compact map menu bar' },
{ prop: InterfaceStoredSettingsProps.isThickConnections, label: 'Thicker connections' },
{ prop: InterfaceStoredSettingsProps.isShowBackgroundPattern, label: 'Show background pattern' },
{ prop: InterfaceStoredSettingsProps.isSoftBackground, label: 'Enable soft background' },
];
export const MapSettings = ({ show, onHide }: MapSettingsProps) => {
@@ -128,7 +131,7 @@ export const MapSettings = ({ show, onHide }: MapSettingsProps) => {
return (
<Dialog
header="Map settings"
header="Map user settings"
visible={show}
draggable={false}
style={{ width: '550px' }}

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

@@ -49,6 +49,13 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
});
}
out = {
...out,
custom_info: JSON.stringify({
k162Type: values.k162Type,
}),
};
if (values.type != null) {
out = { ...out, type: values.type };
}
@@ -117,10 +124,17 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
return;
}
const { linked_system, ...rest } = signatureData;
const { linked_system, custom_info, ...rest } = signatureData;
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,
});
}, [signatureForm, signatureData]);

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,8 @@ export const MapWrapper = () => {
isShowMinimap = STORED_INTERFACE_DEFAULT_VALUES.isShowMinimap,
isShowKSpace,
isThickConnections,
isShowBackgroundPattern,
isSoftBackground,
},
} = useMapRootState();
const { deleteSystems } = useDeleteSystems();
@@ -45,6 +51,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 +128,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 +164,9 @@ export const MapWrapper = () => {
showKSpaceBG={isShowKSpace}
onManualDelete={handleManualDelete}
isThickConnections={isThickConnections}
isShowBackgroundPattern={isShowBackgroundPattern}
isSoftBackground={isSoftBackground}
onAddSystem={onAddSystem}
/>
{openSettings != null && (
@@ -149,6 +181,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,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

@@ -26,6 +26,7 @@ const INITIAL_DATA: MapRootData = {
selectedSystems: [],
selectedConnections: [],
userPermissions: {},
options: {},
};
export enum InterfaceStoredSettingsProps {
@@ -33,6 +34,9 @@ export enum InterfaceStoredSettingsProps {
isShowMinimap = 'isShowMinimap',
isShowKSpace = 'isShowKSpace',
isThickConnections = 'isThickConnections',
isShowUnsplashedSignatures = 'isShowUnsplashedSignatures',
isShowBackgroundPattern = 'isShowBackgroundPattern',
isSoftBackground = 'isSoftBackground',
}
export type InterfaceStoredSettings = {
@@ -40,6 +44,9 @@ export type InterfaceStoredSettings = {
isShowMinimap: boolean;
isShowKSpace: boolean;
isThickConnections: boolean;
isShowUnsplashedSignatures: boolean;
isShowBackgroundPattern: boolean;
isSoftBackground: boolean;
};
export const STORED_INTERFACE_DEFAULT_VALUES: InterfaceStoredSettings = {
@@ -47,6 +54,9 @@ export const STORED_INTERFACE_DEFAULT_VALUES: InterfaceStoredSettings = {
isShowMinimap: true,
isShowKSpace: false,
isThickConnections: false,
isShowUnsplashedSignatures: false,
isShowBackgroundPattern: true,
isSoftBackground: false,
};
export interface MapRootContextProps {

View File

@@ -1,6 +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,11 +2,14 @@ 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: [] });
@@ -56,5 +59,29 @@ export const useCommandsSystems = () => {
update({ systems: out }, true);
}, []);
return { addSystems, removeSystems, updateSystems };
const updateSystemSignatures = useCallback(
async (systemId: string) => {
const { update, systems } = ref.current;
const { signatures } = await outCommand({
type: OutCommand.getSignatures,
data: { system_id: `${systemId}` },
});
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 });
},
[outCommand],
);
return { addSystems, removeSystems, updateSystems, updateSystemSignatures };
};

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

@@ -20,6 +20,7 @@ export const useMapInit = () => {
present_characters,
hubs,
user_permissions,
options,
}: CommandInit) => {
const updateData: Partial<MapRootData> = {};
@@ -60,6 +61,10 @@ export const useMapInit = () => {
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();
@@ -88,7 +89,7 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
break;
case Commands.signaturesUpdated: // USED
// do nothing here
updateSystemSignatures(data as CommandSignaturesUpdated);
break;
case Commands.linkSignatureToSystem: // USED

View File

@@ -14,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 = {

View File

@@ -62,6 +62,7 @@ export type CommandInit = {
user_permissions: UserPermissions;
hubs: string[];
routes: RoutesList;
options: Record<string, string | boolean>;
reset?: boolean;
};
export type CommandAddSystems = SolarSystemRawType[];
@@ -152,6 +153,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

@@ -19,4 +19,5 @@ export type MapUnionTypes = {
kills: Record<number, number>;
connections: SolarSystemConnection[];
userPermissions: Partial<UserPermissions>;
options: Record<string, string | boolean>;
};

View File

@@ -21,9 +21,11 @@ export type SystemSignature = {
eve_id: string;
kind: string;
name: string;
custom_info?: string;
description?: string;
group: SignatureGroup;
type: string;
k162Type?: string;
linked_system?: SolarSystemStaticInfoRaw;
inserted_at?: string;
updated_at?: string;

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',
@@ -116,4 +118,15 @@ export type SolarSystemRawType = {
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

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

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

@@ -12,32 +12,37 @@ defmodule WandererApp.Api.MapCharacterSettings do
code_interface do
define(:create, action: :create)
define(:destroy, action: :destroy)
define(:read_by_map,
action: :read_by_map
)
define(:tracked_by_map_filtered,
action: :tracked_by_map_filtered
)
define(:tracked_by_map_all,
action: :tracked_by_map_all
)
define(:read_by_map, action: :read_by_map)
define(:by_map_filtered, action: :by_map_filtered)
define(:tracked_by_map_filtered, action: :tracked_by_map_filtered)
define(:tracked_by_map_all, action: :tracked_by_map_all)
define(:track, action: :track)
define(:untrack, action: :untrack)
define(:follow, action: :follow)
define(:unfollow, action: :unfollow)
end
actions do
default_accept [
:map_id,
:character_id,
:tracked
:tracked,
:followed
]
defaults [:create, :read, :update, :destroy]
read :by_map_filtered do
argument(:map_id, :string, allow_nil?: false)
argument(:character_ids, {:array, :uuid}, allow_nil?: false)
filter(expr(map_id == ^arg(:map_id) and character_id in ^arg(:character_ids)))
end
read :tracked_by_map_filtered do
argument(:map_id, :string, allow_nil?: false)
argument(:character_ids, {:array, :uuid}, allow_nil?: false)
@@ -64,6 +69,14 @@ defmodule WandererApp.Api.MapCharacterSettings do
update :untrack do
change(set_attribute(:tracked, false))
end
update :follow do
change(set_attribute(:followed, true))
end
update :unfollow do
change(set_attribute(:followed, false))
end
end
attributes do
@@ -74,6 +87,11 @@ defmodule WandererApp.Api.MapCharacterSettings do
allow_nil? true
end
attribute :followed, :boolean do
default false
allow_nil? true
end
create_timestamp(:inserted_at)
update_timestamp(:updated_at)
end

View File

@@ -128,11 +128,13 @@ defmodule WandererApp.Api.MapConnection do
allow_nil?(true)
end
# where 0 - Frigate
# where 1 - Medium and Large
# where 2 - Capital
# where 0 - Frigate (small
# where 1 - Medium
# where 2 - Large
# where 3 - Freight
# where 4 - Capital
attribute :ship_size_type, :integer do
default(1)
default(2)
allow_nil?(true)
end

View File

@@ -24,6 +24,7 @@ defmodule WandererApp.Api.MapSystemSignature do
)
define(:by_system_id, action: :by_system_id, args: [:system_id])
define(:by_linked_system_id, action: :by_linked_system_id, args: [:linked_system_id])
end
actions do
@@ -55,7 +56,8 @@ defmodule WandererApp.Api.MapSystemSignature do
:description,
:kind,
:group,
:type
:type,
:custom_info
]
argument :system_id, :uuid, allow_nil?: false
@@ -73,6 +75,7 @@ defmodule WandererApp.Api.MapSystemSignature do
:kind,
:group,
:type,
:custom_info,
:updated
]
@@ -97,6 +100,12 @@ defmodule WandererApp.Api.MapSystemSignature do
filter(expr(system_id == ^arg(:system_id)))
end
read :by_linked_system_id do
argument(:linked_system_id, :integer, allow_nil?: false)
filter(expr(linked_system_id == ^arg(:linked_system_id)))
end
end
attributes do
@@ -129,6 +138,10 @@ defmodule WandererApp.Api.MapSystemSignature do
attribute :kind, :string
attribute :group, :string
attribute :custom_info, :string do
allow_nil? true
end
attribute :updated, :integer
create_timestamp(:inserted_at)

View File

@@ -446,8 +446,13 @@ defmodule WandererApp.Character.Tracker do
|> Map.merge(%{alliance_id: alliance_id, corporation_id: corporation_id})
|> maybe_update_alliance()
_error ->
Logger.warning("Failed to get corporation info for #{corporation_id}")
error ->
Logger.warning(
"Failed to get corporation info for character #{character_id}: #{inspect(error)}",
character_id: character_id,
corporation_id: corporation_id
)
state
end
end
@@ -484,7 +489,7 @@ defmodule WandererApp.Character.Tracker do
defp maybe_update_location(
%{
character_id: character_id
character_id: character_id,
} =
state,
location

View File

@@ -49,7 +49,7 @@ defmodule WandererApp.EveDataService do
end)
end)
Task.await_many(tasks, :timer.minutes(1))
Task.await_many(tasks, :timer.minutes(30))
end
def download_file(file_name) do

View File

@@ -14,6 +14,7 @@ defmodule WandererApp.Map do
hubs: [],
connections: Map.new(),
acls: [],
options: Map.new(),
characters_limit: nil,
hubs_limit: nil
@@ -69,6 +70,9 @@ defmodule WandererApp.Map do
def get_characters_limit(map_id),
do: {:ok, map_id |> get_map!() |> Map.get(:characters_limit, 100)}
def get_options(map_id),
do: {:ok, map_id |> get_map!() |> Map.get(:options, Map.new())}
@doc """
Returns a full list of characters in the map
"""
@@ -187,7 +191,7 @@ defmodule WandererApp.Map do
case characters |> Enum.member?(character_id) do
true ->
map_id
|> update_map(%{characters: Enum.reject(characters, fn id -> id == character_id end)})
|> update_map(%{characters: characters |> Enum.reject(fn id -> id == character_id end)})
:ok
@@ -251,6 +255,13 @@ defmodule WandererApp.Map do
map
end
def update_options!(%{map_id: map_id} = map, options) do
map_id
|> update_map(%{options: options})
map
end
def add_systems!(map, []), do: map
def add_systems!(%{map_id: map_id} = map, [system | rest]) do

View File

@@ -277,15 +277,11 @@ defmodule WandererApp.Map.Server.Impl do
}
end
def handle_event({:options_updated, options}, state),
do: %{
state
| map_opts: [
layout: options |> Map.get("layout", "left_to_right"),
store_custom_labels:
options |> Map.get("store_custom_labels", "false") |> String.to_existing_atom()
]
}
def handle_event({:options_updated, options}, %{map: map} = state) do
map |> WandererApp.Map.update_options!(options)
%{state | map_opts: map_options(options)}
end
def handle_event({ref, _result}, %{map_id: _map_id} = state) do
Process.demonitor(ref, [:flush])
@@ -319,6 +315,16 @@ defmodule WandererApp.Map.Server.Impl do
map |> Map.put_new(attribute, get_in(update, [Access.key(attribute)]))
end)}
defp map_options(options) do
[
layout: options |> Map.get("layout", "left_to_right"),
store_custom_labels:
options |> Map.get("store_custom_labels", "false") |> String.to_existing_atom(),
restrict_offline_showing:
options |> Map.get("restrict_offline_showing", "false") |> String.to_existing_atom()
]
end
defp save_map_state(%{map_id: map_id} = _state) do
systems_last_activity =
map_id
@@ -389,22 +395,17 @@ defmodule WandererApp.Map.Server.Impl do
systems,
connections
) do
{:ok, options} = WandererApp.MapRepo.options_to_form_data(initial_map)
map =
initial_map
|> WandererApp.Map.new()
|> WandererApp.Map.update_options!(options)
|> WandererApp.Map.update_subscription_settings!(subscription_settings)
|> WandererApp.Map.add_systems!(systems)
|> WandererApp.Map.add_connections!(connections)
|> WandererApp.Map.add_characters!(characters)
{:ok, map_options} = WandererApp.MapRepo.options_to_form_data(initial_map)
map_opts = [
layout: map_options |> Map.get("layout", "left_to_right"),
store_custom_labels:
map_options |> Map.get("store_custom_labels", "false") |> String.to_existing_atom()
]
character_ids =
map_id
|> WandererApp.Map.get_map!()
@@ -412,7 +413,7 @@ defmodule WandererApp.Map.Server.Impl do
WandererApp.Cache.insert("map_#{map_id}:invalidate_character_ids", character_ids)
%{state | map: map, map_opts: map_opts}
%{state | map: map, map_opts: map_options(options)}
end
def maybe_import_systems(state, %{"systems" => systems} = _settings, user_id, character_id) do

View File

@@ -15,13 +15,12 @@ defmodule WandererApp.Map.Server.CharactersImpl do
WandererApp.MapCharacterSettingsRepo.create(%{
character_id: character_id,
map_id: map_id,
tracked: track_character
tracked: track_character,
followed: false
}),
{:ok, character} <- WandererApp.Character.get_character(character_id) do
Impl.broadcast!(map_id, :character_added, character)
:telemetry.execute([:wanderer_app, :map, :character, :added], %{count: 1})
:ok
else
_error ->
@@ -89,6 +88,11 @@ defmodule WandererApp.Map.Server.CharactersImpl do
[]
)
acls =
map_id
|> WandererApp.Map.get_map!()
|> Map.get(:acls, [])
invalidate_character_ids
|> Task.async_stream(
fn character_id ->
@@ -96,11 +100,6 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|> WandererApp.Character.get_character()
|> case do
{:ok, character} ->
acls =
map_id
|> WandererApp.Map.get_map!()
|> Map.get(:acls, [])
[character_permissions] =
WandererApp.Permissions.check_characters_access([character], acls)
@@ -157,12 +156,12 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|> untrack_characters(character_ids)
map_id
|> WandererApp.MapCharacterSettingsRepo.get_tracked_by_map_filtered(character_ids)
|> WandererApp.MapCharacterSettingsRepo.get_by_map_filtered(character_ids)
|> case do
{:ok, settings} ->
settings
|> Enum.each(fn s ->
WandererApp.MapCharacterSettingsRepo.untrack(s)
WandererApp.MapCharacterSettingsRepo.destroy!(s)
remove_character(map_id, s.character_id)
end)
@@ -382,7 +381,6 @@ defmodule WandererApp.Map.Server.CharactersImpl do
{:character_location, %{solar_system_id: solar_system_id},
%{solar_system_id: old_solar_system_id}}
]
_ ->
[:skip]
end

View File

@@ -333,7 +333,7 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
Impl.broadcast!(map_id, :maybe_select_system, %{
character_id: character_id,
solar_system_id: location.solar_system_id
solar_system_id: location.solar_system_id,
})
Impl.broadcast!(map_id, :add_connection, connection)
@@ -346,9 +346,20 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
:ok
{:error, error} ->
Logger.debug(fn -> "Failed to add connection: #{inspect(error, pretty: true)}" end)
:ok
{:error, :already_exists} ->
# Still broadcast location change in case of followed character
Impl.broadcast!(map_id, :maybe_select_system, %{
character_id: character_id,
solar_system_id: location.solar_system_id
})
:ok
{:error, error} ->
Logger.debug(fn -> "Failed to add connection: #{inspect(error, pretty: true)}" end)
:ok
end
end
@@ -356,35 +367,33 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
def can_add_location(_scope, nil), do: false
def can_add_location(:all, _solar_system_id), do: true
def can_add_location(:none, _solar_system_id), do: false
def can_add_location(scope, solar_system_id) do
system_static_info =
case WandererApp.CachedInfo.get_system_static_info(solar_system_id) do
{:ok, system_static_info} when not is_nil(system_static_info) ->
system_static_info
_ ->
%{system_class: nil}
end
{:ok, system_static_info} = get_system_static_info(solar_system_id)
case scope do
:wormholes ->
not (@prohibited_system_classes |> Enum.member?(system_static_info.system_class)) and
not is_prohibited_system_class?(system_static_info.system_class) and
not (@prohibited_systems |> Enum.member?(solar_system_id)) and
@wh_space |> Enum.member?(system_static_info.system_class)
:stargates ->
not (@prohibited_system_classes |> Enum.member?(system_static_info.system_class)) and
not is_prohibited_system_class?(system_static_info.system_class) and
@known_space |> Enum.member?(system_static_info.system_class)
:all ->
not is_prohibited_system_class?(system_static_info.system_class)
_ ->
false
end
end
def is_prohibited_system_class?(system_class) do
@prohibited_system_classes |> Enum.member?(system_class)
end
def is_connection_exist(map_id, from_solar_system_id, to_solar_system_id),
do:
not is_nil(
@@ -413,24 +422,21 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
current_system_id: to_solar_system_id
})
system_static_info =
case WandererApp.CachedInfo.get_system_static_info(to_solar_system_id) do
{:ok, system_static_info} when not is_nil(system_static_info) ->
system_static_info
_ ->
%{system_class: nil}
end
{:ok, from_system_static_info} = get_system_static_info(from_solar_system_id)
{:ok, to_system_static_info} = get_system_static_info(to_solar_system_id)
case scope do
:wormholes ->
not (@prohibited_system_classes |> Enum.member?(system_static_info.system_class)) and
not is_prohibited_system_class?(from_system_static_info.system_class) and
not is_prohibited_system_class?(to_system_static_info.system_class) and
not (@prohibited_systems |> Enum.member?(from_solar_system_id)) and
not (@prohibited_systems |> Enum.member?(to_solar_system_id)) and
known_jumps |> Enum.empty?() and to_solar_system_id != @jita and
from_solar_system_id != @jita
:stargates ->
not (@prohibited_system_classes |> Enum.member?(system_static_info.system_class)) and
not is_prohibited_system_class?(from_system_static_info.system_class) and
not is_prohibited_system_class?(to_system_static_info.system_class) and
not (known_jumps |> Enum.empty?())
end
end
@@ -446,6 +452,16 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
end
end
defp get_system_static_info(solar_system_id) do
case WandererApp.CachedInfo.get_system_static_info(solar_system_id) do
{:ok, system_static_info} when not is_nil(system_static_info) ->
{:ok, system_static_info}
_ ->
{:ok, %{system_class: nil}}
end
end
defp maybe_remove_connection(map_id, location, old_location)
when not is_nil(location) and not is_nil(old_location) and
location.solar_system_id != old_location.solar_system_id do

View File

@@ -224,6 +224,19 @@ defmodule WandererApp.Map.Server.SystemsImpl do
WandererApp.MapConnectionRepo.destroy(map_id, connection)
end)
removed_ids
|> Enum.map(fn solar_system_id ->
WandererApp.Api.MapSystemSignature.by_linked_system_id!(solar_system_id)
end)
|> List.flatten()
|> Enum.uniq_by(& &1.system_id)
|> Enum.each(fn s ->
{:ok, %{system: system}} = s |> Ash.load([:system])
Ash.destroy!(s)
Impl.broadcast!(map_id, :signatures_updated, system.solar_system_id)
end)
@ddrt.delete(removed_ids, rtree_name)
Impl.broadcast!(map_id, :remove_connections, connections_to_remove)
@@ -473,7 +486,7 @@ defmodule WandererApp.Map.Server.SystemsImpl do
state
else
error ->
Logger.error("Fail ed to update system: #{inspect(error, pretty: true)}")
Logger.error("Failed to update system: #{inspect(error, pretty: true)}")
state
end
end

View File

@@ -84,20 +84,22 @@ defmodule WandererApp.Maps do
id: id,
eve_id: eve_id,
corporation_ticker: corporation_ticker,
tracked: false
tracked: false,
followed: false
}
def map_character(
%{name: name, id: id, eve_id: eve_id, corporation_ticker: corporation_ticker} =
_character,
%{tracked: tracked} = _character_settings
%{tracked: tracked, followed: followed} = _character_settings
),
do: %{
name: name,
id: id,
eve_id: eve_id,
corporation_ticker: corporation_ticker,
tracked: tracked
tracked: tracked,
followed: followed
}
@decorate cacheable(

View File

@@ -11,15 +11,44 @@ defmodule WandererApp.MapCharacterSettingsRepo do
character_ids: character_ids
})
def get_by_map_filtered(map_id, character_ids),
do:
WandererApp.Api.MapCharacterSettings.by_map_filtered(%{
map_id: map_id,
character_ids: character_ids
})
def get_all_by_map(map_id),
do: WandererApp.Api.MapCharacterSettings.read_by_map(%{map_id: map_id})
def get_tracked_by_map_all(map_id),
do: WandererApp.Api.MapCharacterSettings.tracked_by_map_all(%{map_id: map_id})
def get_by_map(map_id, character_id) do
case get_by_map_filtered(map_id, [character_id]) do
{:ok, [setting | _]} ->
{:ok, setting}
{:ok, []} ->
{:error, :not_found}
{:error, reason} ->
{:error, reason}
end
end
def track(settings), do: settings |> WandererApp.Api.MapCharacterSettings.track()
def untrack(settings), do: settings |> WandererApp.Api.MapCharacterSettings.untrack()
def track!(settings), do: settings |> WandererApp.Api.MapCharacterSettings.track!()
def untrack!(settings), do: settings |> WandererApp.Api.MapCharacterSettings.untrack!()
def follow(settings), do: settings |> WandererApp.Api.MapCharacterSettings.follow()
def unfollow(settings), do: settings |> WandererApp.Api.MapCharacterSettings.unfollow()
def follow!(settings), do: settings |> WandererApp.Api.MapCharacterSettings.follow!()
def unfollow!(settings), do: settings |> WandererApp.Api.MapCharacterSettings.unfollow!()
def destroy!(settings), do: settings |> WandererApp.Api.MapCharacterSettings.destroy!()
end

View File

@@ -1,7 +1,11 @@
defmodule WandererApp.MapRepo do
use WandererApp, :repository
@default_map_options %{"layout" => "left_to_right", "store_custom_labels" => "false"}
@default_map_options %{
"layout" => "left_to_right",
"store_custom_labels" => "false",
"restrict_offline_showing" => "false"
}
def get(map_id, relationships \\ []) do
map_id

View File

@@ -0,0 +1,47 @@
defmodule WandererAppWeb.CharacterActivity do
use WandererAppWeb, :live_component
use LiveViewEvents
@impl true
def mount(socket) do
{:ok, socket}
end
@impl true
def update(
assigns,
socket
) do
{:ok,
socket
|> handle_info_or_assign(assigns)}
end
def render(assigns) do
~H"""
<div id={@id}>
<.table class="!max-h-[80vh] !overflow-y-auto" id="activity-tbl" rows={@activity}>
<:col :let={row} label="Character">
<.character_item character={row.character} />
</:col>
<:col :let={row} label="Passages">
<%= row.count %>
</:col>
</.table>
</div>
"""
end
def character_item(assigns) do
~H"""
<div class="flex items-center gap-3">
<div class="avatar">
<div class="rounded-md w-12 h-12">
<img src={member_icon_url(@character.eve_id)} alt={@character.name} />
</div>
</div>
<%= @character.name %>
</div>
"""
end
end

View File

@@ -143,15 +143,10 @@ defmodule WandererAppWeb.CoreComponents do
def server_status(assigns) do
~H"""
<div
class="flex flex-col p-4 items-center absolute bottom-16 left-1 gap-2 tooltip tooltip-right"
class="flex flex-col p-4 items-center absolute bottom-16 left-2 gap-2 tooltip tooltip-right"
data-tip="server: Tranquility"
>
<div
:if={@online}
class="absolute block w-4 h-4 rounded-full shadow-inner bg-green-500 animate-ping"
>
</div>
<div class={"block w-4 h-4 rounded-full shadow-inner #{if @online, do: " bg-green-500", else: "bg-red-500"}"}>
<div class={"block w-2 h-2 rounded-full shadow-inner #{if @online, do: " bg-green-500", else: "bg-red-500"}"}>
</div>
</div>
"""
@@ -336,6 +331,7 @@ defmodule WandererAppWeb.CoreComponents do
"""
attr(:id, :any, default: nil)
attr(:class, :string, default: nil)
attr(:wrapper_class, :string, default: nil)
attr(:name, :any)
attr(:label, :string, default: nil)
attr(:prefix, :string, default: nil)
@@ -381,21 +377,63 @@ defmodule WandererAppWeb.CoreComponents do
end)
~H"""
<div phx-feedback-for={@name} class="form-control mt-8">
<label class="label cursor-pointer gap-2">
<span class="label-text"><%= @label %></span>
<input type="hidden" name={@name} value="false" />
<input
type="checkbox"
id={@id}
name={@name}
value="true"
checked={@checked}
class="checkbox"
{@rest}
/>
<div phx-feedback-for={@name} class="form-control mt-2">
<label class="inputContainer" for={@name}>
<span><%= @label %></span>
<div></div>
<div class="smallInputSwitch">
<div class="flex items-center">
<div
class={[
"checkboxRoot sizeM p-checkbox p-component",
classes("p-highlight": @checked)
]}
data-p-highlight={@checked}
data-p-disabled="false"
data-pc-name="checkbox"
data-pc-section="root"
>
<input type="hidden" name={@name} value="false" disabled={@rest[:disabled]} />
<input
id={@id}
name={@name}
type="checkbox"
class="p-checkbox-input"
aria-invalid="false"
data-pc-section="input"
value="true"
checked={@checked}
{@rest}
/>
<div
class="p-checkbox-box"
data-p-highlight={@checked}
data-p-disabled="false"
data-pc-section="box"
>
<svg
:if={@checked}
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="p-icon p-checkbox-icon"
aria-hidden="true"
data-pc-section="icon"
>
<path
d="M4.86199 11.5948C4.78717 11.5923 4.71366 11.5745 4.64596 11.5426C4.57826 11.5107 4.51779 11.4652 4.46827 11.4091L0.753985 7.69483C0.683167 7.64891 0.623706 7.58751 0.580092 7.51525C0.536478 7.44299 0.509851 7.36177 0.502221 7.27771C0.49459 7.19366 0.506156 7.10897 0.536046 7.03004C0.565935 6.95111 0.613367 6.88 0.674759 6.82208C0.736151 6.76416 0.8099 6.72095 0.890436 6.69571C0.970973 6.67046 1.05619 6.66385 1.13966 6.67635C1.22313 6.68886 1.30266 6.72017 1.37226 6.76792C1.44186 6.81567 1.4997 6.8786 1.54141 6.95197L4.86199 10.2503L12.6397 2.49483C12.7444 2.42694 12.8689 2.39617 12.9932 2.40745C13.1174 2.41873 13.2343 2.47141 13.3251 2.55705C13.4159 2.64268 13.4753 2.75632 13.4938 2.87973C13.5123 3.00315 13.4888 3.1292 13.4271 3.23768L5.2557 11.4091C5.20618 11.4652 5.14571 11.5107 5.07801 11.5426C5.01031 11.5745 4.9368 11.5923 4.86199 11.5948Z"
fill="currentColor"
>
</path>
</svg>
</div>
</div>
<label for={@name} class="select-none ml-1.5"></label>
</div>
</div>
</label>
<.error :for={msg <- @errors}><%= msg %></.error>
</div>
"""
end
@@ -403,24 +441,28 @@ defmodule WandererAppWeb.CoreComponents do
def input(%{type: "range"} = assigns) do
~H"""
<div phx-feedback-for={@name}>
<label class="form-control w-full">
<div class="form-control w-full">
<.label for={@id}>
<span class="label-text"><%= @label %></span>
<span class="label-value"><%= @value %></span>
<span><%= @label %></span>
<div></div>
<%= @value %>
</.label>
<input
type="range"
id={@id}
name={@name}
value={@value}
class={[
"p-component w-full",
@class,
@errors != [] && "border-rose-400 focus:border-rose-400"
]}
{@rest}
/>
</label>
<div>
<input
type="range"
id={@id}
name={@name}
value={@value}
class={[
"p-component w-full",
@class,
@errors != [] && "border-rose-400 focus:border-rose-400"
]}
{@rest}
/>
</div>
</div>
<.error :for={msg <- @errors}><%= msg %></.error>
</div>
"""
@@ -428,13 +470,20 @@ defmodule WandererAppWeb.CoreComponents do
def input(%{type: "select"} = assigns) do
~H"""
<div phx-feedback-for={@name}>
<div
phx-feedback-for={@name}
class={[
"inputContainer",
@wrapper_class
]}
>
<.label :if={@label} for={@id}><%= @label %></.label>
<div :if={@label}></div>
<select
id={@id}
name={@name}
class={[
"w-full",
"p-component",
@class
]}
multiple={@multiple}
@@ -503,9 +552,9 @@ defmodule WandererAppWeb.CoreComponents do
def label(assigns) do
~H"""
<div for={@for} class="label">
<label for={@for} class="inputContainer">
<%= render_slot(@inner_block) %>
</div>
</label>
"""
end

View File

@@ -9,7 +9,7 @@
>
<%= @inner_content %>
</main>
<aside class="h-full w-14 left-0 absolute bg-gray-400 bg-opacity-5 text-gray-200 shadow-lg border-r border-gray-900 bg-opacity-70 bg-neutral-900">
<aside class="h-full w-14 left-0 absolute bg-gray-400 bg-opacity-5 text-gray-200 shadow-lg border-r border-stone-800 bg-opacity-70 bg-neutral-900">
<.sidebar_nav_links
active_tab={@active_tab}
show_admin={@show_admin}

View File

@@ -32,14 +32,22 @@
/>
<script
crossorigin="anonymous"
src="https://unpkg.com/react@18/umd/react.production.min.js"
integrity={integrity_hash("https://unpkg.com/react@18/umd/react.production.min.js")}
src="https://cdn.jsdelivr.net/npm/react@18/umd/react.production.min.js"
integrity={
integrity_hash(
"https://cdn.jsdelivr.net/npm/react-dom@16/umd/react-dom.development.js https://unpkg.com/react@18/umd/react.production.min.js"
)
}
>
</script>
<script
crossorigin="anonymous"
src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"
integrity={integrity_hash("https://unpkg.com/react-dom@18/umd/react-dom.production.min.js")}
src="https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.production.min.js"
integrity={
integrity_hash(
"https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.production.min.js"
)
}
>
</script>

View File

@@ -48,7 +48,7 @@ defmodule WandererAppWeb.MapPicker do
:if={maps}
type="select"
field={f[:map_slug]}
class="select h-8 min-h-[0px] !pt-1 !pb-1 text-sm bg-neutral-900"
class="select h-8 min-h-[10px] !pt-1 !pb-1 text-sm bg-neutral-900"
placeholder="Select a map..."
options={Enum.map(@maps.result, fn map -> {map.label, map.value} end)}
/>

View File

@@ -8,7 +8,8 @@ defmodule WandererAppWeb.UserActivity do
end
@impl true
def update(assigns,
def update(
assigns,
socket
) do
{:ok,
@@ -116,7 +117,6 @@ defmodule WandererAppWeb.UserActivity do
<h6 class="text-base leading-[150%] font-semibold dark:text-white">
<%= _get_event_data(@event_type, Jason.decode!(@event_data) |> Map.drop(["character_id"])) %>
</h6>
</div>
</div>
"""
@@ -125,7 +125,6 @@ defmodule WandererAppWeb.UserActivity do
@impl true
def handle_event("undo", %{"event-data" => event_data} = _params, socket) do
# notify_to(socket.assigns.notify_to, socket.assigns.event_name, map_slug)
IO.inspect(event_data)
{:noreply, socket}
end

View File

@@ -93,13 +93,9 @@ defmodule WandererAppWeb.AuthController do
|> redirect(to: ~p"/")
end
def maybe_update_character_user_id(character, user_id) do
case character.user_id do
nil ->
WandererApp.Api.Character.assign_user!(character, %{user_id: user_id})
_ ->
Logger.debug("character already has user_id")
end
def maybe_update_character_user_id(character, user_id) when not is_nil(user_id) do
WandererApp.Api.Character.assign_user!(character, %{user_id: user_id})
end
def maybe_update_character_user_id(_character, _user_id), do: :ok
end

View File

@@ -136,8 +136,10 @@
<.input
type="select"
field={f[:owner_id]}
class="p-dropdown p-component p-inputwrapper mt-8"
placeholder="Select a map owner"
class="select h-8 min-h-[10px] !pt-1 !pb-1 text-sm bg-neutral-900"
wrapper_class="mt-2"
label="Owner"
placeholder="Select an owner"
options={Enum.map(@characters, fn character -> {character.label, character.id} end)}
/>
<div class="modal-action">

View File

@@ -39,7 +39,8 @@ defmodule WandererAppWeb.AclMember do
<.input
type="select"
field={f[:role]}
class="select h-8 min-h-[0px] !pt-1 !pb-1 text-sm bg-neutral-900 w-[70px]"
class="select h-8 min-h-[0px] !pt-1 !pb-1 text-sm bg-neutral-900"
wrapper_class="w-[60px] mr-16"
placeholder="Select a role..."
options={Enum.map(@roles, fn role -> {role.label, role.value} end)}
/>

View File

@@ -28,6 +28,7 @@ defmodule WandererAppWeb.CharactersLive do
{:ok,
socket
|> assign(
show_characters_add_alert: true,
mode: :blocks,
wallet_tracking_enabled?: WandererApp.Env.wallet_tracking_enabled?(),
characters: characters |> Enum.sort_by(& &1.name, :asc) |> Enum.map(&map_ui_character/1),
@@ -45,6 +46,13 @@ defmodule WandererAppWeb.CharactersLive do
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
end
@impl true
def handle_event("restore_show_characters_add_alert", %{"value" => value}, socket) do
{:noreply,
socket
|> assign(show_characters_add_alert: value)}
end
@impl true
def handle_event("authorize", form, socket) do
track_wallet = form |> Map.get("track_wallet", false)

View File

@@ -1,4 +1,4 @@
<nav class="px-6 flex items-center justify-between w-full h-12 pointer-events-auto border-b border-gray-900 bg-opacity-70 bg-neutral-900">
<nav class="px-6 flex items-center justify-between w-full h-12 pointer-events-auto border-b border-stone-800 bg-opacity-70 bg-neutral-900">
<span className="w-full"></span>
<span className="mr-2"></span>
<div class="flex gap-2">
@@ -29,9 +29,45 @@
id="characters-list"
class="w-full h-full col-span-2 lg:col-span-1 p-4 pl-20 pb-20 overflow-auto"
>
<div
:if={@show_characters_add_alert}
role="alert"
class="alert"
id="characters-add-alert"
phx-hook="ShowCharactersAddAlert"
phx-ignore
data-key="show_characters_add_alert"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="h-6 w-6 shrink-0 stroke-current"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
>
</path>
</svg>
<span>
All added characters will be automatically linked to your user account. You can't have same characters linked to several accounts.
</span>
<div>
<button
class="btn btn-sm"
id="characters-add-alert-hide"
phx-click={JS.toggle_class("hidden", to: "#characters-add-alert")}
>
Hide
</button>
</div>
</div>
<div
:if={@mode == :blocks}
class="gap-4 grid grid-cols-1 lg:grid-cols-5 md:grid-cols-3 sm:grid-cols-2 "
class="gap-4 grid grid-cols-1 lg:grid-cols-5 md:grid-cols-3 sm:grid-cols-2 mt-4"
>
<.link patch={~p"/characters/authorize"}>
<div class="card card-side rounded-none h-full items-center hover:text-white bg-gradient-to-l from-stone-950 to-stone-900 transform transition duration-500">
@@ -51,9 +87,24 @@
<div class="absolute left-0 bottom-0 w-full h-30 bg-opacity-60 bg-gray-900">
<h2 class="absolute w-full flex justify-between px-4 left-0 top-10 text-xs">
Corporation:
<span
:if={
is_nil(
character
|> get(
path(:corporation_name),
nil
)
)
}
class="loading loading-dots loading-xs"
/>
<span>
<%= character
|> get(path(:corporation_name), "-") %>
|> get(
path(:corporation_name),
""
) %>
</span>
</h2>
<h2 class="absolute w-full flex justify-between px-4 left-0 top-16 text-xs">
@@ -65,9 +116,21 @@
</h2>
<h2 class="absolute left-0 bottom-12 px-4 text-xs w-full flex justify-between">
Location:
<span
:if={
is_nil(
character
|> get(
path(:location / :solar_system_info / :solar_system_name, :map),
nil
)
)
}
class="loading loading-dots loading-xs"
/>
<span>
<%= character
|> get(path(:location / :solar_system_info / :solar_system_name, :map), "-") %>
|> get(path(:location / :solar_system_info / :solar_system_name, :map), "") %>
</span>
</h2>
<h2
@@ -126,7 +189,7 @@
</div>
</div>
<div :if={@mode == :table} class="flex flex-col gap-4">
<div :if={@mode == :table} class="flex flex-col gap-4 mt-4">
<.link patch={~p"/characters/authorize"}>
<button
type="button"

View File

@@ -86,7 +86,8 @@ defmodule WandererAppWeb.CharactersTrackingLive do
WandererApp.MapCharacterSettingsRepo.create(%{
character_id: character_id,
map_id: selected_map.id,
tracked: true
tracked: true,
followed: false
})
{:noreply, socket}

View File

@@ -18,15 +18,12 @@ defmodule WandererAppWeb.MapActivityEventHandler do
do: MapCoreEventHandler.handle_server_event(event, socket)
def handle_ui_event("show_activity", _, %{assigns: %{map_id: map_id}} = socket) do
Task.async(fn ->
{:ok, character_activity} = map_id |> get_character_activity()
{:character_activity, character_activity}
end)
{:noreply,
socket
|> assign(:show_activity?, true)}
|> assign(:show_activity?, true)
|> assign_async(:character_activity, fn ->
map_id |> get_character_activity()
end)}
end
def handle_ui_event("hide_activity", _, socket),
@@ -44,6 +41,6 @@ defmodule WandererAppWeb.MapActivityEventHandler do
%{p | character: p.character |> MapEventHandler.map_ui_character_stat()}
end)
{:ok, %{jumps: jumps}}
{:ok, %{character_activity: jumps}}
end
end

View File

@@ -66,35 +66,9 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
def handle_ui_event(
"add_character",
_,
%{
assigns: %{
current_user: current_user,
map_id: map_id,
user_permissions: %{track_character: true}
}
} = socket
) do
{:noreply,
socket
|> assign(show_tracking?: true)
|> assign_async(:characters, fn ->
{:ok, map} =
map_id
|> WandererApp.MapRepo.get([:acls])
{:ok, character_settings} =
case WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id) do
{:ok, settings} -> {:ok, settings}
_ -> {:ok, []}
end
map
|> WandererApp.Maps.load_characters(
character_settings,
current_user.id
)
end)}
end
socket
),
do: {:noreply, socket |> add_character()}
def handle_ui_event(
"add_character",
@@ -137,7 +111,8 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
WandererApp.MapCharacterSettingsRepo.create(%{
character_id: character_id,
map_id: map_id,
tracked: true
tracked: true,
followed: false
})
character = map_character_settings |> Ash.load!(:character) |> Map.get(:character)
@@ -216,12 +191,130 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
)}
end
def handle_ui_event(
"toggle_follow",
%{"character-id" => clicked_char_id},
%{
assigns: %{
map_id: map_id,
current_user: current_user
}
} = socket
) do
{:ok, all_settings} = WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id)
# Find and filter user's characters
{:ok, user_characters} = get_tracked_map_characters(map_id, current_user)
user_char_ids = Enum.map(user_characters, & &1.id)
my_settings =
all_settings
|> Enum.filter(fn s ->
s.character_id in user_char_ids
end)
existing = Enum.find(my_settings, &(&1.character_id == clicked_char_id))
{:ok, target_setting} =
if existing do
{:ok, existing}
else
WandererApp.MapCharacterSettingsRepo.create(%{
character_id: clicked_char_id,
map_id: map_id,
tracked: true,
followed: true
})
end
# If the target_setting is already followed => unfollow it
if target_setting.followed do
{:ok, updated} = WandererApp.MapCharacterSettingsRepo.unfollow(target_setting)
else
# Only unfollow other rows from the current user
for s <- my_settings, s.id != target_setting.id, s.followed == true do
WandererApp.MapCharacterSettingsRepo.unfollow!(s)
end
# Ensure the new followed char is tracked
if not target_setting.tracked do
WandererApp.MapCharacterSettingsRepo.track!(target_setting)
char = target_setting |> Ash.load!(:character) |> Map.get(:character)
:ok = track_characters([char], map_id, true)
:ok = add_characters([char], map_id, true)
end
{:ok, updated} = WandererApp.MapCharacterSettingsRepo.follow(target_setting)
end
# re-fetch or re-map to confirm final results in UI
%{result: characters} = socket.assigns.characters
{:ok, tracked_characters} = get_tracked_map_characters(map_id, current_user)
user_eve_ids = Enum.map(tracked_characters, & &1.eve_id)
{:ok, final_settings} = WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id)
updated_chars =
characters
|> Enum.map(fn c ->
s = Enum.find(final_settings, &(&1.character_id == c.id))
WandererApp.Maps.map_character(c, s)
end)
socket =
socket
|> assign(user_characters: user_eve_ids)
|> assign(has_tracked_characters?: has_tracked_characters?(user_eve_ids))
|> assign_async(:characters, fn ->
{:ok, %{characters: updated_chars}}
end)
|> MapEventHandler.push_map_event("init", %{user_characters: user_eve_ids, reset: false})
{:noreply, socket}
end
def handle_ui_event("hide_tracking", _, socket),
do: {:noreply, socket |> assign(show_tracking?: false)}
def handle_ui_event(event, body, socket),
do: MapCoreEventHandler.handle_ui_event(event, body, socket)
def add_character(
%{
assigns: %{
current_user: current_user,
map_id: map_id,
user_permissions: %{track_character: true}
}
} = socket
),
do:
socket
|> assign(show_tracking?: true)
|> assign_async(:characters, fn ->
{:ok, map} =
map_id
|> WandererApp.MapRepo.get([:acls])
{:ok, character_settings} =
case WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id) do
{:ok, settings} -> {:ok, settings}
_ -> {:ok, []}
end
map
|> WandererApp.Maps.load_characters(
character_settings,
current_user.id
)
end)
def add_character(socket), do: socket
def has_tracked_characters?([]), do: false
def has_tracked_characters?(_user_characters), do: true

View File

@@ -3,7 +3,7 @@ defmodule WandererAppWeb.MapCoreEventHandler do
use Phoenix.Component
require Logger
alias WandererAppWeb.{MapEventHandler, MapCharactersEventHandler}
alias WandererAppWeb.{MapEventHandler, MapCharactersEventHandler, MapSystemsEventHandler}
def handle_server_event(:update_permissions, socket) do
DebounceAndThrottle.Debounce.apply(
@@ -120,7 +120,7 @@ defmodule WandererAppWeb.MapCoreEventHandler do
do: socket
def handle_server_event(event, socket) do
Logger.warning(fn -> "unhandled map core event: #{inspect(event)}" end)
Logger.warning(fn -> "unhandled map core event: #{inspect(event)} #{inspect(socket)} " end)
socket
end
@@ -147,7 +147,7 @@ defmodule WandererAppWeb.MapCoreEventHandler do
options =
WandererApp.Api.MapSolarSystem.find_by_name!(%{name: text})
|> Enum.take(100)
|> Enum.map(&map_system/1)
|> Enum.map(&MapSystemsEventHandler.map_system/1)
send_update(LiveSelect.Component, options: options, id: id)
@@ -162,6 +162,14 @@ defmodule WandererAppWeb.MapCoreEventHandler do
socket
)
def handle_ui_event("toggle_follow_" <> character_id, _, socket),
do:
MapCharactersEventHandler.handle_ui_event(
"toggle_follow",
%{"character-id" => character_id},
socket
)
def handle_ui_event(
"get_user_settings",
_,
@@ -221,8 +229,9 @@ defmodule WandererAppWeb.MapCoreEventHandler do
socket
|> put_flash(
:error,
"You should enable tracking for at least one character."
)}
"You should enable tracking for at least one character!"
)
|> MapCharactersEventHandler.add_character()}
def handle_ui_event(event, body, socket) do
Logger.warning(fn -> "unhandled map ui event: #{event} #{inspect(body)}" end)
@@ -434,28 +443,49 @@ defmodule WandererAppWeb.MapCoreEventHandler do
socket
|> handle_map_start_events(map_id, events)
map_characters = map_id |> WandererApp.Map.list_characters()
{:ok, options} =
map_id
|> WandererApp.Map.get_options()
socket
|> assign(
map_loaded?: true,
map_user_settings: map_user_settings,
user_characters: user_character_eve_ids,
has_tracked_characters?:
MapCharactersEventHandler.has_tracked_characters?(user_character_eve_ids)
)
|> MapEventHandler.push_map_event(
"init",
user_permissions =
initial_data
|> Map.put(
:characters,
map_characters |> Enum.map(&MapCharactersEventHandler.map_ui_character/1)
|> Map.get(:user_permissions)
map_characters =
map_id
|> WandererApp.Map.list_characters()
|> filter_map_characters(user_character_eve_ids, user_permissions, options)
|> Enum.map(&MapCharactersEventHandler.map_ui_character/1)
has_tracked_characters? =
MapCharactersEventHandler.has_tracked_characters?(user_character_eve_ids)
socket =
socket
|> assign(
map_loaded?: true,
map_user_settings: map_user_settings,
user_characters: user_character_eve_ids,
has_tracked_characters?: has_tracked_characters?
)
)
|> push_event("js-exec", %{
to: "#map-loader",
attr: "data-loaded"
})
|> MapEventHandler.push_map_event(
"init",
initial_data
|> Map.put(:characters, map_characters)
)
|> push_event("js-exec", %{
to: "#map-loader",
attr: "data-loaded"
})
case not has_tracked_characters? && user_permissions.track_character do
true ->
socket
|> MapCharactersEventHandler.add_character()
_ ->
socket
end
end
defp handle_map_start_events(socket, map_id, events) do
@@ -478,10 +508,6 @@ defmodule WandererAppWeb.MapCoreEventHandler do
:empty_tracked_characters ->
socket
|> put_flash(
:info,
"You should enable tracking for at least one character to work with map."
)
:map_character_limit ->
socket
@@ -500,13 +526,15 @@ defmodule WandererAppWeb.MapCoreEventHandler do
{:ok, hubs} = map_id |> WandererApp.Map.list_hubs()
{:ok, connections} = map_id |> WandererApp.Map.list_connections()
{:ok, systems} = map_id |> WandererApp.Map.list_systems()
{:ok, options} = map_id |> WandererApp.Map.get_options()
%{
systems:
systems
|> Enum.map(fn system -> MapEventHandler.map_ui_system(system, include_static_data?) end),
hubs: hubs,
connections: connections |> Enum.map(&MapEventHandler.map_ui_connection/1)
connections: connections |> Enum.map(&MapEventHandler.map_ui_connection/1),
options: options
}
end
@@ -525,20 +553,23 @@ defmodule WandererAppWeb.MapCoreEventHandler do
end
end
defp map_system(
defp filter_map_characters(
characters,
user_character_eve_ids,
%{
solar_system_name: solar_system_name,
constellation_name: constellation_name,
region_name: region_name,
solar_system_id: solar_system_id,
class_title: class_title
} = _system
),
do: %{
label: solar_system_name,
value: solar_system_id,
constellation_name: constellation_name,
region_name: region_name,
class_title: class_title
}
manage_map: manage_map_permission
} = _user_permissions,
options
) do
restrict_offline_showing =
options |> Map.get("restrict_offline_showing", "false") |> String.to_existing_atom()
show_offline? = not restrict_offline_showing or manage_map_permission
characters
|> Enum.filter(fn character ->
show_offline? || character.online ||
user_character_eve_ids |> Enum.member?(character.eve_id)
end)
end
end

View File

@@ -312,7 +312,7 @@ defmodule WandererAppWeb.MapSignaturesEventHandler do
def handle_ui_event(event, body, socket),
do: MapCoreEventHandler.handle_ui_event(event, body, socket)
defp get_system_signatures(system_id),
def get_system_signatures(system_id),
do:
system_id
|> WandererApp.Api.MapSystemSignature.by_system_id!()
@@ -328,7 +328,8 @@ defmodule WandererAppWeb.MapSignaturesEventHandler do
:description,
:kind,
:group,
:type
:type,
:custom_info
])
|> Map.put(:linked_system, MapEventHandler.get_system_static_info(linked_system_id))
|> Map.put(:inserted_at, inserted_at |> Calendar.strftime("%Y/%m/%d %H:%M:%S"))
@@ -352,6 +353,7 @@ defmodule WandererAppWeb.MapSignaturesEventHandler do
kind: kind,
group: group,
type: Map.get(signature, "type"),
custom_info: Map.get(signature, "custom_info"),
character_eve_id: character_eve_id
}
end)

View File

@@ -21,32 +21,57 @@ defmodule WandererAppWeb.MapSystemsEventHandler do
|> MapEventHandler.push_map_event("remove_systems", solar_system_ids)
def handle_server_event(
%{
event: :maybe_select_system,
payload: %{
character_id: character_id,
solar_system_id: solar_system_id
}
},
%{assigns: %{current_user: current_user, map_user_settings: map_user_settings}} = socket
) do
is_user_character =
current_user.characters |> Enum.map(& &1.id) |> Enum.member?(character_id)
%{
event: :maybe_select_system,
payload: %{
character_id: character_id,
solar_system_id: solar_system_id
}
},
%{assigns: %{current_user: current_user, map_id: map_id, map_user_settings: map_user_settings}} = socket
) do
is_select_on_spash =
map_user_settings
|> WandererApp.MapUserSettingsRepo.to_form_data!()
|> WandererApp.MapUserSettingsRepo.get_boolean_setting("select_on_spash")
is_user_character =
current_user.characters
|> Enum.map(& &1.id)
|> Enum.member?(character_id)
(is_user_character && is_select_on_spash)
|> case do
true ->
is_select_on_spash =
map_user_settings
|> WandererApp.MapUserSettingsRepo.to_form_data!()
|> WandererApp.MapUserSettingsRepo.get_boolean_setting("select_on_spash")
is_followed =
case WandererApp.MapCharacterSettingsRepo.get_by_map(map_id, character_id) do
{:ok, setting} -> setting.followed == true
_ -> false
end
must_select? = is_user_character && (is_select_on_spash || is_followed)
if not must_select? do
socket
|> MapEventHandler.push_map_event("select_system", solar_system_id)
else
# Check if we already selected this exact system for this char:
last_selected =
WandererApp.Cache.lookup!(
"char:#{character_id}:map:#{map_id}:last_selected_system_id",
nil
)
false ->
socket
end
if last_selected == solar_system_id do
# same system => skip
socket
else
# new system => update cache + push event
WandererApp.Cache.put(
"char:#{character_id}:map:#{map_id}:last_selected_system_id",
solar_system_id
)
socket
|> MapEventHandler.push_map_event("select_system", solar_system_id)
end
end
end
def handle_server_event(%{event: :kills_updated, payload: kills}, socket) do
@@ -65,55 +90,32 @@ defmodule WandererAppWeb.MapSystemsEventHandler do
do: MapCoreEventHandler.handle_server_event(event, socket)
def handle_ui_event(
"add_system",
%{"system_id" => [solar_system_id]} = _event,
"manual_add_system",
%{"solar_system_id" => solar_system_id, "coordinates" => coordinates} = _event,
%{
assigns:
%{
map_id: map_id,
map_slug: map_slug,
current_user: current_user,
tracked_character_ids: tracked_character_ids,
user_permissions: %{add_system: true}
} = assigns
} = socket
)
when is_binary(solar_system_id) and solar_system_id != "" do
coordinates = Map.get(assigns, :coordinates)
assigns: %{
current_user: current_user,
has_tracked_characters?: true,
map_id: map_id,
tracked_character_ids: tracked_character_ids,
user_permissions: %{add_system: true}
}
} =
socket
) do
WandererApp.Map.Server.add_system(
map_id,
%{
solar_system_id: solar_system_id |> String.to_integer(),
solar_system_id: solar_system_id,
coordinates: coordinates
},
current_user.id,
tracked_character_ids |> List.first()
)
{:noreply,
socket
|> push_patch(to: ~p"/#{map_slug}")}
{:noreply, socket}
end
def handle_ui_event(
"manual_add_system",
%{"coordinates" => coordinates} = _event,
%{
assigns: %{
has_tracked_characters?: true,
map_slug: map_slug,
user_permissions: %{add_system: true}
}
} =
socket
),
do:
{:noreply,
socket
|> assign(coordinates: coordinates)
|> push_patch(to: ~p"/#{map_slug}/add-system")}
def handle_ui_event(
"add_hub",
%{"system_id" => solar_system_id} = _event,
@@ -280,6 +282,25 @@ defmodule WandererAppWeb.MapSystemsEventHandler do
{:reply, %{system_static_infos: system_static_infos}, socket}
end
def handle_ui_event(
"search_systems",
%{"text" => text} = _event,
socket
) do
systems =
WandererApp.Api.MapSolarSystem.find_by_name!(%{name: text})
|> Enum.take(100)
|> Enum.map(&map_system/1)
|> Enum.filter(fn system ->
not is_nil(system) && not is_nil(system.system_static_info) &&
not WandererApp.Map.Server.ConnectionsImpl.is_prohibited_system_class?(
system.system_static_info.system_class
)
end)
{:reply, %{systems: systems}, socket}
end
def handle_ui_event(
"delete_systems",
solar_system_ids,
@@ -307,6 +328,27 @@ defmodule WandererAppWeb.MapSystemsEventHandler do
def handle_ui_event(event, body, socket),
do: MapCoreEventHandler.handle_ui_event(event, body, socket)
def map_system(
%{
solar_system_name: solar_system_name,
constellation_name: constellation_name,
region_name: region_name,
solar_system_id: solar_system_id,
class_title: class_title
} = _system
) do
system_static_info = MapEventHandler.get_system_static_info(solar_system_id)
%{
label: solar_system_name,
value: solar_system_id,
constellation_name: constellation_name,
region_name: region_name,
class_title: class_title,
system_static_info: system_static_info
}
end
defp can_update_system?(:locked, %{lock_system: false} = _user_permissions), do: false
defp can_update_system?(_key, _user_permissions), do: true

View File

@@ -15,7 +15,7 @@
/>
</div>
</main>
<nav class="fixed top-0 z-100 px-6 pl-20 flex items-center justify-between w-full h-12 pointer-events-auto border-b border-gray-900 bg-opacity-70 bg-neutral-900">
<nav class="fixed top-0 z-100 px-6 pl-20 flex items-center justify-between w-full h-12 pointer-events-auto border-b border-stone-800 bg-opacity-70 bg-neutral-900">
<span className="w-full font-medium text-sm">
<.link navigate={~p"/#{@map_slug}"} class="text-neutral-100">
<%= @map_name %>

View File

@@ -24,6 +24,7 @@ defmodule WandererAppWeb.MapEventHandler do
@map_characters_ui_events [
"add_character",
"toggle_track",
"toggle_follow",
"hide_tracking"
]
@@ -38,10 +39,10 @@ defmodule WandererAppWeb.MapEventHandler do
@map_system_ui_events [
"add_hub",
"delete_hub",
"add_system",
"delete_systems",
"manual_add_system",
"get_system_static_infos",
"manual_add_system",
"search_systems",
"update_system_position",
"update_system_positions",
"update_system_name",
@@ -234,6 +235,7 @@ defmodule WandererAppWeb.MapEventHandler do
def map_ui_system(
%{
id: system_id,
solar_system_id: solar_system_id,
name: name,
description: description,
@@ -249,12 +251,20 @@ defmodule WandererAppWeb.MapEventHandler do
) do
system_static_info = get_system_static_info(solar_system_id)
system_signatures =
system_id
|> WandererAppWeb.MapSignaturesEventHandler.get_system_signatures()
|> Enum.filter(fn signature ->
is_nil(signature.linked_system) && signature.group == "Wormhole"
end)
%{
id: "#{solar_system_id}",
position: %{x: position_x, y: position_y},
description: description,
name: name,
system_static_info: system_static_info,
system_signatures: system_signatures,
labels: labels,
locked: locked,
status: status,

View File

@@ -6,7 +6,7 @@ defmodule WandererAppWeb.MapLive do
@impl true
def mount(%{"slug" => map_slug} = _params, _session, socket) when is_connected?(socket) do
Process.send_after(self(), %{event: :load_map}, Enum.random(10..500))
Process.send_after(self(), %{event: :load_map}, Enum.random(10..800))
{:ok,
socket
@@ -76,13 +76,15 @@ defmodule WandererAppWeb.MapLive do
def handle_info(:not_all_characters_tracked, %{assigns: %{map_slug: map_slug}} = socket),
do:
{:noreply,
socket
|> put_flash(
:error,
"You should enable tracking for all characters that have access to this map first!"
)
|> push_navigate(to: ~p"/tracking/#{map_slug}")}
WandererAppWeb.MapEventHandler.handle_ui_event(
%{event: "add_character"},
nil,
socket
|> put_flash(
:error,
"You should enable tracking for all characters that have access to this map first!"
)
)
@impl true
def handle_info(info, socket),
@@ -100,13 +102,6 @@ defmodule WandererAppWeb.MapLive do
|> assign(:active_page, :map)
end
defp apply_action(socket, :add_system, _params) do
socket
|> assign(:active_page, :map)
|> assign(:page_title, "Add System")
|> assign(:add_system_form, to_form(%{"system_id" => nil}))
end
def character_item(assigns) do
~H"""
<div class="flex items-center gap-3">

View File

@@ -31,41 +31,6 @@
</.link>
</div>
<.modal
:if={@live_action in [:add_system] && not is_nil(assigns |> Map.get(:map_slug)) && @map_loaded?}
id="add-system-modal"
class="!w-[400px]"
title="Add System"
show
on_cancel={JS.patch(~p"/#{@map_slug}")}
>
<.form :let={f} for={@add_system_form} phx-submit="add_system">
<.live_select
label="Search system"
field={f[:system_id]}
update_min_len={2}
available_option_class="w-full text-sm"
debounce={200}
mode={:tags}
>
<:option :let={option}>
<div class="gap-1 w-full flex flex-align-center p-autocomplete-item text-sm">
<div class="eve-wh-type-color-c1 text-gray-400 w-8"><%= option.class_title %></div>
<div class="text-white w-16"><%= option.label %></div>
<div class="text-gray-600 w-20"><%= option.constellation_name %></div>
<div class="text-gray-600"><%= option.region_name %></div>
</div>
</:option>
</.live_select>
<div class="mt-2 bg-neutral text-neutral-content rounded-md p-1 text-xs w-full">
* Start search system. You should type at least 2 symbols.
</div>
<div class="modal-action mt-0">
<.button class="mt-2" type="submit">Add</.button>
</div>
</.form>
</.modal>
<.modal
:if={assigns |> Map.get(:show_activity?, false)}
id="map-activity-modal"
@@ -74,31 +39,35 @@
show
on_cancel={JS.push("hide_activity")}
>
<.table
:if={not (assigns |> Map.get(:character_activity) |> is_nil())}
class="!max-h-[80vh] !overflow-y-auto"
id="activity-tbl"
rows={@character_activity.jumps}
>
<:col :let={activity} label="Character">
<.character_item character={activity.character} />
</:col>
<:col :let={activity} label="Passages">
<%= activity.count %>
</:col>
</.table>
<.async_result :let={character_activity} assign={@character_activity}>
<:loading>Loading...</:loading>
<:failed :let={reason}><%= reason %></:failed>
<span :if={character_activity}>
<.live_component
module={WandererAppWeb.CharacterActivity}
id="character-activity"
activity={character_activity}
notify_to={self()}
/>
</span>
</.async_result>
</.modal>
<.modal
:if={assigns |> Map.get(:show_tracking?, false)}
id="map-tracking-modal"
title="Characters tracking"
title="Track and Follow Characters"
show
on_cancel={JS.push("hide_tracking")}
>
<.async_result :let={characters} assign={@characters}>
<:loading><span class="loading loading-dots loading-xs" />.</:loading>
<:failed :let={reason}><%= reason %></:failed>
<:loading>
<span class="loading loading-dots loading-xs" />
</:loading>
<:failed :let={reason}>
<%= reason %>
</:failed>
<.table
:if={characters}
@@ -106,8 +75,8 @@
class="h-[400px] !overflow-y-auto"
rows={characters}
>
<:col :let={character} label="Tracked">
<label class="flex items-center gap-3">
<:col :let={character} label="Track">
<label class="flex items-center gap-2 justify-center">
<input
type="checkbox"
class="checkbox"
@@ -116,17 +85,34 @@
id={"character-track-#{character.id}"}
checked={character.tracked}
/>
<div class="flex items-center gap-3">
<.avatar url={member_icon_url(character.eve_id)} label={character.name} />
<div>
<div class="font-bold">
<%= character.name %><span class="ml-1 text-gray-400">[<%= character.corporation_ticker %>]</span>
</div>
<div class="text-sm opacity-50"></div>
</div>
</div>
</label>
</:col>
<:col :let={character} label="Follow">
<label class="flex items-center gap-2 justify-center">
<input
type="radio"
name="followed_character"
class="radio"
phx-click="toggle_follow"
phx-value-character-id={character.id}
checked={Map.get(character, :followed, false)}
/>
</label>
</:col>
<:col :let={character} label="Character">
<div class="flex items-center gap-3">
<.avatar url={member_icon_url(character.eve_id)} label={character.name} />
<div>
<div class="font-bold">
<%= character.name %>
<span class="ml-1 text-gray-400">
[<%= character.corporation_ticker %>]
</span>
</div>
<div class="text-sm opacity-50"></div>
</div>
</div>
</:col>
</.table>
</.async_result>
</.modal>

View File

@@ -197,11 +197,16 @@ defmodule WandererAppWeb.MapsLive do
{:noreply, socket}
end
def handle_event("validate", %{"form" => params} = _params, socket) do
def handle_event("validate", %{"form" => form} = _params, socket) do
form =
AshPhoenix.Form.validate(
socket.assigns.form,
params |> Map.put("acls", params["acls"] || [])
form
|> Map.put("acls", form["acls"] || [])
|> Map.put(
"only_tracked_characters",
(form["only_tracked_characters"] || "false") |> String.to_existing_atom()
)
)
{:noreply, socket |> assign(form: form)}
@@ -280,14 +285,17 @@ defmodule WandererAppWeb.MapsLive do
do:
{:noreply,
socket
|> assign(:amounts, [
%{label: "150M", value: 150_000_000},
%{label: "300M", value: 300_000_000},
%{label: "600M", value: 600_000_000},
%{label: "1.2B", value: 1_200_000_000},
%{label: "2.4B", value: 2_400_000_000},
%{label: "5B", value: 5_000_000_000}
])
|> assign(
:amounts,
[
{"150M", 150_000_000},
{"300M", 300_000_000},
{"600M", 600_000_000},
{"1.2B", 1_200_000_000},
{"2.4B", 2_400_000_000},
{"5B", 5_000_000_000}
]
)
|> assign(is_topping_up?: true)}
@impl true
@@ -588,7 +596,13 @@ defmodule WandererAppWeb.MapsLive do
scope -> scope
end
form = form |> Map.put("scope", scope)
form =
form
|> Map.put("scope", scope)
|> Map.put(
"only_tracked_characters",
(form["only_tracked_characters"] || "false") |> String.to_existing_atom()
)
map
|> WandererApp.Api.Map.update(form)
@@ -654,7 +668,7 @@ defmodule WandererAppWeb.MapsLive do
) do
options =
options_form
|> Map.take(["layout", "store_custom_labels"])
|> Map.take(["layout", "store_custom_labels", "restrict_offline_showing"])
{:ok, updated_map} = WandererApp.MapRepo.update_options(map, options)

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