mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-12-04 14:55:34 +00:00
Compare commits
193 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
802e81b1cd | ||
|
|
41f0834c51 | ||
|
|
880de0b047 | ||
|
|
bbe7fda4e0 | ||
|
|
92a9274dce | ||
|
|
8765d83083 | ||
|
|
a298152bc8 | ||
|
|
2b7abe5774 | ||
|
|
3e9241892e | ||
|
|
6ea79a7960 | ||
|
|
2af562e313 | ||
|
|
40672f6a47 | ||
|
|
6d66ae3f50 | ||
|
|
94c89e0325 | ||
|
|
3670ef40a3 | ||
|
|
16d464fba5 | ||
|
|
0b7e0b9cd0 | ||
|
|
dd5fd114d2 | ||
|
|
6e53879344 | ||
|
|
af2bfd4d59 | ||
|
|
a4a34c8ba7 | ||
|
|
8c609f4fdf | ||
|
|
197f5b583f | ||
|
|
4eb4a03e59 | ||
|
|
3d4e66d438 | ||
|
|
ffbc9f169a | ||
|
|
99650187e9 | ||
|
|
92699317cd | ||
|
|
0e48315803 | ||
|
|
868ec246bd | ||
|
|
0030a688c6 | ||
|
|
3ba8f51a2f | ||
|
|
04576b335c | ||
|
|
ea29aa176f | ||
|
|
9a9b7289ba | ||
|
|
d601790864 | ||
|
|
bf58d3ae93 | ||
|
|
d6c32e2d39 | ||
|
|
bdc4948afb | ||
|
|
331db10029 | ||
|
|
2daf9e34d2 | ||
|
|
558cd9b8b3 | ||
|
|
a0f02d0d2f | ||
|
|
9feb8492aa | ||
|
|
e5aa726899 | ||
|
|
93d1c28ccd | ||
|
|
b5ba9200bc | ||
|
|
699d866670 | ||
|
|
c3071344cb | ||
|
|
9e998dd2b6 | ||
|
|
c9accf6079 | ||
|
|
1b41a51004 | ||
|
|
3338dce900 | ||
|
|
1364779f81 | ||
|
|
b49d3423fc | ||
|
|
cccab2a985 | ||
|
|
1abaa90a7d | ||
|
|
6e1993ca8a | ||
|
|
171c821ac4 | ||
|
|
7ebf9186bf | ||
|
|
57d2f2baef | ||
|
|
0aee13878a | ||
|
|
f93ef0ca76 | ||
|
|
4ec03d8338 | ||
|
|
cb318aa6c6 | ||
|
|
733482cd5c | ||
|
|
3969d1287d | ||
|
|
1aa7854b0d | ||
|
|
7b27d4a1a7 | ||
|
|
24ddb8771f | ||
|
|
7134714245 | ||
|
|
96b320ac26 | ||
|
|
1a27b21efe | ||
|
|
b88e121b30 | ||
|
|
4ba4119c2b | ||
|
|
91d1ca201c | ||
|
|
8bf063a228 | ||
|
|
4f53de39b1 | ||
|
|
8c3804f107 | ||
|
|
1be4ec2b90 | ||
|
|
8f0ed44b11 | ||
|
|
cbadfc4ac4 | ||
|
|
3d88ae4452 | ||
|
|
e57f565812 | ||
|
|
da2605ee03 | ||
|
|
07e2196eb4 | ||
|
|
6d99c54af7 | ||
|
|
2b7901e9a8 | ||
|
|
fb06dd1dbc | ||
|
|
d3b825529e | ||
|
|
ccf9c0db22 | ||
|
|
f8ba36b8be | ||
|
|
5bf9d99b3d | ||
|
|
7cad05342a | ||
|
|
867780e525 | ||
|
|
ff4f9a79c9 | ||
|
|
6699c36fb3 | ||
|
|
abd4556994 | ||
|
|
ccf0d17371 | ||
|
|
898584bbb6 | ||
|
|
6d7a267e39 | ||
|
|
9f656ca3cb | ||
|
|
fede6451e2 | ||
|
|
9797ad380c | ||
|
|
33bc4a4d22 | ||
|
|
6378754c57 | ||
|
|
30fc972d78 | ||
|
|
c022b31c79 | ||
|
|
049b06bb39 | ||
|
|
e17d5213c0 | ||
|
|
dcf681941e | ||
|
|
1cd7d40405 | ||
|
|
fbd80ba2c7 | ||
|
|
88ab85bd04 | ||
|
|
78f98744fd | ||
|
|
9c9634a927 | ||
|
|
be47be626c | ||
|
|
2fbd3d8e19 | ||
|
|
d5c3d4c051 | ||
|
|
fac60f7ddd | ||
|
|
c371478c61 | ||
|
|
5911e29f34 | ||
|
|
99d68dfc0e | ||
|
|
c9b366f3e2 | ||
|
|
4e732e9491 | ||
|
|
dd5b12aa38 | ||
|
|
7bd960fba9 | ||
|
|
c338c33902 | ||
|
|
df6b7ae635 | ||
|
|
a3346f8223 | ||
|
|
196f2c2c3b | ||
|
|
77d549ac1b | ||
|
|
5c3cce66c1 | ||
|
|
cc2f09601e | ||
|
|
14af04cc73 | ||
|
|
37c9e68c21 | ||
|
|
2bd343b2da | ||
|
|
f5d502c5ad | ||
|
|
35cf460ccf | ||
|
|
28c7b90c3f | ||
|
|
4fbdaf42e1 | ||
|
|
90910620d9 | ||
|
|
eb4336fef7 | ||
|
|
69264cc8ec | ||
|
|
ab0cb74ca9 | ||
|
|
42101ab6fd | ||
|
|
8d69c70076 | ||
|
|
beb3077159 | ||
|
|
ecb3ca2b4e | ||
|
|
2ba42e0c25 | ||
|
|
3ef5590e18 | ||
|
|
8412e3867d | ||
|
|
90c40100d1 | ||
|
|
92cb49da90 | ||
|
|
abc09c067f | ||
|
|
edbd1e4bbc | ||
|
|
75edb91825 | ||
|
|
602a61b08d | ||
|
|
d8222d83f0 | ||
|
|
7da5512d45 | ||
|
|
8bf9ae7824 | ||
|
|
f57777e417 | ||
|
|
b3cc3d857a | ||
|
|
bf442d9e70 | ||
|
|
1a556d05ba | ||
|
|
dab301e6d3 | ||
|
|
8ab4b4c788 | ||
|
|
4b29060c96 | ||
|
|
8a5f96a847 | ||
|
|
149fa57075 | ||
|
|
affe184ccd | ||
|
|
1e5e73c4ae | ||
|
|
c76316da03 | ||
|
|
de6205f860 | ||
|
|
f994255091 | ||
|
|
6d4981a3db | ||
|
|
06fef2296f | ||
|
|
999a702291 | ||
|
|
020b9bb2c2 | ||
|
|
7713caab51 | ||
|
|
97a777d729 | ||
|
|
8241d1f08c | ||
|
|
2ac85bbfff | ||
|
|
3f68ae2235 | ||
|
|
0f7b6f75df | ||
|
|
b048e8f5ca | ||
|
|
9783dc45ff | ||
|
|
badbefbade | ||
|
|
b6a265cfad | ||
|
|
9b5ea2f84b | ||
|
|
d8acfa5c05 | ||
|
|
2a5b6924eb | ||
|
|
3b9aee1eb9 |
42
.github/workflows/build.yml
vendored
42
.github/workflows/build.yml
vendored
@@ -18,49 +18,8 @@ permissions:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy-test:
|
|
||||||
name: 🚀 Deploy to test env (fly.io)
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: ${{ github.base_ref == 'main' || (github.ref == 'refs/heads/main' && github.event_name == 'push') }}
|
|
||||||
steps:
|
|
||||||
- name: ⬇️ Checkout repo
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- uses: superfly/flyctl-actions/setup-flyctl@master
|
|
||||||
|
|
||||||
- name: 👀 Read app name
|
|
||||||
uses: SebRollen/toml-action@v1.0.0
|
|
||||||
id: app_name
|
|
||||||
with:
|
|
||||||
file: "fly.toml"
|
|
||||||
field: "app"
|
|
||||||
|
|
||||||
- name: 🚀 Deploy Test
|
|
||||||
run: flyctl deploy --remote-only --wait-timeout=300 --ha=false
|
|
||||||
env:
|
|
||||||
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
|
|
||||||
|
|
||||||
manual-approval:
|
|
||||||
name: Manual Approval
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: deploy-test
|
|
||||||
if: success()
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
issues: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Await Manual Approval
|
|
||||||
uses: trstringer/manual-approval@v1
|
|
||||||
with:
|
|
||||||
secret: ${{ github.TOKEN }}
|
|
||||||
approvers: DmitryPopov
|
|
||||||
minimum-approvals: 1
|
|
||||||
issue-title: "Manual Approval Required for Release"
|
|
||||||
issue-body: "Please approve or deny the deployment."
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: 🛠 Build
|
name: 🛠 Build
|
||||||
needs: manual-approval
|
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
if: ${{ (github.ref == 'refs/heads/main') && github.event_name == 'push' }}
|
if: ${{ (github.ref == 'refs/heads/main') && github.event_name == 'push' }}
|
||||||
permissions:
|
permissions:
|
||||||
@@ -157,7 +116,6 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
platform:
|
platform:
|
||||||
- linux/amd64
|
- linux/amd64
|
||||||
- linux/arm64
|
|
||||||
steps:
|
steps:
|
||||||
- name: Prepare
|
- name: Prepare
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
470
CHANGELOG.md
470
CHANGELOG.md
@@ -2,6 +2,476 @@
|
|||||||
|
|
||||||
<!-- changelog -->
|
<!-- changelog -->
|
||||||
|
|
||||||
|
## [v1.65.7](https://github.com/wanderer-industries/wanderer/compare/v1.65.6...v1.65.7) (2025-05-26)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Core: Fixed map character tracking issues
|
||||||
|
|
||||||
|
## [v1.65.6](https://github.com/wanderer-industries/wanderer/compare/v1.65.5...v1.65.6) (2025-05-26)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Core: Fixed map character tracking issues
|
||||||
|
|
||||||
|
## [v1.65.5](https://github.com/wanderer-industries/wanderer/compare/v1.65.4...v1.65.5) (2025-05-26)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Core: Fixed map character tracking issues
|
||||||
|
|
||||||
|
* Signature: Update restored signature character
|
||||||
|
|
||||||
|
## [v1.65.4](https://github.com/wanderer-industries/wanderer/compare/v1.65.3...v1.65.4) (2025-05-24)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Signature: Force signature update even if there are no any changes
|
||||||
|
|
||||||
|
## [v1.65.3](https://github.com/wanderer-industries/wanderer/compare/v1.65.2...v1.65.3) (2025-05-23)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Signature: Fixed signature clenup
|
||||||
|
|
||||||
|
## [v1.65.2](https://github.com/wanderer-industries/wanderer/compare/v1.65.1...v1.65.2) (2025-05-23)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Signature: Fixed signature updates
|
||||||
|
|
||||||
|
## [v1.65.1](https://github.com/wanderer-industries/wanderer/compare/v1.65.0...v1.65.1) (2025-05-22)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Core: Added unsync map events timeout handling (force page refresh if outdated map events found)
|
||||||
|
|
||||||
|
## [v1.65.0](https://github.com/wanderer-industries/wanderer/compare/v1.64.8...v1.65.0) (2025-05-22)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Features:
|
||||||
|
|
||||||
|
* default connections from c1 holes to medium size
|
||||||
|
|
||||||
|
* support german and french signatures
|
||||||
|
|
||||||
|
* improve signature undo process
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* remove required id field from character schema
|
||||||
|
|
||||||
|
* update openapi spec response types
|
||||||
|
|
||||||
|
* fix issue with connection generation between k-space
|
||||||
|
|
||||||
|
* Signature: Fixed signatures updates
|
||||||
|
|
||||||
|
* update openapi spec for other apis
|
||||||
|
|
||||||
|
## [v1.64.8](https://github.com/wanderer-industries/wanderer/compare/v1.64.7...v1.64.8) (2025-05-20)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Core: Added unsync map events timeout handling (force page refresh if outdated map events found)
|
||||||
|
|
||||||
|
## [v1.64.7](https://github.com/wanderer-industries/wanderer/compare/v1.64.6...v1.64.7) (2025-05-15)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Core: Fixed connection EOL time refreshed every 2 minutes
|
||||||
|
|
||||||
|
## [v1.64.6](https://github.com/wanderer-industries/wanderer/compare/v1.64.5...v1.64.6) (2025-05-15)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Core: Added map hubs limits checking & a proper warning message shown
|
||||||
|
|
||||||
|
## [v1.64.5](https://github.com/wanderer-industries/wanderer/compare/v1.64.4...v1.64.5) (2025-05-14)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Core: Added character name update on re-auth
|
||||||
|
|
||||||
|
## [v1.64.4](https://github.com/wanderer-industries/wanderer/compare/v1.64.3...v1.64.4) (2025-05-14)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Core: Added 1 min timeout for ship and location updates on ESI API errors
|
||||||
|
|
||||||
|
## [v1.64.3](https://github.com/wanderer-industries/wanderer/compare/v1.64.2...v1.64.3) (2025-05-14)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Core: Fixed character tracking initialization logic & removed search caching
|
||||||
|
|
||||||
|
## [v1.64.2](https://github.com/wanderer-industries/wanderer/compare/v1.64.1...v1.64.2) (2025-05-13)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Core: Fixed tracking of ship & location for offline characters
|
||||||
|
|
||||||
|
## [v1.64.1](https://github.com/wanderer-industries/wanderer/compare/v1.64.0...v1.64.1) (2025-05-13)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Core: Fixed tracking stopped due to server errors
|
||||||
|
|
||||||
|
## [v1.64.0](https://github.com/wanderer-industries/wanderer/compare/v1.63.0...v1.64.0) (2025-05-13)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Features:
|
||||||
|
|
||||||
|
* api: add additional structure/signature methods (#365)
|
||||||
|
|
||||||
|
* api: add additional system/connections methods (#351)
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Core: Fixed EOL connections cleanup
|
||||||
|
|
||||||
|
* Core: Avoid Zarzakh system in routes widget
|
||||||
|
|
||||||
|
* remove repeat errors for token refresh (#375)
|
||||||
|
|
||||||
|
* updated openapi spec for character activity (#374)
|
||||||
|
|
||||||
|
* removed error from characters endpoint, and updated routes (#372)
|
||||||
|
|
||||||
|
* cleanup examples for system and connections (#370)
|
||||||
|
|
||||||
|
* remove error on websocket reconnect (#367)
|
||||||
|
|
||||||
|
## [v1.63.0](https://github.com/wanderer-industries/wanderer/compare/v1.62.4...v1.63.0) (2025-05-11)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Features:
|
||||||
|
|
||||||
|
* Core: Updated map active characters page
|
||||||
|
|
||||||
|
## [v1.62.4](https://github.com/wanderer-industries/wanderer/compare/v1.62.3...v1.62.4) (2025-05-10)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Core: Fixed map characters got untracked
|
||||||
|
|
||||||
|
## [v1.62.3](https://github.com/wanderer-industries/wanderer/compare/v1.62.2...v1.62.3) (2025-05-08)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Core: Fixed map characters got untracked
|
||||||
|
|
||||||
|
## [v1.62.2](https://github.com/wanderer-industries/wanderer/compare/v1.62.1...v1.62.2) (2025-05-05)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Core: Fixed audit export API
|
||||||
|
|
||||||
|
## [v1.62.1](https://github.com/wanderer-industries/wanderer/compare/v1.62.0...v1.62.1) (2025-05-05)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [v1.62.0](https://github.com/wanderer-industries/wanderer/compare/v1.61.2...v1.62.0) (2025-05-05)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Features:
|
||||||
|
|
||||||
|
* Core: added user routes support
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Map: Fixed link signature modal crash afrer destination system removed
|
||||||
|
|
||||||
|
* Map: Change design for tags (#358)
|
||||||
|
|
||||||
|
* Map: Removed paywall restriction from public routes
|
||||||
|
|
||||||
|
* Core: Fixed issues with structures loading
|
||||||
|
|
||||||
|
* Map: Removed unnecessary logs
|
||||||
|
|
||||||
|
* Map: Add support user routes
|
||||||
|
|
||||||
|
* Map: Add support for User Routes on FE side.
|
||||||
|
|
||||||
|
* Map: Refactor Local - show ship name, change placement of ship name. Refactor On the Map - show corp and ally logo. Fixed problem with ellipsis at long character and ship names.
|
||||||
|
|
||||||
|
* Map: Refactored routes widget. Add loader for routes. Prepared for custom hubs
|
||||||
|
|
||||||
|
* Map: Refactor init and update of mapper
|
||||||
|
|
||||||
|
## [v1.61.2](https://github.com/wanderer-industries/wanderer/compare/v1.61.1...v1.61.2) (2025-04-29)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Core: Fixed main character checking & manual systems delete logic
|
||||||
|
|
||||||
|
## [v1.61.1](https://github.com/wanderer-industries/wanderer/compare/v1.61.0...v1.61.1) (2025-04-26)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Core: Fixed additional price calc for map sub updates
|
||||||
|
|
||||||
|
## [v1.61.0](https://github.com/wanderer-industries/wanderer/compare/v1.60.1...v1.61.0) (2025-04-24)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Features:
|
||||||
|
|
||||||
|
* Core: force checking main character set for all map activity
|
||||||
|
|
||||||
|
## [v1.60.1](https://github.com/wanderer-industries/wanderer/compare/v1.60.0...v1.60.1) (2025-04-22)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Map: Removed unnecessary code onFE part
|
||||||
|
|
||||||
|
* Map: Removed unnecessary debugger
|
||||||
|
|
||||||
|
* Map: Changed name for drifters systems. Fixed static info for Barbican.
|
||||||
|
|
||||||
|
## [v1.60.0](https://github.com/wanderer-industries/wanderer/compare/v1.59.11...v1.60.0) (2025-04-17)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Features:
|
||||||
|
|
||||||
|
* api: api showing character by user and main character (#334)
|
||||||
|
|
||||||
|
* Core: force map page reload after 30 mins of user inactivity (switched browser/tab)
|
||||||
|
|
||||||
|
* update character activity to use main character (#333)
|
||||||
|
|
||||||
|
## [v1.59.11](https://github.com/wanderer-industries/wanderer/compare/v1.59.10...v1.59.11) (2025-04-16)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Map: Fixed lifetime for A009 from 16h to 4.5h. Fixed problem with no appearing icon of shattered for Drifter wormholes. Fixed wanderings for Drifter wormholes. For system J011355 added static K346. For system J011824 added static K346. (#329)
|
||||||
|
|
||||||
|
## [v1.59.10](https://github.com/wanderer-industries/wanderer/compare/v1.59.9...v1.59.10) (2025-04-15)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [v1.59.9](https://github.com/wanderer-industries/wanderer/compare/v1.59.8...v1.59.9) (2025-04-15)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Core: Fixed issues with map server manager
|
||||||
|
|
||||||
|
## [v1.59.8](https://github.com/wanderer-industries/wanderer/compare/v1.59.7...v1.59.8) (2025-04-15)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Core: Fixed issues with main character & tracking
|
||||||
|
|
||||||
|
## [v1.59.7](https://github.com/wanderer-industries/wanderer/compare/v1.59.6...v1.59.7) (2025-04-14)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Core: Fixed auto-select splashed systems
|
||||||
|
|
||||||
|
## [v1.59.6](https://github.com/wanderer-industries/wanderer/compare/v1.59.5...v1.59.6) (2025-04-13)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Map: Fix icons of main, follow and shattered (#321)
|
||||||
|
|
||||||
|
## [v1.59.5](https://github.com/wanderer-industries/wanderer/compare/v1.59.4...v1.59.5) (2025-04-12)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Signatures: avoid signatures delete on wrong buffer
|
||||||
|
|
||||||
|
## [v1.59.4](https://github.com/wanderer-industries/wanderer/compare/v1.59.3...v1.59.4) (2025-04-10)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [v1.59.3](https://github.com/wanderer-industries/wanderer/compare/v1.59.2...v1.59.3) (2025-04-10)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [v1.59.2](https://github.com/wanderer-industries/wanderer/compare/v1.59.1...v1.59.2) (2025-04-10)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* Core: fixed connection validation
|
||||||
|
|
||||||
|
## [v1.59.1](https://github.com/wanderer-industries/wanderer/compare/v1.59.0...v1.59.1) (2025-03-26)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* doc: improve bot setup instructions (#309)
|
||||||
|
|
||||||
|
## [v1.59.0](https://github.com/wanderer-industries/wanderer/compare/v1.58.0...v1.59.0) (2025-03-23)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Features:
|
||||||
|
|
||||||
|
* Core: added handling cases when wrong connections created
|
||||||
|
|
||||||
|
## [v1.58.0](https://github.com/wanderer-industries/wanderer/compare/v1.57.1...v1.58.0) (2025-03-22)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Features:
|
||||||
|
|
||||||
|
* Core: Show online state on map characters page
|
||||||
|
|
||||||
|
* api: update character activity and api to allow date range (#299)
|
||||||
|
|
||||||
|
* api: update character activity and api to allow date range
|
||||||
|
|
||||||
|
## [v1.57.1](https://github.com/wanderer-industries/wanderer/compare/v1.57.0...v1.57.1) (2025-03-20)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [v1.57.0](https://github.com/wanderer-industries/wanderer/compare/v1.56.6...v1.57.0) (2025-03-19)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Features:
|
||||||
|
|
||||||
|
* doc: update bot news (#294)
|
||||||
|
|
||||||
|
## [v1.56.6](https://github.com/wanderer-industries/wanderer/compare/v1.56.5...v1.56.6) (2025-03-19)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [v1.56.5](https://github.com/wanderer-industries/wanderer/compare/v1.56.4...v1.56.5) (2025-03-19)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [v1.56.4](https://github.com/wanderer-industries/wanderer/compare/v1.56.3...v1.56.4) (2025-03-19)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [v1.56.3](https://github.com/wanderer-industries/wanderer/compare/v1.56.2...v1.56.3) (2025-03-19)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* cloak key error behavior (#288)
|
||||||
|
|
||||||
|
## [v1.56.2](https://github.com/wanderer-industries/wanderer/compare/v1.56.1...v1.56.2) (2025-03-18)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes:
|
||||||
|
|
||||||
|
* show signature tooltip on top
|
||||||
|
|
||||||
## [v1.56.1](https://github.com/wanderer-industries/wanderer/compare/v1.56.0...v1.56.1) (2025-03-18)
|
## [v1.56.1](https://github.com/wanderer-industries/wanderer/compare/v1.56.0...v1.56.1) (2025-03-18)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
7
Makefile
7
Makefile
@@ -1,4 +1,4 @@
|
|||||||
.PHONY: deploy install cleanup start yarn migrate format test coverage versions
|
.PHONY: deploy install cleanup start yarn migrate format test coverage versions standalone-tests
|
||||||
|
|
||||||
ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
|
ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
|
||||||
SHELL := /bin/bash
|
SHELL := /bin/bash
|
||||||
@@ -35,6 +35,11 @@ test t:
|
|||||||
coverage cover co:
|
coverage cover co:
|
||||||
mix test --cover
|
mix test --cover
|
||||||
|
|
||||||
|
unit-tests ut:
|
||||||
|
@echo "Running unit tests..."
|
||||||
|
@find test/unit -name "*.exs" -exec elixir {} \;
|
||||||
|
@echo "All unit tests completed."
|
||||||
|
|
||||||
versions v:
|
versions v:
|
||||||
@echo "Tool Versions"
|
@echo "Tool Versions"
|
||||||
@cat .tool-versions
|
@cat .tool-versions
|
||||||
|
|||||||
@@ -112,19 +112,19 @@ body > div:first-of-type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.wd-characters-icons {
|
.wd-characters-icons {
|
||||||
display: flex;
|
/*display: flex;*/
|
||||||
transition:
|
/*transition:*/
|
||||||
border-color 250ms,
|
/* border-color 250ms,*/
|
||||||
opacity 250ms;
|
/* opacity 250ms;*/
|
||||||
width: 35px;
|
/*width: 35px;*/
|
||||||
height: 35px;
|
/*height: 35px;*/
|
||||||
border-radius: 50%;
|
/*border-radius: 50%;*/
|
||||||
border-width: 2px;
|
/*border-width: 2px;*/
|
||||||
border-style: solid;
|
/*border-style: solid;*/
|
||||||
border-color: #5a5a5a;
|
/*border-color: #5a5a5a;*/
|
||||||
background-color: rgba(0, 0, 0, 0);
|
/*background-color: rgba(0, 0, 0, 0);*/
|
||||||
cursor: pointer;
|
/*cursor: pointer;*/
|
||||||
opacity: 0.6;
|
/*opacity: 0.6;*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.wd-bg-default {
|
.wd-bg-default {
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { ErrorBoundary } from 'react-error-boundary';
|
|
||||||
import { PrimeReactProvider } from 'primereact/api';
|
import { PrimeReactProvider } from 'primereact/api';
|
||||||
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
|
|
||||||
import { ReactFlowProvider } from 'reactflow';
|
|
||||||
import { MapHandlers } from '@/hooks/Mapper/types/mapHandlers.ts';
|
import { MapHandlers } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||||
import { ErrorInfo, useCallback, useEffect, useRef } from 'react';
|
import { ErrorInfo, useCallback, useEffect, useRef } from 'react';
|
||||||
|
import { ReactFlowProvider } from 'reactflow';
|
||||||
import { useMapperHandlers } from './useMapperHandlers';
|
import { useMapperHandlers } from './useMapperHandlers';
|
||||||
|
|
||||||
import './common-styles/main.scss';
|
|
||||||
import { MapRootProvider } from '@/hooks/Mapper/mapRootProvider';
|
|
||||||
import { MapRootContent } from '@/hooks/Mapper/components/mapRootContent/MapRootContent.tsx';
|
import { MapRootContent } from '@/hooks/Mapper/components/mapRootContent/MapRootContent.tsx';
|
||||||
|
import { MapRootProvider } from '@/hooks/Mapper/mapRootProvider';
|
||||||
|
import './common-styles/main.scss';
|
||||||
|
|
||||||
const ErrorFallback = () => {
|
const ErrorFallback = () => {
|
||||||
return <div className="!z-100 absolute w-screen h-screen bg-transparent"></div>;
|
return <div className="!z-100 absolute w-screen h-screen bg-transparent"></div>;
|
||||||
@@ -20,7 +20,7 @@ export default function MapRoot({ hooks }) {
|
|||||||
|
|
||||||
const mapperHandlerRefs = useRef([providerRef]);
|
const mapperHandlerRefs = useRef([providerRef]);
|
||||||
|
|
||||||
const { handleCommand, handleMapEvent, handleMapEvents } = useMapperHandlers(mapperHandlerRefs.current, hooksRef);
|
const { handleCommand, handleMapEvent } = useMapperHandlers(mapperHandlerRefs.current, hooksRef);
|
||||||
|
|
||||||
const logError = useCallback((error: Error, info: ErrorInfo) => {
|
const logError = useCallback((error: Error, info: ErrorInfo) => {
|
||||||
if (!hooksRef.current) {
|
if (!hooksRef.current) {
|
||||||
@@ -35,7 +35,6 @@ export default function MapRoot({ hooks }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hooksRef.current.handleEvent('map_event', handleMapEvent);
|
hooksRef.current.handleEvent('map_event', handleMapEvent);
|
||||||
hooksRef.current.handleEvent('map_events', handleMapEvents);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -99,6 +99,11 @@
|
|||||||
.p-dropdown-item {
|
.p-dropdown-item {
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.p-dropdown-item-label {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-dropdown-item-group {
|
.p-dropdown-item-group {
|
||||||
@@ -143,3 +148,53 @@
|
|||||||
background: #966d3d;
|
background: #966d3d;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-datatable-wrapper {
|
||||||
|
height: 100%;
|
||||||
|
& {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: rgba(255, 255, 255, 0.5) transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(255, 255, 255, 0.5);
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
background-clip: content-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-button {
|
||||||
|
display: none;
|
||||||
|
height: 0;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-datatable .p-datatable-tbody > tr.p-highlight {
|
||||||
|
background: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suppress-menu-behaviour {
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
.p-menuitem-content {
|
||||||
|
pointer-events: initial;
|
||||||
|
background-color: initial !important;
|
||||||
|
}
|
||||||
|
.p-menuitem-content:hover {
|
||||||
|
background-color: initial !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
/* Основной класс диалога */
|
|
||||||
body .p-dialog {
|
body .p-dialog {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
//position: absolute;
|
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
//visibility: hidden;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
box-shadow: 0 2px 10px 0 rgba(0,0,0,0.2);
|
box-shadow: 0 2px 10px 0 rgba(0,0,0,0.2);
|
||||||
@@ -29,12 +26,10 @@ body .p-dialog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Стиль видимого диалога */
|
|
||||||
.p-dialog-visible {
|
.p-dialog-visible {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Анимации */
|
|
||||||
.p-dialog-enter {
|
.p-dialog-enter {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
@@ -53,31 +48,27 @@ body .p-dialog {
|
|||||||
transition: opacity 0.3s;
|
transition: opacity 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Заголовок диалога */
|
|
||||||
.p-dialog-header {
|
.p-dialog-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
background: #f4f4f4;
|
background: #f4f4f4;
|
||||||
//border-bottom: 1px solid #ddd;
|
height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Содержимое диалога */
|
|
||||||
.p-dialog-content {
|
.p-dialog-content {
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Подвал диалога */
|
|
||||||
.p-dialog-footer {
|
.p-dialog-footer {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border-top: 1px solid #ddd;
|
border-top: 1px solid #ddd;
|
||||||
background: #f4f4f4;
|
background: #f4f4f4;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Кнопка закрытия диалога */
|
|
||||||
.p-dialog-header-close {
|
.p-dialog-header-close {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -93,3 +84,12 @@ body .p-dialog {
|
|||||||
.p-dialog-header-close .pi {
|
.p-dialog-header-close .pi {
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-dialog {
|
||||||
|
.p-dialog-title {
|
||||||
|
font-size: 1rem !important;
|
||||||
|
}
|
||||||
|
.p-dialog-header-icons {
|
||||||
|
align-self: initial !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
.vertical-tabs-container {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 300px;
|
||||||
|
|
||||||
|
.p-tabview {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-tabview-panels {
|
||||||
|
padding: 6px 1rem;
|
||||||
|
flex-grow: 1;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-tabview-nav-container {
|
||||||
|
border-right: none;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-tabview-nav {
|
||||||
|
flex-direction: column;
|
||||||
|
width: 150px;
|
||||||
|
min-height: 100%;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
li {
|
||||||
|
width: 100%;
|
||||||
|
border-right: 4px solid var(--surface-hover);
|
||||||
|
background-color: var(--surface-card);
|
||||||
|
|
||||||
|
transition: background-color 200ms, border-right-color 200ms;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--surface-hover);
|
||||||
|
border-right: 4px solid var(--surface-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-tabview-nav-link {
|
||||||
|
transition: color 200ms;
|
||||||
|
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 10px;
|
||||||
|
//background-color: var(--surface-card);
|
||||||
|
background-color: initial;
|
||||||
|
border: none;
|
||||||
|
color: var(--gray-400);
|
||||||
|
|
||||||
|
border-radius: initial;
|
||||||
|
font-weight: 400;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.p-tabview-selected {
|
||||||
|
background-color: var(--surface-50);
|
||||||
|
border-right: 4px solid var(--primary-color);
|
||||||
|
|
||||||
|
.p-tabview-nav-link {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
//background-color: var(--surface-hover);
|
||||||
|
border-right: 4px solid var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-tabview-panel {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
@import "fix-dialog";
|
@import "fix-dialog";
|
||||||
@import "fix-popup";
|
@import "fix-popup";
|
||||||
|
@import "fix-tabs";
|
||||||
//@import "fix-input";
|
//@import "fix-input";
|
||||||
|
|
||||||
//@import "theme";
|
//@import "theme";
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
.Docked {
|
||||||
|
content: " ";
|
||||||
|
display: inline-block;
|
||||||
|
width: 11px;
|
||||||
|
height: 11px;
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 1px;
|
||||||
|
|
||||||
|
background-image: url(/images/citadelLarge.png);
|
||||||
|
left: 2px;
|
||||||
|
top: 22px;
|
||||||
|
transform: rotateZ(0deg);
|
||||||
|
}
|
||||||
@@ -4,10 +4,22 @@ import { useAutoAnimate } from '@formkit/auto-animate/react';
|
|||||||
import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
|
import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||||
import { CharacterTypeRaw } from '@/hooks/Mapper/types';
|
import { CharacterTypeRaw } from '@/hooks/Mapper/types';
|
||||||
import { emitMapEvent } from '@/hooks/Mapper/events';
|
import { emitMapEvent } from '@/hooks/Mapper/events';
|
||||||
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
|
import classes from './Characters.module.scss';
|
||||||
|
import { isDocked } from '@/hooks/Mapper/helpers/isDocked.ts';
|
||||||
|
import { PrimeIcons } from 'primereact/api';
|
||||||
|
|
||||||
const Characters = ({ data }: { data: CharacterTypeRaw[] }) => {
|
interface CharactersProps {
|
||||||
|
data: CharacterTypeRaw[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Characters = ({ data }: CharactersProps) => {
|
||||||
const [parent] = useAutoAnimate();
|
const [parent] = useAutoAnimate();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: { mainCharacterEveId, followingCharacterEveId },
|
||||||
|
} = useMapRootState();
|
||||||
|
|
||||||
const handleSelect = useCallback((character: CharacterTypeRaw) => {
|
const handleSelect = useCallback((character: CharacterTypeRaw) => {
|
||||||
emitMapEvent({
|
emitMapEvent({
|
||||||
name: Commands.centerSystem,
|
name: Commands.centerSystem,
|
||||||
@@ -21,21 +33,58 @@ const Characters = ({ data }: { data: CharacterTypeRaw[] }) => {
|
|||||||
className="flex flex-col items-center justify-center"
|
className="flex flex-col items-center justify-center"
|
||||||
onClick={() => handleSelect(character)}
|
onClick={() => handleSelect(character)}
|
||||||
>
|
>
|
||||||
<div className="tooltip tooltip-bottom" title={character.name}>
|
<div
|
||||||
<a
|
className={clsx(
|
||||||
className={clsx('wd-characters-icons wd-bg-default', { ['character-online']: character.online })}
|
'overflow-hidden relative',
|
||||||
|
'flex w-[35px] h-[35px] rounded-[4px] border-[1px] border-solid bg-transparent cursor-pointer',
|
||||||
|
'transition-colors duration-250',
|
||||||
|
{
|
||||||
|
['border-stone-800/90']: !character.online,
|
||||||
|
['border-lime-600/70']: character.online,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
title={character.name}
|
||||||
|
>
|
||||||
|
{mainCharacterEveId === character.eve_id && (
|
||||||
|
<span
|
||||||
|
className={clsx(
|
||||||
|
'absolute top-[2px] left-[22px] w-[9px] h-[9px]',
|
||||||
|
'text-yellow-500 text-[9px] rounded-[1px] z-10',
|
||||||
|
'pi',
|
||||||
|
PrimeIcons.STAR_FILL,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{followingCharacterEveId === character.eve_id && (
|
||||||
|
<span
|
||||||
|
className={clsx(
|
||||||
|
'absolute top-[23px] left-[22px] w-[10px] h-[10px]',
|
||||||
|
'text-sky-300 text-[10px] rounded-[1px] z-10',
|
||||||
|
'pi pi-angle-double-right',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isDocked(character.location) && <div className={classes.Docked} />}
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
'flex w-full h-full bg-transparent cursor-pointer',
|
||||||
|
'bg-center bg-no-repeat bg-[length:100%]',
|
||||||
|
'transition-opacity',
|
||||||
|
'shadow-[inset_0_1px_6px_1px_#000000]',
|
||||||
|
{
|
||||||
|
['opacity-60']: !character.online,
|
||||||
|
['opacity-100']: character.online,
|
||||||
|
},
|
||||||
|
)}
|
||||||
style={{ backgroundImage: `url(https://images.evetech.net/characters/${character.eve_id}/portrait)` }}
|
style={{ backgroundImage: `url(https://images.evetech.net/characters/${character.eve_id}/portrait)` }}
|
||||||
></a>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
));
|
));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className="flex characters" id="characters" ref={parent}>
|
<ul className="flex gap-1 characters" id="characters" ref={parent}>
|
||||||
{items}
|
{items}
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line react/display-name
|
|
||||||
export default Characters;
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/ty
|
|||||||
|
|
||||||
export interface ContextMenuSystemProps {
|
export interface ContextMenuSystemProps {
|
||||||
hubs: string[];
|
hubs: string[];
|
||||||
|
userHubs: string[];
|
||||||
contextMenuRef: RefObject<ContextMenu>;
|
contextMenuRef: RefObject<ContextMenu>;
|
||||||
systemId: string | undefined;
|
systemId: string | undefined;
|
||||||
systems: SolarSystemRawType[];
|
systems: SolarSystemRawType[];
|
||||||
@@ -13,6 +14,7 @@ export interface ContextMenuSystemProps {
|
|||||||
onLockToggle(): void;
|
onLockToggle(): void;
|
||||||
onOpenSettings(): void;
|
onOpenSettings(): void;
|
||||||
onHubToggle(): void;
|
onHubToggle(): void;
|
||||||
|
onUserHubToggle(): void;
|
||||||
onSystemTag(val?: string): void;
|
onSystemTag(val?: string): void;
|
||||||
onSystemStatus(val: number): void;
|
onSystemStatus(val: number): void;
|
||||||
onSystemLabels(val: string): void;
|
onSystemLabels(val: string): void;
|
||||||
@@ -25,7 +27,7 @@ export const ContextMenuSystem: React.FC<ContextMenuSystemProps> = ({ contextMen
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ContextMenu model={items} ref={contextMenuRef} breakpoint="767px" />
|
<ContextMenu className="min-w-[200px]" model={items} ref={contextMenuRef} breakpoint="767px" />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export * from './useTagMenu.ts';
|
export * from './useTagMenu.tsx';
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
import { MenuItem } from 'primereact/menuitem';
|
|
||||||
import { PrimeIcons } from 'primereact/api';
|
|
||||||
import { useCallback, useRef } from 'react';
|
|
||||||
import { SolarSystemRawType } from '@/hooks/Mapper/types';
|
|
||||||
import { getSystemById } from '@/hooks/Mapper/helpers';
|
|
||||||
import clsx from 'clsx';
|
|
||||||
import { GRADIENT_MENU_ACTIVE_CLASSES } from '@/hooks/Mapper/constants.ts';
|
|
||||||
|
|
||||||
const AVAILABLE_LETTERS = ['A', 'B', 'C', 'D', 'E', 'F', 'X', 'Y', 'Z'];
|
|
||||||
const AVAILABLE_NUMBERS = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
|
||||||
|
|
||||||
export const useTagMenu = (
|
|
||||||
systems: SolarSystemRawType[],
|
|
||||||
systemId: string | undefined,
|
|
||||||
onSystemTag: (val?: string) => void,
|
|
||||||
): (() => MenuItem) => {
|
|
||||||
const ref = useRef({ onSystemTag, systems, systemId });
|
|
||||||
ref.current = { onSystemTag, systems, systemId };
|
|
||||||
|
|
||||||
return useCallback(() => {
|
|
||||||
const { onSystemTag, systemId, systems } = ref.current;
|
|
||||||
const system = systemId ? getSystemById(systems, systemId) : undefined;
|
|
||||||
|
|
||||||
const isSelectedLetters = AVAILABLE_LETTERS.includes(system?.tag ?? '');
|
|
||||||
const isSelectedNumbers = AVAILABLE_NUMBERS.includes(system?.tag ?? '');
|
|
||||||
|
|
||||||
const menuItem: MenuItem = {
|
|
||||||
label: 'Tag',
|
|
||||||
icon: PrimeIcons.HASHTAG,
|
|
||||||
className: clsx({ [GRADIENT_MENU_ACTIVE_CLASSES]: isSelectedLetters || isSelectedNumbers }),
|
|
||||||
items: [
|
|
||||||
...(system?.tag !== '' && system?.tag !== null
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
label: 'Clear',
|
|
||||||
icon: PrimeIcons.BAN,
|
|
||||||
command: () => onSystemTag(),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
{
|
|
||||||
label: 'Letter',
|
|
||||||
icon: PrimeIcons.TAGS,
|
|
||||||
className: clsx({ [GRADIENT_MENU_ACTIVE_CLASSES]: isSelectedLetters }),
|
|
||||||
items: AVAILABLE_LETTERS.map(x => ({
|
|
||||||
label: x,
|
|
||||||
icon: PrimeIcons.TAG,
|
|
||||||
command: () => onSystemTag(x),
|
|
||||||
className: clsx({ [GRADIENT_MENU_ACTIVE_CLASSES]: system?.tag === x }),
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Digit',
|
|
||||||
icon: PrimeIcons.TAGS,
|
|
||||||
className: clsx({ [GRADIENT_MENU_ACTIVE_CLASSES]: isSelectedNumbers }),
|
|
||||||
items: AVAILABLE_NUMBERS.map(x => ({
|
|
||||||
label: x,
|
|
||||||
icon: PrimeIcons.TAG,
|
|
||||||
command: () => onSystemTag(x),
|
|
||||||
className: clsx({ [GRADIENT_MENU_ACTIVE_CLASSES]: system?.tag === x }),
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
return menuItem;
|
|
||||||
}, []);
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import { MenuItem } from 'primereact/menuitem';
|
||||||
|
import { PrimeIcons } from 'primereact/api';
|
||||||
|
import { useCallback, useRef } from 'react';
|
||||||
|
import { SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||||
|
import { getSystemById } from '@/hooks/Mapper/helpers';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { GRADIENT_MENU_ACTIVE_CLASSES } from '@/hooks/Mapper/constants.ts';
|
||||||
|
import { LayoutEventBlocker } from '@/hooks/Mapper/components/ui-kit';
|
||||||
|
import { Button } from 'primereact/button';
|
||||||
|
|
||||||
|
const AVAILABLE_TAGS = [
|
||||||
|
'A',
|
||||||
|
'B',
|
||||||
|
'C',
|
||||||
|
'D',
|
||||||
|
'E',
|
||||||
|
'F',
|
||||||
|
'G',
|
||||||
|
'H',
|
||||||
|
'I',
|
||||||
|
'X',
|
||||||
|
'Y',
|
||||||
|
'Z',
|
||||||
|
'0',
|
||||||
|
'1',
|
||||||
|
'2',
|
||||||
|
'3',
|
||||||
|
'4',
|
||||||
|
'5',
|
||||||
|
'6',
|
||||||
|
'7',
|
||||||
|
'8',
|
||||||
|
'9',
|
||||||
|
];
|
||||||
|
|
||||||
|
export const useTagMenu = (
|
||||||
|
systems: SolarSystemRawType[],
|
||||||
|
systemId: string | undefined,
|
||||||
|
onSystemTag: (val?: string) => void,
|
||||||
|
): (() => MenuItem) => {
|
||||||
|
const ref = useRef({ onSystemTag, systems, systemId });
|
||||||
|
ref.current = { onSystemTag, systems, systemId };
|
||||||
|
|
||||||
|
return useCallback(() => {
|
||||||
|
const { onSystemTag, systemId, systems } = ref.current;
|
||||||
|
const system = systemId ? getSystemById(systems, systemId) : undefined;
|
||||||
|
|
||||||
|
const isSelectedTag = AVAILABLE_TAGS.includes(system?.tag ?? '');
|
||||||
|
|
||||||
|
const menuItem: MenuItem = {
|
||||||
|
label: 'Tag',
|
||||||
|
icon: PrimeIcons.HASHTAG,
|
||||||
|
className: clsx({ [GRADIENT_MENU_ACTIVE_CLASSES]: isSelectedTag }),
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Digit',
|
||||||
|
icon: PrimeIcons.TAGS,
|
||||||
|
className: '!h-[128px] suppress-menu-behaviour',
|
||||||
|
template: () => {
|
||||||
|
return (
|
||||||
|
<LayoutEventBlocker className="flex flex-col gap-1 w-[200px] h-full px-2">
|
||||||
|
<div className="grid grid-cols-[auto_auto_auto_auto_auto_auto] gap-1">
|
||||||
|
{AVAILABLE_TAGS.map(x => (
|
||||||
|
<Button
|
||||||
|
outlined={system?.tag !== x}
|
||||||
|
severity="warning"
|
||||||
|
key={x}
|
||||||
|
value={x}
|
||||||
|
size="small"
|
||||||
|
className="p-[3px] justify-center"
|
||||||
|
onClick={() => system?.tag !== x && onSystemTag(x)}
|
||||||
|
>
|
||||||
|
{x}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
<Button
|
||||||
|
disabled={!isSelectedTag}
|
||||||
|
icon="pi pi-ban"
|
||||||
|
size="small"
|
||||||
|
className="!p-0 !w-[initial] justify-center"
|
||||||
|
outlined
|
||||||
|
severity="help"
|
||||||
|
onClick={() => onSystemTag()}
|
||||||
|
></Button>
|
||||||
|
</div>
|
||||||
|
</LayoutEventBlocker>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
return menuItem;
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
@@ -8,19 +8,25 @@ import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
|
|||||||
|
|
||||||
interface UseContextMenuSystemHandlersProps {
|
interface UseContextMenuSystemHandlersProps {
|
||||||
hubs: string[];
|
hubs: string[];
|
||||||
|
userHubs: string[];
|
||||||
systems: SolarSystemRawType[];
|
systems: SolarSystemRawType[];
|
||||||
outCommand: OutCommandHandler;
|
outCommand: OutCommandHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseContextMenuSystemHandlersProps) => {
|
export const useContextMenuSystemHandlers = ({
|
||||||
|
systems,
|
||||||
|
hubs,
|
||||||
|
userHubs,
|
||||||
|
outCommand,
|
||||||
|
}: UseContextMenuSystemHandlersProps) => {
|
||||||
const contextMenuRef = useRef<ContextMenu | null>(null);
|
const contextMenuRef = useRef<ContextMenu | null>(null);
|
||||||
|
|
||||||
const [system, setSystem] = useState<string>();
|
const [system, setSystem] = useState<string>();
|
||||||
|
|
||||||
const { deleteSystems } = useDeleteSystems();
|
const { deleteSystems } = useDeleteSystems();
|
||||||
|
|
||||||
const ref = useRef({ hubs, system, systems, outCommand, deleteSystems });
|
const ref = useRef({ hubs, userHubs, system, systems, outCommand, deleteSystems });
|
||||||
ref.current = { hubs, system, systems, outCommand, deleteSystems };
|
ref.current = { hubs, userHubs, system, systems, outCommand, deleteSystems };
|
||||||
|
|
||||||
const open = useCallback((ev: any, systemId: string) => {
|
const open = useCallback((ev: any, systemId: string) => {
|
||||||
setSystem(systemId);
|
setSystem(systemId);
|
||||||
@@ -72,6 +78,21 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
|
|||||||
setSystem(undefined);
|
setSystem(undefined);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const onUserHubToggle = useCallback(() => {
|
||||||
|
const { userHubs, system, outCommand } = ref.current;
|
||||||
|
if (!system) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
outCommand({
|
||||||
|
type: !userHubs.includes(system) ? OutCommand.addUserHub : OutCommand.deleteUserHub,
|
||||||
|
data: {
|
||||||
|
system_id: system,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
setSystem(undefined);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const onSystemTag = useCallback((tag?: string) => {
|
const onSystemTag = useCallback((tag?: string) => {
|
||||||
const { system, outCommand } = ref.current;
|
const { system, outCommand } = ref.current;
|
||||||
if (!system) {
|
if (!system) {
|
||||||
@@ -104,7 +125,6 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
|
|||||||
setSystem(undefined);
|
setSystem(undefined);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
const onSystemStatus = useCallback((status: number) => {
|
const onSystemStatus = useCallback((status: number) => {
|
||||||
const { system, outCommand } = ref.current;
|
const { system, outCommand } = ref.current;
|
||||||
if (!system) {
|
if (!system) {
|
||||||
@@ -177,6 +197,7 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
|
|||||||
onDeleteSystem,
|
onDeleteSystem,
|
||||||
onLockToggle,
|
onLockToggle,
|
||||||
onHubToggle,
|
onHubToggle,
|
||||||
|
onUserHubToggle,
|
||||||
onSystemTag,
|
onSystemTag,
|
||||||
onSystemTemporaryName,
|
onSystemTemporaryName,
|
||||||
onSystemStatus,
|
onSystemStatus,
|
||||||
|
|||||||
@@ -9,11 +9,14 @@ import { FastSystemActions } from '@/hooks/Mapper/components/contexts/components
|
|||||||
import { useMapCheckPermissions } from '@/hooks/Mapper/mapRootProvider/hooks/api';
|
import { useMapCheckPermissions } from '@/hooks/Mapper/mapRootProvider/hooks/api';
|
||||||
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
|
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
|
||||||
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
|
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
|
||||||
|
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
|
||||||
|
import { MapAddIcon, MapDeleteIcon, MapUserAddIcon, MapUserDeleteIcon } from '@/hooks/Mapper/icons';
|
||||||
|
|
||||||
export const useContextMenuSystemItems = ({
|
export const useContextMenuSystemItems = ({
|
||||||
onDeleteSystem,
|
onDeleteSystem,
|
||||||
onLockToggle,
|
onLockToggle,
|
||||||
onHubToggle,
|
onHubToggle,
|
||||||
|
onUserHubToggle,
|
||||||
onSystemTag,
|
onSystemTag,
|
||||||
onSystemStatus,
|
onSystemStatus,
|
||||||
onSystemLabels,
|
onSystemLabels,
|
||||||
@@ -22,6 +25,7 @@ export const useContextMenuSystemItems = ({
|
|||||||
onWaypointSet,
|
onWaypointSet,
|
||||||
systemId,
|
systemId,
|
||||||
hubs,
|
hubs,
|
||||||
|
userHubs,
|
||||||
systems,
|
systems,
|
||||||
}: Omit<ContextMenuSystemProps, 'contextMenuRef'>) => {
|
}: Omit<ContextMenuSystemProps, 'contextMenuRef'>) => {
|
||||||
const getTags = useTagMenu(systems, systemId, onSystemTag);
|
const getTags = useTagMenu(systems, systemId, onSystemTag);
|
||||||
@@ -32,6 +36,7 @@ export const useContextMenuSystemItems = ({
|
|||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const system = systemId ? getSystemById(systems, systemId) : undefined;
|
const system = systemId ? getSystemById(systems, systemId) : undefined;
|
||||||
|
const systemStaticInfo = getSystemStaticInfo(systemId)!;
|
||||||
|
|
||||||
if (!system || !systemId) {
|
if (!system || !systemId) {
|
||||||
return [];
|
return [];
|
||||||
@@ -44,9 +49,9 @@ export const useContextMenuSystemItems = ({
|
|||||||
return (
|
return (
|
||||||
<FastSystemActions
|
<FastSystemActions
|
||||||
systemId={systemId}
|
systemId={systemId}
|
||||||
systemName={system.system_static_info.solar_system_name}
|
systemName={systemStaticInfo.solar_system_name}
|
||||||
regionName={system.system_static_info.region_name}
|
regionName={systemStaticInfo.region_name}
|
||||||
isWH={isWormholeSpace(system.system_static_info.system_class)}
|
isWH={isWormholeSpace(systemStaticInfo.system_class)}
|
||||||
showEdit
|
showEdit
|
||||||
onOpenSettings={onOpenSettings}
|
onOpenSettings={onOpenSettings}
|
||||||
/>
|
/>
|
||||||
@@ -57,12 +62,25 @@ export const useContextMenuSystemItems = ({
|
|||||||
getTags(),
|
getTags(),
|
||||||
getStatus(),
|
getStatus(),
|
||||||
...getLabels(),
|
...getLabels(),
|
||||||
...getWaypointMenu(systemId, system.system_static_info.system_class),
|
...getWaypointMenu(systemId, systemStaticInfo.system_class),
|
||||||
{
|
{
|
||||||
label: !hubs.includes(systemId) ? 'Add in Routes' : 'Remove from Routes',
|
label: !hubs.includes(systemId) ? 'Add Route' : 'Remove Route',
|
||||||
icon: PrimeIcons.MAP_MARKER,
|
icon: !hubs.includes(systemId) ? (
|
||||||
|
<MapAddIcon className="mr-1 relative left-[-2px]" />
|
||||||
|
) : (
|
||||||
|
<MapDeleteIcon className="mr-1 relative left-[-2px]" />
|
||||||
|
),
|
||||||
command: onHubToggle,
|
command: onHubToggle,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: !userHubs.includes(systemId) ? 'Add User Route' : 'Remove User Route',
|
||||||
|
icon: !userHubs.includes(systemId) ? (
|
||||||
|
<MapUserAddIcon className="mr-1 relative left-[-2px]" />
|
||||||
|
) : (
|
||||||
|
<MapUserDeleteIcon className="mr-1 relative left-[-2px]" />
|
||||||
|
),
|
||||||
|
command: onUserHubToggle,
|
||||||
|
},
|
||||||
...(system.locked
|
...(system.locked
|
||||||
? canLockSystem
|
? canLockSystem
|
||||||
? [
|
? [
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { FastSystemActions } from '@/hooks/Mapper/components/contexts/components
|
|||||||
import { useJumpPlannerMenu } from '@/hooks/Mapper/components/contexts/hooks';
|
import { useJumpPlannerMenu } from '@/hooks/Mapper/components/contexts/hooks';
|
||||||
import { Route } from '@/hooks/Mapper/types/routes.ts';
|
import { Route } from '@/hooks/Mapper/types/routes.ts';
|
||||||
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
|
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
|
||||||
|
import { MapAddIcon, MapDeleteIcon } from '@/hooks/Mapper/icons';
|
||||||
|
|
||||||
export interface ContextMenuSystemInfoProps {
|
export interface ContextMenuSystemInfoProps {
|
||||||
systemStatics: Map<number, SolarSystemStaticInfoRaw>;
|
systemStatics: Map<number, SolarSystemStaticInfoRaw>;
|
||||||
@@ -69,8 +70,12 @@ export const ContextMenuSystemInfo: React.FC<ContextMenuSystemInfoProps> = ({
|
|||||||
...getJumpPlannerMenu(system, routes),
|
...getJumpPlannerMenu(system, routes),
|
||||||
...getWaypointMenu(systemId, system.system_class),
|
...getWaypointMenu(systemId, system.system_class),
|
||||||
{
|
{
|
||||||
label: !hubs.includes(systemId) ? 'Add in Routes' : 'Remove from Routes',
|
label: !hubs.includes(systemId) ? 'Add Route' : 'Remove Route',
|
||||||
icon: PrimeIcons.MAP_MARKER,
|
icon: !hubs.includes(systemId) ? (
|
||||||
|
<MapAddIcon className="mr-1 relative left-[-2px]" />
|
||||||
|
) : (
|
||||||
|
<MapDeleteIcon className="mr-1 relative left-[-2px]" />
|
||||||
|
),
|
||||||
command: onHubToggle,
|
command: onHubToggle,
|
||||||
},
|
},
|
||||||
...(!systemOnMap
|
...(!systemOnMap
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useCallback, useRef, useState } from 'react';
|
import { useCallback, useRef, useState } from 'react';
|
||||||
import { ContextMenu } from 'primereact/contextmenu';
|
import { ContextMenu } from 'primereact/contextmenu';
|
||||||
import { Commands, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
|
import { Commands, OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||||
import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
||||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||||
import { SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
|
import { SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
|
||||||
import { emitMapEvent } from '@/hooks/Mapper/events';
|
import { emitMapEvent } from '@/hooks/Mapper/events';
|
||||||
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
|
import { useRouteProvider } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesProvider.tsx';
|
||||||
|
|
||||||
interface UseContextMenuSystemHandlersProps {
|
export const useContextMenuSystemInfoHandlers = () => {
|
||||||
hubs: string[];
|
const { outCommand } = useMapRootState();
|
||||||
outCommand: OutCommandHandler;
|
const { hubs = [], toggleHubCommand } = useRouteProvider();
|
||||||
}
|
|
||||||
|
|
||||||
export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand }: UseContextMenuSystemHandlersProps) => {
|
|
||||||
const contextMenuRef = useRef<ContextMenu | null>(null);
|
const contextMenuRef = useRef<ContextMenu | null>(null);
|
||||||
|
|
||||||
const [system, setSystem] = useState<string>();
|
const [system, setSystem] = useState<string>();
|
||||||
const routeRef = useRef<(SolarSystemStaticInfoRaw | undefined)[]>([]);
|
const routeRef = useRef<(SolarSystemStaticInfoRaw | undefined)[]>([]);
|
||||||
|
|
||||||
const ref = useRef({ hubs, system, outCommand });
|
const ref = useRef({ hubs, system, outCommand, toggleHubCommand });
|
||||||
ref.current = { hubs, system, outCommand };
|
ref.current = { hubs, system, outCommand, toggleHubCommand };
|
||||||
|
|
||||||
const open = useCallback(
|
const open = useCallback(
|
||||||
(ev: React.SyntheticEvent, systemId: string, route: (SolarSystemStaticInfoRaw | undefined)[]) => {
|
(ev: React.SyntheticEvent, systemId: string, route: (SolarSystemStaticInfoRaw | undefined)[]) => {
|
||||||
@@ -33,17 +33,12 @@ export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand }: UseContex
|
|||||||
);
|
);
|
||||||
|
|
||||||
const onHubToggle = useCallback(() => {
|
const onHubToggle = useCallback(() => {
|
||||||
const { hubs, system, outCommand } = ref.current;
|
const { system } = ref.current;
|
||||||
if (!system) {
|
if (!system) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
outCommand({
|
ref.current.toggleHubCommand(system);
|
||||||
type: !hubs.includes(system) ? OutCommand.addHub : OutCommand.deleteHub,
|
|
||||||
data: {
|
|
||||||
system_id: system,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
setSystem(undefined);
|
setSystem(undefined);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -59,6 +54,8 @@ export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand }: UseContex
|
|||||||
system_id: solarSystemId,
|
system_id: solarSystemId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO add it to some queue
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
emitMapEvent({
|
emitMapEvent({
|
||||||
name: Commands.centerSystem,
|
name: Commands.centerSystem,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useCallback, useRef } from 'react';
|
import { useCallback, useRef } from 'react';
|
||||||
import { LayoutEventBlocker, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
import { LayoutEventBlocker, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
||||||
import { ANOIK_ICON, DOTLAN_ICON, ZKB_ICON } from '@/hooks/Mapper/icons.ts';
|
import { ANOIK_ICON, DOTLAN_ICON, ZKB_ICON } from '@/hooks/Mapper/icons';
|
||||||
|
|
||||||
import classes from './FastSystemActions.module.scss';
|
import classes from './FastSystemActions.module.scss';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { useCallback } from 'react';
|
|||||||
import { isPossibleSpace } from '@/hooks/Mapper/components/map/helpers/isKnownSpace.ts';
|
import { isPossibleSpace } from '@/hooks/Mapper/components/map/helpers/isKnownSpace.ts';
|
||||||
import { Route } from '@/hooks/Mapper/types/routes.ts';
|
import { Route } from '@/hooks/Mapper/types/routes.ts';
|
||||||
import { SolarSystemRawType, SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
|
import { SolarSystemRawType, SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
|
||||||
import { getSystemById } from '@/hooks/Mapper/helpers';
|
|
||||||
import { SOLAR_SYSTEM_CLASS_IDS } from '@/hooks/Mapper/components/map/constants.ts';
|
import { SOLAR_SYSTEM_CLASS_IDS } from '@/hooks/Mapper/components/map/constants.ts';
|
||||||
|
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
|
||||||
|
|
||||||
const imperialSpace = [SOLAR_SYSTEM_CLASS_IDS.hs, SOLAR_SYSTEM_CLASS_IDS.ls, SOLAR_SYSTEM_CLASS_IDS.ns];
|
const imperialSpace = [SOLAR_SYSTEM_CLASS_IDS.hs, SOLAR_SYSTEM_CLASS_IDS.ls, SOLAR_SYSTEM_CLASS_IDS.ns];
|
||||||
const criminalSpace = [SOLAR_SYSTEM_CLASS_IDS.ls, SOLAR_SYSTEM_CLASS_IDS.ns];
|
const criminalSpace = [SOLAR_SYSTEM_CLASS_IDS.ls, SOLAR_SYSTEM_CLASS_IDS.ns];
|
||||||
@@ -47,7 +47,7 @@ export const useJumpPlannerMenu = (
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const origin = getSystemById(systems, systemIdFrom)?.system_static_info;
|
const origin = getSystemStaticInfo(systemIdFrom);
|
||||||
|
|
||||||
if (!origin) {
|
if (!origin) {
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
import { getSystemById } from '@/hooks/Mapper/helpers';
|
||||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { getSystemById } from '@/hooks/Mapper/helpers';
|
import { getSystemStaticInfo } from '../../mapRootProvider/hooks/useLoadSystemStatic';
|
||||||
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
|
|
||||||
|
|
||||||
interface UseSystemInfoProps {
|
interface UseSystemInfoProps {
|
||||||
systemId: string;
|
systemId: string;
|
||||||
@@ -12,14 +12,12 @@ export const useSystemInfo = ({ systemId }: UseSystemInfoProps) => {
|
|||||||
data: { systems, connections },
|
data: { systems, connections },
|
||||||
} = useMapRootState();
|
} = useMapRootState();
|
||||||
|
|
||||||
const { systems: systemStatics } = useLoadSystemStatic({ systems: [systemId] });
|
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const staticInfo = systemStatics.get(parseInt(systemId));
|
const staticInfo = getSystemStaticInfo(parseInt(systemId));
|
||||||
const dynamicInfo = getSystemById(systems, systemId);
|
const dynamicInfo = getSystemById(systems, systemId);
|
||||||
|
|
||||||
if (!staticInfo || !dynamicInfo) {
|
if (!staticInfo || !dynamicInfo) {
|
||||||
throw new Error(`Error on getting system ${systemId}`);
|
return { dynamicInfo, staticInfo, leadsTo: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
const leadsTo = connections
|
const leadsTo = connections
|
||||||
@@ -29,5 +27,5 @@ export const useSystemInfo = ({ systemId }: UseSystemInfoProps) => {
|
|||||||
.filter(x => x !== systemId);
|
.filter(x => x !== systemId);
|
||||||
|
|
||||||
return { dynamicInfo, staticInfo, leadsTo };
|
return { dynamicInfo, staticInfo, leadsTo };
|
||||||
}, [systemStatics, systemId, systems, connections]);
|
}, [systemId, systems, connections]);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ const INITIAL_DATA: MapData = {
|
|||||||
systemSignatures: {} as Record<string, SystemSignature[]>,
|
systemSignatures: {} as Record<string, SystemSignature[]>,
|
||||||
options: {} as Record<string, string | boolean>,
|
options: {} as Record<string, string | boolean>,
|
||||||
isSubscriptionActive: false,
|
isSubscriptionActive: false,
|
||||||
|
mainCharacterEveId: null,
|
||||||
|
followingCharacterEveId: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface MapContextProps {
|
export interface MapContextProps {
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import React, { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { SystemKillsContent } from '../../../mapInterface/widgets/SystemKills/SystemKillsContent/SystemKillsContent';
|
|
||||||
import { useKillsCounter } from '../../hooks/useKillsCounter';
|
import { useKillsCounter } from '../../hooks/useKillsCounter';
|
||||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||||
import { WithChildren, WithClassName } from '@/hooks/Mapper/types/common';
|
import { WithChildren, WithClassName } from '@/hooks/Mapper/types/common';
|
||||||
|
import {
|
||||||
|
KILLS_ROW_HEIGHT,
|
||||||
|
SystemKillsList,
|
||||||
|
} from '@/hooks/Mapper/components/mapInterface/widgets/WSystemKills/SystemKillsList';
|
||||||
|
import { TooltipSize } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper/utils.ts';
|
||||||
|
|
||||||
const ITEM_HEIGHT = 35;
|
|
||||||
const MIN_TOOLTIP_HEIGHT = 40;
|
const MIN_TOOLTIP_HEIGHT = 40;
|
||||||
|
|
||||||
type TooltipSize = 'xs' | 'sm' | 'md' | 'lg';
|
|
||||||
|
|
||||||
type KillsBookmarkTooltipProps = {
|
type KillsBookmarkTooltipProps = {
|
||||||
killsCount: number;
|
killsCount: number;
|
||||||
killsActivityType: string | null;
|
killsActivityType: string | null;
|
||||||
@@ -18,12 +19,14 @@ type KillsBookmarkTooltipProps = {
|
|||||||
} & WithChildren &
|
} & WithChildren &
|
||||||
WithClassName;
|
WithClassName;
|
||||||
|
|
||||||
export const KillsCounter = ({ killsCount, systemId, className, children, size = 'xs' }: KillsBookmarkTooltipProps) => {
|
export const KillsCounter = ({
|
||||||
const {
|
killsCount,
|
||||||
isLoading,
|
systemId,
|
||||||
kills: detailedKills,
|
className,
|
||||||
systemNameMap,
|
children,
|
||||||
} = useKillsCounter({
|
size = TooltipSize.xs,
|
||||||
|
}: KillsBookmarkTooltipProps) => {
|
||||||
|
const { isLoading, kills: detailedKills } = useKillsCounter({
|
||||||
realSystemId: systemId,
|
realSystemId: systemId,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -37,28 +40,24 @@ export const KillsCounter = ({ killsCount, systemId, className, children, size =
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate height based on number of kills, but ensure a minimum height
|
// Calculate height based on number of kills, but ensure a minimum height
|
||||||
const killsNeededHeight = limitedKills.length * ITEM_HEIGHT;
|
const killsNeededHeight = limitedKills.length * KILLS_ROW_HEIGHT;
|
||||||
// Add a small buffer (10px) to prevent scrollbar from appearing unnecessarily
|
// Add a small buffer (10px) to prevent scrollbar from appearing unnecessarily
|
||||||
const tooltipHeight = Math.max(MIN_TOOLTIP_HEIGHT, Math.min(killsNeededHeight + 10, 500));
|
const tooltipHeight = Math.max(MIN_TOOLTIP_HEIGHT, Math.min(killsNeededHeight + 10, 500));
|
||||||
|
|
||||||
const tooltipContent = (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
width: '400px',
|
|
||||||
height: `${tooltipHeight}px`,
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
}}
|
|
||||||
className="overflow-hidden"
|
|
||||||
>
|
|
||||||
<div className="flex-1 h-full">
|
|
||||||
<SystemKillsContent kills={limitedKills} systemNameMap={systemNameMap} onlyOneSystem />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WdTooltipWrapper content={tooltipContent} className={className} size={size} interactive={true}>
|
<WdTooltipWrapper
|
||||||
|
content={
|
||||||
|
<div className="overflow-hidden flex w-[450px] flex-col" style={{ height: `${tooltipHeight}px` }}>
|
||||||
|
<div className="flex-1 h-full">
|
||||||
|
<SystemKillsList kills={limitedKills} onlyOneSystem />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
className={className}
|
||||||
|
tooltipClassName="!px-0"
|
||||||
|
size={size}
|
||||||
|
interactive={true}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</WdTooltipWrapper>
|
</WdTooltipWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import { CharItemProps, LocalCharactersList } from '../../../mapInterface/widget
|
|||||||
import { useLocalCharactersItemTemplate } from '../../../mapInterface/widgets/LocalCharacters/hooks/useLocalCharacters';
|
import { useLocalCharactersItemTemplate } from '../../../mapInterface/widgets/LocalCharacters/hooks/useLocalCharacters';
|
||||||
import { useLocalCharacterWidgetSettings } from '../../../mapInterface/widgets/LocalCharacters/hooks/useLocalWidgetSettings';
|
import { useLocalCharacterWidgetSettings } from '../../../mapInterface/widgets/LocalCharacters/hooks/useLocalWidgetSettings';
|
||||||
import classes from './SolarSystemLocalCounter.module.scss';
|
import classes from './SolarSystemLocalCounter.module.scss';
|
||||||
import { AvailableThemes } from '@/hooks/Mapper/mapRootProvider';
|
|
||||||
import { useTheme } from '@/hooks/Mapper/hooks/useTheme.ts';
|
import { useTheme } from '@/hooks/Mapper/hooks/useTheme.ts';
|
||||||
|
import { AvailableThemes } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||||
|
|
||||||
interface LocalCounterProps {
|
interface LocalCounterProps {
|
||||||
localCounterCharacters: Array<CharItemProps>;
|
localCounterCharacters: Array<CharItemProps>;
|
||||||
|
|||||||
@@ -355,3 +355,15 @@ $tooltip-bg: #202020;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ShatteredIcon {
|
||||||
|
position: relative;
|
||||||
|
//top: -1px;
|
||||||
|
left: -1px;
|
||||||
|
|
||||||
|
background-size: 100%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
|
||||||
|
background-image: url(/images/chart-network-svgrepo-com.svg)
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Handle, NodeProps, Position } from 'reactflow';
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import classes from './SolarSystemNodeDefault.module.scss';
|
import classes from './SolarSystemNodeDefault.module.scss';
|
||||||
import { PrimeIcons } from 'primereact/api';
|
import { PrimeIcons } from 'primereact/api';
|
||||||
import { useLocalCounter, useSolarSystemNode, useNodeKillsCount } from '../../hooks';
|
import { useLocalCounter, useNodeKillsCount, useSolarSystemNode } from '../../hooks';
|
||||||
import {
|
import {
|
||||||
EFFECT_BACKGROUND_STYLES,
|
EFFECT_BACKGROUND_STYLES,
|
||||||
MARKER_BOOKMARK_BG_STYLES,
|
MARKER_BOOKMARK_BG_STYLES,
|
||||||
@@ -14,25 +14,27 @@ import { WormholeClassComp } from '@/hooks/Mapper/components/map/components/Worm
|
|||||||
import { UnsplashedSignature } from '@/hooks/Mapper/components/map/components/UnsplashedSignature';
|
import { UnsplashedSignature } from '@/hooks/Mapper/components/map/components/UnsplashedSignature';
|
||||||
import { LocalCounter } from './SolarSystemLocalCounter';
|
import { LocalCounter } from './SolarSystemLocalCounter';
|
||||||
import { KillsCounter } from './SolarSystemKillsCounter';
|
import { KillsCounter } from './SolarSystemKillsCounter';
|
||||||
|
import { TooltipSize } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper/utils.ts';
|
||||||
|
import { TooltipPosition, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
|
||||||
|
import { Tag } from 'primereact/tag';
|
||||||
|
|
||||||
|
// let render = 0;
|
||||||
export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>) => {
|
export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>) => {
|
||||||
const nodeVars = useSolarSystemNode(props);
|
const nodeVars = useSolarSystemNode(props);
|
||||||
const { localCounterCharacters } = useLocalCounter(nodeVars);
|
const { localCounterCharacters } = useLocalCounter(nodeVars);
|
||||||
const localKillsCount = useNodeKillsCount(nodeVars.solarSystemId, nodeVars.killsCount);
|
const localKillsCount = useNodeKillsCount(nodeVars.solarSystemId, nodeVars.killsCount);
|
||||||
|
|
||||||
|
// console.log('JOipP', `render ${nodeVars.id}`, render++);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{nodeVars.visible && (
|
{nodeVars.visible && (
|
||||||
<div className={classes.Bookmarks}>
|
<div className={classes.Bookmarks}>
|
||||||
{nodeVars.labelCustom !== '' && (
|
|
||||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.custom)}>
|
|
||||||
<span className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]">{nodeVars.labelCustom}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{nodeVars.isShattered && (
|
{nodeVars.isShattered && (
|
||||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.shattered)}>
|
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.shattered, '!pr-[2px]')}>
|
||||||
<span className={clsx('pi pi-chart-pie', classes.icon)} />
|
<WdTooltipWrapper content="Shattered" position={TooltipPosition.top}>
|
||||||
|
<span className={clsx('block w-[10px] h-[10px]', classes.ShatteredIcon)} />
|
||||||
|
</WdTooltipWrapper>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -40,7 +42,7 @@ export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>
|
|||||||
<KillsCounter
|
<KillsCounter
|
||||||
killsCount={localKillsCount}
|
killsCount={localKillsCount}
|
||||||
systemId={nodeVars.solarSystemId}
|
systemId={nodeVars.solarSystemId}
|
||||||
size="lg"
|
size={TooltipSize.lg}
|
||||||
killsActivityType={nodeVars.killsActivityType}
|
killsActivityType={nodeVars.killsActivityType}
|
||||||
className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[nodeVars.killsActivityType!])}
|
className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[nodeVars.killsActivityType!])}
|
||||||
>
|
>
|
||||||
@@ -51,6 +53,12 @@ export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>
|
|||||||
</KillsCounter>
|
</KillsCounter>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{nodeVars.labelCustom !== '' && (
|
||||||
|
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.custom)}>
|
||||||
|
<span className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]">{nodeVars.labelCustom}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{nodeVars.labelsInfo.map(x => (
|
{nodeVars.labelsInfo.map(x => (
|
||||||
<div key={x.id} className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[x.id])}>
|
<div key={x.id} className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[x.id])}>
|
||||||
{x.shortName}
|
{x.shortName}
|
||||||
@@ -82,7 +90,11 @@ export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{nodeVars.tag != null && nodeVars.tag !== '' && (
|
{nodeVars.tag != null && nodeVars.tag !== '' && (
|
||||||
<div className={clsx(classes.TagTitle, 'text-sky-400 font-medium')}>{nodeVars.tag}</div>
|
<Tag
|
||||||
|
value={nodeVars.tag}
|
||||||
|
severity="warning"
|
||||||
|
className="py-0 px-[2px] text-[9px] [&_.p-tag-value]:leading-[1.3]"
|
||||||
|
></Tag>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -14,25 +14,26 @@ import { WormholeClassComp } from '@/hooks/Mapper/components/map/components/Worm
|
|||||||
import { UnsplashedSignature } from '@/hooks/Mapper/components/map/components/UnsplashedSignature';
|
import { UnsplashedSignature } from '@/hooks/Mapper/components/map/components/UnsplashedSignature';
|
||||||
import { LocalCounter } from './SolarSystemLocalCounter';
|
import { LocalCounter } from './SolarSystemLocalCounter';
|
||||||
import { KillsCounter } from './SolarSystemKillsCounter';
|
import { KillsCounter } from './SolarSystemKillsCounter';
|
||||||
|
import { TooltipPosition, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
|
||||||
|
import { TooltipSize } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper/utils.ts';
|
||||||
|
|
||||||
|
// let render = 0;
|
||||||
export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>) => {
|
export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>) => {
|
||||||
const nodeVars = useSolarSystemNode(props);
|
const nodeVars = useSolarSystemNode(props);
|
||||||
const { localCounterCharacters } = useLocalCounter(nodeVars);
|
const { localCounterCharacters } = useLocalCounter(nodeVars);
|
||||||
const localKillsCount = useNodeKillsCount(nodeVars.solarSystemId, nodeVars.killsCount);
|
const localKillsCount = useNodeKillsCount(nodeVars.solarSystemId, nodeVars.killsCount);
|
||||||
|
|
||||||
|
// console.log('JOipP', `render ${nodeVars.id}`, render++);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{nodeVars.visible && (
|
{nodeVars.visible && (
|
||||||
<div className={classes.Bookmarks}>
|
<div className={classes.Bookmarks}>
|
||||||
{nodeVars.labelCustom !== '' && (
|
|
||||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.custom)}>
|
|
||||||
<span className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]">{nodeVars.labelCustom}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{nodeVars.isShattered && (
|
{nodeVars.isShattered && (
|
||||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.shattered)}>
|
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.shattered, '!pr-[2px]')}>
|
||||||
<span className={clsx('pi pi-chart-pie', classes.icon)} />
|
<WdTooltipWrapper content="Shattered" position={TooltipPosition.top}>
|
||||||
|
<span className={clsx('block w-[10px] h-[10px]', classes.ShatteredIcon)} />
|
||||||
|
</WdTooltipWrapper>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>)
|
|||||||
<KillsCounter
|
<KillsCounter
|
||||||
killsCount={localKillsCount}
|
killsCount={localKillsCount}
|
||||||
systemId={nodeVars.solarSystemId}
|
systemId={nodeVars.solarSystemId}
|
||||||
size="lg"
|
size={TooltipSize.lg}
|
||||||
killsActivityType={nodeVars.killsActivityType}
|
killsActivityType={nodeVars.killsActivityType}
|
||||||
className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[nodeVars.killsActivityType!])}
|
className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[nodeVars.killsActivityType!])}
|
||||||
>
|
>
|
||||||
@@ -51,6 +52,12 @@ export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>)
|
|||||||
</KillsCounter>
|
</KillsCounter>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{nodeVars.labelCustom !== '' && (
|
||||||
|
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.custom)}>
|
||||||
|
<span className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]">{nodeVars.labelCustom}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{nodeVars.labelsInfo.map(x => (
|
{nodeVars.labelsInfo.map(x => (
|
||||||
<div key={x.id} className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[x.id])}>
|
<div key={x.id} className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[x.id])}>
|
||||||
{x.shortName}
|
{x.shortName}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export enum SOLAR_SYSTEM_CLASS_IDS {
|
|||||||
thera = 12,
|
thera = 12,
|
||||||
c13 = 13,
|
c13 = 13,
|
||||||
sentinel = 14,
|
sentinel = 14,
|
||||||
baribican = 15,
|
barbican = 15,
|
||||||
vidette = 16,
|
vidette = 16,
|
||||||
conflux = 17,
|
conflux = 17,
|
||||||
redoubt = 18,
|
redoubt = 18,
|
||||||
@@ -82,7 +82,7 @@ export const SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS = {
|
|||||||
thera: SOLAR_SYSTEM_CLASS_GROUPS.thera,
|
thera: SOLAR_SYSTEM_CLASS_GROUPS.thera,
|
||||||
c13: SOLAR_SYSTEM_CLASS_GROUPS.c13,
|
c13: SOLAR_SYSTEM_CLASS_GROUPS.c13,
|
||||||
sentinel: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
sentinel: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
||||||
baribican: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
barbican: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
||||||
vidette: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
vidette: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
||||||
conflux: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
conflux: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
||||||
redoubt: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
redoubt: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
||||||
@@ -217,7 +217,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
|
|||||||
wormholeClassID: 14,
|
wormholeClassID: 14,
|
||||||
effectPower: 2,
|
effectPower: 2,
|
||||||
title: 'Class 14 (Sentinel Drifter)',
|
title: 'Class 14 (Sentinel Drifter)',
|
||||||
shortTitle: 'Sentinel',
|
shortTitle: 'Sentinel MZ',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'barbican',
|
id: 'barbican',
|
||||||
@@ -225,7 +225,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
|
|||||||
wormholeClassID: 15,
|
wormholeClassID: 15,
|
||||||
effectPower: 2,
|
effectPower: 2,
|
||||||
title: 'Class 15 (Barbican Drifter)',
|
title: 'Class 15 (Barbican Drifter)',
|
||||||
shortTitle: 'Barbican',
|
shortTitle: 'Liberated Barbican',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'vidette',
|
id: 'vidette',
|
||||||
@@ -233,7 +233,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
|
|||||||
wormholeClassID: 16,
|
wormholeClassID: 16,
|
||||||
effectPower: 2,
|
effectPower: 2,
|
||||||
title: 'Class 16 (Vidette Drifter)',
|
title: 'Class 16 (Vidette Drifter)',
|
||||||
shortTitle: 'Vidette',
|
shortTitle: 'Sanctified Vidette',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'conflux',
|
id: 'conflux',
|
||||||
@@ -241,7 +241,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
|
|||||||
wormholeClassID: 17,
|
wormholeClassID: 17,
|
||||||
effectPower: 2,
|
effectPower: 2,
|
||||||
title: 'Class 17 (Conflux Drifter)',
|
title: 'Class 17 (Conflux Drifter)',
|
||||||
shortTitle: 'Conflux',
|
shortTitle: 'Conflux Eyrie',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'redoubt',
|
id: 'redoubt',
|
||||||
@@ -249,7 +249,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
|
|||||||
wormholeClassID: 18,
|
wormholeClassID: 18,
|
||||||
effectPower: 2,
|
effectPower: 2,
|
||||||
title: 'Class 18 (Redoubt Drifter)',
|
title: 'Class 18 (Redoubt Drifter)',
|
||||||
shortTitle: 'Redoubt',
|
shortTitle: 'Azdaja Redoubt',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'a1',
|
id: 'a1',
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export const isWormholeSpace = (wormholeClassID: number) => {
|
|||||||
case SOLAR_SYSTEM_CLASS_IDS.c6:
|
case SOLAR_SYSTEM_CLASS_IDS.c6:
|
||||||
case SOLAR_SYSTEM_CLASS_IDS.c13:
|
case SOLAR_SYSTEM_CLASS_IDS.c13:
|
||||||
case SOLAR_SYSTEM_CLASS_IDS.thera:
|
case SOLAR_SYSTEM_CLASS_IDS.thera:
|
||||||
case SOLAR_SYSTEM_CLASS_IDS.baribican:
|
case SOLAR_SYSTEM_CLASS_IDS.barbican:
|
||||||
case SOLAR_SYSTEM_CLASS_IDS.vidette:
|
case SOLAR_SYSTEM_CLASS_IDS.vidette:
|
||||||
case SOLAR_SYSTEM_CLASS_IDS.conflux:
|
case SOLAR_SYSTEM_CLASS_IDS.conflux:
|
||||||
case SOLAR_SYSTEM_CLASS_IDS.redoubt:
|
case SOLAR_SYSTEM_CLASS_IDS.redoubt:
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ import { Node, useReactFlow } from 'reactflow';
|
|||||||
import { useCallback, useRef } from 'react';
|
import { useCallback, useRef } from 'react';
|
||||||
import { CommandAddSystems } from '@/hooks/Mapper/types/mapHandlers.ts';
|
import { CommandAddSystems } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||||
import { convertSystem2Node } from '../../helpers';
|
import { convertSystem2Node } from '../../helpers';
|
||||||
|
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
|
||||||
|
|
||||||
export const useMapAddSystems = () => {
|
export const useMapAddSystems = () => {
|
||||||
const rf = useReactFlow();
|
const rf = useReactFlow();
|
||||||
|
|
||||||
|
const { addSystemStatic } = useLoadSystemStatic({ systems: [] });
|
||||||
|
|
||||||
const ref = useRef({ rf });
|
const ref = useRef({ rf });
|
||||||
ref.current = { rf };
|
ref.current = { rf };
|
||||||
|
|
||||||
@@ -13,7 +16,10 @@ export const useMapAddSystems = () => {
|
|||||||
const { rf } = ref.current;
|
const { rf } = ref.current;
|
||||||
const nodes = rf.getNodes();
|
const nodes = rf.getNodes();
|
||||||
|
|
||||||
const prepared: Node[] = systems.filter(x => !nodes.some(y => x.id === y.id)).map(convertSystem2Node);
|
const newSystems = systems.filter(x => !nodes.some(y => x.id === y.id));
|
||||||
|
newSystems.forEach(x => addSystemStatic(x.system_static_info));
|
||||||
|
|
||||||
|
const prepared: Node[] = newSystems.map(convertSystem2Node);
|
||||||
rf.addNodes(prepared);
|
rf.addNodes(prepared);
|
||||||
}, []);
|
}, []);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { MapData, useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
import { MapData, useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
||||||
import { useCallback, useRef } from 'react';
|
|
||||||
import { CommandKillsUpdated, CommandMapUpdated } from '@/hooks/Mapper/types';
|
import { CommandKillsUpdated, CommandMapUpdated } from '@/hooks/Mapper/types';
|
||||||
|
import { useCallback, useRef } from 'react';
|
||||||
|
|
||||||
export const useMapCommands = () => {
|
export const useMapCommands = () => {
|
||||||
const { update } = useMapState();
|
const { update } = useMapState();
|
||||||
@@ -8,13 +8,21 @@ export const useMapCommands = () => {
|
|||||||
const ref = useRef({ update });
|
const ref = useRef({ update });
|
||||||
ref.current = { update };
|
ref.current = { update };
|
||||||
|
|
||||||
const mapUpdated = useCallback(({ hubs }: CommandMapUpdated) => {
|
const mapUpdated = useCallback(({ hubs, system_signatures, kills }: CommandMapUpdated) => {
|
||||||
const out: Partial<MapData> = {};
|
const out: Partial<MapData> = {};
|
||||||
|
|
||||||
if (hubs) {
|
if (hubs) {
|
||||||
out.hubs = hubs;
|
out.hubs = hubs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (system_signatures) {
|
||||||
|
out.systemSignatures = system_signatures;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kills) {
|
||||||
|
out.kills = kills.reduce((acc, x) => ({ ...acc, [x.solar_system_id]: x.kills }), {});
|
||||||
|
}
|
||||||
|
|
||||||
ref.current.update(out);
|
ref.current.update(out);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export const useMapInit = () => {
|
|||||||
return useCallback(
|
return useCallback(
|
||||||
({
|
({
|
||||||
systems,
|
systems,
|
||||||
|
system_signatures,
|
||||||
kills,
|
kills,
|
||||||
connections,
|
connections,
|
||||||
wormholes,
|
wormholes,
|
||||||
@@ -51,6 +52,10 @@ export const useMapInit = () => {
|
|||||||
updateData.systems = systems;
|
updateData.systems = systems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (system_signatures) {
|
||||||
|
updateData.systemSignatures = system_signatures;
|
||||||
|
}
|
||||||
|
|
||||||
if (kills) {
|
if (kills) {
|
||||||
updateData.kills = kills.reduce((acc, x) => ({ ...acc, [x.solar_system_id]: x.kills }), {});
|
updateData.kills = kills.reduce((acc, x) => ({ ...acc, [x.solar_system_id]: x.kills }), {});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useSystemKills } from '../../mapInterface/widgets/SystemKills/hooks/useSystemKills';
|
|
||||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
|
import { useSystemKills } from '@/hooks/Mapper/components/mapInterface/widgets/WSystemKills/hooks/useSystemKills.ts';
|
||||||
|
|
||||||
interface UseKillsCounterProps {
|
interface UseKillsCounterProps {
|
||||||
realSystemId: string;
|
realSystemId: string;
|
||||||
|
|||||||
@@ -13,28 +13,26 @@ interface MapEvent {
|
|||||||
payload?: Kill[];
|
payload?: Kill[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useNodeKillsCount(
|
export function useNodeKillsCount(systemId: number | string, initialKillsCount: number | null): number | null {
|
||||||
systemId: number | string,
|
|
||||||
initialKillsCount: number | null
|
|
||||||
): number | null {
|
|
||||||
const [killsCount, setKillsCount] = useState<number | null>(initialKillsCount);
|
const [killsCount, setKillsCount] = useState<number | null>(initialKillsCount);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setKillsCount(initialKillsCount);
|
setKillsCount(initialKillsCount);
|
||||||
}, [initialKillsCount]);
|
}, [initialKillsCount]);
|
||||||
|
|
||||||
const handleEvent = useCallback((event: MapEvent): boolean => {
|
const handleEvent = useCallback(
|
||||||
if (event.name === Commands.killsUpdated && Array.isArray(event.payload)) {
|
(event: MapEvent): boolean => {
|
||||||
const killForSystem = event.payload.find(
|
if (event.name === Commands.killsUpdated && Array.isArray(event.payload)) {
|
||||||
kill => kill.solar_system_id.toString() === systemId.toString()
|
const killForSystem = event.payload.find(kill => kill.solar_system_id.toString() === systemId.toString());
|
||||||
);
|
if (killForSystem && typeof killForSystem.kills === 'number') {
|
||||||
if (killForSystem && typeof killForSystem.kills === 'number') {
|
setKillsCount(killForSystem.kills);
|
||||||
setKillsCount(killForSystem.kills);
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
return false;
|
||||||
}
|
},
|
||||||
return false;
|
[systemId],
|
||||||
}, [systemId]);
|
);
|
||||||
|
|
||||||
useMapEventListener(handleEvent);
|
useMapEventListener(handleEvent);
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { CharacterTypeRaw, OutCommand, SystemSignature } from '@/hooks/Mapper/ty
|
|||||||
import { useUnsplashedSignatures } from './useUnsplashedSignatures';
|
import { useUnsplashedSignatures } from './useUnsplashedSignatures';
|
||||||
import { useSystemName } from './useSystemName';
|
import { useSystemName } from './useSystemName';
|
||||||
import { LabelInfo, useLabelsInfo } from './useLabelsInfo';
|
import { LabelInfo, useLabelsInfo } from './useLabelsInfo';
|
||||||
|
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
|
||||||
|
|
||||||
function getActivityType(count: number): string {
|
function getActivityType(count: number): string {
|
||||||
if (count <= 5) return 'activityNormal';
|
if (count <= 5) return 'activityNormal';
|
||||||
@@ -43,8 +44,7 @@ export function useLocalCounter(nodeVars: SolarSystemNodeVars) {
|
|||||||
export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarSystemNodeVars {
|
export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarSystemNodeVars {
|
||||||
const { id, data, selected } = props;
|
const { id, data, selected } = props;
|
||||||
const {
|
const {
|
||||||
system_static_info,
|
id: solar_system_id,
|
||||||
system_signatures,
|
|
||||||
locked,
|
locked,
|
||||||
name,
|
name,
|
||||||
tag,
|
tag,
|
||||||
@@ -54,23 +54,26 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
|
|||||||
linked_sig_eve_id: linkedSigEveId = '',
|
linked_sig_eve_id: linkedSigEveId = '',
|
||||||
} = data;
|
} = data;
|
||||||
|
|
||||||
|
const {
|
||||||
|
storedSettings: { interfaceSettings },
|
||||||
|
data: { systemSignatures: mapSystemSignatures },
|
||||||
|
} = useMapRootState();
|
||||||
|
|
||||||
|
const systemStaticInfo = useMemo(() => {
|
||||||
|
return getSystemStaticInfo(solar_system_id)!;
|
||||||
|
}, [solar_system_id]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
system_class,
|
system_class,
|
||||||
security,
|
security,
|
||||||
class_title,
|
class_title,
|
||||||
solar_system_id,
|
|
||||||
statics,
|
statics,
|
||||||
effect_name,
|
effect_name,
|
||||||
region_name,
|
region_name,
|
||||||
region_id,
|
region_id,
|
||||||
is_shattered,
|
is_shattered,
|
||||||
solar_system_name,
|
solar_system_name,
|
||||||
} = system_static_info;
|
} = systemStaticInfo;
|
||||||
|
|
||||||
const {
|
|
||||||
interfaceSettings,
|
|
||||||
data: { systemSignatures: mapSystemSignatures },
|
|
||||||
} = useMapRootState();
|
|
||||||
|
|
||||||
const { isShowUnsplashedSignatures } = interfaceSettings;
|
const { isShowUnsplashedSignatures } = interfaceSettings;
|
||||||
const isTempSystemNameEnabled = useMapGetOption('show_temp_system_name') === 'true';
|
const isTempSystemNameEnabled = useMapGetOption('show_temp_system_name') === 'true';
|
||||||
@@ -95,13 +98,10 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
|
|||||||
|
|
||||||
const visible = useMemo(() => visibleNodes.has(id), [id, visibleNodes]);
|
const visible = useMemo(() => visibleNodes.has(id), [id, visibleNodes]);
|
||||||
|
|
||||||
const systemSigs = useMemo(
|
const systemSigs = useMemo(() => mapSystemSignatures[solar_system_id] || [], [solar_system_id, mapSystemSignatures]);
|
||||||
() => mapSystemSignatures[solar_system_id] || system_signatures,
|
|
||||||
[system_signatures, solar_system_id, mapSystemSignatures],
|
|
||||||
);
|
|
||||||
|
|
||||||
const charactersInSystem = useMemo(() => {
|
const charactersInSystem = useMemo(() => {
|
||||||
return characters.filter(c => c.location?.solar_system_id === solar_system_id && c.online);
|
return characters.filter(c => c.location?.solar_system_id === parseInt(solar_system_id) && c.online);
|
||||||
}, [characters, solar_system_id]);
|
}, [characters, solar_system_id]);
|
||||||
|
|
||||||
const isWormhole = isWormholeSpace(system_class);
|
const isWormhole = isWormholeSpace(system_class);
|
||||||
@@ -121,7 +121,7 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
|
|||||||
isShowLinkedSigId,
|
isShowLinkedSigId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const killsCount = useMemo(() => kills[solar_system_id] ?? null, [kills, solar_system_id]);
|
const killsCount = useMemo(() => kills[parseInt(solar_system_id)] ?? null, [kills, solar_system_id]);
|
||||||
const killsActivityType = killsCount ? getActivityType(killsCount) : null;
|
const killsActivityType = killsCount ? getActivityType(killsCount) : null;
|
||||||
|
|
||||||
const hasUserCharacters = useMemo(
|
const hasUserCharacters = useMemo(
|
||||||
@@ -132,7 +132,7 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
|
|||||||
const dbClick = useDoubleClick(() => {
|
const dbClick = useDoubleClick(() => {
|
||||||
outCommand({
|
outCommand({
|
||||||
type: OutCommand.openSettings,
|
type: OutCommand.openSettings,
|
||||||
data: { system_id: solar_system_id.toString() },
|
data: { system_id: solar_system_id },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -144,10 +144,10 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
|
|||||||
const { systemName, computedTemporaryName, customName } = useSystemName({
|
const { systemName, computedTemporaryName, customName } = useSystemName({
|
||||||
isTempSystemNameEnabled,
|
isTempSystemNameEnabled,
|
||||||
temporary_name,
|
temporary_name,
|
||||||
solar_system_name: solar_system_name || '',
|
|
||||||
isShowLinkedSigIdTempName,
|
isShowLinkedSigIdTempName,
|
||||||
linkedSigPrefix,
|
linkedSigPrefix,
|
||||||
name,
|
name,
|
||||||
|
systemStaticInfo,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { unsplashedLeft, unsplashedRight } = useUnsplashedSignatures(systemSigs, isShowUnsplashedSignatures);
|
const { unsplashedLeft, unsplashedRight } = useUnsplashedSignatures(systemSigs, isShowUnsplashedSignatures);
|
||||||
|
|||||||
@@ -1,30 +1,34 @@
|
|||||||
// useSystemName.ts
|
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import { SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
|
||||||
|
|
||||||
interface UseSystemNameParams {
|
interface UseSystemNameParams {
|
||||||
isTempSystemNameEnabled: boolean;
|
isTempSystemNameEnabled: boolean;
|
||||||
temporary_name?: string | null;
|
temporary_name?: string | null;
|
||||||
solar_system_name: string;
|
|
||||||
isShowLinkedSigIdTempName: boolean;
|
isShowLinkedSigIdTempName: boolean;
|
||||||
linkedSigPrefix: string | null;
|
linkedSigPrefix: string | null;
|
||||||
name?: string | null;
|
name?: string | null;
|
||||||
|
systemStaticInfo: SolarSystemStaticInfoRaw;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useSystemName({
|
export const useSystemName = ({
|
||||||
isTempSystemNameEnabled,
|
isTempSystemNameEnabled,
|
||||||
temporary_name,
|
temporary_name,
|
||||||
solar_system_name,
|
|
||||||
isShowLinkedSigIdTempName,
|
isShowLinkedSigIdTempName,
|
||||||
linkedSigPrefix,
|
linkedSigPrefix,
|
||||||
name,
|
name,
|
||||||
}: UseSystemNameParams) {
|
systemStaticInfo,
|
||||||
|
}: UseSystemNameParams) => {
|
||||||
|
const { solar_system_name = '' } = systemStaticInfo;
|
||||||
|
|
||||||
const computedTemporaryName = useMemo(() => {
|
const computedTemporaryName = useMemo(() => {
|
||||||
if (!isTempSystemNameEnabled) {
|
if (!isTempSystemNameEnabled) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isShowLinkedSigIdTempName && linkedSigPrefix) {
|
if (isShowLinkedSigIdTempName && linkedSigPrefix) {
|
||||||
return temporary_name ? `${linkedSigPrefix}・${temporary_name}` : `${linkedSigPrefix}・${solar_system_name}`;
|
return temporary_name ? `${linkedSigPrefix}・${temporary_name}` : `${linkedSigPrefix}・${solar_system_name}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return temporary_name ?? '';
|
return temporary_name ?? '';
|
||||||
}, [isTempSystemNameEnabled, temporary_name, solar_system_name, isShowLinkedSigIdTempName, linkedSigPrefix]);
|
}, [isTempSystemNameEnabled, temporary_name, solar_system_name, isShowLinkedSigIdTempName, linkedSigPrefix]);
|
||||||
|
|
||||||
@@ -32,6 +36,7 @@ export function useSystemName({
|
|||||||
if (isTempSystemNameEnabled && computedTemporaryName) {
|
if (isTempSystemNameEnabled && computedTemporaryName) {
|
||||||
return computedTemporaryName;
|
return computedTemporaryName;
|
||||||
}
|
}
|
||||||
|
|
||||||
return solar_system_name;
|
return solar_system_name;
|
||||||
}, [isTempSystemNameEnabled, computedTemporaryName, solar_system_name]);
|
}, [isTempSystemNameEnabled, computedTemporaryName, solar_system_name]);
|
||||||
|
|
||||||
@@ -39,11 +44,13 @@ export function useSystemName({
|
|||||||
if (isTempSystemNameEnabled && computedTemporaryName && name) {
|
if (isTempSystemNameEnabled && computedTemporaryName && name) {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (solar_system_name !== name && name) {
|
if (solar_system_name !== name && name) {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}, [isTempSystemNameEnabled, computedTemporaryName, name, solar_system_name]);
|
}, [isTempSystemNameEnabled, computedTemporaryName, name, solar_system_name]);
|
||||||
|
|
||||||
return { systemName, computedTemporaryName, customName };
|
return { systemName, computedTemporaryName, customName };
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -542,48 +542,32 @@
|
|||||||
background-color: #d10600;
|
background-color: #d10600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-flow {
|
|
||||||
color: var(--text-color);
|
|
||||||
|
|
||||||
&__pane {
|
.react-flow__minimap-node {
|
||||||
cursor: auto;
|
fill: #ffb03a;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__minimap {
|
.react-flow__minimap {
|
||||||
background-color: rgba(66, 66, 66, 1);
|
border: 1px solid #282828;
|
||||||
opacity: 0.7;
|
border-radius: 4px;
|
||||||
border: 1px solid #2f2f2f;
|
background-color: rgb(47 37 37) !important;
|
||||||
border-radius: 4px;
|
overflow: hidden;
|
||||||
overflow: hidden;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&__minimap-mask {
|
.react-flow__minimap-mask {
|
||||||
fill: rgba(28, 28, 28, 0.75);
|
stroke-width: 2px;
|
||||||
}
|
fill: rgba(0, 0, 0, 0.5);
|
||||||
|
mix-blend-mode: overlay;
|
||||||
|
}
|
||||||
|
|
||||||
&__controls {
|
.react-flow__minimap-mask {
|
||||||
filter: brightness(1.5);
|
stroke-width: 2px;
|
||||||
}
|
fill: rgb(0 0 0 / 50%) !important;
|
||||||
|
mix-blend-mode: inherit;
|
||||||
&__minimap-node {
|
opacity: 1;
|
||||||
fill: #ffb03a;
|
stroke: #fff;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.context-menu-active {
|
.context-menu-active {
|
||||||
background-color: rgba(131, 131, 131, 0.33);
|
background-color: rgba(131, 131, 131, 0.33);
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-dialog {
|
|
||||||
.p-dialog-header {
|
|
||||||
height: 40px;
|
|
||||||
padding: 1rem;
|
|
||||||
padding-right: 10px !important;
|
|
||||||
}
|
|
||||||
.p-dialog-title {
|
|
||||||
font-size: 1rem !important;
|
|
||||||
}
|
|
||||||
.p-dialog-header-icons {
|
|
||||||
align-self: initial !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -43,7 +43,7 @@ export const Comments = ({}: CommentsProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-1 mt-1 whitespace-nowrap overflow-auto text-ellipsis custom-scrollbar">
|
<div className="flex flex-col gap-1 whitespace-nowrap overflow-auto text-ellipsis custom-scrollbar">
|
||||||
{commentsList.map(({ id, text, updated_at, characterEveId }) => (
|
{commentsList.map(({ id, text, updated_at, characterEveId }) => (
|
||||||
<MarkdownComment key={id} text={text} time={updated_at} characterEveId={characterEveId} id={id} />
|
<MarkdownComment key={id} text={text} time={updated_at} characterEveId={characterEveId} id={id} />
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ const stopEventPropagationPlugin = ViewPlugin.fromClass(
|
|||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.pasteHandler = (event: Event) => {
|
this.pasteHandler = (event: Event) => {
|
||||||
console.log('Paste done in editor, stopping global listeners.');
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
import { useCallback, useMemo, useRef } from 'react';
|
|
||||||
import { Dialog } from 'primereact/dialog';
|
import { Dialog } from 'primereact/dialog';
|
||||||
|
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||||
|
|
||||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
|
||||||
import { CommandLinkSignatureToSystem, SignatureGroup, SystemSignature, TimeStatus } from '@/hooks/Mapper/types';
|
|
||||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
|
||||||
import { SystemSignaturesContent } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent';
|
|
||||||
import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo';
|
|
||||||
import { getWhSize } from '@/hooks/Mapper/helpers/getWhSize';
|
|
||||||
import { useSystemInfo } from '@/hooks/Mapper/components/hooks';
|
import { useSystemInfo } from '@/hooks/Mapper/components/hooks';
|
||||||
import {
|
import {
|
||||||
SOLAR_SYSTEM_CLASS_IDS,
|
SOLAR_SYSTEM_CLASS_IDS,
|
||||||
SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS,
|
SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS,
|
||||||
WORMHOLES_ADDITIONAL_INFO_BY_SHORT_NAME,
|
WORMHOLES_ADDITIONAL_INFO_BY_SHORT_NAME,
|
||||||
} from '@/hooks/Mapper/components/map/constants.ts';
|
} from '@/hooks/Mapper/components/map/constants.ts';
|
||||||
import { K162_TYPES_MAP } from '@/hooks/Mapper/constants.ts';
|
|
||||||
import {
|
import {
|
||||||
SETTINGS_KEYS,
|
SETTINGS_KEYS,
|
||||||
SignatureSettingsType,
|
SignatureSettingsType,
|
||||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||||
|
import { SystemSignaturesContent } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent';
|
||||||
|
import { K162_TYPES_MAP } from '@/hooks/Mapper/constants.ts';
|
||||||
|
import { getWhSize } from '@/hooks/Mapper/helpers/getWhSize';
|
||||||
|
import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo';
|
||||||
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
|
import { CommandLinkSignatureToSystem, SignatureGroup, SystemSignature, TimeStatus } from '@/hooks/Mapper/types';
|
||||||
|
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||||
|
|
||||||
const K162_SIGNATURE_TYPE = WORMHOLES_ADDITIONAL_INFO_BY_SHORT_NAME['K162'].shortName;
|
const K162_SIGNATURE_TYPE = WORMHOLES_ADDITIONAL_INFO_BY_SHORT_NAME['K162'].shortName;
|
||||||
|
|
||||||
@@ -49,7 +49,9 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
|
|||||||
ref.current = { outCommand };
|
ref.current = { outCommand };
|
||||||
|
|
||||||
// Get system info for the target system
|
// Get system info for the target system
|
||||||
const { staticInfo: targetSystemInfo } = useSystemInfo({ systemId: `${data.solar_system_target}` });
|
const { staticInfo: targetSystemInfo, dynamicInfo: targetSystemDynamicInfo } = useSystemInfo({
|
||||||
|
systemId: `${data.solar_system_target}`,
|
||||||
|
});
|
||||||
|
|
||||||
// Get the system class group for the target system
|
// Get the system class group for the target system
|
||||||
const targetSystemClassGroup = useMemo(() => {
|
const targetSystemClassGroup = useMemo(() => {
|
||||||
@@ -160,6 +162,12 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
|
|||||||
[data, setVisible, wormholes],
|
[data, setVisible, wormholes],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!targetSystemDynamicInfo) {
|
||||||
|
handleHide();
|
||||||
|
}
|
||||||
|
}, [targetSystemDynamicInfo]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
header="Select signature to link"
|
header="Select signature to link"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { OutCommand } from '@/hooks/Mapper/types';
|
|||||||
import { IconField } from 'primereact/iconfield';
|
import { IconField } from 'primereact/iconfield';
|
||||||
import { TooltipPosition, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
import { TooltipPosition, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
||||||
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager.ts';
|
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager.ts';
|
||||||
|
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
|
||||||
|
|
||||||
interface SystemSettingsDialog {
|
interface SystemSettingsDialog {
|
||||||
systemId: string;
|
systemId: string;
|
||||||
@@ -26,6 +27,7 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
|
|||||||
const isTempSystemNameEnabled = useMapGetOption('show_temp_system_name') === 'true';
|
const isTempSystemNameEnabled = useMapGetOption('show_temp_system_name') === 'true';
|
||||||
|
|
||||||
const system = getSystemById(systems, systemId);
|
const system = getSystemById(systems, systemId);
|
||||||
|
const systemStaticInfo = getSystemStaticInfo(systemId);
|
||||||
|
|
||||||
const [name, setName] = useState('');
|
const [name, setName] = useState('');
|
||||||
const [label, setLabel] = useState('');
|
const [label, setLabel] = useState('');
|
||||||
@@ -33,11 +35,11 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
|
|||||||
const [description, setDescription] = useState('');
|
const [description, setDescription] = useState('');
|
||||||
const inputRef = useRef<HTMLInputElement>();
|
const inputRef = useRef<HTMLInputElement>();
|
||||||
|
|
||||||
const ref = useRef({ name, description, temporaryName, label, outCommand, systemId, system });
|
const ref = useRef({ name, description, temporaryName, label, outCommand, systemId, system, systemStaticInfo });
|
||||||
ref.current = { name, description, label, temporaryName, outCommand, systemId, system };
|
ref.current = { name, description, label, temporaryName, outCommand, systemId, system, systemStaticInfo };
|
||||||
|
|
||||||
const handleSave = useCallback(() => {
|
const handleSave = useCallback(() => {
|
||||||
const { name, description, label, temporaryName, outCommand, systemId, system } = ref.current;
|
const { name, description, label, temporaryName, outCommand, systemId, system, systemStaticInfo } = ref.current;
|
||||||
|
|
||||||
const outLabel = new LabelsManager(system?.labels ?? '');
|
const outLabel = new LabelsManager(system?.labels ?? '');
|
||||||
outLabel.updateCustomLabel(label);
|
outLabel.updateCustomLabel(label);
|
||||||
@@ -62,7 +64,7 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
|
|||||||
type: OutCommand.updateSystemName,
|
type: OutCommand.updateSystemName,
|
||||||
data: {
|
data: {
|
||||||
system_id: systemId,
|
system_id: systemId,
|
||||||
value: name.trim() || system?.system_static_info.solar_system_name,
|
value: name.trim() || systemStaticInfo?.solar_system_name,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -78,11 +80,11 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
|
|||||||
}, [setVisible]);
|
}, [setVisible]);
|
||||||
|
|
||||||
const handleResetSystemName = useCallback(() => {
|
const handleResetSystemName = useCallback(() => {
|
||||||
const { system } = ref.current;
|
const { systemStaticInfo } = ref.current;
|
||||||
if (!system) {
|
if (!systemStaticInfo) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setName(system.system_static_info.solar_system_name);
|
setName(systemStaticInfo.solar_system_name);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onShow = useCallback(() => {
|
const onShow = useCallback(() => {
|
||||||
@@ -130,7 +132,7 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
|
|||||||
<label htmlFor="username">Custom name</label>
|
<label htmlFor="username">Custom name</label>
|
||||||
|
|
||||||
<IconField>
|
<IconField>
|
||||||
{name !== system?.system_static_info.solar_system_name && (
|
{name !== systemStaticInfo?.solar_system_name && (
|
||||||
<WdImgButton
|
<WdImgButton
|
||||||
className="pi pi-undo"
|
className="pi pi-undo"
|
||||||
textSize={WdImageSize.large}
|
textSize={WdImageSize.large}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
.root {
|
.root {
|
||||||
padding-bottom: 5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Header {
|
.Header {
|
||||||
|
|||||||
@@ -2,14 +2,15 @@ import React from 'react';
|
|||||||
|
|
||||||
import classes from './Widget.module.scss';
|
import classes from './Widget.module.scss';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import { WithChildren } from '@/hooks/Mapper/types/common.ts';
|
||||||
|
|
||||||
export interface WidgetProps {
|
export type WidgetProps = {
|
||||||
label: React.ReactNode | string;
|
label: React.ReactNode | string;
|
||||||
windowId?: string;
|
windowId?: string;
|
||||||
children?: React.ReactNode;
|
contentClassName?: string;
|
||||||
}
|
} & WithChildren;
|
||||||
|
|
||||||
export const Widget = ({ label, children, windowId }: WidgetProps) => {
|
export const Widget = ({ label, children, windowId, contentClassName }: WidgetProps) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-window-id={windowId}
|
data-window-id={windowId}
|
||||||
@@ -34,7 +35,7 @@ export const Widget = ({ label, children, windowId }: WidgetProps) => {
|
|||||||
{label}
|
{label}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={clsx(classes.Content, 'overflow-auto', 'bg-opacity-5 custom-scrollbar')}
|
className={clsx(classes.Content, 'overflow-auto', 'bg-opacity-5 custom-scrollbar', contentClassName)}
|
||||||
style={{ flexGrow: 1 }}
|
style={{ flexGrow: 1 }}
|
||||||
onContextMenu={e => {
|
onContextMenu={e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { WindowProps } from '@/hooks/Mapper/components/ui-kit/WindowManager/types.ts';
|
import { WindowProps } from '@/hooks/Mapper/components/ui-kit/WindowManager/types.ts';
|
||||||
import {
|
import {
|
||||||
|
CommentsWidget,
|
||||||
LocalCharacters,
|
LocalCharacters,
|
||||||
RoutesWidget,
|
|
||||||
SystemInfo,
|
SystemInfo,
|
||||||
SystemSignatures,
|
SystemSignatures,
|
||||||
SystemStructures,
|
SystemStructures,
|
||||||
SystemKills,
|
WRoutesPublic,
|
||||||
|
WRoutesUser,
|
||||||
|
WSystemKills,
|
||||||
} from '@/hooks/Mapper/components/mapInterface/widgets';
|
} from '@/hooks/Mapper/components/mapInterface/widgets';
|
||||||
import { CommentsWidget } from '@/hooks/Mapper/components/mapInterface/widgets/CommentsWidget';
|
|
||||||
|
|
||||||
export const CURRENT_WINDOWS_VERSION = 9;
|
export const CURRENT_WINDOWS_VERSION = 9;
|
||||||
export const WINDOWS_LOCAL_STORE_KEY = 'windows:settings:v2';
|
export const WINDOWS_LOCAL_STORE_KEY = 'windows:settings:v2';
|
||||||
@@ -20,6 +21,7 @@ export enum WidgetsIds {
|
|||||||
structures = 'structures',
|
structures = 'structures',
|
||||||
kills = 'kills',
|
kills = 'kills',
|
||||||
comments = 'comments',
|
comments = 'comments',
|
||||||
|
userRoutes = 'userRoutes',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const STORED_VISIBLE_WIDGETS_DEFAULT = [
|
export const STORED_VISIBLE_WIDGETS_DEFAULT = [
|
||||||
@@ -56,7 +58,14 @@ export const DEFAULT_WIDGETS: WindowProps[] = [
|
|||||||
position: { x: 10, y: 530 },
|
position: { x: 10, y: 530 },
|
||||||
size: { width: 510, height: 200 },
|
size: { width: 510, height: 200 },
|
||||||
zIndex: 0,
|
zIndex: 0,
|
||||||
content: () => <RoutesWidget />,
|
content: () => <WRoutesPublic />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: WidgetsIds.userRoutes,
|
||||||
|
position: { x: 10, y: 530 },
|
||||||
|
size: { width: 510, height: 200 },
|
||||||
|
zIndex: 0,
|
||||||
|
content: () => <WRoutesUser />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: WidgetsIds.structures,
|
id: WidgetsIds.structures,
|
||||||
@@ -70,7 +79,7 @@ export const DEFAULT_WIDGETS: WindowProps[] = [
|
|||||||
position: { x: 270, y: 730 },
|
position: { x: 270, y: 730 },
|
||||||
size: { width: 510, height: 200 },
|
size: { width: 510, height: 200 },
|
||||||
zIndex: 0,
|
zIndex: 0,
|
||||||
content: () => <SystemKills />,
|
content: () => <WSystemKills />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: WidgetsIds.comments,
|
id: WidgetsIds.comments,
|
||||||
@@ -103,6 +112,10 @@ export const WIDGETS_CHECKBOXES_PROPS: WidgetsCheckboxesType = [
|
|||||||
id: WidgetsIds.routes,
|
id: WidgetsIds.routes,
|
||||||
label: 'Routes',
|
label: 'Routes',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: WidgetsIds.userRoutes,
|
||||||
|
label: 'User Routes',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: WidgetsIds.structures,
|
id: WidgetsIds.structures,
|
||||||
label: 'Structures',
|
label: 'Structures',
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export const CommentsWidget = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Widget
|
<Widget
|
||||||
|
contentClassName="my-1"
|
||||||
label={
|
label={
|
||||||
<div ref={containerRef} className="flex justify-between items-center gap-1 text-xs w-full">
|
<div ref={containerRef} className="flex justify-between items-center gap-1 text-xs w-full">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export const LocalCharactersItemTemplate = ({ showShipName, ...options }: LocalC
|
|||||||
)}
|
)}
|
||||||
style={{ height: `${options.props.itemSize}px` }}
|
style={{ height: `${options.props.itemSize}px` }}
|
||||||
>
|
>
|
||||||
<CharacterCard showShipName={showShipName} {...options} />
|
<CharacterCard showShipName={showShipName} showTicker {...options} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,79 +1,34 @@
|
|||||||
import React, { createContext, useContext, useEffect } from 'react';
|
import React, { createContext, forwardRef, useContext, useImperativeHandle, useState } from 'react';
|
||||||
import { ContextStoreDataUpdate, useContextStore } from '@/hooks/Mapper/utils';
|
import {
|
||||||
import { SESSION_KEY } from '@/hooks/Mapper/constants.ts';
|
RoutesImperativeHandle,
|
||||||
|
RoutesProviderInnerProps,
|
||||||
|
RoutesWidgetProps,
|
||||||
|
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/types.ts';
|
||||||
|
|
||||||
export type RoutesType = {
|
type MapProviderProps = {
|
||||||
path_type: 'shortest' | 'secure' | 'insecure';
|
|
||||||
include_mass_crit: boolean;
|
|
||||||
include_eol: boolean;
|
|
||||||
include_frig: boolean;
|
|
||||||
include_cruise: boolean;
|
|
||||||
include_thera: boolean;
|
|
||||||
avoid_wormholes: boolean;
|
|
||||||
avoid_pochven: boolean;
|
|
||||||
avoid_edencom: boolean;
|
|
||||||
avoid_triglavian: boolean;
|
|
||||||
avoid: number[];
|
|
||||||
};
|
|
||||||
|
|
||||||
interface MapProviderProps {
|
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
} & RoutesWidgetProps;
|
||||||
|
|
||||||
export const DEFAULT_SETTINGS: RoutesType = {
|
const RoutesContext = createContext<RoutesProviderInnerProps>({
|
||||||
path_type: 'shortest',
|
|
||||||
include_mass_crit: true,
|
|
||||||
include_eol: true,
|
|
||||||
include_frig: true,
|
|
||||||
include_cruise: true,
|
|
||||||
include_thera: true,
|
|
||||||
avoid_wormholes: false,
|
|
||||||
avoid_pochven: false,
|
|
||||||
avoid_edencom: false,
|
|
||||||
avoid_triglavian: false,
|
|
||||||
avoid: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface MapContextProps {
|
|
||||||
update: ContextStoreDataUpdate<RoutesType>;
|
|
||||||
data: RoutesType;
|
|
||||||
}
|
|
||||||
|
|
||||||
const RoutesContext = createContext<MapContextProps>({
|
|
||||||
update: () => {},
|
update: () => {},
|
||||||
data: { ...DEFAULT_SETTINGS },
|
// @ts-ignore
|
||||||
|
data: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const RoutesProvider: React.FC<MapProviderProps> = ({ children }) => {
|
export const RoutesProvider = forwardRef<RoutesImperativeHandle, MapProviderProps>(({ children, ...props }, ref) => {
|
||||||
const { update, ref } = useContextStore<RoutesType>(
|
const [loading, setLoading] = useState(false);
|
||||||
{ ...DEFAULT_SETTINGS },
|
|
||||||
{
|
useImperativeHandle(ref, () => ({
|
||||||
onAfterAUpdate: values => {
|
stopLoading() {
|
||||||
localStorage.setItem(SESSION_KEY.routes, JSON.stringify(values));
|
setLoading(false);
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
}));
|
||||||
|
|
||||||
useEffect(() => {
|
return <RoutesContext.Provider value={{ ...props, loading, setLoading }}>{children}</RoutesContext.Provider>;
|
||||||
const items = localStorage.getItem(SESSION_KEY.routes);
|
});
|
||||||
if (items) {
|
RoutesProvider.displayName = 'RoutesProvider';
|
||||||
update(JSON.parse(items));
|
|
||||||
}
|
|
||||||
}, [update]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<RoutesContext.Provider
|
|
||||||
value={{
|
|
||||||
update,
|
|
||||||
data: ref,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</RoutesContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useRouteProvider = () => {
|
export const useRouteProvider = () => {
|
||||||
const context = useContext<MapContextProps>(RoutesContext);
|
const context = useContext<RoutesProviderInnerProps>(RoutesContext);
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
|
|||||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
import {
|
import {
|
||||||
LayoutEventBlocker,
|
LayoutEventBlocker,
|
||||||
|
LoadingWrapper,
|
||||||
SystemViewStandalone,
|
SystemViewStandalone,
|
||||||
TooltipPosition,
|
TooltipPosition,
|
||||||
WdCheckbox,
|
WdCheckbox,
|
||||||
WdImgButton,
|
WdImgButton,
|
||||||
} from '@/hooks/Mapper/components/ui-kit';
|
} from '@/hooks/Mapper/components/ui-kit';
|
||||||
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
|
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
|
||||||
import { MouseEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { forwardRef, MouseEvent, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { getSystemById } from '@/hooks/Mapper/helpers/getSystemById.ts';
|
import { getSystemById } from '@/hooks/Mapper/helpers/getSystemById.ts';
|
||||||
import classes from './RoutesWidget.module.scss';
|
import classes from './RoutesWidget.module.scss';
|
||||||
import { useLoadRoutes } from './hooks';
|
import { useLoadRoutes } from './hooks';
|
||||||
@@ -25,7 +26,10 @@ import {
|
|||||||
AddSystemDialog,
|
AddSystemDialog,
|
||||||
SearchOnSubmitCallback,
|
SearchOnSubmitCallback,
|
||||||
} from '@/hooks/Mapper/components/mapInterface/components/AddSystemDialog';
|
} from '@/hooks/Mapper/components/mapInterface/components/AddSystemDialog';
|
||||||
import { OutCommand } from '@/hooks/Mapper/types';
|
import {
|
||||||
|
RoutesImperativeHandle,
|
||||||
|
RoutesWidgetProps,
|
||||||
|
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/types.ts';
|
||||||
|
|
||||||
const sortByDist = (a: Route, b: Route) => {
|
const sortByDist = (a: Route, b: Route) => {
|
||||||
const distA = a.has_connection ? a.systems?.length || 0 : Infinity;
|
const distA = a.has_connection ? a.systems?.length || 0 : Infinity;
|
||||||
@@ -36,19 +40,16 @@ const sortByDist = (a: Route, b: Route) => {
|
|||||||
|
|
||||||
export const RoutesWidgetContent = () => {
|
export const RoutesWidgetContent = () => {
|
||||||
const {
|
const {
|
||||||
data: { selectedSystems, hubs = [], systems, routes },
|
data: { selectedSystems, systems, isSubscriptionActive },
|
||||||
outCommand,
|
|
||||||
} = useMapRootState();
|
} = useMapRootState();
|
||||||
|
const { hubs = [], routesList, isRestricted } = useRouteProvider();
|
||||||
|
|
||||||
const [systemId] = selectedSystems;
|
const [systemId] = selectedSystems;
|
||||||
|
|
||||||
const { loading } = useLoadRoutes();
|
const { loading } = useLoadRoutes();
|
||||||
|
|
||||||
const { systems: systemStatics, loadSystems, lastUpdateKey } = useLoadSystemStatic({ systems: hubs ?? [] });
|
const { systems: systemStatics, loadSystems, lastUpdateKey } = useLoadSystemStatic({ systems: hubs ?? [] });
|
||||||
const { open, ...systemCtxProps } = useContextMenuSystemInfoHandlers({
|
const { open, ...systemCtxProps } = useContextMenuSystemInfoHandlers();
|
||||||
outCommand,
|
|
||||||
hubs,
|
|
||||||
});
|
|
||||||
|
|
||||||
const preparedHubs = useMemo(() => {
|
const preparedHubs = useMemo(() => {
|
||||||
return hubs.map(x => {
|
return hubs.map(x => {
|
||||||
@@ -61,20 +62,20 @@ export const RoutesWidgetContent = () => {
|
|||||||
|
|
||||||
const preparedRoutes: Route[] = useMemo(() => {
|
const preparedRoutes: Route[] = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
routes?.routes
|
routesList?.routes
|
||||||
.sort(sortByDist)
|
.sort(sortByDist)
|
||||||
.filter(x => x.destination.toString() !== systemId)
|
// .filter(x => x.destination.toString() !== systemId)
|
||||||
.map(route => ({
|
.map(route => ({
|
||||||
...route,
|
...route,
|
||||||
mapped_systems:
|
mapped_systems:
|
||||||
route.systems?.map(solar_system_id =>
|
route.systems?.map(solar_system_id =>
|
||||||
routes?.systems_static_data.find(
|
routesList?.systems_static_data.find(
|
||||||
system_static_data => system_static_data.solar_system_id === solar_system_id,
|
system_static_data => system_static_data.solar_system_id === solar_system_id,
|
||||||
),
|
),
|
||||||
) ?? [],
|
) ?? [],
|
||||||
})) ?? []
|
})) ?? []
|
||||||
);
|
);
|
||||||
}, [routes?.routes, routes?.systems_static_data, systemId]);
|
}, [routesList?.routes, routesList?.systems_static_data, systemId]);
|
||||||
|
|
||||||
const refData = useRef({ open, loadSystems, preparedRoutes });
|
const refData = useRef({ open, loadSystems, preparedRoutes });
|
||||||
refData.current = { open, loadSystems, preparedRoutes };
|
refData.current = { open, loadSystems, preparedRoutes };
|
||||||
@@ -97,9 +98,13 @@ export const RoutesWidgetContent = () => {
|
|||||||
[handleClick],
|
[handleClick],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (loading) {
|
if (isRestricted && !isSubscriptionActive) {
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full flex justify-center items-center select-none text-center">Loading routes...</div>
|
<div className="w-full h-full flex items-center justify-center">
|
||||||
|
<span className="select-none text-center text-stone-400/80 text-sm">
|
||||||
|
User Routes available with 'Active' map subscription only (contact map administrators)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +122,7 @@ export const RoutesWidgetContent = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{systemId !== undefined && routes && (
|
<LoadingWrapper loading={loading}>
|
||||||
<div className={clsx(classes.RoutesGrid, 'px-2 py-2')}>
|
<div className={clsx(classes.RoutesGrid, 'px-2 py-2')}>
|
||||||
{preparedRoutes.map(route => {
|
{preparedRoutes.map(route => {
|
||||||
const sys = preparedHubs.find(x => x.solar_system_id === route.destination)!;
|
const sys = preparedHubs.find(x => x.solar_system_id === route.destination)!;
|
||||||
@@ -132,7 +137,11 @@ export const RoutesWidgetContent = () => {
|
|||||||
<WdImgButton
|
<WdImgButton
|
||||||
className={clsx(PrimeIcons.BARS, classes.RemoveBtn)}
|
className={clsx(PrimeIcons.BARS, classes.RemoveBtn)}
|
||||||
onClick={e => handleClick(e, route.destination.toString())}
|
onClick={e => handleClick(e, route.destination.toString())}
|
||||||
tooltip={{ content: 'Click here to open system menu', position: TooltipPosition.top, offset: 10 }}
|
tooltip={{
|
||||||
|
content: 'Click here to open system menu',
|
||||||
|
position: TooltipPosition.top,
|
||||||
|
offset: 10,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SystemViewStandalone
|
<SystemViewStandalone
|
||||||
@@ -151,7 +160,7 @@ export const RoutesWidgetContent = () => {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</LoadingWrapper>
|
||||||
|
|
||||||
<ContextMenuSystemInfo
|
<ContextMenuSystemInfo
|
||||||
hubs={hubs}
|
hubs={hubs}
|
||||||
@@ -165,15 +174,13 @@ export const RoutesWidgetContent = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RoutesWidgetComp = () => {
|
type RoutesWidgetCompProps = {
|
||||||
const [routeSettingsVisible, setRouteSettingsVisible] = useState(false);
|
title: ReactNode | string;
|
||||||
const { data, update } = useRouteProvider();
|
};
|
||||||
const {
|
|
||||||
data: { hubs = [] },
|
|
||||||
outCommand,
|
|
||||||
} = useMapRootState();
|
|
||||||
|
|
||||||
const preparedHubs = useMemo(() => hubs.map(x => parseInt(x)), [hubs]);
|
export const RoutesWidgetComp = ({ title }: RoutesWidgetCompProps) => {
|
||||||
|
const [routeSettingsVisible, setRouteSettingsVisible] = useState(false);
|
||||||
|
const { data, update, addHubCommand } = useRouteProvider();
|
||||||
|
|
||||||
const isSecure = data.path_type === 'secure';
|
const isSecure = data.path_type === 'secure';
|
||||||
const handleSecureChange = useCallback(() => {
|
const handleSecureChange = useCallback(() => {
|
||||||
@@ -190,24 +197,15 @@ export const RoutesWidgetComp = () => {
|
|||||||
const onAddSystem = useCallback(() => setOpenAddSystem(true), []);
|
const onAddSystem = useCallback(() => setOpenAddSystem(true), []);
|
||||||
|
|
||||||
const handleSubmitAddSystem: SearchOnSubmitCallback = useCallback(
|
const handleSubmitAddSystem: SearchOnSubmitCallback = useCallback(
|
||||||
async item => {
|
async item => addHubCommand(item.value.toString()),
|
||||||
if (preparedHubs.includes(item.value)) {
|
[addHubCommand],
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await outCommand({
|
|
||||||
type: OutCommand.addHub,
|
|
||||||
data: { system_id: item.value },
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[hubs, outCommand],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Widget
|
<Widget
|
||||||
label={
|
label={
|
||||||
<div className="flex justify-between items-center text-xs w-full" ref={ref}>
|
<div className="flex justify-between items-center text-xs w-full" ref={ref}>
|
||||||
<span className="select-none">Routes</span>
|
<span className="select-none">{title}</span>
|
||||||
<LayoutEventBlocker className="flex items-center gap-2">
|
<LayoutEventBlocker className="flex items-center gap-2">
|
||||||
<WdImgButton
|
<WdImgButton
|
||||||
className={PrimeIcons.PLUS_CIRCLE}
|
className={PrimeIcons.PLUS_CIRCLE}
|
||||||
@@ -231,6 +229,7 @@ export const RoutesWidgetComp = () => {
|
|||||||
className={PrimeIcons.SLIDERS_H}
|
className={PrimeIcons.SLIDERS_H}
|
||||||
onClick={() => setRouteSettingsVisible(true)}
|
onClick={() => setRouteSettingsVisible(true)}
|
||||||
tooltip={{
|
tooltip={{
|
||||||
|
position: TooltipPosition.top,
|
||||||
content: 'Click here to open Routes settings',
|
content: 'Click here to open Routes settings',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -251,10 +250,13 @@ export const RoutesWidgetComp = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RoutesWidget = () => {
|
export const RoutesWidget = forwardRef<RoutesImperativeHandle, RoutesWidgetProps & RoutesWidgetCompProps>(
|
||||||
return (
|
({ title, ...props }, ref) => {
|
||||||
<RoutesProvider>
|
return (
|
||||||
<RoutesWidgetComp />
|
<RoutesProvider {...props} ref={ref}>
|
||||||
</RoutesProvider>
|
<RoutesWidgetComp title={title} />
|
||||||
);
|
</RoutesProvider>
|
||||||
};
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
RoutesWidget.displayName = 'RoutesWidget';
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
import { OutCommand } from '@/hooks/Mapper/types';
|
|
||||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
import {
|
import { useRouteProvider } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesProvider.tsx';
|
||||||
RoutesType,
|
import { RoutesType } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||||
useRouteProvider,
|
|
||||||
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesProvider.tsx';
|
|
||||||
|
|
||||||
function usePrevious<T>(value: T): T | undefined {
|
function usePrevious<T>(value: T): T | undefined {
|
||||||
const ref = useRef<T>();
|
const ref = useRef<T>();
|
||||||
@@ -17,12 +14,10 @@ function usePrevious<T>(value: T): T | undefined {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const useLoadRoutes = () => {
|
export const useLoadRoutes = () => {
|
||||||
const [loading, setLoading] = useState(false);
|
const { data: routesSettings, loadRoutesCommand, hubs, routesList, loading, setLoading } = useRouteProvider();
|
||||||
const { data: routesSettings } = useRouteProvider();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
outCommand,
|
data: { selectedSystems, systems, connections },
|
||||||
data: { selectedSystems, hubs, systems, connections },
|
|
||||||
} = useMapRootState();
|
} = useMapRootState();
|
||||||
|
|
||||||
const prevSys = usePrevious(systems);
|
const prevSys = usePrevious(systems);
|
||||||
@@ -31,17 +26,16 @@ export const useLoadRoutes = () => {
|
|||||||
|
|
||||||
const loadRoutes = useCallback(
|
const loadRoutes = useCallback(
|
||||||
(systemId: string, routesSettings: RoutesType) => {
|
(systemId: string, routesSettings: RoutesType) => {
|
||||||
outCommand({
|
loadRoutesCommand(systemId, routesSettings);
|
||||||
type: OutCommand.getRoutes,
|
setLoading(true);
|
||||||
data: {
|
|
||||||
system_id: systemId,
|
|
||||||
routes_settings: routesSettings,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[outCommand],
|
[loadRoutesCommand],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLoading(false);
|
||||||
|
}, [routesList]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedSystems.length !== 1) {
|
if (selectedSystems.length !== 1) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { RoutesType } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||||
|
import { RoutesList } from '@/hooks/Mapper/types/routes.ts';
|
||||||
|
|
||||||
|
export type LoadRoutesCommand = (systemId: string, routesSettings: RoutesType) => Promise<void>;
|
||||||
|
export type AddHubCommand = (systemId: string) => Promise<void>;
|
||||||
|
export type ToggleHubCommand = (systemId: string) => Promise<void>;
|
||||||
|
|
||||||
|
export type RoutesWidgetProps = {
|
||||||
|
data: RoutesType;
|
||||||
|
update: (d: RoutesType) => void;
|
||||||
|
hubs: string[];
|
||||||
|
routesList: RoutesList | undefined;
|
||||||
|
|
||||||
|
loadRoutesCommand: LoadRoutesCommand;
|
||||||
|
addHubCommand: AddHubCommand;
|
||||||
|
toggleHubCommand: ToggleHubCommand;
|
||||||
|
isRestricted?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RoutesProviderInnerProps = RoutesWidgetProps & {
|
||||||
|
loading: boolean;
|
||||||
|
setLoading(loading: boolean): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RoutesImperativeHandle = {
|
||||||
|
stopLoading: () => void;
|
||||||
|
};
|
||||||
@@ -1,24 +1,24 @@
|
|||||||
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
|
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
|
||||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
import { LayoutEventBlocker, SystemView, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
import { LayoutEventBlocker, SystemView, TooltipPosition, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
||||||
import { SystemInfoContent } from './SystemInfoContent';
|
import { SystemInfoContent } from './SystemInfoContent';
|
||||||
import { PrimeIcons } from 'primereact/api';
|
import { PrimeIcons } from 'primereact/api';
|
||||||
import { useState, useCallback } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { SystemSettingsDialog } from '@/hooks/Mapper/components/mapInterface/components/SystemSettingsDialog/SystemSettingsDialog.tsx';
|
import { SystemSettingsDialog } from '@/hooks/Mapper/components/mapInterface/components/SystemSettingsDialog/SystemSettingsDialog.tsx';
|
||||||
import { getSystemById } from '@/hooks/Mapper/helpers';
|
|
||||||
import { ANOIK_ICON, DOTLAN_ICON, ZKB_ICON } from '@/hooks/Mapper/icons';
|
import { ANOIK_ICON, DOTLAN_ICON, ZKB_ICON } from '@/hooks/Mapper/icons';
|
||||||
|
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
|
||||||
|
|
||||||
export const SystemInfo = () => {
|
export const SystemInfo = () => {
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: { selectedSystems, systems },
|
data: { selectedSystems },
|
||||||
} = useMapRootState();
|
} = useMapRootState();
|
||||||
|
|
||||||
const [systemId] = selectedSystems;
|
const [systemId] = selectedSystems;
|
||||||
|
|
||||||
const sys = getSystemById(systems, systemId)!;
|
const systemStaticInfo = getSystemStaticInfo(systemId)!;
|
||||||
const { solar_system_name: solarSystemName } = sys?.system_static_info || {};
|
const { solar_system_name: solarSystemName } = systemStaticInfo || {};
|
||||||
|
|
||||||
const isNotSelectedSystem = selectedSystems.length !== 1;
|
const isNotSelectedSystem = selectedSystems.length !== 1;
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ export const SystemInfo = () => {
|
|||||||
<WdImgButton
|
<WdImgButton
|
||||||
className="pi pi-pen-to-square"
|
className="pi pi-pen-to-square"
|
||||||
onClick={() => setVisible(true)}
|
onClick={() => setVisible(true)}
|
||||||
tooltip={{ content: 'Edit system name and description' }}
|
tooltip={{ position: TooltipPosition.top, content: 'Edit system name and description' }}
|
||||||
/>
|
/>
|
||||||
</LayoutEventBlocker>
|
</LayoutEventBlocker>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormhol
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { getSystemById, sortWHClasses } from '@/hooks/Mapper/helpers';
|
import { getSystemById, sortWHClasses } from '@/hooks/Mapper/helpers';
|
||||||
import { InfoDrawer, WHClassView, WHEffectView } from '@/hooks/Mapper/components/ui-kit';
|
import { InfoDrawer, WHClassView, WHEffectView } from '@/hooks/Mapper/components/ui-kit';
|
||||||
|
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
|
||||||
|
|
||||||
interface SystemInfoContentProps {
|
interface SystemInfoContentProps {
|
||||||
systemId: string;
|
systemId: string;
|
||||||
@@ -14,9 +15,9 @@ export const SystemInfoContent = ({ systemId }: SystemInfoContentProps) => {
|
|||||||
} = useMapRootState();
|
} = useMapRootState();
|
||||||
|
|
||||||
const sys = getSystemById(systems, systemId)! || {};
|
const sys = getSystemById(systems, systemId)! || {};
|
||||||
|
const systemStaticInfo = getSystemStaticInfo(systemId)!;
|
||||||
const { description } = sys;
|
const { description } = sys;
|
||||||
const { system_class, region_name, constellation_name, statics, effect_name, effect_power } =
|
const { system_class, region_name, constellation_name, statics, effect_name, effect_power } = systemStaticInfo || {};
|
||||||
sys.system_static_info || {};
|
|
||||||
const isWH = isWormholeSpace(system_class);
|
const isWH = isWormholeSpace(system_class);
|
||||||
const sortedStatics = useMemo(() => sortWHClasses(wormholesData, statics), [wormholesData, statics]);
|
const sortedStatics = useMemo(() => sortWHClasses(wormholesData, statics), [wormholesData, statics]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,106 +0,0 @@
|
|||||||
import React, { useMemo, useState } from 'react';
|
|
||||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
|
||||||
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
|
|
||||||
import { SystemKillsContent } from './SystemKillsContent/SystemKillsContent';
|
|
||||||
import { KillsHeader } from './components/SystemKillsHeader';
|
|
||||||
import { useKillsWidgetSettings } from './hooks/useKillsWidgetSettings';
|
|
||||||
import { useSystemKills } from './hooks/useSystemKills';
|
|
||||||
import { KillsSettingsDialog } from './components/SystemKillsSettingsDialog';
|
|
||||||
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace';
|
|
||||||
import { SolarSystemRawType } from '@/hooks/Mapper/types';
|
|
||||||
|
|
||||||
export const SystemKills: React.FC = React.memo(() => {
|
|
||||||
const {
|
|
||||||
data: { selectedSystems, systems, isSubscriptionActive },
|
|
||||||
outCommand,
|
|
||||||
} = useMapRootState();
|
|
||||||
|
|
||||||
const [systemId] = selectedSystems || [];
|
|
||||||
const [settingsDialogVisible, setSettingsDialogVisible] = useState(false);
|
|
||||||
|
|
||||||
const systemNameMap = useMemo(() => {
|
|
||||||
const map: Record<string, string> = {};
|
|
||||||
systems.forEach(sys => {
|
|
||||||
map[sys.id] = sys.temporary_name || sys.name || '???';
|
|
||||||
});
|
|
||||||
return map;
|
|
||||||
}, [systems]);
|
|
||||||
|
|
||||||
const systemBySolarSystemId = useMemo(() => {
|
|
||||||
const map: Record<number, SolarSystemRawType> = {};
|
|
||||||
systems.forEach(sys => {
|
|
||||||
if (sys.system_static_info?.solar_system_id != null) {
|
|
||||||
map[sys.system_static_info.solar_system_id] = sys;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return map;
|
|
||||||
}, [systems]);
|
|
||||||
|
|
||||||
const [settings] = useKillsWidgetSettings();
|
|
||||||
const visible = settings.showAll;
|
|
||||||
|
|
||||||
const { kills, isLoading, error } = useSystemKills({
|
|
||||||
systemId,
|
|
||||||
outCommand,
|
|
||||||
showAllVisible: visible,
|
|
||||||
sinceHours: settings.timeRange,
|
|
||||||
});
|
|
||||||
|
|
||||||
const isNothingSelected = !systemId && !visible;
|
|
||||||
const showLoading = isLoading && kills.length === 0;
|
|
||||||
|
|
||||||
const filteredKills = useMemo(() => {
|
|
||||||
if (!settings.whOnly || !visible) return kills;
|
|
||||||
return kills.filter(kill => {
|
|
||||||
const system = systemBySolarSystemId[kill.solar_system_id];
|
|
||||||
if (!system) {
|
|
||||||
console.warn(`System with id ${kill.solar_system_id} not found.`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return isWormholeSpace(system.system_static_info.system_class);
|
|
||||||
});
|
|
||||||
}, [kills, settings.whOnly, systemBySolarSystemId, visible]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="h-full flex flex-col">
|
|
||||||
<Widget label={<KillsHeader systemId={systemId} onOpenSettings={() => setSettingsDialogVisible(true)} />}>
|
|
||||||
{!isSubscriptionActive ? (
|
|
||||||
<div className="w-full h-full flex items-center justify-center">
|
|
||||||
<span className="select-none text-center text-stone-400/80 text-sm">
|
|
||||||
Kills available with 'Active' map subscription only (contact map administrators)
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
) : isNothingSelected ? (
|
|
||||||
<div className="w-full h-full flex items-center justify-center">
|
|
||||||
<span className="select-none text-center text-stone-400/80 text-sm">
|
|
||||||
No system selected (or toggle "Show all systems")
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
) : showLoading ? (
|
|
||||||
<div className="w-full h-full flex items-center justify-center">
|
|
||||||
<span className="select-none text-center text-stone-400/80 text-sm">Loading Kills...</span>
|
|
||||||
</div>
|
|
||||||
) : error ? (
|
|
||||||
<div className="w-full h-full flex items-center justify-center">
|
|
||||||
<span className="select-none text-center text-red-400 text-sm">{error}</span>
|
|
||||||
</div>
|
|
||||||
) : !filteredKills || filteredKills.length === 0 ? (
|
|
||||||
<div className="w-full h-full flex items-center justify-center">
|
|
||||||
<span className="select-none text-center text-stone-400/80 text-sm">No kills found</span>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<SystemKillsContent
|
|
||||||
kills={filteredKills}
|
|
||||||
systemNameMap={systemNameMap}
|
|
||||||
onlyOneSystem={!visible}
|
|
||||||
timeRange={settings.timeRange}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Widget>
|
|
||||||
|
|
||||||
{settingsDialogVisible && <KillsSettingsDialog visible setVisible={setSettingsDialogVisible} />}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
SystemKills.displayName = 'SystemKills';
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
// Custom scrollbar styling is now handled by the global custom-scrollbar class
|
|
||||||
.scrollerContent {
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
height: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// VirtualScroller specific styles that can't be handled with Tailwind
|
|
||||||
.VirtualScroller {
|
|
||||||
overflow: hidden !important;
|
|
||||||
display: flex !important;
|
|
||||||
flex-direction: column !important;
|
|
||||||
height: 100% !important;
|
|
||||||
|
|
||||||
// Target this specific VirtualScroller instance
|
|
||||||
&:global(.p-virtualscroller) {
|
|
||||||
height: 100% !important;
|
|
||||||
|
|
||||||
:global(.p-virtualscroller-content) {
|
|
||||||
height: 100% !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fix for PrimeReact VirtualScroller - these need to be global
|
|
||||||
:global {
|
|
||||||
.p-virtualscroller {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-virtualscroller-content {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { DetailedKill } from '@/hooks/Mapper/types/kills';
|
|
||||||
import { VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
|
|
||||||
import { KillRow } from './SystemKillsRow';
|
|
||||||
import clsx from 'clsx';
|
|
||||||
|
|
||||||
export function KillItemTemplate(
|
|
||||||
systemNameMap: Record<string, string>,
|
|
||||||
onlyOneSystem: boolean,
|
|
||||||
kill: DetailedKill,
|
|
||||||
options: VirtualScrollerTemplateOptions,
|
|
||||||
) {
|
|
||||||
const systemIdStr = String(kill.solar_system_id);
|
|
||||||
const systemName = systemNameMap[systemIdStr] || `System ${systemIdStr}`;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={{ height: `${options.props.itemSize}px` }} className={clsx({ 'bg-gray-900': options.odd })}>
|
|
||||||
<KillRow killDetails={kill} systemName={systemName} onlyOneSystem={onlyOneSystem} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { DetailedKill } from '@/hooks/Mapper/types/kills';
|
|
||||||
import { KillRowDetail } from './KillRowDetail.tsx';
|
|
||||||
|
|
||||||
export interface KillRowProps {
|
|
||||||
killDetails: DetailedKill;
|
|
||||||
systemName: string;
|
|
||||||
onlyOneSystem?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const KillRowComponent: React.FC<KillRowProps> = ({ killDetails, systemName, onlyOneSystem = false }) => {
|
|
||||||
return <KillRowDetail killDetails={killDetails} systemName={systemName} onlyOneSystem={onlyOneSystem} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const KillRow = React.memo(KillRowComponent);
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './SystemKills';
|
|
||||||
@@ -10,7 +10,7 @@ export interface SignatureViewProps {
|
|||||||
export const SignatureView = ({ signature, showCharacterPortrait = false }: SignatureViewProps) => {
|
export const SignatureView = ({ signature, showCharacterPortrait = false }: SignatureViewProps) => {
|
||||||
const isWormhole = signature?.group === SignatureGroup.Wormhole;
|
const isWormhole = signature?.group === SignatureGroup.Wormhole;
|
||||||
const hasCharacterInfo = showCharacterPortrait && signature.character_eve_id;
|
const hasCharacterInfo = showCharacterPortrait && signature.character_eve_id;
|
||||||
const groupDisplay = isWormhole ? SignatureGroup.Wormhole : (signature?.group ?? SignatureGroup.CosmicSignature);
|
const groupDisplay = isWormhole ? SignatureGroup.Wormhole : signature?.group ?? SignatureGroup.CosmicSignature;
|
||||||
const characterName = signature.character_name || 'Unknown character';
|
const characterName = signature.character_name || 'Unknown character';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export type HeaderProps = {
|
|||||||
lazyDeleteValue: boolean;
|
lazyDeleteValue: boolean;
|
||||||
onLazyDeleteChange: (checked: boolean) => void;
|
onLazyDeleteChange: (checked: boolean) => void;
|
||||||
pendingCount: number;
|
pendingCount: number;
|
||||||
pendingTimeRemaining?: number; // Time remaining in ms
|
undoCountdown?: number;
|
||||||
onUndoClick: () => void;
|
onUndoClick: () => void;
|
||||||
onSettingsClick: () => void;
|
onSettingsClick: () => void;
|
||||||
};
|
};
|
||||||
@@ -29,7 +29,7 @@ export const SystemSignaturesHeader = ({
|
|||||||
lazyDeleteValue,
|
lazyDeleteValue,
|
||||||
onLazyDeleteChange,
|
onLazyDeleteChange,
|
||||||
pendingCount,
|
pendingCount,
|
||||||
pendingTimeRemaining,
|
undoCountdown,
|
||||||
onUndoClick,
|
onUndoClick,
|
||||||
onSettingsClick,
|
onSettingsClick,
|
||||||
}: HeaderProps) => {
|
}: HeaderProps) => {
|
||||||
@@ -43,13 +43,6 @@ export const SystemSignaturesHeader = ({
|
|||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const isCompact = useMaxWidth(containerRef, COMPACT_MAX_WIDTH);
|
const isCompact = useMaxWidth(containerRef, COMPACT_MAX_WIDTH);
|
||||||
|
|
||||||
// Format time remaining as seconds
|
|
||||||
const formatTimeRemaining = () => {
|
|
||||||
if (!pendingTimeRemaining) return '';
|
|
||||||
const seconds = Math.ceil(pendingTimeRemaining / 1000);
|
|
||||||
return ` (${seconds}s remaining)`;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef} className="w-full">
|
<div ref={containerRef} className="w-full">
|
||||||
<div className="flex justify-between items-center text-xs w-full h-full">
|
<div className="flex justify-between items-center text-xs w-full h-full">
|
||||||
@@ -78,7 +71,9 @@ export const SystemSignaturesHeader = ({
|
|||||||
<WdImgButton
|
<WdImgButton
|
||||||
className={PrimeIcons.UNDO}
|
className={PrimeIcons.UNDO}
|
||||||
style={{ color: 'red' }}
|
style={{ color: 'red' }}
|
||||||
tooltip={{ content: `Undo pending changes (${pendingCount})${formatTimeRemaining()}` }}
|
tooltip={{
|
||||||
|
content: `Undo pending deletions (${pendingCount})${undoCountdown && undoCountdown > 0 ? ` — ${undoCountdown}s left` : ''}`,
|
||||||
|
}}
|
||||||
onClick={onUndoClick}
|
onClick={onUndoClick}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
.verticalTabsContainer {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
min-height: 300px;
|
|
||||||
|
|
||||||
:global {
|
|
||||||
.p-tabview {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-tabview-panels {
|
|
||||||
padding: 6px 1rem !important;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-tabview-nav-container {
|
|
||||||
border-right: none;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-tabview-nav {
|
|
||||||
flex-direction: column;
|
|
||||||
width: 150px;
|
|
||||||
min-height: 100%;
|
|
||||||
border: none;
|
|
||||||
|
|
||||||
li {
|
|
||||||
width: 100%;
|
|
||||||
border-right: 4px solid var(--surface-hover);
|
|
||||||
background-color: var(--surface-card);
|
|
||||||
|
|
||||||
transition:
|
|
||||||
background-color 200ms,
|
|
||||||
border-right-color 200ms;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--surface-hover);
|
|
||||||
border-right: 4px solid var(--surface-100);
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-tabview-nav-link {
|
|
||||||
transition: color 200ms;
|
|
||||||
|
|
||||||
justify-content: flex-end;
|
|
||||||
padding: 10px;
|
|
||||||
//background-color: var(--surface-card);
|
|
||||||
background-color: initial;
|
|
||||||
border: none;
|
|
||||||
color: var(--gray-400);
|
|
||||||
|
|
||||||
border-radius: initial;
|
|
||||||
font-weight: 400;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.p-tabview-selected {
|
|
||||||
background-color: var(--surface-50);
|
|
||||||
border-right: 4px solid var(--primary-color);
|
|
||||||
|
|
||||||
.p-tabview-nav-link {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
//background-color: var(--surface-hover);
|
|
||||||
border-right: 4px solid var(--primary-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-tabview-panel {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,6 @@ import { Dialog } from 'primereact/dialog';
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { Button } from 'primereact/button';
|
import { Button } from 'primereact/button';
|
||||||
import { TabPanel, TabView } from 'primereact/tabview';
|
import { TabPanel, TabView } from 'primereact/tabview';
|
||||||
import styles from './SystemSignatureSettingsDialog.module.scss';
|
|
||||||
import { PrettySwitchbox } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components';
|
import { PrettySwitchbox } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components';
|
||||||
import { Dropdown } from 'primereact/dropdown';
|
import { Dropdown } from 'primereact/dropdown';
|
||||||
import {
|
import {
|
||||||
@@ -72,26 +71,24 @@ export const SystemSignatureSettingsDialog = ({
|
|||||||
<Dialog header="System Signatures Settings" visible={true} onHide={onCancel} className="w-full max-w-lg h-[500px]">
|
<Dialog header="System Signatures Settings" visible={true} onHide={onCancel} className="w-full max-w-lg h-[500px]">
|
||||||
<div className="flex flex-col gap-3 justify-between h-full">
|
<div className="flex flex-col gap-3 justify-between h-full">
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<div className={styles.verticalTabsContainer}>
|
<TabView
|
||||||
<TabView
|
activeIndex={activeIndex}
|
||||||
activeIndex={activeIndex}
|
onTabChange={e => setActiveIndex(e.index)}
|
||||||
onTabChange={e => setActiveIndex(e.index)}
|
className="vertical-tabs-container"
|
||||||
className={styles.verticalTabView}
|
>
|
||||||
>
|
<TabPanel header="Filters">
|
||||||
<TabPanel header="Filters" headerClassName={styles.verticalTabHeader}>
|
<div className="w-full h-full flex flex-col gap-1">
|
||||||
<div className="w-full h-full flex flex-col gap-1">
|
{SIGNATURE_SETTINGS.filterFlags.map(renderSetting)}
|
||||||
{SIGNATURE_SETTINGS.filterFlags.map(renderSetting)}
|
</div>
|
||||||
</div>
|
</TabPanel>
|
||||||
</TabPanel>
|
<TabPanel header="User Interface">
|
||||||
<TabPanel header="User Interface" headerClassName={styles.verticalTabHeader}>
|
<div className="w-full h-full flex flex-col gap-1">
|
||||||
<div className="w-full h-full flex flex-col gap-1">
|
{SIGNATURE_SETTINGS.uiFlags.map(renderSetting)}
|
||||||
{SIGNATURE_SETTINGS.uiFlags.map(renderSetting)}
|
<div className="my-2 border-t border-stone-700/50"></div>
|
||||||
<div className="my-2 border-t border-stone-700/50"></div>
|
{SIGNATURE_SETTINGS.uiOther.map(renderSetting)}
|
||||||
{SIGNATURE_SETTINGS.uiOther.map(renderSetting)}
|
</div>
|
||||||
</div>
|
</TabPanel>
|
||||||
</TabPanel>
|
</TabView>
|
||||||
</TabView>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2 justify-end">
|
<div className="flex gap-2 justify-end">
|
||||||
|
|||||||
@@ -1,99 +1,156 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useState, useEffect, useRef, useMemo } from 'react';
|
||||||
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
|
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
|
||||||
import { SystemSignaturesContent } from './SystemSignaturesContent';
|
import { SystemSignaturesContent } from './SystemSignaturesContent';
|
||||||
import { SystemSignatureSettingsDialog } from './SystemSignatureSettingsDialog';
|
import { SystemSignatureSettingsDialog } from './SystemSignatureSettingsDialog';
|
||||||
import { ExtendedSystemSignature, SystemSignature } from '@/hooks/Mapper/types';
|
|
||||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
import { useHotkey } from '@/hooks/Mapper/hooks';
|
|
||||||
import { SystemSignaturesHeader } from './SystemSignatureHeader';
|
import { SystemSignaturesHeader } from './SystemSignatureHeader';
|
||||||
import useLocalStorageState from 'use-local-storage-state';
|
import useLocalStorageState from 'use-local-storage-state';
|
||||||
|
import { useHotkey } from '@/hooks/Mapper/hooks/useHotkey';
|
||||||
import {
|
import {
|
||||||
SETTINGS_KEYS,
|
SETTINGS_KEYS,
|
||||||
SETTINGS_VALUES,
|
SETTINGS_VALUES,
|
||||||
SIGNATURE_DELETION_TIMEOUTS,
|
|
||||||
SIGNATURE_SETTING_STORE_KEY,
|
SIGNATURE_SETTING_STORE_KEY,
|
||||||
SIGNATURE_WINDOW_ID,
|
SIGNATURE_WINDOW_ID,
|
||||||
SIGNATURES_DELETION_TIMING,
|
|
||||||
SignatureSettingsType,
|
SignatureSettingsType,
|
||||||
|
SIGNATURES_DELETION_TIMING,
|
||||||
|
SIGNATURE_DELETION_TIMEOUTS,
|
||||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||||
import { calculateTimeRemaining } from './helpers';
|
import { OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers';
|
||||||
|
|
||||||
export const SystemSignatures = () => {
|
/**
|
||||||
const [visible, setVisible] = useState(false);
|
* Custom hook for managing pending signature deletions and undo countdown.
|
||||||
const [sigCount, setSigCount] = useState<number>(0);
|
*/
|
||||||
const [pendingSigs, setPendingSigs] = useState<SystemSignature[]>([]);
|
function useSignatureUndo(
|
||||||
const [pendingTimeRemaining, setPendingTimeRemaining] = useState<number | undefined>();
|
systemId: string | undefined,
|
||||||
const undoPendingFnRef = useRef<() => void>(() => {});
|
settings: SignatureSettingsType,
|
||||||
|
outCommand: OutCommandHandler,
|
||||||
|
) {
|
||||||
|
const [countdown, setCountdown] = useState<number>(0);
|
||||||
|
const [pendingIds, setPendingIds] = useState<Set<string>>(new Set());
|
||||||
|
const intervalRef = useRef<number | null>(null);
|
||||||
|
|
||||||
const {
|
const addDeleted = useCallback((ids: string[]) => {
|
||||||
data: { selectedSystems },
|
setPendingIds(prev => {
|
||||||
} = useMapRootState();
|
const next = new Set(prev);
|
||||||
|
ids.forEach(id => next.add(id));
|
||||||
const [currentSettings, setCurrentSettings] = useLocalStorageState(SIGNATURE_SETTING_STORE_KEY, {
|
return next;
|
||||||
defaultValue: SETTINGS_VALUES,
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const handleSigCountChange = useCallback((count: number) => {
|
|
||||||
setSigCount(count);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const [systemId] = selectedSystems;
|
// kick off or clear countdown whenever pendingIds changes
|
||||||
const isNotSelectedSystem = selectedSystems.length !== 1;
|
|
||||||
|
|
||||||
const handleSettingsChange = useCallback((newSettings: SignatureSettingsType) => {
|
|
||||||
setCurrentSettings(newSettings);
|
|
||||||
setVisible(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleLazyDeleteChange = useCallback((value: boolean) => {
|
|
||||||
setCurrentSettings(prev => ({ ...prev, [SETTINGS_KEYS.LAZY_DELETE_SIGNATURES]: value }));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useHotkey(true, ['z'], event => {
|
|
||||||
if (pendingSigs.length > 0) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
undoPendingFnRef.current();
|
|
||||||
setPendingSigs([]);
|
|
||||||
setPendingTimeRemaining(undefined);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleUndoClick = useCallback(() => {
|
|
||||||
undoPendingFnRef.current();
|
|
||||||
setPendingSigs([]);
|
|
||||||
setPendingTimeRemaining(undefined);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleSettingsButtonClick = useCallback(() => {
|
|
||||||
setVisible(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handlePendingChange = useCallback(
|
|
||||||
(pending: React.MutableRefObject<Record<string, ExtendedSystemSignature>>, newUndo: () => void) => {
|
|
||||||
setPendingSigs(() => {
|
|
||||||
return Object.values(pending.current).filter(sig => sig.pendingDeletion);
|
|
||||||
});
|
|
||||||
undoPendingFnRef.current = newUndo;
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Calculate the minimum time remaining for any pending signature
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (pendingSigs.length === 0) {
|
// clear any existing timer
|
||||||
setPendingTimeRemaining(undefined);
|
if (intervalRef.current != null) {
|
||||||
|
clearInterval(intervalRef.current);
|
||||||
|
intervalRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pendingIds.size === 0) {
|
||||||
|
setCountdown(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const calculate = () => {
|
// determine timeout from settings
|
||||||
setPendingTimeRemaining(() => calculateTimeRemaining(pendingSigs));
|
const timingKey = Number(settings[SETTINGS_KEYS.DELETION_TIMING] ?? SIGNATURES_DELETION_TIMING.DEFAULT);
|
||||||
};
|
const timeoutMs =
|
||||||
|
Number(SIGNATURE_DELETION_TIMEOUTS[timingKey as keyof typeof SIGNATURE_DELETION_TIMEOUTS]) || 10000;
|
||||||
|
setCountdown(Math.ceil(timeoutMs / 1000));
|
||||||
|
|
||||||
calculate();
|
// start new interval
|
||||||
const interval = setInterval(calculate, 1000);
|
intervalRef.current = window.setInterval(() => {
|
||||||
return () => clearInterval(interval);
|
setCountdown(prev => {
|
||||||
}, [pendingSigs]);
|
if (prev <= 1) {
|
||||||
|
clearInterval(intervalRef.current!);
|
||||||
|
intervalRef.current = null;
|
||||||
|
setPendingIds(new Set());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return prev - 1;
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (intervalRef.current != null) {
|
||||||
|
clearInterval(intervalRef.current);
|
||||||
|
intervalRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [pendingIds, settings[SETTINGS_KEYS.DELETION_TIMING]]);
|
||||||
|
|
||||||
|
// undo handler
|
||||||
|
const handleUndo = useCallback(async () => {
|
||||||
|
if (!systemId || pendingIds.size === 0) return;
|
||||||
|
await outCommand({
|
||||||
|
type: OutCommand.undoDeleteSignatures,
|
||||||
|
data: { system_id: systemId, eve_ids: Array.from(pendingIds) },
|
||||||
|
});
|
||||||
|
setPendingIds(new Set());
|
||||||
|
setCountdown(0);
|
||||||
|
if (intervalRef.current != null) {
|
||||||
|
clearInterval(intervalRef.current);
|
||||||
|
intervalRef.current = null;
|
||||||
|
}
|
||||||
|
}, [systemId, pendingIds, outCommand]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
pendingIds,
|
||||||
|
countdown,
|
||||||
|
addDeleted,
|
||||||
|
handleUndo,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SystemSignatures = () => {
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const [sigCount, setSigCount] = useState(0);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: { selectedSystems },
|
||||||
|
outCommand,
|
||||||
|
} = useMapRootState();
|
||||||
|
|
||||||
|
const [currentSettings, setCurrentSettings] = useLocalStorageState<SignatureSettingsType>(
|
||||||
|
SIGNATURE_SETTING_STORE_KEY,
|
||||||
|
{
|
||||||
|
defaultValue: SETTINGS_VALUES,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const [systemId] = selectedSystems;
|
||||||
|
const isSystemSelected = useMemo(() => selectedSystems.length === 1, [selectedSystems.length]);
|
||||||
|
const { pendingIds, countdown, addDeleted, handleUndo } = useSignatureUndo(systemId, currentSettings, outCommand);
|
||||||
|
|
||||||
|
useHotkey(true, ['z', 'Z'], (event: KeyboardEvent) => {
|
||||||
|
if (pendingIds.size > 0 && countdown > 0) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
handleUndo();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleCountChange = useCallback((count: number) => {
|
||||||
|
setSigCount(count);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSettingsSave = useCallback(
|
||||||
|
(newSettings: SignatureSettingsType) => {
|
||||||
|
setCurrentSettings(newSettings);
|
||||||
|
setVisible(false);
|
||||||
|
},
|
||||||
|
[setCurrentSettings],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleLazyDeleteToggle = useCallback(
|
||||||
|
(value: boolean) => {
|
||||||
|
setCurrentSettings(prev => ({
|
||||||
|
...prev,
|
||||||
|
[SETTINGS_KEYS.LAZY_DELETE_SIGNATURES]: value,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
[setCurrentSettings],
|
||||||
|
);
|
||||||
|
|
||||||
|
const openSettings = useCallback(() => setVisible(true), []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Widget
|
<Widget
|
||||||
@@ -101,16 +158,16 @@ export const SystemSignatures = () => {
|
|||||||
<SystemSignaturesHeader
|
<SystemSignaturesHeader
|
||||||
sigCount={sigCount}
|
sigCount={sigCount}
|
||||||
lazyDeleteValue={currentSettings[SETTINGS_KEYS.LAZY_DELETE_SIGNATURES] as boolean}
|
lazyDeleteValue={currentSettings[SETTINGS_KEYS.LAZY_DELETE_SIGNATURES] as boolean}
|
||||||
pendingCount={pendingSigs.length}
|
pendingCount={pendingIds.size}
|
||||||
pendingTimeRemaining={pendingTimeRemaining}
|
undoCountdown={countdown}
|
||||||
onLazyDeleteChange={handleLazyDeleteChange}
|
onLazyDeleteChange={handleLazyDeleteToggle}
|
||||||
onUndoClick={handleUndoClick}
|
onUndoClick={handleUndo}
|
||||||
onSettingsClick={handleSettingsButtonClick}
|
onSettingsClick={openSettings}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
windowId={SIGNATURE_WINDOW_ID}
|
windowId={SIGNATURE_WINDOW_ID}
|
||||||
>
|
>
|
||||||
{isNotSelectedSystem ? (
|
{!isSystemSelected ? (
|
||||||
<div className="w-full h-full flex justify-center items-center select-none text-center text-stone-400/80 text-sm">
|
<div className="w-full h-full flex justify-center items-center select-none text-center text-stone-400/80 text-sm">
|
||||||
System is not selected
|
System is not selected
|
||||||
</div>
|
</div>
|
||||||
@@ -118,22 +175,17 @@ export const SystemSignatures = () => {
|
|||||||
<SystemSignaturesContent
|
<SystemSignaturesContent
|
||||||
systemId={systemId}
|
systemId={systemId}
|
||||||
settings={currentSettings}
|
settings={currentSettings}
|
||||||
deletionTiming={
|
onLazyDeleteChange={handleLazyDeleteToggle}
|
||||||
SIGNATURE_DELETION_TIMEOUTS[
|
onCountChange={handleCountChange}
|
||||||
(currentSettings[SETTINGS_KEYS.DELETION_TIMING] as keyof typeof SIGNATURE_DELETION_TIMEOUTS) ||
|
onSignatureDeleted={addDeleted}
|
||||||
SIGNATURES_DELETION_TIMING.DEFAULT
|
|
||||||
] as number
|
|
||||||
}
|
|
||||||
onLazyDeleteChange={handleLazyDeleteChange}
|
|
||||||
onCountChange={handleSigCountChange}
|
|
||||||
onPendingChange={handlePendingChange}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{visible && (
|
{visible && (
|
||||||
<SystemSignatureSettingsDialog
|
<SystemSignatureSettingsDialog
|
||||||
settings={currentSettings}
|
settings={currentSettings}
|
||||||
onCancel={() => setVisible(false)}
|
onCancel={() => setVisible(false)}
|
||||||
onSave={handleSettingsChange}
|
onSave={handleSettingsSave}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Widget>
|
</Widget>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { PrimeIcons } from 'primereact/api';
|
||||||
|
import { Column } from 'primereact/column';
|
||||||
import {
|
import {
|
||||||
DataTable,
|
DataTable,
|
||||||
DataTableRowClickEvent,
|
DataTableRowClickEvent,
|
||||||
@@ -6,13 +7,9 @@ import {
|
|||||||
DataTableStateEvent,
|
DataTableStateEvent,
|
||||||
SortOrder,
|
SortOrder,
|
||||||
} from 'primereact/datatable';
|
} from 'primereact/datatable';
|
||||||
import { Column } from 'primereact/column';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { PrimeIcons } from 'primereact/api';
|
|
||||||
import useLocalStorageState from 'use-local-storage-state';
|
import useLocalStorageState from 'use-local-storage-state';
|
||||||
|
|
||||||
import { ExtendedSystemSignature, SignatureGroup, SignatureKind, SystemSignature } from '@/hooks/Mapper/types';
|
|
||||||
import { SignatureSettings } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings';
|
|
||||||
import { WdTooltip, WdTooltipHandlers, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
|
|
||||||
import { SignatureView } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SignatureView';
|
import { SignatureView } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SignatureView';
|
||||||
import {
|
import {
|
||||||
COMPACT_MAX_WIDTH,
|
COMPACT_MAX_WIDTH,
|
||||||
@@ -24,6 +21,9 @@ import {
|
|||||||
SIGNATURE_WINDOW_ID,
|
SIGNATURE_WINDOW_ID,
|
||||||
SignatureSettingsType,
|
SignatureSettingsType,
|
||||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants';
|
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants';
|
||||||
|
import { SignatureSettings } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings';
|
||||||
|
import { TooltipPosition, WdTooltip, WdTooltipHandlers, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
|
||||||
|
import { ExtendedSystemSignature, SignatureGroup, SignatureKind, SystemSignature } from '@/hooks/Mapper/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
renderAddedTimeLeft,
|
renderAddedTimeLeft,
|
||||||
@@ -32,10 +32,10 @@ import {
|
|||||||
renderInfoColumn,
|
renderInfoColumn,
|
||||||
renderUpdatedTimeLeft,
|
renderUpdatedTimeLeft,
|
||||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
|
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
|
||||||
import { useSystemSignaturesData } from '../hooks/useSystemSignaturesData';
|
|
||||||
import { getSignatureRowClass } from '../helpers/rowStyles';
|
|
||||||
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth';
|
|
||||||
import { useClipboard, useHotkey } from '@/hooks/Mapper/hooks';
|
import { useClipboard, useHotkey } from '@/hooks/Mapper/hooks';
|
||||||
|
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth';
|
||||||
|
import { getSignatureRowClass } from '../helpers/rowStyles';
|
||||||
|
import { useSystemSignaturesData } from '../hooks/useSystemSignaturesData';
|
||||||
|
|
||||||
const renderColIcon = (sig: SystemSignature) => renderIcon(sig);
|
const renderColIcon = (sig: SystemSignature) => renderIcon(sig);
|
||||||
|
|
||||||
@@ -57,12 +57,8 @@ interface SystemSignaturesContentProps {
|
|||||||
onSelect?: (signature: SystemSignature) => void;
|
onSelect?: (signature: SystemSignature) => void;
|
||||||
onLazyDeleteChange?: (value: boolean) => void;
|
onLazyDeleteChange?: (value: boolean) => void;
|
||||||
onCountChange?: (count: number) => void;
|
onCountChange?: (count: number) => void;
|
||||||
onPendingChange?: (
|
|
||||||
pending: React.MutableRefObject<Record<string, ExtendedSystemSignature>>,
|
|
||||||
undo: () => void,
|
|
||||||
) => void;
|
|
||||||
deletionTiming?: number;
|
|
||||||
filterSignature?: (signature: SystemSignature) => boolean;
|
filterSignature?: (signature: SystemSignature) => boolean;
|
||||||
|
onSignatureDeleted?: (deletedIds: string[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SystemSignaturesContent = ({
|
export const SystemSignaturesContent = ({
|
||||||
@@ -73,9 +69,8 @@ export const SystemSignaturesContent = ({
|
|||||||
onSelect,
|
onSelect,
|
||||||
onLazyDeleteChange,
|
onLazyDeleteChange,
|
||||||
onCountChange,
|
onCountChange,
|
||||||
onPendingChange,
|
|
||||||
deletionTiming,
|
|
||||||
filterSignature,
|
filterSignature,
|
||||||
|
onSignatureDeleted,
|
||||||
}: SystemSignaturesContentProps) => {
|
}: SystemSignaturesContentProps) => {
|
||||||
const [selectedSignatureForDialog, setSelectedSignatureForDialog] = useState<SystemSignature | null>(null);
|
const [selectedSignatureForDialog, setSelectedSignatureForDialog] = useState<SystemSignature | null>(null);
|
||||||
const [showSignatureSettings, setShowSignatureSettings] = useState(false);
|
const [showSignatureSettings, setShowSignatureSettings] = useState(false);
|
||||||
@@ -95,15 +90,21 @@ export const SystemSignaturesContent = ({
|
|||||||
{ defaultValue: SORT_DEFAULT_VALUES },
|
{ defaultValue: SORT_DEFAULT_VALUES },
|
||||||
);
|
);
|
||||||
|
|
||||||
const { signatures, selectedSignatures, setSelectedSignatures, handleDeleteSelected, handleSelectAll, handlePaste } =
|
const {
|
||||||
useSystemSignaturesData({
|
signatures,
|
||||||
systemId,
|
selectedSignatures,
|
||||||
settings,
|
setSelectedSignatures,
|
||||||
onCountChange,
|
handleDeleteSelected,
|
||||||
onPendingChange,
|
handleSelectAll,
|
||||||
onLazyDeleteChange,
|
handlePaste,
|
||||||
deletionTiming,
|
hasUnsupportedLanguage,
|
||||||
});
|
} = useSystemSignaturesData({
|
||||||
|
systemId,
|
||||||
|
settings,
|
||||||
|
onCountChange,
|
||||||
|
onLazyDeleteChange,
|
||||||
|
onSignatureDeleted,
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectable) return;
|
if (selectable) return;
|
||||||
@@ -125,6 +126,10 @@ export const SystemSignaturesContent = ({
|
|||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
if (onSignatureDeleted && selectedSignatures.length > 0) {
|
||||||
|
const deletedIds = selectedSignatures.map(s => s.eve_id);
|
||||||
|
onSignatureDeleted(deletedIds);
|
||||||
|
}
|
||||||
handleDeleteSelected();
|
handleDeleteSelected();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -155,7 +160,7 @@ export const SystemSignaturesContent = ({
|
|||||||
(e: { value: SystemSignature[] }) => {
|
(e: { value: SystemSignature[] }) => {
|
||||||
selectable ? onSelect?.(e.value[0]) : setSelectedSignatures(e.value as ExtendedSystemSignature[]);
|
selectable ? onSelect?.(e.value[0]) : setSelectedSignatures(e.value as ExtendedSystemSignature[]);
|
||||||
},
|
},
|
||||||
[selectable],
|
[onSelect, selectable, setSelectedSignatures],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { showDescriptionColumn, showUpdatedColumn, showCharacterColumn, showCharacterPortrait } = useMemo(
|
const { showDescriptionColumn, showUpdatedColumn, showCharacterColumn, showCharacterPortrait } = useMemo(
|
||||||
@@ -188,7 +193,11 @@ export const SystemSignaturesContent = ({
|
|||||||
x => GROUPS_LIST.includes(x as SignatureGroup) && settings[x as SETTINGS_KEYS],
|
x => GROUPS_LIST.includes(x as SignatureGroup) && settings[x as SETTINGS_KEYS],
|
||||||
);
|
);
|
||||||
|
|
||||||
return enabledGroups.includes(getGroupIdByRawGroup(sig.group));
|
const mappedGroup = getGroupIdByRawGroup(sig.group);
|
||||||
|
if (!mappedGroup) {
|
||||||
|
return true; // If we can't determine the group, still show it
|
||||||
|
}
|
||||||
|
return enabledGroups.includes(mappedGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -236,118 +245,127 @@ export const SystemSignaturesContent = ({
|
|||||||
No signatures
|
No signatures
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<DataTable
|
<>
|
||||||
value={filteredSignatures}
|
{hasUnsupportedLanguage && (
|
||||||
size="small"
|
<div className="w-full flex justify-center items-center text-amber-500 text-xs p-1 bg-amber-950/20 border-b border-amber-800/30">
|
||||||
selectionMode="multiple"
|
<i className={PrimeIcons.EXCLAMATION_TRIANGLE + ' mr-1'} />
|
||||||
selection={selectedSignatures}
|
Non-English signatures detected. Some signatures may not display correctly. Double-click to edit signature details.
|
||||||
metaKeySelection
|
</div>
|
||||||
onSelectionChange={handleSelectSignatures}
|
|
||||||
dataKey="eve_id"
|
|
||||||
className="w-full select-none"
|
|
||||||
resizableColumns={false}
|
|
||||||
rowHover
|
|
||||||
selectAll
|
|
||||||
onRowDoubleClick={handleRowClick}
|
|
||||||
sortField={sortSettings.sortField}
|
|
||||||
sortOrder={sortSettings.sortOrder}
|
|
||||||
onSort={handleSortSettings}
|
|
||||||
onRowMouseEnter={onRowMouseEnter}
|
|
||||||
onRowMouseLeave={onRowMouseLeave}
|
|
||||||
// @ts-ignore
|
|
||||||
rowClassName={getRowClassName}
|
|
||||||
>
|
|
||||||
<Column
|
|
||||||
field="icon"
|
|
||||||
header=""
|
|
||||||
body={renderColIcon}
|
|
||||||
bodyClassName="p-0 px-1"
|
|
||||||
style={{ maxWidth: 26, minWidth: 26, width: 26 }}
|
|
||||||
/>
|
|
||||||
<Column
|
|
||||||
field="eve_id"
|
|
||||||
header="Id"
|
|
||||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
|
||||||
style={{ maxWidth: 72, minWidth: 72, width: 72 }}
|
|
||||||
sortable
|
|
||||||
/>
|
|
||||||
<Column
|
|
||||||
field="group"
|
|
||||||
header="Group"
|
|
||||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
|
||||||
style={{ maxWidth: 110, minWidth: 110, width: 110 }}
|
|
||||||
body={sig => sig.group ?? ''}
|
|
||||||
hidden={isCompact}
|
|
||||||
sortable
|
|
||||||
/>
|
|
||||||
<Column
|
|
||||||
field="info"
|
|
||||||
header="Info"
|
|
||||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
|
||||||
style={{ maxWidth: nameColumnWidth }}
|
|
||||||
hidden={isCompact || isMedium}
|
|
||||||
body={renderInfoColumn}
|
|
||||||
/>
|
|
||||||
{showDescriptionColumn && (
|
|
||||||
<Column
|
|
||||||
field="description"
|
|
||||||
header="Description"
|
|
||||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
|
||||||
hidden={isCompact}
|
|
||||||
body={renderDescription}
|
|
||||||
sortable
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
<Column
|
<DataTable
|
||||||
field="inserted_at"
|
value={filteredSignatures}
|
||||||
header="Added"
|
size="small"
|
||||||
dataType="date"
|
selectionMode="multiple"
|
||||||
body={renderAddedTimeLeft}
|
selection={selectedSignatures}
|
||||||
style={{ minWidth: 70, maxWidth: 80 }}
|
metaKeySelection
|
||||||
bodyClassName="ssc-header text-ellipsis overflow-hidden whitespace-nowrap"
|
onSelectionChange={handleSelectSignatures}
|
||||||
sortable
|
dataKey="eve_id"
|
||||||
/>
|
className="w-full select-none"
|
||||||
{showUpdatedColumn && (
|
resizableColumns={false}
|
||||||
<Column
|
rowHover
|
||||||
field="updated_at"
|
selectAll
|
||||||
header="Updated"
|
onRowDoubleClick={handleRowClick}
|
||||||
dataType="date"
|
sortField={sortSettings.sortField}
|
||||||
body={renderUpdatedTimeLeft}
|
sortOrder={sortSettings.sortOrder}
|
||||||
style={{ minWidth: 70, maxWidth: 80 }}
|
onSort={handleSortSettings}
|
||||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
onRowMouseEnter={onRowMouseEnter}
|
||||||
sortable
|
onRowMouseLeave={onRowMouseLeave}
|
||||||
/>
|
// @ts-ignore
|
||||||
)}
|
rowClassName={getRowClassName}
|
||||||
|
>
|
||||||
{showCharacterColumn && (
|
|
||||||
<Column
|
|
||||||
field="character_name"
|
|
||||||
header="Character"
|
|
||||||
bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
|
|
||||||
sortable
|
|
||||||
></Column>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!selectable && (
|
|
||||||
<Column
|
<Column
|
||||||
|
field="icon"
|
||||||
header=""
|
header=""
|
||||||
body={() => (
|
body={renderColIcon}
|
||||||
<div className="flex justify-end items-center gap-2 mr-[4px]">
|
bodyClassName="p-0 px-1"
|
||||||
<WdTooltipWrapper content="Double-click a row to edit signature">
|
|
||||||
<span className={PrimeIcons.PENCIL + ' text-[10px]'} />
|
|
||||||
</WdTooltipWrapper>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
style={{ maxWidth: 26, minWidth: 26, width: 26 }}
|
style={{ maxWidth: 26, minWidth: 26, width: 26 }}
|
||||||
bodyClassName="p-0 pl-1 pr-2"
|
|
||||||
/>
|
/>
|
||||||
)}
|
<Column
|
||||||
</DataTable>
|
field="eve_id"
|
||||||
|
header="Id"
|
||||||
|
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||||
|
style={{ maxWidth: 72, minWidth: 72, width: 72 }}
|
||||||
|
sortable
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
field="group"
|
||||||
|
header="Group"
|
||||||
|
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||||
|
style={{ maxWidth: 110, minWidth: 110, width: 110 }}
|
||||||
|
body={sig => sig.group ?? ''}
|
||||||
|
hidden={isCompact}
|
||||||
|
sortable
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
field="info"
|
||||||
|
header="Info"
|
||||||
|
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||||
|
style={{ maxWidth: nameColumnWidth }}
|
||||||
|
hidden={isCompact || isMedium}
|
||||||
|
body={renderInfoColumn}
|
||||||
|
/>
|
||||||
|
{showDescriptionColumn && (
|
||||||
|
<Column
|
||||||
|
field="description"
|
||||||
|
header="Description"
|
||||||
|
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||||
|
hidden={isCompact}
|
||||||
|
body={renderDescription}
|
||||||
|
sortable
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Column
|
||||||
|
field="inserted_at"
|
||||||
|
header="Added"
|
||||||
|
dataType="date"
|
||||||
|
body={renderAddedTimeLeft}
|
||||||
|
style={{ minWidth: 70, maxWidth: 80 }}
|
||||||
|
bodyClassName="ssc-header text-ellipsis overflow-hidden whitespace-nowrap"
|
||||||
|
sortable
|
||||||
|
/>
|
||||||
|
{showUpdatedColumn && (
|
||||||
|
<Column
|
||||||
|
field="updated_at"
|
||||||
|
header="Updated"
|
||||||
|
dataType="date"
|
||||||
|
body={renderUpdatedTimeLeft}
|
||||||
|
style={{ minWidth: 70, maxWidth: 80 }}
|
||||||
|
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||||
|
sortable
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{showCharacterColumn && (
|
||||||
|
<Column
|
||||||
|
field="character_name"
|
||||||
|
header="Character"
|
||||||
|
bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
|
||||||
|
sortable
|
||||||
|
></Column>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!selectable && (
|
||||||
|
<Column
|
||||||
|
header=""
|
||||||
|
body={() => (
|
||||||
|
<div className="flex justify-end items-center gap-2 mr-[4px]">
|
||||||
|
<WdTooltipWrapper content="Double-click a row to edit signature">
|
||||||
|
<span className={PrimeIcons.PENCIL + ' text-[10px]'} />
|
||||||
|
</WdTooltipWrapper>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
style={{ maxWidth: 26, minWidth: 26, width: 26 }}
|
||||||
|
bodyClassName="p-0 pl-1 pr-2"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</DataTable>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<WdTooltip
|
<WdTooltip
|
||||||
className="bg-stone-900/95 text-slate-50"
|
className="bg-stone-900/95 text-slate-50"
|
||||||
ref={tooltipRef}
|
ref={tooltipRef}
|
||||||
|
position={TooltipPosition.top}
|
||||||
content={
|
content={
|
||||||
hoveredSignature ? (
|
hoveredSignature ? (
|
||||||
<SignatureView signature={hoveredSignature} showCharacterPortrait={showCharacterPortrait} />
|
<SignatureView signature={hoveredSignature} showCharacterPortrait={showCharacterPortrait} />
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import {
|
import {
|
||||||
GroupType,
|
GroupType,
|
||||||
SignatureGroup,
|
SignatureGroup,
|
||||||
|
SignatureGroupDE,
|
||||||
SignatureGroupENG,
|
SignatureGroupENG,
|
||||||
|
SignatureGroupFR,
|
||||||
SignatureGroupRU,
|
SignatureGroupRU,
|
||||||
SignatureKind,
|
SignatureKind,
|
||||||
|
SignatureKindDE,
|
||||||
SignatureKindENG,
|
SignatureKindENG,
|
||||||
|
SignatureKindFR,
|
||||||
SignatureKindRU,
|
SignatureKindRU,
|
||||||
} from '@/hooks/Mapper/types';
|
} from '@/hooks/Mapper/types';
|
||||||
|
|
||||||
@@ -40,46 +44,58 @@ export const GROUPS: Record<SignatureGroup, GroupType> = {
|
|||||||
[SignatureGroup.CosmicSignature]: { id: SignatureGroup.CosmicSignature, icon: '/icons/x_close14.png', w: 9, h: 9 },
|
[SignatureGroup.CosmicSignature]: { id: SignatureGroup.CosmicSignature, icon: '/icons/x_close14.png', w: 9, h: 9 },
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MAPPING_GROUP_TO_ENG = {
|
export const LANGUAGE_GROUP_MAPPINGS = {
|
||||||
// ENGLISH
|
EN: {
|
||||||
[SignatureGroupENG.GasSite]: SignatureGroup.GasSite,
|
[SignatureGroupENG.GasSite]: SignatureGroup.GasSite,
|
||||||
[SignatureGroupENG.RelicSite]: SignatureGroup.RelicSite,
|
[SignatureGroupENG.RelicSite]: SignatureGroup.RelicSite,
|
||||||
[SignatureGroupENG.DataSite]: SignatureGroup.DataSite,
|
[SignatureGroupENG.DataSite]: SignatureGroup.DataSite,
|
||||||
[SignatureGroupENG.OreSite]: SignatureGroup.OreSite,
|
[SignatureGroupENG.OreSite]: SignatureGroup.OreSite,
|
||||||
[SignatureGroupENG.CombatSite]: SignatureGroup.CombatSite,
|
[SignatureGroupENG.CombatSite]: SignatureGroup.CombatSite,
|
||||||
[SignatureGroupENG.Wormhole]: SignatureGroup.Wormhole,
|
[SignatureGroupENG.Wormhole]: SignatureGroup.Wormhole,
|
||||||
[SignatureGroupENG.CosmicSignature]: SignatureGroup.CosmicSignature,
|
[SignatureGroupENG.CosmicSignature]: SignatureGroup.CosmicSignature,
|
||||||
|
},
|
||||||
// RUSSIAN
|
RU: {
|
||||||
[SignatureGroupRU.GasSite]: SignatureGroup.GasSite,
|
[SignatureGroupRU.GasSite]: SignatureGroup.GasSite,
|
||||||
[SignatureGroupRU.RelicSite]: SignatureGroup.RelicSite,
|
[SignatureGroupRU.RelicSite]: SignatureGroup.RelicSite,
|
||||||
[SignatureGroupRU.DataSite]: SignatureGroup.DataSite,
|
[SignatureGroupRU.DataSite]: SignatureGroup.DataSite,
|
||||||
[SignatureGroupRU.OreSite]: SignatureGroup.OreSite,
|
[SignatureGroupRU.OreSite]: SignatureGroup.OreSite,
|
||||||
[SignatureGroupRU.CombatSite]: SignatureGroup.CombatSite,
|
[SignatureGroupRU.CombatSite]: SignatureGroup.CombatSite,
|
||||||
[SignatureGroupRU.Wormhole]: SignatureGroup.Wormhole,
|
[SignatureGroupRU.Wormhole]: SignatureGroup.Wormhole,
|
||||||
[SignatureGroupRU.CosmicSignature]: SignatureGroup.CosmicSignature,
|
[SignatureGroupRU.CosmicSignature]: SignatureGroup.CosmicSignature,
|
||||||
|
},
|
||||||
|
FR: {
|
||||||
|
[SignatureGroupFR.GasSite]: SignatureGroup.GasSite,
|
||||||
|
[SignatureGroupFR.RelicSite]: SignatureGroup.RelicSite,
|
||||||
|
[SignatureGroupFR.DataSite]: SignatureGroup.DataSite,
|
||||||
|
[SignatureGroupFR.OreSite]: SignatureGroup.OreSite,
|
||||||
|
[SignatureGroupFR.CombatSite]: SignatureGroup.CombatSite,
|
||||||
|
[SignatureGroupFR.Wormhole]: SignatureGroup.Wormhole,
|
||||||
|
[SignatureGroupFR.CosmicSignature]: SignatureGroup.CosmicSignature,
|
||||||
|
},
|
||||||
|
DE: {
|
||||||
|
[SignatureGroupDE.GasSite]: SignatureGroup.GasSite,
|
||||||
|
[SignatureGroupDE.RelicSite]: SignatureGroup.RelicSite,
|
||||||
|
[SignatureGroupDE.DataSite]: SignatureGroup.DataSite,
|
||||||
|
[SignatureGroupDE.OreSite]: SignatureGroup.OreSite,
|
||||||
|
[SignatureGroupDE.CombatSite]: SignatureGroup.CombatSite,
|
||||||
|
[SignatureGroupDE.Wormhole]: SignatureGroup.Wormhole,
|
||||||
|
[SignatureGroupDE.CosmicSignature]: SignatureGroup.CosmicSignature,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MAPPING_TYPE_TO_ENG = {
|
// Flatten the structure for backward compatibility
|
||||||
// ENGLISH
|
export const MAPPING_GROUP_TO_ENG: Record<string, SignatureGroup> = (() => {
|
||||||
[SignatureKindENG.CosmicSignature]: SignatureKind.CosmicSignature,
|
const flattened: Record<string, SignatureGroup> = {};
|
||||||
[SignatureKindENG.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
|
for (const [, mappings] of Object.entries(LANGUAGE_GROUP_MAPPINGS)) {
|
||||||
[SignatureKindENG.Structure]: SignatureKind.Structure,
|
Object.assign(flattened, mappings);
|
||||||
[SignatureKindENG.Ship]: SignatureKind.Ship,
|
}
|
||||||
[SignatureKindENG.Deployable]: SignatureKind.Deployable,
|
return flattened;
|
||||||
[SignatureKindENG.Drone]: SignatureKind.Drone,
|
})();
|
||||||
|
|
||||||
// RUSSIAN
|
export const getGroupIdByRawGroup = (val: string): SignatureGroup | undefined => {
|
||||||
[SignatureKindRU.CosmicSignature]: SignatureKind.CosmicSignature,
|
return MAPPING_GROUP_TO_ENG[val] || undefined;
|
||||||
[SignatureKindRU.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
|
|
||||||
[SignatureKindRU.Structure]: SignatureKind.Structure,
|
|
||||||
[SignatureKindRU.Ship]: SignatureKind.Ship,
|
|
||||||
[SignatureKindRU.Deployable]: SignatureKind.Deployable,
|
|
||||||
[SignatureKindRU.Drone]: SignatureKind.Drone,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getGroupIdByRawGroup = (val: string) => MAPPING_GROUP_TO_ENG[val as SignatureGroup];
|
|
||||||
|
|
||||||
export const SIGNATURE_WINDOW_ID = 'system_signatures_window';
|
export const SIGNATURE_WINDOW_ID = 'system_signatures_window';
|
||||||
export const SIGNATURE_SETTING_STORE_KEY = 'wanderer_system_signature_settings_v6_5';
|
export const SIGNATURE_SETTING_STORE_KEY = 'wanderer_system_signature_settings_v6_5';
|
||||||
|
|
||||||
@@ -123,7 +139,7 @@ export type Setting = {
|
|||||||
name: string;
|
name: string;
|
||||||
type: SettingsTypes;
|
type: SettingsTypes;
|
||||||
isSeparator?: boolean;
|
isSeparator?: boolean;
|
||||||
options?: { label: string; value: any }[];
|
options?: { label: string; value: number | string | boolean }[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum SIGNATURES_DELETION_TIMING {
|
export enum SIGNATURES_DELETION_TIMING {
|
||||||
@@ -208,3 +224,52 @@ export const SIGNATURE_DELETION_TIMEOUTS: SignatureDeletionTimingType = {
|
|||||||
[SIGNATURES_DELETION_TIMING.IMMEDIATE]: 0,
|
[SIGNATURES_DELETION_TIMING.IMMEDIATE]: 0,
|
||||||
[SIGNATURES_DELETION_TIMING.EXTENDED]: 30_000,
|
[SIGNATURES_DELETION_TIMING.EXTENDED]: 30_000,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Replace the flat structure with a nested structure by language
|
||||||
|
export const LANGUAGE_TYPE_MAPPINGS = {
|
||||||
|
EN: {
|
||||||
|
[SignatureKindENG.CosmicSignature]: SignatureKind.CosmicSignature,
|
||||||
|
[SignatureKindENG.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
|
||||||
|
[SignatureKindENG.Structure]: SignatureKind.Structure,
|
||||||
|
[SignatureKindENG.Ship]: SignatureKind.Ship,
|
||||||
|
[SignatureKindENG.Deployable]: SignatureKind.Deployable,
|
||||||
|
[SignatureKindENG.Drone]: SignatureKind.Drone,
|
||||||
|
[SignatureKindENG.Starbase]: SignatureKind.Starbase,
|
||||||
|
},
|
||||||
|
RU: {
|
||||||
|
[SignatureKindRU.CosmicSignature]: SignatureKind.CosmicSignature,
|
||||||
|
[SignatureKindRU.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
|
||||||
|
[SignatureKindRU.Structure]: SignatureKind.Structure,
|
||||||
|
[SignatureKindRU.Ship]: SignatureKind.Ship,
|
||||||
|
[SignatureKindRU.Deployable]: SignatureKind.Deployable,
|
||||||
|
[SignatureKindRU.Drone]: SignatureKind.Drone,
|
||||||
|
[SignatureKindRU.Starbase]: SignatureKind.Starbase,
|
||||||
|
},
|
||||||
|
FR: {
|
||||||
|
[SignatureKindFR.CosmicSignature]: SignatureKind.CosmicSignature,
|
||||||
|
[SignatureKindFR.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
|
||||||
|
[SignatureKindFR.Structure]: SignatureKind.Structure,
|
||||||
|
[SignatureKindFR.Ship]: SignatureKind.Ship,
|
||||||
|
[SignatureKindFR.Deployable]: SignatureKind.Deployable,
|
||||||
|
[SignatureKindFR.Drone]: SignatureKind.Drone,
|
||||||
|
[SignatureKindFR.Starbase]: SignatureKind.Starbase,
|
||||||
|
},
|
||||||
|
DE: {
|
||||||
|
[SignatureKindDE.CosmicSignature]: SignatureKind.CosmicSignature,
|
||||||
|
[SignatureKindDE.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
|
||||||
|
[SignatureKindDE.Structure]: SignatureKind.Structure,
|
||||||
|
[SignatureKindDE.Ship]: SignatureKind.Ship,
|
||||||
|
[SignatureKindDE.Deployable]: SignatureKind.Deployable,
|
||||||
|
[SignatureKindDE.Drone]: SignatureKind.Drone,
|
||||||
|
[SignatureKindDE.Starbase]: SignatureKind.Starbase,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Flatten the structure for backward compatibility
|
||||||
|
export const MAPPING_TYPE_TO_ENG: Record<string, SignatureKind> = (() => {
|
||||||
|
const flattened: Record<string, SignatureKind> = {};
|
||||||
|
for (const [, mappings] of Object.entries(LANGUAGE_TYPE_MAPPINGS)) {
|
||||||
|
Object.assign(flattened, mappings);
|
||||||
|
}
|
||||||
|
return flattened;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
|
||||||
import { GROUPS_LIST } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants';
|
import { GROUPS_LIST } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants';
|
||||||
|
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||||
import { getState } from './getState';
|
import { getState } from './getState';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,6 +22,7 @@ export const getActualSigs = (
|
|||||||
|
|
||||||
oldSignatures.forEach(oldSig => {
|
oldSignatures.forEach(oldSig => {
|
||||||
const newSig = newSignatures.find(s => s.eve_id === oldSig.eve_id);
|
const newSig = newSignatures.find(s => s.eve_id === oldSig.eve_id);
|
||||||
|
|
||||||
if (newSig) {
|
if (newSig) {
|
||||||
const needUpgrade = getState(GROUPS_LIST, newSig) > getState(GROUPS_LIST, oldSig);
|
const needUpgrade = getState(GROUPS_LIST, newSig) > getState(GROUPS_LIST, oldSig);
|
||||||
const mergedSig = { ...oldSig };
|
const mergedSig = { ...oldSig };
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
import { UNKNOWN_SIGNATURE_NAME } from '@/hooks/Mapper/helpers';
|
||||||
|
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
|
||||||
|
|
||||||
export const getState = (_: string[], newSig: SystemSignature) => {
|
export const getState = (_: string[], newSig: SystemSignature) => {
|
||||||
let state = -1;
|
let state = -1;
|
||||||
if (!newSig.group) {
|
if (!newSig.group || newSig.group === SignatureGroup.CosmicSignature) {
|
||||||
state = 0;
|
state = 0;
|
||||||
} else if (!newSig.name || newSig.name === '') {
|
} else if (!newSig.name || newSig.name === '' || newSig.name === UNKNOWN_SIGNATURE_NAME) {
|
||||||
state = 1;
|
state = 1;
|
||||||
} else if (newSig.name !== '') {
|
} else if (newSig.name !== '') {
|
||||||
state = 2;
|
state = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,23 +1,18 @@
|
|||||||
import { useCallback, useRef, useEffect } from 'react';
|
import { useCallback, useRef } from 'react';
|
||||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
||||||
import { prepareUpdatePayload, scheduleLazyTimers } from '../helpers';
|
import { prepareUpdatePayload } from '../helpers';
|
||||||
import { UsePendingDeletionParams } from './types';
|
import { UsePendingDeletionParams } from './types';
|
||||||
import { FINAL_DURATION_MS } from '../constants';
|
|
||||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
import { ExtendedSystemSignature } from '@/hooks/Mapper/types';
|
import { ExtendedSystemSignature } from '@/hooks/Mapper/types';
|
||||||
|
|
||||||
export function usePendingDeletions({
|
export function usePendingDeletions({
|
||||||
systemId,
|
systemId,
|
||||||
setSignatures,
|
setSignatures,
|
||||||
deletionTiming,
|
|
||||||
onPendingChange,
|
onPendingChange,
|
||||||
}: UsePendingDeletionParams) {
|
}: Omit<UsePendingDeletionParams, 'deletionTiming'>) {
|
||||||
const { outCommand } = useMapRootState();
|
const { outCommand } = useMapRootState();
|
||||||
const pendingDeletionMapRef = useRef<Record<string, ExtendedSystemSignature>>({});
|
const pendingDeletionMapRef = useRef<Record<string, ExtendedSystemSignature>>({});
|
||||||
|
|
||||||
// Use the provided deletion timing or fall back to the default
|
|
||||||
const finalDuration = deletionTiming !== undefined ? deletionTiming : FINAL_DURATION_MS;
|
|
||||||
|
|
||||||
const processRemovedSignatures = useCallback(
|
const processRemovedSignatures = useCallback(
|
||||||
async (
|
async (
|
||||||
removed: ExtendedSystemSignature[],
|
removed: ExtendedSystemSignature[],
|
||||||
@@ -25,63 +20,15 @@ export function usePendingDeletions({
|
|||||||
updated: ExtendedSystemSignature[],
|
updated: ExtendedSystemSignature[],
|
||||||
) => {
|
) => {
|
||||||
if (!removed.length) return;
|
if (!removed.length) return;
|
||||||
|
await outCommand({
|
||||||
// If deletion timing is 0, immediately delete without pending state
|
type: OutCommand.updateSignatures,
|
||||||
if (finalDuration === 0) {
|
data: prepareUpdatePayload(systemId, added, updated, removed),
|
||||||
await outCommand({
|
});
|
||||||
type: OutCommand.updateSignatures,
|
|
||||||
data: prepareUpdatePayload(systemId, added, updated, removed),
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const now = Date.now();
|
|
||||||
const processedRemoved = removed.map(r => ({
|
|
||||||
...r,
|
|
||||||
pendingDeletion: true,
|
|
||||||
pendingUntil: now + finalDuration,
|
|
||||||
}));
|
|
||||||
pendingDeletionMapRef.current = {
|
|
||||||
...pendingDeletionMapRef.current,
|
|
||||||
...processedRemoved.reduce((acc: any, sig) => {
|
|
||||||
acc[sig.eve_id] = sig;
|
|
||||||
return acc;
|
|
||||||
}, {}),
|
|
||||||
};
|
|
||||||
|
|
||||||
onPendingChange?.(pendingDeletionMapRef, clearPendingDeletions);
|
|
||||||
|
|
||||||
setSignatures(prev =>
|
|
||||||
prev.map(sig => {
|
|
||||||
if (processedRemoved.find(r => r.eve_id === sig.eve_id)) {
|
|
||||||
return { ...sig, pendingDeletion: true, pendingUntil: now + finalDuration };
|
|
||||||
}
|
|
||||||
return sig;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
scheduleLazyTimers(
|
|
||||||
processedRemoved,
|
|
||||||
pendingDeletionMapRef,
|
|
||||||
async sig => {
|
|
||||||
await outCommand({
|
|
||||||
type: OutCommand.updateSignatures,
|
|
||||||
data: prepareUpdatePayload(systemId, [], [], [sig]),
|
|
||||||
});
|
|
||||||
delete pendingDeletionMapRef.current[sig.eve_id];
|
|
||||||
setSignatures(prev => prev.filter(x => x.eve_id !== sig.eve_id));
|
|
||||||
onPendingChange?.(pendingDeletionMapRef, clearPendingDeletions);
|
|
||||||
},
|
|
||||||
finalDuration,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
[systemId, outCommand, finalDuration],
|
[systemId, outCommand],
|
||||||
);
|
);
|
||||||
|
|
||||||
const clearPendingDeletions = useCallback(() => {
|
const clearPendingDeletions = useCallback(() => {
|
||||||
Object.values(pendingDeletionMapRef.current).forEach(({ finalTimeoutId }) => {
|
|
||||||
clearTimeout(finalTimeoutId);
|
|
||||||
});
|
|
||||||
pendingDeletionMapRef.current = {};
|
pendingDeletionMapRef.current = {};
|
||||||
setSignatures(prev => prev.map(x => (x.pendingDeletion ? { ...x, pendingDeletion: false } : x)));
|
setSignatures(prev => prev.map(x => (x.pendingDeletion ? { ...x, pendingDeletion: false } : x)));
|
||||||
onPendingChange?.(pendingDeletionMapRef, clearPendingDeletions);
|
onPendingChange?.(pendingDeletionMapRef, clearPendingDeletions);
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
|
||||||
import useRefState from 'react-usestateref';
|
|
||||||
import { useMapEventListener } from '@/hooks/Mapper/events';
|
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||||
|
import { parseSignatures } from '@/hooks/Mapper/helpers';
|
||||||
import { Commands, ExtendedSystemSignature, SignatureKind } from '@/hooks/Mapper/types';
|
import { Commands, ExtendedSystemSignature, SignatureKind } from '@/hooks/Mapper/types';
|
||||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
||||||
import { parseSignatures } from '@/hooks/Mapper/helpers';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import useRefState from 'react-usestateref';
|
||||||
|
|
||||||
import { getActualSigs } from '../helpers';
|
|
||||||
import { useSignatureFetching } from './useSignatureFetching';
|
|
||||||
import { usePendingDeletions } from './usePendingDeletions';
|
|
||||||
import { UseSystemSignaturesDataProps } from './types';
|
|
||||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
|
||||||
import { SETTINGS_KEYS } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
import { SETTINGS_KEYS } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||||
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
|
import { getActualSigs } from '../helpers';
|
||||||
|
import { UseSystemSignaturesDataProps } from './types';
|
||||||
|
import { usePendingDeletions } from './usePendingDeletions';
|
||||||
|
import { useSignatureFetching } from './useSignatureFetching';
|
||||||
|
|
||||||
export const useSystemSignaturesData = ({
|
export const useSystemSignaturesData = ({
|
||||||
systemId,
|
systemId,
|
||||||
@@ -18,16 +18,18 @@ export const useSystemSignaturesData = ({
|
|||||||
onCountChange,
|
onCountChange,
|
||||||
onPendingChange,
|
onPendingChange,
|
||||||
onLazyDeleteChange,
|
onLazyDeleteChange,
|
||||||
deletionTiming,
|
onSignatureDeleted,
|
||||||
}: UseSystemSignaturesDataProps) => {
|
}: Omit<UseSystemSignaturesDataProps, 'deletionTiming'> & {
|
||||||
|
onSignatureDeleted?: (deletedIds: string[]) => void;
|
||||||
|
}) => {
|
||||||
const { outCommand } = useMapRootState();
|
const { outCommand } = useMapRootState();
|
||||||
const [signatures, setSignatures, signaturesRef] = useRefState<ExtendedSystemSignature[]>([]);
|
const [signatures, setSignatures, signaturesRef] = useRefState<ExtendedSystemSignature[]>([]);
|
||||||
const [selectedSignatures, setSelectedSignatures] = useState<ExtendedSystemSignature[]>([]);
|
const [selectedSignatures, setSelectedSignatures] = useState<ExtendedSystemSignature[]>([]);
|
||||||
|
const [hasUnsupportedLanguage, setHasUnsupportedLanguage] = useState<boolean>(false);
|
||||||
|
|
||||||
const { pendingDeletionMapRef, processRemovedSignatures, clearPendingDeletions } = usePendingDeletions({
|
const { pendingDeletionMapRef, processRemovedSignatures, clearPendingDeletions } = usePendingDeletions({
|
||||||
systemId,
|
systemId,
|
||||||
setSignatures,
|
setSignatures,
|
||||||
deletionTiming,
|
|
||||||
onPendingChange,
|
onPendingChange,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -42,19 +44,40 @@ export const useSystemSignaturesData = ({
|
|||||||
async (clipboardString: string) => {
|
async (clipboardString: string) => {
|
||||||
const lazyDeleteValue = settings[SETTINGS_KEYS.LAZY_DELETE_SIGNATURES] as boolean;
|
const lazyDeleteValue = settings[SETTINGS_KEYS.LAZY_DELETE_SIGNATURES] as boolean;
|
||||||
|
|
||||||
|
// Parse the incoming signatures
|
||||||
const incomingSignatures = parseSignatures(
|
const incomingSignatures = parseSignatures(
|
||||||
clipboardString,
|
clipboardString,
|
||||||
Object.keys(settings).filter(skey => skey in SignatureKind),
|
Object.keys(settings).filter(skey => skey in SignatureKind),
|
||||||
) as ExtendedSystemSignature[];
|
) as ExtendedSystemSignature[];
|
||||||
|
|
||||||
|
if (incomingSignatures.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if any signatures might be using unsupported languages
|
||||||
|
// This is a basic heuristic: if we have signatures where the original group wasn't mapped
|
||||||
|
const clipboardRows = clipboardString.split('\n').filter(row => row.trim() !== '');
|
||||||
|
const detectedSignatureCount = clipboardRows.filter(row => row.match(/^[A-Z]{3}-\d{3}/)).length;
|
||||||
|
|
||||||
|
// If we detected valid IDs but got fewer parsed signatures, we might have language issues
|
||||||
|
if (detectedSignatureCount > 0 && incomingSignatures.length < detectedSignatureCount) {
|
||||||
|
setHasUnsupportedLanguage(true);
|
||||||
|
} else {
|
||||||
|
setHasUnsupportedLanguage(false);
|
||||||
|
}
|
||||||
|
|
||||||
const currentNonPending = lazyDeleteValue
|
const currentNonPending = lazyDeleteValue
|
||||||
? signaturesRef.current.filter(sig => !sig.pendingDeletion)
|
? signaturesRef.current.filter(sig => !sig.pendingDeletion)
|
||||||
: signaturesRef.current.filter(sig => !sig.pendingDeletion || !sig.pendingAddition);
|
: signaturesRef.current.filter(sig => !sig.pendingDeletion || !sig.pendingAddition);
|
||||||
|
|
||||||
const { added, updated, removed } = getActualSigs(currentNonPending, incomingSignatures, !lazyDeleteValue, true);
|
const { added, updated, removed } = getActualSigs(currentNonPending, incomingSignatures, !lazyDeleteValue, false);
|
||||||
|
|
||||||
if (removed.length > 0) {
|
if (removed.length > 0) {
|
||||||
await processRemovedSignatures(removed, added, updated);
|
await processRemovedSignatures(removed, added, updated);
|
||||||
|
if (onSignatureDeleted) {
|
||||||
|
const deletedIds = removed.map(sig => sig.eve_id);
|
||||||
|
onSignatureDeleted(deletedIds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updated.length !== 0 || added.length !== 0) {
|
if (updated.length !== 0 || added.length !== 0) {
|
||||||
@@ -74,17 +97,16 @@ export const useSystemSignaturesData = ({
|
|||||||
onLazyDeleteChange?.(false);
|
onLazyDeleteChange?.(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[settings, signaturesRef, processRemovedSignatures, outCommand, systemId, onLazyDeleteChange],
|
[settings, signaturesRef, processRemovedSignatures, outCommand, systemId, onLazyDeleteChange, onSignatureDeleted],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDeleteSelected = useCallback(async () => {
|
const handleDeleteSelected = useCallback(async () => {
|
||||||
if (!selectedSignatures.length) return;
|
if (!selectedSignatures.length) return;
|
||||||
const selectedIds = selectedSignatures.map(s => s.eve_id);
|
const selectedIds = selectedSignatures.map(s => s.eve_id);
|
||||||
const finalList = signatures.filter(s => !selectedIds.includes(s.eve_id));
|
const finalList = signatures.filter(s => !selectedIds.includes(s.eve_id));
|
||||||
|
|
||||||
await handleUpdateSignatures(finalList, false, true);
|
await handleUpdateSignatures(finalList, false, true);
|
||||||
setSelectedSignatures([]);
|
setSelectedSignatures([]);
|
||||||
}, [selectedSignatures, signatures]);
|
}, [handleUpdateSignatures, selectedSignatures, signatures]);
|
||||||
|
|
||||||
const handleSelectAll = useCallback(() => {
|
const handleSelectAll = useCallback(() => {
|
||||||
setSelectedSignatures(signatures);
|
setSelectedSignatures(signatures);
|
||||||
@@ -115,11 +137,12 @@ export const useSystemSignaturesData = ({
|
|||||||
}, [signatures]);
|
}, [signatures]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
signatures,
|
signatures: signatures.filter(sig => !sig.deleted),
|
||||||
selectedSignatures,
|
selectedSignatures,
|
||||||
setSelectedSignatures,
|
setSelectedSignatures,
|
||||||
handleDeleteSelected,
|
handleDeleteSelected,
|
||||||
handleSelectAll,
|
handleSelectAll,
|
||||||
handlePaste,
|
handlePaste,
|
||||||
|
hasUnsupportedLanguage,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -32,7 +32,12 @@ export function parseFormatOneLine(line: string): StructureItem | null {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rawTypeName != STRUCTURE_TYPE_MAP[rawTypeId]) {
|
// in some localizations (like russian) there is an option called "mark names with *"
|
||||||
|
// The example output will be "35826 Itamo - Research & Production Azbel* 609 м"
|
||||||
|
// so, let's fix this
|
||||||
|
const localizationFixedName = rawTypeName.replace("*", "");
|
||||||
|
|
||||||
|
if (localizationFixedName != STRUCTURE_TYPE_MAP[rawTypeId]) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import { Commands, OutCommand } from '@/hooks/Mapper/types';
|
||||||
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
|
import {
|
||||||
|
AddHubCommand,
|
||||||
|
LoadRoutesCommand,
|
||||||
|
RoutesImperativeHandle,
|
||||||
|
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/types.ts';
|
||||||
|
import { useCallback, useRef } from 'react';
|
||||||
|
import { RoutesWidget } from '@/hooks/Mapper/components/mapInterface/widgets';
|
||||||
|
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||||
|
|
||||||
|
export const WRoutesPublic = () => {
|
||||||
|
const {
|
||||||
|
outCommand,
|
||||||
|
storedSettings: { settingsRoutes, settingsRoutesUpdate },
|
||||||
|
data: { hubs, routes },
|
||||||
|
} = useMapRootState();
|
||||||
|
|
||||||
|
const ref = useRef<RoutesImperativeHandle>(null);
|
||||||
|
|
||||||
|
const loadRoutesCommand: LoadRoutesCommand = useCallback(
|
||||||
|
async (systemId, routesSettings) => {
|
||||||
|
outCommand({
|
||||||
|
type: OutCommand.getRoutes,
|
||||||
|
data: {
|
||||||
|
system_id: systemId,
|
||||||
|
routes_settings: routesSettings,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[outCommand],
|
||||||
|
);
|
||||||
|
|
||||||
|
const addHubCommand: AddHubCommand = useCallback(
|
||||||
|
async systemId => {
|
||||||
|
if (hubs.includes(systemId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await outCommand({
|
||||||
|
type: OutCommand.addHub,
|
||||||
|
data: { system_id: systemId },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[hubs, outCommand],
|
||||||
|
);
|
||||||
|
|
||||||
|
const toggleHubCommand: AddHubCommand = useCallback(
|
||||||
|
async (systemId: string | undefined) => {
|
||||||
|
if (!systemId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
outCommand({
|
||||||
|
type: !hubs.includes(systemId) ? OutCommand.addHub : OutCommand.deleteHub,
|
||||||
|
data: {
|
||||||
|
system_id: systemId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[hubs, outCommand],
|
||||||
|
);
|
||||||
|
|
||||||
|
useMapEventListener(event => {
|
||||||
|
if (event.name === Commands.routes) {
|
||||||
|
ref.current?.stopLoading();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RoutesWidget
|
||||||
|
ref={ref}
|
||||||
|
title="Routes"
|
||||||
|
data={settingsRoutes}
|
||||||
|
update={settingsRoutesUpdate}
|
||||||
|
hubs={hubs}
|
||||||
|
routesList={routes}
|
||||||
|
loadRoutesCommand={loadRoutesCommand}
|
||||||
|
addHubCommand={addHubCommand}
|
||||||
|
toggleHubCommand={toggleHubCommand}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './WRoutesPublic';
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import { Commands, OutCommand } from '@/hooks/Mapper/types';
|
||||||
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
|
import {
|
||||||
|
AddHubCommand,
|
||||||
|
LoadRoutesCommand,
|
||||||
|
RoutesImperativeHandle,
|
||||||
|
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/types.ts';
|
||||||
|
import { useCallback, useRef } from 'react';
|
||||||
|
import { RoutesWidget } from '@/hooks/Mapper/components/mapInterface/widgets';
|
||||||
|
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||||
|
|
||||||
|
export const WRoutesUser = () => {
|
||||||
|
const {
|
||||||
|
outCommand,
|
||||||
|
storedSettings: { settingsRoutes, settingsRoutesUpdate },
|
||||||
|
data: { userHubs, userRoutes },
|
||||||
|
} = useMapRootState();
|
||||||
|
|
||||||
|
const ref = useRef<RoutesImperativeHandle>(null);
|
||||||
|
|
||||||
|
const loadRoutesCommand: LoadRoutesCommand = useCallback(
|
||||||
|
async (systemId, routesSettings) => {
|
||||||
|
outCommand({
|
||||||
|
type: OutCommand.getUserRoutes,
|
||||||
|
data: {
|
||||||
|
system_id: systemId,
|
||||||
|
routes_settings: routesSettings,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[outCommand],
|
||||||
|
);
|
||||||
|
|
||||||
|
const addHubCommand: AddHubCommand = useCallback(
|
||||||
|
async systemId => {
|
||||||
|
if (userHubs.includes(systemId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await outCommand({
|
||||||
|
type: OutCommand.addUserHub,
|
||||||
|
data: { system_id: systemId },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[userHubs, outCommand],
|
||||||
|
);
|
||||||
|
|
||||||
|
const toggleHubCommand: AddHubCommand = useCallback(
|
||||||
|
async (systemId: string | undefined) => {
|
||||||
|
if (!systemId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
outCommand({
|
||||||
|
type: !userHubs.includes(systemId) ? OutCommand.addUserHub : OutCommand.deleteUserHub,
|
||||||
|
data: {
|
||||||
|
system_id: systemId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[userHubs, outCommand],
|
||||||
|
);
|
||||||
|
|
||||||
|
useMapEventListener(event => {
|
||||||
|
if (event.name === Commands.userRoutes) {
|
||||||
|
ref.current?.stopLoading();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RoutesWidget
|
||||||
|
ref={ref}
|
||||||
|
title="User Routes"
|
||||||
|
data={settingsRoutes}
|
||||||
|
update={settingsRoutesUpdate}
|
||||||
|
hubs={userHubs}
|
||||||
|
routesList={userRoutes}
|
||||||
|
loadRoutesCommand={loadRoutesCommand}
|
||||||
|
addHubCommand={addHubCommand}
|
||||||
|
toggleHubCommand={toggleHubCommand}
|
||||||
|
isRestricted
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './WRoutesUser';
|
||||||
@@ -1,26 +1,26 @@
|
|||||||
import React, { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { DetailedKill } from '@/hooks/Mapper/types/kills';
|
import { DetailedKill } from '@/hooks/Mapper/types/kills';
|
||||||
import { VirtualScroller } from 'primereact/virtualscroller';
|
import { VirtualScroller } from 'primereact/virtualscroller';
|
||||||
import { useSystemKillsItemTemplate } from '../hooks/useSystemKillsItemTemplate';
|
import { useSystemKillsItemTemplate } from '../hooks/useSystemKillsItemTemplate';
|
||||||
import classes from './SystemKillsContent.module.scss';
|
import clsx from 'clsx';
|
||||||
|
import { WithClassName } from '@/hooks/Mapper/types/common.ts';
|
||||||
|
|
||||||
export const ITEM_HEIGHT = 35;
|
export const KILLS_ROW_HEIGHT = 40;
|
||||||
|
|
||||||
export interface SystemKillsContentProps {
|
export type SystemKillsContentProps = {
|
||||||
kills: DetailedKill[];
|
kills: DetailedKill[];
|
||||||
systemNameMap: Record<string, string>;
|
|
||||||
onlyOneSystem?: boolean;
|
onlyOneSystem?: boolean;
|
||||||
timeRange?: number;
|
timeRange?: number;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
}
|
} & WithClassName;
|
||||||
|
|
||||||
export const SystemKillsContent: React.FC<SystemKillsContentProps> = ({
|
export const SystemKillsList = ({
|
||||||
kills,
|
kills,
|
||||||
systemNameMap,
|
|
||||||
onlyOneSystem = false,
|
onlyOneSystem = false,
|
||||||
timeRange = 4,
|
timeRange = 4,
|
||||||
limit,
|
limit,
|
||||||
}) => {
|
className,
|
||||||
|
}: SystemKillsContentProps) => {
|
||||||
const processedKills = useMemo(() => {
|
const processedKills = useMemo(() => {
|
||||||
if (!kills || kills.length === 0) return [];
|
if (!kills || kills.length === 0) return [];
|
||||||
|
|
||||||
@@ -47,30 +47,17 @@ export const SystemKillsContent: React.FC<SystemKillsContentProps> = ({
|
|||||||
return filteredKills;
|
return filteredKills;
|
||||||
}, [kills, timeRange, limit]);
|
}, [kills, timeRange, limit]);
|
||||||
|
|
||||||
const itemTemplate = useSystemKillsItemTemplate(systemNameMap, onlyOneSystem);
|
const itemTemplate = useSystemKillsItemTemplate(onlyOneSystem);
|
||||||
|
|
||||||
// Define style for the VirtualScroller
|
|
||||||
const virtualScrollerStyle: React.CSSProperties = {
|
|
||||||
boxSizing: 'border-box',
|
|
||||||
height: '100%', // Use 100% height to fill the container
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full flex flex-col overflow-hidden" data-testid="system-kills-content">
|
<VirtualScroller
|
||||||
<VirtualScroller
|
items={processedKills}
|
||||||
items={processedKills}
|
itemSize={KILLS_ROW_HEIGHT}
|
||||||
itemSize={ITEM_HEIGHT}
|
itemTemplate={itemTemplate}
|
||||||
itemTemplate={itemTemplate}
|
className={clsx(
|
||||||
className={`w-full h-full flex-1 select-none ${classes.VirtualScroller}`}
|
'w-full flex-1 select-none !h-full overflow-x-hidden overflow-y-auto custom-scrollbar',
|
||||||
style={virtualScrollerStyle}
|
className,
|
||||||
pt={{
|
)}
|
||||||
content: {
|
/>
|
||||||
className: `custom-scrollbar ${classes.scrollerContent}`,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SystemKillsContent;
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './SystemKillsList';
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
|
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
|
||||||
|
import { SystemKillsList } from './SystemKillsList';
|
||||||
|
import { KillsHeader } from './components/SystemKillsHeader';
|
||||||
|
import { useKillsWidgetSettings } from './hooks/useKillsWidgetSettings';
|
||||||
|
import { useSystemKills } from './hooks/useSystemKills';
|
||||||
|
import { KillsSettingsDialog } from './components/SystemKillsSettingsDialog';
|
||||||
|
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace';
|
||||||
|
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
|
||||||
|
|
||||||
|
const SystemKillsContent = () => {
|
||||||
|
const {
|
||||||
|
data: { selectedSystems, isSubscriptionActive },
|
||||||
|
outCommand,
|
||||||
|
} = useMapRootState();
|
||||||
|
|
||||||
|
const [systemId] = selectedSystems || [];
|
||||||
|
|
||||||
|
const systemStaticInfo = getSystemStaticInfo(systemId)!;
|
||||||
|
|
||||||
|
const [settings] = useKillsWidgetSettings();
|
||||||
|
const visible = settings.showAll;
|
||||||
|
|
||||||
|
const { kills, isLoading, error } = useSystemKills({
|
||||||
|
systemId,
|
||||||
|
outCommand,
|
||||||
|
showAllVisible: visible,
|
||||||
|
sinceHours: settings.timeRange,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isNothingSelected = !systemId && !visible;
|
||||||
|
const showLoading = isLoading && kills.length === 0;
|
||||||
|
|
||||||
|
const filteredKills = useMemo(() => {
|
||||||
|
if (!settings.whOnly || !visible) return kills;
|
||||||
|
return kills.filter(kill => {
|
||||||
|
if (!systemStaticInfo) {
|
||||||
|
console.warn(`System with id ${kill.solar_system_id} not found.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return isWormholeSpace(systemStaticInfo.system_class);
|
||||||
|
});
|
||||||
|
}, [kills, settings.whOnly, systemStaticInfo, visible]);
|
||||||
|
|
||||||
|
if (!isSubscriptionActive) {
|
||||||
|
return (
|
||||||
|
<div className="w-full h-full flex items-center justify-center">
|
||||||
|
<span className="select-none text-center text-stone-400/80 text-sm">
|
||||||
|
Kills available with 'Active' map subscription only (contact map administrators)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNothingSelected) {
|
||||||
|
return (
|
||||||
|
<div className="w-full h-full flex items-center justify-center">
|
||||||
|
<span className="select-none text-center text-stone-400/80 text-sm">
|
||||||
|
No system selected (or toggle "Show all systems")
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showLoading) {
|
||||||
|
return (
|
||||||
|
<div className="w-full h-full flex items-center justify-center">
|
||||||
|
<span className="select-none text-center text-stone-400/80 text-sm">Loading Kills...</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className="w-full h-full flex items-center justify-center">
|
||||||
|
<span className="select-none text-center text-red-400 text-sm">{error}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!filteredKills || filteredKills.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className="w-full h-full flex items-center justify-center">
|
||||||
|
<span className="select-none text-center text-stone-400/80 text-sm">No kills found</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <SystemKillsList kills={filteredKills} onlyOneSystem={!visible} timeRange={settings.timeRange} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WSystemKills = () => {
|
||||||
|
const [settingsDialogVisible, setSettingsDialogVisible] = useState(false);
|
||||||
|
const {
|
||||||
|
data: { selectedSystems },
|
||||||
|
} = useMapRootState();
|
||||||
|
|
||||||
|
const [systemId] = selectedSystems || [];
|
||||||
|
|
||||||
|
const handleOpenSettings = useCallback(() => setSettingsDialogVisible(true), []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Widget label={<KillsHeader systemId={systemId} onOpenSettings={handleOpenSettings} />}>
|
||||||
|
<SystemKillsContent />
|
||||||
|
{settingsDialogVisible && <KillsSettingsDialog visible setVisible={setSettingsDialogVisible} />}
|
||||||
|
</Widget>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { DetailedKill } from '@/hooks/Mapper/types/kills';
|
||||||
|
import { VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
|
||||||
|
import { KillRowDetail } from '@/hooks/Mapper/components/mapInterface/widgets/WSystemKills/components/KillRowDetail.tsx';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
|
||||||
|
|
||||||
|
export const KillItemTemplate = (
|
||||||
|
onlyOneSystem: boolean,
|
||||||
|
kill: DetailedKill,
|
||||||
|
options: VirtualScrollerTemplateOptions,
|
||||||
|
) => {
|
||||||
|
const systemName = getSystemStaticInfo(kill.solar_system_id)?.solar_system_name || `System ${kill.solar_system_id}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ height: `${options.props.itemSize}px` }}>
|
||||||
|
<KillRowDetail
|
||||||
|
killDetails={kill}
|
||||||
|
systemName={systemName}
|
||||||
|
onlyOneSystem={onlyOneSystem}
|
||||||
|
className={clsx(options.odd && 'bg-stone-800/50')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import { useMemo } from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { DetailedKill } from '@/hooks/Mapper/types/kills';
|
import { DetailedKill } from '@/hooks/Mapper/types/kills';
|
||||||
import {
|
import {
|
||||||
@@ -14,14 +14,15 @@ import {
|
|||||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
|
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
|
||||||
import classes from './KillRowDetail.module.scss';
|
import classes from './KillRowDetail.module.scss';
|
||||||
import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
|
import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
|
||||||
|
import { WithClassName } from '@/hooks/Mapper/types/common.ts';
|
||||||
|
|
||||||
export interface CompactKillRowProps {
|
export type CompactKillRowProps = {
|
||||||
killDetails: DetailedKill;
|
killDetails: DetailedKill;
|
||||||
systemName: string;
|
systemName: string;
|
||||||
onlyOneSystem: boolean;
|
onlyOneSystem: boolean;
|
||||||
}
|
} & WithClassName;
|
||||||
|
|
||||||
export const KillRowDetail: React.FC<CompactKillRowProps> = ({ killDetails, systemName, onlyOneSystem }) => {
|
export const KillRowDetail = ({ killDetails, systemName, onlyOneSystem, className }: CompactKillRowProps) => {
|
||||||
const {
|
const {
|
||||||
killmail_id = 0,
|
killmail_id = 0,
|
||||||
// Victim data
|
// Victim data
|
||||||
@@ -80,13 +81,24 @@ export const KillRowDetail: React.FC<CompactKillRowProps> = ({ killDetails, syst
|
|||||||
'Victim',
|
'Victim',
|
||||||
);
|
);
|
||||||
|
|
||||||
const { url: attackerPrimaryImageUrl, tooltip: attackerPrimaryTooltip } = getAttackerPrimaryImageAndTooltip(
|
const { url: attackerPrimaryImageUrl, tooltip: attackerPrimaryTooltip } = useMemo(
|
||||||
attackerIsNpc,
|
() =>
|
||||||
attackerAllianceLogoUrl,
|
getAttackerPrimaryImageAndTooltip(
|
||||||
attackerCorpLogoUrl,
|
attackerIsNpc,
|
||||||
final_blow_alliance_name,
|
attackerAllianceLogoUrl,
|
||||||
final_blow_corp_name,
|
attackerCorpLogoUrl,
|
||||||
final_blow_ship_type_id,
|
final_blow_alliance_name,
|
||||||
|
final_blow_corp_name,
|
||||||
|
final_blow_ship_type_id,
|
||||||
|
),
|
||||||
|
[
|
||||||
|
attackerAllianceLogoUrl,
|
||||||
|
attackerCorpLogoUrl,
|
||||||
|
attackerIsNpc,
|
||||||
|
final_blow_alliance_name,
|
||||||
|
final_blow_corp_name,
|
||||||
|
final_blow_ship_type_id,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Define attackerTicker to use the alliance ticker if available, otherwise the corp ticker.
|
// Define attackerTicker to use the alliance ticker if available, otherwise the corp ticker.
|
||||||
@@ -100,6 +112,8 @@ export const KillRowDetail: React.FC<CompactKillRowProps> = ({ killDetails, syst
|
|||||||
className={clsx(
|
className={clsx(
|
||||||
'h-10 flex items-center border-b border-stone-800',
|
'h-10 flex items-center border-b border-stone-800',
|
||||||
'text-xs whitespace-nowrap overflow-hidden leading-none',
|
'text-xs whitespace-nowrap overflow-hidden leading-none',
|
||||||
|
'px-1',
|
||||||
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{/* Victim Section */}
|
{/* Victim Section */}
|
||||||
@@ -142,12 +156,14 @@ export const KillRowDetail: React.FC<CompactKillRowProps> = ({ killDetails, syst
|
|||||||
{victim_char_name}
|
{victim_char_name}
|
||||||
<span className="text-stone-400"> / {victimAffiliationTicker}</span>
|
<span className="text-stone-400"> / {victimAffiliationTicker}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="truncate text-stone-300">
|
<div className="truncate text-stone-300 flex items-center gap-1">
|
||||||
{victim_ship_name}
|
<span className="text-stone-400 overflow-hidden text-ellipsis whitespace-nowrap max-w-[140px]">
|
||||||
|
{victim_ship_name}
|
||||||
|
</span>
|
||||||
{killValueFormatted && (
|
{killValueFormatted && (
|
||||||
<>
|
<>
|
||||||
<span className="ml-1 text-stone-400">/</span>
|
<span className="text-stone-400">/</span>
|
||||||
<span className="ml-1 text-green-400">{killValueFormatted}</span>
|
<span className="text-green-400">{killValueFormatted}</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
import { useCallback, useMemo, useState, useEffect, useRef } from 'react';
|
import { useCallback, useMemo, useState, useEffect, useRef } from 'react';
|
||||||
import debounce from 'lodash.debounce';
|
import debounce from 'lodash.debounce';
|
||||||
import { Commands, OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
||||||
import { DetailedKill } from '@/hooks/Mapper/types/kills';
|
import { DetailedKill } from '@/hooks/Mapper/types/kills';
|
||||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
import { useKillsWidgetSettings } from './useKillsWidgetSettings';
|
import { useKillsWidgetSettings } from './useKillsWidgetSettings';
|
||||||
import { useMapEventListener, MapEvent } from '@/hooks/Mapper/events';
|
|
||||||
|
|
||||||
interface UseSystemKillsProps {
|
interface UseSystemKillsProps {
|
||||||
systemId?: string;
|
systemId?: string;
|
||||||
@@ -29,10 +28,6 @@ function combineKills(existing: DetailedKill[], incoming: DetailedKill[], sinceH
|
|||||||
return Object.values(byId);
|
return Object.values(byId);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DetailedKillsEvent extends MapEvent<Commands> {
|
|
||||||
payload: Record<string, DetailedKill[]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useSystemKills({ systemId, outCommand, showAllVisible = false, sinceHours = 24 }: UseSystemKillsProps) {
|
export function useSystemKills({ systemId, outCommand, showAllVisible = false, sinceHours = 24 }: UseSystemKillsProps) {
|
||||||
const { data, update } = useMapRootState();
|
const { data, update } = useMapRootState();
|
||||||
const { detailedKills = {}, systems = [] } = data;
|
const { detailedKills = {}, systems = [] } = data;
|
||||||
@@ -41,32 +36,6 @@ export function useSystemKills({ systemId, outCommand, showAllVisible = false, s
|
|||||||
|
|
||||||
const effectiveSinceHours = sinceHours;
|
const effectiveSinceHours = sinceHours;
|
||||||
|
|
||||||
const updateDetailedKills = useCallback(
|
|
||||||
(newKillsMap: Record<string, DetailedKill[]>) => {
|
|
||||||
update(prev => {
|
|
||||||
const oldKills = prev.detailedKills ?? {};
|
|
||||||
const updated = { ...oldKills };
|
|
||||||
for (const [sid, killsArr] of Object.entries(newKillsMap)) {
|
|
||||||
updated[sid] = killsArr;
|
|
||||||
}
|
|
||||||
return { ...prev, detailedKills: updated };
|
|
||||||
}, true);
|
|
||||||
},
|
|
||||||
[update],
|
|
||||||
);
|
|
||||||
|
|
||||||
useMapEventListener((event: MapEvent<Commands>) => {
|
|
||||||
if (event.name === Commands.detailedKillsUpdated) {
|
|
||||||
const detailedEvent = event as DetailedKillsEvent;
|
|
||||||
if (systemId && !Object.keys(detailedEvent.payload).includes(systemId.toString())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
updateDetailedKills(detailedEvent.payload);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
const effectiveSystemIds = useMemo(() => {
|
const effectiveSystemIds = useMemo(() => {
|
||||||
if (showAllVisible) {
|
if (showAllVisible) {
|
||||||
return systems.map(s => s.id).filter(id => !excludedSystems.includes(Number(id)));
|
return systems.map(s => s.id).filter(id => !excludedSystems.includes(Number(id)));
|
||||||
@@ -4,10 +4,9 @@ import { VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
|
|||||||
import { DetailedKill } from '@/hooks/Mapper/types/kills';
|
import { DetailedKill } from '@/hooks/Mapper/types/kills';
|
||||||
import { KillItemTemplate } from '../components/KillItemTemplate';
|
import { KillItemTemplate } from '../components/KillItemTemplate';
|
||||||
|
|
||||||
export function useSystemKillsItemTemplate(systemNameMap: Record<string, string>, onlyOneSystem: boolean) {
|
export function useSystemKillsItemTemplate(onlyOneSystem: boolean) {
|
||||||
return useCallback(
|
return useCallback(
|
||||||
(kill: DetailedKill, options: VirtualScrollerTemplateOptions) =>
|
(kill: DetailedKill, options: VirtualScrollerTemplateOptions) => KillItemTemplate(onlyOneSystem, kill, options),
|
||||||
KillItemTemplate(systemNameMap, onlyOneSystem, kill, options),
|
[onlyOneSystem],
|
||||||
[systemNameMap, onlyOneSystem],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './WSystemKills';
|
||||||
@@ -3,4 +3,7 @@ export * from './SystemInfo';
|
|||||||
export * from './RoutesWidget';
|
export * from './RoutesWidget';
|
||||||
export * from './SystemSignatures';
|
export * from './SystemSignatures';
|
||||||
export * from './SystemStructures';
|
export * from './SystemStructures';
|
||||||
export * from './SystemKills';
|
export * from './WSystemKills';
|
||||||
|
export * from './WRoutesUser';
|
||||||
|
export * from './WRoutesPublic';
|
||||||
|
export * from './CommentsWidget';
|
||||||
|
|||||||
@@ -9,28 +9,42 @@ import { MapContextMenu } from '@/hooks/Mapper/components/mapRootContent/compone
|
|||||||
import { useSkipContextMenu } from '@/hooks/Mapper/hooks/useSkipContextMenu';
|
import { useSkipContextMenu } from '@/hooks/Mapper/hooks/useSkipContextMenu';
|
||||||
import { MapSettings } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings';
|
import { MapSettings } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings';
|
||||||
import { CharacterActivity } from '@/hooks/Mapper/components/mapRootContent/components/CharacterActivity';
|
import { CharacterActivity } from '@/hooks/Mapper/components/mapRootContent/components/CharacterActivity';
|
||||||
import { TrackAndFollow } from '@/hooks/Mapper/components/mapRootContent/components/TrackAndFollow/TrackAndFollow';
|
|
||||||
import { useCharacterActivityHandlers } from './hooks/useCharacterActivityHandlers';
|
import { useCharacterActivityHandlers } from './hooks/useCharacterActivityHandlers';
|
||||||
import { useTrackAndFollowHandlers } from './hooks/useTrackAndFollowHandlers';
|
import { TrackingDialog } from '@/hooks/Mapper/components/mapRootContent/components/TrackingDialog';
|
||||||
|
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||||
|
import { Commands } from '@/hooks/Mapper/types';
|
||||||
|
|
||||||
export interface MapRootContentProps {}
|
export interface MapRootContentProps {}
|
||||||
|
|
||||||
// eslint-disable-next-line no-empty-pattern
|
// eslint-disable-next-line no-empty-pattern
|
||||||
export const MapRootContent = ({}: MapRootContentProps) => {
|
export const MapRootContent = ({}: MapRootContentProps) => {
|
||||||
const { interfaceSettings, data } = useMapRootState();
|
const {
|
||||||
|
storedSettings: { interfaceSettings },
|
||||||
|
data,
|
||||||
|
} = useMapRootState();
|
||||||
const { isShowMenu } = interfaceSettings;
|
const { isShowMenu } = interfaceSettings;
|
||||||
const { showCharacterActivity, showTrackAndFollow } = data;
|
const { showCharacterActivity } = data;
|
||||||
const { handleHideCharacterActivity } = useCharacterActivityHandlers();
|
const { handleHideCharacterActivity } = useCharacterActivityHandlers();
|
||||||
const { handleHideTracking } = useTrackAndFollowHandlers();
|
|
||||||
|
|
||||||
const themeClass = `${interfaceSettings.theme ?? 'default'}-theme`;
|
const themeClass = `${interfaceSettings.theme ?? 'default'}-theme`;
|
||||||
|
|
||||||
const [showOnTheMap, setShowOnTheMap] = useState(false);
|
const [showOnTheMap, setShowOnTheMap] = useState(false);
|
||||||
const [showMapSettings, setShowMapSettings] = useState(false);
|
const [showMapSettings, setShowMapSettings] = useState(false);
|
||||||
|
const [showTrackingDialog, setShowTrackingDialog] = useState(false);
|
||||||
|
|
||||||
|
/* Important Notice - this solution needs for use one instance of MapInterface */
|
||||||
const mapInterface = <MapInterface />;
|
const mapInterface = <MapInterface />;
|
||||||
|
|
||||||
const handleShowOnTheMap = useCallback(() => setShowOnTheMap(true), []);
|
const handleShowOnTheMap = useCallback(() => setShowOnTheMap(true), []);
|
||||||
const handleShowMapSettings = useCallback(() => setShowMapSettings(true), []);
|
const handleShowMapSettings = useCallback(() => setShowMapSettings(true), []);
|
||||||
|
const handleShowTrackingDialog = useCallback(() => setShowTrackingDialog(true), []);
|
||||||
|
|
||||||
|
useMapEventListener(event => {
|
||||||
|
if (event.name === Commands.showTracking) {
|
||||||
|
setShowTrackingDialog(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
useSkipContextMenu();
|
useSkipContextMenu();
|
||||||
|
|
||||||
@@ -44,13 +58,21 @@ export const MapRootContent = ({}: MapRootContentProps) => {
|
|||||||
{mapInterface}
|
{mapInterface}
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute top-0 right-0 w-14 h-[calc(100%+3.5rem)] pointer-events-auto">
|
<div className="absolute top-0 right-0 w-14 h-[calc(100%+3.5rem)] pointer-events-auto">
|
||||||
<RightBar onShowOnTheMap={handleShowOnTheMap} onShowMapSettings={handleShowMapSettings} />
|
<RightBar
|
||||||
|
onShowOnTheMap={handleShowOnTheMap}
|
||||||
|
onShowMapSettings={handleShowMapSettings}
|
||||||
|
onShowTrackingDialog={handleShowTrackingDialog}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="absolute top-0 left-14 w-[calc(100%-3.5rem)] h-[calc(100%-3.5rem)] pointer-events-none">
|
<div className="absolute top-0 left-14 w-[calc(100%-3.5rem)] h-[calc(100%-3.5rem)] pointer-events-none">
|
||||||
<Topbar>
|
<Topbar>
|
||||||
<MapContextMenu onShowOnTheMap={handleShowOnTheMap} onShowMapSettings={handleShowMapSettings} />
|
<MapContextMenu
|
||||||
|
onShowOnTheMap={handleShowOnTheMap}
|
||||||
|
onShowMapSettings={handleShowMapSettings}
|
||||||
|
onShowTrackingDialog={handleShowTrackingDialog}
|
||||||
|
/>
|
||||||
</Topbar>
|
</Topbar>
|
||||||
{mapInterface}
|
{mapInterface}
|
||||||
</div>
|
</div>
|
||||||
@@ -60,7 +82,9 @@ export const MapRootContent = ({}: MapRootContentProps) => {
|
|||||||
{showCharacterActivity && (
|
{showCharacterActivity && (
|
||||||
<CharacterActivity visible={showCharacterActivity} onHide={handleHideCharacterActivity} />
|
<CharacterActivity visible={showCharacterActivity} onHide={handleHideCharacterActivity} />
|
||||||
)}
|
)}
|
||||||
{showTrackAndFollow && <TrackAndFollow visible={showTrackAndFollow} onHide={handleHideTracking} />}
|
{showTrackingDialog && (
|
||||||
|
<TrackingDialog visible={showTrackingDialog} onHide={() => setShowTrackingDialog(false)} />
|
||||||
|
)}
|
||||||
</Layout>
|
</Layout>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,129 +1,22 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
|
||||||
import { Dialog } from 'primereact/dialog';
|
import { Dialog } from 'primereact/dialog';
|
||||||
import { DataTable } from 'primereact/datatable';
|
import { CharacterActivityContent } from '@/hooks/Mapper/components/mapRootContent/components/CharacterActivity/CharacterActivityContent.tsx';
|
||||||
import { Column } from 'primereact/column';
|
|
||||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
|
||||||
import { ProgressSpinner } from 'primereact/progressspinner';
|
|
||||||
import { CharacterCard } from '../../../ui-kit';
|
|
||||||
import { CharacterTypeRaw } from '@/hooks/Mapper/types';
|
|
||||||
|
|
||||||
export interface ActivitySummary {
|
|
||||||
character: CharacterTypeRaw;
|
|
||||||
passages: number;
|
|
||||||
connections: number;
|
|
||||||
signatures: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CharacterActivityProps {
|
interface CharacterActivityProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
onHide: () => void;
|
onHide: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getRowClassName = () => ['text-xs', 'leading-tight'];
|
|
||||||
|
|
||||||
const renderCharacterTemplate = (rowData: ActivitySummary) => {
|
|
||||||
return <CharacterCard compact isOwn {...rowData.character} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderValueTemplate = (rowData: ActivitySummary, field: keyof ActivitySummary) => {
|
|
||||||
return <div className="tabular-nums">{rowData[field] as number}</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CharacterActivity = ({ visible, onHide }: CharacterActivityProps) => {
|
export const CharacterActivity = ({ visible, onHide }: CharacterActivityProps) => {
|
||||||
const { data } = useMapRootState();
|
|
||||||
const { characterActivityData } = data;
|
|
||||||
const [localActivity, setLocalActivity] = useState<ActivitySummary[]>([]);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
|
|
||||||
const activity = useMemo(() => {
|
|
||||||
return characterActivityData?.activity || [];
|
|
||||||
}, [characterActivityData]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setLocalActivity(activity);
|
|
||||||
setLoading(characterActivityData?.loading !== false);
|
|
||||||
}, [activity, characterActivityData]);
|
|
||||||
|
|
||||||
const renderContent = () => {
|
|
||||||
if (loading) {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col items-center justify-center h-[400px] w-full">
|
|
||||||
<ProgressSpinner className="w-[50px] h-[50px]" strokeWidth="4" />
|
|
||||||
<div className="mt-4 text-text-color-secondary text-sm">Loading character activity data...</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (localActivity.length === 0) {
|
|
||||||
return (
|
|
||||||
<div className="p-8 text-center text-text-color-secondary italic">No character activity data available</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DataTable
|
|
||||||
value={localActivity}
|
|
||||||
scrollable
|
|
||||||
scrollHeight="400px"
|
|
||||||
resizableColumns
|
|
||||||
columnResizeMode="fit"
|
|
||||||
className="w-full"
|
|
||||||
tableClassName="w-full border-0"
|
|
||||||
emptyMessage="No character activity data available"
|
|
||||||
sortField="passages"
|
|
||||||
sortOrder={-1}
|
|
||||||
size="small"
|
|
||||||
rowClassName={getRowClassName}
|
|
||||||
rowHover
|
|
||||||
>
|
|
||||||
<Column
|
|
||||||
field="character_name"
|
|
||||||
header="Character"
|
|
||||||
body={renderCharacterTemplate}
|
|
||||||
sortable
|
|
||||||
// headerStyle={{ minWidth: '75px', height: 'auto', overflow: 'visible' }}
|
|
||||||
// bodyStyle={{ minWidth: '75px' }}
|
|
||||||
// className={classes.characterColumn}
|
|
||||||
// headerClassName={classes.columnHeader}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Column
|
|
||||||
field="passages"
|
|
||||||
header="Passages"
|
|
||||||
body={rowData => renderValueTemplate(rowData, 'passages')}
|
|
||||||
sortable
|
|
||||||
// headerStyle={{ width: '120px', textAlign: 'center', height: 'auto', overflow: 'visible' }}
|
|
||||||
// bodyStyle={{ width: '120px', textAlign: 'center' }}
|
|
||||||
// className={classes.numericColumn}
|
|
||||||
// headerClassName={classes.columnHeader}
|
|
||||||
/>
|
|
||||||
<Column
|
|
||||||
field="connections"
|
|
||||||
header="Connections"
|
|
||||||
body={rowData => renderValueTemplate(rowData, 'connections')}
|
|
||||||
sortable
|
|
||||||
// headerStyle={{ width: '120px', textAlign: 'center', height: 'auto', overflow: 'visible' }}
|
|
||||||
// bodyStyle={{ width: '120px', textAlign: 'center' }}
|
|
||||||
// className={classes.numericColumn}
|
|
||||||
// headerClassName={classes.columnHeader}
|
|
||||||
/>
|
|
||||||
<Column
|
|
||||||
field="signatures"
|
|
||||||
header="Signatures"
|
|
||||||
body={rowData => renderValueTemplate(rowData, 'signatures')}
|
|
||||||
sortable
|
|
||||||
// headerStyle={{ width: '120px', textAlign: 'center', height: 'auto', overflow: 'visible' }}
|
|
||||||
// bodyStyle={{ width: '120px', textAlign: 'center' }}
|
|
||||||
// className={classes.numericColumn}
|
|
||||||
// headerClassName={classes.columnHeader}
|
|
||||||
/>
|
|
||||||
</DataTable>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog header="Character Activity" visible={visible} className="max-w-[600px]" onHide={onHide} dismissableMask>
|
<Dialog
|
||||||
<div className="w-full h-[400px] flex flex-col overflow-hidden p-0 m-0">{renderContent()}</div>
|
header="Character Activity"
|
||||||
|
visible={visible}
|
||||||
|
className="w-[550px] max-h-[90vh]"
|
||||||
|
onHide={onHide}
|
||||||
|
dismissableMask
|
||||||
|
contentClassName="p-0 h-full flex flex-col"
|
||||||
|
>
|
||||||
|
<CharacterActivityContent />
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import { ProgressSpinner } from 'primereact/progressspinner';
|
||||||
|
import { DataTable } from 'primereact/datatable';
|
||||||
|
import {
|
||||||
|
getRowClassName,
|
||||||
|
renderCharacterTemplate,
|
||||||
|
renderValueTemplate,
|
||||||
|
} from '@/hooks/Mapper/components/mapRootContent/components/CharacterActivity/helpers.tsx';
|
||||||
|
import { Column } from 'primereact/column';
|
||||||
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
export const CharacterActivityContent = () => {
|
||||||
|
const {
|
||||||
|
data: { characterActivityData },
|
||||||
|
} = useMapRootState();
|
||||||
|
|
||||||
|
const activity = useMemo(() => characterActivityData?.activity || [], [characterActivityData]);
|
||||||
|
const loading = useMemo(() => characterActivityData?.loading !== false, [characterActivityData]);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center h-full w-full">
|
||||||
|
<ProgressSpinner className="w-[50px] h-[50px]" strokeWidth="4" />
|
||||||
|
<div className="mt-4 text-text-color-secondary text-sm">Loading character activity data...</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activity.length === 0) {
|
||||||
|
return <div className="p-8 text-center text-text-color-secondary italic">No character activity data available</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full h-full overflow-auto custom-scrollbar">
|
||||||
|
<DataTable
|
||||||
|
value={activity}
|
||||||
|
scrollable
|
||||||
|
className="w-full"
|
||||||
|
tableClassName="w-full border-0"
|
||||||
|
emptyMessage="No character activity data available"
|
||||||
|
sortField="passages"
|
||||||
|
sortOrder={-1}
|
||||||
|
size="small"
|
||||||
|
rowClassName={getRowClassName}
|
||||||
|
rowHover
|
||||||
|
>
|
||||||
|
<Column
|
||||||
|
field="character_name"
|
||||||
|
header="Character"
|
||||||
|
body={renderCharacterTemplate}
|
||||||
|
sortable
|
||||||
|
className="!py-[6px]"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Column
|
||||||
|
field="passages"
|
||||||
|
header="Passages"
|
||||||
|
headerClassName="[&_.p-column-header-content]:justify-center"
|
||||||
|
body={rowData => renderValueTemplate(rowData, 'passages')}
|
||||||
|
sortable
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
field="connections"
|
||||||
|
header="Connections"
|
||||||
|
headerClassName="[&_.p-column-header-content]:justify-center"
|
||||||
|
body={rowData => renderValueTemplate(rowData, 'connections')}
|
||||||
|
sortable
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
field="signatures"
|
||||||
|
header="Signatures"
|
||||||
|
headerClassName="[&_.p-column-header-content]:justify-center"
|
||||||
|
body={rowData => renderValueTemplate(rowData, 'signatures')}
|
||||||
|
sortable
|
||||||
|
/>
|
||||||
|
</DataTable>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { CharacterCard } from '@/hooks/Mapper/components/ui-kit';
|
||||||
|
import { ActivitySummary } from '@/hooks/Mapper/types';
|
||||||
|
|
||||||
|
export const getRowClassName = () => ['text-xs', 'leading-tight'];
|
||||||
|
|
||||||
|
export const renderCharacterTemplate = (rowData: ActivitySummary) => {
|
||||||
|
return <CharacterCard compact isOwn {...rowData.character} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const renderValueTemplate = (rowData: ActivitySummary, field: keyof ActivitySummary) => {
|
||||||
|
return <div className="tabular-nums w-full flex justify-center">{rowData[field] as number}</div>;
|
||||||
|
};
|
||||||
@@ -11,22 +11,16 @@ import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
|
|||||||
export interface MapContextMenuProps {
|
export interface MapContextMenuProps {
|
||||||
onShowOnTheMap?: () => void;
|
onShowOnTheMap?: () => void;
|
||||||
onShowMapSettings?: () => void;
|
onShowMapSettings?: () => void;
|
||||||
|
onShowTrackingDialog?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MapContextMenu = ({ onShowOnTheMap, onShowMapSettings }: MapContextMenuProps) => {
|
export const MapContextMenu = ({ onShowOnTheMap, onShowMapSettings, onShowTrackingDialog }: MapContextMenuProps) => {
|
||||||
const { outCommand, setInterfaceSettings } = useMapRootState();
|
const { outCommand, setInterfaceSettings } = useMapRootState();
|
||||||
|
|
||||||
const canTrackCharacters = useMapCheckPermissions([UserPermission.TRACK_CHARACTER]);
|
const canTrackCharacters = useMapCheckPermissions([UserPermission.TRACK_CHARACTER]);
|
||||||
|
|
||||||
const menuRight = useRef<Menu>(null);
|
const menuRight = useRef<Menu>(null);
|
||||||
|
|
||||||
const handleAddCharacter = useCallback(() => {
|
|
||||||
outCommand({
|
|
||||||
type: OutCommand.showTracking,
|
|
||||||
data: {},
|
|
||||||
});
|
|
||||||
}, [outCommand]);
|
|
||||||
|
|
||||||
const handleShowActivity = useCallback(() => {
|
const handleShowActivity = useCallback(() => {
|
||||||
outCommand({
|
outCommand({
|
||||||
type: OutCommand.showActivity,
|
type: OutCommand.showActivity,
|
||||||
@@ -40,8 +34,8 @@ export const MapContextMenu = ({ onShowOnTheMap, onShowMapSettings }: MapContext
|
|||||||
{
|
{
|
||||||
label: 'Tracking',
|
label: 'Tracking',
|
||||||
icon: 'pi pi-user-plus',
|
icon: 'pi pi-user-plus',
|
||||||
command: handleAddCharacter,
|
command: onShowTrackingDialog,
|
||||||
visible: true,
|
visible: canTrackCharacters,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Character Activity',
|
label: 'Character Activity',
|
||||||
@@ -76,7 +70,7 @@ export const MapContextMenu = ({ onShowOnTheMap, onShowMapSettings }: MapContext
|
|||||||
).filter(item => item.visible);
|
).filter(item => item.visible);
|
||||||
}, [
|
}, [
|
||||||
canTrackCharacters,
|
canTrackCharacters,
|
||||||
handleAddCharacter,
|
onShowTrackingDialog,
|
||||||
handleShowActivity,
|
handleShowActivity,
|
||||||
onShowMapSettings,
|
onShowMapSettings,
|
||||||
onShowOnTheMap,
|
onShowOnTheMap,
|
||||||
|
|||||||
@@ -1,84 +1,3 @@
|
|||||||
.verticalTabsContainer {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
min-height: 300px;
|
|
||||||
|
|
||||||
:global {
|
|
||||||
.p-tabview {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-tabview-panels {
|
|
||||||
padding: 6px 1rem !important;
|
|
||||||
flex-grow: 1;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-tabview-nav-container {
|
|
||||||
border-right: none;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-tabview-nav {
|
|
||||||
flex-direction: column;
|
|
||||||
width: 150px;
|
|
||||||
min-height: 100%;
|
|
||||||
border: none;
|
|
||||||
|
|
||||||
li {
|
|
||||||
width: 100%;
|
|
||||||
border-right: 4px solid var(--surface-hover);
|
|
||||||
background-color: var(--surface-card);
|
|
||||||
|
|
||||||
transition: background-color 200ms, border-right-color 200ms;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--surface-hover);
|
|
||||||
border-right: 4px solid var(--surface-100);
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-tabview-nav-link {
|
|
||||||
transition: color 200ms;
|
|
||||||
|
|
||||||
justify-content: flex-end;
|
|
||||||
padding: 10px;
|
|
||||||
//background-color: var(--surface-card);
|
|
||||||
background-color: initial;
|
|
||||||
border: none;
|
|
||||||
color: var(--gray-400);
|
|
||||||
|
|
||||||
border-radius: initial;
|
|
||||||
font-weight: 400;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.p-tabview-selected {
|
|
||||||
background-color: var(--surface-50);
|
|
||||||
border-right: 4px solid var(--primary-color);
|
|
||||||
|
|
||||||
.p-tabview-nav-link {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
//background-color: var(--surface-hover);
|
|
||||||
border-right: 4px solid var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-tabview-panel {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.CheckboxContainer {
|
.CheckboxContainer {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr auto;
|
grid-template-columns: auto 1fr auto;
|
||||||
|
|||||||
@@ -1,210 +1,56 @@
|
|||||||
import styles from './MapSettings.module.scss';
|
import styles from './MapSettings.module.scss';
|
||||||
import { Dialog } from 'primereact/dialog';
|
import { Dialog } from 'primereact/dialog';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useRef, useState } from 'react';
|
||||||
import { TabPanel, TabView } from 'primereact/tabview';
|
import { TabPanel, TabView } from 'primereact/tabview';
|
||||||
import { PrettySwitchbox } from './components';
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
import {
|
|
||||||
InterfaceStoredSettingsProps,
|
|
||||||
useMapRootState,
|
|
||||||
InterfaceStoredSettings,
|
|
||||||
AvailableThemes
|
|
||||||
} from '@/hooks/Mapper/mapRootProvider';
|
|
||||||
import { OutCommand } from '@/hooks/Mapper/types';
|
import { OutCommand } from '@/hooks/Mapper/types';
|
||||||
import { Dropdown } from 'primereact/dropdown';
|
import {
|
||||||
import { WidgetsSettings } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components/WidgetsSettings/WidgetsSettings.tsx';
|
CONNECTIONS_CHECKBOXES_PROPS,
|
||||||
|
SIGNATURES_CHECKBOXES_PROPS,
|
||||||
export enum UserSettingsRemoteProps {
|
SYSTEMS_CHECKBOXES_PROPS,
|
||||||
link_signature_on_splash = 'link_signature_on_splash',
|
THEME_SETTING,
|
||||||
select_on_spash = 'select_on_spash',
|
UI_CHECKBOXES_PROPS,
|
||||||
delete_connection_with_sigs = 'delete_connection_with_sigs',
|
} from './constants.ts';
|
||||||
}
|
import {
|
||||||
|
MapSettingsProvider,
|
||||||
export const DEFAULT_REMOTE_SETTINGS = {
|
useMapSettings,
|
||||||
[UserSettingsRemoteProps.link_signature_on_splash]: false,
|
} from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx';
|
||||||
[UserSettingsRemoteProps.select_on_spash]: false,
|
import { WidgetsSettings } from './components/WidgetsSettings';
|
||||||
[UserSettingsRemoteProps.delete_connection_with_sigs]: false,
|
import { CommonSettings } from './components/CommonSettings';
|
||||||
};
|
import { SettingsListItem } from './types.ts';
|
||||||
|
|
||||||
export const UserSettingsRemoteList = [
|
|
||||||
UserSettingsRemoteProps.link_signature_on_splash,
|
|
||||||
UserSettingsRemoteProps.select_on_spash,
|
|
||||||
UserSettingsRemoteProps.delete_connection_with_sigs,
|
|
||||||
];
|
|
||||||
|
|
||||||
export type UserSettingsRemote = {
|
|
||||||
link_signature_on_splash: boolean;
|
|
||||||
select_on_spash: boolean;
|
|
||||||
delete_connection_with_sigs: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type UserSettings = UserSettingsRemote & InterfaceStoredSettings;
|
|
||||||
|
|
||||||
export interface MapSettingsProps {
|
export interface MapSettingsProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
onHide: () => void;
|
onHide: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type SettingsListItem = {
|
export const MapSettingsComp = ({ visible, onHide }: MapSettingsProps) => {
|
||||||
prop: keyof UserSettings;
|
|
||||||
label: string;
|
|
||||||
type: 'checkbox' | 'dropdown';
|
|
||||||
options?: { label: string; value: string }[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const COMMON_CHECKBOXES_PROPS: SettingsListItem[] = [
|
|
||||||
{
|
|
||||||
prop: InterfaceStoredSettingsProps.isShowMinimap,
|
|
||||||
label: 'Show Minimap',
|
|
||||||
type: 'checkbox',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const SYSTEMS_CHECKBOXES_PROPS: SettingsListItem[] = [
|
|
||||||
{
|
|
||||||
prop: InterfaceStoredSettingsProps.isShowKSpace,
|
|
||||||
label: 'Highlight Low/High-security systems',
|
|
||||||
type: 'checkbox',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: UserSettingsRemoteProps.select_on_spash,
|
|
||||||
label: 'Auto-select splashed',
|
|
||||||
type: 'checkbox',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const SIGNATURES_CHECKBOXES_PROPS: SettingsListItem[] = [
|
|
||||||
{
|
|
||||||
prop: UserSettingsRemoteProps.link_signature_on_splash,
|
|
||||||
label: 'Link signature on splash',
|
|
||||||
type: 'checkbox',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: InterfaceStoredSettingsProps.isShowUnsplashedSignatures,
|
|
||||||
label: 'Show unsplashed signatures',
|
|
||||||
type: 'checkbox',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const CONNECTIONS_CHECKBOXES_PROPS: SettingsListItem[] = [
|
|
||||||
{
|
|
||||||
prop: UserSettingsRemoteProps.delete_connection_with_sigs,
|
|
||||||
label: 'Delete connections to linked signatures',
|
|
||||||
type: 'checkbox',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: InterfaceStoredSettingsProps.isThickConnections,
|
|
||||||
label: 'Thicker connections',
|
|
||||||
type: 'checkbox',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const UI_CHECKBOXES_PROPS: SettingsListItem[] = [
|
|
||||||
{
|
|
||||||
prop: InterfaceStoredSettingsProps.isShowMenu,
|
|
||||||
label: 'Enable compact map menu bar',
|
|
||||||
type: 'checkbox',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: InterfaceStoredSettingsProps.isShowBackgroundPattern,
|
|
||||||
label: 'Show background pattern',
|
|
||||||
type: 'checkbox',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: InterfaceStoredSettingsProps.isSoftBackground,
|
|
||||||
label: 'Enable soft background',
|
|
||||||
type: 'checkbox',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const THEME_OPTIONS = [
|
|
||||||
{ label: 'Default', value: AvailableThemes.default },
|
|
||||||
{ label: 'Pathfinder', value: AvailableThemes.pathfinder },
|
|
||||||
];
|
|
||||||
|
|
||||||
const THEME_SETTING: SettingsListItem = {
|
|
||||||
prop: 'theme',
|
|
||||||
label: 'Theme',
|
|
||||||
type: 'dropdown',
|
|
||||||
options: THEME_OPTIONS,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MapSettings = ({ visible, onHide }: MapSettingsProps) => {
|
|
||||||
const [activeIndex, setActiveIndex] = useState(0);
|
const [activeIndex, setActiveIndex] = useState(0);
|
||||||
const { outCommand, interfaceSettings, setInterfaceSettings } = useMapRootState();
|
const { outCommand } = useMapRootState();
|
||||||
const [userRemoteSettings, setUserRemoteSettings] = useState<UserSettingsRemote>({
|
|
||||||
...DEFAULT_REMOTE_SETTINGS,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mergedSettings = useMemo(() => {
|
const { renderSettingItem, setUserRemoteSettings } = useMapSettings();
|
||||||
return {
|
|
||||||
...userRemoteSettings,
|
|
||||||
...interfaceSettings,
|
|
||||||
};
|
|
||||||
}, [userRemoteSettings, interfaceSettings]);
|
|
||||||
|
|
||||||
const handleShow = async () => {
|
const refVars = useRef({ outCommand, onHide, visible });
|
||||||
const { user_settings } = await outCommand({
|
refVars.current = { outCommand, onHide, visible };
|
||||||
|
|
||||||
|
const handleShow = useCallback(async () => {
|
||||||
|
const { user_settings } = await refVars.current.outCommand({
|
||||||
type: OutCommand.getUserSettings,
|
type: OutCommand.getUserSettings,
|
||||||
data: null,
|
data: null,
|
||||||
});
|
});
|
||||||
setUserRemoteSettings({
|
setUserRemoteSettings({
|
||||||
...user_settings,
|
...user_settings,
|
||||||
});
|
});
|
||||||
};
|
}, [setUserRemoteSettings]);
|
||||||
|
|
||||||
const handleSettingChange = useCallback(
|
const handleHide = useCallback(() => {
|
||||||
async (prop: keyof UserSettings, value: boolean | string) => {
|
if (!refVars.current.visible) {
|
||||||
if (UserSettingsRemoteList.includes(prop as any)) {
|
return;
|
||||||
const newRemoteSettings = {
|
|
||||||
...userRemoteSettings,
|
|
||||||
[prop]: value,
|
|
||||||
};
|
|
||||||
await outCommand({
|
|
||||||
type: OutCommand.updateUserSettings,
|
|
||||||
data: newRemoteSettings,
|
|
||||||
});
|
|
||||||
setUserRemoteSettings(newRemoteSettings);
|
|
||||||
} else {
|
|
||||||
setInterfaceSettings({
|
|
||||||
...interfaceSettings,
|
|
||||||
[prop]: value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[userRemoteSettings, interfaceSettings, outCommand, setInterfaceSettings],
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderSettingItem = (item: SettingsListItem) => {
|
|
||||||
const currentValue = mergedSettings[item.prop];
|
|
||||||
|
|
||||||
if (item.type === 'checkbox') {
|
|
||||||
return (
|
|
||||||
<PrettySwitchbox
|
|
||||||
key={item.prop}
|
|
||||||
label={item.label}
|
|
||||||
checked={!!currentValue}
|
|
||||||
setChecked={checked => handleSettingChange(item.prop, checked)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.type === 'dropdown' && item.options) {
|
setActiveIndex(0);
|
||||||
return (
|
refVars.current.onHide();
|
||||||
<div key={item.prop} className="flex items-center gap-2 mt-2">
|
}, []);
|
||||||
<label className="text-sm">{item.label}:</label>
|
|
||||||
<Dropdown
|
|
||||||
className="text-sm"
|
|
||||||
value={currentValue}
|
|
||||||
options={item.options}
|
|
||||||
onChange={e => handleSettingChange(item.prop, e.value)}
|
|
||||||
placeholder="Select a theme"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderSettingsList = (list: SettingsListItem[]) => {
|
const renderSettingsList = (list: SettingsListItem[]) => {
|
||||||
return list.map(renderSettingItem);
|
return list.map(renderSettingItem);
|
||||||
@@ -217,47 +63,53 @@ export const MapSettings = ({ visible, onHide }: MapSettingsProps) => {
|
|||||||
draggable={false}
|
draggable={false}
|
||||||
style={{ width: '550px' }}
|
style={{ width: '550px' }}
|
||||||
onShow={handleShow}
|
onShow={handleShow}
|
||||||
onHide={() => {
|
onHide={handleHide}
|
||||||
if (!visible) return;
|
|
||||||
setActiveIndex(0);
|
|
||||||
onHide();
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<div className={styles.verticalTabsContainer}>
|
<TabView
|
||||||
<TabView activeIndex={activeIndex} onTabChange={e => setActiveIndex(e.index)}>
|
activeIndex={activeIndex}
|
||||||
<TabPanel header="Common" headerClassName={styles.verticalTabHeader}>
|
className="vertical-tabs-container"
|
||||||
<div className="w-full h-full flex flex-col gap-1">{renderSettingsList(COMMON_CHECKBOXES_PROPS)}</div>
|
onTabChange={e => setActiveIndex(e.index)}
|
||||||
</TabPanel>
|
>
|
||||||
|
<TabPanel header="Common" headerClassName={styles.verticalTabHeader}>
|
||||||
|
<CommonSettings />
|
||||||
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel header="Systems" headerClassName={styles.verticalTabHeader}>
|
<TabPanel header="Systems" headerClassName={styles.verticalTabHeader}>
|
||||||
<div className="w-full h-full flex flex-col gap-1">{renderSettingsList(SYSTEMS_CHECKBOXES_PROPS)}</div>
|
<div className="w-full h-full flex flex-col gap-1">{renderSettingsList(SYSTEMS_CHECKBOXES_PROPS)}</div>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel header="Connections" headerClassName={styles.verticalTabHeader}>
|
<TabPanel header="Connections" headerClassName={styles.verticalTabHeader}>
|
||||||
{renderSettingsList(CONNECTIONS_CHECKBOXES_PROPS)}
|
{renderSettingsList(CONNECTIONS_CHECKBOXES_PROPS)}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel header="Signatures" headerClassName={styles.verticalTabHeader}>
|
<TabPanel header="Signatures" headerClassName={styles.verticalTabHeader}>
|
||||||
{renderSettingsList(SIGNATURES_CHECKBOXES_PROPS)}
|
{renderSettingsList(SIGNATURES_CHECKBOXES_PROPS)}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel header="User Interface" headerClassName={styles.verticalTabHeader}>
|
<TabPanel header="User Interface" headerClassName={styles.verticalTabHeader}>
|
||||||
{renderSettingsList(UI_CHECKBOXES_PROPS)}
|
{renderSettingsList(UI_CHECKBOXES_PROPS)}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel header="Widgets" className="h-full" headerClassName={styles.verticalTabHeader}>
|
<TabPanel header="Widgets" className="h-full" headerClassName={styles.verticalTabHeader}>
|
||||||
<WidgetsSettings />
|
<WidgetsSettings />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel header="Theme" headerClassName={styles.verticalTabHeader}>
|
<TabPanel header="Theme" headerClassName={styles.verticalTabHeader}>
|
||||||
{renderSettingItem(THEME_SETTING)}
|
{renderSettingItem(THEME_SETTING)}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabView>
|
</TabView>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const MapSettings = (props: MapSettingsProps) => {
|
||||||
|
return (
|
||||||
|
<MapSettingsProvider>
|
||||||
|
<MapSettingsComp {...props} />
|
||||||
|
</MapSettingsProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
import {
|
||||||
|
createContext,
|
||||||
|
Dispatch,
|
||||||
|
ReactNode,
|
||||||
|
SetStateAction,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
import {
|
||||||
|
SettingsListItem,
|
||||||
|
UserSettings,
|
||||||
|
UserSettingsRemote,
|
||||||
|
} from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/types.ts';
|
||||||
|
import {
|
||||||
|
DEFAULT_REMOTE_SETTINGS,
|
||||||
|
UserSettingsRemoteList,
|
||||||
|
} from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts';
|
||||||
|
import { OutCommand } from '@/hooks/Mapper/types';
|
||||||
|
import { PrettySwitchbox } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components';
|
||||||
|
import { Dropdown } from 'primereact/dropdown';
|
||||||
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
|
|
||||||
|
type MapSettingsContextType = {
|
||||||
|
renderSettingItem: (item: SettingsListItem) => ReactNode;
|
||||||
|
setUserRemoteSettings: Dispatch<SetStateAction<UserSettingsRemote>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const MapSettingsContext = createContext<MapSettingsContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
export const MapSettingsProvider = ({ children }: { children: ReactNode }) => {
|
||||||
|
const {
|
||||||
|
outCommand,
|
||||||
|
storedSettings: { interfaceSettings, setInterfaceSettings },
|
||||||
|
} = useMapRootState();
|
||||||
|
|
||||||
|
const [userRemoteSettings, setUserRemoteSettings] = useState<UserSettingsRemote>({
|
||||||
|
...DEFAULT_REMOTE_SETTINGS,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mergedSettings: UserSettings = useMemo(() => {
|
||||||
|
return {
|
||||||
|
...userRemoteSettings,
|
||||||
|
...interfaceSettings,
|
||||||
|
};
|
||||||
|
}, [userRemoteSettings, interfaceSettings]);
|
||||||
|
|
||||||
|
const refVars = useRef({ mergedSettings, userRemoteSettings, interfaceSettings, outCommand, setInterfaceSettings });
|
||||||
|
refVars.current = { mergedSettings, userRemoteSettings, interfaceSettings, outCommand, setInterfaceSettings };
|
||||||
|
|
||||||
|
const handleSettingChange = useCallback(async (prop: keyof UserSettings, value: boolean | string) => {
|
||||||
|
const { userRemoteSettings, interfaceSettings, outCommand, setInterfaceSettings } = refVars.current;
|
||||||
|
|
||||||
|
if (UserSettingsRemoteList.includes(prop as any)) {
|
||||||
|
const newRemoteSettings = {
|
||||||
|
...userRemoteSettings,
|
||||||
|
[prop]: value,
|
||||||
|
};
|
||||||
|
await outCommand({
|
||||||
|
type: OutCommand.updateUserSettings,
|
||||||
|
data: newRemoteSettings,
|
||||||
|
});
|
||||||
|
setUserRemoteSettings(newRemoteSettings);
|
||||||
|
} else {
|
||||||
|
setInterfaceSettings({
|
||||||
|
...interfaceSettings,
|
||||||
|
[prop]: value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const renderSettingItem = useCallback(
|
||||||
|
(item: SettingsListItem) => {
|
||||||
|
const currentValue = refVars.current.mergedSettings[item.prop];
|
||||||
|
|
||||||
|
if (item.type === 'checkbox') {
|
||||||
|
return (
|
||||||
|
<PrettySwitchbox
|
||||||
|
key={item.prop.toString()}
|
||||||
|
label={item.label}
|
||||||
|
checked={!!currentValue}
|
||||||
|
setChecked={checked => handleSettingChange(item.prop, checked)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.type === 'dropdown' && item.options) {
|
||||||
|
return (
|
||||||
|
<div key={item.prop.toString()} className="flex items-center gap-2 mt-2">
|
||||||
|
<label className="text-sm">{item.label}:</label>
|
||||||
|
<Dropdown
|
||||||
|
className="text-sm"
|
||||||
|
value={currentValue}
|
||||||
|
options={item.options}
|
||||||
|
onChange={e => handleSettingChange(item.prop, e.value)}
|
||||||
|
placeholder="Select a theme"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
[handleSettingChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MapSettingsContext.Provider value={{ renderSettingItem, setUserRemoteSettings }}>
|
||||||
|
{children}
|
||||||
|
</MapSettingsContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useMapSettings = () => {
|
||||||
|
const context = useContext(MapSettingsContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useMapSettings must be used within a MapSettingsProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { COMMON_CHECKBOXES_PROPS } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts';
|
||||||
|
import { useMapSettings } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx';
|
||||||
|
import { SettingsListItem } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/types.ts';
|
||||||
|
|
||||||
|
export const CommonSettings = () => {
|
||||||
|
const { renderSettingItem } = useMapSettings();
|
||||||
|
|
||||||
|
const renderSettingsList = (list: SettingsListItem[]) => {
|
||||||
|
return list.map(renderSettingItem);
|
||||||
|
};
|
||||||
|
|
||||||
|
return <div className="w-full h-full flex flex-col gap-1">{renderSettingsList(COMMON_CHECKBOXES_PROPS)}</div>;
|
||||||
|
};
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user