mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-11-03 16:07:25 +00:00
Compare commits
198 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f53de39b1 | ||
|
|
8c3804f107 | ||
|
|
1be4ec2b90 | ||
|
|
8f0ed44b11 | ||
|
|
cbadfc4ac4 | ||
|
|
3d88ae4452 | ||
|
|
6d99c54af7 | ||
|
|
2b7901e9a8 | ||
|
|
fb06dd1dbc | ||
|
|
f8ba36b8be | ||
|
|
5bf9d99b3d | ||
|
|
7cad05342a | ||
|
|
867780e525 | ||
|
|
ff4f9a79c9 | ||
|
|
6699c36fb3 | ||
|
|
abd4556994 | ||
|
|
ccf0d17371 | ||
|
|
898584bbb6 | ||
|
|
6d7a267e39 | ||
|
|
9f656ca3cb | ||
|
|
fede6451e2 | ||
|
|
9797ad380c | ||
|
|
33bc4a4d22 | ||
|
|
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 | ||
|
|
83801c9063 | ||
|
|
0f34350c58 | ||
|
|
1c4c0f0715 | ||
|
|
3825fc831a | ||
|
|
654670cbc8 | ||
|
|
947570072c | ||
|
|
01b6b45380 | ||
|
|
b9dc1f8357 | ||
|
|
b4bd810c9d | ||
|
|
490b037920 | ||
|
|
cdff5458bc | ||
|
|
09314a09e9 | ||
|
|
49ea8edb27 | ||
|
|
86e5ff2fff | ||
|
|
1ade0ae6b9 | ||
|
|
cadfb59b8d | ||
|
|
5c2013f19c | ||
|
|
8db46113f4 | ||
|
|
3028a0b1c0 | ||
|
|
e9b4e39061 | ||
|
|
9c6715e4e5 | ||
|
|
0b03d5ee90 | ||
|
|
30fecf6428 | ||
|
|
752eaaa0f5 | ||
|
|
006d10381f | ||
|
|
a1ffe3cc0e | ||
|
|
b4a1cbbf55 | ||
|
|
b2ae5a33ae | ||
|
|
aec0a75e87 | ||
|
|
7086413f0c | ||
|
|
d55f03b63c | ||
|
|
aa4fd2fe90 | ||
|
|
6abf628a38 | ||
|
|
ad46002c85 | ||
|
|
2f21bd0f44 | ||
|
|
993608f911 | ||
|
|
c6c6adb7d8 | ||
|
|
3937330ce4 | ||
|
|
1590c848c9 | ||
|
|
2bb45b312c | ||
|
|
1fc95c96eb | ||
|
|
ee7a453a72 | ||
|
|
4b79afbac0 | ||
|
|
c8fc31257b | ||
|
|
8e0b8fd7f9 | ||
|
|
ee8f9e4d24 | ||
|
|
994e03945d | ||
|
|
aff00a18b5 | ||
|
|
6c22e6554d | ||
|
|
2a0d7654e7 | ||
|
|
4eb1f641ae | ||
|
|
2da5a243ec | ||
|
|
5ac8ccbe5c | ||
|
|
0568533550 | ||
|
|
178abc2af2 | ||
|
|
adb2a5f459 | ||
|
|
ada1571e1e | ||
|
|
5931c00ff3 | ||
|
|
a6e7c1bf74 | ||
|
|
1a5374f2f6 | ||
|
|
c9e3683b8e | ||
|
|
aba93b342a | ||
|
|
dee78b77a9 | ||
|
|
d21705f355 | ||
|
|
9abcd4bd0b | ||
|
|
b052943e34 | ||
|
|
e1e9b4c2e8 | ||
|
|
42c30e0741 | ||
|
|
3b45e77e65 | ||
|
|
dcb2b6b912 | ||
|
|
638a4e2535 | ||
|
|
489fde16d1 | ||
|
|
35e1c363e5 | ||
|
|
6b97d36bf1 | ||
|
|
82f6a7f701 | ||
|
|
2d92dfbafa | ||
|
|
f81f41f555 | ||
|
|
54c7b44d69 | ||
|
|
9da6605ccb | ||
|
|
a90bf9762a | ||
|
|
c87cfb3c43 | ||
|
|
85cb9ccfa8 | ||
|
|
da2639786d | ||
|
|
3cf77da293 | ||
|
|
3dd7633194 | ||
|
|
ae7f4edf4a | ||
|
|
52eab28f27 | ||
|
|
6098d32bce |
@@ -1,6 +1,27 @@
|
||||
FROM elixir:1.17-otp-27
|
||||
|
||||
RUN apt install -yq curl gnupg
|
||||
# Install OS packages and Node.js (via nodesource),
|
||||
# plus inotify-tools and yarn
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
sudo \
|
||||
curl \
|
||||
make \
|
||||
git \
|
||||
bash \
|
||||
build-essential \
|
||||
ca-certificates \
|
||||
jq \
|
||||
vim \
|
||||
net-tools \
|
||||
procps \
|
||||
# Optionally add any other tools you need, e.g. vim, wget...
|
||||
&& curl -sL https://deb.nodesource.com/setup_18.x | bash - \
|
||||
&& apt-get install -y --no-install-recommends nodejs inotify-tools \
|
||||
&& npm install -g yarn \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN apt --fix-broken install
|
||||
|
||||
RUN mix local.hex --force
|
||||
|
||||
@@ -1,20 +1,30 @@
|
||||
{
|
||||
"name": "wanderer-dev",
|
||||
"dockerComposeFile": ["./docker-compose.yml"],
|
||||
"extensions": [
|
||||
"jakebecker.elixir-ls",
|
||||
"JakeBecker.elixir-ls",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode"
|
||||
],
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"jakebecker.elixir-ls",
|
||||
"JakeBecker.elixir-ls",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode"
|
||||
],
|
||||
"settings": {
|
||||
"editor.formatOnSave": true,
|
||||
"search.exclude": {
|
||||
"**/doc": true
|
||||
},
|
||||
"elixirLS.dialyzerEnabled": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"service": "wanderer",
|
||||
"workspaceFolder": "/app",
|
||||
"shutdownAction": "stopCompose",
|
||||
"settings": {
|
||||
"editor.formatOnSave": true,
|
||||
"search.exclude": {
|
||||
"**/doc": true
|
||||
},
|
||||
"elixirLS.dialyzerEnabled": false
|
||||
}
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/common-utils:2": {
|
||||
"networkArgs": ["--add-host=host.docker.internal:host-gateway"]
|
||||
}
|
||||
},
|
||||
"forwardPorts": [4444]
|
||||
}
|
||||
|
||||
@@ -14,15 +14,15 @@ services:
|
||||
|
||||
wanderer:
|
||||
environment:
|
||||
PORT: 8000
|
||||
PORT: 4444
|
||||
DB_HOST: db
|
||||
WEB_APP_URL: "http://localhost:8000"
|
||||
WEB_APP_URL: "http://localhost:4444"
|
||||
ERL_AFLAGS: "-kernel shell_history enabled"
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- 8000:8000
|
||||
- 4444:4444
|
||||
volumes:
|
||||
- ..:/app:delegated
|
||||
- ~/.gitconfig:/root/.gitconfig
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -26,6 +26,7 @@
|
||||
|
||||
.env
|
||||
*.local.env
|
||||
test/manual/.auto*
|
||||
|
||||
.direnv/
|
||||
.cache/
|
||||
|
||||
520
CHANGELOG.md
520
CHANGELOG.md
@@ -2,6 +2,526 @@
|
||||
|
||||
<!-- changelog -->
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* update activity api (#284)
|
||||
|
||||
* qol updates for dev (#283)
|
||||
|
||||
## [v1.56.0](https://github.com/wanderer-industries/wanderer/compare/v1.55.2...v1.56.0) (2025-03-17)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* add static wh info (#262)
|
||||
|
||||
* add static wh info
|
||||
|
||||
* api: add character activity api (#263)
|
||||
|
||||
* api: add character activity api
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* character activity hide error
|
||||
|
||||
* character added to map on follow (#272)
|
||||
|
||||
## [v1.55.2](https://github.com/wanderer-industries/wanderer/compare/v1.55.1...v1.55.2) (2025-03-16)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: fixed lazy delete reset state
|
||||
|
||||
## [v1.55.1](https://github.com/wanderer-industries/wanderer/compare/v1.55.0...v1.55.1) (2025-03-16)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: fixed lazy delete timeouts
|
||||
|
||||
* Core: fixed lazy delete settings
|
||||
|
||||
* keep character api off by default (#258)
|
||||
|
||||
## [v1.55.0](https://github.com/wanderer-industries/wanderer/compare/v1.54.1...v1.55.0) (2025-03-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* News: added map subscription news
|
||||
|
||||
* Api: added map audit base API. Added comments server validations.
|
||||
|
||||
* enhance character activty and summmarize by user (#206)
|
||||
|
||||
* enhance character activty and summmarize by user (#206)
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: updated balance top up instructions
|
||||
|
||||
* updated connections cleanup logic
|
||||
|
||||
* removed placeholder favicon (#240)
|
||||
|
||||
* fixed activity aggregation and new user tracking (#230)
|
||||
|
||||
* fixed activity aggregation and new user tracking (#230)
|
||||
|
||||
* fixed activity aggregation and new user tracking (#230)
|
||||
|
||||
* fixed activity aggregation and new user tracking (#230)
|
||||
|
||||
## [v1.54.1](https://github.com/wanderer-industries/wanderer/compare/v1.54.0...v1.54.1) (2025-03-06)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* fix scroll and size issues with kills widget (#219)
|
||||
|
||||
* fix scroll and size issues with kills widget
|
||||
|
||||
## [v1.54.0](https://github.com/wanderer-industries/wanderer/compare/v1.53.4...v1.54.0) (2025-03-05)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* added auto-refresh timeout for cloud new version updates
|
||||
|
||||
* add selectable sig deletion timing, and color options (#208)
|
||||
|
||||
## [v1.53.4](https://github.com/wanderer-industries/wanderer/compare/v1.53.3...v1.53.4) (2025-03-04)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* add retry on kills retrieval (#207)
|
||||
|
||||
* add missing masses to wh sizes const (#215)
|
||||
|
||||
## [v1.53.3](https://github.com/wanderer-industries/wanderer/compare/v1.53.2...v1.53.3) (2025-02-27)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: little bit up performance for windows manager
|
||||
|
||||
## [v1.53.2](https://github.com/wanderer-industries/wanderer/compare/v1.53.1...v1.53.2) (2025-02-27)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.53.1](https://github.com/wanderer-industries/wanderer/compare/v1.53.0...v1.53.1) (2025-02-26)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed map ACLs add/remove behaviour
|
||||
|
||||
## [v1.53.0](https://github.com/wanderer-industries/wanderer/compare/v1.52.8...v1.53.0) (2025-02-26)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Auto-set connection EOL status and ship size when linking/editing signatures (#194)
|
||||
|
||||
* Automatically set connection EOL status and ship size type when linking/updating signatures
|
||||
|
||||
## [v1.52.8](https://github.com/wanderer-industries/wanderer/compare/v1.52.7...v1.52.8) (2025-02-26)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Added delete systems hotkey
|
||||
|
||||
## [v1.52.7](https://github.com/wanderer-industries/wanderer/compare/v1.52.6...v1.52.7) (2025-02-24)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* update news image link (#204)
|
||||
|
||||
* Map: Block map events for old client versions
|
||||
|
||||
## [v1.52.6](https://github.com/wanderer-industries/wanderer/compare/v1.52.5...v1.52.6) (2025-02-23)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fixed delete systems on map changes
|
||||
|
||||
## [v1.52.5](https://github.com/wanderer-industries/wanderer/compare/v1.52.4...v1.52.5) (2025-02-22)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fixed delete system on signature deletion
|
||||
|
||||
* Map: Fixed delete system on signature deletion
|
||||
|
||||
## [v1.52.4](https://github.com/wanderer-industries/wanderer/compare/v1.52.3...v1.52.4) (2025-02-21)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* signature paste for russian lang
|
||||
|
||||
## [v1.52.3](https://github.com/wanderer-industries/wanderer/compare/v1.52.2...v1.52.3) (2025-02-21)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* remove signature expiration (#196)
|
||||
|
||||
## [v1.52.2](https://github.com/wanderer-industries/wanderer/compare/v1.52.1...v1.52.2) (2025-02-21)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* prevent constant full signature widget rerender (#195)
|
||||
|
||||
## [v1.52.1](https://github.com/wanderer-industries/wanderer/compare/v1.52.0...v1.52.1) (2025-02-20)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* proper virtual scroller usage (#192)
|
||||
|
||||
* restore delete key functionality for nodes (#191)
|
||||
|
||||
## [v1.52.0](https://github.com/wanderer-industries/wanderer/compare/v1.51.3...v1.52.0) (2025-02-19)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Map: Added map characters view
|
||||
|
||||
## [v1.51.3](https://github.com/wanderer-industries/wanderer/compare/v1.51.2...v1.51.3) (2025-02-19)
|
||||
|
||||
|
||||
|
||||
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))))
|
||||
SHELL := /bin/bash
|
||||
@@ -35,6 +35,11 @@ test t:
|
||||
coverage cover co:
|
||||
mix test --cover
|
||||
|
||||
unit-tests ut:
|
||||
@echo "Running unit tests..."
|
||||
@find test/unit -name "*.exs" -exec elixir {} \;
|
||||
@echo "All unit tests completed."
|
||||
|
||||
versions v:
|
||||
@echo "Tool Versions"
|
||||
@cat .tool-versions
|
||||
|
||||
@@ -112,19 +112,19 @@ body > div:first-of-type {
|
||||
}
|
||||
|
||||
.wd-characters-icons {
|
||||
display: flex;
|
||||
transition:
|
||||
border-color 250ms,
|
||||
opacity 250ms;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
border-radius: 50%;
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
border-color: #5a5a5a;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
cursor: pointer;
|
||||
opacity: 0.6;
|
||||
/*display: flex;*/
|
||||
/*transition:*/
|
||||
/* border-color 250ms,*/
|
||||
/* opacity 250ms;*/
|
||||
/*width: 35px;*/
|
||||
/*height: 35px;*/
|
||||
/*border-radius: 50%;*/
|
||||
/*border-width: 2px;*/
|
||||
/*border-style: solid;*/
|
||||
/*border-color: #5a5a5a;*/
|
||||
/*background-color: rgba(0, 0, 0, 0);*/
|
||||
/*cursor: pointer;*/
|
||||
/*opacity: 0.6;*/
|
||||
}
|
||||
|
||||
.wd-bg-default {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { PrimeReactProvider } from 'primereact/api';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
|
||||
import { ReactFlowProvider } from 'reactflow';
|
||||
import { MapHandlers } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { ErrorInfo, useCallback, useEffect, useRef } from 'react';
|
||||
import { ReactFlowProvider } from 'reactflow';
|
||||
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 { MapRootProvider } from '@/hooks/Mapper/mapRootProvider';
|
||||
import './common-styles/main.scss';
|
||||
|
||||
const ErrorFallback = () => {
|
||||
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 { handleCommand, handleMapEvent, handleMapEvents } = useMapperHandlers(mapperHandlerRefs.current, hooksRef);
|
||||
const { handleCommand, handleMapEvent } = useMapperHandlers(mapperHandlerRefs.current, hooksRef);
|
||||
|
||||
const logError = useCallback((error: Error, info: ErrorInfo) => {
|
||||
if (!hooksRef.current) {
|
||||
@@ -35,7 +35,6 @@ export default function MapRoot({ hooks }) {
|
||||
}
|
||||
|
||||
hooksRef.current.handleEvent('map_event', handleMapEvent);
|
||||
hooksRef.current.handleEvent('map_events', handleMapEvents);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -67,16 +67,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
.p-sortable-column {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
padding: 3px 4px;
|
||||
.p-datatable-thead {
|
||||
th, th.p-sortable-column {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
padding: 3px 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.p-selectable-row td {
|
||||
padding: 4px 4px;
|
||||
}
|
||||
|
||||
.p-datatable.p-datatable-sm .p-datatable-tbody > tr > td {
|
||||
padding: 3px 4px;
|
||||
}
|
||||
|
||||
.p-sortable-column > .p-column-header-content > span:last-child {
|
||||
transform: scale(0.7);
|
||||
|
||||
@@ -93,6 +99,11 @@
|
||||
.p-dropdown-item {
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
|
||||
.p-dropdown-item-label {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.p-dropdown-item-group {
|
||||
@@ -137,3 +148,53 @@
|
||||
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 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
//position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
//visibility: hidden;
|
||||
overflow: hidden;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 2px 10px 0 rgba(0,0,0,0.2);
|
||||
@@ -29,12 +26,10 @@ body .p-dialog {
|
||||
}
|
||||
}
|
||||
|
||||
/* Стиль видимого диалога */
|
||||
.p-dialog-visible {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* Анимации */
|
||||
.p-dialog-enter {
|
||||
opacity: 0;
|
||||
}
|
||||
@@ -53,31 +48,27 @@ body .p-dialog {
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
/* Заголовок диалога */
|
||||
.p-dialog-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 1rem;
|
||||
background: #f4f4f4;
|
||||
//border-bottom: 1px solid #ddd;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
/* Содержимое диалога */
|
||||
.p-dialog-content {
|
||||
padding: 0.5rem;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Подвал диалога */
|
||||
.p-dialog-footer {
|
||||
padding: 1rem;
|
||||
border-top: 1px solid #ddd;
|
||||
background: #f4f4f4;
|
||||
}
|
||||
|
||||
/* Кнопка закрытия диалога */
|
||||
.p-dialog-header-close {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -93,3 +84,12 @@ body .p-dialog {
|
||||
.p-dialog-header-close .pi {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.p-dialog {
|
||||
.p-dialog-title {
|
||||
font-size: 1rem !important;
|
||||
}
|
||||
.p-dialog-header-icons {
|
||||
align-self: initial !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
.p-confirm-popup {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
@apply p-[12px];
|
||||
|
||||
&::before, &::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.p-confirm-popup-content, .p-confirm-popup-footer {
|
||||
@apply p-0 m-0;
|
||||
}
|
||||
|
||||
.p-confirm-popup-content {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.p-confirm-popup-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.p-confirm-popup-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.p-confirm-popup-message {
|
||||
@apply m-0;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.p-confirm-popup-reject.p-button-sm,
|
||||
.p-confirm-popup-accept.p-button-sm {
|
||||
@apply px-1.5 py-1 m-0;
|
||||
|
||||
& > span {
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,4 +1,6 @@
|
||||
@import "fix-dialog";
|
||||
@import "fix-popup";
|
||||
@import "fix-tabs";
|
||||
//@import "fix-input";
|
||||
|
||||
//@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 { CharacterTypeRaw } from '@/hooks/Mapper/types';
|
||||
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 {
|
||||
data: { mainCharacterEveId, followingCharacterEveId },
|
||||
} = useMapRootState();
|
||||
|
||||
const handleSelect = useCallback((character: CharacterTypeRaw) => {
|
||||
emitMapEvent({
|
||||
name: Commands.centerSystem,
|
||||
@@ -21,21 +33,58 @@ const Characters = ({ data }: { data: CharacterTypeRaw[] }) => {
|
||||
className="flex flex-col items-center justify-center"
|
||||
onClick={() => handleSelect(character)}
|
||||
>
|
||||
<div className="tooltip tooltip-bottom" title={character.name}>
|
||||
<a
|
||||
className={clsx('wd-characters-icons wd-bg-default', { ['character-online']: character.online })}
|
||||
<div
|
||||
className={clsx(
|
||||
'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)` }}
|
||||
></a>
|
||||
></div>
|
||||
</div>
|
||||
</li>
|
||||
));
|
||||
|
||||
return (
|
||||
<ul className="flex characters" id="characters" ref={parent}>
|
||||
<ul className="flex gap-1 characters" id="characters" ref={parent}>
|
||||
{items}
|
||||
</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 {
|
||||
hubs: string[];
|
||||
userHubs: string[];
|
||||
contextMenuRef: RefObject<ContextMenu>;
|
||||
systemId: string | undefined;
|
||||
systems: SolarSystemRawType[];
|
||||
@@ -13,6 +14,7 @@ export interface ContextMenuSystemProps {
|
||||
onLockToggle(): void;
|
||||
onOpenSettings(): void;
|
||||
onHubToggle(): void;
|
||||
onUserHubToggle(): void;
|
||||
onSystemTag(val?: string): void;
|
||||
onSystemStatus(val: number): void;
|
||||
onSystemLabels(val: string): void;
|
||||
@@ -25,7 +27,7 @@ export const ContextMenuSystem: React.FC<ContextMenuSystemProps> = ({ contextMen
|
||||
|
||||
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 {
|
||||
hubs: string[];
|
||||
userHubs: string[];
|
||||
systems: SolarSystemRawType[];
|
||||
outCommand: OutCommandHandler;
|
||||
}
|
||||
|
||||
export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseContextMenuSystemHandlersProps) => {
|
||||
export const useContextMenuSystemHandlers = ({
|
||||
systems,
|
||||
hubs,
|
||||
userHubs,
|
||||
outCommand,
|
||||
}: UseContextMenuSystemHandlersProps) => {
|
||||
const contextMenuRef = useRef<ContextMenu | null>(null);
|
||||
|
||||
const [system, setSystem] = useState<string>();
|
||||
|
||||
const { deleteSystems } = useDeleteSystems();
|
||||
|
||||
const ref = useRef({ hubs, system, systems, outCommand, deleteSystems });
|
||||
ref.current = { hubs, system, systems, outCommand, deleteSystems };
|
||||
const ref = useRef({ hubs, userHubs, system, systems, outCommand, deleteSystems });
|
||||
ref.current = { hubs, userHubs, system, systems, outCommand, deleteSystems };
|
||||
|
||||
const open = useCallback((ev: any, systemId: string) => {
|
||||
setSystem(systemId);
|
||||
@@ -72,6 +78,21 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
|
||||
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 { system, outCommand } = ref.current;
|
||||
if (!system) {
|
||||
@@ -104,7 +125,6 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
|
||||
setSystem(undefined);
|
||||
}, []);
|
||||
|
||||
|
||||
const onSystemStatus = useCallback((status: number) => {
|
||||
const { system, outCommand } = ref.current;
|
||||
if (!system) {
|
||||
@@ -177,6 +197,7 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
|
||||
onDeleteSystem,
|
||||
onLockToggle,
|
||||
onHubToggle,
|
||||
onUserHubToggle,
|
||||
onSystemTag,
|
||||
onSystemTemporaryName,
|
||||
onSystemStatus,
|
||||
|
||||
@@ -9,11 +9,14 @@ import { FastSystemActions } from '@/hooks/Mapper/components/contexts/components
|
||||
import { useMapCheckPermissions } from '@/hooks/Mapper/mapRootProvider/hooks/api';
|
||||
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
|
||||
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
|
||||
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
|
||||
import { MapAddIcon, MapDeleteIcon, MapUserAddIcon, MapUserDeleteIcon } from '@/hooks/Mapper/icons';
|
||||
|
||||
export const useContextMenuSystemItems = ({
|
||||
onDeleteSystem,
|
||||
onLockToggle,
|
||||
onHubToggle,
|
||||
onUserHubToggle,
|
||||
onSystemTag,
|
||||
onSystemStatus,
|
||||
onSystemLabels,
|
||||
@@ -22,6 +25,7 @@ export const useContextMenuSystemItems = ({
|
||||
onWaypointSet,
|
||||
systemId,
|
||||
hubs,
|
||||
userHubs,
|
||||
systems,
|
||||
}: Omit<ContextMenuSystemProps, 'contextMenuRef'>) => {
|
||||
const getTags = useTagMenu(systems, systemId, onSystemTag);
|
||||
@@ -32,6 +36,7 @@ export const useContextMenuSystemItems = ({
|
||||
|
||||
return useMemo(() => {
|
||||
const system = systemId ? getSystemById(systems, systemId) : undefined;
|
||||
const systemStaticInfo = getSystemStaticInfo(systemId)!;
|
||||
|
||||
if (!system || !systemId) {
|
||||
return [];
|
||||
@@ -44,9 +49,9 @@ export const useContextMenuSystemItems = ({
|
||||
return (
|
||||
<FastSystemActions
|
||||
systemId={systemId}
|
||||
systemName={system.system_static_info.solar_system_name}
|
||||
regionName={system.system_static_info.region_name}
|
||||
isWH={isWormholeSpace(system.system_static_info.system_class)}
|
||||
systemName={systemStaticInfo.solar_system_name}
|
||||
regionName={systemStaticInfo.region_name}
|
||||
isWH={isWormholeSpace(systemStaticInfo.system_class)}
|
||||
showEdit
|
||||
onOpenSettings={onOpenSettings}
|
||||
/>
|
||||
@@ -57,12 +62,25 @@ export const useContextMenuSystemItems = ({
|
||||
getTags(),
|
||||
getStatus(),
|
||||
...getLabels(),
|
||||
...getWaypointMenu(systemId, system.system_static_info.system_class),
|
||||
...getWaypointMenu(systemId, systemStaticInfo.system_class),
|
||||
{
|
||||
label: !hubs.includes(systemId) ? 'Add in Routes' : 'Remove from Routes',
|
||||
icon: PrimeIcons.MAP_MARKER,
|
||||
label: !hubs.includes(systemId) ? 'Add Route' : 'Remove Route',
|
||||
icon: !hubs.includes(systemId) ? (
|
||||
<MapAddIcon className="mr-1 relative left-[-2px]" />
|
||||
) : (
|
||||
<MapDeleteIcon className="mr-1 relative left-[-2px]" />
|
||||
),
|
||||
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
|
||||
? canLockSystem
|
||||
? [
|
||||
|
||||
@@ -11,6 +11,7 @@ import { FastSystemActions } from '@/hooks/Mapper/components/contexts/components
|
||||
import { useJumpPlannerMenu } from '@/hooks/Mapper/components/contexts/hooks';
|
||||
import { Route } from '@/hooks/Mapper/types/routes.ts';
|
||||
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
|
||||
import { MapAddIcon, MapDeleteIcon } from '@/hooks/Mapper/icons';
|
||||
|
||||
export interface ContextMenuSystemInfoProps {
|
||||
systemStatics: Map<number, SolarSystemStaticInfoRaw>;
|
||||
@@ -69,8 +70,12 @@ export const ContextMenuSystemInfo: React.FC<ContextMenuSystemInfoProps> = ({
|
||||
...getJumpPlannerMenu(system, routes),
|
||||
...getWaypointMenu(systemId, system.system_class),
|
||||
{
|
||||
label: !hubs.includes(systemId) ? 'Add in Routes' : 'Remove from Routes',
|
||||
icon: PrimeIcons.MAP_MARKER,
|
||||
label: !hubs.includes(systemId) ? 'Add Route' : 'Remove Route',
|
||||
icon: !hubs.includes(systemId) ? (
|
||||
<MapAddIcon className="mr-1 relative left-[-2px]" />
|
||||
) : (
|
||||
<MapDeleteIcon className="mr-1 relative left-[-2px]" />
|
||||
),
|
||||
command: onHubToggle,
|
||||
},
|
||||
...(!systemOnMap
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import * as React from 'react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
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 { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||
import { SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
|
||||
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 {
|
||||
hubs: string[];
|
||||
outCommand: OutCommandHandler;
|
||||
}
|
||||
export const useContextMenuSystemInfoHandlers = () => {
|
||||
const { outCommand } = useMapRootState();
|
||||
const { hubs = [], toggleHubCommand } = useRouteProvider();
|
||||
|
||||
export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand }: UseContextMenuSystemHandlersProps) => {
|
||||
const contextMenuRef = useRef<ContextMenu | null>(null);
|
||||
|
||||
const [system, setSystem] = useState<string>();
|
||||
const routeRef = useRef<(SolarSystemStaticInfoRaw | undefined)[]>([]);
|
||||
|
||||
const ref = useRef({ hubs, system, outCommand });
|
||||
ref.current = { hubs, system, outCommand };
|
||||
const ref = useRef({ hubs, system, outCommand, toggleHubCommand });
|
||||
ref.current = { hubs, system, outCommand, toggleHubCommand };
|
||||
|
||||
const open = useCallback(
|
||||
(ev: React.SyntheticEvent, systemId: string, route: (SolarSystemStaticInfoRaw | undefined)[]) => {
|
||||
@@ -33,17 +33,12 @@ export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand }: UseContex
|
||||
);
|
||||
|
||||
const onHubToggle = useCallback(() => {
|
||||
const { hubs, system, outCommand } = ref.current;
|
||||
const { system } = ref.current;
|
||||
if (!system) {
|
||||
return;
|
||||
}
|
||||
|
||||
outCommand({
|
||||
type: !hubs.includes(system) ? OutCommand.addHub : OutCommand.deleteHub,
|
||||
data: {
|
||||
system_id: system,
|
||||
},
|
||||
});
|
||||
ref.current.toggleHubCommand(system);
|
||||
setSystem(undefined);
|
||||
}, []);
|
||||
|
||||
@@ -59,6 +54,8 @@ export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand }: UseContex
|
||||
system_id: solarSystemId,
|
||||
},
|
||||
});
|
||||
|
||||
// TODO add it to some queue
|
||||
setTimeout(() => {
|
||||
emitMapEvent({
|
||||
name: Commands.centerSystem,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
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 clsx from 'clsx';
|
||||
|
||||
@@ -4,8 +4,8 @@ import { useCallback } from 'react';
|
||||
import { isPossibleSpace } from '@/hooks/Mapper/components/map/helpers/isKnownSpace.ts';
|
||||
import { Route } from '@/hooks/Mapper/types/routes.ts';
|
||||
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 { 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 criminalSpace = [SOLAR_SYSTEM_CLASS_IDS.ls, SOLAR_SYSTEM_CLASS_IDS.ns];
|
||||
@@ -47,7 +47,7 @@ export const useJumpPlannerMenu = (
|
||||
return [];
|
||||
}
|
||||
|
||||
const origin = getSystemById(systems, systemIdFrom)?.system_static_info;
|
||||
const origin = getSystemStaticInfo(systemIdFrom);
|
||||
|
||||
if (!origin) {
|
||||
return [];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getSystemById } from '@/hooks/Mapper/helpers';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useMemo } from 'react';
|
||||
import { getSystemById } from '@/hooks/Mapper/helpers';
|
||||
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
|
||||
import { getSystemStaticInfo } from '../../mapRootProvider/hooks/useLoadSystemStatic';
|
||||
|
||||
interface UseSystemInfoProps {
|
||||
systemId: string;
|
||||
@@ -12,14 +12,12 @@ export const useSystemInfo = ({ systemId }: UseSystemInfoProps) => {
|
||||
data: { systems, connections },
|
||||
} = useMapRootState();
|
||||
|
||||
const { systems: systemStatics } = useLoadSystemStatic({ systems: [systemId] });
|
||||
|
||||
return useMemo(() => {
|
||||
const staticInfo = systemStatics.get(parseInt(systemId));
|
||||
const staticInfo = getSystemStaticInfo(parseInt(systemId));
|
||||
const dynamicInfo = getSystemById(systems, systemId);
|
||||
|
||||
if (!staticInfo || !dynamicInfo) {
|
||||
throw new Error(`Error on getting system ${systemId}`);
|
||||
return { dynamicInfo, staticInfo, leadsTo: [] };
|
||||
}
|
||||
|
||||
const leadsTo = connections
|
||||
@@ -29,5 +27,5 @@ export const useSystemInfo = ({ systemId }: UseSystemInfoProps) => {
|
||||
.filter(x => x !== systemId);
|
||||
|
||||
return { dynamicInfo, staticInfo, leadsTo };
|
||||
}, [systemStatics, systemId, systems, connections]);
|
||||
}, [systemId, systems, connections]);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@ import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect, useMemo }
|
||||
import ReactFlow, {
|
||||
Background,
|
||||
Edge,
|
||||
EdgeChange,
|
||||
MiniMap,
|
||||
Node,
|
||||
NodeChange,
|
||||
@@ -79,11 +78,12 @@ const edgeTypes = {
|
||||
floating: SolarSystemEdge,
|
||||
};
|
||||
|
||||
export const MAP_ROOT_ID = 'MAP_ROOT_ID';
|
||||
|
||||
interface MapCompProps {
|
||||
refn: ForwardedRef<MapHandlers>;
|
||||
onCommand: OutCommandHandler;
|
||||
onSelectionChange: OnMapSelectionChange;
|
||||
onManualDelete(systems: string[]): void;
|
||||
onConnectionInfoClick?(e: SolarSystemConnection): void;
|
||||
onAddSystem?: OnMapAddSystemCallback;
|
||||
onSelectionContextMenu?: NodeSelectionMouseHandler;
|
||||
@@ -105,7 +105,6 @@ const MapComp = ({
|
||||
onSystemContextMenu,
|
||||
onConnectionInfoClick,
|
||||
onSelectionContextMenu,
|
||||
onManualDelete,
|
||||
isShowMinimap,
|
||||
showKSpaceBG,
|
||||
isThickConnections,
|
||||
@@ -114,7 +113,7 @@ const MapComp = ({
|
||||
theme,
|
||||
onAddSystem,
|
||||
}: MapCompProps) => {
|
||||
const { getNode, getNodes } = useReactFlow();
|
||||
const { getNodes } = useReactFlow();
|
||||
const [nodes, , onNodesChange] = useNodesState<Node<SolarSystemRawType>>(initialNodes);
|
||||
const [edges, , onEdgesChange] = useEdgesState<Edge<SolarSystemConnection>>(initialEdges);
|
||||
|
||||
@@ -187,8 +186,6 @@ const MapComp = ({
|
||||
|
||||
const handleNodesChange = useCallback(
|
||||
(changes: NodeChange[]) => {
|
||||
const systemsIdsToRemove: string[] = [];
|
||||
|
||||
// prevents single node deselection on background / same node click
|
||||
// allows deseletion of all nodes if multiple are currently selected
|
||||
if (changes.length === 1 && changes[0].type == 'select' && changes[0].selected === false) {
|
||||
@@ -196,30 +193,12 @@ const MapComp = ({
|
||||
}
|
||||
|
||||
const nextChanges = changes.reduce((acc, change) => {
|
||||
if (change.type !== 'remove') {
|
||||
return [...acc, change];
|
||||
}
|
||||
|
||||
const node = getNode(change.id);
|
||||
if (!node) {
|
||||
return [...acc, change];
|
||||
}
|
||||
|
||||
if (node.data.locked) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
systemsIdsToRemove.push(node.data.id);
|
||||
return [...acc, change];
|
||||
}, [] as NodeChange[]);
|
||||
|
||||
if (systemsIdsToRemove.length > 0) {
|
||||
onManualDelete(systemsIdsToRemove);
|
||||
}
|
||||
|
||||
onNodesChange(nextChanges);
|
||||
},
|
||||
[getNode, getNodes, onManualDelete, onNodesChange],
|
||||
[getNodes, onNodesChange],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -232,7 +211,10 @@ const MapComp = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={clsx(classes.MapRoot, { [classes.BackgroundAlternateColor]: isSoftBackground })}>
|
||||
<div
|
||||
data-window-id={MAP_ROOT_ID}
|
||||
className={clsx(classes.MapRoot, { [classes.BackgroundAlternateColor]: isSoftBackground })}
|
||||
>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
@@ -276,7 +258,7 @@ const MapComp = ({
|
||||
minZoom={0.2}
|
||||
maxZoom={1.5}
|
||||
elevateNodesOnSelect
|
||||
deleteKeyCode={['Delete']}
|
||||
deleteKeyCode={['']}
|
||||
{...(isPanAndDrag
|
||||
? {
|
||||
selectionOnDrag: true,
|
||||
|
||||
@@ -37,7 +37,9 @@ const INITIAL_DATA: MapData = {
|
||||
userPermissions: {},
|
||||
systemSignatures: {} as Record<string, SystemSignature[]>,
|
||||
options: {} as Record<string, string | boolean>,
|
||||
is_subscription_active: false,
|
||||
isSubscriptionActive: false,
|
||||
mainCharacterEveId: null,
|
||||
followingCharacterEveId: null,
|
||||
};
|
||||
|
||||
export interface MapContextProps {
|
||||
@@ -53,7 +55,7 @@ const MapContext = createContext<MapContextProps>({
|
||||
outCommand: async () => void 0,
|
||||
});
|
||||
|
||||
export const MapProvider: React.FC<MapProviderProps> = ({ children, onCommand }) => {
|
||||
export const MapProvider = ({ children, onCommand }: MapProviderProps) => {
|
||||
const { update, ref } = useContextStore<MapData>({ ...INITIAL_DATA });
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,7 +2,6 @@ import React, { RefObject, useMemo } from 'react';
|
||||
import { ContextMenu } from 'primereact/contextmenu';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { MenuItem } from 'primereact/menuitem';
|
||||
import { Edge } from '@reactflow/core/dist/esm/types/edges';
|
||||
import { ConnectionType, MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
|
||||
import clsx from 'clsx';
|
||||
import classes from './ContextMenuConnection.module.scss';
|
||||
@@ -14,6 +13,7 @@ import {
|
||||
SHIP_SIZES_NAMES_SHORT,
|
||||
SHIP_SIZES_SIZE,
|
||||
} from '@/hooks/Mapper/components/map/constants.ts';
|
||||
import { Edge } from 'reactflow';
|
||||
|
||||
export interface ContextMenuConnectionProps {
|
||||
contextMenuRef: RefObject<ContextMenu>;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { EdgeMouseHandler } from 'reactflow';
|
||||
import { Edge, EdgeMouseHandler } from 'reactflow';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { ContextMenu } from 'primereact/contextmenu';
|
||||
import { useMapState } from '../../MapProvider.tsx';
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { Edge } from '@reactflow/core/dist/esm/types/edges';
|
||||
import { ConnectionType, MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
|
||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import { SystemKillsContent } from '../../../mapInterface/widgets/SystemKills/SystemKillsContent/SystemKillsContent';
|
||||
import { useMemo } from 'react';
|
||||
import { useKillsCounter } from '../../hooks/useKillsCounter';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
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';
|
||||
|
||||
type TooltipSize = 'xs' | 'sm' | 'md' | 'lg';
|
||||
const MIN_TOOLTIP_HEIGHT = 40;
|
||||
|
||||
type KillsBookmarkTooltipProps = {
|
||||
killsCount: number;
|
||||
@@ -14,25 +19,45 @@ type KillsBookmarkTooltipProps = {
|
||||
} & WithChildren &
|
||||
WithClassName;
|
||||
|
||||
export const KillsCounter = ({ killsCount, systemId, className, children, size = 'xs' }: KillsBookmarkTooltipProps) => {
|
||||
const { isLoading, kills: detailedKills, systemNameMap } = useKillsCounter({ realSystemId: systemId });
|
||||
export const KillsCounter = ({
|
||||
killsCount,
|
||||
systemId,
|
||||
className,
|
||||
children,
|
||||
size = TooltipSize.xs,
|
||||
}: KillsBookmarkTooltipProps) => {
|
||||
const { isLoading, kills: detailedKills } = useKillsCounter({
|
||||
realSystemId: systemId,
|
||||
});
|
||||
|
||||
if (!killsCount || detailedKills.length === 0 || !systemId || isLoading) return null;
|
||||
const limitedKills = useMemo(() => {
|
||||
if (!detailedKills || detailedKills.length === 0) return [];
|
||||
return detailedKills.slice(0, killsCount);
|
||||
}, [detailedKills, killsCount]);
|
||||
|
||||
const tooltipContent = (
|
||||
<div style={{ width: '100%', minWidth: '300px', overflow: 'hidden' }}>
|
||||
<SystemKillsContent
|
||||
kills={detailedKills}
|
||||
systemNameMap={systemNameMap}
|
||||
onlyOneSystem={true}
|
||||
autoSize={true}
|
||||
limit={killsCount}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
if (!killsCount || limitedKills.length === 0 || !systemId || isLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Calculate height based on number of kills, but ensure a minimum height
|
||||
const killsNeededHeight = limitedKills.length * KILLS_ROW_HEIGHT;
|
||||
// Add a small buffer (10px) to prevent scrollbar from appearing unnecessarily
|
||||
const tooltipHeight = Math.max(MIN_TOOLTIP_HEIGHT, Math.min(killsNeededHeight + 10, 500));
|
||||
|
||||
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}
|
||||
</WdTooltipWrapper>
|
||||
);
|
||||
|
||||
@@ -6,8 +6,8 @@ import { CharItemProps, LocalCharactersList } from '../../../mapInterface/widget
|
||||
import { useLocalCharactersItemTemplate } from '../../../mapInterface/widgets/LocalCharacters/hooks/useLocalCharacters';
|
||||
import { useLocalCharacterWidgetSettings } from '../../../mapInterface/widgets/LocalCharacters/hooks/useLocalWidgetSettings';
|
||||
import classes from './SolarSystemLocalCounter.module.scss';
|
||||
import { AvailableThemes } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useTheme } from '@/hooks/Mapper/hooks/useTheme.ts';
|
||||
import { AvailableThemes } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
|
||||
interface LocalCounterProps {
|
||||
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 classes from './SolarSystemNodeDefault.module.scss';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { useLocalCounter, useSolarSystemNode, useNodeKillsCount } from '../../hooks/useSolarSystemLogic';
|
||||
import { useLocalCounter, useNodeKillsCount, useSolarSystemNode } from '../../hooks';
|
||||
import {
|
||||
EFFECT_BACKGROUND_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 { LocalCounter } from './SolarSystemLocalCounter';
|
||||
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>) => {
|
||||
const nodeVars = useSolarSystemNode(props);
|
||||
const { localCounterCharacters } = useLocalCounter(nodeVars);
|
||||
const localKillsCount = useNodeKillsCount(nodeVars.solarSystemId, nodeVars.killsCount);
|
||||
|
||||
// console.log('JOipP', `render ${nodeVars.id}`, render++);
|
||||
|
||||
return (
|
||||
<>
|
||||
{nodeVars.visible && (
|
||||
<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 && (
|
||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.shattered)}>
|
||||
<span className={clsx('pi pi-chart-pie', classes.icon)} />
|
||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.shattered, '!pr-[2px]')}>
|
||||
<WdTooltipWrapper content="Shattered" position={TooltipPosition.top}>
|
||||
<span className={clsx('block w-[10px] h-[10px]', classes.ShatteredIcon)} />
|
||||
</WdTooltipWrapper>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -40,7 +42,7 @@ export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>
|
||||
<KillsCounter
|
||||
killsCount={localKillsCount}
|
||||
systemId={nodeVars.solarSystemId}
|
||||
size="lg"
|
||||
size={TooltipSize.lg}
|
||||
killsActivityType={nodeVars.killsActivityType}
|
||||
className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[nodeVars.killsActivityType!])}
|
||||
>
|
||||
@@ -51,6 +53,12 @@ export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>
|
||||
</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 => (
|
||||
<div key={x.id} className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[x.id])}>
|
||||
{x.shortName}
|
||||
@@ -82,7 +90,11 @@ export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>
|
||||
</div>
|
||||
|
||||
{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
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Handle, NodeProps, Position } from 'reactflow';
|
||||
import clsx from 'clsx';
|
||||
import classes from './SolarSystemNodeTheme.module.scss';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { useLocalCounter, useNodeKillsCount, useSolarSystemNode } from '../../hooks/useSolarSystemLogic';
|
||||
import { useLocalCounter, useNodeKillsCount, useSolarSystemNode } from '../../hooks';
|
||||
import {
|
||||
EFFECT_BACKGROUND_STYLES,
|
||||
MARKER_BOOKMARK_BG_STYLES,
|
||||
@@ -14,25 +14,26 @@ import { WormholeClassComp } from '@/hooks/Mapper/components/map/components/Worm
|
||||
import { UnsplashedSignature } from '@/hooks/Mapper/components/map/components/UnsplashedSignature';
|
||||
import { LocalCounter } from './SolarSystemLocalCounter';
|
||||
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>) => {
|
||||
const nodeVars = useSolarSystemNode(props);
|
||||
const { localCounterCharacters } = useLocalCounter(nodeVars);
|
||||
const localKillsCount = useNodeKillsCount(nodeVars.solarSystemId, nodeVars.killsCount);
|
||||
|
||||
// console.log('JOipP', `render ${nodeVars.id}`, render++);
|
||||
|
||||
return (
|
||||
<>
|
||||
{nodeVars.visible && (
|
||||
<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 && (
|
||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.shattered)}>
|
||||
<span className={clsx('pi pi-chart-pie', classes.icon)} />
|
||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.shattered, '!pr-[2px]')}>
|
||||
<WdTooltipWrapper content="Shattered" position={TooltipPosition.top}>
|
||||
<span className={clsx('block w-[10px] h-[10px]', classes.ShatteredIcon)} />
|
||||
</WdTooltipWrapper>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -40,7 +41,7 @@ export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>)
|
||||
<KillsCounter
|
||||
killsCount={localKillsCount}
|
||||
systemId={nodeVars.solarSystemId}
|
||||
size="lg"
|
||||
size={TooltipSize.lg}
|
||||
killsActivityType={nodeVars.killsActivityType}
|
||||
className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[nodeVars.killsActivityType!])}
|
||||
>
|
||||
@@ -51,6 +52,12 @@ export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>)
|
||||
</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 => (
|
||||
<div key={x.id} className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[x.id])}>
|
||||
{x.shortName}
|
||||
|
||||
@@ -16,7 +16,7 @@ export enum SOLAR_SYSTEM_CLASS_IDS {
|
||||
thera = 12,
|
||||
c13 = 13,
|
||||
sentinel = 14,
|
||||
baribican = 15,
|
||||
barbican = 15,
|
||||
vidette = 16,
|
||||
conflux = 17,
|
||||
redoubt = 18,
|
||||
@@ -82,7 +82,7 @@ export const SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS = {
|
||||
thera: SOLAR_SYSTEM_CLASS_GROUPS.thera,
|
||||
c13: SOLAR_SYSTEM_CLASS_GROUPS.c13,
|
||||
sentinel: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
||||
baribican: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
||||
barbican: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
||||
vidette: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
||||
conflux: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
||||
redoubt: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
||||
@@ -217,7 +217,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
|
||||
wormholeClassID: 14,
|
||||
effectPower: 2,
|
||||
title: 'Class 14 (Sentinel Drifter)',
|
||||
shortTitle: 'Sentinel',
|
||||
shortTitle: 'Sentinel MZ',
|
||||
},
|
||||
{
|
||||
id: 'barbican',
|
||||
@@ -225,7 +225,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
|
||||
wormholeClassID: 15,
|
||||
effectPower: 2,
|
||||
title: 'Class 15 (Barbican Drifter)',
|
||||
shortTitle: 'Barbican',
|
||||
shortTitle: 'Liberated Barbican',
|
||||
},
|
||||
{
|
||||
id: 'vidette',
|
||||
@@ -233,7 +233,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
|
||||
wormholeClassID: 16,
|
||||
effectPower: 2,
|
||||
title: 'Class 16 (Vidette Drifter)',
|
||||
shortTitle: 'Vidette',
|
||||
shortTitle: 'Sanctified Vidette',
|
||||
},
|
||||
{
|
||||
id: 'conflux',
|
||||
@@ -241,7 +241,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
|
||||
wormholeClassID: 17,
|
||||
effectPower: 2,
|
||||
title: 'Class 17 (Conflux Drifter)',
|
||||
shortTitle: 'Conflux',
|
||||
shortTitle: 'Conflux Eyrie',
|
||||
},
|
||||
{
|
||||
id: 'redoubt',
|
||||
@@ -249,7 +249,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
|
||||
wormholeClassID: 18,
|
||||
effectPower: 2,
|
||||
title: 'Class 18 (Redoubt Drifter)',
|
||||
shortTitle: 'Redoubt',
|
||||
shortTitle: 'Azdaja Redoubt',
|
||||
},
|
||||
{
|
||||
id: 'a1',
|
||||
@@ -322,6 +322,9 @@ export const WORMHOLES_ADDITIONAL_INFO: Record<string, WormholesAdditionalInfoTy
|
||||
export const WORMHOLES_ADDITIONAL_INFO_BY_CLASS_ID: Record<string, WormholesAdditionalInfoType> =
|
||||
WORMHOLES_ADDITIONAL_INFO_RAW.reduce((acc, x) => ({ ...acc, [x.wormholeClassID]: x }), {});
|
||||
|
||||
export const WORMHOLES_ADDITIONAL_INFO_BY_SHORT_NAME: Record<string, WormholesAdditionalInfoType> =
|
||||
WORMHOLES_ADDITIONAL_INFO_RAW.reduce((acc, x) => ({ ...acc, [x.shortName]: x }), {});
|
||||
|
||||
// export const SOLAR_SYSTEM_CLASS_NAMES = {
|
||||
// ccp1 = ,
|
||||
// c1 = ,
|
||||
@@ -650,6 +653,7 @@ export enum LABELS {
|
||||
l3 = '3',
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const LABELS_INFO: Record<string, any> = {
|
||||
[LABELS.clear]: { id: 'clear', name: 'Clear', shortName: '', icon: '' },
|
||||
[LABELS.la]: { id: 'la', name: 'Label A', shortName: 'A', icon: '' },
|
||||
@@ -750,6 +754,17 @@ export const SHIP_SIZES_SIZE = {
|
||||
[ShipSizeStatus.capital]: '2M',
|
||||
};
|
||||
|
||||
export const SHIP_MASSES_SIZE: Record<number, ShipSizeStatus> = {
|
||||
5_000_000: ShipSizeStatus.small,
|
||||
62_000_000: ShipSizeStatus.medium,
|
||||
300_000_000: ShipSizeStatus.large,
|
||||
375_000_000: ShipSizeStatus.large,
|
||||
1_000_000_000: ShipSizeStatus.freight,
|
||||
1_350_000_000: ShipSizeStatus.capital,
|
||||
1_800_000_000: ShipSizeStatus.capital,
|
||||
2_000_000_000: ShipSizeStatus.capital,
|
||||
};
|
||||
|
||||
export const SHIP_SIZES_DESCRIPTION = {
|
||||
[ShipSizeStatus.small]: 'Frigate wormhole - up to Destroyer | 5K t.',
|
||||
[ShipSizeStatus.medium]: 'Cruise wormhole - up to Battlecruiser | 62K t.',
|
||||
|
||||
@@ -10,7 +10,7 @@ export const isWormholeSpace = (wormholeClassID: number) => {
|
||||
case SOLAR_SYSTEM_CLASS_IDS.c6:
|
||||
case SOLAR_SYSTEM_CLASS_IDS.c13:
|
||||
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.conflux:
|
||||
case SOLAR_SYSTEM_CLASS_IDS.redoubt:
|
||||
|
||||
@@ -2,10 +2,13 @@ import { Node, useReactFlow } from 'reactflow';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { CommandAddSystems } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { convertSystem2Node } from '../../helpers';
|
||||
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
|
||||
|
||||
export const useMapAddSystems = () => {
|
||||
const rf = useReactFlow();
|
||||
|
||||
const { addSystemStatic } = useLoadSystemStatic({ systems: [] });
|
||||
|
||||
const ref = useRef({ rf });
|
||||
ref.current = { rf };
|
||||
|
||||
@@ -13,7 +16,10 @@ export const useMapAddSystems = () => {
|
||||
const { rf } = ref.current;
|
||||
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);
|
||||
}, []);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MapData, useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { CommandKillsUpdated, CommandMapUpdated } from '@/hooks/Mapper/types';
|
||||
import { useCallback, useRef } from 'react';
|
||||
|
||||
export const useMapCommands = () => {
|
||||
const { update } = useMapState();
|
||||
@@ -8,13 +8,21 @@ export const useMapCommands = () => {
|
||||
const ref = useRef({ update });
|
||||
ref.current = { update };
|
||||
|
||||
const mapUpdated = useCallback(({ hubs }: CommandMapUpdated) => {
|
||||
const mapUpdated = useCallback(({ hubs, system_signatures, kills }: CommandMapUpdated) => {
|
||||
const out: Partial<MapData> = {};
|
||||
|
||||
if (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);
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ export const useMapInit = () => {
|
||||
return useCallback(
|
||||
({
|
||||
systems,
|
||||
system_signatures,
|
||||
kills,
|
||||
connections,
|
||||
wormholes,
|
||||
@@ -51,6 +52,10 @@ export const useMapInit = () => {
|
||||
updateData.systems = systems;
|
||||
}
|
||||
|
||||
if (system_signatures) {
|
||||
updateData.systemSignatures = system_signatures;
|
||||
}
|
||||
|
||||
if (kills) {
|
||||
updateData.kills = kills.reduce((acc, x) => ({ ...acc, [x.solar_system_id]: x.kills }), {});
|
||||
}
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
export * from './useMapHandlers';
|
||||
export * from './useUpdateNodes';
|
||||
export * from './useNodesEdgesState';
|
||||
export * from './useBackgroundVars';
|
||||
export * from './useKillsCounter';
|
||||
export * from './useSystemName';
|
||||
export * from './useNodesEdgesState';
|
||||
export * from './useSolarSystemNode';
|
||||
export * from './useUnsplashedSignatures';
|
||||
export * from './useUpdateNodes';
|
||||
export * from './useNodeKillsCount';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useSystemKills } from '../../mapInterface/widgets/SystemKills/hooks/useSystemKills';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useSystemKills } from '@/hooks/Mapper/components/mapInterface/widgets/WSystemKills/hooks/useSystemKills.ts';
|
||||
|
||||
interface UseKillsCounterProps {
|
||||
realSystemId: string;
|
||||
@@ -27,13 +27,12 @@ export function useKillsCounter({ realSystemId }: UseKillsCounterProps) {
|
||||
const filteredKills = useMemo(() => {
|
||||
if (!allKills || allKills.length === 0) return [];
|
||||
|
||||
return [...allKills]
|
||||
.sort((a, b) => {
|
||||
const aTime = a.kill_time ? new Date(a.kill_time).getTime() : 0;
|
||||
const bTime = b.kill_time ? new Date(b.kill_time).getTime() : 0;
|
||||
return bTime - aTime;
|
||||
})
|
||||
.slice(0, 10);
|
||||
// Sort kills by time, most recent first, but don't limit the number of kills
|
||||
return [...allKills].sort((a, b) => {
|
||||
const aTime = a.kill_time ? new Date(a.kill_time).getTime() : 0;
|
||||
const bTime = b.kill_time ? new Date(b.kill_time).getTime() : 0;
|
||||
return bTime - aTime;
|
||||
});
|
||||
}, [allKills]);
|
||||
|
||||
return {
|
||||
|
||||
31
assets/js/hooks/Mapper/components/map/hooks/useLabelsInfo.ts
Normal file
31
assets/js/hooks/Mapper/components/map/hooks/useLabelsInfo.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { useMemo } from 'react';
|
||||
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager';
|
||||
import { LABELS_INFO, LABELS_ORDER } from '@/hooks/Mapper/components/map/constants';
|
||||
interface UseLabelsInfoParams {
|
||||
labels: string | null;
|
||||
linkedSigPrefix: string | null;
|
||||
isShowLinkedSigId: boolean;
|
||||
}
|
||||
|
||||
export type LabelInfo = {
|
||||
id: string;
|
||||
shortName: string;
|
||||
};
|
||||
|
||||
function sortedLabels(labels: string[]): LabelInfo[] {
|
||||
if (!labels) return [];
|
||||
return LABELS_ORDER.filter(x => labels.includes(x)).map(x => LABELS_INFO[x] as LabelInfo);
|
||||
}
|
||||
|
||||
export function useLabelsInfo({ labels, linkedSigPrefix, isShowLinkedSigId }: UseLabelsInfoParams) {
|
||||
const labelsManager = useMemo(() => new LabelsManager(labels ?? ''), [labels]);
|
||||
const labelsInfo = useMemo(() => sortedLabels(labelsManager.list), [labelsManager]);
|
||||
const labelCustom = useMemo(() => {
|
||||
if (isShowLinkedSigId && linkedSigPrefix) {
|
||||
return labelsManager.customLabel ? `${linkedSigPrefix}・${labelsManager.customLabel}` : linkedSigPrefix;
|
||||
}
|
||||
return labelsManager.customLabel;
|
||||
}, [linkedSigPrefix, isShowLinkedSigId, labelsManager]);
|
||||
|
||||
return { labelsInfo, labelCustom };
|
||||
}
|
||||
@@ -131,6 +131,21 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.characterActivityData:
|
||||
break;
|
||||
|
||||
case Commands.trackingCharactersData:
|
||||
break;
|
||||
|
||||
case Commands.updateActivity:
|
||||
break;
|
||||
|
||||
case Commands.updateTracking:
|
||||
break;
|
||||
|
||||
case Commands.userSettingsUpdated:
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn(`Map handlers: Unknown command: ${type}`, data);
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||
import { Commands } from '@/hooks/Mapper/types';
|
||||
|
||||
interface Kill {
|
||||
solar_system_id: number | string;
|
||||
kills: number;
|
||||
}
|
||||
|
||||
interface MapEvent {
|
||||
name: Commands;
|
||||
data?: any;
|
||||
payload?: Kill[];
|
||||
}
|
||||
|
||||
export function useNodeKillsCount(systemId: number | string, initialKillsCount: number | null): number | null {
|
||||
const [killsCount, setKillsCount] = useState<number | null>(initialKillsCount);
|
||||
|
||||
useEffect(() => {
|
||||
setKillsCount(initialKillsCount);
|
||||
}, [initialKillsCount]);
|
||||
|
||||
const handleEvent = useCallback(
|
||||
(event: MapEvent): boolean => {
|
||||
if (event.name === Commands.killsUpdated && Array.isArray(event.payload)) {
|
||||
const killForSystem = event.payload.find(kill => kill.solar_system_id.toString() === systemId.toString());
|
||||
if (killForSystem && typeof killForSystem.kills === 'number') {
|
||||
setKillsCount(killForSystem.kills);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
[systemId],
|
||||
);
|
||||
|
||||
useMapEventListener(handleEvent);
|
||||
|
||||
return killsCount;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { MapSolarSystemType } from '../map.types';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
@@ -7,19 +7,13 @@ import { useMapState } from '@/hooks/Mapper/components/map/MapProvider';
|
||||
import { useDoubleClick } from '@/hooks/Mapper/hooks/useDoubleClick';
|
||||
import { REGIONS_MAP, Spaces } from '@/hooks/Mapper/constants';
|
||||
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace';
|
||||
import { getSystemClassStyles, prepareUnsplashedChunks } from '@/hooks/Mapper/components/map/helpers';
|
||||
import { getSystemClassStyles } from '@/hooks/Mapper/components/map/helpers';
|
||||
import { sortWHClasses } from '@/hooks/Mapper/helpers';
|
||||
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager';
|
||||
import { CharacterTypeRaw, Commands, OutCommand, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { LABELS_INFO, LABELS_ORDER } from '@/hooks/Mapper/components/map/constants';
|
||||
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||
|
||||
export type LabelInfo = {
|
||||
id: string;
|
||||
shortName: string;
|
||||
};
|
||||
|
||||
export type UnsplashedSignatureType = SystemSignature & { sig_id: string };
|
||||
import { CharacterTypeRaw, OutCommand, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { useUnsplashedSignatures } from './useUnsplashedSignatures';
|
||||
import { useSystemName } from './useSystemName';
|
||||
import { LabelInfo, useLabelsInfo } from './useLabelsInfo';
|
||||
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
|
||||
|
||||
function getActivityType(count: number): string {
|
||||
if (count <= 5) return 'activityNormal';
|
||||
@@ -34,11 +28,6 @@ const SpaceToClass: Record<string, string> = {
|
||||
[Spaces.Gallente]: 'Gallente',
|
||||
};
|
||||
|
||||
function sortedLabels(labels: string[]): LabelInfo[] {
|
||||
if (!labels) return [];
|
||||
return LABELS_ORDER.filter(x => labels.includes(x)).map(x => LABELS_INFO[x] as LabelInfo);
|
||||
}
|
||||
|
||||
export function useLocalCounter(nodeVars: SolarSystemNodeVars) {
|
||||
const localCounterCharacters = useMemo(() => {
|
||||
return nodeVars.charactersInSystem
|
||||
@@ -55,8 +44,7 @@ export function useLocalCounter(nodeVars: SolarSystemNodeVars) {
|
||||
export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarSystemNodeVars {
|
||||
const { id, data, selected } = props;
|
||||
const {
|
||||
system_static_info,
|
||||
system_signatures,
|
||||
id: solar_system_id,
|
||||
locked,
|
||||
name,
|
||||
tag,
|
||||
@@ -66,23 +54,26 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
|
||||
linked_sig_eve_id: linkedSigEveId = '',
|
||||
} = data;
|
||||
|
||||
const {
|
||||
storedSettings: { interfaceSettings },
|
||||
data: { systemSignatures: mapSystemSignatures },
|
||||
} = useMapRootState();
|
||||
|
||||
const systemStaticInfo = useMemo(() => {
|
||||
return getSystemStaticInfo(solar_system_id)!;
|
||||
}, [solar_system_id]);
|
||||
|
||||
const {
|
||||
system_class,
|
||||
security,
|
||||
class_title,
|
||||
solar_system_id,
|
||||
statics,
|
||||
effect_name,
|
||||
region_name,
|
||||
region_id,
|
||||
is_shattered,
|
||||
solar_system_name,
|
||||
} = system_static_info;
|
||||
|
||||
const {
|
||||
interfaceSettings,
|
||||
data: { systemSignatures: mapSystemSignatures },
|
||||
} = useMapRootState();
|
||||
} = systemStaticInfo;
|
||||
|
||||
const { isShowUnsplashedSignatures } = interfaceSettings;
|
||||
const isTempSystemNameEnabled = useMapGetOption('show_temp_system_name') === 'true';
|
||||
@@ -107,13 +98,10 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
|
||||
|
||||
const visible = useMemo(() => visibleNodes.has(id), [id, visibleNodes]);
|
||||
|
||||
const systemSigs = useMemo(
|
||||
() => mapSystemSignatures[solar_system_id] || system_signatures,
|
||||
[system_signatures, solar_system_id, mapSystemSignatures],
|
||||
);
|
||||
const systemSigs = useMemo(() => mapSystemSignatures[solar_system_id] || [], [solar_system_id, mapSystemSignatures]);
|
||||
|
||||
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]);
|
||||
|
||||
const isWormhole = isWormholeSpace(system_class);
|
||||
@@ -127,80 +115,43 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
|
||||
|
||||
const linkedSigPrefix = useMemo(() => (linkedSigEveId ? linkedSigEveId.split('-')[0] : null), [linkedSigEveId]);
|
||||
|
||||
const labelsManager = useMemo(() => new LabelsManager(labels ?? ''), [labels]);
|
||||
const labelsInfo = useMemo(() => sortedLabels(labelsManager.list), [labelsManager]);
|
||||
const labelCustom = useMemo(() => {
|
||||
if (isShowLinkedSigId && linkedSigPrefix) {
|
||||
return labelsManager.customLabel ? `${linkedSigPrefix}・${labelsManager.customLabel}` : linkedSigPrefix;
|
||||
}
|
||||
return labelsManager.customLabel;
|
||||
}, [linkedSigPrefix, isShowLinkedSigId, labelsManager]);
|
||||
const { labelsInfo, labelCustom } = useLabelsInfo({
|
||||
labels,
|
||||
linkedSigPrefix,
|
||||
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 hasUserCharacters = useMemo(() => {
|
||||
return charactersInSystem.some(x => userCharacters.includes(x.eve_id));
|
||||
}, [charactersInSystem, userCharacters]);
|
||||
const hasUserCharacters = useMemo(
|
||||
() => charactersInSystem.some(x => userCharacters.includes(x.eve_id)),
|
||||
[charactersInSystem, userCharacters],
|
||||
);
|
||||
|
||||
const dbClick = useDoubleClick(() => {
|
||||
outCommand({
|
||||
type: OutCommand.openSettings,
|
||||
data: { system_id: solar_system_id.toString() },
|
||||
data: { system_id: solar_system_id },
|
||||
});
|
||||
});
|
||||
|
||||
const showHandlers = isConnecting || hoverNodeId === id;
|
||||
|
||||
const space = showKSpaceBG ? REGIONS_MAP[region_id] : '';
|
||||
const regionClass = showKSpaceBG ? SpaceToClass[space] : null;
|
||||
const regionClass = showKSpaceBG ? SpaceToClass[space] || null : null;
|
||||
|
||||
const computedTemporaryName = useMemo(() => {
|
||||
if (!isTempSystemNameEnabled) {
|
||||
return '';
|
||||
}
|
||||
if (isShowLinkedSigIdTempName && linkedSigPrefix) {
|
||||
return temporary_name ? `${linkedSigPrefix}・${temporary_name}` : `${linkedSigPrefix}・${solar_system_name}`;
|
||||
}
|
||||
return temporary_name;
|
||||
}, [isShowLinkedSigIdTempName, isTempSystemNameEnabled, linkedSigPrefix, solar_system_name, temporary_name]);
|
||||
const { systemName, computedTemporaryName, customName } = useSystemName({
|
||||
isTempSystemNameEnabled,
|
||||
temporary_name,
|
||||
isShowLinkedSigIdTempName,
|
||||
linkedSigPrefix,
|
||||
name,
|
||||
systemStaticInfo,
|
||||
});
|
||||
|
||||
const systemName = useMemo(() => {
|
||||
if (isTempSystemNameEnabled && computedTemporaryName) {
|
||||
return computedTemporaryName;
|
||||
}
|
||||
return solar_system_name;
|
||||
}, [isTempSystemNameEnabled, solar_system_name, computedTemporaryName]);
|
||||
const { unsplashedLeft, unsplashedRight } = useUnsplashedSignatures(systemSigs, isShowUnsplashedSignatures);
|
||||
|
||||
const customName = useMemo(() => {
|
||||
if (isTempSystemNameEnabled && computedTemporaryName && name) {
|
||||
return name;
|
||||
}
|
||||
if (solar_system_name !== name && name) {
|
||||
return name;
|
||||
}
|
||||
return null;
|
||||
}, [isTempSystemNameEnabled, computedTemporaryName, name, solar_system_name]);
|
||||
|
||||
const [unsplashedLeft, unsplashedRight] = useMemo(() => {
|
||||
if (!isShowUnsplashedSignatures) {
|
||||
return [[], []];
|
||||
}
|
||||
return prepareUnsplashedChunks(
|
||||
systemSigs
|
||||
.filter(s => s.group === 'Wormhole' && !s.linked_system)
|
||||
.map(s => ({
|
||||
eve_id: s.eve_id,
|
||||
type: s.type,
|
||||
custom_info: s.custom_info,
|
||||
kind: s.kind,
|
||||
name: s.name,
|
||||
group: s.group,
|
||||
})) as UnsplashedSignatureType[],
|
||||
);
|
||||
}, [isShowUnsplashedSignatures, systemSigs]);
|
||||
|
||||
// Ensure hubs are always strings.
|
||||
const hubsAsStrings = useMemo(() => hubs.map(item => item.toString()), [hubs]);
|
||||
|
||||
const nodeVars: SolarSystemNodeVars = {
|
||||
@@ -225,12 +176,10 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
|
||||
dbClick,
|
||||
sortedStatics,
|
||||
effectName: effect_name,
|
||||
regionName: region_name,
|
||||
solarSystemId: solar_system_id.toString(),
|
||||
solarSystemName: solar_system_name,
|
||||
locked,
|
||||
hubs: hubsAsStrings,
|
||||
name: name,
|
||||
name,
|
||||
isConnecting,
|
||||
hoverNodeId,
|
||||
charactersInSystem,
|
||||
@@ -239,6 +188,8 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
|
||||
isThickConnections,
|
||||
classTitle: class_title,
|
||||
temporaryName: computedTemporaryName,
|
||||
regionName: region_name,
|
||||
solarSystemName: solar_system_name,
|
||||
};
|
||||
|
||||
return nodeVars;
|
||||
@@ -281,25 +232,3 @@ export interface SolarSystemNodeVars {
|
||||
classTitle: string | null;
|
||||
temporaryName?: string | null;
|
||||
}
|
||||
|
||||
export function useNodeKillsCount(systemId: number | string, initialKillsCount: number | null): number | null {
|
||||
const [killsCount, setKillsCount] = useState<number | null>(initialKillsCount);
|
||||
|
||||
useEffect(() => {
|
||||
setKillsCount(initialKillsCount);
|
||||
}, [initialKillsCount]);
|
||||
|
||||
useMapEventListener(event => {
|
||||
if (event.name === Commands.killsUpdated && event.data?.toString() === systemId.toString()) {
|
||||
//@ts-ignore
|
||||
if (event.payload && typeof event.payload.kills === 'number') {
|
||||
// @ts-ignore
|
||||
setKillsCount(event.payload.kills);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return killsCount;
|
||||
}
|
||||
56
assets/js/hooks/Mapper/components/map/hooks/useSystemName.ts
Normal file
56
assets/js/hooks/Mapper/components/map/hooks/useSystemName.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { useMemo } from 'react';
|
||||
import { SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
|
||||
|
||||
interface UseSystemNameParams {
|
||||
isTempSystemNameEnabled: boolean;
|
||||
temporary_name?: string | null;
|
||||
isShowLinkedSigIdTempName: boolean;
|
||||
linkedSigPrefix: string | null;
|
||||
name?: string | null;
|
||||
systemStaticInfo: SolarSystemStaticInfoRaw;
|
||||
}
|
||||
|
||||
export const useSystemName = ({
|
||||
isTempSystemNameEnabled,
|
||||
temporary_name,
|
||||
isShowLinkedSigIdTempName,
|
||||
linkedSigPrefix,
|
||||
name,
|
||||
systemStaticInfo,
|
||||
}: UseSystemNameParams) => {
|
||||
const { solar_system_name = '' } = systemStaticInfo;
|
||||
|
||||
const computedTemporaryName = useMemo(() => {
|
||||
if (!isTempSystemNameEnabled) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (isShowLinkedSigIdTempName && linkedSigPrefix) {
|
||||
return temporary_name ? `${linkedSigPrefix}・${temporary_name}` : `${linkedSigPrefix}・${solar_system_name}`;
|
||||
}
|
||||
|
||||
return temporary_name ?? '';
|
||||
}, [isTempSystemNameEnabled, temporary_name, solar_system_name, isShowLinkedSigIdTempName, linkedSigPrefix]);
|
||||
|
||||
const systemName = useMemo(() => {
|
||||
if (isTempSystemNameEnabled && computedTemporaryName) {
|
||||
return computedTemporaryName;
|
||||
}
|
||||
|
||||
return solar_system_name;
|
||||
}, [isTempSystemNameEnabled, computedTemporaryName, solar_system_name]);
|
||||
|
||||
const customName = useMemo(() => {
|
||||
if (isTempSystemNameEnabled && computedTemporaryName && name) {
|
||||
return name;
|
||||
}
|
||||
|
||||
if (solar_system_name !== name && name) {
|
||||
return name;
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [isTempSystemNameEnabled, computedTemporaryName, name, solar_system_name]);
|
||||
|
||||
return { systemName, computedTemporaryName, customName };
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import { useMemo } from 'react';
|
||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { prepareUnsplashedChunks } from '@/hooks/Mapper/components/map/helpers';
|
||||
|
||||
export type UnsplashedSignatureType = SystemSignature & { sig_id: string };
|
||||
|
||||
export function useUnsplashedSignatures(systemSigs: SystemSignature[], isShowUnsplashedSignatures: boolean) {
|
||||
return useMemo(() => {
|
||||
if (!isShowUnsplashedSignatures) {
|
||||
return {
|
||||
unsplashedLeft: [] as SystemSignature[],
|
||||
unsplashedRight: [] as SystemSignature[],
|
||||
};
|
||||
}
|
||||
const chunks = prepareUnsplashedChunks(
|
||||
systemSigs
|
||||
.filter(s => s.group === 'Wormhole' && !s.linked_system)
|
||||
.map(s => ({
|
||||
eve_id: s.eve_id,
|
||||
type: s.type,
|
||||
custom_info: s.custom_info,
|
||||
kind: s.kind,
|
||||
name: s.name,
|
||||
group: s.group,
|
||||
})) as UnsplashedSignatureType[],
|
||||
);
|
||||
const [unsplashedLeft, unsplashedRight] = chunks;
|
||||
return { unsplashedLeft, unsplashedRight };
|
||||
}, [isShowUnsplashedSignatures, systemSigs]);
|
||||
}
|
||||
@@ -6,9 +6,9 @@ import { SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||
const useThrottle = () => {
|
||||
const throttleSeed = useRef<number | null>(null);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const throttleFunction = useRef((func: any, delay = 200) => {
|
||||
if (!throttleSeed.current) {
|
||||
// Call the callback immediately for the first time
|
||||
func();
|
||||
throttleSeed.current = setTimeout(() => {
|
||||
throttleSeed.current = null;
|
||||
@@ -75,7 +75,7 @@ export const useUpdateNodes = (nodes: Node<SolarSystemRawType>[]) => {
|
||||
|
||||
const visibleNodes = new Set(nodes.filter(x => isNodeVisible(x, viewport)).map(x => x.id));
|
||||
update({ visibleNodes });
|
||||
}, [nodes]);
|
||||
}, [getViewport, nodes, update]);
|
||||
|
||||
useOnViewportChange({
|
||||
onChange: () => throttle(updateNodesVisibility.bind(this)),
|
||||
@@ -84,5 +84,5 @@ export const useUpdateNodes = (nodes: Node<SolarSystemRawType>[]) => {
|
||||
|
||||
useEffect(() => {
|
||||
updateNodesVisibility();
|
||||
}, [nodes]);
|
||||
}, [nodes, updateNodesVisibility]);
|
||||
};
|
||||
|
||||
@@ -542,48 +542,32 @@
|
||||
background-color: #d10600;
|
||||
}
|
||||
|
||||
.react-flow {
|
||||
color: var(--text-color);
|
||||
|
||||
&__pane {
|
||||
cursor: auto;
|
||||
}
|
||||
.react-flow__minimap-node {
|
||||
fill: #ffb03a;
|
||||
}
|
||||
|
||||
&__minimap {
|
||||
background-color: rgba(66, 66, 66, 1);
|
||||
opacity: 0.7;
|
||||
border: 1px solid #2f2f2f;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.react-flow__minimap {
|
||||
border: 1px solid #282828;
|
||||
border-radius: 4px;
|
||||
background-color: rgb(47 37 37) !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__minimap-mask {
|
||||
fill: rgba(28, 28, 28, 0.75);
|
||||
}
|
||||
.react-flow__minimap-mask {
|
||||
stroke-width: 2px;
|
||||
fill: rgba(0, 0, 0, 0.5);
|
||||
mix-blend-mode: overlay;
|
||||
}
|
||||
|
||||
&__controls {
|
||||
filter: brightness(1.5);
|
||||
}
|
||||
|
||||
&__minimap-node {
|
||||
fill: #ffb03a;
|
||||
}
|
||||
.react-flow__minimap-mask {
|
||||
stroke-width: 2px;
|
||||
fill: rgb(0 0 0 / 50%) !important;
|
||||
mix-blend-mode: inherit;
|
||||
opacity: 1;
|
||||
stroke: #fff;
|
||||
}
|
||||
|
||||
.context-menu-active {
|
||||
background-color: rgba(131, 131, 131, 0.33);
|
||||
}
|
||||
|
||||
.p-dialog {
|
||||
.p-dialog-header {
|
||||
height: 40px;
|
||||
padding: 1rem;
|
||||
padding-right: 10px !important;
|
||||
}
|
||||
.p-dialog-title {
|
||||
font-size: 1rem !important;
|
||||
}
|
||||
.p-dialog-header-icons {
|
||||
align-self: initial !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { MarkdownComment } from '@/hooks/Mapper/components/mapInterface/components/Comments/components';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { CommentType } from '@/hooks/Mapper/types';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
|
||||
export interface CommentsProps {}
|
||||
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
export const Comments = ({}: CommentsProps) => {
|
||||
const [commentsList, setCommentsList] = useState<CommentType[]>([]);
|
||||
|
||||
const {
|
||||
data: { selectedSystems },
|
||||
comments: { loadComments, comments, lastUpdateKey },
|
||||
} = useMapRootState();
|
||||
|
||||
const [systemId] = selectedSystems;
|
||||
|
||||
const ref = useRef({ loadComments, systemId });
|
||||
ref.current = { loadComments, systemId };
|
||||
|
||||
useEffect(() => {
|
||||
const commentsBySystem = comments.get(systemId);
|
||||
if (!commentsBySystem) {
|
||||
return;
|
||||
}
|
||||
|
||||
const els = [...commentsBySystem.comments].sort((a, b) => +new Date(b.updated_at) - +new Date(a.updated_at));
|
||||
|
||||
setCommentsList(els);
|
||||
}, [systemId, lastUpdateKey, comments]);
|
||||
|
||||
useEffect(() => {
|
||||
ref.current.loadComments(systemId);
|
||||
}, [systemId]);
|
||||
|
||||
if (commentsList.length === 0) {
|
||||
return (
|
||||
<div className="w-full h-full flex justify-center items-center select-none text-stone-400/80 text-sm">
|
||||
Not comments found here
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1 whitespace-nowrap overflow-auto text-ellipsis custom-scrollbar">
|
||||
{commentsList.map(({ id, text, updated_at, characterEveId }) => (
|
||||
<MarkdownComment key={id} text={text} time={updated_at} characterEveId={characterEveId} id={id} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
.MarkdownCommentRoot {
|
||||
border-left-width: 3px;
|
||||
|
||||
@apply text-[12px] leading-[1.2] text-stone-300 break-words;
|
||||
@apply bg-gradient-to-r from-stone-600/40 via-stone-600/10 to-stone-600/0;
|
||||
|
||||
.h1 {
|
||||
@apply text-[12px] font-normal m-0 p-0 border-none break-words whitespace-normal;
|
||||
}
|
||||
|
||||
.h2 {
|
||||
@apply text-[12px] font-normal m-0 p-0 border-none break-words whitespace-normal;
|
||||
}
|
||||
|
||||
.h3 {
|
||||
@apply text-[12px] font-normal m-0 p-0 border-none break-words whitespace-normal;
|
||||
}
|
||||
|
||||
.h4 {
|
||||
@apply text-[12px] font-normal m-0 p-0 border-none break-words whitespace-normal;
|
||||
}
|
||||
|
||||
.h5 {
|
||||
@apply text-[12px] font-normal m-0 p-0 border-none break-words whitespace-normal;
|
||||
}
|
||||
|
||||
.h6 {
|
||||
@apply text-[12px] font-normal m-0 p-0 border-none break-words whitespace-normal;
|
||||
}
|
||||
|
||||
p {
|
||||
@apply m-0 p-0 break-words whitespace-normal;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
@apply m-0 p-0 list-none;
|
||||
}
|
||||
|
||||
li {
|
||||
@apply m-0 break-words whitespace-normal;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
@apply border-l-4 border-cyan-400 p-2 m-0 font-normal text-stone-300 italic break-words whitespace-normal;
|
||||
}
|
||||
|
||||
a {
|
||||
@apply text-violet-400 cursor-pointer transition-colors duration-200 break-words whitespace-normal;
|
||||
|
||||
&:hover {
|
||||
@apply underline;
|
||||
}
|
||||
}
|
||||
|
||||
b, strong {
|
||||
@apply font-bold text-green-400 break-words whitespace-normal;
|
||||
}
|
||||
|
||||
i, em {
|
||||
@apply italic text-pink-400 break-words whitespace-normal;
|
||||
}
|
||||
|
||||
del {
|
||||
@apply line-through text-stone-500 break-words whitespace-normal;
|
||||
}
|
||||
|
||||
hr {
|
||||
@apply border-none h-[1px] bg-cyan-400 opacity-50 my-2;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import classes from './MarkdownComment.module.scss';
|
||||
import clsx from 'clsx';
|
||||
import Markdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { InfoDrawer, TimeAgo, TooltipPosition, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
import remarkBreaks from 'remark-breaks';
|
||||
import { useGetCacheCharacter } from '@/hooks/Mapper/mapRootProvider/hooks/api';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { WdTransition } from '@/hooks/Mapper/components/ui-kit/WdTransition/WdTransition.tsx';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { ConfirmPopup } from 'primereact/confirmpopup';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { OutCommand } from '@/hooks/Mapper/types';
|
||||
|
||||
const TOOLTIP_PROPS = { content: 'Remove comment', position: TooltipPosition.top };
|
||||
const REMARK_PLUGINS = [remarkGfm, remarkBreaks];
|
||||
|
||||
export interface MarkdownCommentProps {
|
||||
text: string;
|
||||
time: string;
|
||||
characterEveId: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export const MarkdownComment = ({ text, time, characterEveId, id }: MarkdownCommentProps) => {
|
||||
const char = useGetCacheCharacter(characterEveId);
|
||||
const [hovered, setHovered] = useState(false);
|
||||
|
||||
const cpRemoveBtnRef = useRef<HTMLElement>();
|
||||
const [cpRemoveVisible, setCpRemoveVisible] = useState(false);
|
||||
|
||||
const { outCommand } = useMapRootState();
|
||||
const ref = useRef({ outCommand, id });
|
||||
ref.current = { outCommand, id };
|
||||
|
||||
const handleDelete = useCallback(async () => {
|
||||
await ref.current.outCommand({
|
||||
type: OutCommand.deleteSystemComment,
|
||||
data: ref.current.id,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleMouseEnter = useCallback(() => setHovered(true), []);
|
||||
const handleMouseLeave = useCallback(() => setHovered(false), []);
|
||||
|
||||
const handleShowCP = useCallback(() => setCpRemoveVisible(true), []);
|
||||
const handleHideCP = useCallback(() => setCpRemoveVisible(false), []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<InfoDrawer
|
||||
labelClassName="mb-[3px]"
|
||||
className={clsx(classes.MarkdownCommentRoot, 'p-1 bg-stone-700/20 ')}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
title={
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<span className="text-stone-500">
|
||||
by <span className="text-orange-300/70">{char?.data?.name ?? ''}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<WdTransition active={hovered} timeout={100}>
|
||||
<div className="text-stone-500 max-h-[12px]">
|
||||
{!hovered && <TimeAgo timestamp={time} />}
|
||||
{hovered && (
|
||||
// @ts-ignore
|
||||
<div ref={cpRemoveBtnRef}>
|
||||
<WdImgButton
|
||||
className={clsx(PrimeIcons.TRASH, 'hover:text-red-400')}
|
||||
tooltip={TOOLTIP_PROPS}
|
||||
onClick={handleShowCP}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</WdTransition>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Markdown remarkPlugins={REMARK_PLUGINS}>{text}</Markdown>
|
||||
</InfoDrawer>
|
||||
|
||||
<ConfirmPopup
|
||||
target={cpRemoveBtnRef.current}
|
||||
visible={cpRemoveVisible}
|
||||
onHide={handleHideCP}
|
||||
message="Are you sure you want to delete?"
|
||||
icon="pi pi-exclamation-triangle"
|
||||
accept={handleDelete}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './MarkdownComment.tsx';
|
||||
@@ -0,0 +1 @@
|
||||
export * from './MarkdownComment';
|
||||
@@ -0,0 +1 @@
|
||||
export * from './Comments';
|
||||
@@ -0,0 +1,26 @@
|
||||
export const markdown = `
|
||||
# Heading 1
|
||||
## Heading 2
|
||||
### Heading 3
|
||||
#### Heading 4
|
||||
##### Heading 5
|
||||
###### Heading 6
|
||||
|
||||
---
|
||||
|
||||
## Paragraphs
|
||||
This is a regular text paragraph.
|
||||
|
||||
Another paragraph, but with **bold** and *italic* text, as well as ~~strikethrough~~.
|
||||
|
||||
> This is a block quote.
|
||||
> Second line of the quote.
|
||||
|
||||
## Links
|
||||
[Link to Google](https://www.google.com)
|
||||
|
||||
## Horizontal Line
|
||||
A block quote with ~strikethrough~ and a URL: https://reactjs.org.
|
||||
---
|
||||
|
||||
`;
|
||||
@@ -0,0 +1,72 @@
|
||||
import { TooltipPosition, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
import clsx from 'clsx';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { MarkdownEditor } from '@/hooks/Mapper/components/mapInterface/components/MarkdownEditor';
|
||||
import { useHotkey } from '@/hooks/Mapper/hooks';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { OutCommand } from '@/hooks/Mapper/types';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
|
||||
export interface CommentsEditorProps {}
|
||||
|
||||
export const CommentsEditor = ({}: CommentsEditorProps) => {
|
||||
const [textVal, setTextVal] = useState('');
|
||||
|
||||
const {
|
||||
data: { selectedSystems },
|
||||
outCommand,
|
||||
} = useMapRootState();
|
||||
|
||||
const [systemId] = selectedSystems;
|
||||
|
||||
const ref = useRef({ outCommand, systemId, textVal });
|
||||
ref.current = { outCommand, systemId, textVal };
|
||||
|
||||
const handleFinishEdit = useCallback(async () => {
|
||||
if (ref.current.textVal === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
await ref.current.outCommand({
|
||||
type: OutCommand.addSystemComment,
|
||||
data: {
|
||||
solarSystemId: ref.current.systemId,
|
||||
value: ref.current.textVal,
|
||||
},
|
||||
});
|
||||
setTextVal('');
|
||||
}, []);
|
||||
|
||||
const handleClick = async () => {
|
||||
await handleFinishEdit();
|
||||
};
|
||||
|
||||
useHotkey(true, ['Enter'], async () => {
|
||||
await handleFinishEdit();
|
||||
});
|
||||
|
||||
return (
|
||||
<MarkdownEditor
|
||||
value={textVal}
|
||||
onChange={setTextVal}
|
||||
overlayContent={
|
||||
<div className="w-full h-full flex justify-end items-end pointer-events-none pb-[1px] pr-[8px]">
|
||||
<WdImgButton
|
||||
disabled={textVal.length === 0}
|
||||
tooltip={{
|
||||
position: TooltipPosition.bottom,
|
||||
content: (
|
||||
<span>
|
||||
Also you may use <span className="text-cyan-400">Meta + Enter</span> hotkey.
|
||||
</span>
|
||||
),
|
||||
}}
|
||||
textSize={WdImageSize.large}
|
||||
className={clsx(PrimeIcons.SEND, 'text-[14px]')}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './CommentsEditor';
|
||||
@@ -0,0 +1,40 @@
|
||||
.CERoot {
|
||||
@apply border border-stone-400/30 rounded-[2px];
|
||||
|
||||
:global {
|
||||
.cm-content {
|
||||
@apply bg-stone-600/40;
|
||||
}
|
||||
|
||||
.cm-scroller {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255, 255, 255, 0.5) transparent;
|
||||
}
|
||||
|
||||
.cm-scroller::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.cm-scroller::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.cm-scroller::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
border-radius: 5px;
|
||||
border: 2px solid transparent;
|
||||
background-clip: content-box;
|
||||
}
|
||||
|
||||
.cm-scroller::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.cm-scroller::-webkit-scrollbar-button {
|
||||
display: none;
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import { ReactNode, useCallback, useRef, useState } from 'react';
|
||||
import CodeMirror, { ViewPlugin } from '@uiw/react-codemirror';
|
||||
import { markdown } from '@codemirror/lang-markdown';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
import { EditorView, type ViewUpdate } from '@codemirror/view';
|
||||
|
||||
import classes from './MarkdownEditor.module.scss';
|
||||
import clsx from 'clsx';
|
||||
|
||||
// TODO special plugin which force CodeMirror using capture for paste event
|
||||
const stopEventPropagationPlugin = ViewPlugin.fromClass(
|
||||
class {
|
||||
constructor(view: EditorView) {
|
||||
// @ts-ignore
|
||||
this.view = view;
|
||||
|
||||
// @ts-ignore
|
||||
this.pasteHandler = (event: Event) => {
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
view.dom.addEventListener('paste', this.pasteHandler);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
// @ts-ignore
|
||||
this.view.dom.removeEventListener('paste', this.pasteHandler);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const CODE_MIRROR_EXTENSIONS = [
|
||||
markdown(),
|
||||
EditorView.lineWrapping,
|
||||
EditorView.theme({
|
||||
'&': { backgroundColor: 'transparent !important' },
|
||||
'& .cm-gutterElement': { display: 'none' },
|
||||
}),
|
||||
stopEventPropagationPlugin,
|
||||
];
|
||||
|
||||
export interface MarkdownEditorProps {
|
||||
overlayContent?: ReactNode;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
}
|
||||
|
||||
export const MarkdownEditor = ({ value, onChange, overlayContent }: MarkdownEditorProps) => {
|
||||
const [hasShift, setHasShift] = useState(false);
|
||||
|
||||
const refData = useRef({ onChange });
|
||||
refData.current = { onChange };
|
||||
|
||||
const handleOnChange = useCallback((value: string, viewUpdate: ViewUpdate) => {
|
||||
// Rerender happens after change
|
||||
setTimeout(() => {
|
||||
const scrollDOM = viewUpdate.view.scrollDOM;
|
||||
setHasShift(scrollDOM.scrollHeight > scrollDOM.clientHeight);
|
||||
}, 0);
|
||||
|
||||
refData.current.onChange(value);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={clsx(classes.MarkdownEditor, 'relative')}>
|
||||
<CodeMirror
|
||||
value={value}
|
||||
height="70px"
|
||||
extensions={CODE_MIRROR_EXTENSIONS}
|
||||
className={classes.CERoot}
|
||||
theme={oneDark}
|
||||
onChange={handleOnChange}
|
||||
placeholder="Start typing..."
|
||||
/>
|
||||
<div
|
||||
className={clsx('absolute top-0 left-0 h-full pointer-events-none', {
|
||||
'w-full': !hasShift,
|
||||
'w-[calc(100%-10px)]': hasShift,
|
||||
})}
|
||||
>
|
||||
{overlayContent}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './MarkdownEditor.tsx';
|
||||
@@ -1,59 +1,173 @@
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { CommandLinkSignatureToSystem } from '@/hooks/Mapper/types';
|
||||
import { SystemSignaturesContent } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent';
|
||||
import { SHOW_DESCRIPTION_COLUMN_SETTING } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatures';
|
||||
import { useSystemInfo } from '@/hooks/Mapper/components/hooks';
|
||||
import {
|
||||
Setting,
|
||||
COSMIC_SIGNATURE,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureSettingsDialog';
|
||||
import { SignatureGroup } from '@/hooks/Mapper/types';
|
||||
SOLAR_SYSTEM_CLASS_IDS,
|
||||
SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS,
|
||||
WORMHOLES_ADDITIONAL_INFO_BY_SHORT_NAME,
|
||||
} from '@/hooks/Mapper/components/map/constants.ts';
|
||||
import {
|
||||
SETTINGS_KEYS,
|
||||
SignatureSettingsType,
|
||||
} 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;
|
||||
|
||||
interface SystemLinkSignatureDialogProps {
|
||||
data: CommandLinkSignatureToSystem;
|
||||
setVisible: (visible: boolean) => void;
|
||||
}
|
||||
|
||||
const signatureSettings: Setting[] = [
|
||||
{ key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true },
|
||||
{ key: SignatureGroup.Wormhole, name: 'Wormhole', value: true },
|
||||
{ key: SHOW_DESCRIPTION_COLUMN_SETTING, name: 'Show Description Column', value: true, isFilter: false },
|
||||
];
|
||||
export const LINK_SIGNTATURE_SETTINGS: SignatureSettingsType = {
|
||||
[SETTINGS_KEYS.COSMIC_SIGNATURE]: true,
|
||||
[SETTINGS_KEYS.WORMHOLE]: true,
|
||||
[SETTINGS_KEYS.SHOW_DESCRIPTION_COLUMN]: true,
|
||||
};
|
||||
|
||||
// Extend the SignatureCustomInfo type to include k162Type
|
||||
interface ExtendedSignatureCustomInfo {
|
||||
k162Type?: string;
|
||||
isEOL?: boolean;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignatureDialogProps) => {
|
||||
const { outCommand } = useMapRootState();
|
||||
const {
|
||||
outCommand,
|
||||
data: { wormholes },
|
||||
} = useMapRootState();
|
||||
|
||||
const ref = useRef({ outCommand });
|
||||
ref.current = { outCommand };
|
||||
|
||||
// Get system info for the target system
|
||||
const { staticInfo: targetSystemInfo, dynamicInfo: targetSystemDynamicInfo } = useSystemInfo({
|
||||
systemId: `${data.solar_system_target}`,
|
||||
});
|
||||
|
||||
// Get the system class group for the target system
|
||||
const targetSystemClassGroup = useMemo(() => {
|
||||
if (!targetSystemInfo) return null;
|
||||
const systemClassId = targetSystemInfo.system_class;
|
||||
|
||||
const systemClassKey = Object.keys(SOLAR_SYSTEM_CLASS_IDS).find(
|
||||
key => SOLAR_SYSTEM_CLASS_IDS[key as keyof typeof SOLAR_SYSTEM_CLASS_IDS] === systemClassId,
|
||||
);
|
||||
|
||||
if (!systemClassKey) return null;
|
||||
|
||||
return (
|
||||
SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS[systemClassKey as keyof typeof SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS] || null
|
||||
);
|
||||
}, [targetSystemInfo]);
|
||||
|
||||
const handleHide = useCallback(() => {
|
||||
setVisible(false);
|
||||
}, [setVisible]);
|
||||
|
||||
const handleSelect = useCallback(
|
||||
const filterSignature = useCallback(
|
||||
(signature: SystemSignature) => {
|
||||
if (signature.group !== SignatureGroup.Wormhole || !targetSystemClassGroup) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!signature.type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (signature.type === K162_SIGNATURE_TYPE) {
|
||||
// Parse the custom info to see if the user has specified what class this K162 leads to
|
||||
const customInfo = parseSignatureCustomInfo(signature.custom_info) as ExtendedSignatureCustomInfo;
|
||||
|
||||
// If the user has specified a k162Type for this K162
|
||||
if (customInfo.k162Type) {
|
||||
// Get the K162 type information
|
||||
const k162TypeInfo = K162_TYPES_MAP[customInfo.k162Type];
|
||||
|
||||
if (k162TypeInfo) {
|
||||
// Check if the k162Type matches our target system class
|
||||
return customInfo.k162Type === targetSystemClassGroup;
|
||||
}
|
||||
}
|
||||
|
||||
// If no k162Type is specified or we couldn't find type info, allow it
|
||||
return true;
|
||||
}
|
||||
|
||||
// Find the wormhole data for this signature type
|
||||
const wormholeData = wormholes.find(wh => wh.name === signature.type);
|
||||
if (!wormholeData) {
|
||||
return true; // If we don't know the destination, don't filter it out
|
||||
}
|
||||
|
||||
// Get the destination system class from the wormhole data
|
||||
const destinationClass = wormholeData.dest;
|
||||
|
||||
// Check if the destination class matches the target system class
|
||||
const isMatch = destinationClass === targetSystemClassGroup;
|
||||
return isMatch;
|
||||
},
|
||||
[targetSystemClassGroup, wormholes],
|
||||
);
|
||||
|
||||
const handleSelect = useCallback(
|
||||
async (signature: SystemSignature) => {
|
||||
if (!signature) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { outCommand } = ref.current;
|
||||
|
||||
outCommand({
|
||||
await outCommand({
|
||||
type: OutCommand.linkSignatureToSystem,
|
||||
data: {
|
||||
...data,
|
||||
signature_eve_id: signature.eve_id,
|
||||
},
|
||||
});
|
||||
|
||||
if (parseSignatureCustomInfo(signature.custom_info).isEOL === true) {
|
||||
await outCommand({
|
||||
type: OutCommand.updateConnectionTimeStatus,
|
||||
data: {
|
||||
source: data.solar_system_source,
|
||||
target: data.solar_system_target,
|
||||
value: TimeStatus.eol,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const whShipSize = getWhSize(wormholes, signature.type);
|
||||
if (whShipSize) {
|
||||
await outCommand({
|
||||
type: OutCommand.updateConnectionShipSizeType,
|
||||
data: {
|
||||
source: data.solar_system_source,
|
||||
target: data.solar_system_target,
|
||||
value: whShipSize,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
setVisible(false);
|
||||
},
|
||||
[data, setVisible],
|
||||
[data, setVisible, wormholes],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!targetSystemDynamicInfo) {
|
||||
handleHide();
|
||||
}
|
||||
}, [targetSystemDynamicInfo]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
header="Select signature to link"
|
||||
@@ -66,9 +180,10 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
|
||||
<SystemSignaturesContent
|
||||
systemId={`${data.solar_system_source}`}
|
||||
hideLinkedSignatures
|
||||
settings={signatureSettings}
|
||||
settings={LINK_SIGNTATURE_SETTINGS}
|
||||
onSelect={handleSelect}
|
||||
selectable={true}
|
||||
filterSignature={filterSignature}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -10,6 +10,7 @@ import { OutCommand } from '@/hooks/Mapper/types';
|
||||
import { IconField } from 'primereact/iconfield';
|
||||
import { TooltipPosition, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager.ts';
|
||||
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
|
||||
|
||||
interface SystemSettingsDialog {
|
||||
systemId: string;
|
||||
@@ -26,6 +27,7 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
|
||||
const isTempSystemNameEnabled = useMapGetOption('show_temp_system_name') === 'true';
|
||||
|
||||
const system = getSystemById(systems, systemId);
|
||||
const systemStaticInfo = getSystemStaticInfo(systemId);
|
||||
|
||||
const [name, setName] = useState('');
|
||||
const [label, setLabel] = useState('');
|
||||
@@ -33,11 +35,11 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
|
||||
const [description, setDescription] = useState('');
|
||||
const inputRef = useRef<HTMLInputElement>();
|
||||
|
||||
const ref = useRef({ name, description, temporaryName, label, outCommand, systemId, system });
|
||||
ref.current = { name, description, label, temporaryName, outCommand, systemId, system };
|
||||
const ref = useRef({ name, description, temporaryName, label, outCommand, systemId, system, systemStaticInfo });
|
||||
ref.current = { name, description, label, temporaryName, outCommand, systemId, system, systemStaticInfo };
|
||||
|
||||
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 ?? '');
|
||||
outLabel.updateCustomLabel(label);
|
||||
@@ -62,7 +64,7 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
|
||||
type: OutCommand.updateSystemName,
|
||||
data: {
|
||||
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]);
|
||||
|
||||
const handleResetSystemName = useCallback(() => {
|
||||
const { system } = ref.current;
|
||||
if (!system) {
|
||||
const { systemStaticInfo } = ref.current;
|
||||
if (!systemStaticInfo) {
|
||||
return;
|
||||
}
|
||||
setName(system.system_static_info.solar_system_name);
|
||||
setName(systemStaticInfo.solar_system_name);
|
||||
}, []);
|
||||
|
||||
const onShow = useCallback(() => {
|
||||
@@ -130,7 +132,7 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
|
||||
<label htmlFor="username">Custom name</label>
|
||||
|
||||
<IconField>
|
||||
{name !== system?.system_static_info.solar_system_name && (
|
||||
{name !== systemStaticInfo?.solar_system_name && (
|
||||
<WdImgButton
|
||||
className="pi pi-undo"
|
||||
textSize={WdImageSize.large}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.root {
|
||||
padding-bottom: 5px;
|
||||
|
||||
}
|
||||
|
||||
.Header {
|
||||
|
||||
@@ -2,15 +2,18 @@ import React from 'react';
|
||||
|
||||
import classes from './Widget.module.scss';
|
||||
import clsx from 'clsx';
|
||||
import { WithChildren } from '@/hooks/Mapper/types/common.ts';
|
||||
|
||||
export interface WidgetProps {
|
||||
export type WidgetProps = {
|
||||
label: React.ReactNode | string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
windowId?: string;
|
||||
contentClassName?: string;
|
||||
} & WithChildren;
|
||||
|
||||
export const Widget = ({ label, children }: WidgetProps) => {
|
||||
export const Widget = ({ label, children, windowId, contentClassName }: WidgetProps) => {
|
||||
return (
|
||||
<div
|
||||
data-window-id={windowId}
|
||||
className={clsx(
|
||||
classes.root,
|
||||
'flex flex-col w-full h-full rounded',
|
||||
@@ -32,7 +35,7 @@ export const Widget = ({ label, children }: WidgetProps) => {
|
||||
{label}
|
||||
</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 }}
|
||||
onContextMenu={e => {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { WindowProps } from '@/hooks/Mapper/components/ui-kit/WindowManager/types.ts';
|
||||
import {
|
||||
CommentsWidget,
|
||||
LocalCharacters,
|
||||
RoutesWidget,
|
||||
SystemInfo,
|
||||
SystemSignatures,
|
||||
SystemStructures,
|
||||
SystemKills,
|
||||
WRoutesPublic,
|
||||
WRoutesUser,
|
||||
WSystemKills,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets';
|
||||
|
||||
export const CURRENT_WINDOWS_VERSION = 8;
|
||||
export const CURRENT_WINDOWS_VERSION = 9;
|
||||
export const WINDOWS_LOCAL_STORE_KEY = 'windows:settings:v2';
|
||||
|
||||
export enum WidgetsIds {
|
||||
@@ -18,6 +20,8 @@ export enum WidgetsIds {
|
||||
routes = 'routes',
|
||||
structures = 'structures',
|
||||
kills = 'kills',
|
||||
comments = 'comments',
|
||||
userRoutes = 'userRoutes',
|
||||
}
|
||||
|
||||
export const STORED_VISIBLE_WIDGETS_DEFAULT = [
|
||||
@@ -54,7 +58,14 @@ export const DEFAULT_WIDGETS: WindowProps[] = [
|
||||
position: { x: 10, y: 530 },
|
||||
size: { width: 510, height: 200 },
|
||||
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,
|
||||
@@ -68,7 +79,14 @@ export const DEFAULT_WIDGETS: WindowProps[] = [
|
||||
position: { x: 270, y: 730 },
|
||||
size: { width: 510, height: 200 },
|
||||
zIndex: 0,
|
||||
content: () => <SystemKills />,
|
||||
content: () => <WSystemKills />,
|
||||
},
|
||||
{
|
||||
id: WidgetsIds.comments,
|
||||
position: { x: 10, y: 10 },
|
||||
size: { width: 250, height: 300 },
|
||||
zIndex: 0,
|
||||
content: () => <CommentsWidget />,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -94,6 +112,10 @@ export const WIDGETS_CHECKBOXES_PROPS: WidgetsCheckboxesType = [
|
||||
id: WidgetsIds.routes,
|
||||
label: 'Routes',
|
||||
},
|
||||
{
|
||||
id: WidgetsIds.userRoutes,
|
||||
label: 'User Routes',
|
||||
},
|
||||
{
|
||||
id: WidgetsIds.structures,
|
||||
label: 'Structures',
|
||||
@@ -102,4 +124,8 @@ export const WIDGETS_CHECKBOXES_PROPS: WidgetsCheckboxesType = [
|
||||
id: WidgetsIds.kills,
|
||||
label: 'Kills',
|
||||
},
|
||||
{
|
||||
id: WidgetsIds.comments,
|
||||
label: 'Comments',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
|
||||
import { Comments } from '@/hooks/Mapper/components/mapInterface/components/Comments';
|
||||
import { InfoDrawer, SystemView, TooltipPosition, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { useRef } from 'react';
|
||||
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth.ts';
|
||||
import { COMPACT_MAX_WIDTH } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import clsx from 'clsx';
|
||||
import { CommentsEditor } from '@/hooks/Mapper/components/mapInterface/components/CommentsEditor';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
|
||||
export const CommentsWidgetContent = () => {
|
||||
const {
|
||||
data: { selectedSystems },
|
||||
} = useMapRootState();
|
||||
|
||||
const isNotSelectedSystem = selectedSystems.length !== 1;
|
||||
|
||||
if (isNotSelectedSystem) {
|
||||
return (
|
||||
<div className="w-full h-full flex justify-center items-center select-none text-stone-400/80 text-sm">
|
||||
System is not selected
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={clsx('h-full grid grid-rows-[1fr_auto] gap-1 px-[4px]')}>
|
||||
<Comments />
|
||||
<CommentsEditor />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const CommentsWidget = () => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const isCompact = useMaxWidth(containerRef, COMPACT_MAX_WIDTH);
|
||||
|
||||
const {
|
||||
data: { selectedSystems, isSubscriptionActive },
|
||||
} = useMapRootState();
|
||||
const [systemId] = selectedSystems;
|
||||
const isNotSelectedSystem = selectedSystems.length !== 1;
|
||||
|
||||
return (
|
||||
<Widget
|
||||
contentClassName="my-1"
|
||||
label={
|
||||
<div ref={containerRef} className="flex justify-between items-center gap-1 text-xs w-full">
|
||||
<div className="flex items-center gap-1">
|
||||
{!isCompact && (
|
||||
<div className="flex whitespace-nowrap text-ellipsis overflow-hidden text-stone-400">
|
||||
Comments {isNotSelectedSystem ? '' : 'in'}
|
||||
</div>
|
||||
)}
|
||||
{!isNotSelectedSystem && <SystemView systemId={systemId} className="select-none text-center" hideRegion />}
|
||||
</div>
|
||||
<WdImgButton
|
||||
className={PrimeIcons.QUESTION_CIRCLE}
|
||||
tooltip={{
|
||||
position: TooltipPosition.left,
|
||||
content: (
|
||||
<div className="flex flex-col gap-1">
|
||||
<InfoDrawer title={<b className="text-slate-50">How to add/delete comment?</b>}>
|
||||
It is possible to use markdown formating. <br />
|
||||
Only users with tracking permission can add/delete comments. <br />
|
||||
</InfoDrawer>
|
||||
<InfoDrawer title={<b className="text-slate-50">Limitations</b>}>
|
||||
Each comment length is limited to <b>500</b> characters. <br />
|
||||
No more than <b>{isSubscriptionActive ? '500' : '30'}</b> comments are allowed per system*. <br />
|
||||
<small>* based on active map subscription.</small>
|
||||
</InfoDrawer>
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<CommentsWidgetContent />
|
||||
</Widget>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './CommentsWidget';
|
||||
@@ -13,6 +13,7 @@ export const LocalCharactersItemTemplate = ({ showShipName, ...options }: LocalC
|
||||
className={clsx(
|
||||
classes.CharacterRow,
|
||||
'box-border flex items-center w-full whitespace-nowrap overflow-hidden text-ellipsis min-w-[0px]',
|
||||
'px-1',
|
||||
{
|
||||
'surface-hover': options.odd,
|
||||
'border-b border-gray-600 border-opacity-20': !options.last,
|
||||
@@ -21,7 +22,7 @@ export const LocalCharactersItemTemplate = ({ showShipName, ...options }: LocalC
|
||||
)}
|
||||
style={{ height: `${options.props.itemSize}px` }}
|
||||
>
|
||||
<CharacterCard showShipName={showShipName} {...options} />
|
||||
<CharacterCard showShipName={showShipName} showTicker {...options} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,79 +1,34 @@
|
||||
import React, { createContext, useContext, useEffect } from 'react';
|
||||
import { ContextStoreDataUpdate, useContextStore } from '@/hooks/Mapper/utils';
|
||||
import { SESSION_KEY } from '@/hooks/Mapper/constants.ts';
|
||||
import React, { createContext, forwardRef, useContext, useImperativeHandle, useState } from 'react';
|
||||
import {
|
||||
RoutesImperativeHandle,
|
||||
RoutesProviderInnerProps,
|
||||
RoutesWidgetProps,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/types.ts';
|
||||
|
||||
export type RoutesType = {
|
||||
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 {
|
||||
type MapProviderProps = {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
} & RoutesWidgetProps;
|
||||
|
||||
export const DEFAULT_SETTINGS: RoutesType = {
|
||||
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>({
|
||||
const RoutesContext = createContext<RoutesProviderInnerProps>({
|
||||
update: () => {},
|
||||
data: { ...DEFAULT_SETTINGS },
|
||||
// @ts-ignore
|
||||
data: {},
|
||||
});
|
||||
|
||||
export const RoutesProvider: React.FC<MapProviderProps> = ({ children }) => {
|
||||
const { update, ref } = useContextStore<RoutesType>(
|
||||
{ ...DEFAULT_SETTINGS },
|
||||
{
|
||||
onAfterAUpdate: values => {
|
||||
localStorage.setItem(SESSION_KEY.routes, JSON.stringify(values));
|
||||
},
|
||||
export const RoutesProvider = forwardRef<RoutesImperativeHandle, MapProviderProps>(({ children, ...props }, ref) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
stopLoading() {
|
||||
setLoading(false);
|
||||
},
|
||||
);
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
const items = localStorage.getItem(SESSION_KEY.routes);
|
||||
if (items) {
|
||||
update(JSON.parse(items));
|
||||
}
|
||||
}, [update]);
|
||||
|
||||
return (
|
||||
<RoutesContext.Provider
|
||||
value={{
|
||||
update,
|
||||
data: ref,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</RoutesContext.Provider>
|
||||
);
|
||||
};
|
||||
return <RoutesContext.Provider value={{ ...props, loading, setLoading }}>{children}</RoutesContext.Provider>;
|
||||
});
|
||||
RoutesProvider.displayName = 'RoutesProvider';
|
||||
|
||||
export const useRouteProvider = () => {
|
||||
const context = useContext<MapContextProps>(RoutesContext);
|
||||
const context = useContext<RoutesProviderInnerProps>(RoutesContext);
|
||||
return context;
|
||||
};
|
||||
|
||||
@@ -2,13 +2,14 @@ import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import {
|
||||
LayoutEventBlocker,
|
||||
LoadingWrapper,
|
||||
SystemViewStandalone,
|
||||
TooltipPosition,
|
||||
WdCheckbox,
|
||||
WdImgButton,
|
||||
} from '@/hooks/Mapper/components/ui-kit';
|
||||
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 classes from './RoutesWidget.module.scss';
|
||||
import { useLoadRoutes } from './hooks';
|
||||
@@ -25,7 +26,10 @@ import {
|
||||
AddSystemDialog,
|
||||
SearchOnSubmitCallback,
|
||||
} 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 distA = a.has_connection ? a.systems?.length || 0 : Infinity;
|
||||
@@ -36,19 +40,16 @@ const sortByDist = (a: Route, b: Route) => {
|
||||
|
||||
export const RoutesWidgetContent = () => {
|
||||
const {
|
||||
data: { selectedSystems, hubs = [], systems, routes },
|
||||
outCommand,
|
||||
data: { selectedSystems, systems, isSubscriptionActive },
|
||||
} = useMapRootState();
|
||||
const { hubs = [], routesList, isRestricted } = useRouteProvider();
|
||||
|
||||
const [systemId] = selectedSystems;
|
||||
|
||||
const { loading } = useLoadRoutes();
|
||||
|
||||
const { systems: systemStatics, loadSystems, lastUpdateKey } = useLoadSystemStatic({ systems: hubs ?? [] });
|
||||
const { open, ...systemCtxProps } = useContextMenuSystemInfoHandlers({
|
||||
outCommand,
|
||||
hubs,
|
||||
});
|
||||
const { open, ...systemCtxProps } = useContextMenuSystemInfoHandlers();
|
||||
|
||||
const preparedHubs = useMemo(() => {
|
||||
return hubs.map(x => {
|
||||
@@ -61,20 +62,20 @@ export const RoutesWidgetContent = () => {
|
||||
|
||||
const preparedRoutes: Route[] = useMemo(() => {
|
||||
return (
|
||||
routes?.routes
|
||||
routesList?.routes
|
||||
.sort(sortByDist)
|
||||
.filter(x => x.destination.toString() !== systemId)
|
||||
// .filter(x => x.destination.toString() !== systemId)
|
||||
.map(route => ({
|
||||
...route,
|
||||
mapped_systems:
|
||||
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,
|
||||
),
|
||||
) ?? [],
|
||||
})) ?? []
|
||||
);
|
||||
}, [routes?.routes, routes?.systems_static_data, systemId]);
|
||||
}, [routesList?.routes, routesList?.systems_static_data, systemId]);
|
||||
|
||||
const refData = useRef({ open, loadSystems, preparedRoutes });
|
||||
refData.current = { open, loadSystems, preparedRoutes };
|
||||
@@ -97,9 +98,13 @@ export const RoutesWidgetContent = () => {
|
||||
[handleClick],
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
if (isRestricted && !isSubscriptionActive) {
|
||||
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 (
|
||||
<>
|
||||
{systemId !== undefined && routes && (
|
||||
<LoadingWrapper loading={loading}>
|
||||
<div className={clsx(classes.RoutesGrid, 'px-2 py-2')}>
|
||||
{preparedRoutes.map(route => {
|
||||
const sys = preparedHubs.find(x => x.solar_system_id === route.destination)!;
|
||||
@@ -132,7 +137,11 @@ export const RoutesWidgetContent = () => {
|
||||
<WdImgButton
|
||||
className={clsx(PrimeIcons.BARS, classes.RemoveBtn)}
|
||||
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
|
||||
@@ -151,7 +160,7 @@ export const RoutesWidgetContent = () => {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</LoadingWrapper>
|
||||
|
||||
<ContextMenuSystemInfo
|
||||
hubs={hubs}
|
||||
@@ -165,15 +174,13 @@ export const RoutesWidgetContent = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const RoutesWidgetComp = () => {
|
||||
const [routeSettingsVisible, setRouteSettingsVisible] = useState(false);
|
||||
const { data, update } = useRouteProvider();
|
||||
const {
|
||||
data: { hubs = [] },
|
||||
outCommand,
|
||||
} = useMapRootState();
|
||||
type RoutesWidgetCompProps = {
|
||||
title: ReactNode | string;
|
||||
};
|
||||
|
||||
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 handleSecureChange = useCallback(() => {
|
||||
@@ -190,24 +197,15 @@ export const RoutesWidgetComp = () => {
|
||||
const onAddSystem = useCallback(() => setOpenAddSystem(true), []);
|
||||
|
||||
const handleSubmitAddSystem: SearchOnSubmitCallback = useCallback(
|
||||
async item => {
|
||||
if (preparedHubs.includes(item.value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await outCommand({
|
||||
type: OutCommand.addHub,
|
||||
data: { system_id: item.value },
|
||||
});
|
||||
},
|
||||
[hubs, outCommand],
|
||||
async item => addHubCommand(item.value.toString()),
|
||||
[addHubCommand],
|
||||
);
|
||||
|
||||
return (
|
||||
<Widget
|
||||
label={
|
||||
<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">
|
||||
<WdImgButton
|
||||
className={PrimeIcons.PLUS_CIRCLE}
|
||||
@@ -231,6 +229,7 @@ export const RoutesWidgetComp = () => {
|
||||
className={PrimeIcons.SLIDERS_H}
|
||||
onClick={() => setRouteSettingsVisible(true)}
|
||||
tooltip={{
|
||||
position: TooltipPosition.top,
|
||||
content: 'Click here to open Routes settings',
|
||||
}}
|
||||
/>
|
||||
@@ -251,10 +250,13 @@ export const RoutesWidgetComp = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const RoutesWidget = () => {
|
||||
return (
|
||||
<RoutesProvider>
|
||||
<RoutesWidgetComp />
|
||||
</RoutesProvider>
|
||||
);
|
||||
};
|
||||
export const RoutesWidget = forwardRef<RoutesImperativeHandle, RoutesWidgetProps & RoutesWidgetCompProps>(
|
||||
({ title, ...props }, ref) => {
|
||||
return (
|
||||
<RoutesProvider {...props} ref={ref}>
|
||||
<RoutesWidgetComp title={title} />
|
||||
</RoutesProvider>
|
||||
);
|
||||
},
|
||||
);
|
||||
RoutesWidget.displayName = 'RoutesWidget';
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { OutCommand } from '@/hooks/Mapper/types';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import {
|
||||
RoutesType,
|
||||
useRouteProvider,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesProvider.tsx';
|
||||
import { useRouteProvider } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesProvider.tsx';
|
||||
import { RoutesType } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
|
||||
function usePrevious<T>(value: T): T | undefined {
|
||||
const ref = useRef<T>();
|
||||
@@ -17,12 +14,10 @@ function usePrevious<T>(value: T): T | undefined {
|
||||
}
|
||||
|
||||
export const useLoadRoutes = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { data: routesSettings } = useRouteProvider();
|
||||
const { data: routesSettings, loadRoutesCommand, hubs, routesList, loading, setLoading } = useRouteProvider();
|
||||
|
||||
const {
|
||||
outCommand,
|
||||
data: { selectedSystems, hubs, systems, connections },
|
||||
data: { selectedSystems, systems, connections },
|
||||
} = useMapRootState();
|
||||
|
||||
const prevSys = usePrevious(systems);
|
||||
@@ -31,17 +26,16 @@ export const useLoadRoutes = () => {
|
||||
|
||||
const loadRoutes = useCallback(
|
||||
(systemId: string, routesSettings: RoutesType) => {
|
||||
outCommand({
|
||||
type: OutCommand.getRoutes,
|
||||
data: {
|
||||
system_id: systemId,
|
||||
routes_settings: routesSettings,
|
||||
},
|
||||
});
|
||||
loadRoutesCommand(systemId, routesSettings);
|
||||
setLoading(true);
|
||||
},
|
||||
[outCommand],
|
||||
[loadRoutesCommand],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(false);
|
||||
}, [routesList]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedSystems.length !== 1) {
|
||||
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 { 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 { 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 { getSystemById } from '@/hooks/Mapper/helpers';
|
||||
import { ANOIK_ICON, DOTLAN_ICON, ZKB_ICON } from '@/hooks/Mapper/icons';
|
||||
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
|
||||
|
||||
export const SystemInfo = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
const {
|
||||
data: { selectedSystems, systems },
|
||||
data: { selectedSystems },
|
||||
} = useMapRootState();
|
||||
|
||||
const [systemId] = selectedSystems;
|
||||
|
||||
const sys = getSystemById(systems, systemId)!;
|
||||
const { solar_system_name: solarSystemName } = sys?.system_static_info || {};
|
||||
const systemStaticInfo = getSystemStaticInfo(systemId)!;
|
||||
const { solar_system_name: solarSystemName } = systemStaticInfo || {};
|
||||
|
||||
const isNotSelectedSystem = selectedSystems.length !== 1;
|
||||
|
||||
@@ -42,7 +42,7 @@ export const SystemInfo = () => {
|
||||
<WdImgButton
|
||||
className="pi pi-pen-to-square"
|
||||
onClick={() => setVisible(true)}
|
||||
tooltip={{ content: 'Edit system name and description' }}
|
||||
tooltip={{ position: TooltipPosition.top, content: 'Edit system name and description' }}
|
||||
/>
|
||||
</LayoutEventBlocker>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormhol
|
||||
import { useMemo } from 'react';
|
||||
import { getSystemById, sortWHClasses } from '@/hooks/Mapper/helpers';
|
||||
import { InfoDrawer, WHClassView, WHEffectView } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
|
||||
|
||||
interface SystemInfoContentProps {
|
||||
systemId: string;
|
||||
@@ -14,9 +15,9 @@ export const SystemInfoContent = ({ systemId }: SystemInfoContentProps) => {
|
||||
} = useMapRootState();
|
||||
|
||||
const sys = getSystemById(systems, systemId)! || {};
|
||||
const systemStaticInfo = getSystemStaticInfo(systemId)!;
|
||||
const { description } = sys;
|
||||
const { system_class, region_name, constellation_name, statics, effect_name, effect_power } =
|
||||
sys.system_static_info || {};
|
||||
const { system_class, region_name, constellation_name, statics, effect_name, effect_power } = systemStaticInfo || {};
|
||||
const isWH = isWormholeSpace(system_class);
|
||||
const sortedStatics = useMemo(() => sortWHClasses(wormholesData, statics), [wormholesData, statics]);
|
||||
|
||||
|
||||
@@ -1,109 +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,
|
||||
});
|
||||
|
||||
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 min-h-0">
|
||||
<div className="flex flex-col flex-1 min-h-0">
|
||||
<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>
|
||||
) : (
|
||||
<div className="w-full h-full" style={{ height: '100%' }}>
|
||||
<SystemKillsContent
|
||||
kills={filteredKills}
|
||||
systemNameMap={systemNameMap}
|
||||
onlyOneSystem={!visible}
|
||||
timeRange={settings.timeRange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Widget>
|
||||
</div>
|
||||
|
||||
{settingsDialogVisible && <KillsSettingsDialog visible setVisible={setSettingsDialogVisible} />}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
SystemKills.displayName = 'SystemKills';
|
||||
@@ -1,14 +0,0 @@
|
||||
.wrapper {
|
||||
overflow-x: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.scrollerContent {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.VirtualScroller {
|
||||
height: 100% !important;
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
import React, { useMemo, useRef, useEffect, useState } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { DetailedKill } from '@/hooks/Mapper/types/kills';
|
||||
import { VirtualScroller } from 'primereact/virtualscroller';
|
||||
import { useSystemKillsItemTemplate } from '../hooks/useSystemKillsItemTemplate';
|
||||
import classes from './SystemKillsContent.module.scss';
|
||||
|
||||
export const ITEM_HEIGHT = 35;
|
||||
export const CONTENT_MARGINS = 5;
|
||||
|
||||
export interface SystemKillsContentProps {
|
||||
kills: DetailedKill[];
|
||||
systemNameMap: Record<string, string>;
|
||||
onlyOneSystem?: boolean;
|
||||
autoSize?: boolean;
|
||||
timeRange?: number;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export const SystemKillsContent: React.FC<SystemKillsContentProps> = ({
|
||||
kills,
|
||||
systemNameMap,
|
||||
onlyOneSystem = false,
|
||||
autoSize = false,
|
||||
timeRange = 4,
|
||||
limit,
|
||||
}) => {
|
||||
const processedKills = useMemo(() => {
|
||||
const sortedKills = kills
|
||||
.filter(k => k.kill_time)
|
||||
.sort((a, b) => new Date(b.kill_time!).getTime() - new Date(a.kill_time!).getTime());
|
||||
|
||||
if (limit !== undefined) {
|
||||
return sortedKills.slice(0, limit);
|
||||
} else {
|
||||
const now = Date.now();
|
||||
const cutoff = now - timeRange * 60 * 60 * 1000;
|
||||
return sortedKills.filter(k => new Date(k.kill_time!).getTime() >= cutoff);
|
||||
}
|
||||
}, [kills, timeRange, limit]);
|
||||
|
||||
const computedHeight = autoSize ? Math.max(processedKills.length, 1) * ITEM_HEIGHT + CONTENT_MARGINS : undefined;
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const scrollerRef = useRef<VirtualScroller | null>(null);
|
||||
const [containerHeight, setContainerHeight] = useState<number>(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (!autoSize && containerRef.current) {
|
||||
const measure = () => {
|
||||
const newHeight = containerRef.current?.clientHeight || 0;
|
||||
setContainerHeight(newHeight);
|
||||
};
|
||||
|
||||
measure();
|
||||
const observer = new ResizeObserver(measure);
|
||||
observer.observe(containerRef.current);
|
||||
window.addEventListener('resize', measure);
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
window.removeEventListener('resize', measure);
|
||||
};
|
||||
}
|
||||
}, [autoSize]);
|
||||
|
||||
const itemTemplate = useSystemKillsItemTemplate(systemNameMap, onlyOneSystem);
|
||||
const scrollerHeight = autoSize ? `${computedHeight}px` : containerHeight ? `${containerHeight}px` : '100%';
|
||||
|
||||
return (
|
||||
<div ref={autoSize ? undefined : containerRef} className={clsx('w-full h-full', classes.wrapper)}>
|
||||
<VirtualScroller
|
||||
ref={autoSize ? undefined : scrollerRef}
|
||||
items={processedKills}
|
||||
itemSize={ITEM_HEIGHT}
|
||||
itemTemplate={itemTemplate}
|
||||
autoSize={autoSize}
|
||||
scrollWidth="100%"
|
||||
style={{ height: scrollerHeight }}
|
||||
className={clsx('w-full h-full custom-scrollbar select-none overflow-x-hidden overflow-y-auto', {
|
||||
[classes.VirtualScroller]: !autoSize,
|
||||
})}
|
||||
pt={{
|
||||
content: {
|
||||
className: classes.scrollerContent,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -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';
|
||||
@@ -1,15 +1,36 @@
|
||||
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { renderIcon } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
|
||||
import { getCharacterPortraitUrl } from '@/hooks/Mapper/helpers';
|
||||
|
||||
export interface SignatureViewProps {}
|
||||
export interface SignatureViewProps {
|
||||
signature: SystemSignature;
|
||||
showCharacterPortrait?: boolean;
|
||||
}
|
||||
|
||||
export const SignatureView = ({ signature, showCharacterPortrait = false }: SignatureViewProps) => {
|
||||
const isWormhole = signature?.group === SignatureGroup.Wormhole;
|
||||
const hasCharacterInfo = showCharacterPortrait && signature.character_eve_id;
|
||||
const groupDisplay = isWormhole ? SignatureGroup.Wormhole : (signature?.group ?? SignatureGroup.CosmicSignature);
|
||||
const characterName = signature.character_name || 'Unknown character';
|
||||
|
||||
export const SignatureView = (sig: SignatureViewProps & SystemSignature) => {
|
||||
return (
|
||||
<div className="flex gap-2 items-center">
|
||||
{renderIcon(sig)}
|
||||
<div>{sig?.eve_id}</div>
|
||||
<div>{sig?.group ?? SignatureGroup.CosmicSignature}</div>
|
||||
<div>{sig?.name}</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex gap-2 items-center px-2">
|
||||
{renderIcon(signature)}
|
||||
<div>{signature?.eve_id}</div>
|
||||
<div>{groupDisplay}</div>
|
||||
{!isWormhole && <div>{signature?.name}</div>}
|
||||
{hasCharacterInfo && (
|
||||
<div className="flex items-center gap-1 ml-2 pl-2 border-l border-stone-700">
|
||||
<img
|
||||
src={getCharacterPortraitUrl(signature.character_eve_id)}
|
||||
alt={characterName}
|
||||
className="w-5 h-5 rounded-sm border border-stone-700"
|
||||
/>
|
||||
<div className="text-xs text-stone-300">{characterName}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import { useRef } from 'react';
|
||||
import {
|
||||
InfoDrawer,
|
||||
LayoutEventBlocker,
|
||||
SystemView,
|
||||
TooltipPosition,
|
||||
WdCheckbox,
|
||||
WdImgButton,
|
||||
} from '@/hooks/Mapper/components/ui-kit';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { CheckboxChangeEvent } from 'primereact/checkbox';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth.ts';
|
||||
import { COMPACT_MAX_WIDTH } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
|
||||
export type HeaderProps = {
|
||||
sigCount: number;
|
||||
lazyDeleteValue: boolean;
|
||||
onLazyDeleteChange: (checked: boolean) => void;
|
||||
pendingCount: number;
|
||||
pendingTimeRemaining?: number; // Time remaining in ms
|
||||
onUndoClick: () => void;
|
||||
onSettingsClick: () => void;
|
||||
};
|
||||
|
||||
export const SystemSignaturesHeader = ({
|
||||
sigCount,
|
||||
lazyDeleteValue,
|
||||
onLazyDeleteChange,
|
||||
pendingCount,
|
||||
pendingTimeRemaining,
|
||||
onUndoClick,
|
||||
onSettingsClick,
|
||||
}: HeaderProps) => {
|
||||
const {
|
||||
data: { selectedSystems },
|
||||
} = useMapRootState();
|
||||
|
||||
const [systemId] = selectedSystems;
|
||||
const isNotSelectedSystem = selectedSystems.length !== 1;
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
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 (
|
||||
<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 gap-1">
|
||||
{!isCompact && (
|
||||
<div className="flex whitespace-nowrap text-ellipsis overflow-hidden text-stone-400">
|
||||
{sigCount ? `[${sigCount}] ` : ''}Signatures {isNotSelectedSystem ? '' : 'in'}
|
||||
</div>
|
||||
)}
|
||||
{!isNotSelectedSystem && <SystemView systemId={systemId} className="select-none text-center" hideRegion />}
|
||||
</div>
|
||||
|
||||
<LayoutEventBlocker className="flex gap-2.5">
|
||||
<WdTooltipWrapper content="Enable Lazy delete">
|
||||
<WdCheckbox
|
||||
size="xs"
|
||||
labelSide="left"
|
||||
label={isCompact ? '' : 'Lazy delete'}
|
||||
value={lazyDeleteValue}
|
||||
classNameLabel="text-stone-400 hover:text-stone-200 transition duration-300 whitespace-nowrap text-ellipsis overflow-hidden"
|
||||
onChange={(event: CheckboxChangeEvent) => onLazyDeleteChange(!!event.checked)}
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
|
||||
{pendingCount > 0 && (
|
||||
<WdImgButton
|
||||
className={PrimeIcons.UNDO}
|
||||
style={{ color: 'red' }}
|
||||
tooltip={{ content: `Undo pending changes (${pendingCount})${formatTimeRemaining()}` }}
|
||||
onClick={onUndoClick}
|
||||
/>
|
||||
)}
|
||||
|
||||
<WdImgButton
|
||||
className={PrimeIcons.QUESTION_CIRCLE}
|
||||
tooltip={{
|
||||
position: TooltipPosition.left,
|
||||
content: (
|
||||
<div className="flex flex-col gap-1">
|
||||
<InfoDrawer title={<b className="text-slate-50">How to add/update signature?</b>}>
|
||||
In game you need to select one or more signatures <br /> in the list in{' '}
|
||||
<b className="text-sky-500">Probe scanner</b>. <br /> Use next hotkeys:
|
||||
<br />
|
||||
<b className="text-sky-500">Shift + LMB</b> or <b className="text-sky-500">Ctrl + LMB</b>
|
||||
<br /> or <b className="text-sky-500">Ctrl + A</b> for select all
|
||||
<br /> and then use <b className="text-sky-500">Ctrl + C</b>, after you need to go <br />
|
||||
here, select Solar system and paste it with <b className="text-sky-500">Ctrl + V</b>
|
||||
</InfoDrawer>
|
||||
<InfoDrawer title={<b className="text-slate-50">How to select?</b>}>
|
||||
For selecting any signature, click on it <br /> with hotkeys{' '}
|
||||
<b className="text-sky-500">Shift + LMB</b> or <b className="text-sky-500">Ctrl + LMB</b>
|
||||
</InfoDrawer>
|
||||
<InfoDrawer title={<b className="text-slate-50">How to delete?</b>}>
|
||||
To delete any signature, first select it <br /> and then press <b className="text-sky-500">Del</b>
|
||||
</InfoDrawer>
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
<WdImgButton className={PrimeIcons.SLIDERS_H} onClick={onSettingsClick} />
|
||||
</LayoutEventBlocker>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './SystemSignatureHeader';
|
||||
@@ -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,22 +2,18 @@ import { Dialog } from 'primereact/dialog';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Button } from 'primereact/button';
|
||||
import { TabPanel, TabView } from 'primereact/tabview';
|
||||
import styles from './SystemSignatureSettingsDialog.module.scss';
|
||||
import { PrettySwitchbox } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components';
|
||||
|
||||
export type Setting = { key: string; name: string; value: boolean; isFilter?: boolean };
|
||||
|
||||
export const COSMIC_SIGNATURE = 'Cosmic Signature';
|
||||
export const COSMIC_ANOMALY = 'Cosmic Anomaly';
|
||||
export const DEPLOYABLE = 'Deployable';
|
||||
export const STRUCTURE = 'Structure';
|
||||
export const STARBASE = 'Starbase';
|
||||
export const SHIP = 'Ship';
|
||||
export const DRONE = 'Drone';
|
||||
import { Dropdown } from 'primereact/dropdown';
|
||||
import {
|
||||
Setting,
|
||||
SettingsTypes,
|
||||
SIGNATURE_SETTINGS,
|
||||
SignatureSettingsType,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
|
||||
interface SystemSignatureSettingsDialogProps {
|
||||
settings: Setting[];
|
||||
onSave: (settings: Setting[]) => void;
|
||||
settings: SignatureSettingsType;
|
||||
onSave: (settings: SignatureSettingsType) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
@@ -27,59 +23,72 @@ export const SystemSignatureSettingsDialog = ({
|
||||
onCancel,
|
||||
}: SystemSignatureSettingsDialogProps) => {
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const [settings, setSettings] = useState<Setting[]>(defaultSettings);
|
||||
const [settings, setSettings] = useState<SignatureSettingsType>(defaultSettings);
|
||||
|
||||
const filterSettings = settings.filter(setting => setting.isFilter);
|
||||
const userSettings = settings.filter(setting => !setting.isFilter);
|
||||
|
||||
const handleSettingsChange = (key: string) => {
|
||||
setSettings(prevState => prevState.map(item => (item.key === key ? { ...item, value: !item.value } : item)));
|
||||
const handleSettingsChange = ({ key, type }: Setting, value?: unknown) => {
|
||||
setSettings(prev => {
|
||||
switch (type) {
|
||||
case SettingsTypes.dropdown:
|
||||
return { ...prev, [key]: value };
|
||||
case SettingsTypes.flag:
|
||||
return { ...prev, [key]: !prev[key] };
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
};
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
onSave(settings);
|
||||
}, [onSave, settings]);
|
||||
|
||||
const renderSetting = (setting: Setting) => {
|
||||
const val = settings[setting.key];
|
||||
if (setting.options) {
|
||||
return (
|
||||
<div key={setting.key} className="flex items-center justify-between gap-2 mb-2">
|
||||
<label className="text-[#b8b8b8] text-[13px] select-none">{setting.name}</label>
|
||||
<Dropdown
|
||||
value={val}
|
||||
options={setting.options}
|
||||
onChange={e => handleSettingsChange(setting, e.value)}
|
||||
className="w-40"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<PrettySwitchbox
|
||||
key={setting.key}
|
||||
label={setting.name}
|
||||
checked={!!val}
|
||||
setChecked={() => handleSettingsChange(setting)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog header="System Signatures Settings" visible={true} onHide={onCancel} className="w-full max-w-lg h-[500px]">
|
||||
<div className="flex flex-col gap-3 justify-between h-full">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className={styles.verticalTabsContainer}>
|
||||
<TabView
|
||||
activeIndex={activeIndex}
|
||||
onTabChange={e => setActiveIndex(e.index)}
|
||||
className={styles.verticalTabView}
|
||||
>
|
||||
<TabPanel header="Filters" headerClassName={styles.verticalTabHeader}>
|
||||
<div className="w-full h-full flex flex-col gap-1">
|
||||
{filterSettings.map(setting => {
|
||||
return (
|
||||
<PrettySwitchbox
|
||||
key={setting.key}
|
||||
label={setting.name}
|
||||
checked={setting.value}
|
||||
setChecked={() => handleSettingsChange(setting.key)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel header="User Interface" headerClassName={styles.verticalTabHeader}>
|
||||
<div className="w-full h-full flex flex-col gap-1">
|
||||
{userSettings.map(setting => {
|
||||
return (
|
||||
<PrettySwitchbox
|
||||
key={setting.key}
|
||||
label={setting.name}
|
||||
checked={setting.value}
|
||||
setChecked={() => handleSettingsChange(setting.key)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</TabPanel>
|
||||
</TabView>
|
||||
</div>
|
||||
<TabView
|
||||
activeIndex={activeIndex}
|
||||
onTabChange={e => setActiveIndex(e.index)}
|
||||
className="vertical-tabs-container"
|
||||
>
|
||||
<TabPanel header="Filters">
|
||||
<div className="w-full h-full flex flex-col gap-1">
|
||||
{SIGNATURE_SETTINGS.filterFlags.map(renderSetting)}
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel header="User Interface">
|
||||
<div className="w-full h-full flex flex-col gap-1">
|
||||
{SIGNATURE_SETTINGS.uiFlags.map(renderSetting)}
|
||||
<div className="my-2 border-t border-stone-700/50"></div>
|
||||
{SIGNATURE_SETTINGS.uiOther.map(renderSetting)}
|
||||
</div>
|
||||
</TabPanel>
|
||||
</TabView>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 justify-end">
|
||||
|
||||
@@ -1,92 +1,38 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
|
||||
import {
|
||||
InfoDrawer,
|
||||
LayoutEventBlocker,
|
||||
SystemView,
|
||||
TooltipPosition,
|
||||
WdCheckbox,
|
||||
WdImgButton,
|
||||
} from '@/hooks/Mapper/components/ui-kit';
|
||||
import { SystemSignaturesContent } from './SystemSignaturesContent';
|
||||
import {
|
||||
COSMIC_ANOMALY,
|
||||
COSMIC_SIGNATURE,
|
||||
DEPLOYABLE,
|
||||
DRONE,
|
||||
Setting,
|
||||
SHIP,
|
||||
STARBASE,
|
||||
STRUCTURE,
|
||||
SystemSignatureSettingsDialog,
|
||||
} from './SystemSignatureSettingsDialog';
|
||||
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { SystemSignatureSettingsDialog } from './SystemSignatureSettingsDialog';
|
||||
import { ExtendedSystemSignature, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { CheckboxChangeEvent } from 'primereact/checkbox';
|
||||
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
import { useHotkey } from '@/hooks/Mapper/hooks';
|
||||
import { COMPACT_MAX_WIDTH } from './constants';
|
||||
import { SystemSignaturesHeader } from './SystemSignatureHeader';
|
||||
import useLocalStorageState from 'use-local-storage-state';
|
||||
import {
|
||||
SETTINGS_KEYS,
|
||||
SETTINGS_VALUES,
|
||||
SIGNATURE_DELETION_TIMEOUTS,
|
||||
SIGNATURE_SETTING_STORE_KEY,
|
||||
SIGNATURE_WINDOW_ID,
|
||||
SIGNATURES_DELETION_TIMING,
|
||||
SignatureSettingsType,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
import { calculateTimeRemaining } from './helpers';
|
||||
|
||||
const SIGNATURE_SETTINGS_KEY = 'wanderer_system_signature_settings_v5_5';
|
||||
export const SHOW_DESCRIPTION_COLUMN_SETTING = 'show_description_column_setting';
|
||||
export const SHOW_UPDATED_COLUMN_SETTING = 'SHOW_UPDATED_COLUMN_SETTING';
|
||||
export const SHOW_CHARACTER_COLUMN_SETTING = 'SHOW_CHARACTER_COLUMN_SETTING';
|
||||
export const LAZY_DELETE_SIGNATURES_SETTING = 'LAZY_DELETE_SIGNATURES_SETTING';
|
||||
export const KEEP_LAZY_DELETE_SETTING = 'KEEP_LAZY_DELETE_ENABLED_SETTING';
|
||||
export const SystemSignatures = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [sigCount, setSigCount] = useState<number>(0);
|
||||
const [pendingSigs, setPendingSigs] = useState<SystemSignature[]>([]);
|
||||
const [pendingTimeRemaining, setPendingTimeRemaining] = useState<number | undefined>();
|
||||
const undoPendingFnRef = useRef<() => void>(() => {});
|
||||
|
||||
const SETTINGS: Setting[] = [
|
||||
{ key: SHOW_UPDATED_COLUMN_SETTING, name: 'Show Updated Column', value: false, isFilter: false },
|
||||
{ key: SHOW_DESCRIPTION_COLUMN_SETTING, name: 'Show Description Column', value: false, isFilter: false },
|
||||
{ key: SHOW_CHARACTER_COLUMN_SETTING, name: 'Show Character Column', value: false, isFilter: false },
|
||||
{ key: LAZY_DELETE_SIGNATURES_SETTING, name: 'Lazy Delete Signatures', value: false, isFilter: false },
|
||||
{ key: KEEP_LAZY_DELETE_SETTING, name: 'Keep "Lazy Delete" Enabled', value: false, isFilter: false },
|
||||
|
||||
{ key: COSMIC_ANOMALY, name: 'Show Anomalies', value: true, isFilter: true },
|
||||
{ key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true, isFilter: true },
|
||||
{ key: DEPLOYABLE, name: 'Show Deployables', value: true, isFilter: true },
|
||||
{ key: STRUCTURE, name: 'Show Structures', value: true, isFilter: true },
|
||||
{ key: STARBASE, name: 'Show Starbase', value: true, isFilter: true },
|
||||
{ key: SHIP, name: 'Show Ships', value: true, isFilter: true },
|
||||
{ key: DRONE, name: 'Show Drones And Charges', value: true, isFilter: true },
|
||||
{ key: SignatureGroup.Wormhole, name: 'Show Wormholes', value: true, isFilter: true },
|
||||
{ key: SignatureGroup.RelicSite, name: 'Show Relic Sites', value: true, isFilter: true },
|
||||
{ key: SignatureGroup.DataSite, name: 'Show Data Sites', value: true, isFilter: true },
|
||||
{ key: SignatureGroup.OreSite, name: 'Show Ore Sites', value: true, isFilter: true },
|
||||
{ key: SignatureGroup.GasSite, name: 'Show Gas Sites', value: true, isFilter: true },
|
||||
{ key: SignatureGroup.CombatSite, name: 'Show Combat Sites', value: true, isFilter: true },
|
||||
];
|
||||
|
||||
const getDefaultSettings = (): Setting[] => [...SETTINGS];
|
||||
|
||||
export const SystemSignatures: React.FC = () => {
|
||||
const {
|
||||
data: { selectedSystems },
|
||||
} = useMapRootState();
|
||||
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
const [currentSettings, setCurrentSettings] = useState<Setting[]>(() => {
|
||||
const stored = localStorage.getItem(SIGNATURE_SETTINGS_KEY);
|
||||
if (stored) {
|
||||
try {
|
||||
return JSON.parse(stored) as Setting[];
|
||||
} catch (error) {
|
||||
console.error('Error parsing stored settings', error);
|
||||
}
|
||||
}
|
||||
return getDefaultSettings();
|
||||
const [currentSettings, setCurrentSettings] = useLocalStorageState(SIGNATURE_SETTING_STORE_KEY, {
|
||||
defaultValue: SETTINGS_VALUES,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(SIGNATURE_SETTINGS_KEY, JSON.stringify(currentSettings));
|
||||
}, [currentSettings]);
|
||||
|
||||
const [sigCount, setSigCount] = useState<number>(0);
|
||||
const [pendingSigs, setPendingSigs] = useState<SystemSignature[]>([]);
|
||||
const [undoPending, setUndoPending] = useState<() => void>(() => () => {});
|
||||
|
||||
const handleSigCountChange = useCallback((count: number) => {
|
||||
setSigCount(count);
|
||||
}, []);
|
||||
@@ -94,105 +40,76 @@ export const SystemSignatures: React.FC = () => {
|
||||
const [systemId] = selectedSystems;
|
||||
const isNotSelectedSystem = selectedSystems.length !== 1;
|
||||
|
||||
const lazyDeleteValue = useMemo(
|
||||
() => currentSettings.find(setting => setting.key === LAZY_DELETE_SIGNATURES_SETTING)?.value || false,
|
||||
[currentSettings],
|
||||
);
|
||||
|
||||
const handleSettingsChange = useCallback((newSettings: Setting[]) => {
|
||||
const handleSettingsChange = useCallback((newSettings: SignatureSettingsType) => {
|
||||
setCurrentSettings(newSettings);
|
||||
setVisible(false);
|
||||
}, []);
|
||||
|
||||
const handleLazyDeleteChange = useCallback((value: boolean) => {
|
||||
setCurrentSettings(prevSettings =>
|
||||
prevSettings.map(setting => (setting.key === LAZY_DELETE_SIGNATURES_SETTING ? { ...setting, value } : setting)),
|
||||
);
|
||||
setCurrentSettings(prev => ({ ...prev, [SETTINGS_KEYS.LAZY_DELETE_SIGNATURES]: value }));
|
||||
}, []);
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const isCompact = useMaxWidth(containerRef, COMPACT_MAX_WIDTH);
|
||||
|
||||
useHotkey(true, ['z'], (event: KeyboardEvent) => {
|
||||
useHotkey(true, ['z'], event => {
|
||||
if (pendingSigs.length > 0) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
undoPending();
|
||||
undoPendingFnRef.current();
|
||||
setPendingSigs([]);
|
||||
setPendingTimeRemaining(undefined);
|
||||
}
|
||||
});
|
||||
|
||||
const handleUndoClick = useCallback(() => {
|
||||
undoPending();
|
||||
undoPendingFnRef.current();
|
||||
setPendingSigs([]);
|
||||
}, [undoPending]);
|
||||
setPendingTimeRemaining(undefined);
|
||||
}, []);
|
||||
|
||||
const handleSettingsButtonClick = useCallback(() => {
|
||||
setVisible(true);
|
||||
}, []);
|
||||
|
||||
const renderLabel = () => (
|
||||
<div className="flex justify-between items-center text-xs w-full h-full" ref={containerRef}>
|
||||
<div className="flex justify-between items-center gap-1">
|
||||
{!isCompact && (
|
||||
<div className="flex whitespace-nowrap text-ellipsis overflow-hidden text-stone-400">
|
||||
{sigCount ? `[${sigCount}] ` : ''}Signatures {isNotSelectedSystem ? '' : 'in'}
|
||||
</div>
|
||||
)}
|
||||
{!isNotSelectedSystem && <SystemView systemId={systemId} className="select-none text-center" hideRegion />}
|
||||
</div>
|
||||
<LayoutEventBlocker className="flex gap-2.5">
|
||||
<WdTooltipWrapper content="Enable Lazy delete">
|
||||
<WdCheckbox
|
||||
size="xs"
|
||||
labelSide="left"
|
||||
label={isCompact ? '' : 'Lazy delete'}
|
||||
value={lazyDeleteValue}
|
||||
classNameLabel="text-stone-400 hover:text-stone-200 transition duration-300 whitespace-nowrap text-ellipsis overflow-hidden"
|
||||
onChange={(event: CheckboxChangeEvent) => handleLazyDeleteChange(!!event.checked)}
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
{pendingSigs.length > 0 && (
|
||||
<WdImgButton
|
||||
className={PrimeIcons.UNDO}
|
||||
style={{ color: 'red' }}
|
||||
tooltip={{ content: `Undo pending changes (${pendingSigs.length})` }}
|
||||
onClick={handleUndoClick}
|
||||
/>
|
||||
)}
|
||||
<WdImgButton
|
||||
className={PrimeIcons.QUESTION_CIRCLE}
|
||||
tooltip={{
|
||||
position: TooltipPosition.left,
|
||||
content: (
|
||||
<div className="flex flex-col gap-1">
|
||||
<InfoDrawer title={<b className="text-slate-50">How to add/update signature?</b>}>
|
||||
In game you need to select one or more signatures <br /> in the list in{' '}
|
||||
<b className="text-sky-500">Probe scanner</b>. <br /> Use next hotkeys:
|
||||
<br />
|
||||
<b className="text-sky-500">Shift + LMB</b> or <b className="text-sky-500">Ctrl + LMB</b>
|
||||
<br /> or <b className="text-sky-500">Ctrl + A</b> for select all
|
||||
<br /> and then use <b className="text-sky-500">Ctrl + C</b>, after you need to go <br />
|
||||
here, select Solar system and paste it with <b className="text-sky-500">Ctrl + V</b>
|
||||
</InfoDrawer>
|
||||
<InfoDrawer title={<b className="text-slate-50">How to select?</b>}>
|
||||
For selecting any signature, click on it <br /> with hotkeys{' '}
|
||||
<b className="text-sky-500">Shift + LMB</b> or <b className="text-sky-500">Ctrl + LMB</b>
|
||||
</InfoDrawer>
|
||||
<InfoDrawer title={<b className="text-slate-50">How to delete?</b>}>
|
||||
To delete any signature, first select it <br /> and then press <b className="text-sky-500">Del</b>
|
||||
</InfoDrawer>
|
||||
</div>
|
||||
) as React.ReactNode,
|
||||
}}
|
||||
/>
|
||||
<WdImgButton className={PrimeIcons.SLIDERS_H} onClick={handleSettingsButtonClick} />
|
||||
</LayoutEventBlocker>
|
||||
</div>
|
||||
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(() => {
|
||||
if (pendingSigs.length === 0) {
|
||||
setPendingTimeRemaining(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
const calculate = () => {
|
||||
setPendingTimeRemaining(() => calculateTimeRemaining(pendingSigs));
|
||||
};
|
||||
|
||||
calculate();
|
||||
const interval = setInterval(calculate, 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, [pendingSigs]);
|
||||
|
||||
return (
|
||||
<Widget label={renderLabel()}>
|
||||
<Widget
|
||||
label={
|
||||
<SystemSignaturesHeader
|
||||
sigCount={sigCount}
|
||||
lazyDeleteValue={currentSettings[SETTINGS_KEYS.LAZY_DELETE_SIGNATURES] as boolean}
|
||||
pendingCount={pendingSigs.length}
|
||||
pendingTimeRemaining={pendingTimeRemaining}
|
||||
onLazyDeleteChange={handleLazyDeleteChange}
|
||||
onUndoClick={handleUndoClick}
|
||||
onSettingsClick={handleSettingsButtonClick}
|
||||
/>
|
||||
}
|
||||
windowId={SIGNATURE_WINDOW_ID}
|
||||
>
|
||||
{isNotSelectedSystem ? (
|
||||
<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
|
||||
@@ -201,12 +118,15 @@ export const SystemSignatures: React.FC = () => {
|
||||
<SystemSignaturesContent
|
||||
systemId={systemId}
|
||||
settings={currentSettings}
|
||||
deletionTiming={
|
||||
SIGNATURE_DELETION_TIMEOUTS[
|
||||
(currentSettings[SETTINGS_KEYS.DELETION_TIMING] as keyof typeof SIGNATURE_DELETION_TIMEOUTS) ||
|
||||
SIGNATURES_DELETION_TIMING.DEFAULT
|
||||
] as number
|
||||
}
|
||||
onLazyDeleteChange={handleLazyDeleteChange}
|
||||
onCountChange={handleSigCountChange}
|
||||
onPendingChange={(pending, undo) => {
|
||||
setPendingSigs(pending);
|
||||
setUndoPending(() => undo);
|
||||
}}
|
||||
onPendingChange={handlePendingChange}
|
||||
/>
|
||||
)}
|
||||
{visible && (
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
import { useEffect, useMemo, useRef, useState, useCallback } from 'react';
|
||||
import { DataTable, DataTableRowClickEvent, DataTableRowMouseEvent, SortOrder } from 'primereact/datatable';
|
||||
import { Column } from 'primereact/column';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { Column } from 'primereact/column';
|
||||
import {
|
||||
DataTable,
|
||||
DataTableRowClickEvent,
|
||||
DataTableRowMouseEvent,
|
||||
DataTableStateEvent,
|
||||
SortOrder,
|
||||
} from 'primereact/datatable';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import useLocalStorageState from 'use-local-storage-state';
|
||||
|
||||
import { SignatureGroup, 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 {
|
||||
COMPACT_MAX_WIDTH,
|
||||
getGroupIdByRawGroup,
|
||||
GROUPS_LIST,
|
||||
MEDIUM_MAX_WIDTH,
|
||||
OTHER_COLUMNS_WIDTH,
|
||||
SETTINGS_KEYS,
|
||||
SIGNATURE_WINDOW_ID,
|
||||
SignatureSettingsType,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants';
|
||||
import {
|
||||
KEEP_LAZY_DELETE_SETTING,
|
||||
LAZY_DELETE_SIGNATURES_SETTING,
|
||||
SHOW_DESCRIPTION_COLUMN_SETTING,
|
||||
SHOW_UPDATED_COLUMN_SETTING,
|
||||
SHOW_CHARACTER_COLUMN_SETTING,
|
||||
} from '../SystemSignatures';
|
||||
import { COSMIC_SIGNATURE } from '../SystemSignatureSettingsDialog';
|
||||
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 {
|
||||
renderAddedTimeLeft,
|
||||
renderDescription,
|
||||
@@ -29,11 +32,12 @@ import {
|
||||
renderInfoColumn,
|
||||
renderUpdatedTimeLeft,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
|
||||
import { ExtendedSystemSignature } from '../helpers/contentHelpers';
|
||||
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 useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth';
|
||||
import { getSignatureRowClass } from '../helpers/rowStyles';
|
||||
import { useSystemSignaturesData } from '../hooks/useSystemSignaturesData';
|
||||
|
||||
const renderColIcon = (sig: SystemSignature) => renderIcon(sig);
|
||||
|
||||
type SystemSignaturesSortSettings = {
|
||||
sortField: string;
|
||||
@@ -47,18 +51,21 @@ const SORT_DEFAULT_VALUES: SystemSignaturesSortSettings = {
|
||||
|
||||
interface SystemSignaturesContentProps {
|
||||
systemId: string;
|
||||
settings: { key: string; value: boolean }[];
|
||||
settings: SignatureSettingsType;
|
||||
hideLinkedSignatures?: boolean;
|
||||
selectable?: boolean;
|
||||
onSelect?: (signature: SystemSignature) => void;
|
||||
onLazyDeleteChange?: (value: boolean) => void;
|
||||
onCountChange?: (count: number) => void;
|
||||
onPendingChange?: (pending: ExtendedSystemSignature[], undo: () => void) => void;
|
||||
onPendingChange?: (
|
||||
pending: React.MutableRefObject<Record<string, ExtendedSystemSignature>>,
|
||||
undo: () => void,
|
||||
) => void;
|
||||
deletionTiming?: number;
|
||||
filterSignature?: (signature: SystemSignature) => boolean;
|
||||
}
|
||||
|
||||
const headerInlineStyle = { padding: '2px', fontSize: '12px', lineHeight: '1.333' };
|
||||
|
||||
export function SystemSignaturesContent({
|
||||
export const SystemSignaturesContent = ({
|
||||
systemId,
|
||||
settings,
|
||||
hideLinkedSignatures,
|
||||
@@ -67,7 +74,27 @@ export function SystemSignaturesContent({
|
||||
onLazyDeleteChange,
|
||||
onCountChange,
|
||||
onPendingChange,
|
||||
}: SystemSignaturesContentProps) {
|
||||
deletionTiming,
|
||||
filterSignature,
|
||||
}: SystemSignaturesContentProps) => {
|
||||
const [selectedSignatureForDialog, setSelectedSignatureForDialog] = useState<SystemSignature | null>(null);
|
||||
const [showSignatureSettings, setShowSignatureSettings] = useState(false);
|
||||
const [nameColumnWidth, setNameColumnWidth] = useState('auto');
|
||||
const [hoveredSignature, setHoveredSignature] = useState<SystemSignature | null>(null);
|
||||
|
||||
const tableRef = useRef<HTMLDivElement>(null);
|
||||
const tooltipRef = useRef<WdTooltipHandlers>(null);
|
||||
|
||||
const isCompact = useMaxWidth(tableRef, COMPACT_MAX_WIDTH);
|
||||
const isMedium = useMaxWidth(tableRef, MEDIUM_MAX_WIDTH);
|
||||
|
||||
const { clipboardContent, setClipboardContent } = useClipboard();
|
||||
|
||||
const [sortSettings, setSortSettings] = useLocalStorageState<{ sortField: string; sortOrder: SortOrder }>(
|
||||
'window:signatures:sort',
|
||||
{ defaultValue: SORT_DEFAULT_VALUES },
|
||||
);
|
||||
|
||||
const { signatures, selectedSignatures, setSelectedSignatures, handleDeleteSelected, handleSelectAll, handlePaste } =
|
||||
useSystemSignaturesData({
|
||||
systemId,
|
||||
@@ -75,112 +102,132 @@ export function SystemSignaturesContent({
|
||||
onCountChange,
|
||||
onPendingChange,
|
||||
onLazyDeleteChange,
|
||||
deletionTiming,
|
||||
});
|
||||
|
||||
const [sortSettings, setSortSettings] = useLocalStorageState<{ sortField: string; sortOrder: SortOrder }>(
|
||||
'window:signatures:sort',
|
||||
{ defaultValue: SORT_DEFAULT_VALUES },
|
||||
);
|
||||
|
||||
const tableRef = useRef<HTMLDivElement>(null);
|
||||
const tooltipRef = useRef<WdTooltipHandlers>(null);
|
||||
const [hoveredSignature, setHoveredSignature] = useState<SystemSignature | null>(null);
|
||||
|
||||
const isCompact = useMaxWidth(tableRef, COMPACT_MAX_WIDTH);
|
||||
const isMedium = useMaxWidth(tableRef, MEDIUM_MAX_WIDTH);
|
||||
|
||||
const lazyDeleteEnabled = settings.find(s => s.key === LAZY_DELETE_SIGNATURES_SETTING)?.value ?? false;
|
||||
const keepLazyDeleteEnabled = settings.find(s => s.key === KEEP_LAZY_DELETE_SETTING)?.value ?? false;
|
||||
|
||||
const { clipboardContent, setClipboardContent } = useClipboard();
|
||||
useEffect(() => {
|
||||
if (selectable) return;
|
||||
if (!clipboardContent?.text) return;
|
||||
|
||||
handlePaste(clipboardContent.text);
|
||||
|
||||
if (lazyDeleteEnabled && !keepLazyDeleteEnabled) {
|
||||
onLazyDeleteChange?.(false);
|
||||
}
|
||||
setClipboardContent(null);
|
||||
}, [
|
||||
selectable,
|
||||
clipboardContent,
|
||||
handlePaste,
|
||||
setClipboardContent,
|
||||
lazyDeleteEnabled,
|
||||
keepLazyDeleteEnabled,
|
||||
onLazyDeleteChange,
|
||||
]);
|
||||
}, [selectable, clipboardContent, handlePaste, setClipboardContent]);
|
||||
|
||||
useHotkey(true, ['a'], handleSelectAll);
|
||||
|
||||
useHotkey(false, ['Backspace', 'Delete'], (event: KeyboardEvent) => {
|
||||
const targetWindow = (event.target as HTMLHtmlElement)?.closest(`[data-window-id="${SIGNATURE_WINDOW_ID}"]`);
|
||||
|
||||
if (!targetWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
handleDeleteSelected();
|
||||
});
|
||||
|
||||
const [nameColumnWidth, setNameColumnWidth] = useState('auto');
|
||||
const handleResize = useCallback(() => {
|
||||
if (!tableRef.current) return;
|
||||
const tableWidth = tableRef.current.offsetWidth;
|
||||
const otherColumnsWidth = OTHER_COLUMNS_WIDTH;
|
||||
setNameColumnWidth(`${tableWidth - otherColumnsWidth}px`);
|
||||
|
||||
setNameColumnWidth(`${tableRef.current.offsetWidth - OTHER_COLUMNS_WIDTH}px`);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!tableRef.current) return;
|
||||
|
||||
const observer = new ResizeObserver(handleResize);
|
||||
observer.observe(tableRef.current);
|
||||
handleResize();
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [handleResize]);
|
||||
|
||||
const [selectedSignatureForDialog, setSelectedSignatureForDialog] = useState<SystemSignature | null>(null);
|
||||
const [showSignatureSettings, setShowSignatureSettings] = useState(false);
|
||||
|
||||
const handleRowClick = (e: DataTableRowClickEvent) => {
|
||||
const handleRowClick = useCallback((e: DataTableRowClickEvent) => {
|
||||
setSelectedSignatureForDialog(e.data as SystemSignature);
|
||||
setShowSignatureSettings(true);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleSelectSignatures = useCallback(
|
||||
(e: { value: SystemSignature[] }) => {
|
||||
if (selectable) {
|
||||
onSelect?.(e.value[0]);
|
||||
} else {
|
||||
setSelectedSignatures(e.value as ExtendedSystemSignature[]);
|
||||
}
|
||||
selectable ? onSelect?.(e.value[0]) : setSelectedSignatures(e.value as ExtendedSystemSignature[]);
|
||||
},
|
||||
[selectable, onSelect, setSelectedSignatures],
|
||||
[selectable],
|
||||
);
|
||||
|
||||
const showDescriptionColumn = settings.find(s => s.key === SHOW_DESCRIPTION_COLUMN_SETTING)?.value;
|
||||
const showUpdatedColumn = settings.find(s => s.key === SHOW_UPDATED_COLUMN_SETTING)?.value;
|
||||
const showCharacterColumn = settings.find(s => s.key === SHOW_CHARACTER_COLUMN_SETTING)?.value;
|
||||
|
||||
const enabledGroups = settings
|
||||
.filter(s => GROUPS_LIST.includes(s.key as SignatureGroup) && s.value === true)
|
||||
.map(s => s.key);
|
||||
const { showDescriptionColumn, showUpdatedColumn, showCharacterColumn, showCharacterPortrait } = useMemo(
|
||||
() => ({
|
||||
showDescriptionColumn: settings[SETTINGS_KEYS.SHOW_DESCRIPTION_COLUMN] as boolean,
|
||||
showUpdatedColumn: settings[SETTINGS_KEYS.SHOW_UPDATED_COLUMN] as boolean,
|
||||
showCharacterColumn: settings[SETTINGS_KEYS.SHOW_CHARACTER_COLUMN] as boolean,
|
||||
showCharacterPortrait: settings[SETTINGS_KEYS.SHOW_CHARACTER_PORTRAIT] as boolean,
|
||||
}),
|
||||
[settings],
|
||||
);
|
||||
|
||||
const filteredSignatures = useMemo<ExtendedSystemSignature[]>(() => {
|
||||
return signatures.filter(sig => {
|
||||
if (filterSignature && !filterSignature(sig)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hideLinkedSignatures && sig.linked_system) {
|
||||
return false;
|
||||
}
|
||||
if (sig.kind === COSMIC_SIGNATURE) {
|
||||
const showCosmic = settings.find(y => y.key === COSMIC_SIGNATURE)?.value;
|
||||
if (!showCosmic) return false;
|
||||
if (sig.group) {
|
||||
return enabledGroups.includes(sig.group);
|
||||
|
||||
if (sig.kind === SignatureKind.CosmicSignature) {
|
||||
if (!settings[SETTINGS_KEYS.COSMIC_SIGNATURE]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sig.group) {
|
||||
const enabledGroups = Object.keys(settings).filter(
|
||||
x => GROUPS_LIST.includes(x as SignatureGroup) && settings[x as SETTINGS_KEYS],
|
||||
);
|
||||
|
||||
return enabledGroups.includes(getGroupIdByRawGroup(sig.group));
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return settings.find(y => y.key === sig.kind)?.value;
|
||||
}
|
||||
|
||||
return settings[sig.kind];
|
||||
});
|
||||
}, [signatures, hideLinkedSignatures, settings, enabledGroups]);
|
||||
}, [signatures, hideLinkedSignatures, settings, filterSignature]);
|
||||
|
||||
const onRowMouseEnter = useCallback((e: DataTableRowMouseEvent) => {
|
||||
setHoveredSignature(e.data as SystemSignature);
|
||||
tooltipRef.current?.show(e.originalEvent);
|
||||
}, []);
|
||||
|
||||
const onRowMouseLeave = useCallback(() => {
|
||||
setHoveredSignature(null);
|
||||
tooltipRef.current?.hide();
|
||||
}, []);
|
||||
|
||||
const refVars = useRef({ settings, selectedSignatures, setSortSettings });
|
||||
refVars.current = { settings, selectedSignatures, setSortSettings };
|
||||
|
||||
// @ts-ignore
|
||||
const getRowClassName = useCallback(rowData => {
|
||||
if (!rowData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getSignatureRowClass(
|
||||
rowData as ExtendedSystemSignature,
|
||||
refVars.current.selectedSignatures,
|
||||
refVars.current.settings[SETTINGS_KEYS.COLOR_BY_TYPE] as boolean,
|
||||
);
|
||||
}, []);
|
||||
|
||||
const handleSortSettings = useCallback(
|
||||
(e: DataTableStateEvent) => refVars.current.setSortSettings({ sortField: e.sortField, sortOrder: e.sortOrder }),
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div ref={tableRef} className="h-full">
|
||||
@@ -204,37 +251,22 @@ export function SystemSignaturesContent({
|
||||
onRowDoubleClick={handleRowClick}
|
||||
sortField={sortSettings.sortField}
|
||||
sortOrder={sortSettings.sortOrder}
|
||||
onSort={e => setSortSettings({ sortField: e.sortField, sortOrder: e.sortOrder })}
|
||||
onRowMouseEnter={
|
||||
isCompact || isMedium
|
||||
? (e: DataTableRowMouseEvent) => {
|
||||
setHoveredSignature(filteredSignatures[e.index]);
|
||||
tooltipRef.current?.show(e.originalEvent);
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onRowMouseLeave={
|
||||
isCompact || isMedium
|
||||
? () => {
|
||||
setHoveredSignature(null);
|
||||
tooltipRef.current?.hide();
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
rowClassName={rowData => getSignatureRowClass(rowData as ExtendedSystemSignature, selectedSignatures)}
|
||||
onSort={handleSortSettings}
|
||||
onRowMouseEnter={onRowMouseEnter}
|
||||
onRowMouseLeave={onRowMouseLeave}
|
||||
// @ts-ignore
|
||||
rowClassName={getRowClassName}
|
||||
>
|
||||
<Column
|
||||
field="icon"
|
||||
header=""
|
||||
headerStyle={headerInlineStyle}
|
||||
body={sig => renderIcon(sig)}
|
||||
body={renderColIcon}
|
||||
bodyClassName="p-0 px-1"
|
||||
style={{ maxWidth: 26, minWidth: 26, width: 26 }}
|
||||
/>
|
||||
<Column
|
||||
field="eve_id"
|
||||
header="Id"
|
||||
headerStyle={headerInlineStyle}
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
style={{ maxWidth: 72, minWidth: 72, width: 72 }}
|
||||
sortable
|
||||
@@ -242,7 +274,6 @@ export function SystemSignaturesContent({
|
||||
<Column
|
||||
field="group"
|
||||
header="Group"
|
||||
headerStyle={headerInlineStyle}
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
style={{ maxWidth: 110, minWidth: 110, width: 110 }}
|
||||
body={sig => sig.group ?? ''}
|
||||
@@ -252,7 +283,6 @@ export function SystemSignaturesContent({
|
||||
<Column
|
||||
field="info"
|
||||
header="Info"
|
||||
headerStyle={headerInlineStyle}
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
style={{ maxWidth: nameColumnWidth }}
|
||||
hidden={isCompact || isMedium}
|
||||
@@ -262,7 +292,6 @@ export function SystemSignaturesContent({
|
||||
<Column
|
||||
field="description"
|
||||
header="Description"
|
||||
headerStyle={headerInlineStyle}
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
hidden={isCompact}
|
||||
body={renderDescription}
|
||||
@@ -272,18 +301,16 @@ export function SystemSignaturesContent({
|
||||
<Column
|
||||
field="inserted_at"
|
||||
header="Added"
|
||||
headerStyle={headerInlineStyle}
|
||||
dataType="date"
|
||||
body={renderAddedTimeLeft}
|
||||
style={{ minWidth: 70, maxWidth: 80 }}
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
bodyClassName="ssc-header text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
sortable
|
||||
/>
|
||||
{showUpdatedColumn && (
|
||||
<Column
|
||||
field="updated_at"
|
||||
header="Updated"
|
||||
headerStyle={headerInlineStyle}
|
||||
dataType="date"
|
||||
body={renderUpdatedTimeLeft}
|
||||
style={{ minWidth: 70, maxWidth: 80 }}
|
||||
@@ -304,7 +331,6 @@ export function SystemSignaturesContent({
|
||||
{!selectable && (
|
||||
<Column
|
||||
header=""
|
||||
headerStyle={headerInlineStyle}
|
||||
body={() => (
|
||||
<div className="flex justify-end items-center gap-2 mr-[4px]">
|
||||
<WdTooltipWrapper content="Double-click a row to edit signature">
|
||||
@@ -322,7 +348,12 @@ export function SystemSignaturesContent({
|
||||
<WdTooltip
|
||||
className="bg-stone-900/95 text-slate-50"
|
||||
ref={tooltipRef}
|
||||
content={hoveredSignature ? <SignatureView {...hoveredSignature} /> : null}
|
||||
position={TooltipPosition.top}
|
||||
content={
|
||||
hoveredSignature ? (
|
||||
<SignatureView signature={hoveredSignature} showCharacterPortrait={showCharacterPortrait} />
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
|
||||
{showSignatureSettings && (
|
||||
@@ -335,4 +366,4 @@ export function SystemSignaturesContent({
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -9,8 +9,8 @@ import {
|
||||
} from '@/hooks/Mapper/types';
|
||||
|
||||
export const TIME_ONE_MINUTE = 1000 * 60;
|
||||
export const TIME_TEN_MINUTES = 1000 * 60 * 10;
|
||||
export const TIME_ONE_DAY = 24 * 60 * 60 * 1000;
|
||||
export const TIME_TEN_MINUTES = TIME_ONE_MINUTE * 10;
|
||||
export const TIME_ONE_DAY = 24 * 60 * TIME_ONE_MINUTE;
|
||||
export const TIME_ONE_WEEK = 7 * TIME_ONE_DAY;
|
||||
export const FINAL_DURATION_MS = 10000;
|
||||
|
||||
@@ -79,3 +79,132 @@ export const MAPPING_TYPE_TO_ENG = {
|
||||
};
|
||||
|
||||
export const getGroupIdByRawGroup = (val: string) => MAPPING_GROUP_TO_ENG[val as SignatureGroup];
|
||||
|
||||
export const SIGNATURE_WINDOW_ID = 'system_signatures_window';
|
||||
export const SIGNATURE_SETTING_STORE_KEY = 'wanderer_system_signature_settings_v6_5';
|
||||
|
||||
export enum SETTINGS_KEYS {
|
||||
SHOW_DESCRIPTION_COLUMN = 'show_description_column',
|
||||
SHOW_UPDATED_COLUMN = 'show_updated_column',
|
||||
SHOW_CHARACTER_COLUMN = 'show_character_column',
|
||||
LAZY_DELETE_SIGNATURES = 'lazy_delete_signatures',
|
||||
KEEP_LAZY_DELETE = 'keep_lazy_delete_enabled',
|
||||
DELETION_TIMING = 'deletion_timing',
|
||||
COLOR_BY_TYPE = 'color_by_type',
|
||||
SHOW_CHARACTER_PORTRAIT = 'show_character_portrait',
|
||||
|
||||
// From SignatureKind
|
||||
COSMIC_ANOMALY = SignatureKind.CosmicAnomaly,
|
||||
COSMIC_SIGNATURE = SignatureKind.CosmicSignature,
|
||||
DEPLOYABLE = SignatureKind.Deployable,
|
||||
STRUCTURE = SignatureKind.Structure,
|
||||
STARBASE = SignatureKind.Starbase,
|
||||
SHIP = SignatureKind.Ship,
|
||||
DRONE = SignatureKind.Drone,
|
||||
|
||||
// From SignatureGroup
|
||||
WORMHOLE = SignatureGroup.Wormhole,
|
||||
RELIC_SITE = SignatureGroup.RelicSite,
|
||||
DATA_SITE = SignatureGroup.DataSite,
|
||||
ORE_SITE = SignatureGroup.OreSite,
|
||||
GAS_SITE = SignatureGroup.GasSite,
|
||||
COMBAT_SITE = SignatureGroup.CombatSite,
|
||||
}
|
||||
|
||||
export enum SettingsTypes {
|
||||
flag,
|
||||
dropdown,
|
||||
}
|
||||
|
||||
export type SignatureSettingsType = { [key in SETTINGS_KEYS]?: unknown };
|
||||
|
||||
export type Setting = {
|
||||
key: SETTINGS_KEYS;
|
||||
name: string;
|
||||
type: SettingsTypes;
|
||||
isSeparator?: boolean;
|
||||
options?: { label: string; value: any }[];
|
||||
};
|
||||
|
||||
export enum SIGNATURES_DELETION_TIMING {
|
||||
IMMEDIATE,
|
||||
DEFAULT,
|
||||
EXTENDED,
|
||||
}
|
||||
|
||||
export type SignatureDeletionTimingType = { [key in SIGNATURES_DELETION_TIMING]?: unknown };
|
||||
|
||||
export const SIGNATURE_SETTINGS = {
|
||||
filterFlags: [
|
||||
{ type: SettingsTypes.flag, key: SETTINGS_KEYS.COSMIC_ANOMALY, name: 'Show Anomalies' },
|
||||
{ type: SettingsTypes.flag, key: SETTINGS_KEYS.COSMIC_SIGNATURE, name: 'Show Cosmic Signatures' },
|
||||
{ type: SettingsTypes.flag, key: SETTINGS_KEYS.DEPLOYABLE, name: 'Show Deployables' },
|
||||
{ type: SettingsTypes.flag, key: SETTINGS_KEYS.STRUCTURE, name: 'Show Structures' },
|
||||
{ type: SettingsTypes.flag, key: SETTINGS_KEYS.STARBASE, name: 'Show Starbase' },
|
||||
{ type: SettingsTypes.flag, key: SETTINGS_KEYS.SHIP, name: 'Show Ships' },
|
||||
{ type: SettingsTypes.flag, key: SETTINGS_KEYS.DRONE, name: 'Show Drones And Charges' },
|
||||
{ type: SettingsTypes.flag, key: SETTINGS_KEYS.WORMHOLE, name: 'Show Wormholes' },
|
||||
{ type: SettingsTypes.flag, key: SETTINGS_KEYS.RELIC_SITE, name: 'Show Relic Sites' },
|
||||
{ type: SettingsTypes.flag, key: SETTINGS_KEYS.DATA_SITE, name: 'Show Data Sites' },
|
||||
{ type: SettingsTypes.flag, key: SETTINGS_KEYS.ORE_SITE, name: 'Show Ore Sites' },
|
||||
{ type: SettingsTypes.flag, key: SETTINGS_KEYS.GAS_SITE, name: 'Show Gas Sites' },
|
||||
{ type: SettingsTypes.flag, key: SETTINGS_KEYS.COMBAT_SITE, name: 'Show Combat Sites' },
|
||||
],
|
||||
uiFlags: [
|
||||
{ type: SettingsTypes.flag, key: SETTINGS_KEYS.SHOW_UPDATED_COLUMN, name: 'Show Updated Column' },
|
||||
{ type: SettingsTypes.flag, key: SETTINGS_KEYS.SHOW_DESCRIPTION_COLUMN, name: 'Show Description Column' },
|
||||
{ type: SettingsTypes.flag, key: SETTINGS_KEYS.SHOW_CHARACTER_COLUMN, name: 'Show Character Column' },
|
||||
{ type: SettingsTypes.flag, key: SETTINGS_KEYS.LAZY_DELETE_SIGNATURES, name: 'Lazy Delete Signatures' },
|
||||
{ type: SettingsTypes.flag, key: SETTINGS_KEYS.KEEP_LAZY_DELETE, name: 'Keep "Lazy Delete" Enabled' },
|
||||
{
|
||||
type: SettingsTypes.flag,
|
||||
key: SETTINGS_KEYS.SHOW_CHARACTER_PORTRAIT,
|
||||
name: 'Show Character Portrait in Tooltip',
|
||||
},
|
||||
{ type: SettingsTypes.flag, key: SETTINGS_KEYS.COLOR_BY_TYPE, name: 'Color Signatures by Type' },
|
||||
],
|
||||
uiOther: [
|
||||
{
|
||||
type: SettingsTypes.dropdown,
|
||||
key: SETTINGS_KEYS.DELETION_TIMING,
|
||||
name: 'Deletion Timing',
|
||||
options: [
|
||||
{ value: SIGNATURES_DELETION_TIMING.IMMEDIATE, label: '0s' },
|
||||
{ value: SIGNATURES_DELETION_TIMING.DEFAULT, label: '10s' },
|
||||
{ value: SIGNATURES_DELETION_TIMING.EXTENDED, label: '30s' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const SETTINGS_VALUES: SignatureSettingsType = {
|
||||
[SETTINGS_KEYS.SHOW_UPDATED_COLUMN]: true,
|
||||
[SETTINGS_KEYS.SHOW_DESCRIPTION_COLUMN]: true,
|
||||
[SETTINGS_KEYS.SHOW_CHARACTER_COLUMN]: true,
|
||||
[SETTINGS_KEYS.LAZY_DELETE_SIGNATURES]: true,
|
||||
[SETTINGS_KEYS.KEEP_LAZY_DELETE]: false,
|
||||
[SETTINGS_KEYS.DELETION_TIMING]: SIGNATURES_DELETION_TIMING.DEFAULT,
|
||||
[SETTINGS_KEYS.COLOR_BY_TYPE]: true,
|
||||
[SETTINGS_KEYS.SHOW_CHARACTER_PORTRAIT]: true,
|
||||
|
||||
[SETTINGS_KEYS.COSMIC_ANOMALY]: true,
|
||||
[SETTINGS_KEYS.COSMIC_SIGNATURE]: true,
|
||||
[SETTINGS_KEYS.DEPLOYABLE]: true,
|
||||
[SETTINGS_KEYS.STRUCTURE]: true,
|
||||
[SETTINGS_KEYS.STARBASE]: true,
|
||||
[SETTINGS_KEYS.SHIP]: true,
|
||||
[SETTINGS_KEYS.DRONE]: true,
|
||||
|
||||
[SETTINGS_KEYS.WORMHOLE]: true,
|
||||
[SETTINGS_KEYS.RELIC_SITE]: true,
|
||||
[SETTINGS_KEYS.DATA_SITE]: true,
|
||||
[SETTINGS_KEYS.ORE_SITE]: true,
|
||||
[SETTINGS_KEYS.GAS_SITE]: true,
|
||||
[SETTINGS_KEYS.COMBAT_SITE]: true,
|
||||
};
|
||||
|
||||
export const SIGNATURE_DELETION_TIMEOUTS: SignatureDeletionTimingType = {
|
||||
[SIGNATURES_DELETION_TIMING.DEFAULT]: 10_000,
|
||||
[SIGNATURES_DELETION_TIMING.IMMEDIATE]: 0,
|
||||
[SIGNATURES_DELETION_TIMING.EXTENDED]: 30_000,
|
||||
};
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { ExtendedSystemSignature, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { FINAL_DURATION_MS } from '../constants';
|
||||
|
||||
export interface ExtendedSystemSignature extends SystemSignature {
|
||||
pendingDeletion?: boolean;
|
||||
pendingAddition?: boolean;
|
||||
pendingUntil?: number;
|
||||
}
|
||||
|
||||
export function prepareUpdatePayload(
|
||||
systemId: string,
|
||||
added: ExtendedSystemSignature[],
|
||||
@@ -55,40 +49,56 @@ export function schedulePendingAdditionForSig(
|
||||
);
|
||||
}
|
||||
|
||||
export function mergeLocalPendingAdditions(
|
||||
export function mergeLocalPending(
|
||||
pendingMapRef: React.MutableRefObject<Record<string, ExtendedSystemSignature>>,
|
||||
serverSigs: ExtendedSystemSignature[],
|
||||
localSigs: ExtendedSystemSignature[],
|
||||
): ExtendedSystemSignature[] {
|
||||
const now = Date.now();
|
||||
const pendingAdditions = localSigs.filter(sig => sig.pendingAddition && sig.pendingUntil && sig.pendingUntil > now);
|
||||
const pendingDeletions = Object.values(pendingMapRef.current).filter(
|
||||
sig => sig.pendingDeletion && sig.pendingUntil && sig.pendingUntil > now,
|
||||
);
|
||||
const mergedMap = new Map<string, ExtendedSystemSignature>();
|
||||
serverSigs.forEach(sig => mergedMap.set(sig.eve_id, sig));
|
||||
pendingAdditions.forEach(sig => {
|
||||
if (!mergedMap.has(sig.eve_id)) {
|
||||
|
||||
pendingDeletions.forEach(sig => {
|
||||
if (mergedMap.has(sig.eve_id)) {
|
||||
mergedMap.set(sig.eve_id, sig);
|
||||
}
|
||||
});
|
||||
return Array.from(mergedMap.values());
|
||||
}
|
||||
|
||||
export function scheduleLazyDeletionTimers(
|
||||
toRemove: ExtendedSystemSignature[],
|
||||
setPendingMap: React.Dispatch<React.SetStateAction<Record<string, { finalUntil: number; finalTimeoutId: number }>>>,
|
||||
finalizeRemoval: (sig: ExtendedSystemSignature) => Promise<void>,
|
||||
export function scheduleLazyTimers(
|
||||
signatures: ExtendedSystemSignature[],
|
||||
pendingMapRef: React.MutableRefObject<Record<string, ExtendedSystemSignature>>,
|
||||
finalizeFn: (sig: ExtendedSystemSignature) => Promise<void>,
|
||||
finalDuration = FINAL_DURATION_MS,
|
||||
) {
|
||||
const now = Date.now();
|
||||
toRemove.forEach(sig => {
|
||||
signatures.forEach(sig => {
|
||||
const finalTimeoutId = window.setTimeout(async () => {
|
||||
await finalizeRemoval(sig);
|
||||
await finalizeFn(sig);
|
||||
}, finalDuration);
|
||||
|
||||
setPendingMap(prev => ({
|
||||
...prev,
|
||||
pendingMapRef.current = {
|
||||
...pendingMapRef.current,
|
||||
[sig.eve_id]: {
|
||||
finalUntil: now + finalDuration,
|
||||
...sig,
|
||||
finalTimeoutId,
|
||||
},
|
||||
}));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export const calculateTimeRemaining = (pendingSigs: SystemSignature[]) => {
|
||||
const now = Date.now();
|
||||
let minTime: number | undefined = undefined;
|
||||
|
||||
pendingSigs.forEach(sig => {
|
||||
const extendedSig = sig as unknown as { pendingUntil?: number };
|
||||
if (extendedSig.pendingUntil && (minTime === undefined || extendedSig.pendingUntil - now < minTime)) {
|
||||
minTime = extendedSig.pendingUntil - now;
|
||||
}
|
||||
});
|
||||
|
||||
return minTime && minTime > 0 ? minTime : undefined;
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import { getState } from './getState';
|
||||
|
||||
/**
|
||||
* Compare two lists of signatures and return which are added, updated, or removed.
|
||||
*
|
||||
*
|
||||
* @param oldSignatures existing signatures (in memory or from server)
|
||||
* @param newSignatures newly parsed or incoming signatures from user input
|
||||
* @param updateOnly if true, do NOT remove old signatures not found in newSignatures
|
||||
|
||||
@@ -12,11 +12,11 @@ export const getRowBackgroundColor = (date: Date | undefined): string => {
|
||||
const diff = currentDate.getTime() + currentDate.getTimezoneOffset() * TIME_ONE_MINUTE - date.getTime();
|
||||
|
||||
if (diff < TIME_ONE_MINUTE) {
|
||||
return 'bg-lime-600/50 transition hover:bg-lime-600/60';
|
||||
return '[&_.ssc-header]:text-amber-300 [&_.ssc-header]:hover:text-amber-200 [&_.ssc-header]:font-bold';
|
||||
}
|
||||
|
||||
if (diff < TIME_TEN_MINUTES) {
|
||||
return 'bg-lime-700/40 transition hover:bg-lime-700/50';
|
||||
return '[&_.ssc-header]:text-amber-500 [&_.ssc-header]:hover:text-amber-500 [&_.ssc-header]:font-bold';
|
||||
}
|
||||
|
||||
return '';
|
||||
|
||||
@@ -1,34 +1,4 @@
|
||||
.pendingDeletion {
|
||||
background-color: #f87171;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
|
||||
.pendingDeletion td {
|
||||
background-color: #f87171;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.pendingDeletion {
|
||||
background-color: #f87171;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.Table thead tr {
|
||||
font-size: 12px !important;
|
||||
line-height: 1.333;
|
||||
}
|
||||
|
||||
.TableRowCompact {
|
||||
font-size: 12px !important;
|
||||
line-height: 1.333;
|
||||
}
|
||||
|
||||
.Table td {
|
||||
padding: 2px;
|
||||
height: 25px;
|
||||
border: 1px solid #383838;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,20 +1,48 @@
|
||||
import clsx from 'clsx';
|
||||
import { ExtendedSystemSignature } from './contentHelpers';
|
||||
import { ExtendedSystemSignature, SignatureGroup } from '@/hooks/Mapper/types';
|
||||
import { getRowBackgroundColor } from './getRowBackgroundColor';
|
||||
import classes from './rowStyles.module.scss';
|
||||
|
||||
export function getSignatureRowClass(
|
||||
row: ExtendedSystemSignature,
|
||||
selectedSignatures: ExtendedSystemSignature[],
|
||||
colorByType?: boolean,
|
||||
): string {
|
||||
const isSelected = selectedSignatures.some(s => s.eve_id === row.eve_id);
|
||||
|
||||
return clsx(
|
||||
const baseCls = [
|
||||
classes.TableRowCompact,
|
||||
'p-selectable-row',
|
||||
isSelected && 'bg-amber-500/50 hover:bg-amber-500/70 transition duration-200',
|
||||
!isSelected && row.pendingDeletion && classes.pendingDeletion,
|
||||
!isSelected && getRowBackgroundColor(row.inserted_at ? new Date(row.inserted_at) : undefined),
|
||||
'hover:bg-purple-400/20 transition duration-200',
|
||||
);
|
||||
getRowBackgroundColor(row.inserted_at ? new Date(row.inserted_at) : undefined),
|
||||
'transition duration-200 my-2 hover:bg-purple-400/20',
|
||||
];
|
||||
|
||||
if (isSelected) {
|
||||
return clsx([...baseCls, 'bg-violet-400/40 hover:bg-violet-300/40']);
|
||||
}
|
||||
|
||||
if (row.pendingDeletion) {
|
||||
return clsx([...baseCls, 'bg-red-400/40 hover:bg-red-400/50']);
|
||||
}
|
||||
|
||||
// Apply color by type styling if enabled
|
||||
if (colorByType) {
|
||||
switch (row.group) {
|
||||
case SignatureGroup.CosmicSignature:
|
||||
return clsx([...baseCls, '[&_td:nth-child(-n+3)]:text-rose-400 [&_td:nth-child(-n+3)]:hover:text-rose-300']);
|
||||
case SignatureGroup.Wormhole:
|
||||
return clsx([...baseCls, '[&_td:nth-child(-n+3)]:text-sky-300 [&_td:nth-child(-n+3)]:hover:text-sky-200']);
|
||||
case SignatureGroup.CombatSite:
|
||||
case SignatureGroup.RelicSite:
|
||||
case SignatureGroup.DataSite:
|
||||
case SignatureGroup.GasSite:
|
||||
case SignatureGroup.OreSite:
|
||||
return clsx([...baseCls, '[&_td:nth-child(-n+4)]:text-lime-400 [&_td:nth-child(-n+4)]:hover:text-lime-300']);
|
||||
}
|
||||
|
||||
// Default for color by type - apply same color as CosmicSignature (red) and small text size
|
||||
return clsx([...baseCls, '[&_td:nth-child(-n+3)]:text-rose-400/100']);
|
||||
}
|
||||
|
||||
// Original styling when color by type is disabled
|
||||
return clsx(...baseCls);
|
||||
}
|
||||
|
||||
@@ -1,26 +1,38 @@
|
||||
import { ExtendedSystemSignature } from '../helpers/contentHelpers';
|
||||
import { SignatureSettingsType } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
import { ExtendedSystemSignature } from '@/hooks/Mapper/types';
|
||||
|
||||
export interface UseSystemSignaturesDataProps {
|
||||
systemId: string;
|
||||
settings: { key: string; value: boolean }[];
|
||||
settings: SignatureSettingsType;
|
||||
hideLinkedSignatures?: boolean;
|
||||
onCountChange?: (count: number) => void;
|
||||
onPendingChange?: (pending: ExtendedSystemSignature[], undo: () => void) => void;
|
||||
onPendingChange?: (
|
||||
pending: React.MutableRefObject<Record<string, ExtendedSystemSignature>>,
|
||||
undo: () => void,
|
||||
) => void;
|
||||
onLazyDeleteChange?: (value: boolean) => void;
|
||||
deletionTiming?: number;
|
||||
}
|
||||
|
||||
export interface UseFetchingParams {
|
||||
systemId: string;
|
||||
signaturesRef: React.MutableRefObject<ExtendedSystemSignature[]>;
|
||||
setSignatures: React.Dispatch<React.SetStateAction<ExtendedSystemSignature[]>>;
|
||||
localPendingDeletions: ExtendedSystemSignature[];
|
||||
pendingDeletionMapRef: React.MutableRefObject<Record<string, ExtendedSystemSignature>>;
|
||||
}
|
||||
|
||||
export interface UsePendingDeletionParams {
|
||||
systemId: string;
|
||||
deletionTiming?: number;
|
||||
setSignatures: React.Dispatch<React.SetStateAction<ExtendedSystemSignature[]>>;
|
||||
onPendingChange?: (
|
||||
pending: React.MutableRefObject<Record<string, ExtendedSystemSignature>>,
|
||||
undo: () => void,
|
||||
) => void;
|
||||
}
|
||||
|
||||
export interface UsePendingAdditionParams {
|
||||
systemId: string;
|
||||
setSignatures: React.Dispatch<React.SetStateAction<ExtendedSystemSignature[]>>;
|
||||
deletionTiming?: number;
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import { useState, useCallback, useRef } from 'react';
|
||||
import { ExtendedSystemSignature, schedulePendingAdditionForSig } from '../helpers/contentHelpers';
|
||||
import { UsePendingAdditionParams } from './types';
|
||||
import { FINAL_DURATION_MS } from '../constants';
|
||||
|
||||
export function usePendingAdditions({ setSignatures }: UsePendingAdditionParams) {
|
||||
const [pendingUndoAdditions, setPendingUndoAdditions] = useState<ExtendedSystemSignature[]>([]);
|
||||
const pendingAdditionMapRef = useRef<Record<string, { finalUntil: number; finalTimeoutId: number }>>({});
|
||||
|
||||
const processAddedSignatures = useCallback(
|
||||
(added: ExtendedSystemSignature[]) => {
|
||||
if (!added.length) return;
|
||||
const now = Date.now();
|
||||
setSignatures(prev => [
|
||||
...prev,
|
||||
...added.map(sig => ({
|
||||
...sig,
|
||||
pendingAddition: true,
|
||||
pendingUntil: now + FINAL_DURATION_MS,
|
||||
})),
|
||||
]);
|
||||
added.forEach(sig => {
|
||||
schedulePendingAdditionForSig(
|
||||
sig,
|
||||
FINAL_DURATION_MS,
|
||||
setSignatures,
|
||||
pendingAdditionMapRef,
|
||||
setPendingUndoAdditions,
|
||||
);
|
||||
});
|
||||
},
|
||||
[setSignatures],
|
||||
);
|
||||
|
||||
const clearPendingAdditions = useCallback(() => {
|
||||
Object.values(pendingAdditionMapRef.current).forEach(({ finalTimeoutId }) => {
|
||||
clearTimeout(finalTimeoutId);
|
||||
});
|
||||
pendingAdditionMapRef.current = {};
|
||||
setSignatures(prev =>
|
||||
prev.map(x => (x.pendingAddition ? { ...x, pendingAddition: false, pendingUntil: undefined } : x)),
|
||||
);
|
||||
setPendingUndoAdditions([]);
|
||||
}, [setSignatures]);
|
||||
|
||||
return {
|
||||
pendingUndoAdditions,
|
||||
setPendingUndoAdditions,
|
||||
pendingAdditionMapRef,
|
||||
processAddedSignatures,
|
||||
clearPendingAdditions,
|
||||
};
|
||||
}
|
||||
@@ -1,16 +1,22 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useCallback, useRef, useEffect } from 'react';
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
||||
import { ExtendedSystemSignature, prepareUpdatePayload, scheduleLazyDeletionTimers } from '../helpers';
|
||||
import { prepareUpdatePayload, scheduleLazyTimers } from '../helpers';
|
||||
import { UsePendingDeletionParams } from './types';
|
||||
import { FINAL_DURATION_MS } from '../constants';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { ExtendedSystemSignature } from '@/hooks/Mapper/types';
|
||||
|
||||
export function usePendingDeletions({ systemId, setSignatures }: UsePendingDeletionParams) {
|
||||
export function usePendingDeletions({
|
||||
systemId,
|
||||
setSignatures,
|
||||
deletionTiming,
|
||||
onPendingChange,
|
||||
}: UsePendingDeletionParams) {
|
||||
const { outCommand } = useMapRootState();
|
||||
const [localPendingDeletions, setLocalPendingDeletions] = useState<ExtendedSystemSignature[]>([]);
|
||||
const [pendingDeletionMap, setPendingDeletionMap] = useState<
|
||||
Record<string, { finalUntil: number; finalTimeoutId: number }>
|
||||
>({});
|
||||
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(
|
||||
async (
|
||||
@@ -19,60 +25,70 @@ export function usePendingDeletions({ systemId, setSignatures }: UsePendingDelet
|
||||
updated: ExtendedSystemSignature[],
|
||||
) => {
|
||||
if (!removed.length) return;
|
||||
|
||||
// If deletion timing is 0, immediately delete without pending state
|
||||
if (finalDuration === 0) {
|
||||
await outCommand({
|
||||
type: OutCommand.updateSignatures,
|
||||
data: prepareUpdatePayload(systemId, added, updated, removed),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const processedRemoved = removed.map(r => ({
|
||||
...r,
|
||||
pendingDeletion: true,
|
||||
pendingAddition: false,
|
||||
pendingUntil: now + FINAL_DURATION_MS,
|
||||
pendingUntil: now + finalDuration,
|
||||
}));
|
||||
setLocalPendingDeletions(prev => [...prev, ...processedRemoved]);
|
||||
pendingDeletionMapRef.current = {
|
||||
...pendingDeletionMapRef.current,
|
||||
...processedRemoved.reduce((acc: any, sig) => {
|
||||
acc[sig.eve_id] = sig;
|
||||
return acc;
|
||||
}, {}),
|
||||
};
|
||||
|
||||
outCommand({
|
||||
type: OutCommand.updateSignatures,
|
||||
data: prepareUpdatePayload(systemId, added, updated, []),
|
||||
});
|
||||
onPendingChange?.(pendingDeletionMapRef, clearPendingDeletions);
|
||||
|
||||
setSignatures(prev =>
|
||||
prev.map(sig => {
|
||||
if (processedRemoved.find(r => r.eve_id === sig.eve_id)) {
|
||||
return { ...sig, pendingDeletion: true, pendingUntil: now + FINAL_DURATION_MS };
|
||||
return { ...sig, pendingDeletion: true, pendingUntil: now + finalDuration };
|
||||
}
|
||||
return sig;
|
||||
}),
|
||||
);
|
||||
|
||||
scheduleLazyDeletionTimers(
|
||||
scheduleLazyTimers(
|
||||
processedRemoved,
|
||||
setPendingDeletionMap,
|
||||
pendingDeletionMapRef,
|
||||
async sig => {
|
||||
await outCommand({
|
||||
type: OutCommand.updateSignatures,
|
||||
data: prepareUpdatePayload(systemId, [], [], [sig]),
|
||||
});
|
||||
setLocalPendingDeletions(prev => prev.filter(x => x.eve_id !== sig.eve_id));
|
||||
delete pendingDeletionMapRef.current[sig.eve_id];
|
||||
setSignatures(prev => prev.filter(x => x.eve_id !== sig.eve_id));
|
||||
onPendingChange?.(pendingDeletionMapRef, clearPendingDeletions);
|
||||
},
|
||||
FINAL_DURATION_MS,
|
||||
finalDuration,
|
||||
);
|
||||
},
|
||||
[systemId, outCommand, setSignatures],
|
||||
[systemId, outCommand, finalDuration],
|
||||
);
|
||||
|
||||
const clearPendingDeletions = useCallback(() => {
|
||||
Object.values(pendingDeletionMap).forEach(({ finalTimeoutId }) => clearTimeout(finalTimeoutId));
|
||||
setPendingDeletionMap({});
|
||||
setSignatures(prev =>
|
||||
prev.map(x => (x.pendingDeletion ? { ...x, pendingDeletion: false, pendingUntil: undefined } : x)),
|
||||
);
|
||||
setLocalPendingDeletions([]);
|
||||
}, [pendingDeletionMap, setSignatures]);
|
||||
Object.values(pendingDeletionMapRef.current).forEach(({ finalTimeoutId }) => {
|
||||
clearTimeout(finalTimeoutId);
|
||||
});
|
||||
pendingDeletionMapRef.current = {};
|
||||
setSignatures(prev => prev.map(x => (x.pendingDeletion ? { ...x, pendingDeletion: false } : x)));
|
||||
onPendingChange?.(pendingDeletionMapRef, clearPendingDeletions);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
localPendingDeletions,
|
||||
setLocalPendingDeletions,
|
||||
pendingDeletionMap,
|
||||
setPendingDeletionMap,
|
||||
pendingDeletionMapRef,
|
||||
processRemovedSignatures,
|
||||
clearPendingDeletions,
|
||||
};
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import { useCallback } from 'react';
|
||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { ExtendedSystemSignature, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
||||
import { ExtendedSystemSignature, prepareUpdatePayload, getActualSigs, mergeLocalPendingAdditions } from '../helpers';
|
||||
import { prepareUpdatePayload, getActualSigs, mergeLocalPending } from '../helpers';
|
||||
import { UseFetchingParams } from './types';
|
||||
import { FINAL_DURATION_MS } from '../constants';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
|
||||
export function useSignatureFetching({
|
||||
export const useSignatureFetching = ({
|
||||
systemId,
|
||||
signaturesRef,
|
||||
setSignatures,
|
||||
localPendingDeletions,
|
||||
}: UseFetchingParams) {
|
||||
pendingDeletionMapRef,
|
||||
}: UseFetchingParams) => {
|
||||
const {
|
||||
data: { characters },
|
||||
outCommand,
|
||||
@@ -22,9 +21,6 @@ export function useSignatureFetching({
|
||||
setSignatures([]);
|
||||
return;
|
||||
}
|
||||
if (localPendingDeletions.length) {
|
||||
return;
|
||||
}
|
||||
const resp = await outCommand({
|
||||
type: OutCommand.getSignatures,
|
||||
data: { system_id: systemId },
|
||||
@@ -36,8 +32,8 @@ export function useSignatureFetching({
|
||||
character_name: characters.find(c => c.eve_id === s.character_eve_id)?.name,
|
||||
})) as ExtendedSystemSignature[];
|
||||
|
||||
setSignatures(prev => mergeLocalPendingAdditions(extended, prev));
|
||||
}, [characters, systemId, localPendingDeletions, outCommand, setSignatures]);
|
||||
setSignatures(() => mergeLocalPending(pendingDeletionMapRef, extended));
|
||||
}, [characters, systemId, outCommand]);
|
||||
|
||||
const handleUpdateSignatures = useCallback(
|
||||
async (newList: ExtendedSystemSignature[], updateOnly: boolean, skipUpdateUntouched?: boolean) => {
|
||||
@@ -48,28 +44,16 @@ export function useSignatureFetching({
|
||||
skipUpdateUntouched,
|
||||
);
|
||||
|
||||
if (added.length > 0) {
|
||||
const now = Date.now();
|
||||
setSignatures(prev => [
|
||||
...prev,
|
||||
...added.map(a => ({
|
||||
...a,
|
||||
pendingAddition: true,
|
||||
pendingUntil: now + FINAL_DURATION_MS,
|
||||
})),
|
||||
]);
|
||||
}
|
||||
|
||||
await outCommand({
|
||||
type: OutCommand.updateSignatures,
|
||||
data: prepareUpdatePayload(systemId, added, updated, removed),
|
||||
});
|
||||
},
|
||||
[systemId, outCommand, signaturesRef, setSignatures],
|
||||
[systemId, outCommand, signaturesRef],
|
||||
);
|
||||
|
||||
return {
|
||||
handleGetSignatures,
|
||||
handleUpdateSignatures,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,74 +1,68 @@
|
||||
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||
import { parseSignatures } from '@/hooks/Mapper/helpers';
|
||||
import { Commands, ExtendedSystemSignature, SignatureKind } from '@/hooks/Mapper/types';
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import useRefState from 'react-usestateref';
|
||||
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||
import { Commands, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
||||
import { parseSignatures } from '@/hooks/Mapper/helpers';
|
||||
import {
|
||||
KEEP_LAZY_DELETE_SETTING,
|
||||
LAZY_DELETE_SIGNATURES_SETTING,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets';
|
||||
import { ExtendedSystemSignature, getActualSigs, mergeLocalPendingAdditions } from '../helpers';
|
||||
import { useSignatureFetching } from './useSignatureFetching';
|
||||
import { usePendingAdditions } from './usePendingAdditions';
|
||||
import { usePendingDeletions } from './usePendingDeletions';
|
||||
import { UseSystemSignaturesDataProps } from './types';
|
||||
import { TIME_ONE_DAY, TIME_ONE_WEEK } from '../constants';
|
||||
import { SignatureGroup } from '@/hooks/Mapper/types';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
|
||||
export function useSystemSignaturesData({
|
||||
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 = ({
|
||||
systemId,
|
||||
settings,
|
||||
onCountChange,
|
||||
onPendingChange,
|
||||
onLazyDeleteChange,
|
||||
}: UseSystemSignaturesDataProps) {
|
||||
deletionTiming,
|
||||
}: UseSystemSignaturesDataProps) => {
|
||||
const { outCommand } = useMapRootState();
|
||||
const [signatures, setSignatures, signaturesRef] = useRefState<ExtendedSystemSignature[]>([]);
|
||||
const [selectedSignatures, setSelectedSignatures] = useState<ExtendedSystemSignature[]>([]);
|
||||
|
||||
const { localPendingDeletions, setLocalPendingDeletions, processRemovedSignatures, clearPendingDeletions } =
|
||||
usePendingDeletions({
|
||||
systemId,
|
||||
setSignatures,
|
||||
});
|
||||
const { pendingUndoAdditions, setPendingUndoAdditions, processAddedSignatures, clearPendingAdditions } =
|
||||
usePendingAdditions({
|
||||
setSignatures,
|
||||
});
|
||||
const { pendingDeletionMapRef, processRemovedSignatures, clearPendingDeletions } = usePendingDeletions({
|
||||
systemId,
|
||||
setSignatures,
|
||||
deletionTiming,
|
||||
onPendingChange,
|
||||
});
|
||||
|
||||
const { handleGetSignatures, handleUpdateSignatures } = useSignatureFetching({
|
||||
systemId,
|
||||
signaturesRef,
|
||||
setSignatures,
|
||||
localPendingDeletions,
|
||||
pendingDeletionMapRef,
|
||||
});
|
||||
|
||||
const handlePaste = useCallback(
|
||||
async (clipboardString: string) => {
|
||||
const lazyDeleteValue = settings.find(s => s.key === LAZY_DELETE_SIGNATURES_SETTING)?.value ?? false;
|
||||
const lazyDeleteValue = settings[SETTINGS_KEYS.LAZY_DELETE_SIGNATURES] as boolean;
|
||||
|
||||
const incomingSignatures = parseSignatures(
|
||||
clipboardString,
|
||||
settings.map(s => s.key),
|
||||
Object.keys(settings).filter(skey => skey in SignatureKind),
|
||||
) as ExtendedSystemSignature[];
|
||||
|
||||
const current = signaturesRef.current;
|
||||
if (incomingSignatures.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentNonPending = lazyDeleteValue
|
||||
? current.filter(sig => !sig.pendingDeletion)
|
||||
: current.filter(sig => !sig.pendingDeletion && !sig.pendingAddition);
|
||||
? signaturesRef.current.filter(sig => !sig.pendingDeletion)
|
||||
: signaturesRef.current.filter(sig => !sig.pendingDeletion || !sig.pendingAddition);
|
||||
|
||||
const { added, updated, removed } = getActualSigs(currentNonPending, incomingSignatures, !lazyDeleteValue, true);
|
||||
|
||||
if (added.length > 0) {
|
||||
processAddedSignatures(added);
|
||||
}
|
||||
|
||||
if (removed.length > 0) {
|
||||
await processRemovedSignatures(removed, added, updated);
|
||||
} else {
|
||||
const resp = await outCommand({
|
||||
}
|
||||
|
||||
if (updated.length !== 0 || added.length !== 0) {
|
||||
await outCommand({
|
||||
type: OutCommand.updateSignatures,
|
||||
data: {
|
||||
system_id: systemId,
|
||||
@@ -77,34 +71,14 @@ export function useSystemSignaturesData({
|
||||
removed: [],
|
||||
},
|
||||
});
|
||||
if (resp) {
|
||||
const finalSigs = (resp.signatures ?? []) as SystemSignature[];
|
||||
setSignatures(prev =>
|
||||
mergeLocalPendingAdditions(
|
||||
finalSigs.map(x => ({ ...x })),
|
||||
prev,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const keepLazy = settings.find(s => s.key === KEEP_LAZY_DELETE_SETTING)?.value ?? false;
|
||||
const keepLazy = settings[SETTINGS_KEYS.KEEP_LAZY_DELETE] as boolean;
|
||||
if (lazyDeleteValue && !keepLazy) {
|
||||
setTimeout(() => {
|
||||
onLazyDeleteChange?.(false);
|
||||
}, 0);
|
||||
onLazyDeleteChange?.(false);
|
||||
}
|
||||
},
|
||||
[
|
||||
settings,
|
||||
signaturesRef,
|
||||
processAddedSignatures,
|
||||
processRemovedSignatures,
|
||||
outCommand,
|
||||
systemId,
|
||||
setSignatures,
|
||||
onLazyDeleteChange,
|
||||
],
|
||||
[settings, signaturesRef, processRemovedSignatures, outCommand, systemId, onLazyDeleteChange],
|
||||
);
|
||||
|
||||
const handleDeleteSelected = useCallback(async () => {
|
||||
@@ -114,7 +88,7 @@ export function useSystemSignaturesData({
|
||||
|
||||
await handleUpdateSignatures(finalList, false, true);
|
||||
setSelectedSignatures([]);
|
||||
}, [selectedSignatures, signatures, handleUpdateSignatures]);
|
||||
}, [selectedSignatures, signatures]);
|
||||
|
||||
const handleSelectAll = useCallback(() => {
|
||||
setSelectedSignatures(signatures);
|
||||
@@ -122,57 +96,7 @@ export function useSystemSignaturesData({
|
||||
|
||||
const undoPending = useCallback(() => {
|
||||
clearPendingDeletions();
|
||||
clearPendingAdditions();
|
||||
setSignatures(prev =>
|
||||
prev.map(x => (x.pendingDeletion ? { ...x, pendingDeletion: false, pendingUntil: undefined } : x)),
|
||||
);
|
||||
|
||||
if (pendingUndoAdditions.length) {
|
||||
pendingUndoAdditions.forEach(async sig => {
|
||||
await outCommand({
|
||||
type: OutCommand.updateSignatures,
|
||||
data: {
|
||||
system_id: systemId,
|
||||
added: [],
|
||||
updated: [],
|
||||
removed: [sig],
|
||||
},
|
||||
});
|
||||
});
|
||||
setSignatures(prev => prev.filter(x => !pendingUndoAdditions.some(u => u.eve_id === x.eve_id)));
|
||||
setPendingUndoAdditions([]);
|
||||
}
|
||||
setLocalPendingDeletions([]);
|
||||
}, [
|
||||
clearPendingDeletions,
|
||||
clearPendingAdditions,
|
||||
pendingUndoAdditions,
|
||||
setPendingUndoAdditions,
|
||||
setLocalPendingDeletions,
|
||||
setSignatures,
|
||||
outCommand,
|
||||
systemId,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const combined = [...localPendingDeletions, ...pendingUndoAdditions];
|
||||
onPendingChange?.(combined, undoPending);
|
||||
}, [localPendingDeletions, pendingUndoAdditions, onPendingChange, undoPending]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!systemId) return;
|
||||
const now = Date.now();
|
||||
const oldOnes = signaturesRef.current.filter(sig => {
|
||||
if (!sig.inserted_at) return false;
|
||||
const inserted = new Date(sig.inserted_at).getTime();
|
||||
const threshold = sig.group === SignatureGroup.Wormhole ? TIME_ONE_DAY : TIME_ONE_WEEK;
|
||||
return now - inserted > threshold;
|
||||
});
|
||||
if (oldOnes.length) {
|
||||
const remain = signaturesRef.current.filter(x => !oldOnes.includes(x));
|
||||
handleUpdateSignatures(remain, false, true);
|
||||
}
|
||||
}, [systemId, handleUpdateSignatures, signaturesRef]);
|
||||
}, [clearPendingDeletions]);
|
||||
|
||||
useMapEventListener(event => {
|
||||
if (event.name === Commands.signaturesUpdated && String(event.data) === String(systemId)) {
|
||||
@@ -184,14 +108,15 @@ export function useSystemSignaturesData({
|
||||
useEffect(() => {
|
||||
if (!systemId) {
|
||||
setSignatures([]);
|
||||
undoPending();
|
||||
return;
|
||||
}
|
||||
handleGetSignatures();
|
||||
}, [systemId, handleGetSignatures, setSignatures]);
|
||||
}, [systemId]);
|
||||
|
||||
useEffect(() => {
|
||||
onCountChange?.(signatures.length);
|
||||
}, [signatures, onCountChange]);
|
||||
}, [signatures]);
|
||||
|
||||
return {
|
||||
signatures,
|
||||
@@ -201,4 +126,4 @@ export function useSystemSignaturesData({
|
||||
handleSelectAll,
|
||||
handlePaste,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user